Merge pull request #48 from arionum/assets

Assets and hf
This commit is contained in:
arionum
2019-09-14 02:04:34 +03:00
committed by GitHub
9 changed files with 1299 additions and 132 deletions

68
api.php
View File

@@ -70,7 +70,7 @@ header('Content-Type: application/json');
use Arionum\Blacklist;
require_once __DIR__.'/include/init.inc.php';
error_reporting(0);
$ip = san_ip($_SERVER['REMOTE_ADDR']);
$ip = filter_var($ip, FILTER_VALIDATE_IP);
@@ -419,6 +419,8 @@ if ($q == "getAddress") {
$version = 1;
}
if ($version==1) {
if (!$acc->valid($dst)) {
api_err("Invalid destination address");
@@ -435,7 +437,8 @@ if ($q == "getAddress") {
}
}
$public_key = san($data['public_key']);
if (!$acc->valid_key($public_key)) {
@@ -446,6 +449,9 @@ if ($q == "getAddress") {
api_err("Blacklisted account");
}
}
$private_key = san($data['private_key']);
if (!$acc->valid_key($private_key)) {
api_err("Invalid private key");
@@ -466,6 +472,7 @@ if ($q == "getAddress") {
api_err("Invalid Date");
}
$message=$data['message'];
if (strlen($message) > 128) {
api_err("The message must be less than 128 chars");
@@ -480,10 +487,11 @@ if ($q == "getAddress") {
if ($fee > 10 && $current['height'] > 10800) {
$fee = 10; //10800
}
if ($val < 0.00000001) {
if ($val < 0) {
api_err("Invalid value");
}
// set alias
if ($version==3) {
$fee=10;
@@ -519,6 +527,9 @@ if ($q == "getAddress") {
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);
@@ -547,11 +558,11 @@ if ($q == "getAddress") {
if (!$trx->check($transaction)) {
api_err("Transaction signature failed");
}
$res = $db->single("SELECT COUNT(1) FROM mempool WHERE id=:id", [":id" => $hash]);
if ($res != 0) {
api_err("The transaction is already in mempool");
@@ -771,6 +782,55 @@ if ($q == "getAddress") {
}
}
api_echo(true);
} elseif ($q === "assetBalance"){
$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);
$r=$db->run("SELECT asset, alias, assets_balance.balance FROM assets_balance LEFT JOIN accounts ON accounts.id=assets_balance.asset WHERE assets_balance.account=:account LIMIT 1000",[":account"=>$account]);
api_echo($r);
} elseif ($q === "asset-orders"){
$asset = san($data['asset']);
$account = san($data['account']);
if(empty($asset)&&empty($account)){
api_err("An asset or an account are necessary");
}
$whr="status=0";
$bind=[];
if(!empty($asset)){
$whr.=" AND asset=:asset ";
$bind[':asset']=$asset;
}
if(!empty($account)){
$whr.=" AND account=:account ";
$bind[':account']=$account;
}
$r=$db->run("SELECT * FROM assets_market WHERE $whr", $bind);
api_echo($r);
} elseif ($q === "assets"){
$asset = san($data['asset']);
$whr="";
$bind=[];
if(!empty($asset)){
$whr.=" WHERE assets.id=:asset ";
$bind[':asset']=$asset;
}
$r=$db->run("SELECT assets.*, accounts.alias, accounts.balance FROM assets LEFT JOIN accounts ON accounts.id=assets.id $whr LIMIT 1000",$bind);
api_echo($r);
} else {
api_err("Invalid request");
}

View File

@@ -138,7 +138,7 @@ class Account
if ($orig!=$id) {
return false;
}
// making sure the same alias can only be used in one place
if ($db->single("SELECT COUNT(1) FROM accounts WHERE alias=:alias", [":alias"=>$id])==0) {
return true;
} else {

View File

@@ -2,6 +2,33 @@
class Block
{
public function add_log($hash, $log)
{
global $db;
$hash=san($hash);
//$json=["table"=>"masternode", "key"=>"public_key","id"=>$x['public_key'], "vals"=>['ip'=>$current_ip] ];
$db->run("INSERT into logs SET block=:id, json=:json", [':id'=>$hash, ":json"=>json_encode($log)]);
}
public function reverse_log($hash)
{
global $db;
$r=$db->run("SELECT json, id FROM logs WHERE block=:id ORDER by id DESC", [":id"=>$hash]);
foreach ($r as $json) {
$old=json_decode($json['json'], true);
if ($old!==false&&is_array($old)) {
//making sure there's no sql injection here, as the table name and keys are sanitized to A-Za-z0-9_
$table=san($old['table']);
$key=san($old['key'], '_');
$id=san($old['id'], '_');
foreach ($old['vals'] as $v=>$l) {
$v=san($v, '_');
$db->run("UPDATE `$table` SET `$v`=:val WHERE `$key`=:keyid", [":keyid"=>$id, ":val"=>$l]);
}
}
$db->run("DELETE FROM logs WHERE id=:id", [":id"=>$json['id']]);
}
}
public function add($height, $public_key, $nonce, $data, $date, $signature, $difficulty, $reward_signature, $argon, $bootstrapping=false)
{
global $db;
@@ -37,12 +64,36 @@ class Block
}
}
// lock table to avoid race conditions on blocks
$db->exec("LOCK TABLES blocks WRITE, accounts WRITE, transactions WRITE, mempool WRITE, masternode WRITE, peers write, config WRITE");
$db->exec("LOCK TABLES blocks WRITE, accounts WRITE, transactions WRITE, mempool WRITE, masternode WRITE, peers write, config WRITE, assets WRITE, assets_balance WRITE, assets_market WRITE, votes WRITE, logs WRITE");
$reward = $this->reward($height, $data);
$msg = '';
$mn_reward_rate=0.33;
// hf
if ($height>216000) {
$votes=[];
$r=$db->run("SELECT id,val FROM votes");
foreach ($r as $vote) {
$votes[$vote['id']]=$vote['val'];
}
// emission cut by 30%
if ($votes['emission30']==1) {
$reward=round($reward*0.7);
}
// 50% to masternodes
if ($votes['masternodereward50']==1) {
$mn_reward_rate=0.5;
}
// minimum reward to always be 10 aro
if ($votes['endless10reward']==1&&$reward<10) {
$reward=10;
}
}
if ($height>=80458) {
//reward the masternode
@@ -52,14 +103,28 @@ class Block
);
_log("MN Winner: $mn_winner", 2);
if ($mn_winner!==false) {
$mn_reward=round(0.33*$reward, 8);
$mn_reward=round($mn_reward_rate*$reward, 8);
$reward=round($reward-$mn_reward, 8);
$reward=number_format($reward, 8, ".", "");
$mn_reward=number_format($mn_reward, 8, ".", "");
_log("MN Reward: $mn_reward", 2);
}
}
$cold_winner=false;
$cold_reward=0;
if ($height>216000) {
if ($votes['coldstacking']==1) {
$cold_reward=round($mn_reward*0.2, 8);
$mn_reward=$mn_reward-$cold_reward;
$mn_reward=number_format($mn_reward, 8, ".", "");
$cold_reward=number_format($cold_reward, 8, ".", "");
$cold_winner=$db->single(
"SELECT public_key FROM masternode WHERE height<:start ORDER by cold_last_won ASC, public_key ASC LIMIT 1",
[":current"=>$height, ":start"=>$height-360]
);
_log("Cold MN Winner: $mn_winner", 2);
}
}
@@ -122,7 +187,39 @@ class Block
$db->exec("UNLOCK TABLES");
return false;
}
//masternode rewards
if ($mn_winner!==false&&$height>=80458&&$mn_reward>0) {
//cold stacking rewards
if ($cold_winner!==false&&$height>216000&&$cold_reward>0) {
$db->run("UPDATE accounts SET balance=balance+:bal WHERE public_key=:pub", [":pub"=>$cold_winner, ":bal"=>$cold_reward]);
$bind = [
":id" => hex2coin(hash("sha512", "cold".$hash.$height.$cold_winner)),
":public_key" => $public_key,
":height" => $height,
":block" => $hash,
":dst" => $acc->get_address($cold_winner),
":val" => $cold_reward,
":fee" => 0,
":signature" => $reward_signature,
":version" => 0,
":date" => $date,
":message" => 'masternode-cold',
];
$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) {
// rollback and exit if it fails
_log("Masternode Cold reward DB insert failed");
$db->rollback();
$db->exec("UNLOCK TABLES");
return false;
}
}
$db->run("UPDATE accounts SET balance=balance+:bal WHERE public_key=:pub", [":pub"=>$mn_winner, ":bal"=>$mn_reward]);
$bind = [
":id" => hex2coin(hash("sha512", "mn".$hash.$height.$mn_winner)),
@@ -138,8 +235,8 @@ class Block
":message" => 'masternode',
];
$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
"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) {
// rollback and exit if it fails
@@ -157,6 +254,7 @@ class Block
$db->exec("UNLOCK TABLES");
return false;
}
$this->do_hard_forks($height, $hash);
}
@@ -168,10 +266,27 @@ class Block
$this->blacklist_masternodes();
$this->reset_fails_masternodes($public_key, $height, $hash);
}
// automated asset distribution, checked only every 1000 blocks to reduce load. Payouts every 10000 blocks.
if ($height>216000 && $height%50==1 && $res==true) { // every 50 for testing. No initial height set yet.
$res=$this->asset_distribute_dividends($height, $hash, $public_key, $date, $signature);
}
if ($height>216000 && $res==true) {
$res=$this->asset_market_orders($height, $hash, $public_key, $date, $signature);
}
if ($height>216000 && $height%43200==0) {
$res=$this->masternode_votes($public_key, $height, $hash);
}
// if any fails, rollback
if ($res == false) {
_log("Rollback block",3);
$db->rollback();
} else {
_log("Commiting block",3);
$db->commit();
}
// relese the locking as everything is finished
@@ -179,6 +294,201 @@ class Block
return true;
}
public function masternode_votes($public_key, $height, $hash)
{
global $db;
$arodev='PZ8Tyr4Nx8MHsRAGMpZmZ6TWY63dXWSCvcUb8x4p38GFbZWaJKcncEWqUbe7YJtrDXomwn7DtDYuyYnN2j6s4nQxP1u9BiwCA8U4TjtC9Z21j3R3STLJSFyL';
// masternode votes
if ($height%43200==0) {
_log("Checking masternode votes", 3);
$blacklist=[];
$total_mns=$db->single("SELECT COUNT(1) FROM masternode");
$total_mns_with_key=$db->single("SELECT COUNT(1) FROM masternode WHERE vote_key IS NOT NULL");
// only if at least 50% of the masternodes have voting keys
if ($total_mns_with_key/$total_mns>0.50) {
_log("Counting the votes from other masternodes", 3);
$r=$db->run("SELECT message, count(message) as c FROM transactions WHERE version=106 AND height>:height group by message", [':height'=>$height-43200]);
foreach ($r as $x) {
if ($x['c']>$total_mns_with_key/1.5) {
$blacklist[]=san($x['message']);
}
}
} else {
// If less than 50% of the mns have voting key, AroDev's votes are used
_log("Counting AroDev votes", 3);
$r=$db->run("SELECT message FROM transactions WHERE version=106 AND height>:height AND public_key=:pub", [':height'=>$height-43200, ":pub"=>$arodev]);
foreach ($r as $x) {
$blacklist[]=san($x['message']);
}
}
$r=$db->run("SELECT public_key FROM masternode WHERE voted=1");
foreach ($r as $masternode) {
if (!in_array($masternode, $blacklist)) {
_log("Masternode removed from voting blacklist - $masternode", 3);
$this->add_log($hash, ["table"=>"masternode", "key"=>"public_key","id"=>$masternode, "vals"=>['voted'=>1]]);
$db->run("UPDATE masternode SET voted=0 WHERE public_key=:pub", [":pub"=>$masternode]);
}
}
foreach ($blacklist as $masternode) {
$res=$db->single("SELECT voted FROM masternode WHERE public_key=:pub", [":pub"=>$masternode]);
if ($res==0) {
_log("Masternode blacklist voted - $masternode", 3);
$db->run("UPDATE masternode SET voted=1 WHERE public_key=:pub", [":pub"=>$masternode]);
$this->add_log($hash, ["table"=>"masternode", "key"=>"public_key","id"=>$masternode, "vals"=>['voted'=>0]]);
}
}
}
// blockchain votes
$voted=[];
if ($height%129600==0) {
// only if at least 50% of the masternodes have voting keys
if ($total_mns_with_key/$total_mns>0.50) {
_log("Counting masternode blockchain votes", 3);
$r=$db->run("SELECT message, count(message) as c FROM transactions WHERE version=107 AND height>:height group by message", [':height'=>$height-129600]);
foreach ($r as $x) {
if ($x['c']>$total_mns_with_key/1.5) {
$voted[]=san($x['message']);
}
}
} else {
_log("Counting AroDev blockchain votes", 3);
// If less than 50% of the mns have voting key, AroDev's votes are used
$r=$db->run("SELECT message FROM transactions WHERE version=107 AND height>:height AND public_key=:pub", [':height'=>$height-129600*, ":pub"=>$arodev]);
foreach ($r as $x) {
$voted[]=san($x['message']);
}
}
foreach ($voted as $vote) {
$v=$db->row("SELECT id, val FROM votes WHERE id=:id", [":id"=>$vote]);
if ($v) {
if ($v['val']==0) {
_log("Blockchain vote - $v[id] = 1", 3);
$db->run("UPDATE votes SET val=1 WHERE id=:id", [":id"=>$v['id']]);
$this->add_log($hash, ["table"=>"votes", "key"=>"id","id"=>$v['id'], "vals"=>['val'=>0]]);
} else {
_log("Blockchain vote - $v[id] = 0", 3);
$db->run("UPDATE votes SET val=0 WHERE id=:id", [":id"=>$v['id']]);
$this->add_log($hash, ["table"=>"votes", "key"=>"id","id"=>$v['id'], "vals"=>['val'=>1]]);
}
}
}
}
return true;
}
public function asset_market_orders($height, $hash, $public_key, $date, $signature)
{
global $db;
$trx=new Transaction;
// checks all bid market orders ordered in the same way on all nodes
$r=$db->run("SELECT * FROM assets_market WHERE status=0 and val_done<val AND type='bid' ORDER by asset ASC, id ASC");
foreach ($r as $x) {
$finished=0;
//remaining part of the order
$val=$x['val']-$x['val_done'];
// starts checking all ask orders that are still valid and are on the same price. should probably adapt this to allow lower price as well in the future.
$asks=$db->run("SELECT * FROM assets_market WHERE status=0 and val_done<val AND asset=:asset AND price=:price AND type='ask' ORDER by price ASC, id ASC", [":asset"=>$x['asset'], ":price"=>$x['price']]);
foreach ($asks as $ask) {
//remaining part of the order
$remaining=$ask['val']-$ask['val_done'];
// how much of the ask should we use to fill the bid order
$use=0;
if ($remaining>$val) {
$use=$val;
} else {
$use=$remaining;
}
$val-=$use;
$db->run("UPDATE assets_market SET val_done=val_done+:done WHERE id=:id", [":id"=>$ask['id'], ":done"=>$use]);
$db->run("UPDATE assets_market SET val_done=val_done+:done WHERE id=:id", [":id"=>$x['id'], ":done"=>$use]);
// if we filled the order, we should exit the loop
$db->run("INSERT into assets_balance SET account=:account, asset=:asset, balance=:balance ON DUPLICATE KEY UPDATE balance=balance+:balance2", [":account"=>$x['account'], ":asset"=>$x['asset'], ":balance"=>$use, ":balance2"=>$use]);
$aro=$use*$x['price'];
$db->run("UPDATE accounts SET balance=balance+:balance WHERE id=:id", [":balance"=>$aro, ":id"=>$ask['account']]);
$random = hex2coin(hash("sha512", $x['id'].$ask['id'].$val.$hash));
$new = [
"id" => $random,
"public_key" => $x['id'],
"dst" => $ask['id'],
"val" => $aro,
"fee" => 0,
"signature" => $signature,
"version" => 58,
"date" => $date,
"message" => $use
];
$res=$trx->add($hash, $height, $new);
if (!$res) {
return false;
}
if ($val<=0) {
break;
}
}
}
return true;
}
public function asset_distribute_dividends($height, $hash, $public_key, $date, $signature)
{
global $db;
$trx=new Transaction;
_log("Starting automated dividend distribution", 3);
// just the assets with autodividend
$r=$db->run("SELECT * FROM assets WHERE auto_dividend=1");
if ($r===false) {
return true;
}
foreach ($r as $x) {
$asset=$db->row("SELECT id, public_key, balance FROM accounts WHERE id=:id", [":id"=>$x['id']]);
// minimum balance 1 aro
if ($asset['balance']<1) {
_log("Asset $asset[id] not enough balance", 3);
continue;
}
_log("Autodividend $asset[id] - $asset[balance] ARO", 3);
// every 10000 blocks and at minimum 10000 of asset creation or last distribution, manual or automated
$last=$db->single("SELECT height FROM transactions WHERE (version=54 OR version=50 or version=57) AND public_key=:pub ORDER by height DESC LIMIT 1", [":pub"=>$asset['public_key']]);
if ($height<$last+100) { // 100 for testnet
continue;
}
// generate a pseudorandom id and version 54 transaction for automated dividend distribution. No fees for such automated distributions to encourage the system
$random = hex2coin(hash("sha512", $x['id'].$hash.$height));
$new = [
"id" => $random,
"public_key" => $asset['public_key'],
"dst" => $asset['id'],
"val" => $asset['balance'],
"fee" => 0,
"signature" => $signature,
"version" => 57,
"date" => $date,
"src" => $asset['id'],
"message" => '',
];
$res=$trx->add($hash, $height, $new);
if (!$res) {
return false;
}
}
return true;
}
public function do_hard_forks($height, $block)
{
global $db;
@@ -239,8 +549,8 @@ class Block
// their locked coins are added to dev's safewallet
$id=hex2coin(hash("sha512", "hf".$block.$height.'compromised-masternodes'));
$res=$db->run(
"INSERT into transactions SET id=:id, block=:block, height=:height, dst=:dst, val=4700000, fee=0, signature=:sig, version=0, message=:msg, date=:date, public_key=:public_key",
[":id"=>$id, ":block"=>$block, ":height"=>$height, ":dst"=>'4kWXV4HMuogUcjZBEzmmQdtc1dHzta6VykhCV1HWyEXK7kRWEMJLNoMWbuDwFMTfBrq5a9VthkZfmkMkamTfwRBP', ":sig"=>$id, ":msg"=>'compromised-masternodes-hf', ":date"=>time(), ":public_key"=>'4kWXV4HMuogUcjZBEzmmQdtc1dHzta6VykhCV1HWyEXK7kRWEMJLNoMWbuDwFMTfBrq5a9VthkZfmkMkamTfwRBP']
"INSERT into transactions SET id=:id, block=:block, height=:height, dst=:dst, val=4700000, fee=0, signature=:sig, version=0, message=:msg, date=:date, public_key=:public_key",
[":id"=>$id, ":block"=>$block, ":height"=>$height, ":dst"=>'4kWXV4HMuogUcjZBEzmmQdtc1dHzta6VykhCV1HWyEXK7kRWEMJLNoMWbuDwFMTfBrq5a9VthkZfmkMkamTfwRBP', ":sig"=>$id, ":msg"=>'compromised-masternodes-hf', ":date"=>time(), ":public_key"=>'4kWXV4HMuogUcjZBEzmmQdtc1dHzta6VykhCV1HWyEXK7kRWEMJLNoMWbuDwFMTfBrq5a9VthkZfmkMkamTfwRBP']
);
$db->run("UPDATE accounts SET balance=balance+4700000 where id='4kWXV4HMuogUcjZBEzmmQdtc1dHzta6VykhCV1HWyEXK7kRWEMJLNoMWbuDwFMTfBrq5a9VthkZfmkMkamTfwRBP' LIMIT 1");
}
@@ -281,9 +591,8 @@ class Block
$msg="$mn[blacklist],$mn[last_won],$mn[fails]";
$res=$db->run(
"INSERT into transactions SET id=:id, block=:block, height=:height, dst=:dst, val=0, fee=0, signature=:sig, version=111, message=:msg, date=:date, public_key=:public_key",
[":id"=>$id, ":block"=>$hash, ":height"=>$height, ":dst"=>$hash, ":sig"=>$hash, ":msg"=>$msg, ":date"=>time(), ":public_key"=>$public_key]
[":id"=>$id, ":block"=>$hash, ":height"=>$height, ":dst"=>$hash, ":sig"=>$hash, ":msg"=>$msg, ":date"=>time(), ":public_key"=>$public_key]
);
if ($res!=1) {
@@ -381,14 +690,28 @@ class Block
}
$result=ceil($total_time/$blks);
_log("Block time: $result", 3);
if ($result > 260) {
$dif = bcmul($current['difficulty'], 1.05);
} elseif ($result < 220) {
// if lower, decrease by 5%
$dif = bcmul($current['difficulty'], 0.95);
// 1 minute blocktime
if ($height>216000) {
if ($result > 65) {
$dif = bcmul($current['difficulty'], 1.05);
} elseif ($result < 55) {
// if lower, decrease by 5%
$dif = bcmul($current['difficulty'], 0.95);
} else {
// keep current difficulty
$dif = $current['difficulty'];
}
} else {
// keep current difficulty
$dif = $current['difficulty'];
// 4 minutes blocktime
if ($result > 260) {
$dif = bcmul($current['difficulty'], 1.05);
} elseif ($result < 220) {
// if lower, decrease by 5%
$dif = bcmul($current['difficulty'], 0.95);
} else {
// keep current difficulty
$dif = $current['difficulty'];
}
}
} else {
// hardfork 80000, fix difficulty targetting
@@ -464,17 +787,22 @@ class Block
// calculate the reward for each block
public function reward($id, $data = [])
{
// starting reward
$reward = 1000;
if ($id>216000) {
// 1min block time
$reward=200;
$factor = floor(($id-216000) / 43200) / 100;
$reward -= $reward * $factor;
} else {
// starting reward
$reward = 1000;
// decrease by 1% each 10800 blocks (approx 1 month)
$factor = floor($id / 10800) / 100;
$reward -= $reward * $factor;
}
// decrease by 1% each 10800 blocks (approx 1 month)
$factor = floor($id / 10800) / 100;
$reward -= $reward * $factor;
if ($reward < 0) {
$reward = 0;
}
// calculate the transaction fees
$fees = 0;
if (count($data) > 0) {
@@ -556,17 +884,40 @@ class Block
// reward transaction and signature
$reward = $this->reward($height, $data);
$mn_reward_rate=0.33;
global $db;
// hf
if ($height>216000) {
$votes=[];
$r=$db->run("SELECT id,val FROM votes");
foreach ($r as $vote) {
$votes[$vote['id']]=$vote['val'];
}
// emission cut by 30%
if ($votes['emission30']==1) {
$reward=round($reward*0.7);
}
// 50% to masternodes
if ($votes['masternodereward50']==1) {
$mn_reward_rate=0.5;
}
// minimum reward to always be 10 aro
if ($votes['endless10reward']==1&&$reward<10) {
$reward=10;
}
}
if ($height>=80458) {
//reward the masternode
global $db;
$mn_winner=$db->single(
"SELECT public_key FROM masternode WHERE status=1 AND blacklist<:current AND height<:start ORDER by last_won ASC, public_key ASC LIMIT 1",
[":current"=>$height, ":start"=>$height-360]
);
_log("MN Winner: $mn_winner", 2);
if ($mn_winner!==false) {
$mn_reward=round(0.33*$reward, 8);
$mn_reward=round($mn_reward_rate*$reward, 8);
$reward=round($reward-$mn_reward, 8);
$reward=number_format($reward, 8, ".", "");
$mn_reward=number_format($mn_reward, 8, ".", "");
@@ -862,10 +1213,12 @@ class Block
_log("Transaction check failed - $x[id]", 3);
return false;
}
if ($x['version']>=100&&$x['version']<110) {
if ($x['version']>=100&&$x['version']<110&&$x['version']!=106&&$x['version']!=107) {
$mns[] = $x['public_key'];
}
if($x['version']==106||$x['version']==107){
$mns[]=$x['public_key'].$x['message'];
}
// prepare total balance
$balance[$x['src']] += $x['val'] + $x['fee'];
@@ -887,8 +1240,8 @@ class Block
// check if the account has enough balance to perform the transaction
foreach ($balance as $id => $bal) {
$res = $db->single(
"SELECT COUNT(1) FROM accounts WHERE id=:id AND balance>=:balance",
[":id" => $id, ":balance" => $bal]
"SELECT COUNT(1) FROM accounts WHERE id=:id AND balance>=:balance",
[":id" => $id, ":balance" => $bal]
);
if ($res == 0) {
_log("Not enough balance for transaction - $id", 3);
@@ -965,7 +1318,8 @@ class Block
return;
}
$db->beginTransaction();
$db->exec("LOCK TABLES blocks WRITE, accounts WRITE, transactions WRITE, mempool WRITE, masternode WRITE");
$db->exec("LOCK TABLES blocks WRITE, accounts WRITE, transactions WRITE, mempool WRITE, masternode WRITE, peers write, config WRITE, assets WRITE, assets_balance WRITE, assets_market WRITE, votes WRITE,logs WRITE");
foreach ($r as $x) {
$res = $trx->reverse($x['id']);
if ($res === false) {
@@ -981,8 +1335,11 @@ class Block
$db->exec("UNLOCK TABLES");
return false;
}
$this->reverse_log($x['id']);
}
$db->commit();
$db->exec("UNLOCK TABLES");
return true;
@@ -1002,7 +1359,8 @@ class Block
}
// avoid race conditions on blockchain manipulations
$db->beginTransaction();
$db->exec("LOCK TABLES blocks WRITE, accounts WRITE, transactions WRITE, mempool WRITE");
$db->exec("LOCK TABLES blocks WRITE, accounts WRITE, transactions WRITE, mempool WRITE, masternode WRITE, peers write, config WRITE, assets WRITE, assets_balance WRITE, assets_market WRITE, votes WRITE, logs WRITE");
// reverse all transactions of the block
$res = $trx->reverse($x['id']);
if ($res === false) {
@@ -1066,7 +1424,7 @@ class Block
$r = $db->run("SELECT * FROM transactions WHERE version>0 AND block=:block", [":block" => $block['id']]);
$transactions = [];
foreach ($r as $x) {
if ($x['version']>110) {
if ($x['version']>110||$x['version']==57||$x['version']==58||$x['version']==59) {
//internal transactions
continue;
}

View File

@@ -154,3 +154,5 @@ $_config['masternode'] = false;
// The public key for the masternode
$_config['masternode_public_key'] = '';
$_config['masternode_voting_public_key'] = '';
$_config['masternode_voting_private_key'] = '';

View File

@@ -1,13 +1,13 @@
<?php
// ARO version
define("VERSION", "0.4.5");
// Amsterdam timezone by default, should probably be moved to config
define("VERSION", "1.0.0-alpha.1");
// UTC timezone by default
date_default_timezone_set("UTC");
//error_reporting(E_ALL & ~E_NOTICE);
// error_reporting(E_ALL & ~E_NOTICE);
error_reporting(0);
ini_set('display_errors', "off");
ini_set('display_errors', 0);
ini_set('display_startup_errors', 0);
// not accessible directly
if (php_sapi_name() !== 'cli' && substr_count($_SERVER['PHP_SELF'], "/") > 1) {
die("This application should only be run in the main directory /");

View File

@@ -2,6 +2,7 @@
// when db schema modifications are done, this function is run.
$dbversion = intval($_config['dbversion']);
$db->beginTransaction();
if ($dbversion == 0) {
$db->run("
@@ -146,7 +147,7 @@ if ($dbversion == 7) {
$dbversion++;
}
if ($dbversion == 8) {
$db->run("CREATE TABLE `masternode` (
$db->run("CREATE TABLE `masternode` (
`public_key` varchar(128) COLLATE utf8mb4_bin NOT NULL,
`height` int(11) NOT NULL,
`ip` varchar(16) COLLATE utf8mb4_bin NOT NULL,
@@ -156,18 +157,110 @@ if ($dbversion == 8) {
`status` tinyint(4) NOT NULL DEFAULT '1'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;");
$db->run("ALTER TABLE `masternode`
$db->run("ALTER TABLE `masternode`
ADD PRIMARY KEY (`public_key`),
ADD KEY `last_won` (`last_won`),
ADD KEY `status` (`status`),
ADD KEY `blacklist` (`blacklist`),
ADD KEY `height` (`height`);");
$dbversion++;
$dbversion++;
}
if ($dbversion = 9) {
//dev only
$dbversion++;
if ($dbversion == 9) {
//dev only
$dbversion++;
}
if ($dbversion == 10) {
//assets system
$db->run("
CREATE TABLE `assets` (
`id` varbinary(128) NOT NULL,
`max_supply` bigint(18) NOT NULL DEFAULT '0',
`tradable` tinyint(1) NOT NULL DEFAULT '1',
`price` decimal(20,8) NOT NULL DEFAULT '0.00000000',
`dividend_only` tinyint(1) NOT NULL DEFAULT '0',
`auto_dividend` tinyint(1) NOT NULL DEFAULT '0',
`allow_bid` tinyint(1) NOT NULL DEFAULT '1',
`height` int(11) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
");
$db->run("
ALTER TABLE `assets`
ADD PRIMARY KEY (`id`)
");
$db->run("
CREATE TABLE `assets_market` (
`id` varchar(128) COLLATE utf8mb4_bin NOT NULL,
`account` varbinary(128) NOT NULL,
`asset` varbinary(128) NOT NULL,
`price` decimal(20,8) NOT NULL,
`date` int(11) NOT NULL,
`status` tinyint(1) NOT NULL DEFAULT '0',
`type` enum('bid','ask') COLLATE utf8mb4_bin NOT NULL DEFAULT 'bid',
`val` bigint(18) NOT NULL,
`val_done` bigint(18) NOT NULL DEFAULT '0',
`cancelable` tinyint(1) NOT NULL DEFAULT '1'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
");
$db->run("
ALTER TABLE `assets_market`
ADD PRIMARY KEY (`id`);
");
$db->run("CREATE TABLE `assets_balance` (
`account` varbinary(128) NOT NULL,
`asset` varbinary(128) NOT NULL,
`balance` bigint(128) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
");
$db->run("
ALTER TABLE `assets_balance`
ADD PRIMARY KEY (`account`,`asset`);
");
$dbversion++;
}
if ($dbversion == 11) {
$db->run("ALTER TABLE `transactions` ADD INDEX(`version`); ");
$db->run("ALTER TABLE `transactions` ADD INDEX(`message`); ");
$db->run("
CREATE TABLE `logs` (
`id` int(11) NOT NULL,
`transaction` varbinary(128) NULL DEFAULT NULL,
`block` VARBINARY(128) NULL DEFAULT NULL,
`json` text DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;");
$db->run("ALTER TABLE `logs`
ADD PRIMARY KEY (`id`),
ADD INDEX(`transaction`),
ADD INDEX(`block`);");
$db->run("ALTER TABLE `logs` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;");
$db->run("ALTER TABLE `masternode` ADD `vote_key` VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL AFTER `status`, ADD INDEX(`vote_key`);");
$db->run("ALTER TABLE `masternode` ADD `cold_last_won` INT NOT NULL DEFAULT '0' AFTER `vote_key`, ADD INDEX(`cold_last_won`); ");
$db->run("ALTER TABLE `masternode` ADD `voted` TINYINT NOT NULL DEFAULT '0' AFTER `cold_last_won`, ADD INDEX (`voted`); ");
$db->run("CREATE TABLE `votes` (
`id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`nfo` varchar(64) NOT NULL,
`val` int(11) NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=latin1;");
$db->run("INSERT INTO `votes` (`id`, `nfo`, `val`) VALUES
('coldstacking', 'Enable cold stacking for inactive masternodes', 1),
('emission30', 'Emission reduction by 30 percent', 1),
('endless10reward', 'Minimum reward to be 10 aro forever', 0),
('masternodereward50', 'Masternode reward to be 50 percent of the block reward', 1);");
$db->run("ALTER TABLE `votes` ADD PRIMARY KEY (`id`);");
$dbversion++;
}
// update the db version to the latest one

View File

@@ -4,13 +4,38 @@ use Arionum\Blacklist;
class Transaction
{
public function add_log($x, $log)
{
global $db;
//$json=["table"=>"masternode", "key"=>"public_key","id"=>$x['public_key'], "vals"=>['ip'=>$current_ip] ];
$db->run("INSERT into logs SET transaction=:id, json=:json", [':id'=>$x['id'], ":json"=>json_encode($log)]);
}
public function reverse_log($x)
{
global $db;
$r=$db->run("SELECT json, id FROM logs WHERE transaction=:id ORDER by id DESC", [":id"=>$x['id']]);
foreach ($r as $json) {
$old=json_decode($json['json'], true);
if ($old!==false&&is_array($old)) {
//making sure there's no sql injection here, as the table name and keys are sanitized to A-Za-z0-9_
$table=san($old['table']);
$key=san($old['key'], '_');
$id=san($old['id'], '_');
foreach ($old['vals'] as $v=>$l) {
$v=san($v, '_');
$db->run("UPDATE `$table` SET `$v`=:val WHERE `$key`=:keyid", [":keyid"=>$id, ":val"=>$l]);
}
}
$db->run("DELETE FROM logs WHERE id=:id",[":id"=>$json['id']]);
}
}
// reverse and remove all transactions from a block
public function reverse($block)
{
global $db;
$acc = new Account();
$r = $db->run("SELECT * FROM transactions WHERE block=:block ORDER by `version` ASC", [":block" => $block]);
$r = $db->run("SELECT * FROM transactions WHERE block=:block ORDER by `version` DESC", [":block" => $block]);
foreach ($r as $x) {
_log("Reversing transaction $x[id]", 4);
if (empty($x['src'])) {
@@ -29,19 +54,19 @@ class Transaction
} else {
// other type of transactions
if ($x['version']!=100&&$x['version']<111) {
if ($x['version']!=100 && $x['version']<111 && $x['version'] != 54 && $x['version'] != 57 && $x['version'] != 58 && $x['val']>0) {
$rez=$db->run(
"UPDATE accounts SET balance=balance-:val WHERE id=:id",
[":id" => $x['dst'], ":val" => $x['val']]
"UPDATE accounts SET balance=balance-:val WHERE id=:id",
[":id" => $x['dst'], ":val" => $x['val']]
);
if ($rez!=1) {
_log("Update accounts balance minus failed", 3);
_log("Update accounts balance minus failed - $x[id]", 3);
return false;
}
}
}
// on version 0 / reward transaction, don't credit anyone
if ($x['version'] > 0 && $x['version']<111) {
if ($x['version'] > 0 && $x['version']<111 && $x['version'] != 54 && $x['version'] != 57 && $x['version'] != 58 && ($x['val']+$x['fee'])>0) {
$rez=$db->run(
"UPDATE accounts SET balance=balance+:val WHERE id=:id",
[":id" => $x['src'], ":val" => $x['val'] + $x['fee']]
@@ -74,7 +99,7 @@ class Transaction
} elseif ($x['version']==101) {
$rez=$db->run(
"UPDATE masternode SET status=1 WHERE public_key=:public_key",
[':public_key'=>$x['public_key']]
[':public_key'=>$x['public_key']]
);
} elseif ($x['version']==102) {
$rez=$db->run("UPDATE masternode SET status=0 WHERE public_key=:public_key", [':public_key'=>$x['public_key']]);
@@ -82,7 +107,7 @@ class Transaction
$mnt=$db->row("SELECT height, `message` FROM transactions WHERE version=100 AND public_key=:public_key ORDER by height DESC LIMIT 1", [":public_key"=>$x['public_key']]);
$vers=$db->single(
"SELECT `version` FROM transactions WHERE (version=101 or version=102) AND public_key=:public_key AND height>:height ORDER by height DESC LIMIT 1",
[":public_key"=>$x['public_key'],":height"=>$mnt['height']]
[":public_key"=>$x['public_key'],":height"=>$mnt['height']]
);
$status=1;
@@ -93,7 +118,7 @@ class Transaction
$rez=$db->run(
"INSERT into masternode SET `public_key`=:public_key, `height`=:height, `ip`=:ip, `status`=:status",
[":public_key"=>$x['public_key'], ":height"=>$mnt['height'], ":ip"=>$mnt['message'], ":status"=>$status]
[":public_key"=>$x['public_key'], ":height"=>$mnt['height'], ":ip"=>$mnt['message'], ":status"=>$status]
);
if ($rez!=1) {
_log("Insert into masternode failed", 3);
@@ -104,6 +129,10 @@ class Transaction
_log("Update masternode balance failed", 3);
return false;
}
} elseif ($x['version']==104) {
$this->reverse_log($x);
} elseif ($x['version']==105) {
$db->run("UPDATE masternode SET vote_key=NULL WHERE public_key=:public_key", [":public_key"=>$x['public_key']]);
}
}
// internal masternode history
@@ -113,7 +142,7 @@ class Transaction
$rez=$db->run(
"UPDATE masternode SET fails=:fails, blacklist=:blacklist, last_won=:last_won WHERE public_key=:public_key",
[":public_key"=>$x['public_key'], ":blacklist"=> $m[0], ":fails"=>$m[2], ":last_won"=>$m[1]]
[":public_key"=>$x['public_key'], ":blacklist"=> $m[0], ":fails"=>$m[2], ":last_won"=>$m[1]]
);
if ($rez!=1) {
_log("Update masternode log failed", 3);
@@ -121,8 +150,78 @@ class Transaction
}
}
// asset transactions
if ($x['version']==50) {
$db->run("DELETE FROM assets WHERE id=:id", [":id"=>$x['src']]);
$db->run("DELETE FROM assets_balance WHERE asset=:id", [":id"=>$x['src']]);
} elseif ($x['version']==51) {
$t=json_decode($x['message'], true);
$db->run(
"UPDATE assets_balance SET balance=balance-:balance WHERE account=:account and asset=:asset",
[":account"=>$x['dst'], ":asset"=>san($t[0]), ":balance"=>intval($t[1])]
);
$db->run("UPDATE assets_balance SET balance=balance+:balance WHERE account=:account and asset=:asset", [":account"=>$x['src'], ":asset"=>san($t[0]), ":balance"=>intval($t[1])]);
} elseif ($x['version']==52) {
$t=json_decode($x['message'], true);
if ($t[4]=="ask") {
$type="ask";
} else {
$type="bid";
}
if ($type=="ask") {
$db->run("UPDATE assets_balance SET balance=balance+:val WHERE account=:account AND asset=:asset", [
":account"=>$x['src'],
":asset"=>san($t[0]),
":val"=>intval($t[2])
]);
} else {
$val=number_format($t[2]*$t[1], 8, '.', '');
$db->run("UPDATE accounts SET balance=balance+:val WHERE id=:id", [":id"=>$x['src'], ":val"=>$val]);
}
$db->run("DELETE FROM assets_market WHERE id=:id", [':id'=>$x['id']]);
} elseif ($x['version']==53) {
$order=$db->row("SELECT * FROM assets_market WHERE id=:id AND account=:account AND status=2", [":id"=>san($x['message']), ":account"=>$x['src']]);
if ($order) {
$remaining=$order['val']-$order['val_done'];
if ($remaining>0) {
if ($order['type']=="ask") {
$db->run("UPDATE assets_balance SET balance=balance-:val WHERE account=:account AND asset=:asset", [
":account"=>$x['src'],
":asset"=>san($order['asset']),
":val"=>intval($remaining)
]);
} else {
$val=number_format($order['price']*$remaining, 8, '.', '');
$db->run("UPDATE accounts SET balance=balance-:val WHERE id=:id", [":id"=>$x['src'], ":val"=>$val]);
}
$db->run("UPDATE assets_market SET status=0 WHERE id=:id", [":id"=>san($x['message'])]);
}
}
} elseif ($x['version']==54||$x['version']==57) {
//nothing to be done
} elseif ($x['version']==55) {
// the message stores the increment value
$plus=intval($x['message']);
$db->run("UPDATE assets_balance SET balance=balance-:plus WHERE account=:account AND asset=:asset", [":account"=>$x['src'], ":asset"=>$x['src'], ":plus"=>$plus]);
} elseif ($x['version']==58) {
// the message stores the number of units
$use=intval($x['message']);
// we stored the bid order id in the public key field and the ask in the dst field
$db->run("UPDATE assets_market SET val_done=val_done-:done WHERE id=:id", [":id"=>$x['public_key'], ":done"=>$use]);
$db->run("UPDATE assets_market SET val_done=val_done-:done WHERE id=:id", [":id"=>$x['dst'], ":done"=>$use]);
$bid=$db->row("SELECT * FROM assets_market WHERE id=:id", [':id'=>$x['public_key']]);
$ask=$db->row("SELECT * FROM assets_market WHERE id=:id", [':id'=>$x['dst']]);
$db->run("UPDATE assets_balance SET balance=balance-:balance WHERE account=:account AND asset=:asset", [":account"=>$bid['account'], ":asset"=>$bid['asset'], ":balance"=>$use]);
$aro=$x['val'];
$db->run("UPDATE accounts SET balance=balance-:balance WHERE id=:id", [":balance"=>$aro, ":id"=>$ask['account']]);
}
// add the transactions to mempool
if ($x['version'] > 0 && $x['version']<=110) {
if ($x['version'] > 0 && $x['version']<=110 && $x['version'] != 59 && $x['version'] != 58 && $x['version'] != 57) {
$this->add_mempool($x);
}
$res = $db->run("DELETE FROM transactions WHERE id=:id", [":id" => $x['id']]);
@@ -160,6 +259,7 @@ class Transaction
if (count($r) > 0) {
$i = 0;
$balance = [];
$assets=0;
foreach ($r as $x) {
$trans = [
"id" => $x['id'],
@@ -177,6 +277,24 @@ class Transaction
break;
}
//only a single asset creation per block
if ($x['version']==50) {
$assets++;
if ($assets>1) {
continue;
}
}
// single blockchain vote per block
if ($x['version']==106||$x['version']==107) {
$tid=$x['public_key'].$x['message'];
if ($exists[$tid]==1) {
continue;
}
$exists[$tid]=1;
}
if (empty($x['public_key'])) {
_log("$x[id] - Transaction has empty public_key");
continue;
@@ -250,7 +368,7 @@ class Transaction
];
//only a single masternode command of same type, per block
if ($x['version']>=100&&$x['version']<110) {
if ($x['version']>=100&&$x['version']<110&&$x['version']!=106&&$x['version']!=107) {
$check=$db->single("SELECT COUNT(1) FROM mempool WHERE public_key=:public_key", [":public_key"=>$x['public_key']]);
if ($check!=0) {
_log("Masternode transaction already in mempool", 3);
@@ -270,9 +388,14 @@ class Transaction
{
global $db;
$acc = new Account();
$acc->add($x['public_key'], $block);
if ($x['version']==1) {
$acc->add_id($x['dst'], $block);
// not a valid or useful public key for internal transactions
if ($x['version']!=58 && $x['version']!=59) {
// add the public key to the accounts table
$acc->add($x['public_key'], $block);
if ($x['version']==1 || $x['version'] == 51) {
// make sure the destination address in on the accounts table as well
$acc->add_id($x['dst'], $block);
}
}
$x['id'] = san($x['id']);
$bind = [
@@ -295,8 +418,16 @@ class Transaction
if ($res != 1) {
return false;
}
// market order side chain
if ($x['version']==58) {
return true;
}
if ($x['version'] == 2&&$height>=80000) {
$db->run("UPDATE accounts SET balance=balance+:val WHERE alias=:alias", [":alias" => $x['dst'], ":val" => $x['val']]);
} elseif ($x['version']==50||$x['version']==54||$x['version']==57) {
// asset creation and dividend distribution
} elseif ($x['version']==100&&$height>=80000) {
//master node deposit
} elseif ($x['version']==103&&$height>=80000) {
@@ -310,8 +441,8 @@ class Transaction
// no debit when the transaction is reward
if ($x['version'] > 0) {
// no debit when the transaction is reward or dividend distribution
if ($x['version'] > 0 && $x['version'] != 54 && $x['version'] != 57) {
$db->run(
"UPDATE accounts SET balance=(balance-:val)-:fee WHERE id=:id",
[":id" => $x['src'], ":val" => $x['val'], ":fee" => $x['fee']]
@@ -322,8 +453,8 @@ class Transaction
// set the alias
if ($x['version']==3&&$height>=80000) {
$db->run(
"UPDATE accounts SET alias=:alias WHERE id=:id",
[":id" => $x['src'], ":alias"=>$x['message']]
"UPDATE accounts SET alias=:alias WHERE id=:id",
[":id" => $x['src'], ":alias"=>$x['message']]
);
}
@@ -335,16 +466,142 @@ class Transaction
$db->run("INSERT into masternode SET `public_key`=:public_key, `height`=:height, `ip`=:ip, `status`=1", [":public_key"=>$x['public_key'], ":height"=>$height, ":ip"=>$message]);
} else {
if ($x['version']==101) {
// pause masternode
$db->run("UPDATE masternode SET status=0 WHERE public_key=:public_key", [':public_key'=>$x['public_key']]);
} elseif ($x['version']==102) {
// reactivate pasternode
$db->run("UPDATE masternode SET status=1 WHERE public_key=:public_key", [':public_key'=>$x['public_key']]);
} elseif ($x['version']==103) {
// release and cancel the masternode
$db->run("DELETE FROM masternode WHERE public_key=:public_key", [':public_key'=>$x['public_key']]);
$db->run("UPDATE accounts SET balance=balance+100000 WHERE public_key=:public_key", [':public_key'=>$x['public_key']]);
} elseif ($x['version']==104) {
// update ip
$current_ip=$db->single("SELECT ip FROM masternode WHERE public_key=:public_key", [":public_key"=>$x['public_key']]);
$json=["table"=>"masternode", "key"=>"public_key","id"=>$x['public_key'], "vals"=>['ip'=>$current_ip] ];
$this->add_log($x, $json);
$db->run("UPDATE masternode SET ip=:ip WHERE public_key=:public_key", [':ip'=>$message, ":public_key"=>$x['public_key']]);
} elseif ($x['version']==105) {
// add vote key
$db->run("UPDATE masternode SET vote_key=:vote_key WHERE public_key=:public_key AND vote_key is NULL", [':vote_key'=>san($x['message']), ":public_key"=>$x['public_key']]);
}
}
}
// asset system
if ($x['version']==50) {
// asset creation
$bind=[];
$asset=json_decode($x['message'], true);
$bind[':max_supply']=intval($asset[0]);
$bind[':tradable']=intval($asset[1]);
$bind[':price']=number_format($asset[2], 8, '.', '');
$bind[':dividend_only']=intval($asset[3]);
$bind[':auto_divident']=intval($asset[4]);
$bind[':allow_bid']=intval($asset[5]);
$bind[':height']=$height;
$bind[':id']=$x['src'];
$db->run("INSERT into assets SET id=:id, max_supply=:max_supply, tradable=:tradable, price=:price, dividend_only=:dividend_only, auto_dividend=:auto_divident, height=:height, allow_bid=:allow_bid", $bind);
if ($bind[':max_supply']>0) {
$db->run("INSERT into assets_balance SET account=:account, asset=:asset, balance=:balance", [":account"=>$x['src'], ":asset"=>$x['src'], ":balance"=>$bind[':max_supply']]);
}
} elseif ($x['version']==51) {
// send asset
$t=json_decode($x['message'], true);
$db->run(
"INSERT into assets_balance SET account=:account, asset=:asset, balance=:balance ON DUPLICATE KEY UPDATE balance=balance+:balance2",
[":account"=>$x['dst'], ":asset"=>san($t[0]), ":balance"=>intval($t[1]), ":balance2"=>intval($t[1])]
);
$db->run("UPDATE assets_balance SET balance=balance-:balance WHERE account=:account and asset=:asset", [":account"=>$x['src'], ":asset"=>san($t[0]), ":balance"=>intval($t[1])]);
} elseif ($x['version']==52) {
// market order
$t=json_decode($x['message'], true);
if ($t[4]=="ask") {
$type="ask";
} else {
$type="bid";
}
$bind=[":id" => san($x['id']),
":account" => $x['src'],
":asset" => san($t[0]),
":price" => number_format($t[1], 8, '.', ''),
":date" => $x['date'],
":val"=>intval($t[2]),
":type" => $type,
":cancel" => intval($t[3])
];
$db->run("INSERT into assets_market SET id=:id, account=:account, asset=:asset, price=:price, `date`=:date, status=0, `type`=:type, val=:val, val_done=0, cancelable=:cancel", $bind);
if ($type=="ask") {
$db->run("UPDATE assets_balance SET balance=balance-:val WHERE account=:account AND asset=:asset", [
":account"=>$x['src'],
":asset"=>san($t[0]),
":val"=>intval($t[2])
]);
} else {
$val=number_format($t[2]*$t[1], 8, '.', '');
$db->run("UPDATE accounts SET balance=balance-:val WHERE id=:id", [":id"=>$x['src'], ":val"=>$val]);
}
} elseif ($x['version']==53) {
// cancel order
$order=$db->row("SELECT * FROM assets_market WHERE id=:id AND account=:account AND status=0", [":id"=>san($x['message']), ":account"=>$x['src']]);
if ($order) {
$remaining=$order['val']-$order['val_done'];
if ($remaining>0) {
if ($order['type']=="ask") {
$db->run("UPDATE assets_balance SET balance=balance+:val WHERE account=:account AND asset=:asset", [
":account"=>$x['src'],
":asset"=>san($order['asset']),
":val"=>intval($remaining)
]);
} else {
$val=number_format($order['price']*$remaining, 8, '.', '');
$db->run("UPDATE accounts SET balance=balance+:val WHERE id=:id", [":id"=>$x['src'], ":val"=>$val]);
}
$db->run("UPDATE assets_market SET status=2 WHERE id=:id", [":id"=>san($x['message'])]);
}
}
} elseif ($x['version']==54||$x['version']==57) {
//distribute dividends - only from asset wallet and only to other holders
$r=$db->run("SELECT * FROM assets_balance WHERE asset=:asset AND balance>0 AND account!=:acc", [":asset"=>$x['src'], ":acc"=>$x['src']]);
$total=0;
foreach ($r as $g) {
$total+=$g['balance'];
}
_log("Asset dividend distribution: $total units", 3);
foreach ($r as $g) {
$coins=number_format(($g['balance']/$total)*$x['val'], 8, '.', '');
$fee=number_format(($g['balance']/$total)*$x['fee'], 8, '.', '');
$hash = hex2coin(hash("sha512", $x['id'].$g['account']));
_log("Distributing to $g[account] for $g[balance] units - $coins ARO", 3);
$new = [
"id" => $hash,
"public_key" => $x['public_key'],
"dst" => $g['account'],
"val" => $coins,
"fee" => $fee,
"signature" => $x['signature'],
"version" => 59,
"date" => $x['date'],
"src" => $x['src'],
"message" => '',
];
$res=$this->add($block, $height, $new);
if (!$res) {
return false;
}
}
} elseif ($x['version']==55) {
// increase max supply
$plus=intval($x['message']);
$db->run("INSERT into assets_balance SET balance=:plus, account=:account, asset=:asset ON DUPLICATE KEY UPDATE balance=balance+:plus2", [":account"=>$x['src'], ":asset"=>$x['src'], ":plus"=>$plus, ":plus2"=>$plus]);
}
$db->run("DELETE FROM mempool WHERE id=:id", [":id" => $x['id']]);
@@ -356,12 +613,20 @@ class Transaction
{
$info = $x['val']."-".$x['fee']."-".$x['dst']."-".$x['message']."-".$x['version']."-".$x['public_key']."-".$x['date']."-".$x['signature'];
$hash = hash("sha512", $info);
//_log("Hashing: ".$info,3);
//_log("Hash: $hash",3);
return hex2coin($hash);
}
// check the transaction for validity
public function check($x, $height = 0)
{
global $db;
// blocktime lowered by 1 minute after 216000
$blocktime_factor=1;
if($height>216000){
$blocktime_factor=4;
}
// if no specific block, use current
if ($height === 0) {
$block = new Block();
@@ -371,13 +636,15 @@ class Transaction
$acc = new Account();
$info = $x['val']."-".$x['fee']."-".$x['dst']."-".$x['message']."-".$x['version']."-".$x['public_key']."-".$x['date'];
$src = $acc->get_address($x['public_key']);
// hard fork at 80000 to implement alias, new mining system, assets
// if($x['version']>1 && $height<80000){
// return false;
// }
// internal transactions
if ($x['version']>110) {
if ($x['version']>110 || $x['version'] == 57 || $x['version'] == 58 || $x['version'] == 59) {
return false;
}
@@ -411,8 +678,15 @@ class Transaction
_log("The account already has an alias", 3);
return false;
}
if ($x['dst']!=$src) {
// just to prevent some bypasses in the future
_log("DST must be SRC for this transaction", 3);
return false;
}
}
//masternode transactions
if ($x['version']>=100&&$x['version']<110&&$height>=80000) {
@@ -436,7 +710,12 @@ class Transaction
return false;
} elseif ($x['version']!=100) {
$mn=$acc->get_masternode($x['public_key']);
if ($x['dst']!=$src&&$x['version']!=106) {
// just to prevent some bypasses in the future
_log("DST must be SRC for this transaction", 3);
return false;
}
if (!$mn) {
_log("The masternode does not exist", 3);
return false;
@@ -451,17 +730,283 @@ class Transaction
if ($mn['status']!=0) {
_log("The masternode is not paused", 3);
return false;
} elseif ($height-$mn['last_won']<10800) { //10800
} elseif ($height-$mn['last_won']<10800*$blocktime_factor) { //10800
_log("The masternode last won block is less than 10800 blocks", 3);
return false;
} elseif ($height-$mn['height']<32400) { //32400
} elseif ($height-$mn['height']<32400*$blocktime_factor) { //32400
_log("The masternode start height is less than 32400 blocks! $height - $mn[height]", 3);
return false;
}
} elseif ($x['version']==104) {
//only once per month (every 43200 blocks)
$res=$db->single("SELECT COUNT(1) FROM transactions WHERE public_key=:public_key AND version=104 AND height>:height", [':public_key'=>$x['public_key'], ":height"=>$height-43200]);
if ($res!=0) {
return false;
}
// already using this ip
if ($message==$mn['ip']) {
return false;
}
// valid ips
$message=$x['message'];
$message=preg_replace("/[^0-9\.]/", "", $message);
if (!filter_var($message, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
_log("The Masternode IP is invalid", 3);
return false;
}
// making sure the ip is not already in use
global $db;
$existing=$db->single("SELECT COUNT(1) FROM masternode WHERE ip=:ip", [":ip"=>$message]);
if ($existing!=0) {
return false;
}
} elseif ($x['version']==105) {
// masternode voting key can only be set once
if(!empty($mn['vote_key'])){
return false;
}
}
// masternode votes
elseif ($x['version']==106) {
// value always 0
if ($x['val']!=0) {
return false;
}
// one vote to each mn per 43200 blocks
$res=$db->single("SELECT COUNT(1) FROM transactions WHERE dst=:dst AND version=106 AND public_key=:id AND height>:height", [':dst'=>$x['dst'], ":id"=>$x['public_key'], ":height"=>$height-43200]);
if ($res>0) {
return false;
}
}
// masternode blockchain votes
elseif ($x['version']==107) {
// value always 0
if ($x['val']!=0) {
_log("The value should be 0 for this transaction type - $x[val]",3);
return false;
}
// one vote to each mn per 129600 blocks
$res=$db->single("SELECT COUNT(1) FROM transactions WHERE message=:message AND version=107 AND public_key=:id AND height>:height", [':message'=>$x['message'], ":id"=>$x['public_key'], ":height"=>$height-129600]);
if ($res>0) {
_log("There is already a vote in the last 129600 blocks",3);
return false;
}
}
}
}
// no asset transactions prior to 216000
if($x['version']>=50&&$x['version']<=55&&$height<=216000){
return false;
}
// no masternode voting prior to 216000
if(($x['version']==106||$x['version']==107||$x['version']==105||$x['version']==104)&&$height<=216000){
return false;
}
// assets
if ($x['version']==50) {
// asset creation
// fixed asset price 100 +. The 100 are burned and not distributed to miners.
if ($x['val']!=100) {
_log("The asset creation transaction is not 100", 3);
return false;
}
// stored in message in json format - [max supply, tradable, fixed price, dividend only, autodividend]
$asset=json_decode($x['message'], true);
if ($asset==false) {
_log("Invalid asset creation json", 3);
return false;
}
// minimum 0 (for inflatable assets) and maximum 1.000.000.000
if ($asset[0]>1000000000||$asset[0]<0||intval($asset[0])!=$asset[0]) {
_log("Invalid asset max supply", 3);
return false;
}
// 0 for non-tradable, 1 for tradable on the blockchain market
if ($asset[1]!==1&&$asset[1]!==0) {
_log("Invalid asset tradable", 3);
return false;
}
// If the price is set, it cannot be sold by the asset wallet at a dfferent price. Max price 1.000.000 aro
if (number_format($asset[2], 8, '.', '')!=$asset[2]||$asset[2]<0||$asset[2]>1000000) {
_log("Invalid asset price", 3);
return false;
}
// 1 to allow only dividend distribution, 0 to allow all transfers
if ($asset[3]!==1&&$asset[3]!==0) {
_log("Invalid asset dividend setting", 3);
return false;
}
// automatic dividend distribution every 10000 blocks
if ($asset[4]!==1&&$asset[4]!==0) {
_log("Invalid asset autodividend setting", 3);
return false;
}
// do not allow this asset to buy other assets via the market
if ($asset[5]!==1&&$asset[5]!==0) {
_log("Invalid asset bid_only setting", 3);
return false;
}
// make sure there is no similar asset with the same alias
$chk=$db->single("SELECT COUNT(1) FROM assets WHERE id=:id", [":id"=>$src]);
if ($chk!==0) {
_log("The asset already exists", 3);
return false;
}
}
// asset transfer
if ($x['version']==51) {
// Transfer details in json format, stored in the message. format: [asset id, units]
// The transfer is done to the dst address of the transactions
$asset=json_decode($x['message'], true);
if ($asset==false) {
_log("Invalid asset creation json", 3);
return false;
}
// check if the asset exists
$blockasset=$db->row("SELECT id, price FROM assets WHERE id=:id", [":id"=>san($asset[0])]);
if (!$blockasset) {
_log("Invalid asset", 3);
return false;
}
// minimum 1 unit is transfered
if (intval($asset[1])!=$asset[1]||$asset[1]<1) {
_log("Invalid amount", 3);
return false;
}
//make sure the wallet has enough asset units
$balance=$db->single("SELECT balance FROM assets_balance WHERE account=:account AND asset=:asset", [":account"=>$src, ":asset"=>san($asset[0])]);
if ($balance<=$asset[1]) {
_log("Not enough balance", 3);
return false;
}
if ($blockasset['price']>0 && $src == $blockasset['id'] && $blockasset['price']!=$asset[1] && $blockasset['tradable'] ==1) {
// if the asset has a price defined, check if the asset wallet owns all the asset units and only in this case allow transfers. In such cases, the asset should be sold on market
// on a fixed price always
$chk=$db->single("SELECT COUNT(1) FROM assets_balance WHERE asset=:asset AND account!=:account", [":account"=>$src, ":asset"=>$src]);
if ($chk!=0) {
_log("Initial asset distribution already done. Market orders only on fixed price.", 3);
return false;
}
}
}
// make sure the dividend only function is not bypassed after height X
if (($x['version']==1||$x['version']==2)&&$height>216000) {
$check=$db->single("SELECT COUNT(1) FROM assets WHERE id=:id AND dividend_only=1", [":id"=>$src]);
if ($check==1) {
_log("This asset wallet cannot send funds directly", 3);
return false;
}
}
// asset market orders
if ($x['version']==52) {
// we store the order details in a json array on the format [asset_id, price, amount of asset units, cancelable, order type ]
$asset=json_decode($x['message'], true);
if ($asset==false) {
_log("Invalid asset creation json", 3);
return false;
}
// only ask and bid allowed
if ($asset[4]!="ask"&&$asset[4]!="bid") {
_log("Invalid asset order type", 3);
return false;
}
$type=san($asset[4]);
$blockasset=$db->row("SELECT * FROM assets WHERE id=:id", [":id"=>san($asset[0])]);
if (!$blockasset||$blockasset['tradable']!=1) {
_log("Invalid asset", 3);
return false;
}
// the sale price per unit has to be at least 0.00000001 or max 1000000 aro
if (number_format($asset[1], 8, '.', '')!=$asset[1]||$asset[1]<=0||$asset[1]>1000000) {
_log("Invalid asset price", 3);
return false;
}
// integer min 1 and max 1000000
if (intval($asset[2])!=$asset[2]||$asset[2]<1||$asset[2]>1000000) {
_log("Invalid asset value", 3);
return false;
}
// if the order should be cancelable or not
if ($asset[3]!=1&&$asset[3]!=0) {
_log("Invalid asset cancel setting", 3);
return false;
}
// the type of order, ask or bid
if ($type=="ask") {
$balance=$db->single("SELECT balance FROM assets_balance WHERE asset=:asset AND account=:account", [":account"=>$src, ":asset"=>$asset[0]]);
if ($balance<$asset[2]) {
_log("Not enough asset balance", 3);
return false;
}
} else {
$balance=$acc->balance($src);
if ($balance<$asset[2]*$asset[1]) {
_log("Not enough aro balance", 3);
return false;
}
if ($blockasset['id']!=$src) {
$asset_bids_allowed=$db->single("SELECT COUNT(1) FROM assets WHERE id=:id AND allow_bid=0", [":id"=>$src]);
if ($asset_bids_allowed==1) {
_log("This wallet asset is not allowed to buy other assets", 3);
return false;
}
}
}
if ($blockasset['id']==$src && $blockasset['price']>0 && $blockasset['price']!=$asset[1]) {
// In case the asset has fixed price, the asset wallet cannot sell on a different price (to prevent abuse by the owner)
_log("This asset has fixed market price when sold by it's wallet", 3);
return false;
}
}
if ($x['version']==53) {
if (san($x['message'])!=$x['message']) {
_log("Invalid order id - $x[message]", 3);
return false;
}
$chk=$db->single("SELECT COUNT(1) FROM assets_market WHERE id=:id AND account=:src AND val_done<val AND status=0 AND cancelable=1", [":id"=>san($x['message']), ":src"=>$src]);
if ($chk!=1) {
_log("Invalid order - $x[message]", 3);
return false;
}
}
if ($x['version']==54) {
$balance=$acc->balance($src);
if ($balance<$x['val']||$x['val']<0.00000001) {
_log("Not enough aro balance", 3);
return false;
}
}
if ($x['version']==55) {
$plus=intval($x['message']);
if ($x['message']!=$plus) {
_log("Invalid asset value", 3);
return false;
}
$test=$db->single("SELECT COUNT(1) FROM assets WHERE id=:id AND max_supply=0", [":id"=>$src]);
if ($test!=1) {
_log("Asset not inflatable", 3);
return false;
}
$total=$db->single("SELECT SUM(balance) FROM assets_balance WHERE asset=:id", [":id"=>$src]);
$total+=$db->single("SELECT SUM(val-val_done) FROM assets_market WHERE status=0 AND type='ask' AND asset=:id", [":id"=>$src]);
if ($total+$plus>1000000000) {
_log("Maximum asset unit limits reached", 3);
return false;
}
}
// max fee after block 10800 is 10
if ($height > 10800 && $fee > 10) {
@@ -529,10 +1074,26 @@ class Transaction
}
}
//verify the ecdsa signature
if (!$acc->check_signature($info, $x['signature'], $x['public_key'])) {
_log("$x[id] - Invalid signature - $info");
return false;
if ($x['version']==106) {
// the masternode votes are using a different signature
$vote_key=$db->single("SELECT vote_key FROM masternode WHERE public_key=:public_key", [':public_key'=>$x['public_key']]);
if (empty($vote_key)) {
return false;
}
if (!$acc->check_signature($info, $x['signature'], $vote_key)) {
_log("$x[id] - Invalid vote key signature - $info");
return false;
}
} else {
//verify the ecdsa signature
if (!$acc->check_signature($info, $x['signature'], $x['public_key'])) {
_log("$x[id] - Invalid signature - $info");
return false;
}
}
return true;

View File

@@ -84,8 +84,9 @@ ini_set('memory_limit', '2G');
$block = new Block();
$acc = new Account();
$current = $block->current();
$trx= new Transaction();
$current = $block->current();
@@ -308,9 +309,9 @@ if ($total_peers == 0 && $_config['testnet'] == false) {
}
$peered[$pid] = 1;
if($_config['passive_peering'] == true){
if ($_config['passive_peering'] == true) {
// does not peer, just add it to DB in passive mode
$db->run("INSERT into peers set hostname=:hostname, ping=0, reserve=0,ip=:ip",[":hostname"=>$peer, ":ip"=>md5($peer)]);
$db->run("INSERT into peers set hostname=:hostname, ping=0, reserve=0,ip=:ip", [":hostname"=>$peer, ":ip"=>md5($peer)]);
$res=true;
} else {
// forces the other node to peer with us.
@@ -348,8 +349,8 @@ foreach ($r as $x) {
_log("Peer $x[hostname] unresponsive");
// if the peer is unresponsive, mark it as failed and blacklist it for a while
$db->run(
"UPDATE peers SET fails=fails+1, blacklisted=UNIX_TIMESTAMP()+((fails+1)*3600) WHERE id=:id",
[":id" => $x['id']]
"UPDATE peers SET fails=fails+1, blacklisted=UNIX_TIMESTAMP()+((fails+1)*3600) WHERE id=:id",
[":id" => $x['id']]
);
continue;
}
@@ -380,8 +381,8 @@ foreach ($r as $x) {
}
// make sure there's no peer in db with this ip or hostname
if (!$db->single(
"SELECT COUNT(1) FROM peers WHERE ip=:ip or hostname=:hostname",
[":ip" => $peer['ip'], ":hostname" => $peer['hostname']]
"SELECT COUNT(1) FROM peers WHERE ip=:ip or hostname=:hostname",
[":ip" => $peer['ip'], ":hostname" => $peer['hostname']]
)) {
$i++;
// check a max_test_peers number of peers from each peer
@@ -646,38 +647,43 @@ if ($current['height'] < $largest_height && $largest_height > 1) {
$resyncing=true;
}
if ($resyncing==true) {
_log("Resyncing accounts");
$db->run("INSERT into config SET val=UNIX_TIMESTAMP(), cfg='last_resync' ON DUPLICATE KEY UPDATE val=UNIX_TIMESTAMP()");
$db->exec("LOCK TABLES blocks WRITE, accounts WRITE, transactions WRITE, mempool WRITE");
$r=$db->run("SELECT * FROM accounts");
foreach ($r as $x) {
$alias=$x['alias'];
if (empty($alias)) {
$alias="A";
}
$rec=$db->single("SELECT SUM(val) FROM transactions WHERE (dst=:id or dst=:alias) AND (height<80000 OR version!=100) and version<111", [":id"=>$x['id'], ":alias"=>$alias]);
$releases=$db->single("SELECT COUNT(1) FROM transactions WHERE dst=:id AND version=103", [":id"=>$x['id']]);
if($releases>0){ //masternode releases
$rec+=$releases*100000;
}
$spent=$db->single("SELECT SUM(val+fee) FROM transactions WHERE public_key=:pub AND version>0", [":pub"=>$x['public_key']]);
if ($spent==false) {
$spent=0;
}
$balance=round(($rec-$spent), 8);
if ($x['balance']!=$balance) {
// echo "rec: $rec, spent: $spent, bal: $x[balance], should be: $balance - $x[id] $x[public_key]\n";
if (trim($argv[2])!="check") {
$db->run("UPDATE accounts SET balance=:bal WHERE id=:id", [":id"=>$x['id'], ":bal"=>$balance]);
}
}
}
$current = $block->current();
$db->run("DELETE FROM masternode WHERE height>:h", [":h"=>$current['height']]);
$db->exec("UNLOCK TABLES");
}
// needs to be redone due to the assets
// if ($resyncing==true) {
// _log("Resyncing accounts");
// $db->run("INSERT into config SET val=UNIX_TIMESTAMP(), cfg='last_resync' ON DUPLICATE KEY UPDATE val=UNIX_TIMESTAMP()");
// $db->exec("LOCK TABLES blocks WRITE, accounts WRITE, transactions WRITE, mempool WRITE, masternode WRITE, peers write, config WRITE, assets WRITE, assets_balance WRITE, assets_market WRITE");
// $r=$db->run("SELECT * FROM accounts");
// foreach ($r as $x) {
// $alias=$x['alias'];
// if (empty($alias)) {
// $alias="A";
// }
// $rec=$db->single("SELECT SUM(val) FROM transactions WHERE (dst=:id or dst=:alias) AND (height<80000 OR version!=100) and version<111", [":id"=>$x['id'], ":alias"=>$alias]);
// $releases=$db->single("SELECT COUNT(1) FROM transactions WHERE dst=:id AND version=103", [":id"=>$x['id']]);
// if ($releases>0) { //masternode releases
// $rec+=$releases*100000;
// }
// $spent=$db->single("SELECT SUM(val+fee) FROM transactions WHERE public_key=:pub AND version>0", [":pub"=>$x['public_key']]);
// if ($spent==false) {
// $spent=0;
// }
// $balance=round(($rec-$spent), 8);
// if ($x['balance']!=$balance) {
// // echo "rec: $rec, spent: $spent, bal: $x[balance], should be: $balance - $x[id] $x[public_key]\n";
// if (trim($argv[2])!="check") {
// $db->run("UPDATE accounts SET balance=:bal WHERE id=:id", [":id"=>$x['id'], ":bal"=>$balance]);
// }
// }
// }
// $current = $block->current();
// $db->run("DELETE FROM masternode WHERE height>:h", [":h"=>$current['height']]);
// $db->exec("UNLOCK TABLES");
// }
}
}
@@ -711,13 +717,15 @@ if ($_config['sanity_rebroadcast_locals'] == true && $_config['disable_repropaga
if ($_config['disable_repropagation'] == false) {
$forgotten = $current['height'] - $_config['sanity_rebroadcast_height'];
$r1 = $db->run(
"SELECT id FROM mempool WHERE height<:forgotten ORDER by val DESC LIMIT 10",
[":forgotten" => $forgotten]);
"SELECT id FROM mempool WHERE height<:forgotten ORDER by val DESC LIMIT 10",
[":forgotten" => $forgotten]
);
// getting some random transactions as well
$r2 = $db->run(
"SELECT id FROM mempool WHERE height<:forgotten ORDER by RAND() LIMIT 10",
[":forgotten" => $forgotten]);
$r=array_merge($r1,$r2);
"SELECT id FROM mempool WHERE height<:forgotten ORDER by RAND() LIMIT 10",
[":forgotten" => $forgotten]
);
$r=array_merge($r1, $r2);
_log("Rebroadcasting external transactions - ".count($r));
@@ -811,6 +819,90 @@ if ($_config['sanity_recheck_blocks'] > 0 && $_config['testnet'] == false) {
}
}
// not too often to not cause load
if (rand(0, 10)==1) {
// after 10000 blocks, clear asset internal transactions
$db->run("DELETE FROM transactions WHERE (version=57 or version=58 or version=59) AND height<:height", [":height"=>$current['height']-10000]);
// remove market orders that have been filled, after 10000 blocks
$r=$db->run("SELECT id FROM assets_market WHERE val_done=val or status=2");
foreach ($r as $x) {
$last=$db->single("SELECT height FROM transactions WHERE (public_key=:id or dst=:id2) ORDER by height DESC LIMIT 1", [":id"=>$x['id'], ":id2"=>$x['id']]);
if ($current['height']-$last>10000) {
$db->run("DELETE FROM assets_market WHERE id=:id", [":id"=>$x['id']]);
}
}
}
if($_config['masternode']==true&&!empty($_config['masternode_public_key'])&&!empty($_config['masternode_voting_public_key'])&&!empty($_config['masternode_voting_private_key'])){
echo "Masternode votes\n";
$r=$db->run("SELECT * FROM masternode WHERE status=1 ORDER by RAND() LIMIT 3");
foreach($r as $x){
$blacklist=0;
$x['ip']=san_ip($x['ip']);
echo "Testing masternode: $x[ip]\n";
$f=file_get_contents("http://$x[ip]/api.php?q=currentBlock");
if($f){
$res=json_decode($f,true);
$res=$res['data'];
if($res['height']<$current['height']-50){
$blacklist=1;
}
echo "Masternode Height: ".$res['height']."\n";
} else {
echo "---> Unresponsive\n";
$blacklist=1;
}
if($blacklist){
echo "Blacklisting masternode $x[public_key]\n";
$val='0.00000000';
$fee='0.00000001';
$date=time();
$version=106;
$msg=san($x['public_key']);
$address=$acc->get_address($x['public_key']);
$public_key=$_config['masternode_public_key'];
$private_key=$_config['masternode_voting_private_key'];
$info=$val."-".$fee."-".$address."-".$msg."-$version-".$public_key."-".$date;
$signature=ec_sign($info, $private_key);
$transaction = [
"src" => $acc->get_address($_config['masternode_public_key']),
"val" => $val,
"fee" => $fee,
"dst" => $address,
"public_key" => $public_key,
"date" => $date,
"version" => $version,
"message" => $msg,
"signature" => $signature,
];
$hash = $trx->hash($transaction);
$transaction['id'] = $hash;
if (!$trx->check($transaction)) {
print("Blacklist transaction signature failed\n");
}
$res = $db->single("SELECT COUNT(1) FROM mempool WHERE id=:id", [":id" => $hash]);
if ($res != 0) {
print("Blacklist transaction already in mempool\n");
}
$trx->add_mempool($transaction, "local");
$hash=escapeshellarg(san($hash));
system("php propagate.php transaction $hash > /dev/null 2>&1 &");
echo "Blacklist Hash: $hash\n";
}
}
}
_log("Finishing sanity");
@unlink(SANITY_LOCK_PATH);

View File

@@ -457,13 +457,15 @@ elseif ($cmd == 'get-address') {
$db->run("UPDATE peers SET blacklisted=0, fails=0, stuckfail=0");
echo "All the peers have been removed from the blacklist\n";
} elseif ($cmd == 'resync-accounts') {
die("Currently disabled due to asset implementation.");
// resyncs the balance on all accounts
if (file_exists("tmp/sanity-lock")) {
die("Sanity running. Wait for it to finish");
}
touch("tmp/sanity-lock");
// lock table to avoid race conditions on blocks
$db->exec("LOCK TABLES blocks WRITE, accounts WRITE, transactions WRITE, mempool WRITE");
$db->exec("LOCK TABLES blocks WRITE, accounts WRITE, transactions WRITE, mempool WRITE, masternode WRITE, peers write, config WRITE, assets WRITE, assets_balance WRITE, assets_market WRITE");
$r=$db->run("SELECT * FROM accounts");
foreach ($r as $x) {
@@ -473,8 +475,10 @@ elseif ($cmd == 'get-address') {
}
$rec=$db->single("SELECT SUM(val) FROM transactions WHERE (dst=:id or dst=:alias) AND (height<80000 OR version!=100) and version<111", [":id"=>$x['id'], ":alias"=>$alias]);
$releases=$db->single("SELECT COUNT(1) FROM transactions WHERE dst=:id AND version=103", [":id"=>$x['id']]);
if($releases>0){ //masternode releases
$rec+=$releases*100000;
if ($releases>0) { //masternode releases
$rec+=$releases*100000;
}
$spent=$db->single("SELECT SUM(val+fee) FROM transactions WHERE public_key=:pub AND version>0", [":pub"=>$x['public_key']]);
if ($spent==false) {
@@ -542,7 +546,7 @@ elseif ($cmd == 'get-address') {
echo "Hash:\t\t".md5(json_encode($res))."\n\n";
} elseif ($cmd == "version") {
echo "\n\n".VERSION."\n\n";
} elseif ($cmd == "sendblock"){
} elseif ($cmd == "sendblock") {
$peer=trim($argv[3]);
if (!filter_var($peer, FILTER_VALIDATE_URL)) {
die("Invalid peer hostname");
@@ -553,13 +557,12 @@ elseif ($cmd == 'get-address') {
$data = $block->export("", $height);
if($data===false){
if ($data===false) {
die("Could not find this block");
}
$response = peer_post($peer."/peer.php?q=submitBlock", $data, 60, true);
var_dump($response);
}elseif ($cmd == "recheck-external-blocks") {
} elseif ($cmd == "recheck-external-blocks") {
$peer=trim($argv[2]);
if (!filter_var($peer, FILTER_VALIDATE_URL)) {
die("Invalid peer hostname");
@@ -573,10 +576,10 @@ elseif ($cmd == 'get-address') {
$last=peer_post($peer."/peer.php?q=currentBlock");
$b=peer_post($peer."/peer.php?q=getBlock",["height"=>$height]);
$b=peer_post($peer."/peer.php?q=getBlock", ["height"=>$height]);
for ($i = $height+1; $i <= $last['height']; $i++) {
$c=peer_post($peer."/peer.php?q=getBlock",["height"=>$i]);
$c=peer_post($peer."/peer.php?q=getBlock", ["height"=>$i]);
if (!$block->mine(
$c['public_key'],
@@ -589,12 +592,10 @@ elseif ($cmd == 'get-address') {
)) {
print("Invalid block detected. $c[height] - $c[id]\n");
break;
}
}
echo "Block $i -> ok\n";
$b=$c;
}
} else {
echo "Invalid command\n";
}