91 Commits

Author SHA1 Message Date
admin@arionum.com
88f50fcae1 passive peering 2018-10-25 21:11:24 +03:00
arionum
3251f98ebf Merge pull request #34 from pxgamer/feature/config-peers
Add initial peers list to configuration
2018-10-25 20:33:27 +03:00
pxgamer
98dba661b1 Change namespace to Arionum\Node 2018-10-20 18:15:14 +01:00
pxgamer
ca9e95e7af Move loading of new files to the init file 2018-10-20 18:04:33 +01:00
pxgamer
5bbbca15a9 Update the sanity process to use InitialPeers 2018-10-19 10:01:07 +01:00
pxgamer
4c79b7f460 Add InitialPeers class for manging initial peers 2018-10-19 09:58:32 +01:00
pxgamer
5906372fc0 Add initial peers from the peer file 2018-10-19 09:49:53 +01:00
pxgamer
30c76cab5c Add Arionum exception class 2018-10-19 09:30:11 +01:00
pxgamer
e70d422da5 Add peers array to the configuration sample 2018-10-18 23:05:08 +01:00
arionum
754017f634 Merge pull request #33 from pxgamer/feature/api-docs
Add missing API documentation
2018-10-13 21:06:36 +03:00
admin@arionum.com
4aeff314bb ignored file 2018-10-13 21:05:41 +03:00
admin@arionum.com
c25ccfe064 json content 2018-10-13 21:03:24 +03:00
pxgamer
5fbb3c3ac2 Update the API documentation output 2018-10-12 12:11:42 +01:00
pxgamer
d6c6d3420e Add apiDoc comments for the node-info endpoint 2018-10-12 12:01:56 +01:00
pxgamer
e389988bda Update the formatting of the node-info API code 2018-10-12 11:58:08 +01:00
pxgamer
c8a8d3b9d0 Add apiDoc comments for the sanity endpoint 2018-10-12 11:55:24 +01:00
pxgamer
3f42a53991 Update the formatting of the sanity API code 2018-10-12 11:38:20 +01:00
pxgamer
6383aade6e Fix doc numbering typo for getAlias 2018-10-12 11:35:57 +01:00
arionum
17acd5830e Merge pull request #27 from pxgamer/feature/travis
Add a configuration file for Travis CI
2018-10-10 17:40:49 +03:00
arionum
f33402382b Merge pull request #32 from pxgamer/feature/json-headers
Add JSON header setting to the api functions
2018-10-10 17:40:30 +03:00
pxgamer
3ddc0dd90e Add JSON header setting to the api functions 2018-10-08 12:39:16 +01:00
arionum
33587258a8 Merge pull request #31 from pxgamer/feature/blacklist-addresses
Add support for blacklisting addresses
2018-09-14 22:16:33 +03:00
pxgamer
49df2cf71f Add Blacklist check for addresses 2018-09-14 15:49:10 +01:00
pxgamer
975e602088 Blacklist the new Octaex wallet address 2018-09-14 15:46:52 +01:00
pxgamer
6483f377b5 Add Blacklist::checkAddress() method 2018-09-14 15:45:51 +01:00
arionum
843d1d0fef Merge pull request #29 from pxgamer/feature/blacklist-refactor
Refactor the blacklist to a class
2018-09-09 15:52:39 +03:00
admin@arionum.com
91f6e1f5d4 tag 2018-09-09 15:50:17 +03:00
pxgamer
33aa9cb6c5 Merge 'master' into feature/blacklist-refactor 2018-09-06 13:54:45 +01:00
admin@arionum.com
29c27c426e typo 2018-09-06 14:57:52 +03:00
pxgamer
2202811745 Update blacklist checks to use the class 2018-09-06 12:07:17 +01:00
pxgamer
30f35944ea Require the Blaclist class in init.inc 2018-09-06 12:02:10 +01:00
pxgamer
7146cfd3c0 Add current blacklisted public keys 2018-09-06 12:00:42 +01:00
pxgamer
024042abab Add Blacklist class 2018-09-06 12:00:08 +01:00
admin@arionum.com
0b73adb3d8 paranoid and blacklist 2018-09-05 16:06:23 +03:00
Arionum
a03484adee accounts-hash 2018-08-30 01:33:09 +03:00
Arionum
99f17a3e7d util tools 2018-08-30 01:28:57 +03:00
Arionum
3549da461d Merge branch 'master' of https://github.com/arionum/node
Conflicts:
	util.php
2018-08-30 01:28:07 +03:00
Arionum
616dde11ea sync fix 2018-08-30 01:24:39 +03:00
pxgamer
31f5ff0bd1 Remove progress from Travis testing 2018-08-29 00:01:29 +01:00
pxgamer
3cad1162ef Fix local configuration to prevent recursing 2018-08-28 23:57:37 +01:00
pxgamer
7a83f1ab10 Add test Travis configuration 2018-08-28 23:55:27 +01:00
arionum
b974dc4c9a Update util.php 2018-08-27 15:18:40 +03:00
arionum
55a1dc750c Merge pull request #25 from KyleFromOhio/master
FIXED: formatting
2018-08-24 22:56:31 +03:00
Kyle Anderson
4b47d0d88e FIXED: formatting 2018-08-24 12:45:34 -07:00
Arionum
6a6005ca4c tuning 2018-08-19 23:02:06 +03:00
Arionum
9dd74285f7 limit peers per sanity 2018-08-18 01:55:18 +03:00
Arionum
788f438114 alias transactions 2018-08-18 00:42:36 +03:00
Arionum
f3e430fee5 sanity rollback 2018-08-15 14:16:59 +03:00
Arionum
faf8cc05f7 test 2018-08-14 05:35:43 +03:00
Arionum
de732ee7f9 db link 2018-08-14 05:21:00 +03:00
Arionum
74190d40ba filter 2018-08-14 05:16:31 +03:00
Arionum
65e801989d resync check 2018-08-14 02:52:01 +03:00
Arionum
d688423d3e block rechecl restore 2018-08-14 02:39:18 +03:00
Arionum
ffc87d69bf mine logic error 2018-08-14 02:33:07 +03:00
Arionum
2a49e6c70a dif fix 2018-08-13 06:45:59 +03:00
Arionum
6c8bc7b97c argon typo 2018-08-13 05:57:59 +03:00
Arionum
7efb4df493 string 2018-08-13 05:33:29 +03:00
Arionum
beff53e938 80458 2018-08-13 05:15:02 +03:00
Arionum
5ca54479a7 hf extend 2018-08-13 04:56:01 +03:00
Arionum
547cac4afb diff split 2018-08-13 03:31:43 +03:00
Arionum
bc9c945646 faster sanity 2018-08-13 03:09:21 +03:00
Arionum
8be4270e16 20block diff 2018-08-13 03:03:07 +03:00
Arionum
971c385042 difficulty 2018-08-13 03:01:20 +03:00
Arionum
984aedc940 version 2018-08-13 02:51:49 +03:00
Arionum
e59bb416b4 80460 hard fork 2018-08-13 02:36:59 +03:00
Arionum
57d2257c43 80460 hf 2018-08-13 02:36:20 +03:00
Arionum
8c32d1c71b 80500 hf mn 2018-08-12 21:39:38 +03:00
Arionum
b5110bf01f protection 2018-08-12 16:47:52 +03:00
Arionum
0567899edd temp fix 2018-08-12 05:44:03 +03:00
Arionum
66b1221e22 blacklist time 83000 2018-08-12 01:47:56 +03:00
Arionum
9e9c12fbbb accounts resync option 2018-08-11 23:00:33 +03:00
Arionum
c5002508d6 internal transaction 2018-08-10 22:19:22 +03:00
Arionum
fbc48921f3 mn fix 2018-08-10 21:49:34 +03:00
Arionum
07dcebd895 limit peers 2018-08-10 18:19:19 +03:00
arionum
6651ff54fc Update db-update 2018-08-09 15:32:09 +03:00
Arionum
38745111ef Merge branch 'master' of https://github.com/arionum/node 2018-08-07 22:36:19 +03:00
Arionum
c83895d384 homepage version 2018-08-07 22:36:04 +03:00
arionum
62cc290b50 Update README.md 2018-08-07 18:19:17 +03:00
Arionum
dab6648ae8 log fix 2018-08-07 18:09:26 +03:00
arionum
5c476be954 Merge pull request #23 from pxgamer/feature/config-formatting
Beautify the sample configuration with sections
2018-08-07 15:57:17 +03:00
pxgamer
0d5f79fc20 Beautify the sample configuration with sections 2018-08-07 11:38:47 +01:00
arionum
5fc167cfbf Update README.md 2018-08-07 13:12:32 +03:00
arionum
5f4b6d9d51 Rename config.inc.php to config-sample.inc.php 2018-08-07 13:11:09 +03:00
Arionum
e707f8daa4 external mansternode miner support 2018-08-07 09:32:57 +03:00
Arionum
a09279d091 upgrade info 2018-08-06 00:23:00 +03:00
Arionum
d9e5ad94de docs 2018-08-06 00:17:28 +03:00
Arionum
8b006fc26a hf alias and mining 2018-08-06 00:13:36 +03:00
Arionum
066211b2c0 transaction indexes 2018-06-28 17:52:19 +03:00
arionum
06960e64e7 Merge pull request #21 from pxgamer/feature/custom-index-page
Create a custom index page
2018-06-28 12:47:36 +03:00
pxgamer
b276fde6da Update the order of CSS properties 2018-06-25 09:00:51 +01:00
pxgamer
7ebf3e0e8a Update the styling of the index 2018-06-22 12:23:21 +01:00
27 changed files with 2876 additions and 346 deletions

25
.travis.yml Normal file
View File

@@ -0,0 +1,25 @@
dist: trusty
language: php
php:
- 7.2
- nightly
matrix:
allow_failures:
- php: nightly
# This triggers builds to run on the new TravisCI infrastructure.
# See: https://docs.travis-ci.com/user/reference/trusty#Container-based-with-sudo%3A-false
sudo: false
## Cache composer
cache:
directories:
- $HOME/.composer/cache
before_script:
- travis_retry composer update --no-interaction --prefer-dist
script:
- vendor/bin/phpcs -l --standard=psr2 . include

View File

@@ -4,6 +4,12 @@ The Arionum (ARO) cryptocurrency node.
## Install
**Hardware Requirements:**
```
2GB RAM
1 CPU Core
50GB DISK
```
**Requirements:**
- PHP 7.2
@@ -13,7 +19,7 @@ The Arionum (ARO) cryptocurrency node.
- MySQL/MariaDB
1. Install MySQL or MariaDB and create a database and a user.
2. Edit `include/config.inc.php` and set the DB login data
2. Rename `include/config-sample.inc.php` to `include/config.inc.php` and set the DB login data
3. Change permissions to tmp and `tmp/db-update` to 777 (`chmod 777 tmp -R`)
4. Access the http://ip-or-domain and refresh once

6
UPGRADE Normal file
View File

@@ -0,0 +1,6 @@
In order to upgrade your Arionum Node, follow the next steps:
1. Download the source code from https://github.com/arionum/node
2. Edit the include/config.inc.php with the DB details
3. Replace your old node folder with the new one.
4. chmod 777 tmp -R
5. Load the node's address in your browser.

183
api.php
View File

