diff --git a/api.php b/api.php index 2db89b1..1a65f69 100755 --- a/api.php +++ b/api.php @@ -1,7 +1,7 @@ get_address($public_key)); -} -elseif($q=="base58"){ -/** - * @api {get} /api.php?q=base58 03. base58 - * @apiName base58 - * @apiGroup API - * @apiDescription Converts a string to base58. - * - * @apiParam {string} data Input string - * - * @apiSuccess {string} data Output string - */ +} elseif ($q == "base58") { + /** + * @api {get} /api.php?q=base58 03. base58 + * @apiName base58 + * @apiGroup API + * @apiDescription Converts a string to base58. + * + * @apiParam {string} data Input string + * + * @apiSuccess {string} data Output string + */ api_echo(base58_encode($data['data'])); -} -elseif($q=="getBalance"){ -/** - * @api {get} /api.php?q=getBalance 04. getBalance - * @apiName getBalance - * @apiGroup API - * @apiDescription Returns the balance of a specific account or public key. - * - * @apiParam {string} [public_key] Public key - * @apiParam {string} [account] Account id / address - * - * @apiSuccess {string} data The ARO balance - */ +} elseif ($q == "getBalance") { + /** + * @api {get} /api.php?q=getBalance 04. getBalance + * @apiName getBalance + * @apiGroup API + * @apiDescription Returns the balance of a specific account or public key. + * + * @apiParam {string} [public_key] Public key + * @apiParam {string} [account] Account id / address + * + * @apiSuccess {string} data The ARO balance + */ - $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); + $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"){ -/** - * @api {get} /api.php?q=getPendingBalance 05. getPendingBalance - * @apiName getPendingBalance - * @apiGroup API - * @apiDescription Returns the pending balance, which includes pending transactions, of a specific account or public key. - * - * @apiParam {string} [public_key] Public key - * @apiParam {string} [account] Account id / address - * - * @apiSuccess {string} data The ARO balance - */ - - $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); +} elseif ($q == "getPendingBalance") { + /** + * @api {get} /api.php?q=getPendingBalance 05. getPendingBalance + * @apiName getPendingBalance + * @apiGroup API + * @apiDescription Returns the pending balance, which includes pending transactions, of a specific account or public key. + * + * @apiParam {string} [public_key] Public key + * @apiParam {string} [account] Account id / address + * + * @apiSuccess {string} data The ARO balance + */ + + $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->pending_balance($account)); -} -elseif($q=="getTransactions"){ -/** - * @api {get} /api.php?q=getTransactions 06. getTransactions - * @apiName getTransactions - * @apiGroup API - * @apiDescription Returns the latest transactions of an account. - * - * @apiParam {string} [public_key] Public key - * @apiParam {string} [account] Account id / address - * @apiParam {numeric} [limit] Number of confirmed transactions, max 1000, min 1 - * - * @apiSuccess {string} block Block ID - * @apiSuccess {numeric} confirmation Number of confirmations - * @apiSuccess {numeric} date Transaction's date in UNIX TIMESTAMP format - * @apiSuccess {string} dst Transaction destination - * @apiSuccess {numeric} fee The transaction's fee - * @apiSuccess {numeric} height Block height - * @apiSuccess {string} id Transaction ID/HASH - * @apiSuccess {string} message Transaction's message - * @apiSuccess {string} signature Transaction's signature - * @apiSuccess {string} public_key Account's public_key - * @apiSuccess {string} src Sender's address - * @apiSuccess {string} type "debit", "credit" or "mempool" - * @apiSuccess {numeric} val Transaction value - * @apiSuccess {numeric} version Transaction version - */ +} elseif ($q == "getTransactions") { + /** + * @api {get} /api.php?q=getTransactions 06. getTransactions + * @apiName getTransactions + * @apiGroup API + * @apiDescription Returns the latest transactions of an account. + * + * @apiParam {string} [public_key] Public key + * @apiParam {string} [account] Account id / address + * @apiParam {numeric} [limit] Number of confirmed transactions, max 1000, min 1 + * + * @apiSuccess {string} block Block ID + * @apiSuccess {numeric} confirmation Number of confirmations + * @apiSuccess {numeric} date Transaction's date in UNIX TIMESTAMP format + * @apiSuccess {string} dst Transaction destination + * @apiSuccess {numeric} fee The transaction's fee + * @apiSuccess {numeric} height Block height + * @apiSuccess {string} id Transaction ID/HASH + * @apiSuccess {string} message Transaction's message + * @apiSuccess {string} signature Transaction's signature + * @apiSuccess {string} public_key Account's public_key + * @apiSuccess {string} src Sender's address + * @apiSuccess {string} type "debit", "credit" or "mempool" + * @apiSuccess {numeric} val Transaction value + * @apiSuccess {numeric} version Transaction version + */ - $account=san($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($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"); + } - $limit=intval($data['limit']); - $transactions=$acc->get_mempool_transactions($account); - $transactions=array_merge($transactions, $acc->get_transactions($account,$limit)); + $limit = intval($data['limit']); + $transactions = $acc->get_mempool_transactions($account); + $transactions = array_merge($transactions, $acc->get_transactions($account, $limit)); api_echo($transactions); +} elseif ($q == "getTransaction") { + /** + * @api {get} /api.php?q=getTransaction 07. getTransaction + * @apiName getTransaction + * @apiGroup API + * @apiDescription Returns one transaction. + * + * @apiParam {string} transaction Transaction ID + * + * @apiSuccess {string} block Block ID + * @apiSuccess {numeric} confirmation Number of confirmations + * @apiSuccess {numeric} date Transaction's date in UNIX TIMESTAMP format + * @apiSuccess {string} dst Transaction destination + * @apiSuccess {numeric} fee The transaction's fee + * @apiSuccess {numeric} height Block height + * @apiSuccess {string} id Transaction ID/HASH + * @apiSuccess {string} message Transaction's message + * @apiSuccess {string} signature Transaction's signature + * @apiSuccess {string} public_key Account's public_key + * @apiSuccess {string} src Sender's address + * @apiSuccess {string} type "debit", "credit" or "mempool" + * @apiSuccess {numeric} val Transaction value + * @apiSuccess {numeric} version Transaction version + */ -} elseif($q=="getTransaction"){ -/** - * @api {get} /api.php?q=getTransaction 07. getTransaction - * @apiName getTransaction - * @apiGroup API - * @apiDescription Returns one transaction. - * - * @apiParam {string} transaction Transaction ID - * - * @apiSuccess {string} block Block ID - * @apiSuccess {numeric} confirmation Number of confirmations - * @apiSuccess {numeric} date Transaction's date in UNIX TIMESTAMP format - * @apiSuccess {string} dst Transaction destination - * @apiSuccess {numeric} fee The transaction's fee - * @apiSuccess {numeric} height Block height - * @apiSuccess {string} id Transaction ID/HASH - * @apiSuccess {string} message Transaction's message - * @apiSuccess {string} signature Transaction's signature - * @apiSuccess {string} public_key Account's public_key - * @apiSuccess {string} src Sender's address - * @apiSuccess {string} type "debit", "credit" or "mempool" - * @apiSuccess {numeric} val Transaction value - * @apiSuccess {numeric} version Transaction version - */ - - $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"); + $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=="getPublicKey"){ -/** - * @api {get} /api.php?q=getPublicKey 08. getPublicKey - * @apiName getPublicKey - * @apiGroup API - * @apiDescription Returns the public key of a specific account. - * - * @apiParam {string} account Account id / address - * - * @apiSuccess {string} data The public key - */ +} elseif ($q == "getPublicKey") { + /** + * @api {get} /api.php?q=getPublicKey 08. getPublicKey + * @apiName getPublicKey + * @apiGroup API + * @apiDescription Returns the public key of a specific account. + * + * @apiParam {string} account Account id / address + * + * @apiSuccess {string} data The public key + */ - $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); + $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 == "generateAccount") { + /** + * @api {get} /api.php?q=generateAccount 09. generateAccount + * @apiName generateAccount + * @apiGroup API + * @apiDescription Generates a new account. This function should only be used when the node is on the same host or over a really secure network. + * + * @apiSuccess {string} address Account address + * @apiSuccess {string} public_key Public key + * @apiSuccess {string} private_key Private key + */ - -} elseif($q=="generateAccount"){ -/** - * @api {get} /api.php?q=generateAccount 09. generateAccount - * @apiName generateAccount - * @apiGroup API - * @apiDescription Generates a new account. This function should only be used when the node is on the same host or over a really secure network. - * - * @apiSuccess {string} address Account address - * @apiSuccess {string} public_key Public key - * @apiSuccess {string} private_key Private key - */ + $acc = new Account(); + $res = $acc->generate_account(); + api_echo($res); +} elseif ($q == "currentBlock") { + /** + * @api {get} /api.php?q=currentBlock 10. currentBlock + * @apiName currentBlock + * @apiGroup API + * @apiDescription Returns the current block. + * + * @apiSuccess {string} id Blocks id + * @apiSuccess {string} generator Block Generator + * @apiSuccess {numeric} height Height + * @apiSuccess {numeric} date Block's date in UNIX TIMESTAMP format + * @apiSuccess {string} nonce Mining nonce + * @apiSuccess {string} signature Signature signed by the generator + * @apiSuccess {numeric} difficulty The base target / difficulty + * @apiSuccess {string} argon Mining argon hash + */ - $acc=new Account; - $res=$acc->generate_account(); - api_echo($res); -} elseif($q=="currentBlock"){ -/** - * @api {get} /api.php?q=currentBlock 10. currentBlock - * @apiName currentBlock - * @apiGroup API - * @apiDescription Returns the current block. - * - * @apiSuccess {string} id Blocks id - * @apiSuccess {string} generator Block Generator - * @apiSuccess {numeric} height Height - * @apiSuccess {numeric} date Block's date in UNIX TIMESTAMP format - * @apiSuccess {string} nonce Mining nonce - * @apiSuccess {string} signature Signature signed by the generator - * @apiSuccess {numeric} difficulty The base target / difficulty - * @apiSuccess {string} argon Mining argon hash + $current = $block->current(); + api_echo($current); +} elseif ($q == "getBlock") { + /** + * @api {get} /api.php?q=getBlock 11. getBlock + * @apiName getBlock + * @apiGroup API + * @apiDescription Returns the block. + * + * @apiParam {numeric} height Block Height + * + * @apiSuccess {string} id Block id + * @apiSuccess {string} generator Block Generator + * @apiSuccess {numeric} height Height + * @apiSuccess {numeric} date Block's date in UNIX TIMESTAMP format + * @apiSuccess {string} nonce Mining nonce + * @apiSuccess {string} signature Signature signed by the generator + * @apiSuccess {numeric} difficulty The base target / difficulty + * @apiSuccess {string} argon Mining argon hash + */ + $height = san($data['height']); + $ret = $block->get($height); + if ($ret == false) { + api_err("Invalid block"); + } else { + api_echo($ret); + } +} elseif ($q == "getBlockTransactions") { + /** + * @api {get} /api.php?q=getBlockTransactions 12. getBlockTransactions + * @apiName getBlockTransactions + * @apiGroup API + * @apiDescription Returns the transactions of a specific block. + * + * @apiParam {numeric} [height] Block Height + * @apiParam {string} [block] Block id + * + * @apiSuccess {string} block Block ID + * @apiSuccess {numeric} confirmations Number of confirmations + * @apiSuccess {numeric} date Transaction's date in UNIX TIMESTAMP format + * @apiSuccess {string} dst Transaction destination + * @apiSuccess {numeric} fee The transaction's fee + * @apiSuccess {numeric} height Block height + * @apiSuccess {string} id Transaction ID/HASH + * @apiSuccess {string} message Transaction's message + * @apiSuccess {string} signature Transaction's signature + * @apiSuccess {string} public_key Account's public_key + * @apiSuccess {string} src Sender's address + * @apiSuccess {string} type "debit", "credit" or "mempool" + * @apiSuccess {numeric} val Transaction value + * @apiSuccess {numeric} version Transaction version + */ + $height = san($data['height']); + $block = san($data['block']); + $ret = $trx->get_transactions($height, $block); + if ($ret === false) { + api_err("Invalid block"); + } else { + api_echo($ret); + } +} elseif ($q == "version") { + /** + * @api {get} /api.php?q=version 13. version + * @apiName version + * @apiGroup API + * @apiDescription Returns the node's version. + * + * + * @apiSuccess {string} data Version + */ + api_echo(VERSION); +} elseif ($q == "send") { + /** + * @api {get} /api.php?q=send 14. send + * @apiName send + * @apiGroup API + * @apiDescription Sends a transaction. + * + * @apiParam {numeric} val Transaction value (without fees) + * @apiParam {string} dst Destination address + * @apiParam {string} public_key Sender's public key + * @apiParam {string} [signature] Transaction signature. It's recommended that the transaction is signed before being sent to the node to avoid sending your private key to the node. + * @apiParam {string} [private_key] Sender's private key. Only to be used when the transaction is not signed locally. + * @apiParam {numeric} [date] Transaction's date in UNIX TIMESTAMP format. Requried when the transaction is pre-signed. + * @apiParam {string} [message] A message to be included with the transaction. Maximum 128 chars. + * @apiParam {numeric} [version] The version of the transaction. 1 to send coins. + * + * @apiSuccess {string} data Transaction id + */ + $current = $block->current(); + + if ($current['height'] > 10790 && $current['height'] < 10810) { + api_err("Hard fork in progress. Please retry the transaction later!"); //10800 + } + + $acc = new Account(); + $block = new Block(); + + $trx = new Transaction(); + + $dst = san($data['dst']); + + if (!$acc->valid($dst)) { + api_err("Invalid destination address"); + } + $dst_b = base58_decode($dst); + if (strlen($dst_b) != 64) { + 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; - $current=$block->current(); - api_echo($current); - -} elseif($q=="getBlock"){ -/** - * @api {get} /api.php?q=getBlock 11. getBlock - * @apiName getBlock - * @apiGroup API - * @apiDescription Returns the block. - * - * @apiParam {numeric} height Block Height - * - * @apiSuccess {string} id Block id - * @apiSuccess {string} generator Block Generator - * @apiSuccess {numeric} height Height - * @apiSuccess {numeric} date Block's date in UNIX TIMESTAMP format - * @apiSuccess {string} nonce Mining nonce - * @apiSuccess {string} signature Signature signed by the generator - * @apiSuccess {numeric} difficulty The base target / difficulty - * @apiSuccess {string} argon Mining argon hash - */ - $height=san($data['height']); - $ret=$block->get($height); - if($ret==false) api_err("Invalid block"); - else api_echo($ret); -} elseif($q=="getBlockTransactions"){ -/** - * @api {get} /api.php?q=getBlockTransactions 12. getBlockTransactions - * @apiName getBlockTransactions - * @apiGroup API - * @apiDescription Returns the transactions of a specific block. - * - * @apiParam {numeric} [height] Block Height - * @apiParam {string} [block] Block id - * - * @apiSuccess {string} block Block ID - * @apiSuccess {numeric} confirmations Number of confirmations - * @apiSuccess {numeric} date Transaction's date in UNIX TIMESTAMP format - * @apiSuccess {string} dst Transaction destination - * @apiSuccess {numeric} fee The transaction's fee - * @apiSuccess {numeric} height Block height - * @apiSuccess {string} id Transaction ID/HASH - * @apiSuccess {string} message Transaction's message - * @apiSuccess {string} signature Transaction's signature - * @apiSuccess {string} public_key Account's public_key - * @apiSuccess {string} src Sender's address - * @apiSuccess {string} type "debit", "credit" or "mempool" - * @apiSuccess {numeric} val Transaction value - * @apiSuccess {numeric} version Transaction version - */ - $height=san($data['height']); - $block=san($data['block']); - $ret=$trx->get_transactions($height, $block); - if($ret===false) api_err("Invalid block"); - else api_echo($ret); - -} elseif($q=="version"){ -/** - * @api {get} /api.php?q=version 13. version - * @apiName version - * @apiGroup API - * @apiDescription Returns the node's version. - * - * - * @apiSuccess {string} data Version -*/ - api_echo(VERSION); - -} elseif($q=="send"){ -/** - * @api {get} /api.php?q=send 14. send - * @apiName send - * @apiGroup API - * @apiDescription Sends a transaction. - * - * @apiParam {numeric} val Transaction value (without fees) - * @apiParam {string} dst Destination address - * @apiParam {string} public_key Sender's public key - * @apiParam {string} [signature] Transaction signature. It's recommended that the transaction is signed before being sent to the node to avoid sending your private key to the node. - * @apiParam {string} [private_key] Sender's private key. Only to be used when the transaction is not signed locally. - * @apiParam {numeric} [date] Transaction's date in UNIX TIMESTAMP format. Requried when the transaction is pre-signed. - * @apiParam {string} [message] A message to be included with the transaction. Maximum 128 chars. - * @apiParam {numeric} [version] The version of the transaction. 1 to send coins. - * - * @apiSuccess {string} data Transaction id - */ - $current=$block->current(); - - if($current['height']>10790&&$current['height']<10810) api_err("Hard fork in progress. Please retry the transaction later!"); //10800 - - $acc = new Account; - $block = new Block; - - $trx = new Transaction; - - $dst=san($data['dst']); - - if(!$acc->valid($dst)) api_err("Invalid destination address"); - $dst_b=base58_decode($dst); - if(strlen($dst_b)!=64) api_err("Invalid destination address"); + 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; + } - $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($datetime()+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 ($fee > 10 && $current['height'] > 10800) { + $fee = 10; //10800 + } + if ($val < 0.00000001) { + api_err("Invalid value"); + } - if($fee>10&&$current['height']>10800) $fee=10; //10800 - if($val<0.00000001) api_err("Invalid value"); - - if($version<1) $version=1; + if ($version < 1) { + $version = 1; + } - $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); + $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']); - + $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; - + $transaction = [ + "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)"); - - - + + + $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", [":id" => $hash]); + if ($res != 0) { + api_err("The transaction is already in mempool"); + } + + $res = $db->single("SELECT COUNT(1) FROM transactions WHERE id=:id", [":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", [":id" => $src]); + if ($balance < $val + $fee) { + api_err("Not enough funds"); + } + + + $memspent = $db->single("SELECT SUM(val+fee) FROM mempool WHERE src=:src", [":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 2>&1 &"); api_echo($hash); -} elseif($q=="mempoolSize"){ -/** - * @api {get} /api.php?q=mempoolSize 15. mempoolSize - * @apiName mempoolSize - * @apiGroup API - * @apiDescription Returns the number of transactions in mempool. - * - * @apiSuccess {numeric} data Number of mempool transactions - */ +} elseif ($q == "mempoolSize") { + /** + * @api {get} /api.php?q=mempoolSize 15. mempoolSize + * @apiName mempoolSize + * @apiGroup API + * @apiDescription Returns the number of transactions in mempool. + * + * @apiSuccess {numeric} data Number of mempool transactions + */ - $res=$db->single("SELECT COUNT(1) FROM mempool"); + $res = $db->single("SELECT COUNT(1) FROM mempool"); api_echo($res); +} elseif ($q == 'randomNumber') { + /** + * @api {get} /api.php?q=randomNumber 16. randomNumber + * @apiName randomNumber + * @apiGroup API + * @apiDescription Returns a random number based on an ARO block id. + * + * @apiParam {numeric} height The height of the block on which the random number will be based on (should be a future block when starting) + * @apiParam {numeric} min Minimum number (default 1) + * @apiParam {numeric} max Maximum number + * @apiParam {string} seed A seed to generate different numbers for each use cases. + * @apiSuccess {numeric} data The random number + */ -} elseif($q=='randomNumber'){ -/** - * @api {get} /api.php?q=randomNumber 16. randomNumber - * @apiName randomNumber - * @apiGroup API - * @apiDescription Returns a random number based on an ARO block id. - * - * @apiParam {numeric} height The height of the block on which the random number will be based on (should be a future block when starting) - * @apiParam {numeric} min Minimum number (default 1) - * @apiParam {numeric} max Maximum number - * @apiParam {string} seed A seed to generate different numbers for each use cases. - * @apiSuccess {numeric} data The random number - */ + $height = san($_GET['height']); + $max = intval($_GET['max']); + if (empty($_GET['min'])) { + $min = 1; + } else { + $min = intval($_GET['min']); + } - $height=san($_GET['height']); - $max=intval($_GET['max']); - if(empty($_GET['min'])) $min=1; - else $min=intval($_GET['min']); - - $blk=$db->single("SELECT id FROM blocks WHERE height=:h",array(":h"=>$height)); - if($blk===false) api_err("Unknown block. Future?"); - $base=hash("sha256",$blk.$_GET['seed']); + $blk = $db->single("SELECT id FROM blocks WHERE height=:h", [":h" => $height]); + if ($blk === false) { + api_err("Unknown block. Future?"); + } + $base = hash("sha256", $blk.$_GET['seed']); - $seed1=hexdec(substr($base,0,12)); + $seed1 = hexdec(substr($base, 0, 12)); // generate random numbers based on the seed - mt_srand($seed1,MT_RAND_MT19937 ); - $res=mt_rand($min, $max); + mt_srand($seed1, MT_RAND_MT19937); + $res = mt_rand($min, $max); api_echo($res); - - - } else { - api_err("Invalid request"); - } -?> + api_err("Invalid request"); +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..069931c --- /dev/null +++ b/composer.json @@ -0,0 +1,19 @@ +{ + "name": "arionum/node", + "description": "The Arionum (ARO) cryptocurrency node.", + "license": "MIT", + "require": { + "php": "^7.2", + "ext-bcmath": "*", + "ext-gmp": "*", + "ext-openssl": "*", + "ext-pdo": "*" + }, + "require-dev": { + "squizlabs/php_codesniffer": "^3.2" + }, + "scripts": { + "check-style": "phpcs -p -l --standard=PSR2 --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 . include", + "fix-style": "phpcbf -p -l --standard=PSR2 --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 . include" + } +} diff --git a/include/account.inc.php b/include/account.inc.php index 85007ab..ee90df8 100755 --- a/include/account.inc.php +++ b/include/account.inc.php @@ -1,162 +1,237 @@ 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); - } - // inserts just the account without public key - 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); - } - // generates Account's address from the public key - public function get_address($hash){ +class Account +{ + // inserts the account in the DB and updates the public key if empty + public function add($public_key, $block) + { + global $db; + $id = $this->get_address($public_key); + $bind = [":id" => $id, ":public_key" => $public_key, ":block" => $block, ":public_key2" => $public_key]; - //broken base58 addresses, which are block winners, missing the first 0 bytes from the address. - if($hash=='PZ8Tyr4Nx8MHsRAGMpZmZ6TWY63dXWSCwCpspGFGQSaF9yVGLamBgymdf8M7FafghmP3oPzQb3W4PZsZApVa41uQrrHRVBH5p9bdoz7c6XeRQHK2TkzWR45e') return '22SoB29oyq2JhMxtBbesL7JioEYytyC6VeFmzvBH6fRQrueSvyZfEXR5oR7ajSQ9mLERn6JKU85EAbVDNChke32'; - elseif($hash=='PZ8Tyr4Nx8MHsRAGMpZmZ6TWY63dXWSCzbRyyz5oDNDKhk5jyjg4caRjkbqegMZMrUkuBjVMuYcVfPyc3aKuLmPHS4QEDjCrNGks7Z5oPxwv4yXSv7WJnkbL') return 'AoFnv3SLujrJSa2J7FDTADGD7Eb9kv3KtNAp7YVYQEUPcLE6cC6nLvvhVqcVnRLYF5BFF38C1DyunUtmfJBhyU'; - elseif($hash=='PZ8Tyr4Nx8MHsRAGMpZmZ6TWY63dXWSCyradtFFJoaYB4QdcXyBGSXjiASMMnofsT4f5ZNaxTnNDJt91ubemn3LzgKrfQh8CBpqaphkVNoRLub2ctdMnrzG1') return 'RncXQuc7S7aWkvTUJSHEFvYoV3ntAf7bfxEHjSiZNBvQV37MzZtg44L7GAV7szZ3uV8qWqikBewa3piZMqzBqm'; - elseif($hash=='PZ8Tyr4Nx8MHsRAGMpZmZ6TWY63dXWSCyjKMBY4ihhJ2G25EVezg7KnoCBVbhdvWfqzNA4LC5R7wgu3VNfJgvqkCq9sKKZcCoCpX6Qr9cN882MoXsfGTvZoj') return 'Rq53oLzpCrb4BdJZ1jqQ2zsixV2ukxVdM4H9uvUhCGJCz1q2wagvuXV4hC6UVwK7HqAt1FenukzhVXgzyG1y32'; + $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 + ); + } - - // hashes 9 times in sha512 (binary) and encodes in base58 - for($i=0;$i<9;$i++) $hash=hash('sha512',$hash, true); - return base58_encode($hash); - + // inserts just the account without public key + public function add_id($id, $block) + { + global $db; + $bind = [":id" => $id, ":block" => $block]; + $db->run("INSERT ignore INTO accounts SET id=:id, public_key='', block=:block, balance=0", $bind); + } - } - // checks the ecdsa secp256k1 signature for a specific public key - public function check_signature($data, $signature, $public_key){ - - return ec_verify($data ,$signature, $public_key); - } + // generates Account's address from the public key + public function get_address($hash) + { + //broken base58 addresses, which are block winners, missing the first 0 bytes from the address. + if ($hash == 'PZ8Tyr4Nx8MHsRAGMpZmZ6TWY63dXWSCwCpspGFGQSaF9yVGLamBgymdf8M7FafghmP3oPzQb3W4PZsZApVa41uQrrHRVBH5p9bdoz7c6XeRQHK2TkzWR45e') { + return '22SoB29oyq2JhMxtBbesL7JioEYytyC6VeFmzvBH6fRQrueSvyZfEXR5oR7ajSQ9mLERn6JKU85EAbVDNChke32'; + } elseif ($hash == 'PZ8Tyr4Nx8MHsRAGMpZmZ6TWY63dXWSCzbRyyz5oDNDKhk5jyjg4caRjkbqegMZMrUkuBjVMuYcVfPyc3aKuLmPHS4QEDjCrNGks7Z5oPxwv4yXSv7WJnkbL') { + return 'AoFnv3SLujrJSa2J7FDTADGD7Eb9kv3KtNAp7YVYQEUPcLE6cC6nLvvhVqcVnRLYF5BFF38C1DyunUtmfJBhyU'; + } elseif ($hash == 'PZ8Tyr4Nx8MHsRAGMpZmZ6TWY63dXWSCyradtFFJoaYB4QdcXyBGSXjiASMMnofsT4f5ZNaxTnNDJt91ubemn3LzgKrfQh8CBpqaphkVNoRLub2ctdMnrzG1') { + return 'RncXQuc7S7aWkvTUJSHEFvYoV3ntAf7bfxEHjSiZNBvQV37MzZtg44L7GAV7szZ3uV8qWqikBewa3piZMqzBqm'; + } elseif ($hash == 'PZ8Tyr4Nx8MHsRAGMpZmZ6TWY63dXWSCyjKMBY4ihhJ2G25EVezg7KnoCBVbhdvWfqzNA4LC5R7wgu3VNfJgvqkCq9sKKZcCoCpX6Qr9cN882MoXsfGTvZoj') { + return 'Rq53oLzpCrb4BdJZ1jqQ2zsixV2ukxVdM4H9uvUhCGJCz1q2wagvuXV4hC6UVwK7HqAt1FenukzhVXgzyG1y32'; + } - // generates a new account and a public/private key pair - public function generate_account(){ - // using secp256k1 curve for ECDSA - $args = array( - "curve_name" => "secp256k1", - "private_key_type" => OPENSSL_KEYTYPE_EC, - ); - - // generates a new key pair - $key1 = openssl_pkey_new($args); + // hashes 9 times in sha512 (binary) and encodes in base58 + for ($i = 0; $i < 9; + $i++) { + $hash = hash('sha512', $hash, true); + } + return base58_encode($hash); + } - // exports the private key encoded as PEM - openssl_pkey_export($key1, $pvkey); + // checks the ecdsa secp256k1 signature for a specific public key + public function check_signature($data, $signature, $public_key) + { + return ec_verify($data, $signature, $public_key); + } - // converts the PEM to a base58 format - $private_key= pem2coin($pvkey); + // generates a new account and a public/private key pair + public function generate_account() + { + // using secp256k1 curve for ECDSA + $args = [ + "curve_name" => "secp256k1", + "private_key_type" => OPENSSL_KEYTYPE_EC, + ]; - // exports the private key encoded as PEM - $pub = openssl_pkey_get_details($key1); - - // converts the PEM to a base58 format - $public_key= pem2coin($pub['key']); - - // generates the account's address based on the public key - $address=$this->get_address($public_key); - return array("address"=>$address, "public_key"=>$public_key,"private_key"=>$private_key); + // generates a new key pair + $key1 = openssl_pkey_new($args); + // exports the private key encoded as PEM + openssl_pkey_export($key1, $pvkey); - } - // check the validity of a base58 encoded key. At the moment, it checks only the characters to be base58. - public function valid_key($id){ - $chars = str_split("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"); - for($i=0;$i=70 and <=128. - public function valid($id){ - if(strlen($id)<70||strlen($id)>128) return false; - $chars = str_split("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"); - for($i=0;$iget_address($public_key); + return ["address" => $address, "public_key" => $public_key, "private_key" => $private_key]; + } - } - // returns the current account balance - 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 number_format($res,8,".",""); - } - // returns the account balance - any pending debits from the mempool - 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"; + // check the validity of a base58 encoded key. At the moment, it checks only the characters to be base58. + public function valid_key($id) + { + $chars = str_split("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"); + for ($i = 0; $i < strlen($id); + $i++) { + if (!in_array($id[$i], $chars)) { + return false; + } + } - // if the original balance is 0, no mempool transactions are possible - 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 number_format($rez,8,".",""); - - } - // returns all the transactions of a specific address - public function get_transactions($id,$limit=100){ - global $db; - $block=new Block; - $current=$block->current(); - $public_key=$this->public_key($id); - $limit=intval($limit); - if($limit>100||$limit<1) $limit=100; - $res=$db->run("SELECT * FROM transactions WHERE dst=:dst or public_key=:src ORDER by height DESC LIMIT :limit",array(":src"=>$public_key, ":dst"=>$id, ":limit"=>$limit)); - - $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']; - - // version 0 -> reward transaction, version 1 -> normal transaction - 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; - } - // returns the transactions from the mempool - 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"; - // they are unconfirmed, so they will have -1 confirmations. - $trans['confirmations']=-1; - ksort($trans); - $transactions[]=$trans; - } - return $transactions; - } - // returns the public key for a specific account - public function public_key($id){ - global $db; - $res=$db->single("SELECT public_key FROM accounts WHERE id=:id",array(":id"=>$id)); - return $res; - } + return true; + } + + // check the validity of an address. At the moment, it checks only the characters to be base58 and the length to be >=70 and <=128. + 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; + } + + // returns the current account balance + public function balance($id) + { + global $db; + $res = $db->single("SELECT balance FROM accounts WHERE id=:id", [":id" => $id]); + if ($res === false) { + $res = "0.00000000"; + } + + return number_format($res, 8, ".", ""); + } + + // returns the account balance - any pending debits from the mempool + public function pending_balance($id) + { + global $db; + $res = $db->single("SELECT balance FROM accounts WHERE id=:id", [":id" => $id]); + if ($res === false) { + $res = "0.00000000"; + } + + // if the original balance is 0, no mempool transactions are possible + if ($res == "0.00000000") { + return $res; + } + $mem = $db->single("SELECT SUM(val+fee) FROM mempool WHERE src=:id", [":id" => $id]); + $rez = $res - $mem; + return number_format($rez, 8, ".", ""); + } + + // returns all the transactions of a specific address + public function get_transactions($id, $limit = 100) + { + global $db; + $block = new Block(); + $current = $block->current(); + $public_key = $this->public_key($id); + $limit = intval($limit); + if ($limit > 100 || $limit < 1) { + $limit = 100; + } + $res = $db->run( + "SELECT * FROM transactions WHERE dst=:dst or public_key=:src ORDER by height DESC LIMIT :limit", + [":src" => $public_key, ":dst" => $id, ":limit" => $limit] + ); + + $transactions = []; + foreach ($res as $x) { + $trans = [ + "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']; + + // version 0 -> reward transaction, version 1 -> normal transaction + 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; + } + + // returns the transactions from the mempool + public function get_mempool_transactions($id) + { + global $db; + $transactions = []; + $res = $db->run( + "SELECT * FROM mempool WHERE src=:src ORDER by height DESC LIMIT 100", + [":src" => $id, ":dst" => $id] + ); + foreach ($res as $x) { + $trans = [ + "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"; + // they are unconfirmed, so they will have -1 confirmations. + $trans['confirmations'] = -1; + ksort($trans); + $transactions[] = $trans; + } + return $transactions; + } + + // returns the public key for a specific account + public function public_key($id) + { + global $db; + $res = $db->single("SELECT public_key FROM accounts WHERE id=:id", [":id" => $id]); + return $res; + } } - - - -?> diff --git a/include/block.inc.php b/include/block.inc.php index fddea0b..57b87e2 100755 --- a/include/block.inc.php +++ b/include/block.inc.php @@ -1,487 +1,652 @@ get_address($public_key); + + // the transactions are always sorted in the same way, on all nodes, as they are hashed as json + ksort($data); + + // create the hash / block id + $hash = $this->hash($generator, $height, $date, $nonce, $data, $signature, $difficulty, $argon); + //fix for the broken base58 library used until block 16900, trimming the first 0 bytes. + if ($height < 16900) { + $hash = ltrim($hash, '1'); + } + + $json = json_encode($data); + + // create the block data and check it against the signature + $info = "{$generator}-{$height}-{$date}-{$nonce}-{$json}-{$difficulty}-{$argon}"; + if (!$acc->check_signature($info, $signature, $public_key)) { + _log("Block signature check failed"); + return false; + } + + if (!$this->parse_block($hash, $height, $data, true)) { + _log("Parse block failed"); + return false; + } + + // lock table to avoid race conditions on blocks + $db->exec("LOCK TABLES blocks WRITE, accounts WRITE, transactions WRITE, mempool WRITE"); + + $reward = $this->reward($height, $data); + + $msg = ''; + + // the reward transaction + $transaction = [ + "src" => $generator, + "dst" => $generator, + "val" => $reward, + "version" => 0, + "date" => $date, + "message" => $msg, + "fee" => "0.00000000", + "public_key" => $public_key, + ]; + $transaction['signature'] = $reward_signature; + // hash the transaction + $transaction['id'] = $trx->hash($transaction); + // check the signature + $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)) { + _log("Reward signature failed"); + return false; + } + + // insert the block into the db + $db->beginTransaction(); + $total = count($data); + $bind = [ + ":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) { + // rollback and exit if it fails + _log("Block DB insert failed"); + $db->rollback(); + $db->exec("UNLOCK TABLES"); + return false; + } + + // insert the reward transaction in the db + $trx->add($hash, $height, $transaction); + + // parse the block's transactions and insert them to db + $res = $this->parse_block($hash, $height, $data, false); + // if any fails, rollback + if ($res == false) { + $db->rollback(); + } else { + $db->commit(); + } + // relese the locking as everything is finished + $db->exec("UNLOCK TABLES"); + return true; + } + + // returns the current block, without the transactions + 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; + } + + // returns the previous block + public function prev() + { + global $db; + $current = $db->row("SELECT * FROM blocks ORDER by height DESC LIMIT 1,1"); + + return $current; + } + + // calculates the difficulty / base target for a specific block. The higher the difficulty number, the easier it is to win a block. + public function difficulty($height = 0) + { + global $db; + + // if no block height is specified, use the current block. + if ($height == 0) { + $current = $this->current(); + } else { + $current = $this->get($height); + } + $height = $current['height']; -public function add($height, $public_key, $nonce, $data, $date, $signature, $difficulty, $reward_signature, $argon){ - global $db; - $acc=new Account; - $trx=new Transaction; - - $generator=$acc->get_address($public_key); - - // the transactions are always sorted in the same way, on all nodes, as they are hashed as json - ksort($data); - - // create the hash / block id - $hash=$this->hash($generator, $height, $date, $nonce, $data, $signature, $difficulty, $argon); - //fix for the broken base58 library used until block 16900, trimming the first 0 bytes. - if($height<16900) $hash=ltrim($hash,'1'); - - $json=json_encode($data); + if ($height == 10801) { + return 5555555555; //hard fork 10900 resistance, force new difficulty + } - // create the block data and check it against the signature - $info="{$generator}-{$height}-{$date}-{$nonce}-{$json}-{$difficulty}-{$argon}"; - if(!$acc->check_signature($info,$signature,$public_key)) { _log("Block signature check failed"); return false; } + // last 20 blocks used to check the block times + $limit = 20; + if ($height < 20) { + $limit = $height - 1; + } + + // for the first 10 blocks, use the genesis difficulty + if ($height < 10) { + return $current['difficulty']; + } + + // elapsed time between the last 20 blocks + $first = $db->row("SELECT `date` FROM blocks ORDER by height DESC LIMIT $limit,1"); + $time = $current['date'] - $first['date']; + + // avg block time + $result = ceil($time / $limit); + + // if larger than 200 sec, increase by 5% + if ($result > 220) { + $dif = bcmul($current['difficulty'], 1.05); + } elseif ($result < 260) { + // if lower, decrease by 5% + $dif = bcmul($current['difficulty'], 0.95); + } else { + // keep current difficulty + $dif = $current['difficulty']; + } + if (strpos($dif, '.') !== false) { + $dif = substr($dif, 0, strpos($dif, '.')); + } + + //minimum and maximum diff + if ($dif < 1000) { + $dif = 1000; + } + if ($dif > 9223372036854775800) { + $dif = 9223372036854775800; + } + + return $dif; + } + + // calculates the maximum block size and increase by 10% the number of transactions if > 100 on the last 100 blocks + public function max_transactions() + { + global $db; + $current = $this->current(); + $limit = $current['height'] - 100; + $avg = $db->single("SELECT AVG(transactions) FROM blocks WHERE height>:limit", [":limit" => $limit]); + if ($avg < 100) { + return 100; + } + return ceil($avg * 1.1); + } + + // calculate the reward for each block + public function reward($id, $data = []) + { + // starting reward + $reward = 1000; + + // 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) { + foreach ($data as $x) { + $fees += $x['fee']; + } + } + return number_format($reward + $fees, 8, '.', ''); + } + + // checks the validity of a block + public function check($data) + { + // argon must have at least 20 chars + if (strlen($data['argon']) < 20) { + _log("Invalid block argon - $data[argon]"); + return false; + } + $acc = new Account(); + // generator's public key must be valid + + if (!$acc->valid_key($data['public_key'])) { + _log("Invalid public key - $data[public_key]"); + return false; + } + + //difficulty should be the same as our calculation + if ($data['difficulty'] != $this->difficulty()) { + _log("Invalid difficulty - $data[difficulty] - ".$this->difficulty()); + return false; + } + + //check the argon hash and the nonce to produce a valid block + if (!$this->mine($data['public_key'], $data['nonce'], $data['argon'])) { + _log("Mine check failed"); + return false; + } + + return true; + } + + // creates a new block on this node + public function forge($nonce, $argon, $public_key, $private_key) + { + //check the argon hash and the nonce to produce a valid block + if (!$this->mine($public_key, $nonce, $argon)) { + _log("Forge failed - Invalid argon"); + return false; + } + + // the block's date timestamp must be bigger than the last block + $current = $this->current(); + $height = $current['height'] += 1; + $date = time(); + if ($date <= $current['date']) { + _log("Forge failed - Date older than last block"); + return false; + } + + // get the mempool transactions + $txn = new Transaction(); + $data = $txn->mempool($this->max_transactions()); - if(!$this->parse_block($hash,$height,$data, true)) { _log("Parse block failed"); return false; } + $difficulty = $this->difficulty(); + $acc = new Account(); + $generator = $acc->get_address($public_key); - // lock table to avoid race conditions on blocks - $db->exec("LOCK TABLES blocks WRITE, accounts WRITE, transactions WRITE, mempool WRITE"); + // always sort the transactions in the same way + ksort($data); - $reward=$this->reward($height,$data); - - $msg=''; - - - // the reward transaction - $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; - // hash the transaction - $transaction['id']=$trx->hash($transaction); - // check the signature - $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)) {_log("Reward signature failed"); return false; } + // sign the block + $signature = $this->sign($generator, $height, $date, $nonce, $data, $private_key, $difficulty, $argon); - // insert the block into the db - $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) { - // rollback and exit if it fails - _log("Block DB insert failed"); - $db->rollback(); - $db->exec("UNLOCK TABLES"); - return false; - } - - // insert the reward transaction in the db - $trx->add($hash, $height,$transaction); + // reward transaction and signature + $reward = $this->reward($height, $data); + $msg = ''; + $transaction = [ + "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); - // parse the block's transactions and insert them to db - $res=$this->parse_block($hash,$height,$data, false); - // if any fails, rollback - if($res==false) $db->rollback(); - else $db->commit(); - // relese the locking as everything is finished - $db->exec("UNLOCK TABLES"); - return true; + // add the block to the blockchain + $res = $this->add( + $height, + $public_key, + $nonce, + $data, + $date, + $signature, + $difficulty, + $reward_signature, + $argon + ); + if (!$res) { + _log("Forge failed - Block->Add() failed"); + return false; + } + return true; + } + + // check if the arguments are good for mining a specific block + public function mine($public_key, $nonce, $argon, $difficulty = 0, $current_id = 0, $current_height = 0) + { + global $_config; + // if no id is specified, we use the current + if ($current_id === 0) { + $current = $this->current(); + $current_id = $current['id']; + $current_height = $current['height']; + } + // get the current difficulty if empty + if ($difficulty === 0) { + $difficulty = $this->difficulty(); + } + + // the argon parameters are hardcoded to avoid any exploits + if ($current_height > 10800) { + $argon = '$argon2i$v=19$m=524288,t=1,p=1'.$argon; //10800 block hard fork - resistance against gpu + } else { + $argon = '$argon2i$v=19$m=16384,t=4,p=4'.$argon; + } + + // the hash base for agon + $base = "$public_key-$nonce-".$current_id."-$difficulty"; + + + // check argon's hash validity + if (!password_verify($base, $argon)) { + return false; + } + + // all nonces are valid in testnet + if ($_config['testnet'] == true) { + return true; + } + + // prepare the base for the hashing + $hash = $base.$argon; + + // hash the base 6 times + for ($i = 0; $i < 5; + $i++) { + $hash = hash("sha512", $hash, true); + } + $hash = hash("sha512", $hash); + + // split it in 2 char substrings, to be used as hex + $m = str_split($hash, 2); + + // calculate a number based on 8 hex numbers - no specific reason, we just needed an algoritm to generate the number from the hash + $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]); + + // the number must not start with 0 + $duration = ltrim($duration, '0'); + + // divide the number by the difficulty and create the deadline + $result = gmp_div($duration, $difficulty); + + // if the deadline >0 and <=240, the arguments are valid fora block win + if ($result > 0 && $result <= 240) { + return true; + } + return false; + } + + + // parse the block transactions + public function parse_block($block, $height, $data, $test = true) + { + global $db; + // data must be array + if ($data === false) { + return false; + } + $acc = new Account(); + $trx = new Transaction(); + // no transactions means all are valid + if (count($data) == 0) { + return true; + } + + // check if the number of transactions is not bigger than current block size + $max = $this->max_transactions(); + if (count($data) > $max) { + return false; + } + + $balance = []; + foreach ($data as &$x) { + // get the sender's account if empty + if (empty($x['src'])) { + $x['src'] = $acc->get_address($x['public_key']); + } + + //validate the transaction + if (!$trx->check($x, $height)) { + return false; + } + + // prepare total balance + $balance[$x['src']] += $x['val'] + $x['fee']; + + // check if the transaction is already on the blockchain + if ($db->single("SELECT COUNT(1) FROM transactions WHERE id=:id", [":id" => $x['id']]) > 0) { + return false; + } + } + + // 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] + ); + if ($res == 0) { + return false; // not enough balance for the transactions + } + } + + // if the test argument is false, add the transactions to the blockchain + if ($test == false) { + foreach ($data as $d) { + $res = $trx->add($block, $height, $d); + if ($res == false) { + return false; + } + } + } + + return true; + } + + + // initialize the blockchain, add the genesis block + private function genesis() + { + global $db; + $signature = 'AN1rKvtLTWvZorbiiNk5TBYXLgxiLakra2byFef9qoz1bmRzhQheRtiWivfGSwP6r8qHJGrf8uBeKjNZP1GZvsdKUVVN2XQoL'; + $generator = '2P67zUANj7NRKTruQ8nJRHNdKMroY6gLw4NjptTVmYk6Hh1QPYzzfEa9z4gv8qJhuhCNM8p9GDAEDqGUU1awaLW6'; + $public_key = 'PZ8Tyr4Nx8MHsRAGMpZmZ6TWY63dXWSCyjGMdVDanywM3CbqvswVqysqU8XS87FcjpqNijtpRSSQ36WexRDv3rJL5X8qpGvzvznuErSRMfb2G6aNoiaT3aEJ'; + $reward_signature = '381yXZ3yq2AXHHdXfEm8TDHS4xJ6nkV4suXtUUvLjtvuyi17jCujtwcwXuYALM1F3Wiae2A4yJ6pXL1kTHJxZbrJNgtsKEsb'; + $argon = '$M1ZpVzYzSUxYVFp6cXEwWA$CA6p39MVX7bvdXdIIRMnJuelqequanFfvcxzQjlmiik'; + + $difficulty = "5555555555"; + $height = 1; + $data = []; + $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); + } + + // delete all blocks >= height + 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", [":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", [":id" => $x['id']]); + if ($res != 1) { + $db->rollback(); + $db->exec("UNLOCK TABLES"); + return false; + } + } + + $db->commit(); + $db->exec("UNLOCK TABLES"); + return true; + } + + + // delete specific block + public function delete_id($id) + { + + global $db; + $trx = new Transaction(); + + $x = $db->row("SELECT * FROM blocks WHERE id=:id", [":id" => $id]); + + if ($x === false) { + return false; + } + // avoid race conditions on blockchain manipulations + $db->beginTransaction(); + $db->exec("LOCK TABLES blocks WRITE, accounts WRITE, transactions WRITE, mempool WRITE"); + // reverse all transactions of the block + $res = $trx->reverse($x['id']); + if ($res === false) { + // rollback if you can't reverse the transactions + $db->rollback(); + $db->exec("UNLOCK TABLES"); + return false; + } + // remove the actual block + $res = $db->run("DELETE FROM blocks WHERE id=:id", [":id" => $x['id']]); + if ($res != 1) { + //rollback if you can't delete the block + $db->rollback(); + $db->exec("UNLOCK TABLES"); + return false; + } + // commit and release if all good + $db->commit(); + $db->exec("UNLOCK TABLES"); + return true; + } + + + // sign a new block, used when mining + 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; + } + + // generate the sha512 hash of the block data and converts it to base58 + 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); + } + + + // exports the block data, to be used when submitting to other peers + 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", [":height" => $height]); + } else { + $block = $db->row("SELECT * FROM blocks WHERE id=:id", [":id" => $id]); + } + + if (!$block) { + return false; + } + $r = $db->run("SELECT * FROM transactions WHERE version>0 AND block=:block", [":block" => $block['id']]); + $transactions = []; + foreach ($r as $x) { + $trans = [ + "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; + + // the reward transaction always has version 0 + $gen = $db->row( + "SELECT public_key, signature FROM transactions WHERE version=0 AND block=:block", + [":block" => $block['id']] + ); + $block['public_key'] = $gen['public_key']; + $block['reward_signature'] = $gen['signature']; + return $block; + } + + //return a specific block as array + public function get($height) + { + global $db; + if (empty($height)) { + return false; + } + $block = $db->row("SELECT * FROM blocks WHERE height=:height", [":height" => $height]); + return $block; + } } - -// returns the current block, without the transactions -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; - -} -// returns the previous block -public function prev(){ - global $db; - $current=$db->row("SELECT * FROM blocks ORDER by height DESC LIMIT 1,1"); - - return $current; - -} -// calculates the difficulty / base target for a specific block. The higher the difficulty number, the easier it is to win a block. -public function difficulty($height=0){ - global $db; - - // if no block height is specified, use the current block. - if($height==0){ - $current=$this->current(); - } else{ - $current=$this->get($height); - } - - - $height=$current['height']; - - if($height==10801) return 5555555555; //hard fork 10900 resistance, force new difficulty - - // last 20 blocks used to check the block times - $limit=20; - if($height<20) - $limit=$height-1; - - // for the first 10 blocks, use the genesis difficulty - if($height<10) return $current['difficulty']; - - // elapsed time between the last 20 blocks - $first=$db->row("SELECT `date` FROM blocks ORDER by height DESC LIMIT $limit,1"); - $time=$current['date']-$first['date']; - - // avg block time - $result=ceil($time/$limit); - - // if larger than 200 sec, increase by 5% - if($result>220){ - $dif= bcmul($current['difficulty'], 1.05); - } elseif($result<260){ - // if lower, decrease by 5% - $dif= bcmul($current['difficulty'], 0.95); - } else { - // keep current difficulty - $dif=$current['difficulty']; - } - if(strpos($dif,'.')!==false){ - $dif=substr($dif,0,strpos($dif,'.')); - } - - //minimum and maximum diff - if($dif<1000) $dif=1000; - if($dif>9223372036854775800) $dif=9223372036854775800; - - return $dif; -} - -// calculates the maximum block size and increase by 10% the number of transactions if > 100 on the last 100 blocks -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); -} - -// calculate the reward for each block -public function reward($id,$data=array()){ - - // starting reward - $reward=1000; - - // 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){ - - foreach($data as $x){ - $fees+=$x['fee']; - } - } - return number_format($reward+$fees,8,'.',''); -} - -// checks the validity of a block -public function check($data){ - // argon must have at least 20 chars - if(strlen($data['argon'])<20) { _log("Invalid block argon - $data[argon]"); return false; } - $acc=new Account; - // generator's public key must be valid - - if(!$acc->valid_key($data['public_key'])) { _log("Invalid public key - $data[public_key]"); return false; } - - //difficulty should be the same as our calculation - if($data['difficulty']!=$this->difficulty()) { _log("Invalid difficulty - $data[difficulty] - ".$this->difficulty()); return false; } - - //check the argon hash and the nonce to produce a valid block - if(!$this->mine($data['public_key'],$data['nonce'], $data['argon'])) { _log("Mine check failed"); return false; } - - return true; - -} - -// creates a new block on this node -public function forge($nonce, $argon, $public_key, $private_key){ - - //check the argon hash and the nonce to produce a valid block - - if(!$this->mine($public_key,$nonce, $argon)) { _log("Forge failed - Invalid argon"); return false; } - - // the block's date timestamp must be bigger than the last block - $current=$this->current(); - $height=$current['height']+=1; - $date=time(); - if($date<=$current['date']) { _log("Forge failed - Date older than last block"); return false; } - - // get the mempool transactions - $txn=new Transaction; - $data=$txn->mempool($this->max_transactions()); - - - $difficulty=$this->difficulty(); - $acc=new Account; - $generator=$acc->get_address($public_key); - - // always sort the transactions in the same way - ksort($data); - - // sign the block - $signature=$this->sign($generator, $height, $date, $nonce, $data, $private_key, $difficulty, $argon); - - // reward transaction and 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); - - // add the block to the blockchain - $res=$this->add($height, $public_key, $nonce, $data, $date, $signature, $difficulty, $reward_signature, $argon); - if(!$res) { _log("Forge failed - Block->Add() failed"); return false; } - return true; -} - -// check if the arguments are good for mining a specific block -public function mine($public_key, $nonce, $argon, $difficulty=0, $current_id=0, $current_height=0){ - global $_config; - // if no id is specified, we use the current - if($current_id===0){ - $current=$this->current(); - $current_id=$current['id']; - $current_height=$current['height']; - } - // get the current difficulty if empty - if($difficulty===0) $difficulty=$this->difficulty(); - - // the argon parameters are hardcoded to avoid any exploits - if($current_height>10800) $argon='$argon2i$v=19$m=524288,t=1,p=1'.$argon; //10800 block hard fork - resistance against gpu - else $argon='$argon2i$v=19$m=16384,t=4,p=4'.$argon; - - // the hash base for agon - $base="$public_key-$nonce-".$current_id."-$difficulty"; - - - // check argon's hash validity - if(!password_verify($base,$argon)) { return false; } - - // all nonces are valid in testnet - if($_config['testnet']==true) return true; - - // prepare the base for the hashing - $hash=$base.$argon; - - // hash the base 6 times - for($i=0;$i<5;$i++) $hash=hash("sha512",$hash,true); - $hash=hash("sha512",$hash); - - // split it in 2 char substrings, to be used as hex - $m=str_split($hash,2); - - // calculate a number based on 8 hex numbers - no specific reason, we just needed an algoritm to generate the number from the hash - $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]); - - // the number must not start with 0 - $duration=ltrim($duration, '0'); - - // divide the number by the difficulty and create the deadline - $result=gmp_div($duration, $difficulty); - - // if the deadline >0 and <=240, the arguments are valid fora block win - if($result>0&&$result<=240) return true; - return false; - -} - - - -// parse the block transactions -public function parse_block($block, $height, $data, $test=true){ - global $db; - // data must be array - if($data===false) return false; - $acc=new Account; - $trx=new Transaction; - // no transactions means all are valid - if(count($data)==0) return true; - - // check if the number of transactions is not bigger than current block size - $max=$this->max_transactions(); - if(count($data)>$max) return false; - - $balance=array(); - foreach($data as &$x){ - // get the sender's account if empty - if(empty($x['src'])) $x['src']=$acc->get_address($x['public_key']); - - //validate the transaction - if(!$trx->check($x,$height)) return false; - - // prepare total balance - $balance[$x['src']]+=$x['val']+$x['fee']; - - // check if the transaction is already on the blockchain - if($db->single("SELECT COUNT(1) FROM transactions WHERE id=:id",array(":id"=>$x['id']))>0) return false; - - } - - // 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",array(":id"=>$id, ":balance"=>$bal)); - if($res==0) return false; // not enough balance for the transactions - } - - // if the test argument is false, add the transactions to the blockchain - if($test==false){ - - foreach($data as $d){ - $res=$trx->add($block, $height, $d); - if($res==false) return false; - } - } - - return true; -} - - -// initialize the blockchain, add the genesis block -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); -} - -// delete all blocks >= height -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; -} - - -// delete specific block -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; - // avoid race conditions on blockchain manipulations - $db->beginTransaction(); - $db->exec("LOCK TABLES blocks WRITE, accounts WRITE, transactions WRITE, mempool WRITE"); - // reverse all transactions of the block - $res=$trx->reverse($x['id']); - if($res===false) { - // rollback if you can't reverse the transactions - $db->rollback(); - $db->exec("UNLOCK TABLES"); - return false; - } - // remove the actual block - $res=$db->run("DELETE FROM blocks WHERE id=:id",array(":id"=>$x['id'])); - if($res!=1){ - //rollback if you can't delete the block - $db->rollback(); - $db->exec("UNLOCK TABLES"); - return false; - } - // commit and release if all good - $db->commit(); - $db->exec("UNLOCK TABLES"); - return true; -} - - -// sign a new block, used when mining -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; - -} - -// generate the sha512 hash of the block data and converts it to base58 -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); -} - - -// exports the block data, to be used when submitting to other peers -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; - - // the reward transaction always has version 0 - $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; - -} -//return a specific block as array -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; -} - -} -?> diff --git a/include/config.inc.php b/include/config.inc.php index 5588957..ce26506 100755 --- a/include/config.inc.php +++ b/include/config.inc.php @@ -4,7 +4,7 @@ $_config['db_connect']="mysql:host=localhost;dbname=ENTER-DB-NAME"; $_config['db_user']="ENTER-DB-USER"; $_config['db_pass']="ENTER-DB-PASS"; -// Maximum number of connected peers +// Maximum number of connected peers $_config['max_peers']=30; // Testnet, used for development $_config['testnet']=false; @@ -36,5 +36,3 @@ $_config['sanity_rebroadcast_locals']=true; $_config['enable_logging']=false; // log file, should not be publicly viewable $_config['log_file']="/var/log/aro.log"; - -?> diff --git a/include/db.inc.php b/include/db.inc.php index 922fa06..48651bb 100755 --- a/include/db.inc.php +++ b/include/db.inc.php @@ -1,122 +1,137 @@ 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 - ".$this->error); + } + } - 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 - ".$this->error); + private function debug() + { + if (!$this->debugger) { + return; + } + $error = ["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"; } - 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)); + if ($this->debugger) { + echo nl2br($msg); + } + } - $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 = [$bind]; + } else { + $bind = []; + } } - 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; + } - 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("|", ["select", "describe", "pragma"]).") /i", $this->sql)) { + return $pdostmt->fetchAll(PDO::FETCH_ASSOC); + } elseif (preg_match("/^(".implode("|", ["delete", "insert", "update"]).") /i", $this->sql)) { + return $pdostmt->rowCount(); } - - - - - 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; - } + } + } 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; } - - 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; - } + if (count($query) > 1) { + return $query; } - - - + if (count($query) == 1) { + foreach ($query as $row) { + $result = $row; + } + return $result; + } + } } - -?> diff --git a/include/functions.inc.php b/include/functions.inc.php index b059bbf..ac42d9a 100755 --- a/include/functions.inc.php +++ b/include/functions.inc.php @@ -1,268 +1,294 @@ "error","data"=>$data, "coin"=>$_config['coin'])); - exit; -} -// api print ok and exit -function api_echo($data){ - global $_config; - echo json_encode(array("status"=>"ok","data"=>$data, "coin"=>$_config['coin'])); + echo json_encode(["status" => "error", "data" => $data, "coin" => $_config['coin']]); exit; } + +// api print ok and exit +function api_echo($data) +{ + global $_config; + echo json_encode(["status" => "ok", "data" => $data, "coin" => $_config['coin']]); + exit; +} + // log function, shows only in cli atm -function _log($data){ - $date=date("[Y-m-d H:i:s]"); - $trace=debug_backtrace(); - $loc=count($trace)-1; - $file=substr($trace[$loc]['file'],strrpos($trace[$loc]['file'],"/")+1); - - $res="$date ".$file.":".$trace[$loc]['line']; - - if(!empty($trace[$loc]['class'])) $res.="---".$trace[$loc]['class']; - if(!empty($trace[$loc]['function'])&&$trace[$loc]['function']!='_log') $res.='->'.$trace[$loc]['function'].'()'; - $res.=" $data \n"; - if(php_sapi_name() === 'cli') echo $res; - global $_config; - if($_config['enable_logging']==true){ - @file_put_contents($_config['log_file'],$res, FILE_APPEND); - } +function _log($data) +{ + $date = date("[Y-m-d H:i:s]"); + $trace = debug_backtrace(); + $loc = count($trace) - 1; + $file = substr($trace[$loc]['file'], strrpos($trace[$loc]['file'], "/") + 1); + + $res = "$date ".$file.":".$trace[$loc]['line']; + + if (!empty($trace[$loc]['class'])) { + $res .= "---".$trace[$loc]['class']; + } + if (!empty($trace[$loc]['function']) && $trace[$loc]['function'] != '_log') { + $res .= '->'.$trace[$loc]['function'].'()'; + } + $res .= " $data \n"; + if (php_sapi_name() === 'cli') { + echo $res; + } + global $_config; + if ($_config['enable_logging'] == true) { + @file_put_contents($_config['log_file'], $res, FILE_APPEND); + } } // converts PEM key to hex -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); +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; } // converts hex key to PEM -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-----"; +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-----"; } - - - // Base58 encoding/decoding functions - all credits go to https://github.com/stephen-hill/base58php - function base58_encode($string) - { - $alphabet='123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; - $base=strlen($alphabet); - // Type validation - if (is_string($string) === false) { - return false; - } - // If the string is empty, then the encoded string is obviously empty - if (strlen($string) === 0) { - return ''; - } - // Now we need to convert the byte array into an arbitrary-precision decimal - // We basically do this by performing a base256 to base10 conversion - $hex = unpack('H*', $string); - $hex = reset($hex); - $decimal = gmp_init($hex, 16); - // This loop now performs base 10 to base 58 conversion - // The remainder or modulo on each loop becomes a base 58 character - $output = ''; - while (gmp_cmp($decimal, $base) >= 0) { - list($decimal, $mod) = gmp_div_qr($decimal, $base); - $output .= $alphabet[gmp_intval($mod)]; - } - // If there's still a remainder, append it - if (gmp_cmp($decimal, 0) > 0) { - $output .= $alphabet[gmp_intval($decimal)]; - } - // Now we need to reverse the encoded data - $output = strrev($output); - // Now we need to add leading zeros - $bytes = str_split($string); - foreach ($bytes as $byte) { - if ($byte === "\x00") { - $output = $alphabet[0] . $output; - continue; - } - break; - } - return (string) $output; +// Base58 encoding/decoding functions - all credits go to https://github.com/stephen-hill/base58php +function base58_encode($string) +{ + $alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; + $base = strlen($alphabet); + // Type validation + if (is_string($string) === false) { + return false; } - function base58_decode($base58) - { - $alphabet='123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; - $base=strlen($alphabet); - - // Type Validation - if (is_string($base58) === false) { - return false; - } - // If the string is empty, then the decoded string is obviously empty - if (strlen($base58) === 0) { - return ''; - } - $indexes = array_flip(str_split($alphabet)); - $chars = str_split($base58); - // Check for invalid characters in the supplied base58 string - foreach ($chars as $char) { - if (isset($indexes[$char]) === false) { - return false; - } - } - // Convert from base58 to base10 - $decimal = gmp_init($indexes[$chars[0]], 10); - for ($i = 1, $l = count($chars); $i < $l; $i++) { - $decimal = gmp_mul($decimal, $base); - $decimal = gmp_add($decimal, $indexes[$chars[$i]]); - } - // Convert from base10 to base256 (8-bit byte array) - $output = ''; - while (gmp_cmp($decimal, 0) > 0) { - list($decimal, $byte) = gmp_div_qr($decimal, 256); - $output = pack('C', gmp_intval($byte)) . $output; - } - // Now we need to add leading zeros - foreach ($chars as $char) { - if ($indexes[$char] === 0) { - $output = "\x00" . $output; - continue; - } - break; - } - return $output; + // If the string is empty, then the encoded string is obviously empty + if (strlen($string) === 0) { + return ''; } + // Now we need to convert the byte array into an arbitrary-precision decimal + // We basically do this by performing a base256 to base10 conversion + $hex = unpack('H*', $string); + $hex = reset($hex); + $decimal = gmp_init($hex, 16); + // This loop now performs base 10 to base 58 conversion + // The remainder or modulo on each loop becomes a base 58 character + $output = ''; + while (gmp_cmp($decimal, $base) >= 0) { + list($decimal, $mod) = gmp_div_qr($decimal, $base); + $output .= $alphabet[gmp_intval($mod)]; + } + // If there's still a remainder, append it + if (gmp_cmp($decimal, 0) > 0) { + $output .= $alphabet[gmp_intval($decimal)]; + } + // Now we need to reverse the encoded data + $output = strrev($output); + // Now we need to add leading zeros + $bytes = str_split($string); + foreach ($bytes as $byte) { + if ($byte === "\x00") { + $output = $alphabet[0].$output; + continue; + } + break; + } + return (string)$output; +} + +function base58_decode($base58) +{ + $alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; + $base = strlen($alphabet); + + // Type Validation + if (is_string($base58) === false) { + return false; + } + // If the string is empty, then the decoded string is obviously empty + if (strlen($base58) === 0) { + return ''; + } + $indexes = array_flip(str_split($alphabet)); + $chars = str_split($base58); + // Check for invalid characters in the supplied base58 string + foreach ($chars as $char) { + if (isset($indexes[$char]) === false) { + return false; + } + } + // Convert from base58 to base10 + $decimal = gmp_init($indexes[$chars[0]], 10); + for ($i = 1, $l = count($chars); $i < $l; $i++) { + $decimal = gmp_mul($decimal, $base); + $decimal = gmp_add($decimal, $indexes[$chars[$i]]); + } + // Convert from base10 to base256 (8-bit byte array) + $output = ''; + while (gmp_cmp($decimal, 0) > 0) { + list($decimal, $byte) = gmp_div_qr($decimal, 256); + $output = pack('C', gmp_intval($byte)).$output; + } + // Now we need to add leading zeros + foreach ($chars as $char) { + if ($indexes[$char] === 0) { + $output = "\x00".$output; + continue; + } + break; + } + return $output; +} // converts PEM key to the base58 version used by ARO -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); - - +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); - } + // converts the key in base58 to PEM -function coin2pem ($data, $is_private_key=false) { +function coin2pem($data, $is_private_key = false) +{ + $data = base58_decode($data); + $data = base64_encode($data); - - - $data=base58_decode($data); - $data=base64_encode($data); + $dat = str_split($data, 64); + $data = implode("\n", $dat); - $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"; + 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"; } // sign data with private key -function ec_sign($data, $key){ - // transform the base58 key format to PEM - $private_key=coin2pem($key,true); - - - $pkey=openssl_pkey_get_private($private_key); - - $k=openssl_pkey_get_details($pkey); +function ec_sign($data, $key) +{ + // transform the base58 key format to PEM + $private_key = coin2pem($key, true); - openssl_sign($data,$signature,$pkey,OPENSSL_ALGO_SHA256); - - // the signature will be base58 encoded + $pkey = openssl_pkey_get_private($private_key); + + $k = openssl_pkey_get_details($pkey); + + + openssl_sign($data, $signature, $pkey, OPENSSL_ALGO_SHA256); + + // the signature will be base58 encoded return base58_encode($signature); - } -function ec_verify($data, $signature, $key){ - - +function ec_verify($data, $signature, $key) +{ // transform the base58 key to PEM - $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; + $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; } // POST data to an URL (usualy peer). The data is an array, json encoded with is sent as $_POST['data'] -function peer_post($url, $data=array(),$timeout=60,$debug=false){ +function peer_post($url, $data = [], $timeout = 60, $debug = false) +{ global $_config; - if($debug) echo "\nPeer post: $url\n"; + if ($debug) { + echo "\nPeer post: $url\n"; + } $postdata = http_build_query( - array( + [ 'data' => json_encode($data), - "coin"=>$_config['coin'] - ) + "coin" => $_config['coin'], + ] ); - - $opts = array('http' => - array( - 'timeout' => $timeout, - 'method' => 'POST', - 'header' => 'Content-type: application/x-www-form-urlencoded', - 'content' => $postdata - ) - ); - - $context = stream_context_create($opts); - + + $opts = [ + 'http' => + [ + 'timeout' => $timeout, + 'method' => 'POST', + 'header' => 'Content-type: application/x-www-form-urlencoded', + 'content' => $postdata, + ], + ]; + + $context = stream_context_create($opts); + $result = file_get_contents($url, false, $context); - if($debug) echo "\nPeer response: $result\n"; - $res=json_decode($result,true); + if ($debug) { + echo "\nPeer response: $result\n"; + } + $res = json_decode($result, true); // the function will return false if something goes wrong - if($res['status']!="ok"||$res['coin']!=$_config['coin']) return false; + if ($res['status'] != "ok" || $res['coin'] != $_config['coin']) { + return false; + } return $res['data']; } // convers hex to base58 -function hex2coin($hex){ - - $data=hex2bin($hex); - return base58_encode($data); -} +function hex2coin($hex) +{ + $data = hex2bin($hex); + return base58_encode($data); +} + // converts base58 to hex -function coin2hex($data){ - - $bin= base58_decode($data); - return bin2hex($bin); -} -?> +function coin2hex($data) +{ + $bin = base58_decode($data); + return bin2hex($bin); +} diff --git a/include/init.inc.php b/include/init.inc.php index 245e920..d1a54b1 100755 --- a/include/init.inc.php +++ b/include/init.inc.php @@ -4,18 +4,15 @@ define("VERSION", "0.3.0"); // Amsterdam timezone by default, should probably be moved to config date_default_timezone_set("Europe/Amsterdam"); - - //error_reporting(E_ALL & ~E_NOTICE); error_reporting(0); -ini_set('display_errors',"off"); +ini_set('display_errors', "off"); // 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 /"); +if (php_sapi_name() !== 'cli' && substr_count($_SERVER['PHP_SELF'], "/") > 1) { + die("This application should only be run in the main directory /"); } - require_once("include/config.inc.php"); require_once("include/db.inc.php"); require_once("include/functions.inc.php"); @@ -23,67 +20,81 @@ 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"); +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."); - -// checks for php version and extensions -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 (!extension_loaded('PDO')) api_err("pdo php extension missing"); -if (!extension_loaded("bcmath")) api_err("bcmath php extension missing"); -if (!defined("PASSWORD_ARGON2I")) api_err("The php version is not compiled with argon2i support"); - -if(floatval(phpversion())<7.2) api_err("The minimum php version required is 7.2"); - - - - - -// Getting extra configs from the database -$query=$db->run("SELECT cfg, val FROM config"); -foreach($query as $res){ - $_config[$res['cfg']]=trim($res['val']); +$db = new DB($_config['db_connect'], $_config['db_user'], $_config['db_pass'], 0); +if (!$db) { + die("Could not connect to the DB backend."); } +// checks for php version and extensions +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 (!extension_loaded('PDO')) { + api_err("pdo php extension missing"); +} +if (!extension_loaded("bcmath")) { + api_err("bcmath php extension missing"); +} +if (!defined("PASSWORD_ARGON2I")) { + api_err("The php version is not compiled with argon2i support"); +} +if (floatval(phpversion()) < 7.2) { + api_err("The minimum php version required is 7.2"); +} +// Getting extra configs from the database +$query = $db->run("SELECT cfg, val FROM config"); +foreach ($query as $res) { + $_config[$res['cfg']] = trim($res['val']); +} // nothing is allowed while in maintenance -if($_config['maintenance']==1) api_err("under-maintenance"); - +if ($_config['maintenance'] == 1) { + api_err("under-maintenance"); +} // update the db schema, on every git pull or initial install -if(file_exists("tmp/db-update")){ - - $res=unlink("tmp/db-update"); - if($res){ - echo "Updating db schema! Please refresh!\n"; - require_once("include/schema.inc.php"); - exit; - } - echo "Could not access the tmp/db-update file. Please give full permissions to this file\n"; +if (file_exists("tmp/db-update")) { + $res = unlink("tmp/db-update"); + if ($res) { + echo "Updating db schema! Please refresh!\n"; + require_once("include/schema.inc.php"); + exit; + } + echo "Could not access the tmp/db-update file. Please give full permissions to this file\n"; } // something went wront with the db schema -if($_config['dbversion']<2) exit; +if ($_config['dbversion'] < 2) { + exit; +} // separate blockchain for testnet -if($_config['testnet']==true) $_config['coin'].="-testnet"; +if ($_config['testnet'] == true) { + $_config['coin'] .= "-testnet"; +} // current hostname -$hostname=(!empty($_SERVER['HTTPS'])?'https':'http')."://".san_host($_SERVER['HTTP_HOST']); +$hostname = (!empty($_SERVER['HTTPS']) ? 'https' : 'http')."://".san_host($_SERVER['HTTP_HOST']); // set the hostname to the current one -if($hostname!=$_config['hostname']&&$_SERVER['HTTP_HOST']!="localhost"&&$_SERVER['HTTP_HOST']!="127.0.0.1"&&$_SERVER['hostname']!='::1'&&php_sapi_name() !== 'cli' && ($_config['allow_hostname_change']!=false||empty($_config['hostname']))){ - $db->run("UPDATE config SET val=:hostname WHERE cfg='hostname' LIMIT 1",array(":hostname"=>$hostname)); - $_config['hostname']=$hostname; +if ($hostname != $_config['hostname'] && $_SERVER['HTTP_HOST'] != "localhost" && $_SERVER['HTTP_HOST'] != "127.0.0.1" && $_SERVER['hostname'] != '::1' && php_sapi_name() !== 'cli' && ($_config['allow_hostname_change'] != false || empty($_config['hostname']))) { + $db->run("UPDATE config SET val=:hostname WHERE cfg='hostname' LIMIT 1", [":hostname" => $hostname]); + $_config['hostname'] = $hostname; +} +if (empty($_config['hostname']) || $_config['hostname'] == "http://" || $_config['hostname'] == "https://") { + api_err("Invalid hostname"); } -if(empty($_config['hostname'])||$_config['hostname']=="http://"||$_config['hostname']=="https://") api_err("Invalid hostname"); // run sanity - $t=time(); - if($t-$_config['sanity_last']>$_config['sanity_interval']&& php_sapi_name() !== 'cli') system("php sanity.php > /dev/null 2>&1 &"); - - -?> +$t = time(); +if ($t - $_config['sanity_last'] > $_config['sanity_interval'] && php_sapi_name() !== 'cli') { + system("php sanity.php > /dev/null 2>&1 &"); +} diff --git a/include/schema.inc.php b/include/schema.inc.php index bf5d6cc..54ff219 100755 --- a/include/schema.inc.php +++ b/include/schema.inc.php @@ -1,18 +1,17 @@ beginTransaction(); -if($dbversion==0){ - $db->run(" +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, @@ -24,16 +23,16 @@ if($dbversion==0){ `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');"); @@ -51,7 +50,7 @@ if($dbversion==0){ `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, @@ -60,8 +59,8 @@ if($dbversion==0){ `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, @@ -75,75 +74,74 @@ if($dbversion==0){ `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` + $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` + + $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` + + $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` + + $db->run("ALTER TABLE `transactions` ADD PRIMARY KEY (`id`), ADD KEY `block_id` (`block`);"); - - $db->run("ALTER TABLE `accounts` + + $db->run("ALTER TABLE `accounts` ADD CONSTRAINT `accounts` FOREIGN KEY (`block`) REFERENCES `blocks` (`id`) ON DELETE CASCADE;"); - - $db->run("ALTER TABLE `transactions` + + $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 == 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 == 2) { + $db->run("INSERT INTO `config` (`cfg`, `val`) VALUES ('sanity_sync', '0');"); + $dbversion++; } -if($dbversion==3){ - $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 == 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 == 5) { + $db->run("ALTER TABLE `peers` ADD `fails` TINYINT NOT NULL DEFAULT '0' AFTER `ip`; "); + $dbversion++; } -if($dbversion==6){ - $db->run("ALTER TABLE `peers` ADD `stuckfail` TINYINT(4) NOT NULL DEFAULT '0' AFTER `fails`, ADD INDEX (`stuckfail`); "); - $db->run("ALTER TABLE `accounts` ADD `alias` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL AFTER `balance`; "); - $dbversion++; +if ($dbversion == 6) { + $db->run("ALTER TABLE `peers` ADD `stuckfail` TINYINT(4) NOT NULL DEFAULT '0' AFTER `fails`, ADD INDEX (`stuckfail`); "); + $db->run("ALTER TABLE `accounts` ADD `alias` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL AFTER `balance`; "); + $dbversion++; } // update the db version to the latest one -if($dbversion!=$_config['dbversion']) $db->run("UPDATE config SET val=:val WHERE cfg='dbversion'",array(":val"=>$dbversion)); +if ($dbversion != $_config['dbversion']) { + $db->run("UPDATE config SET val=:val WHERE cfg='dbversion'", [":val" => $dbversion]); +} $db->commit(); - - -?> diff --git a/include/transaction.inc.php b/include/transaction.inc.php index 6a5e674..1663307 100755 --- a/include/transaction.inc.php +++ b/include/transaction.inc.php @@ -1,275 +1,435 @@ 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'])); + $acc = new Account(); + $r = $db->run("SELECT * FROM transactions WHERE block=:block", [":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", + [":id" => $x['dst'], ":val" => $x['val']] + ); - // on version 0 / reward transaction, don't credit anyone - if($x['version']>0) $db->run("UPDATE accounts SET balance=balance+:val WHERE id=:id",array(":id"=>$x['src'], ":val"=>$x['val']+$x['fee'])); - - // add the transactions to mempool - 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; - } - } - // clears the mempool - 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)); - } - - // returns X transactions from mempool - public function mempool($max){ - global $db; - $block=new Block; - $current=$block->current(); - $height=$current['height']+1; - // only get the transactions that are not locked with a future height - $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; + // on version 0 / reward transaction, don't credit anyone + if ($x['version'] > 0) { + $db->run( + "UPDATE accounts SET balance=balance+:val WHERE id=:id", + [":id" => $x['src'], ":val" => $x['val'] + $x['fee']] + ); + } - 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, $current['height'])){ - _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; - } + // add the transactions to mempool + if ($x['version'] > 0) { + $this->add_mempool($x); + } + $res = $db->run("DELETE FROM transactions WHERE id=:id", [":id" => $x['id']]); + if ($res != 1) { + return false; + } } - // always sort the array + } + + // clears the mempool + 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", [":limit" => $limit]); + } + + // returns X transactions from mempool + public function mempool($max) + { + global $db; + $block = new Block(); + $current = $block->current(); + $height = $current['height'] + 1; + // only get the transactions that are not locked with a future height + $r = $db->run( + "SELECT * FROM mempool WHERE height<=:height ORDER by val/fee DESC LIMIT :max", + [":height" => $height, ":max" => $max + 50] + ); + $transactions = []; + if (count($r) > 0) { + $i = 0; + $balance = []; + foreach ($r as $x) { + $trans = [ + "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, $current['height'])) { + _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", [":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", + [":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; + } + } + // always sort the array ksort($transactions); return $transactions; } // add a new transaction to mempool and lock it with the current height - public function add_mempool($x, $peer=""){ + 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); + $block = new Block(); + $current = $block->current(); + $height = $current['height']; + $x['id'] = san($x['id']); + $bind = [ + ":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; - - } // add a new transaction to the blockchain - public function add($block,$height, $x){ + public function add($block, $height, $x) + { global $db; - $acc= new Account; + $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'])); - // no debit when the transaction is reward - if($x['version']>0) $db->run("UPDATE accounts SET balance=(balance-:val)-:fee WHERE id=:id",array(":id"=>$x['src'], ":val"=>$x['val'], ":fee"=>$x['fee'])); - $db->run("DELETE FROM mempool WHERE id=:id",array(":id"=>$x['id'])); + $acc->add_id($x['dst'], $block); + $x['id'] = san($x['id']); + $bind = [ + ":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", [":id" => $x['dst'], ":val" => $x['val']]); + // no debit when the transaction is reward + if ($x['version'] > 0) { + $db->run( + "UPDATE accounts SET balance=(balance-:val)-:fee WHERE id=:id", + [":id" => $x['src'], ":val" => $x['val'], ":fee" => $x['fee']] + ); + } + $db->run("DELETE FROM mempool WHERE id=:id", [":id" => $x['id']]); return true; - - } - + // hash the transaction's most important fields and create the transaction ID - 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 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); } - + // check the transaction for validity - public function check($x, $height=0){ - // if no specific block, use current - if($height===0){ - $block=new Block; - $current=$block->current(); - $height=$current['height']; - } - $acc= new Account; - $info=$x['val']."-".$x['fee']."-".$x['dst']."-".$x['message']."-".$x['version']."-".$x['public_key']."-".$x['date']; + public function check($x, $height = 0) + { + // if no specific block, use current + if ($height === 0) { + $block = new Block(); + $current = $block->current(); + $height = $current['height']; + } + $acc = new Account(); + $info = $x['val']."-".$x['fee']."-".$x['dst']."-".$x['message']."-".$x['version']."-".$x['public_key']."-".$x['date']; - // the value must be >=0 - if($x['val']<0){ _log("$x[id] - Value below 0"); return false; } - - // the fee must be >=0 - if($x['fee']<0) { _log("$x[id] - Fee below 0"); return false; } - - // the fee is 0.25%, hardcoded - $fee=$x['val']*0.0025; - $fee=number_format($fee,8,".",""); - if($fee<0.00000001) $fee=0.00000001; - // max fee after block 10800 is 10 - if($height>10800&&$fee>10) $fee=10; //10800 - // added fee does not match - if($fee!=$x['fee']) { _log("$x[id] - Fee not 0.25%"); return false; } + // the value must be >=0 + if ($x['val'] < 0) { + _log("$x[id] - Value below 0"); + return false; + } - // invalid destination address - if(!$acc->valid($x['dst'])) { _log("$x[id] - Invalid destination address"); return false; } - - // reward transactions are not added via this function - if($x['version']<1) { _log("$x[id] - Invalid version <1"); return false; } - //if($x['version']>1) { _log("$x[id] - Invalid version >1"); return false; } - - // public key must be at least 15 chars / probably should be replaced with the validator function - if(strlen($x['public_key'])<15) { _log("$x[id] - Invalid public key size"); return false; } - // no transactions before the genesis - if($x['date']<1511725068) { _log("$x[id] - Date before genesis"); return false; } - // no future transactions - if($x['date']>time()+86400) { _log("$x[id] - Date in the future"); return false; } - // prevent the resending of broken base58 transactions - if($height>16900&&$x['date']<1519327780) return false; - $id=$this->hash($x); - // the hash does not match our regenerated hash - if($x['id']!=$id) { - // fix for broken base58 library which was used until block 16900, accepts hashes without the first 1 or 2 bytes - $xs=base58_decode($x['id']); - if(((strlen($xs)!=63||substr($id,1)!=$x['id'])&&(strlen($xs)!=62||substr($id,2)!=$x['id']))||$height>16900){ - _log("$x[id] - $id - Invalid hash"); - return false; - } + // the fee must be >=0 + if ($x['fee'] < 0) { + _log("$x[id] - Fee below 0"); + return false; + } - } - - //verify the ecdsa signature - if(!$acc->check_signature($info, $x['signature'], $x['public_key'])) { _log("$x[id] - Invalid signature"); return false; } - - return true; + // the fee is 0.25%, hardcoded + $fee = $x['val'] * 0.0025; + $fee = number_format($fee, 8, ".", ""); + if ($fee < 0.00000001) { + $fee = 0.00000001; + } + // max fee after block 10800 is 10 + if ($height > 10800 && $fee > 10) { + $fee = 10; //10800 + } + // added fee does not match + if ($fee != $x['fee']) { + _log("$x[id] - Fee not 0.25%"); + return false; + } + + // invalid destination address + if (!$acc->valid($x['dst'])) { + _log("$x[id] - Invalid destination address"); + return false; + } + + // reward transactions are not added via this function + if ($x['version'] < 1) { + _log("$x[id] - Invalid version <1"); + return false; + } + //if($x['version']>1) { _log("$x[id] - Invalid version >1"); return false; } + + // public key must be at least 15 chars / probably should be replaced with the validator function + if (strlen($x['public_key']) < 15) { + _log("$x[id] - Invalid public key size"); + return false; + } + // no transactions before the genesis + if ($x['date'] < 1511725068) { + _log("$x[id] - Date before genesis"); + return false; + } + // no future transactions + if ($x['date'] > time() + 86400) { + _log("$x[id] - Date in the future"); + return false; + } + // prevent the resending of broken base58 transactions + if ($height > 16900 && $x['date'] < 1519327780) { + return false; + } + $id = $this->hash($x); + // the hash does not match our regenerated hash + if ($x['id'] != $id) { + // fix for broken base58 library which was used until block 16900, accepts hashes without the first 1 or 2 bytes + $xs = base58_decode($x['id']); + if (((strlen($xs) != 63 || substr($id, 1) != $x['id']) && (strlen($xs) != 62 || substr( + $id, + 2 + ) != $x['id'])) || $height > 16900) { + _log("$x[id] - $id - Invalid hash"); + return false; + } + } + + //verify the ecdsa signature + if (!$acc->check_signature($info, $x['signature'], $x['public_key'])) { + _log("$x[id] - Invalid signature"); + return false; + } + + return true; } - // sign a transaction - 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; + // sign a transaction + 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; } //export a mempool transaction - public function export($id){ + public function export($id) + { global $db; - $r=$db->row("SELECT * FROM mempool WHERE id=:id",array(":id"=>$id)); + $r = $db->row("SELECT * FROM mempool WHERE id=:id", [":id" => $id]); return $r; - } + // get the transaction data as array - public function get_transaction($id){ + public function get_transaction($id) + { global $db; - $acc=new Account; - $block=new Block; - $current=$block->current(); + $acc = new Account(); + $block = new Block(); + $current = $block->current(); - $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']; + $x = $db->row("SELECT * FROM transactions WHERE id=:id", [":id" => $id]); - 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; - + if (!$x) { + return false; + } + $trans = [ + "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; } - // return the transactions for a specific block id or height - public function get_transactions($height="", $id=""){ + // return the transactions for a specific block id or height + public function get_transactions($height = "", $id = "") + { global $db; - $block=new Block; - $current=$block->current(); - $acc=new Account; - $height=san($height); - $id=san($id); - if(empty($id)&&empty($height)) return false; - if(!empty($id)) $r=$db->run("SELECT * FROM transactions WHERE block=:id AND version>0",array(":id"=>$id)); - else $r=$db->run("SELECT * FROM transactions WHERE height=:height AND version>0",array(":height"=>$height)); - $res=array(); - foreach($r 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']=$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); - $res[]=$trans; - } - return $res; + $block = new Block(); + $current = $block->current(); + $acc = new Account(); + $height = san($height); + $id = san($id); + if (empty($id) && empty($height)) { + return false; + } + if (!empty($id)) { + $r = $db->run("SELECT * FROM transactions WHERE block=:id AND version>0", [":id" => $id]); + } else { + $r = $db->run("SELECT * FROM transactions WHERE height=:height AND version>0", [":height" => $height]); + } + $res = []; + foreach ($r as $x) { + $trans = [ + "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); + $res[] = $trans; + } + return $res; } // get a specific mempool transaction as array - public function get_mempool_transaction($id){ + 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']; + $x = $db->row("SELECT * FROM mempool WHERE id=:id", [":id" => $id]); + if (!$x) { + return false; + } + $trans = [ + "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; - + $trans['type'] = "mempool"; + $trans['confirmations'] = -1; + ksort($trans); + return $trans; } - } - - - -?> diff --git a/index.php b/index.php index 5d0f499..5de72ff 100755 --- a/index.php +++ b/index.php @@ -1,7 +1,7 @@ current(); +$block = new Block(); +$current = $block->current(); echo "

Arionum Node

"; echo "System check complete.

Current block: $current[height]"; - - - -?> diff --git a/mine.php b/mine.php index f8b2501..3f3da8b 100755 --- a/mine.php +++ b/mine.php @@ -1,7 +1,7 @@ difficulty(); - $current=$block->current(); - - $res=array("difficulty"=>$diff, "block"=>$current['id'], "height"=>$current['height'], "testnet"=>$_config['testnet']); - api_echo($res); - exit; -} elseif($q=="submitNonce"){ - // in case the blocks are syncing, reject all - 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']); - // check if the miner won the block - $result=$block->mine($public_key, $nonce, $argon); - - if($result) { - // generate the new block - $res=$block->forge($nonce,$argon, $public_key, $private_key); - - - - - - if($res){ - //if the new block is generated, propagate it to all peers in background - $current=$block->current(); - system("php propagate.php block $current[id] > /dev/null 2>&1 &"); - api_echo("accepted"); - } - } - api_err("rejected"); -} else { - api_err("invalid command"); +if ($_config['testnet'] == false && !in_array($ip, $_config['allowed_hosts']) && !empty($ip) && !in_array( + '*', + $_config['allowed_hosts'] +)) { + api_err("unauthorized"); } -?> +if ($q == "info") { + // provides the mining info to the miner + $diff = $block->difficulty(); + $current = $block->current(); + + $res = [ + "difficulty" => $diff, + "block" => $current['id'], + "height" => $current['height'], + "testnet" => $_config['testnet'], + ]; + api_echo($res); + exit; +} elseif ($q == "submitNonce") { + // in case the blocks are syncing, reject all + 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']); + // check if the miner won the block + $result = $block->mine($public_key, $nonce, $argon); + + if ($result) { + // generate the new block + $res = $block->forge($nonce, $argon, $public_key, $private_key); + + + if ($res) { + //if the new block is generated, propagate it to all peers in background + $current = $block->current(); + system("php propagate.php block $current[id] > /dev/null 2>&1 &"); + api_echo("accepted"); + } + } + api_err("rejected"); +} else { + api_err("invalid command"); +} diff --git a/peer.php b/peer.php index 2b2f6a9..5d70627 100755 --- a/peer.php +++ b/peer.php @@ -1,228 +1,275 @@ -single("SELECT COUNT(1) FROM peers WHERE hostname=:hostname AND ip=:ip",array(":hostname"=>$hostname,":ip"=>$ip)); - if($res==1){ - if($data['repeer']==1){ - $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"); - } - api_echo("peer-ok-already"); - } - // if we have enough peers, add it to DB as reserve - $res=$db->single("SELECT COUNT(1) FROM peers WHERE blacklistedUNIX_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)); - // re-peer to make sure the peer is valid - $res=peer_post($hostname."/peer.php?q=peer",array("hostname"=>$_config['hostname'])); - if($res!==false) api_echo("re-peer-ok"); - else{ - $db->run("DELETE FROM peers WHERE ip=:ip",array(":ip"=>$ip)); - api_err("re-peer failed - $result"); - } -} -elseif($q=="ping"){ - // confirm peer is active - api_echo("pong"); -} elseif($q=="submitTransaction"){ - // receive a new transaction from a peer - $current=$block->current(); - - - // no transactions accepted if the sanity is syncing - if($_config['sanity_sync']==1) api_err("sanity-sync"); - - $data['id']=san($data['id']); - // validate transaction data - if(!$trx->check($data)) api_err("Invalid transaction"); - $hash=$data['id']; - // make sure it's not already in mempool - $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"); - // make sure the peer is not flooding us with transactions - $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"=>$ip)); - if($res>$_config['peer_max_mempool']) api_error("Too many transactions broadcasted from this peer"); - - - // make sure the transaction is not already on the blockchain - $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']); - // make sure the sender has enough balance - $balance=$db->single("SELECT balance FROM accounts WHERE id=:id",array(":id"=>$src)); - if($balance<$val+$fee) api_err("Not enough funds"); - - // make sure the sender has enough pending balance - $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)"); - - // add to mempool - $trx->add_mempool($data, $ip); - - // rebroadcast the transaction to some peers unless the transaction is smaller than the average size of transactions in mempool - protect against garbage data flooding - $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 2>&1 &"); - api_echo("transaction-ok"); -} -elseif($q=="submitBlock"){ - // receive a new block from a peer - - // if sanity sync, refuse all - if($_config['sanity_sync']==1){ _log('['.$ip."] Block rejected due to sanity sync"); api_err("sanity-sync"); } - $data['id']=san($data['id']); - $current=$block->current(); - // block already in the blockchain - if($current['id']==$data['id']) api_echo("block-ok"); - if($data['date']>time()+30) api_err("block in the future"); - - if($current['height']==$data['height']&&$current['id']!=$data['id']){ - // different forks, same height - $accept_new=false; - if($current['transactions']<$data['transactions']){ - // accept the one with most transactions - $accept_new=true; - } elseif($current['transactions']==$data['transactions']) { - // convert the first 12 characters from hex to decimal and the block with the largest number wins - $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){ - // if the new block is accepted, run a microsanity to sync it - _log('['.$ip."] Starting microsanity - $data[height]"); - system("php sanity.php microsanity '$ip' > /dev/null 2>&1 &"); - api_echo("microsanity"); - - } else { - _log('['.$ip."] suggesting reverse-microsanity - $data[height]"); - api_echo("reverse-microsanity"); // if it's not, suggest to the peer to get the block from us - } - } - // if it's not the next block - if($current['height']!=$data['height']-1) { - // if the height of the block submitted is lower than our current height, send them our current block - if($data['height']<$current['height']){ - $pr=$db->row("SELECT * FROM peers WHERE ip=:ip",array(":ip"=>$ip)); - if(!$pr) api_err("block-too-old"); - $peer_host=base58_encode($pr['hostname']); - $pr['ip']=escapeshellcmd(san_ip($pr['ip'])); - system("php propagate.php block current '$peer_host' '$pr[ip]' > /dev/null 2>&1 &"); - _log('['.$ip."] block too old, sending our current block - $data[height]"); - - api_err("block-too-old"); - } - // if the block difference is bigger than 150, nothing should be done. They should sync via sanity - if($data['height']-$current['height']>150) { - _log('['.$ip."] block-out-of-sync - $data[height]"); - api_err("block-out-of-sync"); - } - // request them to send us a microsync with the latest blocks - _log('['.$ip."] requesting microsync - $current[height] - $data[height]"); - api_echo(array("request"=>"microsync","height"=>$current['height'], "block"=>$current['id'])); - - } - // check block data - if(!$block->check($data)){ - _log('['.$ip."] invalid block - $data[height]"); - api_err("invalid-block"); - } - $b=$data; - // add the block to the blockchain - $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('['.$ip."] invalid block data - $data[height]"); - api_err("invalid-block-data"); - } - - _log('['.$ip."] block ok, repropagating - $data[height]"); - - // send it to all our peers - system("php propagate.php block '$data[id]' all all linear > /dev/null 2>&1 &"); - api_echo("block-ok"); -} -// return the current block, used in syncing -elseif($q=="currentBlock"){ - $current=$block->current(); - api_echo($current); -} -// return a specific block, used in syncing -elseif($q=="getBlock"){ - $height=intval($data['height']); - - $export=$block->export("",$height); - if(!$export) api_err("invalid-block"); - api_echo($export); - } - elseif($q=="getBlocks"){ -// returns X block starting at height, used in syncing - - $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); - - } - // returns a full list of unblacklisted peers in a random order - elseif($q=="getPeers"){ - $peers=$db->run("SELECT ip,hostname FROM peers WHERE blacklisted +single( + "SELECT COUNT(1) FROM peers WHERE hostname=:hostname AND ip=:ip", + [":hostname" => $hostname, ":ip" => $ip] + ); + if ($res == 1) { + if ($data['repeer'] == 1) { + $res = peer_post($hostname."/peer.php?q=peer", ["hostname" => $_config['hostname']]); + if ($res !== false) { + api_echo("re-peer-ok"); + } else { + api_err("re-peer failed - $result"); + } + } + api_echo("peer-ok-already"); + } + // if we have enough peers, add it to DB as reserve + $res = $db->single("SELECT COUNT(1) FROM peers WHERE blacklistedUNIX_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", + [":ip" => $ip, ":hostname2" => $hostname, ":hostname" => $hostname, ":reserve" => $reserve] + ); + // re-peer to make sure the peer is valid + $res = peer_post($hostname."/peer.php?q=peer", ["hostname" => $_config['hostname']]); + if ($res !== false) { + api_echo("re-peer-ok"); + } else { + $db->run("DELETE FROM peers WHERE ip=:ip", [":ip" => $ip]); + api_err("re-peer failed - $result"); + } +} elseif ($q == "ping") { + // confirm peer is active + api_echo("pong"); +} elseif ($q == "submitTransaction") { + // receive a new transaction from a peer + $current = $block->current(); + + + // no transactions accepted if the sanity is syncing + if ($_config['sanity_sync'] == 1) { + api_err("sanity-sync"); + } + + $data['id'] = san($data['id']); + // validate transaction data + if (!$trx->check($data)) { + api_err("Invalid transaction"); + } + $hash = $data['id']; + // make sure it's not already in mempool + $res = $db->single("SELECT COUNT(1) FROM mempool WHERE id=:id", [":id" => $hash]); + if ($res != 0) { + api_err("The transaction is already in mempool"); + } + // make sure the peer is not flooding us with transactions + $res = $db->single("SELECT COUNT(1) FROM mempool WHERE src=:src", [":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", [":peer" => $ip]); + if ($res > $_config['peer_max_mempool']) { + api_error("Too many transactions broadcasted from this peer"); + } + + + // make sure the transaction is not already on the blockchain + $res = $db->single("SELECT COUNT(1) FROM transactions WHERE id=:id", [":id" => $hash]); + if ($res != 0) { + api_err("The transaction is already in a block"); + } + $acc = new Account(); + $src = $acc->get_address($data['public_key']); + // make sure the sender has enough balance + $balance = $db->single("SELECT balance FROM accounts WHERE id=:id", [":id" => $src]); + if ($balance < $val + $fee) { + api_err("Not enough funds"); + } + + // make sure the sender has enough pending balance + $memspent = $db->single("SELECT SUM(val+fee) FROM mempool WHERE src=:src", [":src" => $src]); + if ($balance - $memspent < $val + $fee) { + api_err("Not enough funds (mempool)"); + } + + // add to mempool + $trx->add_mempool($data, $ip); + + // rebroadcast the transaction to some peers unless the transaction is smaller than the average size of transactions in mempool - protect against garbage data flooding + $res = $db->row("SELECT COUNT(1) as c, sum(val) as v FROM mempool ", [":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 2>&1 &"); + } + api_echo("transaction-ok"); +} elseif ($q == "submitBlock") { + // receive a new block from a peer + + // if sanity sync, refuse all + if ($_config['sanity_sync'] == 1) { + _log('['.$ip."] Block rejected due to sanity sync"); + api_err("sanity-sync"); + } + $data['id'] = san($data['id']); + $current = $block->current(); + // block already in the blockchain + if ($current['id'] == $data['id']) { + api_echo("block-ok"); + } + if ($data['date'] > time() + 30) { + api_err("block in the future"); + } + + if ($current['height'] == $data['height'] && $current['id'] != $data['id']) { + // different forks, same height + $accept_new = false; + if ($current['transactions'] < $data['transactions']) { + // accept the one with most transactions + $accept_new = true; + } elseif ($current['transactions'] == $data['transactions']) { + // convert the first 12 characters from hex to decimal and the block with the largest number wins + $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) { + // if the new block is accepted, run a microsanity to sync it + _log('['.$ip."] Starting microsanity - $data[height]"); + system("php sanity.php microsanity '$ip' > /dev/null 2>&1 &"); + api_echo("microsanity"); + } else { + _log('['.$ip."] suggesting reverse-microsanity - $data[height]"); + api_echo("reverse-microsanity"); // if it's not, suggest to the peer to get the block from us + } + } + // if it's not the next block + if ($current['height'] != $data['height'] - 1) { + // if the height of the block submitted is lower than our current height, send them our current block + if ($data['height'] < $current['height']) { + $pr = $db->row("SELECT * FROM peers WHERE ip=:ip", [":ip" => $ip]); + if (!$pr) { + api_err("block-too-old"); + } + $peer_host = base58_encode($pr['hostname']); + $pr['ip'] = escapeshellcmd(san_ip($pr['ip'])); + system("php propagate.php block current '$peer_host' '$pr[ip]' > /dev/null 2>&1 &"); + _log('['.$ip."] block too old, sending our current block - $data[height]"); + + api_err("block-too-old"); + } + // if the block difference is bigger than 150, nothing should be done. They should sync via sanity + if ($data['height'] - $current['height'] > 150) { + _log('['.$ip."] block-out-of-sync - $data[height]"); + api_err("block-out-of-sync"); + } + // request them to send us a microsync with the latest blocks + _log('['.$ip."] requesting microsync - $current[height] - $data[height]"); + api_echo(["request" => "microsync", "height" => $current['height'], "block" => $current['id']]); + } + // check block data + if (!$block->check($data)) { + _log('['.$ip."] invalid block - $data[height]"); + api_err("invalid-block"); + } + $b = $data; + // add the block to the blockchain + $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('['.$ip."] invalid block data - $data[height]"); + api_err("invalid-block-data"); + } + + _log('['.$ip."] block ok, repropagating - $data[height]"); + + // send it to all our peers + system("php propagate.php block '$data[id]' all all linear > /dev/null 2>&1 &"); + api_echo("block-ok"); +} // return the current block, used in syncing +elseif ($q == "currentBlock") { + $current = $block->current(); + api_echo($current); +} // return a specific block, used in syncing +elseif ($q == "getBlock") { + $height = intval($data['height']); + + $export = $block->export("", $height); + if (!$export) { + api_err("invalid-block"); + } + api_echo($export); +} elseif ($q == "getBlocks") { +// returns X block starting at height, used in syncing + + $height = intval($data['height']); + + $r = $db->run( + "SELECT id,height FROM blocks WHERE height>=:height ORDER by height ASC LIMIT 100", + [":height" => $height] + ); + foreach ($r as $x) { + $blocks[$x['height']] = $block->export($x['id']); + } + api_echo($blocks); +} // returns a full list of unblacklisted peers in a random order +elseif ($q == "getPeers") { + $peers = $db->run("SELECT ip,hostname FROM peers WHERE blacklistedcurrent(); - $id=$current['id']; - } - $data=$block->export($id); - $id=san($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 cache file"); - // broadcasting to all peers - $ewhr=""; - // boradcasting to only certain peers - if($linear==true) $ewhr=" ORDER by RAND() LIMIT 5"; - $r=$db->run("SELECT * FROM peers WHERE blacklisted < UNIX_TIMESTAMP() AND reserve=0 $ewhr"); - foreach($r as $x) { - // encode the hostname in base58 and sanitize the IP to avoid any second order shell injections - $host=base58_encode($x['hostname']); - $ip=filter_var($x['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE); - // fork a new process to send the blocks async - if($debug) system("php propagate.php '$type' '$id' '$host' '$ip' debug"); - elseif($linear) system("php propagate.php '$type' '$id' '$host' '$ip' linear"); - else system("php propagate.php '$type' '$id' '$host' 'ip' > /dev/null 2>&1 &"); - } - exit; +if ((empty($peer) || $peer == 'all') && $type == "block") { + $whr = ""; + if ($id == "current") { + $current = $block->current(); + $id = $current['id']; + } + $data = $block->export($id); + $id = san($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 cache file"); + } + // broadcasting to all peers + $ewhr = ""; + // boradcasting to only certain peers + if ($linear == true) { + $ewhr = " ORDER by RAND() LIMIT 5"; + } + $r = $db->run("SELECT * FROM peers WHERE blacklisted < UNIX_TIMESTAMP() AND reserve=0 $ewhr"); + foreach ($r as $x) { + // encode the hostname in base58 and sanitize the IP to avoid any second order shell injections + $host = base58_encode($x['hostname']); + $ip = filter_var($x['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE); + // fork a new process to send the blocks async + if ($debug) { + system("php propagate.php '$type' '$id' '$host' '$ip' debug"); + } elseif ($linear) { + system("php propagate.php '$type' '$id' '$host' '$ip' linear"); + } else { + system("php propagate.php '$type' '$id' '$host' 'ip' > /dev/null 2>&1 &"); + } + } + exit; } - -// broadcast a block to a single peer (usually a forked process from above) -if($type=="block"){ - // current block or read cache - if($id=="current"){ - $current=$block->current(); - $data=$block->export($current['id']); - if(!$data) { echo "Invalid Block data"; exit; } - } else { - $data=file_get_contents("tmp/$id"); - if(empty($data)) { echo "Invalid Block data"; exit; } - $data=json_decode($data,true); - } - $hostname=base58_decode($peer); - // send the block as POST to the peer - echo "Block sent to $hostname:\n"; - $response= peer_post($hostname."/peer.php?q=submitBlock",$data,60, $debug); - if($response=="block-ok") { echo "Block $i accepted. Exiting.\n"; exit;} - elseif($response['request']=="microsync"){ - // the peer requested us to send more blocks, as it's behind - echo "Microsync request\n"; - $height=intval($response['height']); - $bl=san($response['block']); - $current=$block->current(); - // maximum microsync is 10 blocks, for more, the peer should sync by sanity - if($current['height']-$height>10) { echo "Height Differece too high\n"; exit; } - $last_block=$block->get($height); - // if their last block does not match our blockchain/fork, ignore the request - if ($last_block['id'] != $bl ) { echo "Last block does not match\n"; exit; } - echo "Sending the requested blocks\n"; - //start sending the requested block - for($i=$height+1;$i<=$current['height'];$i++){ - $data=$block->export("",$i); - $response = peer_post($hostname."/peer.php?q=submitBlock",$data,60,$debug); - if($response!="block-ok") { echo "Block $i not accepted. Exiting.\n"; exit;} - echo "Block\t$i\t accepted\n"; - } - - } elseif($response=="reverse-microsanity"){ - // the peer informe us that we should run a microsanity - echo "Running microsanity\n"; - $ip=trim($argv[4]); - $ip=filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE); - if(empty($ip)) die("Invalid IP"); - // fork a microsanity in a new process - system("php sanity.php microsanity '$ip' > /dev/null 2>&1 &"); - } - else echo "Block not accepted!\n"; - +// broadcast a block to a single peer (usually a forked process from above) +if ($type == "block") { + // current block or read cache + if ($id == "current") { + $current = $block->current(); + $data = $block->export($current['id']); + if (!$data) { + echo "Invalid Block data"; + exit; + } + } else { + $data = file_get_contents("tmp/$id"); + if (empty($data)) { + echo "Invalid Block data"; + exit; + } + $data = json_decode($data, true); + } + $hostname = base58_decode($peer); + // send the block as POST to the peer + echo "Block sent to $hostname:\n"; + $response = peer_post($hostname."/peer.php?q=submitBlock", $data, 60, $debug); + if ($response == "block-ok") { + echo "Block $i accepted. Exiting.\n"; + exit; + } elseif ($response['request'] == "microsync") { + // the peer requested us to send more blocks, as it's behind + echo "Microsync request\n"; + $height = intval($response['height']); + $bl = san($response['block']); + $current = $block->current(); + // maximum microsync is 10 blocks, for more, the peer should sync by sanity + if ($current['height'] - $height > 10) { + echo "Height Differece too high\n"; + exit; + } + $last_block = $block->get($height); + // if their last block does not match our blockchain/fork, ignore the request + if ($last_block['id'] != $bl) { + echo "Last block does not match\n"; + exit; + } + echo "Sending the requested blocks\n"; + //start sending the requested block + for ($i = $height + 1; $i <= $current['height']; $i++) { + $data = $block->export("", $i); + $response = peer_post($hostname."/peer.php?q=submitBlock", $data, 60, $debug); + if ($response != "block-ok") { + echo "Block $i not accepted. Exiting.\n"; + exit; + } + echo "Block\t$i\t accepted\n"; + } + } elseif ($response == "reverse-microsanity") { + // the peer informe us that we should run a microsanity + echo "Running microsanity\n"; + $ip = trim($argv[4]); + $ip = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE); + if (empty($ip)) { + die("Invalid IP"); + } + // fork a microsanity in a new process + system("php sanity.php microsanity '$ip' > /dev/null 2>&1 &"); + } else { + echo "Block not accepted!\n"; + } } // broadcast a transaction to some peers -if($type=="transaction"){ +if ($type == "transaction") { + $trx = new Transaction(); + // get the transaction data + $data = $trx->export($id); - $trx=new Transaction; - // get the transaction data - $data=$trx->export($id); - - if(!$data){ echo "Invalid transaction id\n"; exit; } - // if the transaction was first sent locally, we will send it to all our peers, otherwise to just a few - 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 ".intval($_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"; - } + if (!$data) { + echo "Invalid transaction id\n"; + exit; + } + // if the transaction was first sent locally, we will send it to all our peers, otherwise to just a few + 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 ".intval($_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"; + } + } } - -?> diff --git a/sanity.php b/sanity.php index cefaa87..9f15639 100755 --- a/sanity.php +++ b/sanity.php @@ -1,7 +1,7 @@ 86400){ - @unlink("tmp/sanity-lock"); - } - if(!$ignore_lock) die("Sanity lock in place"); -} +if (file_exists("tmp/sanity-lock")) { + $ignore_lock = false; + if ($argv[1] == "force") { + $res = intval(shell_exec("ps aux|grep sanity.php|grep -v grep|wc -l")); + if ($res == 1) { + $ignore_lock = true; + } + } + $pid_time = filemtime("tmp/sanity-lock"); + // if the process died, restart after 1day + if (time() - $pid_time > 86400) { + @unlink("tmp/sanity-lock"); + } + if (!$ignore_lock) { + die("Sanity lock in place"); + } +} // set the new sanity lock $lock = fopen("tmp/sanity-lock", "w"); fclose($lock); -$arg=trim($argv[1]); -$arg2=trim($argv[2]); +$arg = trim($argv[1]); +$arg2 = trim($argv[2]); echo "Sleeping for 3 seconds\n"; // sleep for 3 seconds to make sure there's a delay between starting the sanity and other processes -if($arg!="microsanity") sleep(3); +if ($arg != "microsanity") { + sleep(3); +} require_once("include/init.inc.php"); // the sanity can't run without the schema being installed -if($_config['dbversion']<2){ - die("DB schema not created"); - @unlink("tmp/sanity-lock"); - exit; +if ($_config['dbversion'] < 2) { + die("DB schema not created"); + @unlink("tmp/sanity-lock"); + exit; } -$block=new Block(); -$acc=new Account(); -$current=$block->current(); +$block = new Block(); +$acc = new Account(); +$current = $block->current(); // the microsanity process is an anti-fork measure that will determine the best blockchain to choose for the last block -$microsanity=false; -if($arg=="microsanity"&&!empty($arg2)){ +$microsanity = false; +if ($arg == "microsanity" && !empty($arg2)) { + do { + // the microsanity runs only against 1 specific peer + $x = $db->row( + "SELECT id,hostname FROM peers WHERE reserve=0 AND blacklisted $arg2] + ); -do { - // the microsanity runs only against 1 specific peer - $x=$db->row("SELECT id,hostname FROM peers WHERE reserve=0 AND blacklisted$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; } - $data['id']=san($data['id']); - $data['height']=san($data['height']); - // nothing to be done, same blockchain - if($data['id']==$current['id']) {echo "Same block\n"; break;} + if (!$x) { + echo "Invalid node - $arg2\n"; + break; + } + $url = $x['hostname']."/peer.php?q="; + $data = peer_post($url."getBlock", ["height" => $current['height']]); - // the blockchain with the most transactions wins the fork (to encourage the miners to include as many transactions as possible) / might backfire on garbage - if($current['transactions']>$data['transactions']){ - echo "Block has less transactions\n"; - break; - } elseif($current['transactions']==$data['transactions']) { - // transform the first 12 chars into an integer and choose the blockchain with the biggest value - $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; - } - } - // make sure the block is valid - $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'], $prev['height'])) { echo "Invalid prev-block\n"; break;} - if(!$block->check($data)) break; + if (!$data) { + echo "Invalid getBlock result\n"; + break; + } + $data['id'] = san($data['id']); + $data['height'] = san($data['height']); + // nothing to be done, same blockchain + if ($data['id'] == $current['id']) { + echo "Same block\n"; + break; + } - // delete the last block - $block->pop(1); + // the blockchain with the most transactions wins the fork (to encourage the miners to include as many transactions as possible) / might backfire on garbage + if ($current['transactions'] > $data['transactions']) { + echo "Block has less transactions\n"; + break; + } elseif ($current['transactions'] == $data['transactions']) { + // transform the first 12 chars into an integer and choose the blockchain with the biggest value + $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; + } + } + // make sure the block is valid + $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'], + $prev['height'] + )) { + echo "Invalid prev-block\n"; + break; + } + if (!$block->check($data)) { + break; + } + + // delete the last block + $block->pop(1); - // add the new block - 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]"); - + // add the new block + 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; +} - -} while(0); - - @unlink("tmp/sanity-lock"); -exit; -} - - -$t=time(); +$t = time(); //if($t-$_config['sanity_last']<300) {@unlink("tmp/sanity-lock"); die("The sanity cron was already run recently"); } _log("Starting sanity"); // update the last time sanity ran, to set the execution of the next run -$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; -$total_active_peers=0; +$db->run("UPDATE config SET val=:time WHERE cfg='sanity_last'", [":time" => $t]); +$block_peers = []; +$longest_size = 0; +$longest = 0; +$blocks = []; +$blocks_count = []; +$most_common = ""; +$most_common_size = 0; +$total_active_peers = 0; // checking peers // delete the dead peers $db->run("DELETE from peers WHERE fails>100 OR stuckfail>200"); -$r=$db->run("SELECT id,hostname,stuckfail,fails FROM peers WHERE reserve=0 AND blacklistedrun("SELECT id,hostname,stuckfail,fails FROM peers WHERE reserve=0 AND blacklisted$_config['hostname'], "repeer"=>1)); - if($res!==false) {$i++; echo "Peering OK - $peer\n"; } - else echo "Peering FAIL - $peer\n"; - if($i>$_config['max_peers']) break; - } - // count the total peers we have - $r=$db->run("SELECT id,hostname FROM peers WHERE reserve=0 AND blacklisted exit - @unlink("tmp/sanity-lock"); - die("Could not peer to any peers! Please check internet connectivity!\n"); - } +if ($total_peers == 0 && $_config['testnet'] == false) { + $i = 0; + echo "No peers found. Attempting to get peers from arionum.com\n"; + $f = file("https://www.arionum.com/peers.txt"); + shuffle($f); + // we can't connect to arionum.com + if (count($f) < 2) { + @unlink("tmp/sanity-lock"); + die("Could nto connect to arionum.com! Will try later!\n"); + } + foreach ($f as $peer) { + //peer with all until max_peers, this will ask them to send a peering request to our peer.php where we add their peer to the db. + $peer = trim(san_host($peer)); + $bad_peers = ["127.0.0.1", "localhost", "10.0.0", "192.168.0"]; + if (str_replace($bad_peers, "", $peer) != $peer) { + continue; + } + $peer = filter_var($peer, FILTER_SANITIZE_URL); + if (!filter_var($peer, FILTER_VALIDATE_URL)) { + continue; + } + // store the hostname as md5 hash, for easier checking + $pid = md5($peer); + // do not peer if we are already peered + if ($peered[$pid] == 1) { + continue; + } + $peered[$pid] = 1; + $res = peer_post($peer."/peer.php?q=peer", ["hostname" => $_config['hostname'], "repeer" => 1]); + if ($res !== false) { + $i++; + echo "Peering OK - $peer\n"; + } else { + echo "Peering FAIL - $peer\n"; + } + if ($i > $_config['max_peers']) { + break; + } + } + // count the total peers we have + $r = $db->run("SELECT id,hostname FROM peers WHERE reserve=0 AND blacklisted exit + @unlink("tmp/sanity-lock"); + die("Could not peer to any peers! Please check internet connectivity!\n"); + } } // contact all the active peers -foreach($r as $x){ - _log("Contacting peer $x[hostname]"); - $url=$x['hostname']."/peer.php?q="; - // get their peers list - $data=peer_post($url."getPeers",array(),5); - if($data===false) { - _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",array(":id"=>$x['id'])); - continue; - } - - $i=0; - foreach($data as $peer){ - // store the hostname as md5 hash, for easier checking - $peer['hostname']=san_host($peer['hostname']); - $peer['ip']=san_ip($peer['ip']); - $pid=md5($peer['hostname']); - // do not peer if we are already peered - if($peered[$pid]==1) continue; - $peered[$pid]=1; - $bad_peers=array("127.0.0.1","localhost","10.0.0.","192.168.0."); - if(str_replace($bad_peers,"",$peer['hostname'])!=$peer['hostname']) continue; - // if it's our hostname, ignore - if($peer['hostname']==$_config['hostname']) continue; - // if invalid hostname, ignore - if (!filter_var($peer['hostname'], FILTER_VALIDATE_URL)) continue; - // 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",array(":ip"=>$peer['ip'],":hostname"=>$peer['hostname']))){ - $i++; - // check a max_test_peers number of peers from each peer - if($i>$_config['max_test_peers']) break; - $peer['hostname'] = filter_var($peer['hostname'], FILTER_SANITIZE_URL); - // peer with each one - _log("Trying to peer with recommended peer: $peer[hostname]"); - $test=peer_post($peer['hostname']."/peer.php?q=peer",array("hostname"=>$_config['hostname']),5); - if($test!==false){ - $total_peers++; - echo "Peered with: $peer[hostname]\n"; - } - } - } - - - - - - // get the current block and check it's blockchain - $data=peer_post($url."currentBlock",array(),5); - if($data===false) continue; - // peer was responsive, mark it as good - if($x['fails']>0) $db->run("UPDATE peers SET fails=0 WHERE id=:id",array(":id"=>$x['id'])); - $data['id']=san($data['id']); - $data['height']=san($data['height']); - - if($data['height']<$current['height']-500) { - $db->run("UPDATE peers SET stuckfail=stuckfail+1, blacklisted=UNIX_TIMESTAMP()+7200 WHERE id=:id",array(":id"=>$x['id'])); - continue; - } else { - if($x['stuckfail']>0) $db->run("UPDATE peers SET stuckfail=0 WHERE id=:id",array(":id"=>$x['id'])); - } - $total_active_peers++; - // add the hostname and block relationship to an array - $block_peers[$data['id']][]=$x['hostname']; - // count the number of peers with this block id - $blocks_count[$data['id']]++; - // keep block data for this block id - $blocks[$data['id']]=$data; - // set the most common block on all peers - if($blocks_count[$data['id']]>$most_common_size){ - $most_common=$data['id']; - $most_common_size=$blocks_count[$data['id']]; - } - // set the largest height block - if($data['height']>$largest_height){ - $largest_height=$data['height']; - $largest_height_block=$data['id']; - } elseif($data['height']==$largest_height&&$data['id']!=$largest_height_block){ - // if there are multiple blocks on the largest height, choose one with the smallest (hardest) difficulty - if($data['difficulty']==$blocks[$largest_height_block]['difficulty']){ - // if they have the same difficulty, choose if it's most common - if($most_common==$data['id']){ - $largest_height=$data['height']; - $largest_height_block=$data['id']; - } else { - // if this block has more transactions, declare it as winner - 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']) { - // if the blocks have the same number of transactions, choose the one with the highest derived integer from the first 12 hex characters - $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']){ - // choose smallest (hardest) difficulty - $largest_height=$data['height']; - $largest_height_block=$data['id']; - } - - } +foreach ($r as $x) { + _log("Contacting peer $x[hostname]"); + $url = $x['hostname']."/peer.php?q="; + // get their peers list + $data = peer_post($url."getPeers", [], 5); + if ($data === false) { + _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']] + ); + continue; + } + + $i = 0; + foreach ($data as $peer) { + // store the hostname as md5 hash, for easier checking + $peer['hostname'] = san_host($peer['hostname']); + $peer['ip'] = san_ip($peer['ip']); + $pid = md5($peer['hostname']); + // do not peer if we are already peered + if ($peered[$pid] == 1) { + continue; + } + $peered[$pid] = 1; + $bad_peers = ["127.0.0.1", "localhost", "10.0.0.", "192.168.0."]; + if (str_replace($bad_peers, "", $peer['hostname']) != $peer['hostname']) { + continue; + } + // if it's our hostname, ignore + if ($peer['hostname'] == $_config['hostname']) { + continue; + } + // if invalid hostname, ignore + if (!filter_var($peer['hostname'], FILTER_VALIDATE_URL)) { + continue; + } + // 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']] + )) { + $i++; + // check a max_test_peers number of peers from each peer + if ($i > $_config['max_test_peers']) { + break; + } + $peer['hostname'] = filter_var($peer['hostname'], FILTER_SANITIZE_URL); + // peer with each one + _log("Trying to peer with recommended peer: $peer[hostname]"); + $test = peer_post($peer['hostname']."/peer.php?q=peer", ["hostname" => $_config['hostname']], 5); + if ($test !== false) { + $total_peers++; + echo "Peered with: $peer[hostname]\n"; + } + } + } + // get the current block and check it's blockchain + $data = peer_post($url."currentBlock", [], 5); + if ($data === false) { + continue; + } + // peer was responsive, mark it as good + if ($x['fails'] > 0) { + $db->run("UPDATE peers SET fails=0 WHERE id=:id", [":id" => $x['id']]); + } + $data['id'] = san($data['id']); + $data['height'] = san($data['height']); + if ($data['height'] < $current['height'] - 500) { + $db->run( + "UPDATE peers SET stuckfail=stuckfail+1, blacklisted=UNIX_TIMESTAMP()+7200 WHERE id=:id", + [":id" => $x['id']] + ); + continue; + } else { + if ($x['stuckfail'] > 0) { + $db->run("UPDATE peers SET stuckfail=0 WHERE id=:id", [":id" => $x['id']]); + } + } + $total_active_peers++; + // add the hostname and block relationship to an array + $block_peers[$data['id']][] = $x['hostname']; + // count the number of peers with this block id + $blocks_count[$data['id']]++; + // keep block data for this block id + $blocks[$data['id']] = $data; + // set the most common block on all peers + if ($blocks_count[$data['id']] > $most_common_size) { + $most_common = $data['id']; + $most_common_size = $blocks_count[$data['id']]; + } + // set the largest height block + if ($data['height'] > $largest_height) { + $largest_height = $data['height']; + $largest_height_block = $data['id']; + } elseif ($data['height'] == $largest_height && $data['id'] != $largest_height_block) { + // if there are multiple blocks on the largest height, choose one with the smallest (hardest) difficulty + if ($data['difficulty'] == $blocks[$largest_height_block]['difficulty']) { + // if they have the same difficulty, choose if it's most common + if ($most_common == $data['id']) { + $largest_height = $data['height']; + $largest_height_block = $data['id']; + } else { + // if this block has more transactions, declare it as winner + 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']) { + // if the blocks have the same number of transactions, choose the one with the highest derived integer from the first 12 hex characters + $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']) { + // choose smallest (hardest) difficulty + $largest_height = $data['height']; + $largest_height_block = $data['id']; + } + } } echo "Most common: $most_common\n"; echo "Most common block: $most_common_size\n"; @@ -310,102 +377,141 @@ echo "Max height: $largest_height\n"; echo "Current block: $current[height]\n"; // if we're not on the largest height -if($current['height']<$largest_height&&$largest_height>1){ - // start sanity sync / block all other transactions/blocks - $db->run("UPDATE config SET val=1 WHERE cfg='sanity_sync'"); - sleep(10); - _log("Longest chain rule triggered - $largest_height - $largest_height_block"); - // choose the peers which have the larget height block - $peers=$block_peers[$largest_height_block]; - shuffle($peers); - // sync from them - foreach($peers as $host){ - _log("Starting to sync from $host"); - $url=$host."/peer.php?q="; - $data=peer_post($url."getBlock",array("height"=>$current['height']),60); - // invalid data - if($data===false){ _log("Could not get block from $host - $current[height]"); continue; } - $data['id']=san($data['id']); - $data['height']=san($data['height']); +if ($current['height'] < $largest_height && $largest_height > 1) { + // start sanity sync / block all other transactions/blocks + $db->run("UPDATE config SET val=1 WHERE cfg='sanity_sync'"); + sleep(10); + _log("Longest chain rule triggered - $largest_height - $largest_height_block"); + // choose the peers which have the larget height block + $peers = $block_peers[$largest_height_block]; + shuffle($peers); + // sync from them + foreach ($peers as $host) { + _log("Starting to sync from $host"); + $url = $host."/peer.php?q="; + $data = peer_post($url."getBlock", ["height" => $current['height']], 60); + // invalid data + if ($data === false) { + _log("Could not get block from $host - $current[height]"); + continue; + } + $data['id'] = san($data['id']); + $data['height'] = san($data['height']); - // if we're not on the same blockchain but the blockchain is most common with over 90% of the peers, delete the last 3 blocks and retry - if($data['id']!=$current['id']&&$data['id']==$most_common&&($most_common_size/$total_active_peers)>0.90){ - $block->delete($current['height']-3); - $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; } - - }elseif($data['id']!=$current['id']&&$data['id']!=$most_common){ - //if we're not on the same blockchain and also it's not the most common, verify all the blocks on on this blockchain starting at current-10 until current - $invalid=false; - $last_good=$current['height']; - for($i=$current['height']-10;$i<$current['height'];$i++){ - $data=peer_post($url."getBlock",array("height"=>$i)); - if($data===false){ $invalid=true; break; } - $ext=$block->get($i); - if($i==$current['height']-10&&$ext['id']!=$data['id']){ $invalid=true; break; } - - if($ext['id']==$data['id']) $last_good=$i; - - } - // if last 10 blocks are good, verify all the blocks - if($invalid==false) { - $cblock=array(); - for($i=$last_good;$i<=$largest_height;$i++){ - $data=peer_post($url."getBlock",array("height"=>$i)); - if($data===false){ $invalid=true; break; } - $cblock[$i]=$data; - } - // check if the block mining data is correct - for($i=$last_good+1;$i<=$largest_height;$i++){ - if(!$block->mine($cblock[$i]['public_key'], $cblock[$i]['nonce'], $cblock[$i]['argon'], $cblock[$i]['difficulty'], $cblock[$i-1]['id'],$cblock[$i-1]['height'])) {$invalid=true; break; } - } - } - // if the blockchain proves ok, delete until the last block - if($invalid==false){ - $block->delete($last_good); - $current=$block->current(); - $data=$current; - } - - } - // if current still doesn't match the data, something went wrong - if($data['id']!=$current['id']) continue; - // start syncing all blocks - 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){ - $b['id']=san($b['id']); - $b['height']=san($b['height']); + // if we're not on the same blockchain but the blockchain is most common with over 90% of the peers, delete the last 3 blocks and retry + if ($data['id'] != $current['id'] && $data['id'] == $most_common && ($most_common_size / $total_active_peers) > 0.90) { + $block->delete($current['height'] - 3); + $current = $block->current(); + $data = peer_post($url."getBlock", ["height" => $current['height']]); - 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 ($data === false) { + _log("Could not get block from $host - $current[height]"); + break; + } + } elseif ($data['id'] != $current['id'] && $data['id'] != $most_common) { + //if we're not on the same blockchain and also it's not the most common, verify all the blocks on on this blockchain starting at current-10 until current + $invalid = false; + $last_good = $current['height']; + for ($i = $current['height'] - 10; $i < $current['height']; $i++) { + $data = peer_post($url."getBlock", ["height" => $i]); + if ($data === false) { + $invalid = true; + break; + } + $ext = $block->get($i); + if ($i == $current['height'] - 10 && $ext['id'] != $data['id']) { + $invalid = true; + break; + } - } - if($good_peer) break; - - } - $db->run("UPDATE config SET val=0 WHERE cfg='sanity_sync'",array(":time"=>$t)); + if ($ext['id'] == $data['id']) { + $last_good = $i; + } + } + // if last 10 blocks are good, verify all the blocks + if ($invalid == false) { + $cblock = []; + for ($i = $last_good; $i <= $largest_height; $i++) { + $data = peer_post($url."getBlock", ["height" => $i]); + if ($data === false) { + $invalid = true; + break; + } + $cblock[$i] = $data; + } + // check if the block mining data is correct + for ($i = $last_good + 1; $i <= $largest_height; $i++) { + if (!$block->mine( + $cblock[$i]['public_key'], + $cblock[$i]['nonce'], + $cblock[$i]['argon'], + $cblock[$i]['difficulty'], + $cblock[$i - 1]['id'], + $cblock[$i - 1]['height'] + )) { + $invalid = true; + break; + } + } + } + // if the blockchain proves ok, delete until the last block + if ($invalid == false) { + $block->delete($last_good); + $current = $block->current(); + $data = $current; + } + } + // if current still doesn't match the data, something went wrong + if ($data['id'] != $current['id']) { + continue; + } + // start syncing all blocks + while ($current['height'] < $largest_height) { + $data = peer_post($url."getBlocks", ["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) { + $b['id'] = san($b['id']); + $b['height'] = san($b['height']); + + 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'", [":time" => $t]); } @@ -413,96 +519,120 @@ if($current['height']<$largest_height&&$largest_height>1){ $db->run("DELETE FROM `mempool` WHERE `date` < UNIX_TIMESTAMP()-(3600*24*14)"); - //rebroadcasting local transactions -if($_config['sanity_rebroadcast_locals']==true){ -$r=$db->run("SELECT id FROM mempool WHERE height>=:current and peer='local' order by `height` asc LIMIT 20",array(":current"=>$current['height'])); -_log("Rebroadcasting local transactions - ".count($r)); -foreach($r as $x){ - $x['id']=san($x['id']); +if ($_config['sanity_rebroadcast_locals'] == true) { + $r = $db->run( + "SELECT id FROM mempool WHERE height>=:current and peer='local' order by `height` asc LIMIT 20", + [":current" => $current['height']] + ); + _log("Rebroadcasting local transactions - ".count($r)); + foreach ($r as $x) { + $x['id'] = san($x['id']); system("php propagate.php transaction $x[id] > /dev/null 2>&1 &"); - $db->run("UPDATE mempool SET height=:current WHERE id=:id",array(":id"=>$x['id'], ":current"=>$current['height'])); -} + $db->run( + "UPDATE mempool SET height=:current WHERE id=:id", + [":id" => $x['id'], ":current" => $current['height']] + ); + } } //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)); +$forgotten = $current['height'] - $_config['sanity_rebroadcast_height']; +$r = $db->run( + "SELECT id FROM mempool WHERE height<:forgotten ORDER by val DESC LIMIT 10", + [":forgotten" => $forgotten] +); _log("Rebroadcasting external transactions - ".count($r)); -foreach($r as $x){ - $x['id']=san($x['id']); - system("php propagate.php transaction $x[id] > /dev/null 2>&1 &"); - $db->run("UPDATE mempool SET height=:current WHERE id=:id",array(":id"=>$x['id'], ":current"=>$current['height'])); +foreach ($r as $x) { + $x['id'] = san($x['id']); + system("php propagate.php transaction $x[id] > /dev/null 2>&1 &"); + $db->run("UPDATE mempool SET height=:current WHERE id=:id", [":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 blacklistedrun("UPDATE peers SET reserve=0 WHERE reserve=1 AND blacklistedrun("SELECT * FROM peers WHERE blacklistedrun("UPDATE peers SET fails=fails+1, blacklisted=UNIX_TIMESTAMP()+((fails+1)*60) WHERE id=:id",array(":id"=>$x['id'])); - _log("Random reserve peer test $x[hostname] -> FAILED"); - }else{ - _log("Random reserve peer test $x[hostname] -> OK"); - $db->run("UPDATE peers SET fails=0 WHERE id=:id",array(":id"=>$x['id'])); - } +$r = $db->run("SELECT * FROM peers WHERE blacklistedrun( + "UPDATE peers SET fails=fails+1, blacklisted=UNIX_TIMESTAMP()+((fails+1)*60) WHERE id=:id", + [":id" => $x['id']] + ); + _log("Random reserve peer test $x[hostname] -> FAILED"); + } else { + _log("Random reserve peer test $x[hostname] -> OK"); + $db->run("UPDATE peers SET fails=0 WHERE id=:id", [":id" => $x['id']]); + } } //clean tmp files _log("Cleaning 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"); +$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 the last blocks -if($_config['sanity_recheck_blocks']>0){ -_log("Rechecking blocks"); - $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]; +if ($_config['sanity_recheck_blocks'] > 0) { + _log("Rechecking blocks"); + $blocks = []; + $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", [":height" => $start]); + foreach ($r as $x) { + $blocks[$x['height']] = $x; + $max_height = $x['height']; + } - $key=$db->single("SELECT public_key FROM accounts WHERE id=:id",array(":id"=>$data['generator'])); + for ($i = $start + 1; $i <= $max_height; $i++) { + $data = $blocks[$i]; - if(!$block->mine($key,$data['nonce'], $data['argon'], $data['difficulty'], $blocks[$i-1]['id'], $blocks[$i-1]['height'])) { - $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); + $key = $db->single("SELECT public_key FROM accounts WHERE id=:id", [":id" => $data['generator']]); - $db->run("UPDATE config SET val=0 WHERE cfg='sanity_sync'"); - break; - } - } - if($all_blocks_ok) echo "All checked blocks are ok\n"; + if (!$block->mine( + $key, + $data['nonce'], + $data['argon'], + $data['difficulty'], + $blocks[$i - 1]['id'], + $blocks[$i - 1]['height'] + )) { + $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"; + } } _log("Finishing sanity"); @unlink("tmp/sanity-lock"); -?> diff --git a/util.php b/util.php index 6a617a4..7fe4b84 100755 --- a/util.php +++ b/util.php @@ -1,7 +1,7 @@ run("DELETE FROM {$table}"); +if ($cmd == 'clean') { + $tables = ["blocks", "accounts", "transactions", "mempool"]; + foreach ($tables as $table) { + $db->run("DELETE FROM {$table}"); + } -echo "\n The database has been cleared\n"; - -} - - -/** + echo "\n The database has been cleared\n"; +} /** * @api {php util.php} pop Pop * @apiName pop * @apiGroup UTIL @@ -64,13 +61,11 @@ echo "\n The database has been cleared\n"; * php util.php pop 1 */ -elseif($cmd=='pop'){ - $no=intval($argv[2]); - $block=new Block; - $block->pop($no); -} - -/** +elseif ($cmd == 'pop') { + $no = intval($argv[2]); + $block = new Block(); + $block->pop($no); +} /** * @api {php util.php} block-time Block-time * @apiName block-time * @apiGroup UTIL @@ -86,29 +81,28 @@ elseif($cmd=='pop'){ * Average block time: 217 seconds */ -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 == '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"; +} /** * @api {php util.php} peer Peer * @apiName peer * @apiGroup UTIL * @apiDescription Creates a peering session with another node * * @apiParam {text} arg2 The Hostname of the other node - * + * * @apiExample {cli} Example usage: * php util.php peer http://peer1.arionum.com * @@ -117,12 +111,14 @@ 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 == "peer") { + $res = peer_post($argv[2]."/peer.php?q=peer", ["hostname" => $_config['hostname']]); + if ($res !== false) { + echo "Peering OK\n"; + } else { + echo "Peering FAIL\n"; + } +} /** * @api {php util.php} current Current * @apiName current * @apiGroup UTIL @@ -155,15 +151,14 @@ elseif($cmd=="peer"){ * */ -elseif ($cmd=="current") { - $block=new Block; - var_dump($block->current()); -} -/** +elseif ($cmd == "current") { + $block = new Block(); + var_dump($block->current()); +} /** * @api {php util.php} blocks Blocks * @apiName blocks * @apiGroup UTIL - * @apiDescription Prints the id and the height of the blocks >=arg2, max 100 or arg3 + * @apiDescription Prints the id and the height of the blocks >=arg2, max 100 or arg3 * * @apiParam {number} arg2 Starting height * @@ -180,16 +175,17 @@ elseif ($cmd=="current") { * 10805 5RBeWXo2c9NZ7UF2ubztk53PZpiA4tsk3bhXNXbcBk89cNqorNj771Qu4kthQN5hXLtu1hzUnv7nkH33hDxBM34m * */ - 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 == "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", [":height" => $height]); + foreach ($r as $x) { + echo "$x[height]\t$x[id]\n"; + } +} /** * @api {php util.php} recheck-blocks Recheck-Blocks * @apiName recheck-blocks * @apiGroup UTIL @@ -199,26 +195,32 @@ elseif ($cmd=="current") { * php util.php recheck-blocks * */ -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'])); +elseif ($cmd == "recheck-blocks") { + $blocks = []; + $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]; - if(!$block->mine($key,$data['nonce'], $data['argon'], $data['difficulty'], $blocks[$i-1]['id'],$blocks[$i-1]['height'])) { - _log("Invalid block detected. We should delete everything after $data[height] - $data[id]"); - break; - } - } -} -/** + $key = $db->single("SELECT public_key FROM accounts WHERE id=:id", [":id" => $data['generator']]); + + if (!$block->mine( + $key, + $data['nonce'], + $data['argon'], + $data['difficulty'], + $blocks[$i - 1]['id'], + $blocks[$i - 1]['height'] + )) { + _log("Invalid block detected. We should delete everything after $data[height] - $data[id]"); + break; + } + } +} /** * @api {php util.php} peers Peers * @apiName peers * @apiGroup UTIL @@ -232,15 +234,16 @@ elseif($cmd=="recheck-blocks"){ * ... * http://aro.master.hashpi.com active */ - elseif($cmd=="peers"){ - $r=$db->run("SELECT * FROM peers ORDER by reserve ASC"); - $status="active"; - if($x['reserve']==1) $status="reserve"; - foreach($r as $x){ - echo "$x[hostname]\t$status\n"; - } -} -/** +elseif ($cmd == "peers") { + $r = $db->run("SELECT * FROM peers ORDER by reserve ASC"); + $status = "active"; + if ($x['reserve'] == 1) { + $status = "reserve"; + } + foreach ($r as $x) { + echo "$x[hostname]\t$status\n"; + } +} /** * @api {php util.php} mempool Mempool * @apiName mempool * @apiGroup UTIL @@ -252,12 +255,10 @@ elseif($cmd=="recheck-blocks"){ * @apiSuccessExample {text} Success-Response: * Mempool size: 12 */ - elseif($cmd=="mempool"){ -$res=$db->single("SELECT COUNT(1) from mempool"); -echo "Mempool size: $res\n"; - -} -/** +elseif ($cmd == "mempool") { + $res = $db->single("SELECT COUNT(1) from mempool"); + echo "Mempool size: $res\n"; +} /** * @api {php util.php} delete-peer Delete-peer * @apiName delete-peer * @apiGroup UTIL @@ -271,24 +272,25 @@ echo "Mempool size: $res\n"; * @apiSuccessExample {text} Success-Response: * Peer removed */ -elseif($cmd=="delete-peer"){ - $peer=trim($argv[2]); - if(empty($peer)) die("Invalid peer"); - $db->run("DELETE FROM peers WHERE ip=:ip",array(":ip"=>$peer)); - echo "Peer removed\n"; -}elseif($cmd=="recheck-peers"){ - $r=$db->run("SELECT * FROM peers"); - foreach($r as $x){ - $a=peer_post($x['hostname']."/peer.php?q=ping"); - if($a!="pong"){ - echo "$x[hostname] -> failed\n"; - $db->run("DELETE FROM peers WHERE id=:id",array(":id"=>$x['id'])); - } else echo "$x[hostname] ->ok \n"; - } - -} - -/** +elseif ($cmd == "delete-peer") { + $peer = trim($argv[2]); + if (empty($peer)) { + die("Invalid peer"); + } + $db->run("DELETE FROM peers WHERE ip=:ip", [":ip" => $peer]); + echo "Peer removed\n"; +} elseif ($cmd == "recheck-peers") { + $r = $db->run("SELECT * FROM peers"); + foreach ($r as $x) { + $a = peer_post($x['hostname']."/peer.php?q=ping"); + if ($a != "pong") { + echo "$x[hostname] -> failed\n"; + $db->run("DELETE FROM peers WHERE id=:id", [":id" => $x['id']]); + } else { + echo "$x[hostname] ->ok \n"; + } + } +} /** * @api {php util.php} peers-block Peers-Block * @apiName peers-block * @apiGroup UTIL @@ -302,23 +304,24 @@ elseif($cmd=="delete-peer"){ * ... * http://peer10.arionum.com 16849 */ -elseif($cmd=="peers-block"){ - $only_diff=false; - if($argv[2]=="diff"){ - $current=$db->single("SELECT height FROM blocks ORDER by height DESC LIMIT 1"); - $only_diff=true; - } - $r=$db->run("SELECT * FROM peers WHERE blacklistedsingle("SELECT height FROM blocks ORDER by height DESC LIMIT 1"); + $only_diff = true; + } + $r = $db->run("SELECT * FROM peers WHERE blacklistedsingle( + "SELECT balance FROM accounts WHERE id=:id OR public_key=:id2 LIMIT 1", + [":id" => $id, ":id2" => $id] + ); - $id=san($argv[2]); - $res=$db->single("SELECT balance FROM accounts WHERE id=:id OR public_key=:id2 LIMIT 1",array(":id"=>$id, ":id2"=>$id)); - - echo "Balance: ".number_format($res)."\n"; -} -/** + echo "Balance: ".number_format($res)."\n"; +} /** * @api {php util.php} block Block * @apiName block * @apiGroup UTIL @@ -373,14 +377,12 @@ elseif($cmd=="balance"){ * int(0) * } */ -elseif($cmd=="block"){ - $id=san($argv[2]); - $res=$db->row("SELECT * FROM blocks WHERE id=:id OR height=:id2 LIMIT 1",array(":id"=>$id, ":id2"=>$id)); - - var_dump($res); +elseif ($cmd == "block") { + $id = san($argv[2]); + $res = $db->row("SELECT * FROM blocks WHERE id=:id OR height=:id2 LIMIT 1", [":id" => $id, ":id2" => $id]); -} -/** + var_dump($res); +} /** * @api {php util.php} check-address Check-Address * @apiName check-address * @apiGroup UTIL @@ -394,16 +396,19 @@ elseif($cmd=="block"){ * @apiSuccessExample {text} Success-Response: * The address is valid */ -elseif($cmd=="check-address"){ - $dst=trim($argv[2]); - $acc=new Account; - if(!$acc->valid($dst)) die("Invalid address"); - $dst_b=base58_decode($dst); - if(strlen($dst_b)!=64) die("Invalid address - ".strlen($dst_b)." bytes"); - - echo "The address is valid\n"; -} -/** +elseif ($cmd == "check-address") { + $dst = trim($argv[2]); + $acc = new Account(); + if (!$acc->valid($dst)) { + die("Invalid address"); + } + $dst_b = base58_decode($dst); + if (strlen($dst_b) != 64) { + die("Invalid address - ".strlen($dst_b)." bytes"); + } + + echo "The address is valid\n"; +} /** * @api {php util.php} get-address Get-Address * @apiName get-address * @apiGroup UTIL @@ -418,15 +423,12 @@ elseif($cmd=="check-address"){ * 5WuRMXGM7Pf8NqEArVz1NxgSBptkimSpvuSaYC79g1yo3RDQc8TjVtGH5chQWQV7CHbJEuq9DmW5fbmCEW4AghQr */ -elseif($cmd=='get-address'){ - - $public_key=trim($argv2); - if(strlen($public_key)<32) die("Invalid public key"); +elseif ($cmd == 'get-address') { + $public_key = trim($argv2); + if (strlen($public_key) < 32) { + die("Invalid public key"); + } print($acc->get_address($public_key)); - } else { - echo "Invalid command\n"; + echo "Invalid command\n"; } - - -?>