@@ -23,6 +23,8 @@ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
*/
header('Content-Type: application/json');
/**
* @api {get} /api.php 01. Basic Information
@@ -65,7 +67,9 @@ OR OTHER DEALINGS IN THE SOFTWARE.
* }
*/
require_once("include/init.inc.php");
use Arionum\Blacklist;
require_once __DIR__.'/include/init.inc.php';
error_reporting(0);
$ip = san_ip($_SERVER['REMOTE_ADDR']);
$ip = filter_var($ip, FILTER_VALIDATE_IP);
@@ -124,18 +128,23 @@ if ($q == "getAddress") {
*
* @apiParam {string} [public_key] Public key
* @apiParam {string} [account] Account id / address
* @apiParam {string} [alias] alias
*
* @apiSuccess {string} data The ARO balance
*/
$public_key = $data['public_key'];
$account = $data['account'];
$alias = $data['alias'];
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($alias)) {
$account = $acc->alias2account($alias);
}
if (empty($account)) {
api_err("Invalid account id");
}
@@ -395,22 +404,39 @@ if ($q == "getAddress") {
$block = new Block();
$trx = new Transaction();
$version = intval($data['version']);
$dst = san($data['dst']);
if ($version < 1) {
$version = 1;
}
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 ($version==1) {
if (!$acc->valid($dst)) {
api_err("Invalid destination address");
}
$dst_b = base58_decode($dst);
if (strlen($dst_b) != 64) {
api_err("Invalid destination address");
}
} elseif ($version==2) {
$dst=strtoupper($dst);
$dst = san($dst);
if (!$acc->valid_alias($dst)) {
api_err("Invalid destination alias");
}
}
$public_key = san($data['public_key']);
if (!$acc->valid_key($public_key)) {
api_err("Invalid public key");
}
if ($_config['use_official_blacklist']!==false) {
if (Blacklist::checkPublicKey($public_key)) {
api_err("Blacklisted account");
}
}
$private_key = san($data['private_key']);
if (!$acc->valid_key($private_key)) {
api_err("Invalid private key");
@@ -430,8 +456,8 @@ if ($q == "getAddress") {
if ($date > time() + 86400) {
api_err("Invalid Date");
}
$version = intval($data['version']);
$message = $data['message'];
$message=$data['message'];
if (strlen($message) > 128) {
api_err("The message must be less than 128 chars");
}
@@ -449,8 +475,27 @@ if ($q == "getAddress") {
api_err("Invalid value");
}
if ($version < 1) {
$version = 1;
// set alias
if ($version==3) {
$fee=10;
$message = san($message);
$message=strtoupper($message);
if (!$acc->free_alias($message)) {
api_err("Invalid alias");
}
if ($acc->has_alias($public_key)) {
api_err("This account already has an alias");
}
}
if ($version>=100&&$version<110) {
if ($version==100) {
$message=preg_replace("/[^0-9\.]/", "", $message);
if (!filter_var($message, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
api_err("Invalid Node IP - $message !");
}
$val=100000;
}
}
$val = number_format($val, 8, '.', '');
@@ -523,6 +568,7 @@ if ($q == "getAddress") {
$trx->add_mempool($transaction, "local");
$hash=escapeshellarg(san($hash));
system("php propagate.php transaction $hash > /dev/null 2>&1 &");
api_echo($hash);
} elseif ($q == "mempoolSize") {
@@ -570,6 +616,119 @@ if ($q == "getAddress") {
mt_srand($seed1, MT_RAND_MT19937);
$res = mt_rand($min, $max);
api_echo($res);
} elseif ($q == "checkSignature") {
/**
* @api {get} /api.php?q=checkSignature 17. checkSignature
* @apiName checkSignature
* @apiGroup API
* @apiDescription Checks a signature against a public key
*
* @apiParam {string} [public_key] Public key
* @apiParam {string} [signature] signature
* @apiParam {string} [data] signed data
*
*
* @apiSuccess {boolean} data true or false
*/
$public_key=san($data['public_key']);
$signature=san($data['signature']);
$data=$data['data'];
api_echo(ec_verify($data, $signature, $public_key));
} elseif ($q == "masternodes") {
/**
* @api {get} /api.php?q=masternodes 18. masternodes
* @apiName masternodes
* @apiGroup API
* @apiDescription Returns all the masternode data
*
*
*
* @apiSuccess {boolean} data masternode date
*/
$res=$db->run("SELECT * FROM masternode ORDER by public_key ASC");
api_echo(["masternodes"=>$res, "hash"=>md5(json_encode($res))]);
} elseif ($q == "getAlias") {
/**
* @api {get} /api.php?q=getAlias 19. getAlias
* @apiName getAlias
* @apiGroup API
* @apiDescription Returns the alias of an account
*
* @apiParam {string} [public_key] Public key
* @apiParam {string} [account] Account id / address
*
*
* @apiSuccess {string} data alias
*/
$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->account2alias($account));
} elseif ($q === 'sanity') {
/**
* @api {get} /api.php?q=sanity 20. sanity
* @apiName sanity
* @apiGroup API
* @apiDescription Returns details about the node's sanity process.
*
* @apiSuccess {object} data A collection of data about the sanity process.
* @apiSuccess {boolean} data.sanity_running Whether the sanity process is currently running.
* @apiSuccess {number} data.last_sanity The timestamp for the last time the sanity process was run.
* @apiSuccess {boolean} data.sanity_sync Whether the sanity process is currently synchronising.
*/
$sanity = file_exists(__DIR__.'/tmp/sanity-lock');
$lastSanity = (int)$db->single("SELECT val FROM config WHERE cfg='sanity_last'");
$sanitySync = (bool)$db->single("SELECT val FROM config WHERE cfg='sanity_sync'");
api_echo(['sanity_running' => $sanity, 'last_sanity' => $lastSanity, 'sanity_sync' => $sanitySync]);
} elseif ($q === 'node-info') {
/**
* @api {get} /api.php?q=node-info 21. node-info
* @apiName node-info
* @apiGroup API
* @apiDescription Returns details about the node.
*
* @apiSuccess {object} data A collection of data about the node.
* @apiSuccess {string} data.hostname The hostname of the node.
* @apiSuccess {string} data.version The current version of the node.
* @apiSuccess {string} data.dbversion The database schema version for the node.
* @apiSuccess {number} data.accounts The number of accounts known by the node.
* @apiSuccess {number} data.transactions The number of transactions known by the node.
* @apiSuccess {number} data.mempool The number of transactions in the mempool.
* @apiSuccess {number} data.masternodes The number of masternodes known by the node.
*/
$dbVersion = $db->single("SELECT val FROM config WHERE cfg='dbversion'");
$hostname = $db->single("SELECT val FROM config WHERE cfg='hostname'");
$acc = $db->single("SELECT COUNT(1) FROM accounts");
$tr = $db->single("SELECT COUNT(1) FROM transactions");
$masternodes = $db->single("SELECT COUNT(1) FROM masternode");
$mempool = $db->single("SELECT COUNT(1) FROM mempool");
api_echo([
'hostname' => $hostname,
'version' => VERSION,
'dbversion' => $dbVersion,
'accounts' => $acc,
'transactions' => $tr,
'mempool' => $mempool,
'masternodes' => $masternodes,
]);
} else {
api_err("Invalid request");
}

View File

@@ -101,6 +101,57 @@ define({ "api": [
"filename": "./api.php",
"groupTitle": "API"
},
{
"type": "get",
"url": "/api.php?q=checkSignature",
"title": "17. checkSignature",
"name": "checkSignature",
"group": "API",
"description": "<p>Checks a signature against a public key</p>",
"parameter": {
"fields": {
"Parameter": [
{
"group": "Parameter",
"type": "string",
"optional": true,
"field": "public_key",
"description": "<p>Public key</p>"
},
{
"group": "Parameter",
"type": "string",
"optional": true,
"field": "signature",
"description": "<p>signature</p>"
},
{
"group": "Parameter",
"type": "string",
"optional": true,
"field": "data",
"description": "<p>signed data</p>"
}
]
}
},
"success": {
"fields": {
"Success 200": [
{
"group": "Success 200",
"type": "boolean",
"optional": false,
"field": "data",
"description": "<p>true or false</p>"
}
]
}
},
"version": "0.0.0",
"filename": "./api.php",
"groupTitle": "API"
},
{
"type": "get",
"url": "/api.php?q=currentBlock",
@@ -249,6 +300,50 @@ define({ "api": [
"filename": "./api.php",
"groupTitle": "API"
},
{
"type": "get",
"url": "/api.php?q=getAlias",
"title": "19. getAlias",
"name": "getAlias",
"group": "API",
"description": "<p>Returns the alias of an account</p>",
"parameter": {
"fields": {
"Parameter": [
{
"group": "Parameter",
"type": "string",
"optional": true,
"field": "public_key",
"description": "<p>Public key</p>"
},
{
"group": "Parameter",
"type": "string",
"optional": true,
"field": "account",
"description": "<p>Account id / address</p>"
}
]
}
},
"success": {
"fields": {
"Success 200": [
{
"group": "Success 200",
"type": "string",
"optional": false,
"field": "data",
"description": "<p>alias</p>"
}
]
}
},
"version": "0.0.0",
"filename": "./api.php",
"groupTitle": "API"
},
{
"type": "get",
"url": "/api.php?q=getBalance",
@@ -272,6 +367,13 @@ define({ "api": [
"optional": true,
"field": "account",
"description": "<p>Account id / address</p>"
},
{
"group": "Parameter",
"type": "string",
"optional": true,
"field": "alias",
"description": "<p>alias</p>"
}
]
}
@@ -865,6 +967,30 @@ define({ "api": [
"filename": "./api.php",
"groupTitle": "API"
},
{
"type": "get",
"url": "/api.php?q=masternodes",
"title": "18. masternodes",
"name": "masternodes",
"group": "API",
"description": "<p>Returns all the masternode data</p>",
"success": {
"fields": {
"Success 200": [
{
"group": "Success 200",
"type": "boolean",
"optional": false,
"field": "data",
"description": "<p>masternode date</p>"
}
]
}
},
"version": "0.0.0",
"filename": "./api.php",
"groupTitle": "API"
},
{
"type": "get",
"url": "/api.php?q=mempoolSize",
@@ -889,6 +1015,79 @@ define({ "api": [
"filename": "./api.php",
"groupTitle": "API"
},
{
"type": "get",
"url": "/api.php?q=node-info",
"title": "21. node-info",
"name": "node_info",
"group": "API",
"description": "<p>Returns details about the node.</p>",
"success": {
"fields": {
"Success 200": [
{
"group": "Success 200",
"type": "object",
"optional": false,
"field": "data",
"description": "<p>A collection of data about the node.</p>"
},
{
"group": "Success 200",
"type": "string",
"optional": false,
"field": "data.hostname",
"description": "<p>The hostname of the node.</p>"
},
{
"group": "Success 200",
"type": "string",
"optional": false,
"field": "data.version",
"description": "<p>The current version of the node.</p>"
},
{
"group": "Success 200",
"type": "string",
"optional": false,
"field": "data.dbversion",
"description": "<p>The database schema version for the node.</p>"
},
{
"group": "Success 200",
"type": "number",
"optional": false,
"field": "data.accounts",
"description": "<p>The number of accounts known by the node.</p>"
},
{
"group": "Success 200",
"type": "number",
"optional": false,
"field": "data.transactions",
"description": "<p>The number of transactions known by the node.</p>"
},
{
"group": "Success 200",
"type": "number",
"optional": false,
"field": "data.mempool",
"description": "<p>The number of transactions in the mempool.</p>"
},
{
"group": "Success 200",
"type": "number",
"optional": false,
"field": "data.masternodes",
"description": "<p>The number of masternodes known by the node.</p>"
}
]
}
},
"version": "0.0.0",
"filename": "./api.php",
"groupTitle": "API"
},
{
"type": "get",
"url": "/api.php?q=randomNumber",
@@ -947,6 +1146,51 @@ define({ "api": [
"filename": "./api.php",
"groupTitle": "API"
},
{
"type": "get",
"url": "/api.php?q=sanity",
"title": "20. sanity",
"name": "sanity",
"group": "API",
"description": "<p>Returns details about the node's sanity process.</p>",
"success": {
"fields": {
"Success 200": [
{
"group": "Success 200",
"type": "object",
"optional": false,
"field": "data",
"description": "<p>A collection of data about the sanity process.</p>"
},
{
"group": "Success 200",
"type": "boolean",
"optional": false,
"field": "data.sanity_running",
"description": "<p>Whether the sanity process is currently running.</p>"
},
{
"group": "Success 200",
"type": "number",
"optional": false,
"field": "data.last_sanity",
"description": "<p>The timestamp for the last time the sanity process was run.</p>"
},
{
"group": "Success 200",
"type": "boolean",
"optional": false,
"field": "data.sanity_sync",
"description": "<p>Whether the sanity process is currently synchronising.</p>"
}
]
}
},
"version": "0.0.0",
"filename": "./api.php",
"groupTitle": "API"
},
{
"type": "get",
"url": "/api.php?q=send",
@@ -1057,6 +1301,34 @@ define({ "api": [
"filename": "./api.php",
"groupTitle": "API"
},
{
"success": {
"fields": {
"Success 200": [
{
"group": "Success 200",
"optional": false,
"field": "varname1",
"description": "<p>No type.</p>"
},
{
"group": "Success 200",
"type": "String",
"optional": false,
"field": "varname2",
"description": "<p>With type.</p>"
}
]
}
},
"type": "",
"url": "",
"version": "0.0.0",
"filename": "./doc/main.js",
"group": "C__Users_owen_voke_Documents_GitHub_php_arionum_node_doc_main_js",
"groupTitle": "C__Users_owen_voke_Documents_GitHub_php_arionum_node_doc_main_js",
"name": ""
},
{
"type": "php util.php",
"url": "balance",
@@ -1269,6 +1541,24 @@ define({ "api": [
"filename": "./util.php",
"groupTitle": "UTIL"
},
{
"type": "php util.php",
"url": "clean-blacklist",
"title": "Clean-Blacklist",
"name": "clean_blacklist",
"group": "UTIL",
"description": "<p>Removes all the peers from blacklist</p>",
"examples": [
{
"title": "Example usage:",
"content": "php util.php clean-blacklist",
"type": "cli"
}
],
"version": "0.0.0",
"filename": "./util.php",
"groupTitle": "UTIL"
},
{
"type": "php util.php",
"url": "current",
@@ -1545,33 +1835,5 @@ define({ "api": [
"version": "0.0.0",
"filename": "./util.php",
"groupTitle": "UTIL"
},
{
"success": {
"fields": {
"Success 200": [
{
"group": "Success 200",
"optional": false,
"field": "varname1",
"description": "<p>No type.</p>"
},
{
"group": "Success 200",
"type": "String",
"optional": false,
"field": "varname2",
"description": "<p>With type.</p>"
}
]
}
},
"type": "",
"url": "",
"version": "0.0.0",
"filename": "./doc/main.js",
"group": "_github_node_doc_main_js",
"groupTitle": "_github_node_doc_main_js",
"name": ""
}
] });

View File

@@ -101,6 +101,57 @@
"filename": "./api.php",
"groupTitle": "API"
},
{
"type": "get",
"url": "/api.php?q=checkSignature",
"title": "17. checkSignature",
"name": "checkSignature",
"group": "API",
"description": "<p>Checks a signature against a public key</p>",
"parameter": {
"fields": {
"Parameter": [
{
"group": "Parameter",
"type": "string",
"optional": true,
"field": "public_key",
"description": "<p>Public key</p>"
},
{
"group": "Parameter",
"type": "string",
"optional": true,
"field": "signature",
"description": "<p>signature</p>"
},
{
"group": "Parameter",
"type": "string",
"optional": true,
"field": "data",
"description": "<p>signed data</p>"
}
]
}
},
"success": {
"fields": {
"Success 200": [
{
"group": "Success 200",
"type": "boolean",
"optional": false,
"field": "data",
"description": "<p>true or false</p>"
}
]
}
},
"version": "0.0.0",
"filename": "./api.php",
"groupTitle": "API"
},
{
"type": "get",
"url": "/api.php?q=currentBlock",
@@ -249,6 +300,50 @@
"filename": "./api.php",
"groupTitle": "API"
},
{
"type": "get",
"url": "/api.php?q=getAlias",
"title": "19. getAlias",
"name": "getAlias",
"group": "API",
"description": "<p>Returns the alias of an account</p>",
"parameter": {
"fields": {
"Parameter": [
{
"group": "Parameter",
"type": "string",
"optional": true,
"field": "public_key",
"description": "<p>Public key</p>"
},
{
"group": "Parameter",
"type": "string",
"optional": true,
"field": "account",
"description": "<p>Account id / address</p>"
}
]
}
},
"success": {
"fields": {
"Success 200": [
{
"group": "Success 200",
"type": "string",
"optional": false,
"field": "data",
"description": "<p>alias</p>"
}
]
}
},
"version": "0.0.0",
"filename": "./api.php",
"groupTitle": "API"
},
{
"type": "get",
"url": "/api.php?q=getBalance",
@@ -272,6 +367,13 @@
"optional": true,
"field": "account",
"description": "<p>Account id / address</p>"
},
{
"group": "Parameter",
"type": "string",
"optional": true,
"field": "alias",
"description": "<p>alias</p>"
}
]
}
@@ -865,6 +967,30 @@
"filename": "./api.php",
"groupTitle": "API"
},
{
"type": "get",
"url": "/api.php?q=masternodes",
"title": "18. masternodes",
"name": "masternodes",
"group": "API",
"description": "<p>Returns all the masternode data</p>",
"success": {
"fields": {
"Success 200": [
{
"group": "Success 200",
"type": "boolean",
"optional": false,
"field": "data",
"description": "<p>masternode date</p>"
}
]
}
},
"version": "0.0.0",
"filename": "./api.php",
"groupTitle": "API"
},
{
"type": "get",
"url": "/api.php?q=mempoolSize",
@@ -889,6 +1015,79 @@
"filename": "./api.php",
"groupTitle": "API"
},
{
"type": "get",
"url": "/api.php?q=node-info",
"title": "21. node-info",
"name": "node_info",
"group": "API",
"description": "<p>Returns details about the node.</p>",
"success": {
"fields": {
"Success 200": [
{
"group": "Success 200",
"type": "object",
"optional": false,
"field": "data",
"description": "<p>A collection of data about the node.</p>"
},
{
"group": "Success 200",
"type": "string",
"optional": false,
"field": "data.hostname",
"description": "<p>The hostname of the node.</p>"
},
{
"group": "Success 200",
"type": "string",
"optional": false,
"field": "data.version",
"description": "<p>The current version of the node.</p>"
},
{
"group": "Success 200",
"type": "string",
"optional": false,
"field": "data.dbversion",
"description": "<p>The database schema version for the node.</p>"
},
{
"group": "Success 200",
"type": "number",
"optional": false,
"field": "data.accounts",
"description": "<p>The number of accounts known by the node.</p>"
},
{
"group": "Success 200",
"type": "number",
"optional": false,
"field": "data.transactions",
"description": "<p>The number of transactions known by the node.</p>"
},
{
"group": "Success 200",
"type": "number",
"optional": false,
"field": "data.mempool",
"description": "<p>The number of transactions in the mempool.</p>"
},
{
"group": "Success 200",
"type": "number",
"optional": false,
"field": "data.masternodes",
"description": "<p>The number of masternodes known by the node.</p>"
}
]
}
},
"version": "0.0.0",
"filename": "./api.php",
"groupTitle": "API"
},
{
"type": "get",
"url": "/api.php?q=randomNumber",
@@ -947,6 +1146,51 @@
"filename": "./api.php",
"groupTitle": "API"
},
{
"type": "get",
"url": "/api.php?q=sanity",
"title": "20. sanity",
"name": "sanity",
"group": "API",
"description": "<p>Returns details about the node's sanity process.</p>",
"success": {
"fields": {
"Success 200": [
{
"group": "Success 200",
"type": "object",
"optional": false,
"field": "data",
"description": "<p>A collection of data about the sanity process.</p>"
},
{
"group": "Success 200",
"type": "boolean",
"optional": false,
"field": "data.sanity_running",
"description": "<p>Whether the sanity process is currently running.</p>"
},
{
"group": "Success 200",
"type": "number",
"optional": false,
"field": "data.last_sanity",
"description": "<p>The timestamp for the last time the sanity process was run.</p>"
},
{
"group": "Success 200",
"type": "boolean",
"optional": false,
"field": "data.sanity_sync",
"description": "<p>Whether the sanity process is currently synchronising.</p>"
}
]
}
},
"version": "0.0.0",
"filename": "./api.php",
"groupTitle": "API"
},
{
"type": "get",
"url": "/api.php?q=send",
@@ -1057,6 +1301,34 @@
"filename": "./api.php",
"groupTitle": "API"
},
{
"success": {
"fields": {
"Success 200": [
{
"group": "Success 200",
"optional": false,
"field": "varname1",
"description": "<p>No type.</p>"
},
{
"group": "Success 200",
"type": "String",
"optional": false,
"field": "varname2",
"description": "<p>With type.</p>"
}
]
}
},
"type": "",
"url": "",
"version": "0.0.0",
"filename": "./doc/main.js",
"group": "C__Users_owen_voke_Documents_GitHub_php_arionum_node_doc_main_js",
"groupTitle": "C__Users_owen_voke_Documents_GitHub_php_arionum_node_doc_main_js",
"name": ""
},
{
"type": "php util.php",
"url": "balance",
@@ -1269,6 +1541,24 @@
"filename": "./util.php",
"groupTitle": "UTIL"
},
{
"type": "php util.php",
"url": "clean-blacklist",
"title": "Clean-Blacklist",
"name": "clean_blacklist",
"group": "UTIL",
"description": "<p>Removes all the peers from blacklist</p>",
"examples": [
{
"title": "Example usage:",
"content": "php util.php clean-blacklist",
"type": "cli"
}
],
"version": "0.0.0",
"filename": "./util.php",
"groupTitle": "UTIL"
},
{
"type": "php util.php",
"url": "current",
@@ -1545,33 +1835,5 @@
"version": "0.0.0",
"filename": "./util.php",
"groupTitle": "UTIL"
},
{
"success": {
"fields": {
"Success 200": [
{
"group": "Success 200",
"optional": false,
"field": "varname1",
"description": "<p>No type.</p>"
},
{
"group": "Success 200",
"type": "String",
"optional": false,
"field": "varname2",
"description": "<p>With type.</p>"
}
]
}
},
"type": "",
"url": "",
"version": "0.0.0",
"filename": "./doc/main.js",
"group": "_github_node_doc_main_js",
"groupTitle": "_github_node_doc_main_js",
"name": ""
}
]

View File

@@ -1,13 +1,14 @@
define({
"name": "",
"name": "Arionum Node",
"version": "0.0.0",
"description": "",
"description": "The Arionum Node API and utility documentation.",
"title": "Arionum Node",
"sampleUrl": false,
"defaultVersion": "0.0.0",
"apidoc": "0.3.0",
"generator": {
"name": "apidoc",
"time": "2018-05-09T23:01:25.347Z",
"time": "2018-10-12T11:08:47.065Z",
"url": "http://apidocjs.com",
"version": "0.17.6"
}

View File

@@ -1,13 +1,14 @@
{
"name": "",
"name": "Arionum Node",
"version": "0.0.0",
"description": "",
"description": "The Arionum Node API and utility documentation.",
"title": "Arionum Node",
"sampleUrl": false,
"defaultVersion": "0.0.0",
"apidoc": "0.3.0",
"generator": {
"name": "apidoc",
"time": "2018-05-09T23:01:25.347Z",
"time": "2018-10-12T11:08:47.065Z",
"url": "http://apidocjs.com",
"version": "0.17.6"
}

50
include/Blacklist.php Normal file
View File

@@ -0,0 +1,50 @@
<?php
namespace Arionum;
/**
* Class Blacklist
*/
final class Blacklist
{
/**
* The official list of blacklisted public keys
*/
public const PUBLIC_KEYS = [
// phpcs:disable Generic.Files.LineLength
'PZ8Tyr4Nx8MHsRAGMpZmZ6TWY63dXWSCvVQcHHCNLfiP9LmzWhhpCHx39Bhc67P5HMQM9cctEFvcsUdgrkGqy18taz9ZMrAGtq7NhBYpQ4ZTHkKYiZDaSUqQ' => 'Faucet Abuser',
'PZ8Tyr4Nx8MHsRAGMpZmZ6TWY63dXWSCxYDeQHk7Ke66UB2Un3UMmMoJ7RF5vDZXihdEXi8gk8ZBRAi35aFrER2ZLX1mgND7sLFXKETGTjRYjoHcuRNiJN1g' => 'Octaex Exchange',
// phpcs:enable
];
/**
* The official list of blacklisted addresses
*/
public const ADDRESSES = [
// phpcs:disable Generic.Files.LineLength
'xuzyMbEGA1tmx1o7mcxSXf2nXuuV1GtKbA4sAqjcNq2gh3shuhwBT5nJHez9AynCaxpJwL6dpkavmZBA3JkrMkg' => 'Octaex Exchange',
// phpcs:enable
];
/**
* Check if a public key is blacklisted
*
* @param string $publicKey
* @return bool
*/
public static function checkPublicKey(string $publicKey): bool
{
return key_exists($publicKey, static::PUBLIC_KEYS);
}
/**
* Check if an address is blacklisted
*
* @param string $address
* @return bool
*/
public static function checkAddress(string $address): bool
{
return key_exists($address, static::ADDRESSES);
}
}

11
include/Exception.php Normal file
View File

@@ -0,0 +1,11 @@
<?php
namespace Arionum\Node;
/**
* Class Exception
* A custom exception for Arionum error handling.
*/
class Exception extends \Exception
{
}

81
include/InitialPeers.php Normal file
View File

@@ -0,0 +1,81 @@
<?php
namespace Arionum\Node;
/**
* Class InitialPeers
*/
final class InitialPeers
{
public const MINIMUM_PEERS_REQUIRED = 2;
public const PRELOAD_ERROR = 'Unable to retrieve peers from the preload list.';
public const PRELOAD_LIST = 'https://www.arionum.com/peers.txt';
/**
* @var array
*/
private $peerList = [];
/**
* InitialPeers constructor.
* @param array|null $peerList
* @return void
*/
public function __construct(?array $peerList = [])
{
$this->peerList = $peerList;
}
/**
* Retrieve a peer from the initial peer list.
* @return string
* @throws Exception
*/
public function get(): string
{
if (!$this->peerList || count($this->peerList) < self::MINIMUM_PEERS_REQUIRED) {
$this->retrieveFromPreloadList();
}
return $this->selectPeer();
}
/**
* Retrieve all available initial peers.
* @return array
* @throws Exception
*/
public function getAll(): array
{
if (!$this->peerList || count($this->peerList) < self::MINIMUM_PEERS_REQUIRED) {
$this->retrieveFromPreloadList();
}
return $this->peerList;
}
/**
* @return string
*/
private function selectPeer(): string
{
return $this->peerList[array_rand($this->peerList)];
}
/**
* Retrieve a peer from
*
* @return void
* @throws Exception
*/
private function retrieveFromPreloadList(): void
{
$peerList = file(self::PRELOAD_LIST, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if (!$peerList || count($peerList) < self::MINIMUM_PEERS_REQUIRED) {
throw new Exception(self::PRELOAD_ERROR);
}
$this->peerList = $peerList;
}
}

View File

@@ -93,7 +93,77 @@ class Account
return true;
}
//check alias validity
public function free_alias($id)
{
global $db;
$orig=$id;
$id=strtoupper($id);
$id = san($id);
if (strlen($id)<4||strlen($id)>25) {
return false;
}
if ($orig!=$id) {
return false;
}
if ($db->single("SELECT COUNT(1) FROM accounts WHERE alias=:alias", [":alias"=>$id])==0) {
return true;
} else {
return false;
}
}
//check if an account already has an alias
public function has_alias($public_key)
{
global $db;
$public_key=san($public_key);
$res=$db->single("SELECT COUNT(1) FROM accounts WHERE public_key=:public_key AND alias IS NOT NULL", [":public_key"=>$public_key]);
if ($res!=0) {
return true;
} else {
return false;
}
}
//check alias validity
public function valid_alias($id)
{
global $db;
$orig=$id;
$banned=["MERCURY","DEVS","DEVELOPMENT", "MARKETING", "MERCURY80","DEVARO", "DEVELOPER","DEVELOPERS","ARODEV", "DONATION","MERCATOX", "OCTAEX", "MERCURY", "ARIONUM", "ESCROW","OKEX","BINANCE","CRYPTOPIA","HUOBI","ITFINEX","HITBTC","UPBIT","COINBASE","KRAKEN","BITSTAMP","BITTREX","POLONIEX"];
$id=strtoupper($id);
$id = san($id);
if (in_array($id, $banned)) {
return false;
}
if (strlen($id)<4||strlen($id)>25) {
return false;
}
if ($orig!=$id) {
return false;
}
return $db->single("SELECT COUNT(1) FROM accounts WHERE alias=:alias", [":alias"=>$id]);
}
//returns the account of an alias
public function alias2account($alias)
{
global $db;
$alias=strtoupper($alias);
$res=$db->single("SELECT id FROM accounts WHERE alias=:alias LIMIT 1", [":alias"=>$alias]);
return $res;
}
//returns the alias of an account
public function account2alias($id)
{
global $db;
$id=san($id);
$res=$db->single("SELECT alias FROM accounts WHERE id=:id LIMIT 1", [":id"=>$id]);
return $res;
}
// 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)
{
@@ -148,13 +218,14 @@ class Account
$block = new Block();
$current = $block->current();
$public_key = $this->public_key($id);
$alias = $this->account2alias($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]
"SELECT * FROM transactions WHERE dst=:dst or public_key=:src or dst=:alias ORDER by height DESC LIMIT :limit",
[":src" => $public_key, ":dst" => $id, ":limit" => $limit, ":alias"=>$alias]
);
$transactions = [];
@@ -234,4 +305,14 @@ class Account
$res = $db->single("SELECT public_key FROM accounts WHERE id=:id", [":id" => $id]);
return $res;
}
public function get_masternode($public_key)
{
global $db;
$res = $db->row("SELECT * FROM masternode WHERE public_key=:public_key", [":public_key" => $public_key]);
if (empty($res['public_key'])) {
return false;
}
return $res;
}
}

View File

@@ -2,7 +2,7 @@
class Block
{
public function add($height, $public_key, $nonce, $data, $date, $signature, $difficulty, $reward_signature, $argon)
public function add($height, $public_key, $nonce, $data, $date, $signature, $difficulty, $reward_signature, $argon, $bootstrapping=false)
{
global $db;
$acc = new Account();
@@ -24,23 +24,45 @@ class Block
// 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;
}
// _log($info,3);
if (!$bootstrapping) {
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;
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");
$db->exec("LOCK TABLES blocks WRITE, accounts WRITE, transactions WRITE, mempool WRITE, masternode WRITE, peers write, config WRITE");
$reward = $this->reward($height, $data);
$msg = '';
if ($height>=80458) {
//reward the masternode
$mn_winner=$db->single(
"SELECT public_key FROM masternode WHERE status=1 AND blacklist<:current AND height<:start ORDER by last_won ASC, public_key ASC LIMIT 1",
[":current"=>$height, ":start"=>$height-360]
);
_log("MN Winner: $mn_winner", 2);
if ($mn_winner!==false) {
$mn_reward=round(0.33*$reward, 8);
$reward=round($reward-$mn_reward, 8);
$reward=number_format($reward, 8, ".", "");
$mn_reward=number_format($mn_reward, 8, ".", "");
_log("MN Reward: $mn_reward", 2);
}
}
// the reward transaction
$transaction = [
"src" => $generator,
@@ -55,16 +77,19 @@ class Block
$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;
if (!$bootstrapping) {
// 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,
@@ -89,10 +114,58 @@ class Block
}
// insert the reward transaction in the db
$trx->add($hash, $height, $transaction);
$res=$trx->add($hash, $height, $transaction);
if ($res == false) {
// rollback and exit if it fails
_log("Reward DB insert failed");
$db->rollback();
$db->exec("UNLOCK TABLES");
return false;
}
if ($mn_winner!==false&&$height>=80458&&$mn_reward>0) {
$db->run("UPDATE accounts SET balance=balance+:bal WHERE public_key=:pub", [":pub"=>$mn_winner, ":bal"=>$mn_reward]);
$bind = [
":id" => hex2coin(hash("sha512", "mn".$hash.$height.$mn_winner)),
":public_key" => $public_key,
":height" => $height,
":block" => $hash,
":dst" => $acc->get_address($mn_winner),
":val" => $mn_reward,
":fee" => 0,
":signature" => $reward_signature,
":version" => 0,
":date" => $date,
":message" => 'masternode',
];
$res = $db->run(
"INSERT into transactions SET id=:id, public_key=:public_key, block=:block, height=:height, dst=:dst, val=:val, fee=:fee, signature=:signature, version=:version, message=:message, `date`=:date",
$bind
);
if ($res != 1) {
// rollback and exit if it fails
_log("Masternode reward DB insert failed");
$db->rollback();
$db->exec("UNLOCK TABLES");
return false;
}
$res=$this->reset_fails_masternodes($mn_winner, $height, $hash);
if (!$res) {
// rollback and exit if it fails
_log("Masternode log DB insert failed");
$db->rollback();
$db->exec("UNLOCK TABLES");
return false;
}
}
// parse the block's transactions and insert them to db
$res = $this->parse_block($hash, $height, $data, false);
$res = $this->parse_block($hash, $height, $data, false, $bootstrapping);
if (($height-1)%3==2 && $height>=80000&&$height<80458) {
$this->blacklist_masternodes();
$this->reset_fails_masternodes($public_key, $height, $hash);
}
// if any fails, rollback
if ($res == false) {
$db->rollback();
@@ -104,6 +177,51 @@ class Block
return true;
}
// resets the number of fails when winning a block and marks it with a transaction
public function reset_fails_masternodes($public_key, $height, $hash)
{
global $db;
$res=$this->masternode_log($public_key, $height, $hash);
if ($res===5) {
return false;
}
if ($res) {
$rez=$db->run("UPDATE masternode SET last_won=:last_won,fails=0 WHERE public_key=:public_key", [":public_key"=>$public_key, ":last_won"=>$height]);
if ($rez!=1) {
return false;
}
}
return true;
}
//logs the current masternode status
public function masternode_log($public_key, $height, $hash)
{
global $db;
$mn=$db->row("SELECT blacklist,last_won,fails FROM masternode WHERE public_key=:public_key", [":public_key"=>$public_key]);
if (!$mn) {
return false;
}
$id = hex2coin(hash("sha512", "resetfails-$hash-$height-$public_key"));
$msg="$mn[blacklist],$mn[last_won],$mn[fails]";
$res=$db->run(
"INSERT into transactions SET id=:id, block=:block, height=:height, dst=:dst, val=0, fee=0, signature=:sig, version=111, message=:msg, date=:date, public_key=:public_key",
[":id"=>$id, ":block"=>$hash, ":height"=>$height, ":dst"=>$hash, ":sig"=>$hash, ":msg"=>$msg, ":date"=>time(), ":public_key"=>$public_key]
);
if ($res!=1) {
return 5;
}
return true;
}
// returns the current block, without the transactions
public function current()
{
@@ -140,8 +258,8 @@ class Block
$height = $current['height'];
if ($height == 10801) {
return 5555555555; //hard fork 10900 resistance, force new difficulty
if ($height == 10801||($height>=80456&&$height<80460)) {
return "5555555555"; //hard fork 10900 resistance, force new difficulty
}
// last 20 blocks used to check the block times
@@ -155,23 +273,96 @@ class Block
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'];
// before mnn hf
if ($height<80000) {
// elapsed time between the last 20 blocks
$first = $db->row("SELECT `date` FROM blocks ORDER by height DESC LIMIT :limit,1",[":limit"=>$limit]);
$time = $current['date'] - $first['date'];
// avg block time
$result = ceil($time / $limit);
// avg block time
$result = ceil($time / $limit);
_log("Block time: $result", 3);
// 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);
// 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'];
}
} elseif ($height>=80458) {
$type=$height%2;
$current=$db->row("SELECT difficulty from blocks WHERE height<=:h ORDER by height DESC LIMIT 1,1", [":h"=>$height]);
$blks=0;
$total_time=0;
$blk = $db->run("SELECT `date`, height FROM blocks WHERE height<=:h ORDER by height DESC LIMIT 20", [":h"=>$height]);
for ($i=0;$i<19;$i++) {
$ctype=$blk[$i+1]['height']%2;
$time=$blk[$i]['date']-$blk[$i+1]['date'];
if ($type!=$ctype) {
continue;
}
$blks++;
$total_time+=$time;
}
$result=ceil($total_time/$blks);
_log("Block time: $result", 3);
if ($result > 260) {
$dif = bcmul($current['difficulty'], 1.05);
} elseif ($result < 220) {
// if lower, decrease by 5%
$dif = bcmul($current['difficulty'], 0.95);
} else {
// keep current difficulty
$dif = $current['difficulty'];
}
} else {
// keep current difficulty
$dif = $current['difficulty'];
// hardfork 80000, fix difficulty targetting
$type=$height%3;
// for mn, we use gpu diff
if ($type == 2) {
return $current['difficulty'];
}
$blks=0;
$total_time=0;
$blk = $db->run("SELECT `date`, height FROM blocks ORDER by height DESC LIMIT 60");
for ($i=0;$i<59;$i++) {
$ctype=$blk[$i+1]['height']%3;
$time=$blk[$i]['date']-$blk[$i+1]['date'];
if ($type!=$ctype) {
continue;
}
$blks++;
$total_time+=$time;
}
$result=ceil($total_time/$blks);
_log("Block time: $result", 3);
// if larger than 260 sec, increase by 5%
if ($result > 260) {
$dif = bcmul($current['difficulty'], 1.05);
} elseif ($result < 220) {
// if lower, decrease by 5%
$dif = bcmul($current['difficulty'], 0.95);
} else {
// keep current difficulty
$dif = $current['difficulty'];
}
}
if (strpos($dif, '.') !== false) {
$dif = substr($dif, 0, strpos($dif, '.'));
}
@@ -183,7 +374,7 @@ class Block
if ($dif > 9223372036854775800) {
$dif = 9223372036854775800;
}
_log("Difficulty: $dif", 3);
return $dif;
}
@@ -233,8 +424,13 @@ class Block
return false;
}
$acc = new Account();
// generator's public key must be valid
if ($data['date']>time()+30) {
_log("Future block - $data[date] $data[public_key]", 2);
return false;
}
// generator's public key must be valid
if (!$acc->valid_key($data['public_key'])) {
_log("Invalid public key - $data[public_key]");
return false;
@@ -247,7 +443,7 @@ class Block
}
//check the argon hash and the nonce to produce a valid block
if (!$this->mine($data['public_key'], $data['nonce'], $data['argon'])) {
if (!$this->mine($data['public_key'], $data['nonce'], $data['argon'], $data['difficulty'], 0, 0, $data['date'])) {
_log("Mine check failed");
return false;
}
@@ -290,6 +486,24 @@ class Block
// reward transaction and signature
$reward = $this->reward($height, $data);
if ($height>=80458) {
//reward the masternode
global $db;
$mn_winner=$db->single(
"SELECT public_key FROM masternode WHERE status=1 AND blacklist<:current AND height<:start ORDER by last_won ASC, public_key ASC LIMIT 1",
[":current"=>$height, ":start"=>$height-360]
);
_log("MN Winner: $mn_winner", 2);
if ($mn_winner!==false) {
$mn_reward=round(0.33*$reward, 8);
$reward=round($reward-$mn_reward, 8);
$reward=number_format($reward, 8, ".", "");
$mn_reward=number_format($mn_reward, 8, ".", "");
_log("MN Reward: $mn_reward", 2);
}
}
$msg = '';
$transaction = [
"src" => $generator,
@@ -322,27 +536,178 @@ class Block
}
return true;
}
public function blacklist_masternodes()
{
global $db;
_log("Checking if there are masternodes to be blacklisted", 2);
$current = $this->current();
if (($current['height']-1)%3!=2) {
_log("bad height");
return;
}
$last=$this->get($current['height']-1);
$total_time=$current['date']-$last['date'];
_log("blacklist total time $total_time");
if ($total_time<=600&&$current['height']<80500) {
return;
}
if ($current['height']>=80500&&$total_time<360) {
return false;
}
if ($current['height']>=80500) {
$total_time-=360;
$tem=floor($total_time/120)+1;
if ($tem>5) {
$tem=5;
}
} else {
$tem=floor($total_time/600);
}
_log("We have masternodes to blacklist - $tem", 2);
$ban=$db->run(
"SELECT public_key, blacklist, fails, last_won FROM masternode WHERE status=1 AND blacklist<:current AND height<:start ORDER by last_won ASC, public_key ASC LIMIT 0,:limit",
[":current"=>$last['height'], ":start"=>$last['height']-360, ":limit"=>$tem]
);
_log(json_encode($ban));
$i=0;
foreach ($ban as $b) {
$this->masternode_log($b['public_key'], $current['height'], $current['id']);
_log("Blacklisting masternode - $i $b[public_key]", 2);
$btime=10;
if ($current['height']>83000) {
$btime=360;
}
$db->run("UPDATE masternode SET fails=fails+1, blacklist=:blacklist WHERE public_key=:public_key", [":public_key"=>$b['public_key'], ":blacklist"=> $current['height']+(($b['fails']+1)*$btime)]);
$i++;
}
}
// 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)
public function mine($public_key, $nonce, $argon, $difficulty = 0, $current_id = 0, $current_height = 0, $time=0)
{
global $_config;
// invalid future blocks
if ($time>time()+30) {
return false;
}
// if no id is specified, we use the current
if ($current_id === 0) {
if ($current_id === 0 || $current_height === 0) {
$current = $this->current();
$current_id = $current['id'];
$current_height = $current['height'];
}
_log("Block Timestamp $time", 3);
if ($time == 0) {
$time=time();
}
// 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
if (empty($public_key)) {
_log("Empty public key", 1);
return false;
}
if ($current_height<80000) {
// the argon parameters are hardcoded to avoid any exploits
if ($current_height > 10800) {
_log("Block below 80000 but after 10800, using 512MB argon", 2);
$argon = '$argon2i$v=19$m=524288,t=1,p=1'.$argon; //10800 block hard fork - resistance against gpu
} else {
_log("Block below 10800, using 16MB argon", 2);
$argon = '$argon2i$v=19$m=16384,t=4,p=4'.$argon;
}
} elseif ($current_height>=80458) {
if ($current_height%2==0) {
// cpu mining
_log("CPU Mining - $current_height", 2);
$argon = '$argon2i$v=19$m=524288,t=1,p=1'.$argon;
} else {
// gpu mining
_log("GPU Mining - $current_height", 2);
$argon = '$argon2i$v=19$m=16384,t=4,p=4'.$argon;
}
} else {
$argon = '$argon2i$v=19$m=16384,t=4,p=4'.$argon;
_log("Block > 80000 - $current_height", 2);
if ($current_height%3==0) {
// cpu mining
_log("CPU Mining - $current_height", 2);
$argon = '$argon2i$v=19$m=524288,t=1,p=1'.$argon;
} elseif ($current_height%3==1) {
// gpu mining
_log("GPU Mining - $current_height", 2);
$argon = '$argon2i$v=19$m=16384,t=4,p=4'.$argon;
} else {
_log("Masternode Mining - $current_height", 2);
// masternode
global $db;
// fake time
if ($time>time()) {
_log("Masternode block in the future - $time", 1);
return false;
}
// selecting the masternode winner in order
$winner=$db->single(
"SELECT public_key FROM masternode WHERE status=1 AND blacklist<:current AND height<:start ORDER by last_won ASC, public_key ASC LIMIT 1",
[":current"=>$current_height, ":start"=>$current_height-360]
);
// if there are no active masternodes, give the block to gpu
if ($winner===false) {
_log("No active masternodes, reverting to gpu", 1);
$argon = '$argon2i$v=19$m=16384,t=4,p=4'.$argon;
} else {
_log("The first masternode winner should be $winner", 1);
// 4 mins need to pass since last block
$last_time=$db->single("SELECT `date` FROM blocks WHERE height=:height", [":height"=>$current_height]);
if ($time-$last_time<240&&$_config['testnet']==false) {
_log("4 minutes have not passed since the last block - $time", 1);
return false;
}
if ($public_key==$winner) {
return true;
}
// if 10 mins have passed, try to give the block to the next masternode and do this every 10mins
_log("Last block time: $last_time, difference: ".($time-$last_time), 3);
if (($time-$last_time>600&&$current_height<80500)||($time-$last_time>360&&$current_height>=80500)) {
_log("Current public_key $public_key", 3);
if ($current_height>=80500) {
$total_time=$time-$last_time;
$total_time-=360;
$tem=floor($total_time/120)+1;
} else {
$tem=floor(($time-$last_time)/600);
}
$winner=$db->single(
"SELECT public_key FROM masternode WHERE status=1 AND blacklist<:current AND height<:start ORDER by last_won ASC, public_key ASC LIMIT :tem,1",
[":current"=>$current_height, ":start"=>$current_height-360, ":tem"=>$tem]
);
_log("Moving to the next masternode - $tem - $winner", 1);
// if all masternodes are dead, give the block to gpu
if ($winner===false||($tem>=5&&$current_height>=80500)) {
_log("All masternodes failed, giving the block to gpu", 1);
$argon = '$argon2i$v=19$m=16384,t=4,p=4'.$argon;
} elseif ($winner==$public_key) {
return true;
} else {
return false;
}
} else {
_log("A different masternode should win this block $public_key - $winner", 2);
return false;
}
}
}
}
// the hash base for agon
@@ -351,6 +716,7 @@ class Block
// check argon's hash validity
if (!password_verify($base, $argon)) {
_log("Argon verify failed - $base - $argon", 2);
return false;
}
@@ -390,11 +756,12 @@ class Block
// parse the block transactions
public function parse_block($block, $height, $data, $test = true)
public function parse_block($block, $height, $data, $test = true, $bootstrapping=false)
{
global $db;
// data must be array
if ($data === false) {
_log("Block data is false", 3);
return false;
}
$acc = new Account();
@@ -407,41 +774,58 @@ class Block
// check if the number of transactions is not bigger than current block size
$max = $this->max_transactions();
if (count($data) > $max) {
_log("Too many transactions in block", 3);
return false;
}
$balance = [];
$mns = [];
foreach ($data as &$x) {
// get the sender's account if empty
if (empty($x['src'])) {
$x['src'] = $acc->get_address($x['public_key']);
}
if (!$bootstrapping) {
//validate the transaction
if (!$trx->check($x, $height)) {
_log("Transaction check failed - $x[id]", 3);
return false;
}
if ($x['version']>=100&&$x['version']<110) {
$mns[] = $x['public_key'];
}
//validate the transaction
if (!$trx->check($x, $height)) {
return false;
}
// prepare total balance
$balance[$x['src']] += $x['val'] + $x['fee'];
// 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 transaction is already on the blockchain
if ($db->single("SELECT COUNT(1) FROM transactions WHERE id=:id", [":id" => $x['id']]) > 0) {
_log("Transaction already on the blockchain - $x[id]", 3);
return false;
}
}
}
//only a single masternode transaction per block for any masternode
if (count($mns) != count(array_unique($mns))) {
_log("Too many masternode transactions", 3);
return false;
}
// check if the account has enough balance to perform the transaction
foreach ($balance as $id => $bal) {
$res = $db->single(
if (!$bootstrapping) {
// 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 ($res == 0) {
_log("Not enough balance for transaction - $id", 3);
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) {
@@ -511,16 +895,18 @@ class Block
return;
}
$db->beginTransaction();
$db->exec("LOCK TABLES blocks WRITE, accounts WRITE, transactions WRITE, mempool WRITE");
$db->exec("LOCK TABLES blocks WRITE, accounts WRITE, transactions WRITE, mempool WRITE, masternode WRITE");
foreach ($r as $x) {
$res = $trx->reverse($x['id']);
if ($res === false) {
_log("A transaction could not be reversed. Delete block failed.");
$db->rollback();
$db->exec("UNLOCK TABLES");
return false;
}
$res = $db->run("DELETE FROM blocks WHERE id=:id", [":id" => $x['id']]);
if ($res != 1) {
_log("Delete block failed.");
$db->rollback();
$db->exec("UNLOCK TABLES");
return false;
@@ -536,7 +922,6 @@ class Block
// delete specific block
public function delete_id($id)
{
global $db;
$trx = new Transaction();
@@ -574,7 +959,6 @@ class Block
// 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}";
@@ -612,6 +996,10 @@ class Block
$r = $db->run("SELECT * FROM transactions WHERE version>0 AND block=:block", [":block" => $block['id']]);
$transactions = [];
foreach ($r as $x) {
if ($x['version']>110) {
//internal transactions
continue;
}
$trans = [
"id" => $x['id'],
"dst" => $x['dst'],
@@ -631,7 +1019,7 @@ class Block
// the reward transaction always has version 0
$gen = $db->row(
"SELECT public_key, signature FROM transactions WHERE version=0 AND block=:block",
"SELECT public_key, signature FROM transactions WHERE version=0 AND block=:block AND message=''",
[":block" => $block['id']]
);
$block['public_key'] = $gen['public_key'];

156
include/config-sample.inc.php Executable file
View File

@@ -0,0 +1,156 @@
<?php
/*
|--------------------------------------------------------------------------
| Database Configuration
|--------------------------------------------------------------------------
*/
// The database DSN
$_config['db_connect'] = 'mysql:host=localhost;dbname=ENTER-DB-NAME';
// The database username
$_config['db_user'] = 'ENTER-DB-USER';
// The database password
$_config['db_pass'] = 'ENTER-DB-PASS';
/*
|--------------------------------------------------------------------------
| General Configuration
|--------------------------------------------------------------------------
*/
// Maximum number of connected peers
$_config['max_peers'] = 30;
// Enable testnet mode for development
$_config['testnet'] = false;
// To avoid any problems if other clones are made
$_config['coin'] = 'arionum';
// Allow others to connect to the node api (if set to false, only the below 'allowed_hosts' are allowed)
$_config['public_api'] = true;
// Hosts that are allowed to mine on this node
$_config['allowed_hosts'] = [
'127.0.0.1',
];
// Disable transactions and block repropagation
$_config['disable_repropagation'] = false;
/*
|--------------------------------------------------------------------------
| Peer Configuration
|--------------------------------------------------------------------------
*/
// The number of peers to send each new transaction to
$_config['transaction_propagation_peers'] = 5;
// How many new peers to check from each peer
$_config['max_test_peers'] = 5;
// The initial peers to sync from in sanity
$_config['initial_peer_list'] = [
'http://peer1.arionum.com',
'http://peer2.arionum.com',
'http://peer3.arionum.com',
'http://peer4.arionum.com',
'http://peer5.arionum.com',
'http://peer6.arionum.com',
'http://peer7.arionum.com',
'http://peer8.arionum.com',
'http://peer9.arionum.com',
'http://peer10.arionum.com',
'http://peer11.arionum.com',
'http://peer12.arionum.com',
'http://peer13.arionum.com',
'http://peer14.arionum.com',
'http://peer15.arionum.com',
'http://peer16.arionum.com',
'http://peer17.arionum.com',
'http://peer18.arionum.com',
'http://peer19.arionum.com',
'http://peer20.arionum.com',
'http://peer21.arionum.com',
'http://peer22.arionum.com',
'http://peer23.arionum.com',
'http://peer24.arionum.com',
'http://peer25.arionum.com',
'http://peer26.arionum.com',
'http://peer27.arionum.com',
];
// does not peer with any of the peers. Uses the seed peers and syncs only from those peers. Requires a cronjob on sanity.php
$_config['passive_peering'] = false;
/*
|--------------------------------------------------------------------------
| Mempool Configuration
|--------------------------------------------------------------------------
*/
// The maximum transactions to accept from a single peer
$_config['peer_max_mempool'] = 100;
// The maximum number of mempool transactions to be rebroadcasted
$_config['max_mempool_rebroadcast'] = 5000;
// The number of blocks between rebroadcasting transactions
$_config['sanity_rebroadcast_height'] = 30;
// Block accepting transfers from addresses blacklisted by the Arionum devs
$_config['use_official_blacklist'] = true;
/*
|--------------------------------------------------------------------------
| Sanity Configuration
|--------------------------------------------------------------------------
*/
// Recheck the last blocks on sanity
$_config['sanity_recheck_blocks'] = 10;
// The interval to run the sanity in seconds
$_config['sanity_interval'] = 900;
// Enable setting a new hostname (should be used only if you want to change the hostname)
$_config['allow_hostname_change'] = false;
// Rebroadcast local transactions when running sanity
$_config['sanity_rebroadcast_locals'] = true;
// Get more peers?
$_config['get_more_peers'] = true;
/*
|--------------------------------------------------------------------------
| Logging Configuration
|--------------------------------------------------------------------------
*/
// Enable log output to the specified file
$_config['enable_logging'] = false;
// The specified file to write to (this should not be publicly visible)
$_config['log_file'] = '/var/log/aro.log';
// Log verbosity (default 0, maximum 3)
$_config['log_verbosity'] = 0;
/*
|--------------------------------------------------------------------------
| Masternode Configuration
|--------------------------------------------------------------------------
*/
// Enable this node as a masternode
$_config['masternode'] = false;
// The public key for the masternode
$_config['masternode_public_key'] = '';

View File

@@ -1,38 +0,0 @@
<?php
// Database connection
$_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
$_config['max_peers']=30;
// Testnet, used for development
$_config['testnet']=false;
// To avoid any problems if other clones are made
$_config['coin']="arionum";
// maximum transactions accepted from a single peer
$_config['peer_max_mempool']=100;
// maximum mempool transactions to be rebroadcasted
$_config['max_mempool_rebroadcast']=5000;
// after how many blocks should the transactions be rebroadcasted
$_config['sanity_rebroadcast_height']=30;
// each new received transaction is sent to X peers
$_config['transaction_propagation_peers']=5;
// how many new peers to check from each peer.
$_config['max_test_peers']=5;
// recheck the last blocks on sanity
$_config['sanity_recheck_blocks']=10;
// allow others to connect to node api. If set to false, only allowed_hosts are allowed
$_config['public_api']=true;
// hosts allowed to mine on this node
$_config['allowed_hosts']=array("127.0.0.1");
// sanity is run every X seconds
$_config['sanity_interval']=900;
// accept the setting of new hostnames / should be used only if you want to change the hostname
$_config['allow_hostname_change']=false;
// rebroadcast local transactions on each sanity
$_config['sanity_rebroadcast_locals']=true;
// write logs to file
$_config['enable_logging']=false;
// log file, should not be publicly viewable
$_config['log_file']="/var/log/aro.log";

View File

@@ -31,6 +31,7 @@ class DB extends PDO
private function debug()
{
global $_config;
if (!$this->debugger) {
return;
}
@@ -56,9 +57,9 @@ class DB extends PDO
$msg .= "\n\n$key:\n$val";
}
if ($this->debugger) {
echo nl2br($msg);
}
_log($msg);
}
private function cleanup($bind, $sql = "")

View File

@@ -24,6 +24,10 @@ function san_host($a)
function api_err($data)
{
global $_config;
if (!headers_sent()) {
header('Content-Type: application/json');
}
echo json_encode(["status" => "error", "data" => $data, "coin" => $_config['coin']]);
exit;
}
@@ -32,13 +36,21 @@ function api_err($data)
function api_echo($data)
{
global $_config;
if (!headers_sent()) {
header('Content-Type: application/json');
}
echo json_encode(["status" => "ok", "data" => $data, "coin" => $_config['coin']]);
exit;
}
// log function, shows only in cli atm
function _log($data)
function _log($data, $verbosity = 0)
{
global $_config;
if ($_config['log_verbosity'] < $verbosity) {
return;
}
$date = date("[Y-m-d H:i:s]");
$trace = debug_backtrace();
$loc = count($trace) - 1;
@@ -56,8 +68,7 @@ function _log($data)
if (php_sapi_name() === 'cli') {
echo $res;
}
global $_config;
if ($_config['enable_logging'] == true) {
if ($_config['enable_logging'] == true && $_config['log_verbosity'] >= $verbosity) {
@file_put_contents($_config['log_file'], $res, FILE_APPEND);
}
}

View File

@@ -1,8 +1,8 @@
<?php
// ARO version
define("VERSION", "0.3.0");
define("VERSION", "0.4.3");
// Amsterdam timezone by default, should probably be moved to config
date_default_timezone_set("Europe/Amsterdam");
date_default_timezone_set("UTC");
//error_reporting(E_ALL & ~E_NOTICE);
error_reporting(0);
@@ -13,18 +13,21 @@ 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");
require_once("include/block.inc.php");
require_once("include/account.inc.php");
require_once("include/transaction.inc.php");
require_once __DIR__.'/Exception.php';
require_once __DIR__.'/config.inc.php';
require_once __DIR__.'/db.inc.php';
require_once __DIR__.'/functions.inc.php';
require_once __DIR__.'/Blacklist.php';
require_once __DIR__.'/InitialPeers.php';
require_once __DIR__.'/block.inc.php';
require_once __DIR__.'/account.inc.php';
require_once __DIR__.'/transaction.inc.php';
if ($_config['db_pass'] == "ENTER-DB-PASS") {
die("Please update your config file and set your db password");
}
// initial DB connection
$db = new DB($_config['db_connect'], $_config['db_user'], $_config['db_pass'], 0);
$db = new DB($_config['db_connect'], $_config['db_user'], $_config['db_pass'], $_config['enable_logging']);
if (!$db) {
die("Could not connect to the DB backend.");
}
@@ -63,10 +66,18 @@ if ($_config['maintenance'] == 1) {
// update the db schema, on every git pull or initial install
if (file_exists("tmp/db-update")) {
//checking if the server has at least 2GB of ram
$ram=file_get_contents("/proc/meminfo");
$ramz=explode("MemTotal:",$ram);
$ramb=explode("kB",$ramz[1]);
$ram=intval(trim($ramb[0]));
if($ram<1700000) {
die("The node requires at least 2 GB of RAM");
}
$res = unlink("tmp/db-update");
if ($res) {
echo "Updating db schema! Please refresh!\n";
require_once("include/schema.inc.php");
require_once __DIR__.'/schema.inc.php';
exit;
}
echo "Could not access the tmp/db-update file. Please give full permissions to this file\n";

View File

@@ -140,6 +140,33 @@ if ($dbversion == 6) {
$db->run("ALTER TABLE `accounts` ADD `alias` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL AFTER `balance`; ");
$dbversion++;
}
if ($dbversion == 7) {
$db->run("ALTER TABLE `accounts` ADD INDEX(`alias`); ");
$db->run("ALTER TABLE `transactions` ADD KEY `dst` (`dst`), ADD KEY `height` (`height`), ADD KEY `public_key` (`public_key`);");
$dbversion++;
}
if ($dbversion == 8) {
$db->run("CREATE TABLE `masternode` (
`public_key` varchar(128) COLLATE utf8mb4_bin NOT NULL,
`height` int(11) NOT NULL,
`ip` varchar(16) COLLATE utf8mb4_bin NOT NULL,
`last_won` int(11) NOT NULL DEFAULT '0',
`blacklist` int(11) NOT NULL DEFAULT '0',
`fails` int(11) NOT NULL DEFAULT '0',
`status` tinyint(4) NOT NULL DEFAULT '1'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;");
$db->run("ALTER TABLE `masternode`
ADD PRIMARY KEY (`public_key`),
ADD KEY `last_won` (`last_won`),
ADD KEY `status` (`status`),
ADD KEY `blacklist` (`blacklist`),
ADD KEY `height` (`height`);");
$dbversion++;
}
// update the db version to the latest one
if ($dbversion != $_config['dbversion']) {
$db->run("UPDATE config SET val=:val WHERE cfg='dbversion'", [":val" => $dbversion]);

View File

@@ -1,36 +1,133 @@
<?php
use Arionum\Blacklist;
class Transaction
{
// reverse and remove all transactions from a block
public function reverse($block)
{
global $db;
$acc = new Account();
$r = $db->run("SELECT * FROM transactions WHERE block=:block", [":block" => $block]);
$r = $db->run("SELECT * FROM transactions WHERE block=:block ORDER by `version` ASC", [":block" => $block]);
foreach ($r as $x) {
_log("Reversing transaction $x[id]", 4);
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']]
);
if ($x['version'] == 2) {
// payment sent to alias
$rez=$db->run(
"UPDATE accounts SET balance=balance-:val WHERE alias=:alias",
[":alias" => $x['dst'], ":val" => $x['val']]
);
if ($rez!=1) {
_log("Update alias balance minus failed", 3);
return false;
}
} else {
// other type of transactions
if ($x['version']!=100&&$x['version']<111) {
$rez=$db->run(
"UPDATE accounts SET balance=balance-:val WHERE id=:id",
[":id" => $x['dst'], ":val" => $x['val']]
);
if ($rez!=1) {
_log("Update accounts balance minus failed", 3);
return false;
}
}
}
// on version 0 / reward transaction, don't credit anyone
if ($x['version'] > 0) {
$db->run(
if ($x['version'] > 0 && $x['version']<111) {
$rez=$db->run(
"UPDATE accounts SET balance=balance+:val WHERE id=:id",
[":id" => $x['src'], ":val" => $x['val'] + $x['fee']]
);
if ($rez!=1) {
_log("Update account balance plus failed", 3);
return false;
}
}
// removing the alias if the alias transaction is reversed
if ($x['version']==3) {
$rez=$db->run(
"UPDATE accounts SET alias=NULL WHERE id=:id",
[":id" => $x['src']]
);
if ($rez!=1) {
_log("Clear alias failed", 3);
return false;
}
}
if ($x['version']>=100&&$x['version']<110&&$x['height']>=80000) {
if ($x['version']==100) {
$rez=$db->run("DELETE FROM masternode WHERE public_key=:public_key", [':public_key'=>$x['public_key']]);
if ($rez!=1) {
_log("Delete from masternode failed", 3);
return false;
}
} elseif ($x['version']==101) {
$rez=$db->run(
"UPDATE masternode SET status=1 WHERE public_key=:public_key",
[':public_key'=>$x['public_key']]
);
} elseif ($x['version']==102) {
$rez=$db->run("UPDATE masternode SET status=0 WHERE public_key=:public_key", [':public_key'=>$x['public_key']]);
} elseif ($x['version']==103) {
$mnt=$db->row("SELECT height, `message` FROM transactions WHERE version=100 AND public_key=:public_key ORDER by height DESC LIMIT 1", [":public_key"=>$x['public_key']]);
$vers=$db->single(
"SELECT `version` FROM transactions WHERE (version=101 or version=102) AND public_key=:public_key AND height>:height ORDER by height DESC LIMIT 1",
[":public_key"=>$x['public_key'],":height"=>$mnt['height']]
);
$status=1;
if ($vers==101) {
$status=0;
}
$rez=$db->run(
"INSERT into masternode SET `public_key`=:public_key, `height`=:height, `ip`=:ip, `status`=:status",
[":public_key"=>$x['public_key'], ":height"=>$mnt['height'], ":ip"=>$mnt['message'], ":status"=>$status]
);
if ($rez!=1) {
_log("Insert into masternode failed", 3);
return false;
}
$rez=$db->run("UPDATE accounts SET balance=balance-100000 WHERE public_key=:public_key", [':public_key'=>$x['public_key']]);
if ($rez!=1) {
_log("Update masternode balance failed", 3);
return false;
}
}
}
// internal masternode history
if ($x['version']==111) {
_log("Masternode reverse: $x[message]", 4);
$m=explode(",", $x['message']);
$rez=$db->run(
"UPDATE masternode SET fails=:fails, blacklist=:blacklist, last_won=:last_won WHERE public_key=:public_key",
[":public_key"=>$x['public_key'], ":blacklist"=> $m[0], ":fails"=>$m[2], ":last_won"=>$m[1]]
);
if ($rez!=1) {
_log("Update masternode log failed", 3);
return false;
}
}
// add the transactions to mempool
if ($x['version'] > 0) {
if ($x['version'] > 0 && $x['version']<=110) {
$this->add_mempool($x);
}
$res = $db->run("DELETE FROM transactions WHERE id=:id", [":id" => $x['id']]);
if ($res != 1) {
_log("Delete transaction failed", 3);
return false;
}
}
@@ -92,7 +189,7 @@ class Transaction
_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");
@@ -123,7 +220,17 @@ class Transaction
public function add_mempool($x, $peer = "")
{
global $db;
global $_config;
$block = new Block();
if ($x['version']>110) {
return true;
}
if ($_config['use_official_blacklist']!==false) {
if (Blacklist::checkPublicKey($x['public_key']) || Blacklist::checkAddress($x['src'])) {
return true;
}
}
$current = $block->current();
$height = $current['height'];
$x['id'] = san($x['id']);
@@ -141,6 +248,16 @@ class Transaction
":date" => $x['date'],
":message" => $x['message'],
];
//only a single masternode command of same type, per block
if ($x['version']>=100&&$x['version']<110) {
$check=$db->single("SELECT COUNT(1) FROM mempool WHERE public_key=:public_key", [":public_key"=>$x['public_key']]);
if ($check!=0) {
_log("Masternode transaction already in mempool", 3);
return false;
}
}
$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
@@ -154,7 +271,9 @@ class Transaction
global $db;
$acc = new Account();
$acc->add($x['public_key'], $block);
$acc->add_id($x['dst'], $block);
if ($x['version']==1) {
$acc->add_id($x['dst'], $block);
}
$x['id'] = san($x['id']);
$bind = [
":id" => $x['id'],
@@ -176,7 +295,21 @@ class Transaction
if ($res != 1) {
return false;
}
$db->run("UPDATE accounts SET balance=balance+:val WHERE id=:id", [":id" => $x['dst'], ":val" => $x['val']]);
if ($x['version'] == 2&&$height>=80000) {
$db->run("UPDATE accounts SET balance=balance+:val WHERE alias=:alias", [":alias" => $x['dst'], ":val" => $x['val']]);
} elseif ($x['version']==100&&$height>=80000) {
//master node deposit
} elseif ($x['version']==103&&$height>=80000) {
$blk=new Block();
$blk->masternode_log($x['public_key'], $height, $block);
//master node withdrawal
} else {
$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(
@@ -184,6 +317,36 @@ class Transaction
[":id" => $x['src'], ":val" => $x['val'], ":fee" => $x['fee']]
);
}
// set the alias
if ($x['version']==3&&$height>=80000) {
$db->run(
"UPDATE accounts SET alias=:alias WHERE id=:id",
[":id" => $x['src'], ":alias"=>$x['message']]
);
}
if ($x['version']>=100&&$x['version']<110&&$height>=80000) {
$message=$x['message'];
$message=preg_replace("/[^0-9\.]/", "", $message);
if ($x['version']==100) {
$db->run("INSERT into masternode SET `public_key`=:public_key, `height`=:height, `ip`=:ip, `status`=1", [":public_key"=>$x['public_key'], ":height"=>$height, ":ip"=>$message]);
} else {
if ($x['version']==101) {
$db->run("UPDATE masternode SET status=0 WHERE public_key=:public_key", [':public_key'=>$x['public_key']]);
} elseif ($x['version']==102) {
$db->run("UPDATE masternode SET status=1 WHERE public_key=:public_key", [':public_key'=>$x['public_key']]);
} elseif ($x['version']==103) {
$db->run("DELETE FROM masternode WHERE public_key=:public_key", [':public_key'=>$x['public_key']]);
$db->run("UPDATE accounts SET balance=balance+100000 WHERE public_key=:public_key", [':public_key'=>$x['public_key']]);
}
}
}
$db->run("DELETE FROM mempool WHERE id=:id", [":id" => $x['id']]);
return true;
}
@@ -208,15 +371,25 @@ class Transaction
$acc = new Account();
$info = $x['val']."-".$x['fee']."-".$x['dst']."-".$x['message']."-".$x['version']."-".$x['public_key']."-".$x['date'];
// hard fork at 80000 to implement alias, new mining system, assets
// if($x['version']>1 && $height<80000){
// return false;
// }
// internal transactions
if ($x['version']>110) {
return false;
}
// the value must be >=0
if ($x['val'] < 0) {
_log("$x[id] - Value below 0");
_log("$x[id] - Value below 0", 3);
return false;
}
// the fee must be >=0
if ($x['fee'] < 0) {
_log("$x[id] - Fee below 0");
_log("$x[id] - Fee below 0", 3);
return false;
}
@@ -226,46 +399,120 @@ class Transaction
if ($fee < 0.00000001) {
$fee = 0.00000001;
}
//alias fee
if ($x['version']==3&&$height>=80000) {
$fee=10;
if (!$acc->free_alias($x['message'])) {
_log("Alias not free", 3);
return false;
}
// alias can only be set once per account
if ($acc->has_alias($x['public_key'])) {
_log("The account already has an alias", 3);
return false;
}
}
//masternode transactions
if ($x['version']>=100&&$x['version']<110&&$height>=80000) {
if ($x['version']==100) {
$message=$x['message'];
$message=preg_replace("/[^0-9\.]/", "", $message);
if (!filter_var($message, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
_log("The Masternode IP is invalid", 3);
return false;
}
global $db;
$existing=$db->single("SELECT COUNT(1) FROM masternode WHERE public_key=:id or ip=:ip", ["id"=>$x['public_key'], ":ip"=>$message]);
if ($existing!=0) {
return false;
}
}
if ($x['version']==100&&$x['val']!=100000) {
_log("The masternode transaction is not 100k", 3);
return false;
} elseif ($x['version']!=100) {
$mn=$acc->get_masternode($x['public_key']);
if (!$mn) {
_log("The masternode does not exist", 3);
return false;
}
if ($x['version']==101&&$mn['status']!=1) {
_log("The masternode does is not running", 3);
return false;
} elseif ($x['version']==102 && $mn['status']!=0) {
_log("The masternode is not paused", 3);
return false;
} elseif ($x['version']==103) {
if ($mn['status']!=0) {
_log("The masternode is not paused", 3);
return false;
} elseif ($height-$mn['last_won']<10800) { //10800
_log("The masternode last won block is less than 10800 blocks", 3);
return false;
} elseif ($height-$mn['height']<32400) { //32400
_log("The masternode start height is less than 32400 blocks! $height - $mn[height]", 3);
return false;
}
}
}
}
// 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%");
_log("$x[id] - Fee not 0.25%", 3);
_log(json_encode($x), 3);
return false;
}
// invalid destination address
if (!$acc->valid($x['dst'])) {
_log("$x[id] - Invalid destination address");
return false;
if ($x['version']==1) {
// invalid destination address
if (!$acc->valid($x['dst'])) {
_log("$x[id] - Invalid destination address", 3);
return false;
}
} elseif ($x['version']==2&&$height>=80000) {
if (!$acc->valid_alias($x['dst'])) {
_log("$x[id] - Invalid destination alias", 3);
return false;
}
}
// reward transactions are not added via this function
if ($x['version'] < 1) {
_log("$x[id] - Invalid version <1");
_log("$x[id] - Invalid version <1", 3);
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");
_log("$x[id] - Invalid public key size", 3);
return false;
}
// no transactions before the genesis
if ($x['date'] < 1511725068) {
_log("$x[id] - Date before genesis");
_log("$x[id] - Date before genesis", 3);
return false;
}
// no future transactions
if ($x['date'] > time() + 86400) {
_log("$x[id] - Date in the future");
_log("$x[id] - Date in the future", 3);
return false;
}
// prevent the resending of broken base58 transactions
if ($height > 16900 && $x['date'] < 1519327780) {
_log("$x[id] - Broken base58 transaction", 3);
return false;
}
$id = $this->hash($x);
@@ -284,7 +531,7 @@ class Transaction
//verify the ecdsa signature
if (!$acc->check_signature($info, $x['signature'], $x['public_key'])) {
_log("$x[id] - Invalid signature");
_log("$x[id] - Invalid signature - $info");
return false;
}
@@ -295,6 +542,7 @@ class 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;
@@ -339,7 +587,7 @@ class Transaction
if ($x['version'] == 0) {
$trans['type'] = "mining";
} elseif ($x['version'] == 1) {
} elseif ($x['version'] == 1 || $x['version'] == 2) {
if ($x['dst'] == $id) {
$trans['type'] = "credit";
} else {
@@ -371,6 +619,9 @@ class Transaction
}
$res = [];
foreach ($r as $x) {
if ($x['version']>110) {
continue; //internal transactions
}
$trans = [
"block" => $x['block'],
"height" => $x['height'],
@@ -389,7 +640,7 @@ class Transaction
if ($x['version'] == 0) {
$trans['type'] = "mining";
} elseif ($x['version'] == 1) {
} elseif ($x['version'] == 1||$x['version'] == 2) {
if ($x['dst'] == $id) {
$trans['type'] = "credit";
} else {

297
index.php
View File

@@ -24,9 +24,300 @@ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
*/
require_once("include/init.inc.php");
require_once __DIR__.'/include/init.inc.php';
$block = new Block();
$current = $block->current();
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Arionum Node</title>
<style>
.title:not(:last-child) {
margin-bottom: 1.5rem;
}
echo "<h3>Arionum Node</h3>";
echo "System check complete.<br><br> Current block: $current[height]";
body, h1, html {
margin: 0;
padding: 0;
}
h1 {
font-size: 100%;
font-weight: 400;
}
html {
box-sizing: border-box;
background-color: #fff;
font-size: 16px;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
min-width: 300px;
overflow-x: hidden;
overflow-y: scroll;
text-rendering: optimizeLegibility;
-webkit-text-size-adjust: 100%;
-moz-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
text-size-adjust: 100%;
}
*, ::after, ::before {
box-sizing: inherit;
}
section {
display: block;
}
body {
font-family: "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #4a4a4a;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
}
span {
font-style: inherit;
font-weight: inherit;
}
strong {
color: #363636;
font-weight: 700;
}
.container {
position: relative;
margin: 0 auto;
}
.field.is-grouped {
display: flex;
justify-content: flex-start;
}
.field.is-grouped > .control {
flex-shrink: 0;
}
.field.is-grouped > .control:not(:last-child) {
margin-bottom: 0;
margin-right: .75rem;
}
.field.is-grouped.is-grouped-multiline {
flex-wrap: wrap;
}
.field.is-grouped.is-grouped-multiline > .control:last-child, .field.is-grouped.is-grouped-multiline > .control:not(:last-child) {
margin-bottom: .75rem;
}
.field.is-grouped.is-grouped-multiline:last-child {
margin-bottom: -.75rem;
}
.control {
font-size: 1rem;
position: relative;
text-align: left;
}
.tags {
align-items: center;
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
}
.tags .tag {
margin-bottom: .5rem;
}
.tags .tag:not(:last-child) {
margin-right: .5rem;
}
.tags:last-child {
margin-bottom: -.5rem;
}
.tags.has-addons .tag {
margin-right: 0;
}
.tags.has-addons .tag:not(:first-child) {
border-bottom-left-radius: 0;
border-top-left-radius: 0;
}
.tags.has-addons .tag:not(:last-child) {
border-bottom-right-radius: 0;
border-top-right-radius: 0;
}
.tag:not(body) {
align-items: center;
background-color: #f5f5f5;
border-radius: 4px;
color: #4a4a4a;
display: inline-flex;
font-size: .75rem;
height: 2em;
justify-content: center;
line-height: 1.5;
padding-left: .75em;
padding-right: .75em;
white-space: nowrap;
}
.tag:not(body).is-light {
background-color: #f5f5f5;
color: #363636;
}
.tag:not(body).is-info {
background-color: #209cee;
color: #fff;
}
.tag:not(body).is-danger {
background-color: #f48f42;
color: #fff;
}
.tag:not(body).is-success {
background-color: #23d160;
color: #fff;
}
.title {
word-break: break-word;
color: #363636;
font-size: 2rem;
font-weight: 600;
line-height: 1.125;
}
.hero {
align-items: stretch;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.hero.is-dark {
background-color: #363636;
color: #f5f5f5;
}
.hero.is-dark strong {
color: inherit;
}
.hero.is-dark .title {
color: #f5f5f5;
}
.hero.is-fullheight .hero-body {
align-items: center;
display: flex;
}
.hero.is-fullheight .hero-body > .container {
flex-grow: 1;
flex-shrink: 1;
}
.hero.is-fullheight {
min-height: 100vh;
}
.hero-body {
flex-grow: 1;
flex-shrink: 0;
padding: 3rem 1.5rem;
}
a {
color: #3273dc;
cursor: pointer;
text-decoration: none;
}
a:hover {
color: #363636;
}
a.is-dark {
color: #fff;
}
@media screen and (min-width: 1088px) {
.container {
max-width: 960px;
width: 960px;
}
}
@media screen and (min-width: 1280px) {
.container {
max-width: 1152px;
width: 1152px;
}
}
@media screen and (min-width: 1472px) {
.container {
max-width: 1344px;
width: 1344px;
}
}
</style>
</head>
<body>
<section class="hero is-dark is-fullheight">
<div class="hero-body">
<div class="container">
<h1 class="title">Arionum Node</h1>
<div class="field is-grouped is-grouped-multiline">
<div class="control">
<div class="tags has-addons">
<strong class="tag is-success">Current Block</strong>
<span class="tag is-light"><?= $current['height']; ?></span>
</div>
</div>
<div class="control">
<div class="tags has-addons">
<strong class="tag is-danger">Version</strong>
<span class="tag is-light"><?= VERSION; ?></span>
</div>
</div>
<div class="control">
<div class="tags has-addons">
<strong class="tag is-info">Public API</strong>
<span class="tag is-light"><?= ($_config['public_api']) ? 'yes' : 'no'; ?></span>
</div>
</div>
<div class="control">
<a class="tags is-dark" href="./doc/" target="_blank">
<strong class="tag is-info">Documentation</strong>
</a>
</div>
</div>
</div>
</div>
</section>
</body>
</html>

147
mine.php
View File

@@ -23,7 +23,7 @@ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
*/
require_once("include/init.inc.php");
require_once __DIR__.'/include/init.inc.php';
$block = new Block();
$acc = new Account();
set_time_limit(360);
@@ -45,11 +45,56 @@ if ($q == "info") {
$diff = $block->difficulty();
$current = $block->current();
$current_height=$current['height'];
$recommendation="mine";
$argon_mem=16384;
$argon_threads=4;
$argon_time=4;
if ($current_height<80000) {
if ($current_height > 10800) {
$argon_mem=524288;
$argon_threads=1;
$argon_time=1;
}
} elseif($current_height>=80458){
if($current_height%2==0){
$argon_mem=524288;
$argon_threads=1;
$argon_time=1;
} else {
$argon_mem=16384;
$argon_threads=4;
$argon_time=4;
}
} else {
if ($current_height%3==0) {
$argon_mem=524288;
$argon_threads=1;
$argon_time=1;
} elseif ($current_height%3==2) {
global $db;
$winner=$db->single(
"SELECT public_key FROM masternode WHERE status=1 AND blacklist<:current AND height<:start ORDER by last_won ASC, public_key ASC LIMIT 1",
[":current"=>$current_height, ":start"=>$current_height-360]
);
//$recommendation="pause";
if ($winner===false) {
$recommendation="mine";
}
}
}
$res = [
"difficulty" => $diff,
"block" => $current['id'],
"height" => $current['height'],
"testnet" => $_config['testnet'],
"recommendation"=> $recommendation,
"argon_mem" => $argon_mem,
"argon_threads" => $argon_threads,
"argon_time" => $argon_time,
];
api_echo($res);
exit;
@@ -73,11 +118,111 @@ if ($q == "info") {
if ($res) {
//if the new block is generated, propagate it to all peers in background
$current = $block->current();
$current['id']=escapeshellarg(san($current['id']));
system("php propagate.php block $current[id] > /dev/null 2>&1 &");
api_echo("accepted");
}
}
api_err("rejected");
} elseif ($q == "submitBlock") {
// 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']);
// check if the miner won the block
$result = $block->mine($public_key, $nonce, $argon);
if ($result) {
// generate the new block
$date = intval($_POST['date']);
if ($date <= $current['date']) {
api_err("rejected - date");
}
// get the mempool transactions
$txn = new Transaction();
$current = $block->current();
$height = $current['height'] += 1;
// get the mempool transactions
$txn = new Transaction();
$difficulty = $block->difficulty();
$acc = new Account();
$generator = $acc->get_address($public_key);
$data=json_decode($_POST['data'], true);
// sign the block
$signature = san($_POST['signature']);
// reward transaction and signature
$reward = $block->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 = san($_POST['reward_signature']);
// add the block to the blockchain
$res = $block->add(
$height,
$public_key,
$nonce,
$data,
$date,
$signature,
$difficulty,
$reward_signature,
$argon
);
if ($res) {
//if the new block is generated, propagate it to all peers in background
$current = $block->current();
$current['id']=escapeshellarg(san($current['id']));
system("php propagate.php block $current[id] > /dev/null 2>&1 &");
api_echo("accepted");
} else {
api_err("rejected - add");
}
}
api_err("rejected");
} elseif ($q == "getWork") {
if ($_config['sanity_sync'] == 1) {
api_err("sanity-sync");
}
$block = new Block();
$current = $block->current();
$height = $current['height'] += 1;
$date = time();
// get the mempool transactions
$txn = new Transaction();
$data = $txn->mempool($block->max_transactions());
$difficulty = $block->difficulty();
// always sort the transactions in the same way
ksort($data);
// reward transaction and signature
$reward = $block->reward($height, $data);
api_echo(["height"=>$height, "data"=>$data, "reward"=>$reward, "block"=>$current['id'], "difficulty"=>$difficulty]);
} else {
api_err("invalid command");
}

View File

@@ -23,7 +23,9 @@ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
*/
require_once("include/init.inc.php");
require_once __DIR__.'/include/init.inc.php';
header('Content-Type: application/json');
$trx = new Transaction();
$block = new Block();
$q = $_GET['q'];
@@ -44,6 +46,14 @@ if ($q == "peer") {
// sanitize the hostname
$hostname = filter_var($data['hostname'], FILTER_SANITIZE_URL);
$bad_peers = ["127.", "localhost", "10.", "192.168.","172.16.","172.17.","172.18.","172.19.","172.20.","172.21.","172.22.","172.23.","172.24.","172.25.","172.26.","172.27.","172.28.","172.29.","172.30.","172.31."];
$tpeer=str_replace(["https://","http://","//"], "", $hostname);
foreach ($bad_peers as $bp) {
if (strpos($tpeer, $bp)===0) {
api_err("invalid-hostname");
}
}
if (!filter_var($hostname, FILTER_VALIDATE_URL)) {
api_err("invalid-hostname");
}
@@ -142,6 +152,7 @@ if ($q == "peer") {
// 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']) {
$data['id']=escapeshellarg(san($data['id']));
system("php propagate.php transaction '$data[id]' > /dev/null 2>&1 &");
}
api_echo("transaction-ok");
@@ -166,20 +177,18 @@ if ($q == "peer") {
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]");
$ip=escapeshellarg($ip);
system("php sanity.php microsanity '$ip' > /dev/null 2>&1 &");
api_echo("microsanity");
} else {
@@ -195,7 +204,7 @@ if ($q == "peer") {
if (!$pr) {
api_err("block-too-old");
}
$peer_host = base58_encode($pr['hostname']);
$peer_host = escapeshellcmd(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]");
@@ -238,6 +247,7 @@ if ($q == "peer") {
_log('['.$ip."] block ok, repropagating - $data[height]");
// send it to all our peers
$data['id']=escapeshellcmd(san($data['id']));
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
@@ -247,7 +257,6 @@ elseif ($q == "currentBlock") {
} // return a specific block, used in syncing
elseif ($q == "getBlock") {
$height = intval($data['height']);
$export = $block->export("", $height);
if (!$export) {
api_err("invalid-block");

View File

@@ -24,7 +24,7 @@ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
*/
set_time_limit(360);
require_once("include/init.inc.php");
require_once __DIR__.'/include/init.inc.php';
$block = new Block();
$type = san($argv[1]);
@@ -67,10 +67,15 @@ if ((empty($peer) || $peer == 'all') && $type == "block") {
}
$r = $db->run("SELECT * FROM peers WHERE blacklisted < UNIX_TIMESTAMP() AND reserve=0 $ewhr");
foreach ($r as $x) {
if($x['hostname']==$_config['hostname']) continue;
// 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);
$host = escapeshellcmd(base58_encode($x['hostname']));
$ip = escapeshellcmd(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
$type=escapeshellcmd(san($type));
$id=escapeshellcmd(san($id));
if ($debug) {
system("php propagate.php '$type' '$id' '$host' '$ip' debug");
} elseif ($linear) {
@@ -105,6 +110,7 @@ if ($type == "block") {
// 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);
_log("Propagating block to $hostname - [result: $response] $data[height] - $data[id]",2);
if ($response == "block-ok") {
echo "Block $i accepted. Exiting.\n";
exit;
@@ -164,7 +170,8 @@ if ($type == "transaction") {
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']));
$r = $db->run("SELECT hostname FROM peers WHERE blacklisted < UNIX_TIMESTAMP() AND reserve=0 ORDER by RAND() LIMIT :limit",["limit"=>intval($_config['transaction_propagation_peers'])]);
}
foreach ($r as $x) {
$res = peer_post($x['hostname']."/peer.php?q=submitTransaction", $data);

View File

@@ -60,8 +60,12 @@ if ($arg != "microsanity") {
sleep(3);
}
require_once __DIR__.'/include/init.inc.php';
require_once("include/init.inc.php");
if ($argv[1]=="dev") {
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE);
ini_set("display_errors", "on");
}
// the sanity can't run without the schema being installed
if ($_config['dbversion'] < 2) {
@@ -70,10 +74,77 @@ if ($_config['dbversion'] < 2) {
exit;
}
ini_set('memory_limit', '2G');
$block = new Block();
$acc = new Account();
$current = $block->current();
// bootstrapping the initial sync
if ($current['height']==1) {
echo "Bootstrapping!\n";
$last=file_get_contents("http://dumps.arionum.com/last");
$last=intval($last);
$failed_sync=false;
for ($i=1000;$i<=$last;$i=$i+1000) {
echo "Download file $i\n";
$res=trim(file_get_contents("http://dumps.arionum.com/aro.db.$i"));
if ($res===false) {
echo "Could not download the bootstrap file $i. Syncing the old fashioned way.\n";
break;
}
$data=json_decode($res, true);
if ($data===false||is_null($data)) {
echo "Could not parse the bootstrap file $i. Syncing the old fashioned way.\n";
echo json_last_error_msg();
break;
}
foreach ($data as $x) {
if (count($x['data'])>0) {
$transactions=[];
foreach ($x['data'] as $d) {
$trans = [
"id" => $d[0],
"dst" => $d[1],
"val" => $d[2],
"fee" => $d[3],
"signature" => $d[4],
"message" => $d[5],
"version" => $d[6],
"date" => $d[7],
"public_key" => $d[8],
];
ksort($trans);
$transactions[$d[0]] = $trans;
}
ksort($transactions);
$x['data']=$transactions;
}
echo "-> Adding block $x[height]\n";
$res=$block->add($x['height'], $x['public_key'], $x['nonce'], $x['data'], $x['date'], $x['signature'], $x['difficulty'], $x['reward_signature'], $x['argon'], true);
if (!$res) {
echo "Error: Adding the block failed. Syncing the old way.\n";
$failed_sync=true;
break;
}
}
if ($failed_sync) {
break;
}
}
$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)) {
@@ -104,19 +175,16 @@ if ($arg == "microsanity" && !empty($arg2)) {
}
// 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;
}
// 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']);
@@ -126,7 +194,8 @@ if ($arg == "microsanity" && !empty($arg2)) {
$data['argon'],
$block->difficulty($current['height'] - 1),
$prev['id'],
$prev['height']
$prev['height'],
$data['date']
)) {
echo "Invalid prev-block\n";
break;
@@ -142,6 +211,8 @@ if ($arg == "microsanity" && !empty($arg2)) {
// add the new block
echo "Starting to sync last block from $x[hostname]\n";
$b = $data;
$res = $block->add(
$b['height'],
$b['public_key'],
@@ -186,8 +257,8 @@ $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 blacklisted<UNIX_TIMESTAMP()");
$db->run("DELETE from peers WHERE fails>100 OR stuckfail>100");
$r = $db->run("SELECT id,hostname,stuckfail,fails FROM peers WHERE reserve=0 AND blacklisted<UNIX_TIMESTAMP() LIMIT 50");
$total_peers = count($r);
@@ -195,21 +266,30 @@ $peered = [];
// if we have no peers, get the seed list from the official site
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 not connect to arionum.com! Will try later!\n");
echo 'No peers found. Attempting to get peers from the initial list.'.PHP_EOL;
$initialPeers = new \Arionum\Node\InitialPeers($_config['initial_peer_list'] ?? []);
try {
$peers = $initialPeers->getAll();
} catch (\Arionum\Node\Exception $e) {
@unlink('tmp/sanity-lock');
die($e->getMessage().PHP_EOL);
}
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.
foreach ($peers 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;
$bad_peers = ["127.", "localhost", "10.", "192.168.","172.16.","172.17.","172.18.","172.19.","172.20.","172.21.","172.22.","172.23.","172.24.","172.25.","172.26.","172.27.","172.28.","172.29.","172.30.","172.31."];
$tpeer=str_replace(["https://","http://","//"], "", $peer);
foreach ($bad_peers as $bp) {
if (strpos($tpeer, $bp)===0) {
continue;
}
}
$peer = filter_var($peer, FILTER_SANITIZE_URL);
if (!filter_var($peer, FILTER_VALIDATE_URL)) {
continue;
@@ -221,7 +301,15 @@ if ($total_peers == 0 && $_config['testnet'] == false) {
continue;
}
$peered[$pid] = 1;
$res = peer_post($peer."/peer.php?q=peer", ["hostname" => $_config['hostname'], "repeer" => 1]);
if($_config['passive_peering'] == true){
// does not peer, just add it to DB in passive mode
$db->run("INSERT into peers set hostname=:hostname, ping=0, reserve=0,ip=:ip",[":hostname"=>$peer, ":ip"=>md5($peer)]);
$res=true;
} else {
// forces the other node to peer with us.
$res = peer_post($peer."/peer.php?q=peer", ["hostname" => $_config['hostname'], "repeer" => 1]);
}
if ($res !== false) {
$i++;
echo "Peering OK - $peer\n";
@@ -243,66 +331,69 @@ if ($total_peers == 0 && $_config['testnet'] == false) {
}
// contact all the active peers
$i = 0;
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(
if ($_config['get_more_peers']==true && $_config['passive_peering']!=true) {
$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(
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.", "localhost", "10.", "192.168.","172.16.","172.17.","172.18.","172.19.","172.20.","172.21.","172.22.","172.23.","172.24.","172.25.","172.26.","172.27.","172.28.","172.29.","172.30.","172.31."];
$tpeer=str_replace(["https://","http://","//"], "", $peer['hostname']);
foreach ($bad_peers as $bp) {
if (strpos($tpeer, $bp)===0) {
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";
$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) {
@@ -350,18 +441,14 @@ foreach ($r as $x) {
$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']) {
// 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 ($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']) {
@@ -375,7 +462,7 @@ echo "Most common: $most_common\n";
echo "Most common block: $most_common_size\n";
echo "Max height: $largest_height\n";
echo "Current block: $current[height]\n";
$block_parse_failed=false;
// if we're not on the largest height
if ($current['height'] < $largest_height && $largest_height > 1) {
// start sanity sync / block all other transactions/blocks
@@ -409,17 +496,17 @@ if ($current['height'] < $largest_height && $largest_height > 1) {
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
//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-30 until current
$invalid = false;
$last_good = $current['height'];
for ($i = $current['height'] - 10; $i < $current['height']; $i++) {
for ($i = $current['height'] - 30; $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']) {
if ($i == $current['height'] - 30 && $ext['id'] != $data['id']) {
$invalid = true;
break;
}
@@ -428,6 +515,9 @@ if ($current['height'] < $largest_height && $largest_height > 1) {
$last_good = $i;
}
}
if ($last_good==$current['height']-1) {
$block->pop(1);
}
// if last 10 blocks are good, verify all the blocks
if ($invalid == false) {
$cblock = [];
@@ -441,13 +531,17 @@ if ($current['height'] < $largest_height && $largest_height > 1) {
}
// check if the block mining data is correct
for ($i = $last_good + 1; $i <= $largest_height; $i++) {
if (($i-1)%3==2&&$cblock[$i - 1]['height']<80458) {
continue;
}
if (!$block->mine(
$cblock[$i]['public_key'],
$cblock[$i]['nonce'],
$cblock[$i]['argon'],
$cblock[$i]['difficulty'],
$cblock[$i - 1]['id'],
$cblock[$i - 1]['height']
$cblock[$i - 1]['height'],
$cblock[$i]['date']
)) {
$invalid = true;
break;
@@ -456,6 +550,7 @@ if ($current['height'] < $largest_height && $largest_height > 1) {
}
// if the blockchain proves ok, delete until the last block
if ($invalid == false) {
_log("Changing fork, deleting $last_good", 1);
$block->delete($last_good);
$current = $block->current();
$data = $current;
@@ -477,8 +572,9 @@ if ($current['height'] < $largest_height && $largest_height > 1) {
foreach ($data as $b) {
$b['id'] = san($b['id']);
$b['height'] = san($b['height']);
if (!$block->check($b)) {
$block_parse_failed=true;
_log("Block check: could not add block - $b[id] - $b[height]");
$good_peer = false;
break;
@@ -495,6 +591,7 @@ if ($current['height'] < $largest_height && $largest_height > 1) {
$b['argon']
);
if (!$res) {
$block_parse_failed=true;
_log("Block add: could not add block - $b[id] - $b[height]");
$good_peer = false;
break;
@@ -511,6 +608,70 @@ if ($current['height'] < $largest_height && $largest_height > 1) {
break;
}
}
$resyncing=false;
if ($block_parse_failed==true&&$current['date']<time()-(3600*24)) {
_log("Rechecking reward transactions");
$current = $block->current();
$rwpb=$db->single("SELECT COUNT(1) FROM transactions WHERE version=0 AND message=''");
if ($rwpb!=$current['height']) {
$failed=$db->single("SELECT blocks.height FROM blocks LEFT JOIN transactions ON transactions.block=blocks.id and transactions.version=0 and transactions.message='' WHERE transactions.height is NULL ORDER by blocks.height ASC LIMIT 1");
if ($failed>1) {
_log("Found failed block - $faield");
$block->delete($failed);
$block_parse_failed==false;
}
}
}
if ($block_parse_failed==true||$argv[1]=="resync") {
$last_resync=$db->single("SELECT val FROM config WHERE cfg='last_resync'");
if ($last_resync<time()-(3600*24)||$argv[1]=="resync") {
if ($current['date']<time()-(3600*72)||$argv[1]=="resync") {
$to_remove=3000;
if (intval($argv[2])>0) {
$to_remove=intval($argv[2]);
}
_log("Removing $to_remove blocks, the blockchain is stale.");
$block->pop(to_remove);
$resyncing=true;
} elseif ($current['date']<time()-(3600*24)) {
_log("Removing 200 blocks, the blockchain is stale.");
$block->pop(200);
$resyncing=true;
}
if ($resyncing==true) {
_log("Resyncing accounts");
$db->run("INSERT into config SET val=UNIX_TIMESTAMP(), cfg='last_resync' ON DUPLICATE KEY UPDATE val=UNIX_TIMESTAMP()");
$db->exec("LOCK TABLES blocks WRITE, accounts WRITE, transactions WRITE, mempool WRITE");
$r=$db->run("SELECT * FROM accounts");
foreach ($r as $x) {
$alias=$x['alias'];
if (empty($alias)) {
$alias="A";
}
$rec=$db->single("SELECT SUM(val) FROM transactions WHERE (dst=:id or dst=:alias) AND (height<80000 OR (version!=100 AND version!=103)) and version<111", [":id"=>$x['id'], ":alias"=>$alias]);
$spent=$db->single("SELECT SUM(val+fee) FROM transactions WHERE public_key=:pub AND version>0", [":pub"=>$x['public_key']]);
if ($spent==false) {
$spent=0;
}
$balance=round(($rec-$spent), 8);
if ($x['balance']!=$balance) {
// echo "rec: $rec, spent: $spent, bal: $x[balance], should be: $balance - $x[id] $x[public_key]\n";
if (trim($argv[2])!="check") {
$db->run("UPDATE accounts SET balance=:bal WHERE id=:id", [":id"=>$x['id'], ":bal"=>$balance]);
}
}
}
$current = $block->current();
$db->run("DELETE FROM masternode WHERE height>:h", [":h"=>$current['height']]);
$db->exec("UNLOCK TABLES");
}
}
}
$db->run("UPDATE config SET val=0 WHERE cfg='sanity_sync'", [":time" => $t]);
}
@@ -520,14 +681,14 @@ $db->run("DELETE FROM `mempool` WHERE `date` < UNIX_TIMESTAMP()-(3600*24*14)");
//rebroadcasting local transactions
if ($_config['sanity_rebroadcast_locals'] == true) {
if ($_config['sanity_rebroadcast_locals'] == true && $_config['disable_repropagation'] == false) {
$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']);
$x['id'] = escapeshellarg(san($x['id'])); // i know it's redundant due to san(), but some people are too scared of any exec
system("php propagate.php transaction $x[id] > /dev/null 2>&1 &");
$db->run(
"UPDATE mempool SET height=:current WHERE id=:id",
@@ -537,21 +698,22 @@ if ($_config['sanity_rebroadcast_locals'] == true) {
}
//rebroadcasting transactions
$forgotten = $current['height'] - $_config['sanity_rebroadcast_height'];
$r = $db->run(
if ($_config['disable_repropagation'] == false) {
$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));
_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", [":id" => $x['id'], ":current" => $current['height']]);
foreach ($r as $x) {
$x['id'] = escapeshellarg(san($x['id'])); // i know it's redundant due to san(), but some people are too scared of any exec
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;
@@ -559,7 +721,7 @@ if ($total_peers < $_config['max_peers'] * 0.7) {
}
//random peer check
$r = $db->run("SELECT * FROM peers WHERE blacklisted<UNIX_TIMESTAMP() and reserve=1 LIMIT ".$_config['max_test_peers']);
$r = $db->run("SELECT * FROM peers WHERE blacklisted<UNIX_TIMESTAMP() and reserve=1 LIMIT :limit", [":limit"=>intval($_config['max_test_peers'])]);
foreach ($r as $x) {
$url = $x['hostname']."/peer.php?q=";
$data = peer_post($url."ping", [], 5);
@@ -591,7 +753,7 @@ foreach ($f as $x) {
//recheck the last blocks
if ($_config['sanity_recheck_blocks'] > 0) {
if ($_config['sanity_recheck_blocks'] > 0 && $_config['testnet'] == false) {
_log("Rechecking blocks");
$blocks = [];
$all_blocks_ok = true;
@@ -616,7 +778,8 @@ if ($_config['sanity_recheck_blocks'] > 0) {
$data['argon'],
$data['difficulty'],
$blocks[$i - 1]['id'],
$blocks[$i - 1]['height']
$blocks[$i - 1]['height'],
$data['date']
)) {
$db->run("UPDATE config SET val=1 WHERE cfg='sanity_sync'");
_log("Invalid block detected. Deleting everything after $data[height] - $data[id]");

View File

@@ -0,0 +1 @@
4.0.2

178
util.php
View File

@@ -29,7 +29,7 @@ if (php_sapi_name() !== 'cli') {
die("This should only be run as cli");
}
require_once("include/init.inc.php");
require_once __DIR__.'/include/init.inc.php';
$cmd = trim($argv[1]);
/**
@@ -43,12 +43,19 @@ $cmd = trim($argv[1]);
*/
if ($cmd == 'clean') {
$tables = ["blocks", "accounts", "transactions", "mempool"];
foreach ($tables as $table) {
$db->run("DELETE FROM {$table}");
if (file_exists("tmp/sanity-lock")) {
die("Sanity running. Wait for it to finish");
}
touch("tmp/sanity-lock");
$db->run("SET foreign_key_checks=0;");
$tables = ["accounts", "transactions", "mempool", "masternode","blocks"];
foreach ($tables as $table) {
$db->run("TRUNCATE TABLE {$table}");
}
$db->run("SET foreign_key_checks=1;");
echo "\n The database has been cleared\n";
unlink("tmp/sanity-lock");
} /**
* @api {php util.php} pop Pop
* @apiName pop
@@ -62,9 +69,14 @@ if ($cmd == 'clean') {
*/
elseif ($cmd == 'pop') {
if (file_exists("tmp/sanity-lock")) {
die("Sanity running. Wait for it to finish");
}
touch("tmp/sanity-lock");
$no = intval($argv[2]);
$block = new Block();
$block->pop($no);
unlink("tmp/sanity-lock");
} /**
* @api {php util.php} block-time Block-time
* @apiName block-time
@@ -91,7 +103,7 @@ elseif ($cmd == 'block-time') {
}
$time = $t - $x['date'];
$t = $x['date'];
echo "$x[height] -> $time\n";
echo "$x[height]\t\t$time\t\t$x[difficulty]\n";
$end = $x['date'];
}
echo "Average block time: ".ceil(($start - $end) / 100)." seconds\n";
@@ -181,7 +193,7 @@ elseif ($cmd == "blocks") {
if ($limit < 1) {
$limit = 100;
}
$r = $db->run("SELECT * FROM blocks WHERE height>:height ORDER by height ASC LIMIT $limit", [":height" => $height]);
$r = $db->run("SELECT * FROM blocks WHERE height>:height ORDER by height ASC LIMIT :limit", [":height" => $height, ":limit"=>$limit]);
foreach ($r as $x) {
echo "$x[height]\t$x[id]\n";
}
@@ -214,7 +226,8 @@ elseif ($cmd == "recheck-blocks") {
$data['argon'],
$data['difficulty'],
$blocks[$i - 1]['id'],
$blocks[$i - 1]['height']
$blocks[$i - 1]['height'],
$data['date']
)) {
_log("Invalid block detected. We should delete everything after $data[height] - $data[id]");
break;
@@ -408,7 +421,8 @@ elseif ($cmd == "check-address") {
}
echo "The address is valid\n";
} /**
}
/**
* @api {php util.php} get-address Get-Address
* @apiName get-address
* @apiGroup UTIL
@@ -429,6 +443,154 @@ elseif ($cmd == 'get-address') {
die("Invalid public key");
}
print($acc->get_address($public_key));
/**
* @api {php util.php} clean-blacklist Clean-Blacklist
* @apiName clean-blacklist
* @apiGroup UTIL
* @apiDescription Removes all the peers from blacklist
*
* @apiExample {cli} Example usage:
* php util.php clean-blacklist
*
*/
} elseif ($cmd == 'clean-blacklist') {
$db->run("UPDATE peers SET blacklisted=0, fails=0, stuckfail=0");
echo "All the peers have been removed from the blacklist\n";
} elseif ($cmd == 'resync-accounts') {
// resyncs the balance on all accounts
if (file_exists("tmp/sanity-lock")) {
die("Sanity running. Wait for it to finish");
}
touch("tmp/sanity-lock");
// lock table to avoid race conditions on blocks
$db->exec("LOCK TABLES blocks WRITE, accounts WRITE, transactions WRITE, mempool WRITE");
$r=$db->run("SELECT * FROM accounts");
foreach ($r as $x) {
$alias=$x['alias'];
if (empty($alias)) {
$alias="A";
}
$rec=$db->single("SELECT SUM(val) FROM transactions WHERE (dst=:id or dst=:alias) AND (height<80000 OR (version!=100 AND version!=103)) and version<111", [":id"=>$x['id'], ":alias"=>$alias]);
$spent=$db->single("SELECT SUM(val+fee) FROM transactions WHERE public_key=:pub AND version>0", [":pub"=>$x['public_key']]);
if ($spent==false) {
$spent=0;
}
$balance=round(($rec-$spent), 8);
if ($x['balance']!=$balance) {
echo "rec: $rec, spent: $spent, bal: $x[balance], should be: $balance - $x[id] $x[public_key]\n";
if (trim($argv[2])!="check") {
$db->run("UPDATE accounts SET balance=:bal WHERE id=:id", [":id"=>$x['id'], ":bal"=>$balance]);
}
}
}
$db->exec("UNLOCK TABLES");
echo "All done";
unlink("tmp/sanity-lock");
} elseif ($cmd=="compare-blocks") {
$block=new Block();
$current=$block->current();
$peer=trim($argv[2]);
$limit=intval($argv[3]);
if ($limit==0) {
$limit=5000;
}
for ($i=$current['height']-$limit;$i<=$current['height'];$i++) {
$data=peer_post($peer."/peer.php?q=getBlock", ["height" => $i]);
if ($data==false) {
continue;
}
$our=$block->export(false, $i);
if ($data!=$our) {
echo "Failed block -> $i\n";
if ($argv[4]=="dump") {
echo "\n\n ---- Internal ----\n\n";
var_dump($our);
echo "\n\n ---- External ----\n\n";
var_dump($data);
}
}
}
} elseif ($cmd=='compare-accounts') {
$peer=trim($argv[2]);
$r=$db->run("SELECT id,balance FROM accounts");
foreach ($r as $x) {
$data=peer_post($peer."/api.php?q=getBalance", ["account" => $x['id']]);
if ($data==false) {
continue;
}
if ($data!=$x['balance']) {
echo "$x[id]\t\t$x[balance]\t$data\n";
}
}
} elseif ($cmd=='masternode-hash') {
$res=$db->run("SELECT * FROM masternode ORDER by public_key ASC");
$block=new Block();
$current=$block->current();
echo "Height:\t\t$current[height]\n";
echo "Hash:\t\t".md5(json_encode($res))."\n\n";
} elseif ($cmd=='accounts-hash') {
$res=$db->run("SELECT * FROM accounts ORDER by id ASC");
$block=new Block();
$current=$block->current();
echo "Height:\t\t$current[height]\n";
echo "Hash:\t\t".md5(json_encode($res))."\n\n";
} elseif ($cmd == "version") {
echo "\n\n".VERSION."\n\n";
} elseif ($cmd == "sendblock"){
$peer=trim($argv[3]);
if (!filter_var($peer, FILTER_VALIDATE_URL)) {
die("Invalid peer hostname");
}
$peer = filter_var($peer, FILTER_SANITIZE_URL);
$height=intval($argv[2]);
$block=new Block();
$data = $block->export("", $height);
if($data===false){
die("Could not find this block");
}
$response = peer_post($peer."/peer.php?q=submitBlock", $data, 60, true);
var_dump($response);
}elseif ($cmd == "recheck-external-blocks") {
$peer=trim($argv[2]);
if (!filter_var($peer, FILTER_VALIDATE_URL)) {
die("Invalid peer hostname");
}
$peer = filter_var($peer, FILTER_SANITIZE_URL);
$blocks = [];
$block = new Block();
$height=intval($argv[3]);
$last=peer_post($peer."/peer.php?q=currentBlock");
$b=peer_post($peer."/peer.php?q=getBlock",["height"=>$height]);
for ($i = $height+1; $i <= $last['height']; $i++) {
$c=peer_post($peer."/peer.php?q=getBlock",["height"=>$i]);
if (!$block->mine(
$c['public_key'],
$c['nonce'],
$c['argon'],
$c['difficulty'],
$b['id'],
$b['height'],
$c['date']
)) {
print("Invalid block detected. $c[height] - $c[id]\n");
break;
}
echo "Block $i -> ok\n";
$b=$c;
}
} else {
echo "Invalid command\n";
}