├── logs
└── .gitkeep
├── pids
└── .gitkeep
├── tasks
├── .gitkeep
├── .gitignore
└── newMigration.js
├── sql
├── migrations
│ ├── .gitkeep
│ ├── 20180814120000_add_unique_constraint_to_votes.sql
│ ├── 20180814130000_add_unique_constraint_to_delegates.sql
│ ├── 20160908215531_protectMemAccountsColumns.sql
│ ├── 20160724114255_createMemoryTables.sql
│ ├── 20171123154400_addIndexOnVote.sql
│ ├── 20160723182901_createViews.sql
│ └── 20160723182900_createSchema.sql
├── runtime.sql
├── nodeManager.js
├── multisignatures.js
├── transport.js
├── delegates.js
├── loader.js
├── peers.js
├── transactions.js
├── rounds.js
└── blocks.js
├── .jshintignore
├── test
├── mocha.opts
├── genesisPassphrase.testnet.json
├── genesisPassphrase.json
├── api
│ ├── loader.js
│ ├── peer.js
│ ├── peer.signatures.js
│ ├── peers.js
│ ├── peer.transactions.stress.js
│ ├── peer.transactions.signatures.js
│ ├── signatures.js
│ ├── peer.transactions.delegates.js
│ └── accounts.js
├── index.js
├── unit
│ └── helpers
│ │ └── request-limiter.js
└── config.json
├── helpers
├── milestoneBlocks.js
├── validator
│ ├── package.json
│ ├── utils.js
│ └── field.js
├── json-schema
│ ├── package.json
│ └── field.js
├── transactionTypes.js
├── exceptions.js
├── z_schema-express.js
├── crypto.js
├── diff.js
├── router.js
├── inserts.js
├── request-limiter.js
├── constants.js
├── checkIpInList.js
├── sequence.js
├── slots.js
├── orderBy.js
├── z_schema.js
└── database.js
├── docs
├── public
│ ├── images
│ │ └── gray.png
│ └── fonts
│ │ ├── aller-bold.eot
│ │ ├── aller-bold.ttf
│ │ ├── fleurons.eot
│ │ ├── fleurons.ttf
│ │ ├── fleurons.woff
│ │ ├── aller-bold.woff
│ │ ├── aller-light.eot
│ │ ├── aller-light.ttf
│ │ ├── aller-light.woff
│ │ ├── roboto-black.eot
│ │ ├── roboto-black.ttf
│ │ └── roboto-black.woff
└── template
│ └── arkio.jst
├── .gitmodules
├── schema
├── nodeManager.js
├── api.public.js
├── signatures.js
├── blocks.js
├── loader.js
├── multisignatures.js
├── transport.js
├── accounts.js
├── api.peer.js
├── peers.js
├── delegates.js
└── transactions.js
├── .gitignore
├── .jshintrc
├── optional
└── example.js
├── networks.json
├── modules
├── system.js
└── signatures.js
├── config.json
├── config.devnet.json
├── config.main.json
├── logger.js
├── package.json
├── logic
├── blockReward.js
├── transfer.js
├── signature.js
├── vote.js
└── delegate.js
├── config.mainnet.json
├── Gruntfile.js
├── views
└── example.pug
├── config.testnet.json
├── Vagrantfile
└── README.md
/logs/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pids/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tasks/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/sql/migrations/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.jshintignore:
--------------------------------------------------------------------------------
1 | helpers/bignum.js
2 |
--------------------------------------------------------------------------------
/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --timeout 250s
2 |
--------------------------------------------------------------------------------
/tasks/.gitignore:
--------------------------------------------------------------------------------
1 | *.json
2 | accounts.js
3 |
--------------------------------------------------------------------------------
/helpers/milestoneBlocks.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | };
5 |
--------------------------------------------------------------------------------
/docs/public/images/gray.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArkEcosystemArchive/ark-node/HEAD/docs/public/images/gray.png
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "test/ark-js"]
2 | path = test/ark-js
3 | url = https://github.com/arkecosystem/ark-js.git
4 |
--------------------------------------------------------------------------------
/helpers/validator/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "validator",
3 | "version": "0.0.1",
4 | "main":"validator.js"
5 | }
6 |
--------------------------------------------------------------------------------
/docs/public/fonts/aller-bold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArkEcosystemArchive/ark-node/HEAD/docs/public/fonts/aller-bold.eot
--------------------------------------------------------------------------------
/docs/public/fonts/aller-bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArkEcosystemArchive/ark-node/HEAD/docs/public/fonts/aller-bold.ttf
--------------------------------------------------------------------------------
/docs/public/fonts/fleurons.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArkEcosystemArchive/ark-node/HEAD/docs/public/fonts/fleurons.eot
--------------------------------------------------------------------------------
/docs/public/fonts/fleurons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArkEcosystemArchive/ark-node/HEAD/docs/public/fonts/fleurons.ttf
--------------------------------------------------------------------------------
/docs/public/fonts/fleurons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArkEcosystemArchive/ark-node/HEAD/docs/public/fonts/fleurons.woff
--------------------------------------------------------------------------------
/helpers/json-schema/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "json-schema",
3 | "version": "0.0.1",
4 | "main":"validator.js"
5 | }
6 |
--------------------------------------------------------------------------------
/docs/public/fonts/aller-bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArkEcosystemArchive/ark-node/HEAD/docs/public/fonts/aller-bold.woff
--------------------------------------------------------------------------------
/docs/public/fonts/aller-light.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArkEcosystemArchive/ark-node/HEAD/docs/public/fonts/aller-light.eot
--------------------------------------------------------------------------------
/docs/public/fonts/aller-light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArkEcosystemArchive/ark-node/HEAD/docs/public/fonts/aller-light.ttf
--------------------------------------------------------------------------------
/docs/public/fonts/aller-light.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArkEcosystemArchive/ark-node/HEAD/docs/public/fonts/aller-light.woff
--------------------------------------------------------------------------------
/docs/public/fonts/roboto-black.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArkEcosystemArchive/ark-node/HEAD/docs/public/fonts/roboto-black.eot
--------------------------------------------------------------------------------
/docs/public/fonts/roboto-black.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArkEcosystemArchive/ark-node/HEAD/docs/public/fonts/roboto-black.ttf
--------------------------------------------------------------------------------
/docs/public/fonts/roboto-black.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArkEcosystemArchive/ark-node/HEAD/docs/public/fonts/roboto-black.woff
--------------------------------------------------------------------------------
/sql/runtime.sql:
--------------------------------------------------------------------------------
1 | /* Ark Runtime
2 | *
3 | */
4 |
5 | BEGIN;
6 |
7 | UPDATE "peers" SET "state" = 1, "clock" = NULL WHERE "state" != 0;
8 |
9 | COMMIT;
10 |
--------------------------------------------------------------------------------
/helpers/transactionTypes.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | SEND: 0,
5 | SIGNATURE: 1,
6 | DELEGATE: 2,
7 | VOTE: 3,
8 | MULTI: 4,
9 | IPFS: 5
10 | };
11 |
--------------------------------------------------------------------------------
/sql/migrations/20180814120000_add_unique_constraint_to_votes.sql:
--------------------------------------------------------------------------------
1 | BEGIN;
2 |
3 | CREATE UNIQUE INDEX IF NOT EXISTS "votes_uniq" ON votes ("votes", "transactionId");
4 |
5 | COMMIT;
6 |
--------------------------------------------------------------------------------
/sql/nodeManager.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var NodeManagerSql = {
4 | getTransactionId: 'SELECT "id" FROM transactions WHERE "id" = ${id}'
5 | };
6 |
7 | module.exports = NodeManagerSql;
8 |
--------------------------------------------------------------------------------
/schema/nodeManager.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | transactions: {
5 | id: 'nodeManager.transactions',
6 | type: 'array',
7 | uniqueItems: true,
8 | required: ['transactions']
9 | }
10 | };
11 |
--------------------------------------------------------------------------------
/sql/multisignatures.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var MultisignaturesSql = {
4 | getAccounts: 'SELECT ARRAY_AGG("accountId") AS "accountId" FROM mem_accounts2multisignatures WHERE "dependentId" = ${publicKey}'
5 | };
6 |
7 | module.exports = MultisignaturesSql;
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .ed25519-node
3 | .idea/
4 | .project
5 | __MACOSX/
6 | logs/*.log
7 | node_modules
8 | nodejs/
9 | npm-debug.log
10 | release
11 | ssl/
12 | stacktrace*
13 | tmp
14 | .tags
15 | config.main.json
16 | genesisBlock.main.json
17 | .vagrant
18 | private/
19 |
--------------------------------------------------------------------------------
/sql/migrations/20180814130000_add_unique_constraint_to_delegates.sql:
--------------------------------------------------------------------------------
1 | BEGIN;
2 |
3 | CREATE UNIQUE INDEX IF NOT EXISTS "delegates_username_uniq" ON delegates ("username");
4 | CREATE UNIQUE INDEX IF NOT EXISTS "delegates_transactionId_uniq" ON delegates ("transactionId");
5 |
6 | COMMIT;
7 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "curly": true,
3 | "eqnull": true,
4 | "eqeqeq": true,
5 | "undef": true,
6 | "newcap" : false,
7 | "node": true,
8 | "quotmark": "single",
9 | "strict" : true,
10 | "globals": {
11 | "gc": true,
12 | "RequestSanitizer": true
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/test/genesisPassphrase.testnet.json:
--------------------------------------------------------------------------------
1 | {
2 | "passphrase": "aunt mesh almost hedgehog boss six joke harsh alert lunch gasp city",
3 | "balance": 12500000000000000,
4 | "publicKey": "028f984271e401d5846addf179ff0a9ec32d1f9da6703bca3b0473a67f7b98c5ec",
5 | "address": "aAgSd8PZYjLB64x1cg2WiiJD5QVT1KZkQy"
6 | }
--------------------------------------------------------------------------------
/test/genesisPassphrase.json:
--------------------------------------------------------------------------------
1 | {
2 | "passphrase": "peace vanish bleak box tuna woman rally manage undo royal lucky since",
3 | "balance": 12500000000000000,
4 | "publicKey": "03f0726b59f56ac009a8bd3f9623f681cdd5318dc4f5042b4938716c46b1b05e93",
5 | "address": "AYv4BfHZc5RRkCT6xz3iCw9BbTcK2Xo57m",
6 | "wif": "SAH57bsiNcG4Cw3F1GdnqgT5Et6iqM5uwASDWXUteZ8VkhghkNUp"
7 | }
--------------------------------------------------------------------------------
/schema/api.public.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | 'GET:/api/blocks/getHeight':{
5 | id: 'GET:/api/blocks/getHeight',
6 | type: 'object',
7 | properties: {
8 | success: {
9 | type: 'boolean'
10 | },
11 | height: {
12 | type: 'integer',
13 | minimum: 0
14 | }
15 | },
16 | required: ['success','height']
17 | }
18 | };
19 |
--------------------------------------------------------------------------------
/helpers/json-schema/field.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var util = require('util');
4 |
5 | module.exports = JsonSchemaField;
6 |
7 | var Field = require('../validator').prototype.Field;
8 |
9 | function JsonSchemaField (validator, path, value, rule, thisArg) {
10 | Field.call(this, validator, path, value, rule, thisArg);
11 | }
12 |
13 | util.inherits(JsonSchemaField, Field);
14 |
--------------------------------------------------------------------------------
/test/api/loader.js:
--------------------------------------------------------------------------------
1 | 'use strict'; /*jslint mocha:true, expr:true */
2 |
3 | var node = require('./../node.js');
4 |
5 | describe('GET /api/loader/status/ping', function () {
6 |
7 | it('should be ok', function (done) {
8 | node.get('/api/loader/status/ping', function (err, res) {
9 | node.expect(res.body).to.have.property('success').to.be.ok;
10 | done();
11 | });
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/helpers/exceptions.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | senderPublicKey: [
5 | ],
6 | signatures: [
7 | ],
8 | votes: [
9 | ],
10 | balance:[
11 | "608c7aeba0895da4517496590896eb325a0b5d367e1b186b1c07d7651a568b9e"
12 | ],
13 | blocks: [
14 | "2220204643144799909",
15 | "12932134768278785054",
16 | "5100737560399219173",
17 | "9919055269822630432"
18 | ]
19 | };
20 |
--------------------------------------------------------------------------------
/helpers/z_schema-express.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function (z_schema) {
4 | return function (req, res, next) {
5 | req.sanitize = sanitize;
6 |
7 | function sanitize (value, schema, callback) {
8 | return z_schema.validate(value, schema, function (err, valid) {
9 | return callback(null, {
10 | isValid: valid,
11 | issues: err ? err[0].message + ': ' + err[0].path : null
12 | }, value);
13 | });
14 | }
15 |
16 | next();
17 | };
18 | };
19 |
--------------------------------------------------------------------------------
/schema/signatures.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | addSignature: {
5 | id: 'signatures.addSignature',
6 | type: 'object',
7 | properties: {
8 | secret: {
9 | type: 'string',
10 | minLength: 1
11 | },
12 | secondSecret: {
13 | type: 'string',
14 | minLength: 1
15 | },
16 | publicKey: {
17 | type: 'string',
18 | format: 'publicKey'
19 | },
20 | multisigAccountPublicKey: {
21 | type: 'string',
22 | format: 'publicKey'
23 | }
24 | },
25 | required: ['secret', 'secondSecret']
26 | }
27 | };
28 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | require('./api/accounts.js');
2 | require('./api/blocks.js');
3 | require('./api/delegates.js');
4 | require('./api/loader.js');
5 | require('./api/multisignatures.js');
6 | require('./api/peer.js');
7 | require('./api/peer.transactions.delegates.js');
8 | require('./api/peer.transactions.js');
9 | require('./api/peer.transactions.signatures.js');
10 | require('./api/peer.transactions.votes.js');
11 | require('./api/peers.js');
12 | require('./api/signatures.js');
13 | require('./api/transactions.js');
14 |
15 | require('./unit/helpers/request-limiter.js');
16 | require('./unit/logic/blockReward.js');
17 |
--------------------------------------------------------------------------------
/helpers/crypto.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var arkjs = require('arkjs');
4 |
5 | function Crypto(scope){
6 | this.scope = scope;
7 | this.network = scope.config.network;
8 | }
9 |
10 | Crypto.prototype.makeKeypair = function (seed) {
11 | return arkjs.crypto.getKeys(seed, this.network);
12 | };
13 |
14 | Crypto.prototype.sign = function (hash, keypair) {
15 | return keypair.sign(hash).toDER().toString("hex");
16 | };
17 |
18 | Crypto.prototype.verify = function (hash, signatureBuffer, publicKeyBuffer) {
19 | try {
20 | var ecsignature = arkjs.ECSignature.fromDER(signatureBuffer);
21 | var ecpair = arkjs.ECPair.fromPublicKeyBuffer(publicKeyBuffer, this.network);
22 | return ecpair.verify(hash, ecsignature);
23 | } catch (error){
24 | return false;
25 | }
26 | };
27 |
28 | module.exports = Crypto;
29 |
--------------------------------------------------------------------------------
/tasks/newMigration.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var moment = require('moment');
4 | var fs = require('fs');
5 | var path = require('path');
6 |
7 | module.exports = function (grunt) {
8 | grunt.registerTask('newMigration', 'Create a new migration file.', function (name) {
9 | var migration = {
10 | id: moment().format('YYYYMMDDHHmmss'),
11 | name: String(name)
12 | };
13 |
14 | if (!migration.name.match(/^[a-z]+$/i)) {
15 | grunt.fail.fatal('Invalid migration name');
16 | }
17 |
18 | migration.filename = (
19 | migration.id + '_' + migration.name + '.sql'
20 | );
21 |
22 | grunt.log.write('Creating migration file: ' + migration.filename);
23 |
24 | fs.writeFile(path.join('sql', 'migrations', migration.filename), '', function (err) {
25 | if (err) { grunt.fail.fatal(err); }
26 | });
27 | });
28 | };
29 |
--------------------------------------------------------------------------------
/helpers/validator/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var util = require('util');
4 |
5 | exports.extend = extend;
6 | exports.copy = copy;
7 | exports.inherits = util.inherits;
8 |
9 | function extend (target, source) {
10 | if (!target || typeof target !== 'object') { return target; }
11 |
12 | Array.prototype.slice.call(arguments).forEach(function(source){
13 | if (!source || typeof source !== 'object') { return; }
14 |
15 | util._extend(target, source);
16 | });
17 |
18 | return target;
19 | }
20 |
21 | function copy (target) {
22 | if (!target || typeof target !== 'object') { return target; }
23 |
24 | if (Array.isArray(target)) {
25 | return target.map(copy);
26 | } else if (target.constructor === Object) {
27 | var result = {};
28 | Object.getOwnPropertyNames(target).forEach(function(name){
29 | result[name] = copy(target[name]);
30 | });
31 | return result;
32 | } else {
33 | return target;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/helpers/diff.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | reverse: function (diff) {
5 | var copyDiff = diff.slice();
6 | for (var i = 0; i < copyDiff.length; i++) {
7 | var math = copyDiff[i][0] === '-' ? '+' : '-';
8 | copyDiff[i] = math + copyDiff[i].slice(1);
9 | }
10 | return copyDiff;
11 | },
12 |
13 | merge: function (source, diff) {
14 | var res = source ? source.slice() : [];
15 | var index;
16 |
17 | for (var i = 0; i < diff.length; i++) {
18 | var math = diff[i][0];
19 | var publicKey = diff[i].slice(1);
20 |
21 | if (math === '+') {
22 | res = res || [];
23 |
24 | index = -1;
25 | if (res) {
26 | index = res.indexOf(publicKey);
27 | }
28 | if (index !== -1) {
29 | return false;
30 | }
31 |
32 | res.push(publicKey);
33 | }
34 | if (math === '-') {
35 | index = -1;
36 | if (res) {
37 | index = res.indexOf(publicKey);
38 | }
39 | if (index === -1) {
40 | return false;
41 | }
42 | res.splice(index, 1);
43 | if (!res.length) {
44 | res = null;
45 | }
46 | }
47 | }
48 | return res;
49 | }
50 | };
51 |
--------------------------------------------------------------------------------
/sql/transport.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var TransportSql = {
4 | getCommonBlock: 'SELECT MAX("height") AS "height", "id", "previousBlock", "timestamp" FROM blocks WHERE "id" IN ($1:csv) GROUP BY "id" ORDER BY "height" DESC',
5 | getBlockTransactions: 'SELECT rawtxs::json from blocks WHERE id = ${block_id}',
6 | blockList: 'SELECT id, version, height, timestamp, "previousBlock", "numberOfTransactions" ,"totalAmount", "totalFee", reward, "payloadLength", encode("payloadHash", \'hex\') as "payloadHash", encode("generatorPublicKey", \'hex\') as "generatorPublicKey", encode("blockSignature", \'hex\') as "blockSignature", rawtxs::json as transactions from blocks WHERE "height" > ${lastBlockHeight} ORDER BY "height" ASC LIMIT ${limit}',
7 | block: 'SELECT id, version, height, timestamp, "previousBlock", "numberOfTransactions" ,"totalAmount", "totalFee", reward, "payloadLength", encode("payloadHash", \'hex\') as "payloadHash", encode("generatorPublicKey", \'hex\') as "generatorPublicKey", encode("blockSignature", \'hex\') as "blockSignature", rawtxs::json as transactions from blocks WHERE id = ${id}',
8 | getTransactionId: 'SELECT "id" FROM transactions WHERE "id" = ${id}'
9 | };
10 |
11 | module.exports = TransportSql;
12 |
--------------------------------------------------------------------------------
/helpers/router.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var extend = require('extend');
4 |
5 | function Map (root, config) {
6 | var router = this;
7 |
8 | Object.keys(config).forEach(function (params) {
9 | var route = params.split(' ');
10 | if (route.length !== 2 || ['post', 'get', 'put'].indexOf(route[0]) === -1) {
11 | throw Error('Invalid map config');
12 | }
13 | router[route[0]](route[1], function (req, res, next) {
14 | root[config[params]]({'body': route[0] === 'get' ? req.query : req.body}, function (err, response) {
15 | if (err) {
16 | res.json({'success': false, 'error': err});
17 | } else {
18 | return res.json(extend({}, {'success': true}, response));
19 | }
20 | });
21 | });
22 | });
23 | }
24 |
25 | /**
26 | * @title Router
27 | * @overview Router stub
28 | * @returns {*}
29 | */
30 | var Router = function () {
31 | var router = require('express').Router();
32 |
33 | router.use(function (req, res, next) {
34 | res.header('Access-Control-Allow-Origin', '*');
35 | res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
36 | next();
37 | });
38 |
39 | router.map = Map;
40 |
41 | return router;
42 | };
43 |
44 | module.exports = Router;
45 |
--------------------------------------------------------------------------------
/optional/example.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Router = require('../helpers/router.js');
4 |
5 | // Private fields
6 | var modules, library, self, __private = {};
7 |
8 | // Constructor
9 | function Server (cb, scope) {
10 | library = scope;
11 | self = this;
12 |
13 | return cb(null, self);
14 | }
15 |
16 | // Private methods
17 | __private.attachApi = function () {
18 | var router = new Router();
19 |
20 | router.get('/', function (req, res) {
21 | res.render('./example.pug', {nethash: library.config.nethash});
22 | });
23 |
24 | router.get('/getStats', function (req, res) {
25 | res.status(200).send({
26 | lastBlock: modules.blockchain.getLastBlock(),
27 | transactionPool: modules.transactionPool.getMempoolSize()
28 | });
29 | });
30 |
31 | library.network.app.engine('pug', require('pug').__express);
32 |
33 | library.network.app.use('/app', router);
34 | };
35 |
36 | //
37 | Server.prototype.onBind = function (scope) {
38 | modules = scope;
39 | };
40 |
41 | //
42 | Server.prototype.onAttachPublicApi = function () {
43 | __private.attachApi();
44 | };
45 |
46 |
47 | //
48 | Server.prototype.cleanup = function (cb) {
49 | return cb();
50 | };
51 |
52 | // Shared
53 |
54 | // Export
55 | module.exports = Server;
56 |
--------------------------------------------------------------------------------
/helpers/inserts.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var pgp = require('pg-promise');
4 |
5 | function Inserts (record, values, concat) {
6 | if (!(this instanceof Inserts)) {
7 | return new Inserts(record, values, concat);
8 | }
9 |
10 | var self = this;
11 |
12 | if (!record || !record.table || !record.values) {
13 | throw 'Inserts: Invalid record argument';
14 | }
15 |
16 | if (!values) {
17 | throw 'Inserts: Invalid values argument';
18 | }
19 |
20 | this.namedTemplate = function () {
21 | return record.fields.map(function (field, index) {
22 | return '${' + field + '}';
23 | }).join(',');
24 | };
25 |
26 | this._template = this.namedTemplate();
27 |
28 | this.template = function () {
29 | var values;
30 | var fields = record.fields.map(pgp.as.name).join(',');
31 | if (concat) {
32 | values = '$1';
33 | } else {
34 | values = '(' + this.namedTemplate() + ')';
35 | }
36 | return pgp.as.format('INSERT INTO $1~($2^) VALUES $3^', [record.table, fields, values]);
37 | };
38 |
39 | this._rawDBType = true;
40 |
41 | this.formatDBType = function () {
42 | return values.map(function (v) {
43 | return '(' + pgp.as.format(self._template, v) + ')';
44 | }).join(',');
45 | };
46 | }
47 |
48 | module.exports = Inserts;
49 |
--------------------------------------------------------------------------------
/schema/blocks.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var constants = require('../helpers/constants.js');
4 |
5 | module.exports = {
6 | loadBlocksFromPeer: {
7 | id: 'blocks.loadBlocksFromPeer',
8 | type: 'array'
9 | },
10 | getBlock: {
11 | id: 'blocks.getBlock',
12 | type: 'object',
13 | properties: {
14 | id: {
15 | type: 'string',
16 | minLength: 1
17 | }
18 | },
19 | required: ['id']
20 | },
21 | getBlocks: {
22 | id: 'blocks.getBlocks',
23 | type: 'object',
24 | properties: {
25 | limit: {
26 | type: 'integer',
27 | minimum: 0,
28 | maximum: 100
29 | },
30 | orderBy: {
31 | type: 'string'
32 | },
33 | offset: {
34 | type: 'integer',
35 | minimum: 0
36 | },
37 | generatorPublicKey: {
38 | type: 'string',
39 | format: 'publicKey'
40 | },
41 | totalAmount: {
42 | type: 'integer',
43 | minimum: 0,
44 | maximum: constants.totalAmount
45 | },
46 | totalFee: {
47 | type: 'integer',
48 | minimum: 0,
49 | maximum: constants.totalAmount
50 | },
51 | reward: {
52 | type: 'integer',
53 | minimum: 0
54 | },
55 | previousBlock: {
56 | type: 'string'
57 | },
58 | height: {
59 | type: 'integer'
60 | }
61 | }
62 | }
63 | };
64 |
--------------------------------------------------------------------------------
/sql/delegates.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var pgp = require('pg-promise');
4 |
5 | var DelegatesSql = {
6 | sortFields: [
7 | 'username',
8 | 'address',
9 | 'publicKey',
10 | 'vote',
11 | 'missedblocks',
12 | 'producedblocks'
13 | ],
14 |
15 | count: 'SELECT COUNT(*)::int FROM delegates',
16 |
17 | search: function (params) {
18 | var sql = [
19 | 'SELECT m."username", m."address", ENCODE(m."publicKey", \'hex\') AS "publicKey", m."vote", m."producedblocks", m."missedblocks"',
20 | 'FROM mem_accounts m',
21 | 'WHERE m."isDelegate" = 1 AND m."username" LIKE ${q}',
22 | 'ORDER BY ' + [params.sortField, params.sortMethod].join(' '),
23 | 'LIMIT ${limit}'
24 | ].join(' ');
25 |
26 | params.q = '%' + String(params.q).toLowerCase() + '%';
27 | return pgp.as.format(sql, params);
28 | },
29 |
30 | insertFork: 'INSERT INTO forks_stat ("delegatePublicKey", "blockTimestamp", "blockId", "blockHeight", "previousBlock", "cause") VALUES (${delegatePublicKey}, ${blockTimestamp}, ${blockId}, ${blockHeight}, ${previousBlock}, ${cause});',
31 |
32 | getVoters: 'SELECT ARRAY_AGG("accountId") AS "accountIds" FROM mem_accounts2delegates WHERE "dependentId" = ${publicKey}'
33 | };
34 |
35 | module.exports = DelegatesSql;
36 |
--------------------------------------------------------------------------------
/sql/loader.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var LoaderSql = {
4 | countBlocks: 'SELECT COUNT("rowId")::int FROM blocks',
5 |
6 | countMemAccounts: 'SELECT COUNT(*)::int FROM mem_accounts WHERE "blockId" = (SELECT "id" FROM "blocks" ORDER BY "height" DESC LIMIT 1)',
7 |
8 | resetMemAccounts: [
9 | 'UPDATE mem_accounts SET "u_isDelegate" = "isDelegate", "u_secondSignature" = "secondSignature", "u_username" = "username", "u_balance" = "balance", "u_delegates" = "delegates", "u_multisignatures" = "multisignatures", "u_multimin" = "multimin", "u_multilifetime" = "multilifetime";',
10 | 'DELETE FROM mem_accounts2u_delegates; INSERT INTO mem_accounts2u_delegates SELECT * FROM mem_accounts2delegates;',
11 | 'DELETE FROM mem_accounts2u_multisignatures; INSERT INTO mem_accounts2u_multisignatures SELECT * FROM mem_accounts2multisignatures;',
12 | ].join(''),
13 |
14 | getOrphanedMemAccounts: 'SELECT a."blockId", b."id" FROM mem_accounts a LEFT OUTER JOIN blocks b ON b."id" = a."blockId" WHERE a."blockId" IS NOT NULL AND a."blockId" != \'0\' AND b."id" IS NULL',
15 |
16 | getDelegates: 'SELECT ENCODE("publicKey", \'hex\') FROM mem_accounts WHERE "isDelegate" = 1',
17 |
18 | getTransactionId: 'SELECT "id" FROM transactions WHERE "id" = ${id}'
19 | };
20 |
21 | module.exports = LoaderSql;
22 |
--------------------------------------------------------------------------------
/helpers/request-limiter.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var RateLimit = require('express-rate-limit');
4 |
5 | var defaults = {
6 | max: 0, // Disabled
7 | delayMs: 0, // Disabled
8 | delayAfter: 0, // Disabled
9 | windowMs: 60000 // 1 minute window
10 | };
11 |
12 | function applyLimits (limits) {
13 | if (typeof limits === 'object') {
14 | return {
15 | max: Math.floor(limits.max) || defaults.max,
16 | delayMs: Math.floor(limits.delayMs) || defaults.delayMs,
17 | delayAfter: Math.floor(limits.delayAfter) || defaults.delayAfter,
18 | windowMs: Math.floor(limits.windowMs) || defaults.windowMs
19 | };
20 | } else {
21 | return defaults;
22 | }
23 | }
24 |
25 | module.exports = function (app, config) {
26 | if (config.trustProxy) {
27 | app.enable('trust proxy');
28 | }
29 |
30 | config.api = config.api || {};
31 | config.api.options = config.api.options || {};
32 |
33 | config.peers = config.peers || {};
34 | config.peers.options = config.peers.options || {};
35 |
36 | var limits = {
37 | client: applyLimits(config.api.options.limits),
38 | peer: applyLimits(config.peers.options.limits)
39 | };
40 |
41 | limits.middleware = {
42 | client: app.use('/api/', new RateLimit(limits.client)),
43 | peer: app.use('/peer/', new RateLimit(limits.peer))
44 | };
45 |
46 | return limits;
47 | };
48 |
--------------------------------------------------------------------------------
/networks.json:
--------------------------------------------------------------------------------
1 | {
2 | "ark": {
3 | "messagePrefix": "ARK message:\n",
4 | "bip32": {
5 | "public": 46090600,
6 | "private": 46089520
7 | },
8 | "pubKeyHash": 23,
9 | "wif": 170,
10 | "client": {
11 | "token":"ARK",
12 | "symbol":"Ѧ",
13 | "explorer":"https://explorer.ark.io"
14 | }
15 | },
16 | "bitcoin": {
17 | "messagePrefix": "Bitcoin message:\n",
18 | "bip32": {
19 | "public": 46090600,
20 | "private": 46089520
21 | },
22 | "pubKeyHash": 1,
23 | "wif": 1,
24 | "client": {
25 | "token":"Bitcoin",
26 | "symbol":"B"
27 | }
28 | },
29 | "testnet": {
30 | "messagePrefix": "TESTNET message:\n",
31 | "bip32": {
32 | "public": 70617039,
33 | "private": 70615956
34 | },
35 | "pubKeyHash": 82,
36 | "wif": 186,
37 | "client": {
38 | "token":"TARK",
39 | "symbol":"TѦ",
40 | "explorer":"http://texplorer.ark.io"
41 | }
42 | },
43 | "devnet": {
44 | "messagePrefix": "DEVNET message:\n",
45 | "bip32": {
46 | "public": 70617039,
47 | "private": 70615956
48 | },
49 | "pubKeyHash": 30,
50 | "wif": 187,
51 | "client": {
52 | "token":"DARK",
53 | "symbol":"DѦ",
54 | "explorer":"http://dexplorer.ark.io"
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/helpers/constants.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | activeDelegates: 51,
5 | maximumVotes: 1,
6 | addressLength: 208,
7 | blockHeaderLength: 248,
8 | confirmationLength: 77,
9 | epochTime: new Date(Date.UTC(2017, 2, 21, 13, 0, 0, 0)),
10 | fees:{
11 | send: 10000000,
12 | vote: 100000000,
13 | secondsignature: 500000000,
14 | delegate: 2500000000,
15 | multisignature: 500000000
16 | },
17 | feeStart: 1,
18 | feeStartVolume: 10000 * 100000000,
19 | fixedPoint : Math.pow(10, 8),
20 | forgingTimeOut: 3060, // 102 blocks / 2 rounds
21 | maxAddressesLength: 208 * 128,
22 | maxAmount: 100000000,
23 | maxClientConnections: 100,
24 | maxConfirmations : 77 * 100,
25 | maxPayloadLength: 2 * 1024 * 1024,
26 | maxRequests: 10000 * 12,
27 | maxSignaturesLength: 196 * 256,
28 | maxTxsPerBlock: 50,
29 | blocktime: 8,
30 | numberLength: 100000000,
31 | requestLength: 104,
32 | rewards: {
33 | milestones: [
34 | 200000000, // Initial Reward
35 | 200000000, // Milestone 1
36 | 200000000, // Milestone 2
37 | 200000000, // Milestone 3
38 | 200000000 // Milestone 4
39 | ],
40 | offset: 75600, // Start rewards at block, ie 7 days after net start
41 | distance: 3000000, // Distance between each milestone
42 | },
43 | signatureLength: 196,
44 | totalAmount: 12500000000000004, // TODO: Fix properly because this value exceeds JS Number precision
45 | unconfirmedTransactionTimeOut: 10800 // 1080 blocks
46 | };
47 |
--------------------------------------------------------------------------------
/helpers/checkIpInList.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('lodash');
4 | var ip = require('ip');
5 |
6 | /*
7 | Checks if ip address is in list (e.g. whitelist, blacklist).
8 |
9 | @param list an array of ip addresses or ip subnets
10 | @param addr the ip address to check if in array
11 | @param returnListIsEmpty the return value, if list is empty (default: true)
12 | @returns true if ip is in the list, false otherwise
13 | */
14 | function CheckIpInList (list, addr, returnListIsEmpty) {
15 | var i, n;
16 |
17 | if (!_.isBoolean(returnListIsEmpty)) {
18 | returnListIsEmpty = true;
19 | }
20 |
21 | if (!_.isArray(list) || list.length === 0) {
22 | return returnListIsEmpty;
23 | }
24 |
25 | if (!list._subNets) { // First call, create subnet list
26 | list._subNets = [];
27 | for (i = list.length - 1; i >= 0; i--) {
28 | var entry = list[i];
29 | if (ip.isV4Format(entry)) { // IPv4 host entry
30 | entry = entry + '/32';
31 | } else if (ip.isV6Format(entry)) { // IPv6 host entry
32 | entry = entry + '/128';
33 | }
34 | try {
35 | var subnet = ip.cidrSubnet(entry);
36 | list._subNets.push(subnet);
37 | } catch (err) {
38 | console.error('CheckIpInList:', err.toString());
39 | }
40 | }
41 | }
42 |
43 | if (list._subNets.length === 0) {
44 | return returnListIsEmpty;
45 | }
46 |
47 | // Check subnets
48 | for (i = 0, n = list._subNets.length; i < n; i++) {
49 | if (list._subNets[i].contains(addr)) {
50 | return true;
51 | }
52 | }
53 |
54 | // IP address not found
55 | return false;
56 | }
57 |
58 | module.exports = CheckIpInList;
59 |
--------------------------------------------------------------------------------
/schema/loader.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | loadSignatures: {
5 | id: 'loader.loadSignatures',
6 | type: 'object',
7 | properties: {
8 | signatures: {
9 | type: 'array',
10 | uniqueItems: true
11 | }
12 | },
13 | required: ['signatures']
14 | },
15 | loadUnconfirmedTransactions: {
16 | id: 'loader.loadUnconfirmedTransactions',
17 | type: 'object',
18 | properties: {
19 | transactions: {
20 | type: 'array',
21 | uniqueItems: true
22 | }
23 | },
24 | required: ['transactions']
25 | },
26 | getNetwork: {
27 | peers: {
28 | id: 'loader.getNetwork.peers',
29 | type: 'object',
30 | properties: {
31 | peers: {
32 | type: 'array',
33 | uniqueItems: true
34 | }
35 | },
36 | required: ['peers']
37 | },
38 | peer: {
39 | id: 'loader.getNetwork.peer',
40 | type: 'object',
41 | properties: {
42 | ip: {
43 | type: 'string',
44 | format: 'ip'
45 | },
46 | port: {
47 | type: 'integer',
48 | minimum: 1,
49 | maximum: 65535
50 | },
51 | state: {
52 | type: 'integer',
53 | minimum: 0,
54 | maximum: 3
55 | },
56 | os: {
57 | type: 'string'
58 | },
59 | version: {
60 | type: 'string'
61 | }
62 | },
63 | required: ['ip', 'port']
64 | },
65 | height: {
66 | id: 'loader.getNetwork.height',
67 | type: 'object',
68 | properties: {
69 | height: {
70 | type: 'integer',
71 | minimum: 0
72 | },
73 | id: {
74 | type: 'string',
75 | minLength: 1
76 | }
77 | },
78 | required: ['height']
79 | }
80 | }
81 | };
82 |
--------------------------------------------------------------------------------
/modules/system.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var os = require('os');
4 |
5 | // Private fields
6 | var modules, library, self, __private = {}, shared = {};
7 |
8 | // Constructor
9 | function System (cb, scope) {
10 | library = scope;
11 | self = this;
12 |
13 | __private.version = library.config.version;
14 | __private.port = library.config.port;
15 | __private.nethash = library.config.nethash;
16 | __private.osName = os.platform() + os.release();
17 |
18 | return cb(null, self);
19 | }
20 |
21 | // Private methods
22 |
23 | // Public methods
24 | //
25 | //__API__ `getOS`
26 |
27 | //
28 | System.prototype.getOS = function () {
29 | return __private.osName;
30 | };
31 |
32 | //
33 | //__API__ `getVersion`
34 |
35 | //
36 | System.prototype.getVersion = function () {
37 | return __private.version;
38 | };
39 |
40 | //
41 | //__API__ `getPort`
42 |
43 | //
44 | System.prototype.getPort = function () {
45 | return __private.port;
46 | };
47 |
48 | //
49 | //__API__ `getNethash`
50 |
51 | //
52 | System.prototype.getNethash = function () {
53 | return __private.nethash;
54 | };
55 |
56 | // Events
57 | //
58 | //__EVENT__ `onBind`
59 |
60 | //
61 | System.prototype.onBind = function (scope) {
62 | modules = scope;
63 | };
64 |
65 | //
66 | //__API__ `isMyself`
67 |
68 | //
69 | System.prototype.isMyself = function (peer) {
70 | var interfaces = os.networkInterfaces();
71 | return Object.keys(interfaces).some(function(family){
72 | return interfaces[family].some(function(nic){
73 | return nic.address == peer.ip && peer.port == __private.port;
74 | });
75 | });
76 | }
77 |
78 | // Shared
79 |
80 | // Export
81 | module.exports = System;
82 |
--------------------------------------------------------------------------------
/helpers/sequence.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var extend = require('extend');
4 | var util = require('util');
5 |
6 | function Sequence (config) {
7 | var _default = {
8 | onWarning: null,
9 | warningLimit: 50
10 | };
11 | _default = extend(_default, config);
12 | var self = this;
13 | this.sequence = [];
14 |
15 | setImmediate(function nextSequenceTick () {
16 | if (_default.onWarning && self.sequence.length >= _default.warningLimit) {
17 | _default.onWarning(self.sequence.length, _default.warningLimit);
18 | }
19 | self.__tick(function() {
20 | setTimeout(nextSequenceTick, 0);
21 | });
22 | });
23 | }
24 |
25 | //
26 | //__API__ `__tick`
27 |
28 | //
29 | Sequence.prototype.__tick = function (cb) {
30 | var task = this.sequence.shift();
31 | if (!task) {
32 | return setImmediate(cb);
33 | }
34 | var args = [function (err, res) {
35 | if (task.done) {
36 | task.done(err, res);
37 | }
38 | setImmediate(cb);
39 | }];
40 | if (task.args) {
41 | args = args.concat(task.args);
42 | }
43 | task.worker.apply(task.worker, args);
44 | };
45 |
46 | //
47 | //__API__ `add`
48 |
49 | //
50 | Sequence.prototype.add = function (worker, args, done) {
51 | if (!done && args && typeof(args) === 'function') {
52 | done = args;
53 | args = undefined;
54 | }
55 | if (worker && typeof(worker) === 'function') {
56 | var task = {worker: worker, done: done};
57 | if (util.isArray(args)) {
58 | task.args = args;
59 | }
60 | this.sequence.push(task);
61 | }
62 | };
63 |
64 | //
65 | //__API__ `count`
66 |
67 | //
68 | Sequence.prototype.count = function () {
69 | return this.sequence.length;
70 | };
71 |
72 | module.exports = Sequence;
73 |
--------------------------------------------------------------------------------
/schema/multisignatures.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | getAccounts: {
5 | id: 'multisignatures.getAccounts',
6 | type: 'object',
7 | properties: {
8 | publicKey: {
9 | type: 'string',
10 | format: 'publicKey'
11 | }
12 | },
13 | required: ['publicKey']
14 | },
15 | pending: {
16 | id: 'multisignatures.pending',
17 | type: 'object',
18 | properties: {
19 | publicKey: {
20 | type: 'string',
21 | format: 'publicKey'
22 | }
23 | },
24 | required: ['publicKey']
25 | },
26 | sign: {
27 | id: 'multisignatures.sign',
28 | type: 'object',
29 | properties: {
30 | secret: {
31 | type: 'string',
32 | minLength: 1,
33 | maxLength: 100
34 | },
35 | secondSecret: {
36 | type: 'string',
37 | minLength: 1,
38 | maxLength: 100
39 | },
40 | publicKey: {
41 | type: 'string',
42 | format: 'publicKey'
43 | },
44 | transactionId: {
45 | type: 'string'
46 | }
47 | },
48 | required: ['transactionId', 'secret']
49 | },
50 | addMultisignature: {
51 | id: 'multisignatures.addMultisignature',
52 | type: 'object',
53 | properties: {
54 | secret: {
55 | type: 'string',
56 | minLength: 1,
57 | maxLength: 100
58 | },
59 | publicKey: {
60 | type: 'string',
61 | format: 'publicKey'
62 | },
63 | secondSecret: {
64 | type: 'string',
65 | minLength: 1,
66 | maxLength: 100
67 | },
68 | min: {
69 | type: 'integer',
70 | minimum: 1,
71 | maximum: 16
72 | },
73 | lifetime: {
74 | type: 'integer',
75 | minimum: 1,
76 | maximum: 72
77 | },
78 | keysgroup: {
79 | type: 'array',
80 | minLength: 1,
81 | maxLength: 10
82 | }
83 | },
84 | required: ['min', 'lifetime', 'keysgroup', 'secret']
85 | }
86 | };
87 |
--------------------------------------------------------------------------------
/schema/transport.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | headers: {
5 | id: 'transport.headers',
6 | type: 'object',
7 | properties: {
8 | ip: {
9 | type: 'string',
10 | format: 'ip'
11 | },
12 | port: {
13 | type: 'integer',
14 | minimum: 1,
15 | maximum: 65535
16 | },
17 | os: {
18 | type: 'string',
19 | maxLength: 64
20 | },
21 | nethash: {
22 | type: 'string',
23 | maxLength: 64
24 | },
25 | version: {
26 | type: 'string',
27 | maxLength: 11
28 | }
29 | },
30 | required: ['ip', 'port', 'nethash', 'version']
31 | },
32 | commonBlock: {
33 | id: 'transport.commonBlock',
34 | type: 'object',
35 | properties: {
36 | ids: {
37 | type: 'string',
38 | format: 'csv'
39 | }
40 | },
41 | required: ['ids']
42 | },
43 | transactionsFromIds: {
44 | id: 'transport.transactionsFromIds',
45 | type: 'object',
46 | properties: {
47 | ids: {
48 | type: 'string',
49 | format: 'csv'
50 | }
51 | },
52 | required: ['ids']
53 | },
54 | blocks: {
55 | id: 'transport.blocks',
56 | type: 'object',
57 | properties: {
58 | lastBlockHeight: {
59 | type: 'integer'
60 | }
61 | },
62 | },
63 | block: {
64 | id: 'transport.block',
65 | type: 'object',
66 | properties: {
67 | id: {
68 | type: 'string'
69 | }
70 | },
71 | },
72 | signatures: {
73 | id: 'transport.signatures',
74 | type: 'object',
75 | properties: {
76 | signature: {
77 | type: 'object',
78 | properties: {
79 | transaction: {
80 | type: 'string'
81 | },
82 | signature: {
83 | type: 'string',
84 | format: 'signature'
85 | }
86 | },
87 | required: ['transaction', 'signature']
88 | }
89 | },
90 | required: ['signature']
91 | }
92 | };
93 |
--------------------------------------------------------------------------------
/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "port": 4000,
3 | "address": "0.0.0.0",
4 | "version": "0.2.1",
5 | "fileLogLevel": "info",
6 | "logFileName": "logs/ark.log",
7 | "consoleLogLevel": "debug",
8 | "trustProxy": false,
9 | "db": {
10 | "host": "localhost",
11 | "port": 5432,
12 | "database": "ark_test",
13 | "user": null,
14 | "password": "password",
15 | "poolSize": 20,
16 | "poolIdleTimeout": 30000,
17 | "reapIntervalMillis": 1000,
18 | "logEvents": [
19 | "error"
20 | ]
21 | },
22 | "api": {
23 | "access": {
24 | "whiteList": []
25 | },
26 | "options": {
27 | "limits": {
28 | "max": 0,
29 | "delayMs": 0,
30 | "delayAfter": 0,
31 | "windowMs": 60000
32 | }
33 | }
34 | },
35 | "peers": {
36 | "minimumNetworkReach": 1,
37 | "list": [
38 | {
39 | "ip": "127.0.0.1",
40 | "port": 4000
41 | }
42 | ],
43 | "blackList": [],
44 | "options": {
45 | "limits": {
46 | "max": 0,
47 | "delayMs": 0,
48 | "delayAfter": 0,
49 | "windowMs": 60000
50 | },
51 | "maxUpdatePeers": 20,
52 | "timeout": 5000
53 | }
54 | },
55 | "forging": {
56 | "coldstart": 60,
57 | "force": true,
58 | "secret": [],
59 | "access": {
60 | "whiteList": [
61 | "127.0.0.1"
62 | ]
63 | }
64 | },
65 | "loading": {
66 | "verifyOnLoading": false,
67 | "loadPerIteration": 5000
68 | },
69 | "modules": {
70 | "example":"./optional/example.js"
71 | },
72 | "ssl": {
73 | "enabled": false,
74 | "options": {
75 | "port": 443,
76 | "address": "0.0.0.0",
77 | "key": "./ssl/ark.key",
78 | "cert": "./ssl/ark.crt"
79 | }
80 | },
81 | "nethash": "31a3d2f549dcb0ce6856b5d440cb7a1402d766f290a89d408f155f937f106f8c"
82 | }
83 |
--------------------------------------------------------------------------------
/helpers/slots.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var constants = require('./constants.js');
4 |
5 | /**
6 | * Get time from Ark epoch.
7 | * @param {number|undefined} time Time in unix seconds
8 | * @returns {number}
9 | */
10 |
11 | function beginEpochTime () {
12 | var d = constants.epochTime;
13 |
14 | return d;
15 | }
16 |
17 | function getEpochTime (time) {
18 | if (time === undefined) {
19 | time = (new Date()).getTime();
20 | }
21 |
22 | var d = beginEpochTime();
23 | var t = d.getTime();
24 |
25 | return Math.floor((time - t) / 1000);
26 | }
27 |
28 | module.exports = {
29 | interval: constants.blocktime,
30 | delegates: constants.activeDelegates,
31 |
32 | getTime: function (time) {
33 | return getEpochTime(time);
34 | },
35 |
36 | getRealTime: function (epochTime) {
37 | if (epochTime === undefined) {
38 | epochTime = this.getTime();
39 | }
40 |
41 | var d = beginEpochTime();
42 | var t = Math.floor(d.getTime() / 1000) * 1000;
43 |
44 | return t + epochTime * 1000;
45 | },
46 |
47 | getSlotNumber: function (epochTime) {
48 | if (epochTime === undefined) {
49 | epochTime = this.getTime();
50 | }
51 |
52 | return Math.floor(epochTime / this.interval);
53 | },
54 |
55 | // Forging is allowed only during the first half of blocktime
56 | isForgingAllowed: function (epochTime) {
57 | if (epochTime === undefined) {
58 | epochTime = this.getTime();
59 | }
60 |
61 | return Math.floor(epochTime / this.interval) == Math.floor((epochTime + this.interval / 2) / this.interval);
62 | },
63 |
64 | getSlotTime: function (slot) {
65 | return slot * this.interval;
66 | },
67 |
68 | getNextSlot: function () {
69 | var slot = this.getSlotNumber();
70 |
71 | return slot + 1;
72 | },
73 |
74 | getLastSlot: function (nextSlot) {
75 | return nextSlot + this.delegates;
76 | },
77 |
78 | roundTime: function (date) {
79 | return Math.floor(date.getTime() / 1000) * 1000;
80 | }
81 | };
82 |
--------------------------------------------------------------------------------
/helpers/orderBy.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | function OrderBy (orderBy, options) {
4 | options = (typeof options === 'object') ? options : {};
5 | options.sortField = options.sortField || null;
6 | options.sortMethod = options.sortMethod || null;
7 | options.sortFields = Array.isArray(options.sortFields) ? options.sortFields : [];
8 |
9 | if (typeof options.quoteField === 'undefined') {
10 | options.quoteField = true;
11 | } else {
12 | options.quoteField = Boolean(options.quoteField);
13 | }
14 |
15 | var sortField, sortMethod;
16 |
17 | if (orderBy) {
18 | var sort = String(orderBy).split(':');
19 | sortField = sort[0].replace(/[^\w\s]/gi, '');
20 |
21 | if (sort.length === 2) {
22 | sortMethod = sort[1] === 'desc' ? 'DESC' : 'ASC';
23 | }
24 | }
25 |
26 | function prefixField (sortField) {
27 | if (!sortField) {
28 | return sortField;
29 | } else if (typeof options.fieldPrefix === 'string') {
30 | return options.fieldPrefix + sortField;
31 | } else if (typeof options.fieldPrefix === 'function') {
32 | return options.fieldPrefix(sortField);
33 | } else {
34 | return sortField;
35 | }
36 | }
37 |
38 | function quoteField (sortField) {
39 | if (sortField && options.quoteField) {
40 | return ('"' + sortField + '"');
41 | } else {
42 | return sortField;
43 | }
44 | }
45 |
46 | var emptyWhiteList = options.sortFields.length === 0;
47 |
48 | var inWhiteList = options.sortFields.length >= 1 && options.sortFields.indexOf(sortField) > -1;
49 |
50 | if (sortField) {
51 | if (emptyWhiteList || inWhiteList) {
52 | sortField = prefixField(sortField);
53 | } else {
54 | return {
55 | error: 'Invalid sort field'
56 | };
57 | }
58 | } else {
59 | sortField = prefixField(options.sortField);
60 | }
61 |
62 | if (!sortMethod) {
63 | sortMethod = options.sortMethod;
64 | }
65 |
66 | return {
67 | sortField: quoteField(sortField),
68 | sortMethod: sortMethod
69 | };
70 | }
71 |
72 | module.exports = OrderBy;
73 |
--------------------------------------------------------------------------------
/sql/peers.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var PeersSql = {
4 | sortFields: ['ip', 'port', 'state', 'os', 'version'],
5 |
6 | count: 'SELECT COUNT(*)::int FROM peers',
7 |
8 | banManager: 'UPDATE peers SET "state" = 1, "clock" = null WHERE ("state" = 0 AND "clock" - ${now} < 0);',
9 |
10 | getByFilter: function (params) {
11 | return [
12 | 'SELECT "ip", "port", "state", "os", "version" FROM peers',
13 | (params.where.length ? 'WHERE ' + params.where.join(' AND ') : ''),
14 | (params.sortField ? 'ORDER BY ' + [params.sortField, params.sortMethod].join(' ') : 'ORDER BY random()'),
15 | 'LIMIT ${limit} OFFSET ${offset}'
16 | ].filter(Boolean).join(' ');
17 | },
18 |
19 | randomList: function (params) {
20 | return [
21 | 'SELECT p."ip", p."port", p."state", p."os", p."version" FROM peers p',
22 | 'WHERE p."state" > 0 ORDER BY RANDOM() LIMIT ${limit}'
23 | ].filter(Boolean).join(' ');
24 | },
25 |
26 | state: 'UPDATE peers SET "state" = ${state}, "clock" = ${clock} WHERE "ip" = ${ip} AND "port" = ${port};',
27 |
28 | remove: 'DELETE FROM peers WHERE "ip" = ${ip} AND "port" = ${port};',
29 |
30 | getByIdPort: 'SELECT "id" FROM peers WHERE "ip" = ${ip} AND "port" = ${port}',
31 |
32 | insert: 'INSERT INTO peers ("ip", "port", "state", "os", "version") VALUES (${ip}, ${port}, ${state}, ${os}, ${version}) ON CONFLICT DO NOTHING;',
33 |
34 | upsertWithState: 'INSERT INTO peers ("ip", "port", "state", "os", "version") VALUES (${ip}, ${port}, ${state}, ${os}, ${version}) ON CONFLICT ("ip", "port") DO UPDATE SET ("ip", "port", "state", "os", "version") = (${ip}, ${port}, (CASE WHEN EXCLUDED."state" = 0 THEN EXCLUDED."state" ELSE ${state} END), ${os}, ${version})',
35 |
36 | upsertWithoutState: 'INSERT INTO peers ("ip", "port", "os", "version") VALUES (${ip}, ${port}, ${os}, ${version}) ON CONFLICT ("ip", "port") DO UPDATE SET ("ip", "port", "os", "version") = (${ip}, ${port}, ${os}, ${version})',
37 |
38 | insertSeed: 'INSERT INTO peers("ip", "port", "state") VALUES(${ip}, ${port}, ${state}) ON CONFLICT DO NOTHING;',
39 | };
40 |
41 | module.exports = PeersSql;
42 |
--------------------------------------------------------------------------------
/config.devnet.json:
--------------------------------------------------------------------------------
1 | {
2 | "port": 4002,
3 | "address": "0.0.0.0",
4 | "version": "1.1.1",
5 | "fileLogLevel": "info",
6 | "logFileName": "logs/ark.log",
7 | "consoleLogLevel": "debug",
8 | "trustProxy": false,
9 | "db": {
10 | "host": "localhost",
11 | "port": 5432,
12 | "database": "ark_devnet",
13 | "user": null,
14 | "password": "password",
15 | "poolSize": 20,
16 | "poolIdleTimeout": 30000,
17 | "reapIntervalMillis": 1000,
18 | "logEvents": [
19 | "error"
20 | ]
21 | },
22 | "api": {
23 | "mount": true,
24 | "access": {
25 | "whiteList": []
26 | },
27 | "options": {
28 | "limits": {
29 | "max": 0,
30 | "delayMs": 0,
31 | "delayAfter": 0,
32 | "windowMs": 60000
33 | }
34 | }
35 | },
36 | "peers": {
37 | "minimumNetworkReach": 10,
38 | "list": [
39 | {
40 | "ip": "167.114.29.51",
41 | "port": 4002
42 | },
43 | {
44 | "ip": "167.114.29.52",
45 | "port": 4002
46 | },
47 | {
48 | "ip": "167.114.29.53",
49 | "port": 4002
50 | },
51 | {
52 | "ip": "167.114.29.54",
53 | "port": 4002
54 | },
55 | {
56 | "ip": "167.114.29.55",
57 | "port": 4002
58 | }
59 | ],
60 | "blackList": [],
61 | "options": {
62 | "limits": {
63 | "max": 0,
64 | "delayMs": 0,
65 | "delayAfter": 0,
66 | "windowMs": 60000
67 | },
68 | "maxUpdatePeers": 20,
69 | "timeout": 5000
70 | }
71 | },
72 | "forging": {
73 | "coldstart": 6,
74 | "force": true,
75 | "secret": [],
76 | "access": {
77 | "whiteList": [
78 | "127.0.0.1"
79 | ]
80 | }
81 | },
82 | "loading": {
83 | "verifyOnLoading": false,
84 | "loadPerIteration": 5000
85 | },
86 | "ssl": {
87 | "enabled": false,
88 | "options": {
89 | "port": 443,
90 | "address": "0.0.0.0",
91 | "key": "./ssl/ark.key",
92 | "cert": "./ssl/ark.crt"
93 | }
94 | },
95 | "network": "devnet",
96 | "nethash": "578e820911f24e039733b45e4882b73e301f813a0d2c31330dafda84534ffa23"
97 | }
98 |
--------------------------------------------------------------------------------
/schema/accounts.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | open: {
5 | id: 'accounts.openAccount',
6 | type: 'object',
7 | properties: {
8 | secret: {
9 | type: 'string',
10 | minLength: 1,
11 | maxLength: 100
12 | }
13 | },
14 | required: ['secret']
15 | },
16 | getBalance: {
17 | id: 'accounts.getBalance',
18 | type: 'object',
19 | properties: {
20 | address: {
21 | type: 'string',
22 | minLength: 1,
23 | format: 'address'
24 | }
25 | },
26 | required: ['address']
27 | },
28 | getPublicKey: {
29 | id: 'accounts.getPublickey',
30 | type: 'object',
31 | properties: {
32 | address: {
33 | type: 'string',
34 | minLength: 1,
35 | format: 'address'
36 | }
37 | },
38 | required: ['address']
39 | },
40 | generatePublicKey: {
41 | id: 'accounts.generatePublickey',
42 | type: 'object',
43 | properties: {
44 | secret: {
45 | type: 'string',
46 | minLength: 1
47 | }
48 | },
49 | required: ['secret']
50 | },
51 | getDelegates: {
52 | id: 'accounts.getDelegates',
53 | type: 'object',
54 | properties: {
55 | address: {
56 | type: 'string',
57 | minLength: 1,
58 | format: 'address'
59 | }
60 | },
61 | required: ['address']
62 | },
63 | addDelegates: {
64 | id: 'accounts.addDelegates',
65 | type: 'object',
66 | properties: {
67 | secret: {
68 | type: 'string',
69 | minLength: 1
70 | },
71 | publicKey: {
72 | type: 'string',
73 | format: 'publicKey'
74 | },
75 | secondSecret: {
76 | type: 'string',
77 | minLength: 1
78 | }
79 | }
80 | },
81 | getAccount: {
82 | id: 'accounts.getAccount',
83 | type: 'object',
84 | properties: {
85 | address: {
86 | type: 'string',
87 | minLength: 1,
88 | format: 'address'
89 | }
90 | },
91 | required: ['address']
92 | },
93 | top: {
94 | id: 'accounts.top',
95 | type: 'object',
96 | properties: {
97 | limit: {
98 | type: 'integer',
99 | minimum: 0,
100 | maximum: 100
101 | },
102 | offset: {
103 | type: 'integer',
104 | minimum: 0
105 | }
106 | }
107 | }
108 | };
109 |
--------------------------------------------------------------------------------
/test/api/peer.js:
--------------------------------------------------------------------------------
1 | 'use strict'; /*jslint mocha:true, expr:true */
2 |
3 | var node = require('./../node.js');
4 |
5 | describe('GET /peer/list', function () {
6 |
7 | before(function (done) {
8 | node.addPeers(2, done);
9 | });
10 |
11 | it('using incorrect nethash in headers should fail', function (done) {
12 | node.get('/peer/list')
13 | .set('nethash', 'incorrect')
14 | .end(function (err, res) {
15 | node.debug('> Response:'.grey, JSON.stringify(res.body));
16 | node.expect(res.body).to.have.property('success').to.be.not.ok;
17 | node.expect(res.body.expected).to.equal(node.config.nethash);
18 | done();
19 | });
20 | });
21 |
22 | it('using valid headers should be ok', function (done) {
23 | node.get('/peer/list')
24 | .end(function (err, res) {
25 | node.debug('> Response:'.grey, JSON.stringify(res.body));
26 | node.expect(res.body).to.have.property('peers').that.is.an('array');
27 | node.expect(res.body.peers).to.have.length.of.at.least(0);
28 | res.body.peers.forEach(function (peer) {
29 | node.expect(peer).to.have.property('ip').that.is.a('string');
30 | node.expect(peer).to.have.property('port').that.is.a('number');
31 | node.expect(peer).to.have.property('os');
32 | node.expect(peer).to.have.property('version');
33 | });
34 | done();
35 | });
36 | });
37 | });
38 |
39 | describe('GET /peer/height', function () {
40 |
41 | it('using incorrect nethash in headers should fail', function (done) {
42 | node.get('/peer/height')
43 | .set('nethash', 'incorrect')
44 | .end(function (err, res) {
45 | node.debug('> Response:'.grey, JSON.stringify(res.body));
46 | node.expect(res.body).to.have.property('success').to.be.not.ok;
47 | node.expect(res.body.expected).to.equal(node.config.nethash);
48 | done();
49 | });
50 | });
51 |
52 | it('using valid headers should be ok', function (done) {
53 | node.get('/peer/height')
54 | .end(function (err, res) {
55 | node.debug('> Response:'.grey, JSON.stringify(res.body));
56 | node.expect(res.body).to.have.property('success').to.be.ok;
57 | node.expect(res.body).to.be.an('object').that.has.property('height');
58 | node.expect(res.body.height).to.be.a('number').to.be.above(1);
59 | done();
60 | });
61 | });
62 | });
63 |
--------------------------------------------------------------------------------
/docs/template/arkio.jst:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | <%= title %>
6 |
7 |
8 |
9 |
10 |
11 | <% if (!hasTitle) { %>
12 |
13 |
<%= title %>
14 |

15 |
16 |
17 | <% } %>
18 |
19 |
20 | <% if (sources.length > 1) { %>
21 |
22 | -
23 | Jump To …
24 | +
25 |
26 |
27 |
28 | <% for (var i=0, l=sources.length; i
29 | <% var source = sources[i]; %>
30 |
31 | <%= path.basename(source) %>
32 |
33 | <% } %>
34 |
35 |
36 |
37 |
38 | <% } %>
39 |
40 | <% for (var i=0, l=sections.length; i
41 | <% var section = sections[i]; %>
42 | -
43 | <% if (section.codeText.replace(/\s/gm, '') == '') { %>
44 |
45 | <% } else { %>
46 |
47 | <% } %>
48 | <% heading = section.docsHtml.match(/^\s*<(h\d)>/) %>
49 |
52 | <%= section.docsHtml %>
53 |
54 | <% if (section.codeText.replace(/\s/gm, '') != '') { %>
55 |
<%= section.codeHtml %>
56 | <% } %>
57 |
58 | <% } %>
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/schema/api.peer.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | 'GET:/peer/status':{
5 | id: 'GET:/peer/status',
6 | type: 'object',
7 | properties: {
8 | success: {
9 | type: 'boolean'
10 | },
11 | height: {
12 | type: 'integer',
13 | minimum: 0
14 | },
15 | currentSlot: {
16 | type: 'integer',
17 | minimum: 0
18 | },
19 | forgingAllowed: {
20 | type: 'boolean'
21 | },
22 | header: {
23 | type: 'object'
24 | }
25 | },
26 | required: ['success','height','header','currentSlot','forgingAllowed']
27 | },
28 | 'GET:/peer/height':{
29 | id: 'GET:/peer/height',
30 | type: 'object',
31 | properties: {
32 | success: {
33 | type: 'boolean'
34 | },
35 | height: {
36 | type: 'integer',
37 | minimum: 0
38 | },
39 | header: {
40 | type: 'object'
41 | }
42 | },
43 | required: ['success','height','header']
44 | },
45 | 'POST:/peer/transactions':{
46 | id: 'POST:/peer/transactions',
47 | type: 'object'
48 | },
49 | 'GET:/peer/transactions':{
50 | id: 'GET:/peer/transactions',
51 | type: 'object',
52 | properties: {
53 | success: {
54 | type: 'boolean'
55 | },
56 | transactions: {
57 | type: 'array',
58 | uniqueItems: true
59 | }
60 | },
61 | required: ['transactions']
62 | },
63 | 'GET:/peer/transactionsFromIds':{
64 | id: 'POST:/peer/transactionsFromIds',
65 | type: 'object'
66 | },
67 | 'GET:/peer/blocks':{
68 | id: 'GET:/peer/blocks',
69 | type: 'object',
70 | properties: {
71 | success: {
72 | type: 'boolean'
73 | },
74 | blocks: {
75 | type: 'array'
76 | },
77 | },
78 | required: ['blocks']
79 | },
80 | 'POST:/peer/blocks':{
81 | id: 'POST:/peer/blocks',
82 | type: 'object',
83 | properties: {
84 | success: {
85 | type: 'boolean'
86 | },
87 | blockId: {
88 | type: 'string'
89 | },
90 | },
91 | required: ['success', 'blockId']
92 | },
93 | 'GET:/peer/block':{
94 | id: 'GET:/peer/block',
95 | type: 'object'
96 | },
97 | 'GET:/peer/blocks/common':{
98 | id: 'GET:/peer/blocks/common',
99 | type: 'object'
100 | },
101 | 'GET:/peer/list':{
102 | id: 'GET:/peer/list',
103 | type: 'object',
104 | properties: {
105 | success: {
106 | type: 'boolean'
107 | },
108 | peers: {
109 | type: 'array'
110 | },
111 | },
112 | required: ['peers']
113 | }
114 | };
115 |
--------------------------------------------------------------------------------
/sql/transactions.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var TransactionsSql = {
4 | sortFields: [
5 | 'id',
6 | 'blockId',
7 | 'amount',
8 | 'fee',
9 | 'type',
10 | 'timestamp',
11 | 'senderPublicKey',
12 | 'senderId',
13 | 'recipientId',
14 | 'confirmations',
15 | 'height'
16 | ],
17 |
18 | countById: 'SELECT COUNT("id")::int AS "count" FROM transactions WHERE "id" = ${id}',
19 |
20 | countList: function (params) {
21 | return [
22 | 'SELECT COUNT("id") FROM transactions',
23 | (params.where.length || params.owner ? 'WHERE' : ''),
24 | (params.where.length ? '(' + params.where.join(' OR ') + ')' : ''),
25 | (params.where.length && params.owner ? ' AND ' + params.owner : params.owner)
26 | ].filter(Boolean).join(' ');
27 | },
28 |
29 | list: function (params) {
30 | // Need to fix 'or' or 'and' in query
31 | return [
32 | 'SELECT t.id, b.id as blockid, type, t.timestamp, amount, fee, "vendorField", "senderId", "recipientId", encode("senderPublicKey", \'hex\') as "senderPublicKey", encode("requesterPublicKey", \'hex\') as "requesterPublicKey", encode("signature", \'hex\') as "signature", encode("signSignature", \'hex\') as "signSignature", signatures::json as signatures, rawasset::json as asset, (SELECT MAX(height) + 1 FROM blocks) - b.height AS confirmations FROM transactions t, blocks b WHERE b.id = t."blockId"',
33 | (params.where.length || params.owner ? 'AND' : ''),
34 | (params.where.length ? '(' + params.where.join(' OR ') + ')' : ''),
35 | (params.where.length && params.owner ? ' AND ' + params.owner : params.owner),
36 | (params.sortField ? 'ORDER BY ' + [params.sortField, params.sortMethod].join(' ') : ''),
37 | 'LIMIT ${limit} OFFSET ${offset}'
38 | ].filter(Boolean).join(' ');
39 | },
40 |
41 | getById: 'SELECT t.id, b.id as blockid, b.height, type, t.timestamp, amount, fee, "vendorField", "senderId", "recipientId", encode("senderPublicKey", \'hex\') as "senderPublicKey", encode("requesterPublicKey", \'hex\') as "requesterPublicKey", encode("signature", \'hex\') as "signature", encode("signSignature", \'hex\') as "signSignature", signatures::json as signatures, rawasset::json as asset, (SELECT MAX(height) + 1 FROM blocks) - b.height AS confirmations FROM transactions t, blocks b WHERE b.id = t."blockId" AND t.id = ${id}',
42 |
43 | getVotesById: 'SELECT * FROM votes WHERE "transactionId" = ${id}'
44 |
45 | };
46 |
47 | module.exports = TransactionsSql;
48 |
--------------------------------------------------------------------------------
/config.main.json:
--------------------------------------------------------------------------------
1 | {
2 | "port": 8000,
3 | "address": "0.0.0.0",
4 | "version": "0.3.3",
5 | "fileLogLevel": "info",
6 | "logFileName": "logs/ark.log",
7 | "consoleLogLevel": "debug",
8 | "trustProxy": false,
9 | "db": {
10 | "host": "localhost",
11 | "port": 5432,
12 | "database": "ark_main",
13 | "user": null,
14 | "password": "password",
15 | "poolSize": 20,
16 | "poolIdleTimeout": 30000,
17 | "reapIntervalMillis": 1000,
18 | "logEvents": [
19 | "error"
20 | ]
21 | },
22 | "api": {
23 | "access": {
24 | "whiteList": []
25 | },
26 | "options": {
27 | "limits": {
28 | "max": 0,
29 | "delayMs": 0,
30 | "delayAfter": 0,
31 | "windowMs": 60000
32 | }
33 | }
34 | },
35 | "peers": {
36 | "list": [
37 | {
38 | "ip": "40.68.214.86",
39 | "port": 8000
40 | },
41 | {
42 | "ip": "13.70.207.248",
43 | "port": 8000
44 | },
45 | {
46 | "ip": "13.89.42.130",
47 | "port": 8000
48 | },
49 | {
50 | "ip": "52.160.98.183",
51 | "port": 8000
52 | },
53 | {
54 | "ip": "40.121.84.254",
55 | "port": 8000
56 | }
57 | ],
58 | "blackList": [],
59 | "options": {
60 | "limits": {
61 | "max": 0,
62 | "delayMs": 0,
63 | "delayAfter": 0,
64 | "windowMs": 60000
65 | },
66 | "maxUpdatePeers": 20,
67 | "timeout": 5000
68 | }
69 | },
70 | "forging": {
71 | "force": false,
72 | "secret": [],
73 | "access": {
74 | "whiteList": [
75 | "127.0.0.1"
76 | ]
77 | }
78 | },
79 | "loading": {
80 | "verifyOnLoading": false,
81 | "loadPerIteration": 5000
82 | },
83 | "ssl": {
84 | "enabled": false,
85 | "options": {
86 | "port": 443,
87 | "address": "0.0.0.0",
88 | "key": "./ssl/ark.key",
89 | "cert": "./ssl/ark.crt"
90 | }
91 | },
92 | "nethash": "ed14889723f24ecc54871d058d98ce91ff2f973192075c0155ba2b7b70ad2511"
93 | }
94 |
--------------------------------------------------------------------------------
/sql/migrations/20160908215531_protectMemAccountsColumns.sql:
--------------------------------------------------------------------------------
1 | /* Protect Mem Accounts Columns
2 | *
3 | */
4 |
5 | BEGIN;
6 |
7 | DROP TRIGGER IF EXISTS protect_mem_accounts ON "mem_accounts";
8 |
9 | DROP FUNCTION IF EXISTS revert_mem_account();
10 |
11 | CREATE FUNCTION revert_mem_account() RETURNS TRIGGER LANGUAGE PLPGSQL AS $$
12 | BEGIN
13 |
14 | -- As per columns marked as immutable within application layer (logic/account.js).
15 |
16 | -- Revert any change of address
17 | IF NEW."address" <> OLD."address" THEN
18 | RAISE WARNING 'Reverting change of address from % to %', OLD."address", NEW."address";
19 | NEW."address" = OLD."address";
20 | END IF;
21 |
22 | -- Revert any change of u_username
23 | IF NEW."u_username" <> OLD."u_username" AND OLD."u_username" IS NOT NULL THEN
24 | RAISE WARNING 'Reverting change of u_username from % to %', OLD."u_username", NEW."u_username";
25 | NEW."u_username" = OLD."u_username";
26 | END IF;
27 |
28 | -- Revert any change of username
29 | IF NEW."username" <> OLD."username" AND OLD."username" IS NOT NULL THEN
30 | RAISE WARNING 'Reverting change of username from % to %', OLD."username", NEW."username";
31 | NEW."username" = OLD."username";
32 | END IF;
33 |
34 | -- Revert any change of virginity
35 | -- If account is no longer a virgin
36 | IF NEW."virgin" <> OLD."virgin" AND OLD."virgin" = 0 THEN
37 | RAISE WARNING 'Reverting change of virgin from % to %', OLD."virgin", NEW."virgin";
38 | NEW."virgin" = OLD."virgin";
39 | END IF;
40 |
41 | -- Revert any change of publicKey
42 | -- If account is no longer a virgin
43 | -- And publicKey is already set
44 | IF NEW."publicKey" <> OLD."publicKey" AND OLD."virgin" = 0 AND OLD."publicKey" IS NOT NULL THEN
45 | RAISE WARNING 'Reverting change of publicKey from % to %', ENCODE(OLD."publicKey", 'hex'), ENCODE(NEW."publicKey", 'hex');
46 | NEW."publicKey" = OLD."publicKey";
47 | END IF;
48 |
49 | -- Revert any change of secondPublicKey
50 | -- If secondPublicKey is already set
51 | IF NEW."secondPublicKey" <> OLD."secondPublicKey" AND OLD."secondPublicKey" IS NOT NULL THEN
52 | RAISE WARNING 'Reverting change of secondPublicKey from % to %', ENCODE(OLD."secondPublicKey", 'hex'), ENCODE(NEW."secondPublicKey", 'hex');
53 | NEW."secondPublicKey" = OLD."secondPublicKey";
54 | END IF;
55 |
56 | RETURN NEW;
57 |
58 | END $$;
59 |
60 | CREATE TRIGGER protect_mem_accounts
61 | BEFORE UPDATE ON "mem_accounts" FOR EACH ROW
62 | EXECUTE PROCEDURE revert_mem_account();
63 |
64 | COMMIT;
65 |
--------------------------------------------------------------------------------
/schema/peers.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | headers: {
5 | id: 'peer.headers',
6 | type: 'object',
7 | properties: {
8 | port: {
9 | type: 'integer',
10 | minimum: 1,
11 | maximum: 65535
12 | },
13 | os: {
14 | type: 'string',
15 | maxLength: 64
16 | },
17 | nethash: {
18 | type: 'string',
19 | maxLength: 64
20 | },
21 | height: {
22 | type: 'integer',
23 | minimum: 0
24 | },
25 | version: {
26 | type: 'string',
27 | maxLength: 11
28 | },
29 | blockheader: {
30 | type: 'object'
31 | }
32 | },
33 | required: ['port', 'nethash', 'version']
34 | },
35 | updatePeersList: {
36 | peers: {
37 | id: 'peer.updatePeersList.peers',
38 | type: 'object',
39 | properties: {
40 | peers: {
41 | type: 'array',
42 | uniqueItems: true
43 | }
44 | },
45 | required: ['peers']
46 | },
47 | peer: {
48 | id: 'peer.updatePeersList.peer',
49 | type: 'object',
50 | properties: {
51 | ip: {
52 | type: 'string',
53 | format: 'ip'
54 | },
55 | port: {
56 | type: 'integer',
57 | minimum: 1,
58 | maximum: 65535
59 | },
60 | state: {
61 | type: 'integer',
62 | minimum: 0,
63 | maximum: 3
64 | },
65 | os: {
66 | type: 'string',
67 | maxLength: 64
68 | },
69 | version: {
70 | type: 'string',
71 | maxLength: 11
72 | }
73 | },
74 | required: ['ip', 'port']
75 | }
76 | },
77 | getPeers: {
78 | id: 'peer.getPeers',
79 | type: 'object',
80 | properties: {
81 | port: {
82 | type: 'integer',
83 | minimum: 1,
84 | maximum: 65535
85 | },
86 | state: {
87 | type: 'integer',
88 | minimum: 0,
89 | maximum: 3
90 | },
91 | os: {
92 | type: 'string',
93 | maxLength: 64
94 | },
95 | version: {
96 | type: 'string',
97 | maxLength: 11
98 | },
99 | orderBy: {
100 | type: 'string'
101 | },
102 | limit: {
103 | type: 'integer',
104 | minimum: 0,
105 | maximum: 100
106 | },
107 | offset: {
108 | type: 'integer',
109 | minimum: 0
110 | }
111 | }
112 | },
113 | getPeer: {
114 | id: 'peer.getPeer',
115 | type: 'object',
116 | properties: {
117 | ip: {
118 | type: 'string',
119 | format: 'ip'
120 | },
121 | port: {
122 | type: 'integer',
123 | minimum: 0,
124 | maximum: 65535
125 | }
126 | },
127 | required: ['ip', 'port']
128 | }
129 | };
130 |
--------------------------------------------------------------------------------
/sql/rounds.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var RoundsSql = {
4 |
5 | saveActiveDelegates: function (activedelegates) {
6 | var values = activedelegates.map(function(ad){
7 | return "('"+ad.publicKey+"', (${round})::bigint, ("+ad.vote+")::bigint)";
8 | }).join(",");
9 |
10 | return 'DELETE FROM mem_delegates where round = (${round})::bigint; INSERT INTO mem_delegates ("publicKey", round, vote) VALUES ' + values;
11 | },
12 |
13 | getActiveDelegates: 'SELECT * FROM mem_delegates WHERE round = (${round})::bigint ORDER BY vote DESC, "publicKey" ASC;',
14 |
15 | getRoundForgers: 'SELECT ENCODE("generatorPublicKey", \'hex\') as "publicKey" FROM blocks WHERE height > ${minheight} AND height < ${maxheight}+1 ORDER BY height desc;',
16 |
17 | updateActiveDelegatesStats: function (stats) {
18 | var statements = Object.keys(stats).map(function(pk){
19 | var stat = stats[pk];
20 | var statement = 'UPDATE mem_delegates SET ';
21 | statement += 'missedblocks = '+stat.missedblocks+',';
22 | statement += 'producedblocks = '+stat.producedblocks;
23 | statement += ' WHERE "publicKey" = \''+pk+'\' AND round = (${round})::bigint;';
24 | if(stat.missedblocks > 0){
25 | statement += 'UPDATE mem_accounts SET ';
26 | statement += 'missedblocks = missedblocks + ' + stat.missedblocks;
27 | statement += ' WHERE ENCODE("publicKey", \'hex\') = \''+pk+'\';';
28 | }
29 | return statement;
30 | });
31 |
32 | return statements.join("");
33 | },
34 |
35 | truncateBlocks: 'DELETE FROM blocks WHERE "height" > (${height})::bigint;',
36 |
37 | updateMissedBlocks: function (backwards) {
38 | return [
39 | 'UPDATE mem_accounts SET "missedblocks" = "missedblocks"',
40 | (backwards ? '- 1' : '+ 1'),
41 | 'WHERE "address" IN ($1:csv);'
42 | ].join(' ');
43 | },
44 |
45 | getTotalVotes: 'select ARRAY_AGG(a."accountId") as voters, SUM(b.balance) as vote FROM mem_accounts2delegates a, mem_accounts b where a."accountId" = b.address AND a."dependentId" = ${delegate};',
46 |
47 | updateVotes: 'UPDATE mem_accounts SET "vote" = "vote" + (${amount})::bigint WHERE "address" = ${address};',
48 |
49 | updateTotalVotes: 'UPDATE mem_accounts m SET vote = (SELECT COALESCE(SUM(b.balance), 0) as vote FROM mem_accounts2delegates a, mem_accounts b where a."accountId" = b.address AND a."dependentId" = encode(m."publicKey", \'hex\')) WHERE m."isDelegate" = 1;',
50 |
51 | updateBlockId: 'UPDATE mem_accounts SET "blockId" = ${newId} WHERE "blockId" = ${oldId};'
52 |
53 | };
54 |
55 | module.exports = RoundsSql;
56 |
--------------------------------------------------------------------------------
/logger.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var strftime = require('strftime').utc();
4 | var fs = require('fs');
5 | var util = require('util');
6 | require('colors');
7 |
8 | module.exports = function (config) {
9 | config = config || {};
10 | var exports = {};
11 |
12 | config.levels = config.levels || {
13 | trace: 0,
14 | debug: 1,
15 | log: 2,
16 | info: 3,
17 | warn: 4,
18 | error: 5,
19 | fatal: 6
20 | };
21 |
22 | config.level_abbr = config.level_abbr || {
23 | trace: 'trc',
24 | debug: 'dbg',
25 | log: 'log',
26 | info: 'inf',
27 | warn: 'WRN',
28 | error: 'ERR',
29 | fatal: 'FTL'
30 | };
31 |
32 | config.filename = config.filename || __dirname + '/logs.log';
33 |
34 | config.errorLevel = config.errorLevel || 'log';
35 |
36 | var log_file = fs.createWriteStream(config.filename, {flags: 'a'});
37 |
38 | exports.setLevel = function (errorLevel) {
39 | config.errorLevel = errorLevel;
40 | };
41 |
42 | function snipsecret (data) {
43 | for (var key in data) {
44 | if (key.search(/secret/i) > -1) {
45 | data[key] = 'XXXXXXXXXX';
46 | }
47 | }
48 | return data;
49 | }
50 |
51 | Object.keys(config.levels).forEach(function (name) {
52 | function log (message, data) {
53 | var log = {
54 | level: name,
55 | timestamp: strftime('%F %T', new Date())
56 | };
57 |
58 | if (message instanceof Error) {
59 | log.message = message.stack;
60 | } else {
61 |
62 | if(message){
63 | log.message = message.toString();
64 | if(log.message.startsWith("# ")){
65 | var head="#".repeat(message.length+2);
66 | log.message=head+"\n";
67 | log.message+=message+" #\n";
68 | log.message+=head;
69 | }
70 | }
71 | else {
72 | log.message = message;
73 | }
74 | }
75 |
76 | if (data && util.isObject(data)) {
77 | log.data = JSON.stringify(snipsecret(data));
78 | } else {
79 | log.data = data;
80 | }
81 |
82 | log.symbol = config.level_abbr[log.level] ? config.level_abbr[log.level] : '???';
83 |
84 | if (config.levels[config.errorLevel] <= config.levels[log.level]) {
85 | log.message.split("\n").forEach(function(m){
86 | if (log.data) {
87 | log_file.write(util.format('[%s] %s | %s - %s\n', log.symbol, log.timestamp, m, log.data));
88 | } else {
89 | log_file.write(util.format('[%s] %s | %s\n', log.symbol, log.timestamp, m));
90 | }
91 | });
92 | }
93 |
94 | if (config.echo && config.levels[config.echo] <= config.levels[log.level]) {
95 | log.message.split("\n").forEach(function(m){
96 | if (log.data) {
97 | console.log('['+log.symbol.bgYellow.black+']', log.timestamp.grey, '|', m, '-', log.data);
98 | } else {
99 | console.log('['+log.symbol.bgYellow.black+']', log.timestamp.grey, '|', m);
100 | }
101 | });
102 | }
103 | }
104 |
105 | exports[name] = log;
106 | });
107 |
108 | return exports;
109 | };
110 |
--------------------------------------------------------------------------------
/schema/delegates.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var constants = require('../helpers/constants.js');
4 |
5 | module.exports = {
6 | enableForging: {
7 | id: 'delegates.enableForging',
8 | type: 'object',
9 | properties: {
10 | secret: {
11 | type: 'string',
12 | minLength: 1,
13 | maxLength: 100
14 | },
15 | publicKey: {
16 | type: 'string',
17 | format: 'publicKey'
18 | }
19 | },
20 | required: ['secret']
21 | },
22 | disableForging: {
23 | id: 'delegates.disableForging',
24 | type: 'object',
25 | properties: {
26 | secret: {
27 | type: 'string',
28 | minLength: 1,
29 | maxLength: 100
30 | },
31 | publicKey: {
32 | type: 'string',
33 | format: 'publicKey'
34 | }
35 | },
36 | required: ['secret']
37 | },
38 | forgingStatus: {
39 | id: 'delegates.forgingStatus',
40 | type: 'object',
41 | properties: {
42 | publicKey: {
43 | type: 'string',
44 | format: 'publicKey'
45 | }
46 | },
47 | required: ['publicKey']
48 | },
49 | getDelegate: {
50 | id: 'delegates.getDelegate',
51 | type: 'object',
52 | properties: {
53 | publicKey: {
54 | type: 'string'
55 | },
56 | username: {
57 | type: 'string'
58 | }
59 | }
60 | },
61 | search: {
62 | id: 'delegates.search',
63 | type: 'object',
64 | properties: {
65 | q: {
66 | type: 'string',
67 | minLength: 1,
68 | maxLength: 20
69 | },
70 | limit: {
71 | type: 'integer',
72 | minimum: 1,
73 | maximum: 100
74 | }
75 | },
76 | required: ['q']
77 | },
78 | getVoters: {
79 | id: 'delegates.getVoters',
80 | type: 'object',
81 | properties: {
82 | publicKey: {
83 | type: 'string',
84 | format: 'publicKey'
85 | }
86 | },
87 | required: ['publicKey']
88 | },
89 | getDelegates: {
90 | id: 'delegates.getDelegates',
91 | type: 'object',
92 | properties: {
93 | orderBy: {
94 | type: 'string'
95 | },
96 | limit: {
97 | type: 'integer',
98 | minimum: 1,
99 | maximum: constants.activeDelegates
100 | },
101 | offset: {
102 | type: 'integer',
103 | minimum: 0
104 | }
105 | }
106 | },
107 | getForgedByAccount: {
108 | id: 'delegates.getForgedByAccount',
109 | type: 'object',
110 | properties: {
111 | generatorPublicKey: {
112 | type: 'string',
113 | format: 'publicKey'
114 | }
115 | },
116 | required: ['generatorPublicKey']
117 | },
118 | addDelegate: {
119 | id: 'delegates.addDelegate',
120 | type: 'object',
121 | properties: {
122 | secret: {
123 | type: 'string',
124 | minLength: 1,
125 | maxLength: 100
126 | },
127 | publicKey: {
128 | type: 'string',
129 | format: 'publicKey'
130 | },
131 | secondSecret: {
132 | type: 'string',
133 | minLength: 1,
134 | maxLength: 100
135 | },
136 | username: {
137 | type: 'string'
138 | }
139 | },
140 | required: ['secret']
141 | }
142 | };
143 |
--------------------------------------------------------------------------------
/test/api/peer.signatures.js:
--------------------------------------------------------------------------------
1 | 'use strict'; /*jslint mocha:true, expr:true */
2 |
3 | var node = require('./../node.js');
4 |
5 | describe('GET /peer/signatures', function () {
6 |
7 | it('using incorrect nethash in headers should fail', function (done) {
8 | node.get('/peer/signatures')
9 | .set('nethash', 'incorrect')
10 | .end(function (err, res) {
11 | node.debug('> Response:'.grey, JSON.stringify(res.body));
12 | node.expect(res.body).to.have.property('success').to.be.not.ok;
13 | node.expect(res.body.expected).to.equal(node.config.nethash);
14 | done();
15 | });
16 | });
17 |
18 | it('using valid headers should be ok', function (done) {
19 | node.get('/peer/signatures')
20 | .end(function (err, res) {
21 | node.debug('> Response:'.grey, JSON.stringify(res.body));
22 | node.expect(res.body).to.have.property('success').to.be.ok;
23 | node.expect(res.body).to.have.property('signatures').that.is.an('array');
24 | done();
25 | });
26 | });
27 | });
28 |
29 | describe('POST /peer/signatures', function () {
30 |
31 | var validParams;
32 |
33 | var transaction = node.ark.transaction.createTransaction('AacRfTLtxAkR3Mind1XdPCddj1uDkHtwzD', 1, null, node.gAccount.password);
34 |
35 | beforeEach(function (done) {
36 | validParams = {
37 | signature: {
38 | signature: transaction.signature,
39 | transaction: transaction.id
40 | }
41 | };
42 | done();
43 | });
44 |
45 | it('using incorrect nethash in headers should fail', function (done) {
46 | node.post('/peer/signatures')
47 | .set('nethash', 'incorrect')
48 | .end(function (err, res) {
49 | node.debug('> Response:'.grey, JSON.stringify(res.body));
50 | node.expect(res.body).to.have.property('success').to.be.not.ok;
51 | node.expect(res.body.expected).to.equal(node.config.nethash);
52 | done();
53 | });
54 | });
55 |
56 | it('using invalid signature schema should fail', function (done) {
57 | delete validParams.signature.transaction;
58 |
59 | node.post('/peer/signatures', validParams)
60 | .end(function (err, res) {
61 | node.debug('> Response:'.grey, JSON.stringify(res.body));
62 | node.expect(res.body).to.have.property('success').to.be.not.ok;
63 | node.expect(res.body).to.have.property('error').to.equal('Signature validation failed');
64 | done();
65 | });
66 | });
67 |
68 | it('using unprocessable signature should fail', function (done) {
69 | validParams.signature.transaction = 'invalidId';
70 |
71 | node.post('/peer/signatures', validParams)
72 | .end(function (err, res) {
73 | node.debug('> Response:'.grey, JSON.stringify(res.body));
74 | node.expect(res.body).to.have.property('success').to.be.not.ok;
75 | node.expect(res.body).to.have.property('error').to.equal('Error processing signature');
76 | done();
77 | });
78 | });
79 |
80 | it('using processable signature should be ok');
81 | });
82 |
--------------------------------------------------------------------------------
/schema/transactions.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var constants = require('../helpers/constants.js');
4 |
5 | module.exports = {
6 | getTransactions: {
7 | id: 'transactions.getTransactions',
8 | type: 'object',
9 | properties: {
10 | blockId: {
11 | type: 'string'
12 | },
13 | limit: {
14 | type: 'integer',
15 | minimum: 0,
16 | maximum: 100
17 | },
18 | type: {
19 | type: 'integer',
20 | minimum: 0,
21 | maximum: 10
22 | },
23 | orderBy: {
24 | type: 'string'
25 | },
26 | offset: {
27 | type: 'integer',
28 | minimum: 0
29 | },
30 | senderPublicKey: {
31 | type: 'string',
32 | format: 'publicKey'
33 | },
34 | vendorField: {
35 | type: 'string',
36 | format: 'vendorField'
37 | },
38 | ownerPublicKey: {
39 | type: 'string',
40 | format: 'publicKey'
41 | },
42 | ownerAddress: {
43 | type: 'string'
44 | },
45 | senderId: {
46 | type: 'string',
47 | format: 'address'
48 | },
49 | recipientId: {
50 | type: 'string',
51 | format: 'address'
52 | },
53 | amount: {
54 | type: 'integer',
55 | minimum: 0,
56 | maximum: constants.fixedPoint
57 | },
58 | fee: {
59 | type: 'integer',
60 | minimum: 0,
61 | maximum: constants.fixedPoint
62 | }
63 | }
64 | },
65 | getTransaction: {
66 | id: 'transactions.getTransaction',
67 | type: 'object',
68 | properties: {
69 | id: {
70 | type: 'string',
71 | minLength: 1
72 | }
73 | },
74 | required: ['id']
75 | },
76 | getUnconfirmedTransaction: {
77 | id: 'transactions.getUnconfirmedTransaction',
78 | type: 'object',
79 | properties: {
80 | id: {
81 | type: 'string',
82 | minLength: 1
83 | }
84 | },
85 | required: ['id']
86 | },
87 | getUnconfirmedTransactions: {
88 | id: 'transactions.getUnconfirmedTransactions',
89 | type: 'object',
90 | properties: {
91 | senderPublicKey: {
92 | type: 'string',
93 | format: 'publicKey'
94 | },
95 | address: {
96 | type: 'string'
97 | }
98 | }
99 | },
100 | addTransactions: {
101 | id: 'transactions.addTransactions',
102 | type: 'object',
103 | properties: {
104 | secret: {
105 | type: 'string',
106 | minLength: 1,
107 | maxLength: 100
108 | },
109 | amount: {
110 | type: 'integer',
111 | minimum: 1,
112 | maximum: constants.totalAmount
113 | },
114 | recipientId: {
115 | type: 'string',
116 | minLength: 1,
117 | format: 'address'
118 | },
119 | vendorField: {
120 | type: 'string',
121 | format: 'vendorField'
122 | },
123 | publicKey: {
124 | type: 'string',
125 | format: 'publicKey'
126 | },
127 | secondSecret: {
128 | type: 'string',
129 | minLength: 1,
130 | maxLength: 100
131 | },
132 | multisigAccountPublicKey: {
133 | type: 'string',
134 | format: 'publicKey'
135 | }
136 | },
137 | required: ['secret', 'amount', 'recipientId']
138 | }
139 | };
140 |
--------------------------------------------------------------------------------
/sql/migrations/20160724114255_createMemoryTables.sql:
--------------------------------------------------------------------------------
1 | /* Create Memory Tables
2 | *
3 | */
4 |
5 | BEGIN;
6 |
7 | CREATE TABLE IF NOT EXISTS "mem_accounts"(
8 | "username" VARCHAR(20),
9 | "isDelegate" SMALLINT DEFAULT 0,
10 | "u_isDelegate" SMALLINT DEFAULT 0,
11 | "secondSignature" SMALLINT DEFAULT 0,
12 | "u_secondSignature" SMALLINT DEFAULT 0,
13 | "u_username" VARCHAR(20),
14 | "address" VARCHAR(36) NOT NULL UNIQUE PRIMARY KEY,
15 | "publicKey" BYTEA,
16 | "secondPublicKey" BYTEA,
17 | "balance" BIGINT DEFAULT 0,
18 | "u_balance" BIGINT DEFAULT 0,
19 | "vote" BIGINT DEFAULT 0,
20 | "rate" BIGINT DEFAULT 0,
21 | "delegates" TEXT,
22 | "u_delegates" TEXT,
23 | "multisignatures" TEXT,
24 | "u_multisignatures" TEXT,
25 | "multimin" SMALLINT DEFAULT 0,
26 | "u_multimin" SMALLINT DEFAULT 0,
27 | "multilifetime" SMALLINT DEFAULT 0,
28 | "u_multilifetime" SMALLINT DEFAULT 0,
29 | "blockId" VARCHAR(64),
30 | "nameexist" SMALLINT DEFAULT 0,
31 | "u_nameexist" SMALLINT DEFAULT 0,
32 | "producedblocks" int DEFAULT 0,
33 | "missedblocks" int DEFAULT 0,
34 | "fees" BIGINT DEFAULT 0,
35 | "rewards" BIGINT DEFAULT 0,
36 | "virgin" SMALLINT DEFAULT 1
37 | );
38 |
39 | CREATE INDEX IF NOT EXISTS "mem_accounts_balance" ON "mem_accounts"("balance");
40 |
41 | CREATE TABLE IF NOT EXISTS "mem_delegates"(
42 | "publicKey" VARCHAR(66) NOT NULL,
43 | "vote" BIGINT NOT NULL,
44 | "round" BIGINT NOT NULL,
45 | "producedblocks" int,
46 | "missedblocks" int
47 | );
48 |
49 | CREATE TABLE IF NOT EXISTS "mem_accounts2delegates"(
50 | "accountId" VARCHAR(36) NOT NULL,
51 | "dependentId" VARCHAR(66) NOT NULL,
52 | FOREIGN KEY ("accountId") REFERENCES mem_accounts("address") ON DELETE CASCADE
53 | );
54 |
55 | CREATE TABLE IF NOT EXISTS "mem_accounts2u_delegates"(
56 | "accountId" VARCHAR(36) NOT NULL,
57 | "dependentId" VARCHAR(66) NOT NULL,
58 | FOREIGN KEY ("accountId") REFERENCES mem_accounts("address") ON DELETE CASCADE
59 | );
60 |
61 | CREATE TABLE IF NOT EXISTS "mem_accounts2multisignatures"(
62 | "accountId" VARCHAR(36) NOT NULL,
63 | "dependentId" VARCHAR(66) NOT NULL,
64 | FOREIGN KEY ("accountId") REFERENCES mem_accounts("address") ON DELETE CASCADE
65 | );
66 |
67 | CREATE TABLE IF NOT EXISTS "mem_accounts2u_multisignatures"(
68 | "accountId" VARCHAR(36) NOT NULL,
69 | "dependentId" VARCHAR(66) NOT NULL,
70 | FOREIGN KEY ("accountId") REFERENCES mem_accounts("address") ON DELETE CASCADE
71 | );
72 |
73 |
74 | CREATE INDEX IF NOT EXISTS "mem_delegates_vote" ON "mem_delegates"("vote");
75 |
76 | CREATE INDEX IF NOT EXISTS "mem_delegates_round" ON "mem_delegates"("round");
77 |
78 | CREATE INDEX IF NOT EXISTS "mem_accounts2delegates_accountId" ON "mem_accounts2delegates"("accountId");
79 |
80 | CREATE INDEX IF NOT EXISTS "mem_accounts2u_delegates_accountId" ON "mem_accounts2u_delegates"("accountId");
81 |
82 | CREATE INDEX IF NOT EXISTS "mem_accounts2multisignatures_accountId" ON "mem_accounts2multisignatures"("accountId");
83 |
84 | CREATE INDEX IF NOT EXISTS "mem_accounts2u_multisignatures_accountId" ON "mem_accounts2u_multisignatures"("accountId");
85 |
86 | COMMIT;
87 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ark-node",
3 | "version": "0.2.0",
4 | "private": true,
5 | "scripts": {
6 | "start": "node app.js",
7 | "doc": "docco -t docs/template/arkio.jst -c docs/template/arkio.css modules/* app.js README.md",
8 | "start:testnet": "node app.js --genesis genesisBlock.testnet.json --config config.testnet.json",
9 | "start:devnet": "node app.js --genesis genesisBlock.devnet.json --config config.devnet.json",
10 | "start:mainnet": "node app.js --genesis genesisBlock.mainnet.json --config config.mainnet.json -i",
11 | "start:test": "node app.js --genesis test/genesisBlock.json --config test/config.json",
12 | "test": "./node_modules/.bin/mocha",
13 | "cov": "./node_modules/.bin/mocha --require blanket -R html-cov test/helpers test/logic > tmp/coverage.html"
14 | },
15 | "author": "Boris Povod , Pavel Nekrasov , Oliver Beddows , FX Thoorens ",
16 | "dependencies": {
17 | "arkjs": "github:arkecosystem/ark-js#mainnet",
18 | "async": "=2.0.1",
19 | "bip39": "^2.2.0",
20 | "body-parser": "^1.18.3",
21 | "bytebuffer": "=5.0.1",
22 | "change-case": "=3.0.0",
23 | "colors": "=1.1.2",
24 | "commander": "=2.9.0",
25 | "compression": "^1.7.3",
26 | "cors": "=2.8.1",
27 | "ejs": "=2.5.5",
28 | "express": "^4.16.4",
29 | "express-domain-middleware": "=0.1.0",
30 | "express-query-int": "=1.0.1",
31 | "express-rate-limit": "=2.5.0",
32 | "extend": "=3.0.0",
33 | "ip": "=1.1.3",
34 | "json-schema": "=0.2.3",
35 | "json-sql": "LiskHQ/json-sql#27e1ed1",
36 | "lodash": "=4.17.5",
37 | "method-override": "=2.3.10",
38 | "pg-monitor": "=0.5.11",
39 | "pg-native": "=1.10.0",
40 | "pg-promise": "=5.3.3",
41 | "popsicle": "=8.2.0",
42 | "pug": "^2.0.0-beta11",
43 | "randomstring": "=1.1.5",
44 | "request-ip": "^2.1.1",
45 | "rimraf": "=2.5.4",
46 | "socket.io": "=1.4.8",
47 | "strftime": "=0.9.2",
48 | "valid-url": "=1.0.9",
49 | "validator": "=5.7.0",
50 | "validator.js": "=2.0.3",
51 | "vorpal": "^1.11.4",
52 | "z-schema": "=3.18.0"
53 | },
54 | "devDependencies": {
55 | "bitcore-mnemonic": "^1.5.0",
56 | "blanket": "=1.2.3",
57 | "browserify-bignum": "=1.3.0-2",
58 | "buffer": "=4.9.1",
59 | "chai": "=3.5.0",
60 | "chai-bignumber": "=2.0.0",
61 | "crypto-browserify": "=3.11.0",
62 | "csv": "=1.1.0",
63 | "faker": "=3.1.0",
64 | "grunt": "=0.4.5",
65 | "grunt-cli": "=1.2.0",
66 | "grunt-contrib-compress": "=1.3.0",
67 | "grunt-contrib-jshint": "=1.0.0",
68 | "grunt-exec": "=1.0.1",
69 | "grunt-jsdox": "=0.1.7",
70 | "grunt-obfuscator": "=0.1.0",
71 | "jsdox": "=0.4.10",
72 | "jshint": "^2.9.6",
73 | "mocha": "=3.1.0",
74 | "moment": "=2.22.2",
75 | "supertest": "=2.0.0"
76 | },
77 | "config": {
78 | "blanket": {
79 | "pattern": [
80 | "helpers",
81 | "logic"
82 | ],
83 | "data-cover-never": [
84 | "node_modules",
85 | "test",
86 | "tmp"
87 | ]
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/logic/blockReward.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var constants = require('../helpers/constants.js');
4 |
5 | // Private fields
6 | var __private = {};
7 |
8 | // Constructor
9 | function BlockReward () {
10 | // Array of milestones
11 | this.milestones = constants.rewards.milestones;
12 |
13 | // Distance between each milestone
14 | this.distance = Math.floor(constants.rewards.distance);
15 |
16 | // Start rewards at block (n)
17 | this.rewardOffset = Math.floor(constants.rewards.offset);
18 | }
19 |
20 | // Private methods
21 | __private.parseHeight = function (height) {
22 | if (isNaN(height)) {
23 | throw 'Invalid block height';
24 | } else {
25 | return Math.abs(height);
26 | }
27 | };
28 |
29 | // Public methods
30 | //
31 | //__API__ `calcMilestone`
32 |
33 | //
34 | BlockReward.prototype.calcMilestone = function (height) {
35 | var location = Math.trunc((__private.parseHeight(height) - this.rewardOffset) / this.distance);
36 | var lastMile = this.milestones[this.milestones.length - 1];
37 |
38 | if (location > (this.milestones.length - 1)) {
39 | return this.milestones.lastIndexOf(lastMile);
40 | } else {
41 | return location;
42 | }
43 | };
44 |
45 | //
46 | //__API__ `calcReward`
47 |
48 | //
49 | BlockReward.prototype.calcReward = function (height) {
50 | height = __private.parseHeight(height);
51 |
52 | if (height < this.rewardOffset) {
53 | return 0;
54 | } else {
55 | return this.milestones[this.calcMilestone(height)];
56 | }
57 | };
58 |
59 | //
60 | //__API__ `calcSupply`
61 |
62 | //
63 | BlockReward.prototype.calcSupply = function (height) {
64 | height = __private.parseHeight(height);
65 | var milestone = this.calcMilestone(height);
66 | var supply = constants.totalAmount / Math.pow(10,8);
67 | var rewards = [];
68 |
69 | var amount = 0, multiplier = 0;
70 |
71 | for (var i = 0; i < this.milestones.length; i++) {
72 | if (milestone >= i) {
73 | multiplier = (this.milestones[i] / Math.pow(10,8));
74 |
75 | if (height < this.rewardOffset) {
76 | break; // Rewards not started yet
77 | } else if (height < this.distance) {
78 | amount = height % this.distance; // Measure this.distance thus far
79 | } else {
80 | amount = this.distance; // Assign completed milestone
81 | height -= this.distance; // Deduct from total height
82 |
83 | // After last milestone
84 | if (height > 0 && i === this.milestones.length - 1) {
85 | var postHeight = this.rewardOffset - 1;
86 |
87 | if (height >= postHeight) {
88 | amount += (height - postHeight);
89 | } else {
90 | amount += (postHeight - height);
91 | }
92 | }
93 | }
94 |
95 | rewards.push([amount, multiplier]);
96 | } else {
97 | break; // Milestone out of bounds
98 | }
99 | }
100 |
101 | for (i = 0; i < rewards.length; i++) {
102 | var reward = rewards[i];
103 | supply += reward[0] * reward[1];
104 | }
105 |
106 | return supply * Math.pow(10,8);
107 | };
108 |
109 | // Export
110 | module.exports = BlockReward;
111 |
--------------------------------------------------------------------------------
/helpers/z_schema.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var ip = require('ip');
4 | var bs58check = require('bs58check');
5 |
6 | function schema(network){
7 | this.z_schema = require('z-schema');
8 |
9 | this.z_schema.registerFormat('hex', function (str) {
10 | try {
11 | new Buffer(str, 'hex');
12 | } catch (e) {
13 | return false;
14 | }
15 |
16 | return true;
17 | });
18 |
19 | this.z_schema.registerFormat('publicKey', function (str) {
20 | if (str.length === 0) {
21 | return true;
22 | }
23 |
24 | try {
25 | var publicKey = new Buffer(str, 'hex');
26 | return publicKey.length === 33;
27 | } catch (e) {
28 | return false;
29 | }
30 | });
31 |
32 | this.z_schema.registerFormat('address', function (str) {
33 | if (str.length === 0) {
34 | return true;
35 | }
36 |
37 | var version = network.pubKeyHash;
38 | try {
39 | var decode = bs58check.decode(str);
40 | return decode[0] == version;
41 | } catch(e){
42 | return false;
43 | }
44 | });
45 |
46 |
47 | this.z_schema.registerFormat('vendorField', function (str) {
48 | if (str.length === 0) {
49 | return true;
50 | }
51 |
52 | try {
53 | var vendorField = new Buffer(str);
54 |
55 | return vendorField.length < 65;
56 | } catch (e) {
57 | return false;
58 | }
59 | });
60 |
61 | this.z_schema.registerFormat('csv', function (str) {
62 | try {
63 | var a = str.split(',');
64 | if (a.length > 0 && a.length <= 1000) {
65 | return true;
66 | } else {
67 | return false;
68 | }
69 | } catch (e) {
70 | return false;
71 | }
72 | });
73 |
74 | this.z_schema.registerFormat('signature', function (str) {
75 | if (str.length === 0) {
76 | return true;
77 | }
78 |
79 | try {
80 | var signature = new Buffer(str, 'hex');
81 | return signature.length < 73;
82 | } catch (e) {
83 | return false;
84 | }
85 | });
86 |
87 | this.z_schema.registerFormat('voteString', function (str) {
88 | //Excluding capital hex characters
89 | //(mainnet database could contain mixed case vote strings?)
90 | return /^[-+]0[23][0-9a-fA-F]{64}$/.test(str);
91 | });
92 |
93 | this.z_schema.registerFormat('queryList', function (obj) {
94 | obj.limit = 100;
95 | return true;
96 | });
97 |
98 | this.z_schema.registerFormat('delegatesList', function (obj) {
99 | obj.limit = 51;
100 | return true;
101 | });
102 |
103 | this.z_schema.registerFormat('parsedInt', function (value) {
104 | /*jslint eqeq: true*/
105 | if (isNaN(value) || parseInt(value) != value || isNaN(parseInt(value, 10))) {
106 | return false;
107 | }
108 |
109 | value = parseInt(value);
110 | return true;
111 | });
112 |
113 | this.z_schema.registerFormat('ip', function (str) {
114 | return ip.isV4Format(str);
115 | });
116 | }
117 |
118 |
119 |
120 | // var registeredFormats = z_schema.getRegisteredFormats();
121 | // console.log(registeredFormats);
122 |
123 | // Exports
124 | module.exports = schema;
125 |
--------------------------------------------------------------------------------
/config.mainnet.json:
--------------------------------------------------------------------------------
1 | {
2 | "port": 4001,
3 | "address": "0.0.0.0",
4 | "version": "1.3.3",
5 | "fileLogLevel": "info",
6 | "logFileName": "logs/ark.log",
7 | "consoleLogLevel": "info",
8 | "trustProxy": false,
9 | "minimumVersion": "1.3.1",
10 | "db": {
11 | "host": "localhost",
12 | "port": 5432,
13 | "database": "ark_mainnet",
14 | "user": null,
15 | "password": "password",
16 | "poolSize": 20,
17 | "poolIdleTimeout": 30000,
18 | "reapIntervalMillis": 1000,
19 | "logEvents": [
20 | "error"
21 | ]
22 | },
23 | "api": {
24 | "mount": true,
25 | "access": {
26 | "whiteList": []
27 | },
28 | "options": {
29 | "limits": {
30 | "max": 0,
31 | "delayMs": 0,
32 | "delayAfter": 0,
33 | "windowMs": 60000
34 | }
35 | }
36 | },
37 | "peers": {
38 | "queryReach": 20,
39 | "minimumNetworkReach": 20,
40 | "list": [
41 | {"ip":"5.39.9.240", "port":4001},
42 | {"ip":"5.39.9.241", "port":4001},
43 | {"ip":"5.39.9.242", "port":4001},
44 | {"ip":"5.39.9.243", "port":4001},
45 | {"ip":"5.39.9.244", "port":4001},
46 | {"ip":"5.39.9.245", "port":4001},
47 | {"ip":"5.39.9.246", "port":4001},
48 | {"ip":"5.39.9.247", "port":4001},
49 | {"ip":"5.39.9.248", "port":4001},
50 | {"ip":"5.39.9.249", "port":4001},
51 | {"ip":"5.39.9.250", "port":4001},
52 | {"ip":"5.39.9.251", "port":4001},
53 | {"ip":"5.39.9.252", "port":4001},
54 | {"ip":"5.39.9.253", "port":4001},
55 | {"ip":"5.39.9.254", "port":4001},
56 | {"ip":"5.39.9.255", "port":4001},
57 | {"ip":"54.38.48.160", "port":4001},
58 | {"ip":"54.38.48.161", "port":4001},
59 | {"ip":"54.38.48.162", "port":4001},
60 | {"ip":"54.38.48.163", "port":4001},
61 | {"ip":"54.38.48.164", "port":4001},
62 | {"ip":"54.38.48.165", "port":4001},
63 | {"ip":"54.38.48.166", "port":4001},
64 | {"ip":"54.38.48.167", "port":4001},
65 | {"ip":"54.38.48.168", "port":4001},
66 | {"ip":"54.38.48.169", "port":4001},
67 | {"ip":"54.38.48.170", "port":4001},
68 | {"ip":"54.38.48.171", "port":4001},
69 | {"ip":"54.38.48.172", "port":4001},
70 | {"ip":"54.38.48.173", "port":4001},
71 | {"ip":"54.38.48.174", "port":4001},
72 | {"ip":"54.38.48.175", "port":4001}
73 | ],
74 | "blackList": [],
75 | "options": {
76 | "limits": {
77 | "max": 0,
78 | "delayMs": 0,
79 | "delayAfter": 0,
80 | "windowMs": 60000
81 | },
82 | "maxUpdatePeers": 20,
83 | "timeout": 5000
84 | }
85 | },
86 | "forging": {
87 | "coldstart": 30,
88 | "force": true,
89 | "secret": [],
90 | "access": {
91 | "whiteList": [
92 | "127.0.0.1"
93 | ]
94 | }
95 | },
96 | "loading": {
97 | "verifyOnLoading": false,
98 | "loadPerIteration": 5000
99 | },
100 | "ssl": {
101 | "enabled": false,
102 | "options": {
103 | "port": 443,
104 | "address": "0.0.0.0",
105 | "key": "./ssl/ark.key",
106 | "cert": "./ssl/ark.crt"
107 | }
108 | },
109 | "network":"ark",
110 | "nethash": "6e84d08bd299ed97c212c886c98a57e36545c8f5d645ca7eeae63a8bd62d8988"
111 | }
112 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var moment = require('moment');
4 | var util = require('util');
5 |
6 | module.exports = function (grunt) {
7 | var files = [
8 | 'logger.js',
9 | 'helpers/**/*.js',
10 | 'modules/*.js',
11 | 'logic/*.js',
12 | 'schema/**/*.js',
13 | 'sql/**/*.js',
14 | 'app.js'
15 | ];
16 |
17 | var today = moment().format('HH:mm:ss DD/MM/YYYY');
18 |
19 | var config = require('./config.json');
20 |
21 | var release_dir = __dirname + '/release/',
22 | version_dir = release_dir + config.version;
23 |
24 | grunt.initConfig({
25 | obfuscator: {
26 | files: files,
27 | entry: 'app.js',
28 | out: 'release/app.js',
29 | strings: true,
30 | root: __dirname
31 | },
32 |
33 | exec: {
34 | package: {
35 | command: function () {
36 | return [
37 | util.format('mkdir -p %s', version_dir),
38 | util.format('mkdir -p %s/logs', version_dir),
39 | util.format('mkdir -p %s/pids', version_dir),
40 | util.format('cp %s/app.js %s', release_dir, version_dir),
41 | util.format('cp %s/config.json %s', __dirname, version_dir),
42 | util.format('cp %s/package.json %s', __dirname, version_dir),
43 | util.format('cp %s/genesisBlock.json %s', __dirname, version_dir),
44 | util.format('mkdir -p %s/sql/migrations', version_dir),
45 | util.format('cp %s/sql/*.sql %s/sql/', __dirname, version_dir),
46 | util.format('cp %s/sql/migrations/*.sql %s/sql/migrations/', __dirname, version_dir)
47 | ].join(' && ');
48 | }
49 | },
50 | folder: {
51 | command: 'mkdir -p ' + release_dir
52 | },
53 | build: {
54 | command: 'cd ' + version_dir + '/ && touch build && echo "v' + today + '" > build'
55 | }
56 | },
57 |
58 | compress: {
59 | main: {
60 | options: {
61 | archive: version_dir + '.tar.gz',
62 | mode: 'tgz',
63 | level: 6
64 | },
65 | files: [
66 | { expand: true, cwd: release_dir, src: [config.version + '/**'], dest: './' }
67 | ]
68 | }
69 | },
70 |
71 | jsdox: {
72 | generate: {
73 | src: [
74 | 'helpers/*.js'
75 | // './modules/*.js'
76 | ],
77 | dest: 'tmp/docs',
78 | options: {
79 | templateDir: 'var/jsdox'
80 | }
81 | }
82 | },
83 |
84 | jshint: {
85 | options: {
86 | jshintrc: true
87 | },
88 | all: [
89 | '*.js',
90 | 'helpers/**/*.js',
91 | 'modules/**/*.js',
92 | 'logic/**/*.js',
93 | 'schema/**/*.js',
94 | 'sql/**/*.js',
95 | 'tasks/**/*.js',
96 | 'test/*.js',
97 | 'test/api/**/*.js',
98 | 'test/unit/**/*.js'
99 | ]
100 | },
101 |
102 | mochaTest: {
103 | test: {
104 | options: {
105 | reporter: 'spec',
106 | quiet: false,
107 | clearRequireCache: false,
108 | noFail: false,
109 | timeout: '250s'
110 | },
111 | src: ['test']
112 | }
113 | }
114 | });
115 |
116 | grunt.loadTasks('tasks');
117 |
118 | grunt.loadNpmTasks('grunt-obfuscator');
119 | grunt.loadNpmTasks('grunt-jsdox');
120 | grunt.loadNpmTasks('grunt-exec');
121 | grunt.loadNpmTasks('grunt-contrib-compress');
122 | grunt.loadNpmTasks('grunt-contrib-jshint');
123 | grunt.loadNpmTasks('grunt-mocha-test');
124 |
125 | grunt.registerTask('default', ['release']);
126 | grunt.registerTask('release', ['exec:folder', 'obfuscator', 'exec:package', 'exec:build', 'compress']);
127 | grunt.registerTask('travis', ['jshint', 'mochaTest']);
128 | };
129 |
--------------------------------------------------------------------------------
/test/api/peers.js:
--------------------------------------------------------------------------------
1 | 'use strict'; /*jslint mocha:true, expr:true */
2 |
3 | var node = require('./../node.js');
4 |
5 | describe('GET /api/peers/version', function () {
6 |
7 | it('should be ok', function (done) {
8 | node.get('/api/peers/version', function (err, res) {
9 | node.expect(res.body).to.have.property('success').to.be.ok;
10 | node.expect(res.body).to.have.property('build').to.be.a('string');
11 | node.expect(res.body).to.have.property('version').to.be.a('string');
12 | done();
13 | });
14 | });
15 | });
16 |
17 | describe('GET /api/peers', function () {
18 |
19 | it('using empty parameters should fail', function (done) {
20 | var params = [
21 | 'state=',
22 | 'os=',
23 | 'shared=',
24 | 'version=',
25 | 'limit=',
26 | 'offset=',
27 | 'orderBy='
28 | ];
29 |
30 | node.get('/api/peers?' + params.join('&'), function (err, res) {
31 | node.expect(res.body).to.have.property('success').to.be.not.ok;
32 | node.expect(res.body).to.have.property('error');
33 | done();
34 | });
35 | });
36 |
37 |
38 | it('using limit > 100 should fail', function (done) {
39 | var limit = 101;
40 | var params = 'limit=' + limit;
41 |
42 | node.get('/api/peers?' + params, function (err, res) {
43 | node.expect(res.body).to.have.property('success').to.be.not.ok;
44 | node.expect(res.body).to.have.property('error');
45 | done();
46 | });
47 | });
48 |
49 | it('using invalid parameters should fail', function (done) {
50 | var params = [
51 | 'state=invalid',
52 | 'os=invalid',
53 | 'shared=invalid',
54 | 'version=invalid',
55 | 'limit=invalid',
56 | 'offset=invalid',
57 | 'orderBy=invalid'
58 | ];
59 |
60 | node.get('/api/peers?' + params.join('&'), function (err, res) {
61 | node.expect(res.body).to.have.property('success').to.be.not.ok;
62 | node.expect(res.body).to.have.property('error');
63 | done();
64 | });
65 | });
66 | });
67 |
68 | describe('GET /api/peers/get', function () {
69 |
70 | var validParams;
71 |
72 | before(function (done) {
73 | node.addPeers(1, function (err, headers) {
74 | validParams = headers;
75 | done();
76 | });
77 | });
78 |
79 | it('using known ip address with no port should fail', function (done) {
80 | node.get('/api/peers/get?ip=127.0.0.1', function (err, res) {
81 | node.expect(res.body).to.have.property('success').to.be.not.ok;
82 | node.expect(res.body).to.have.property('error').to.equal('Missing required property: port');
83 | done();
84 | });
85 | });
86 |
87 | it('using valid port with no ip address should fail', function (done) {
88 | node.get('/api/peers/get?port=' + validParams.port, function (err, res) {
89 | node.expect(res.body).to.have.property('success').to.be.not.ok;
90 | node.expect(res.body).to.have.property('error').to.equal('Missing required property: ip');
91 | done();
92 | });
93 | });
94 |
95 | it('using known ip address and port should be ok', function (done) {
96 | node.get('/api/peers/get?ip=127.0.0.1&port=4000', function (err, res) {
97 | node.expect(res.body).to.have.property('success').to.be.ok;
98 | node.expect(res.body).to.have.property('peer').to.be.an('object');
99 | done();
100 | });
101 | });
102 |
103 | it('using unknown ip address and port should fail', function (done) {
104 | node.get('/api/peers/get?ip=0.0.0.0&port=' + validParams.port, function (err, res) {
105 | node.expect(res.body).to.have.property('success').to.be.not.ok;
106 | node.expect(res.body).to.have.property('error').to.equal('Peer not found');
107 | done();
108 | });
109 | });
110 | });
111 |
--------------------------------------------------------------------------------
/sql/migrations/20171123154400_addIndexOnVote.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Fix slow query execution times by adding an index on mem_accounts2delegates ("dependentId")
3 | Before index fix:
4 | ```
5 | ark_mainnet=> EXPLAIN (ANALYZE) UPDATE mem_accounts m
6 | SET vote = (SELECT COALESCE(SUM(b.balance), 0) AS vote
7 | FROM mem_accounts2delegates a, mem_accounts b
8 | WHERE a."accountId" = b.address AND a."dependentId" = encode(m."publicKey", 'hex'))
9 | WHERE m."isDelegate" = 1;
10 | Update on mem_accounts m (cost=0.00..571465.84 rows=601 width=345) (actual time=2801.314..2801.314 rows=0 loops=1)
11 | -> Seq Scan on mem_accounts m (cost=0.00..571465.84 rows=601 width=345) (actual time=8.935..2778.252 rows=594 loops=1)
12 | Filter: ("isDelegate" = 1)
13 | Rows Removed by Filter: 36990
14 | SubPlan 1
15 | -> Aggregate (cost=947.02..947.03 rows=1 width=8) (actual time=4.657..4.657 rows=1 loops=594)
16 | -> Nested Loop (cost=0.41..946.87 rows=61 width=8) (actual time=3.052..4.650 rows=23 loops=594)
17 | -> Seq Scan on mem_accounts2delegates a (cost=0.00..447.88 rows=61 width=35) (actual time=3.043..4.478 rows=23 loops=594)
18 | Filter: (("dependentId")::text = encode(m."publicKey", 'hex'::text))
19 | Rows Removed by Filter: 13392
20 | -> Index Scan using mem_accounts_pkey on mem_accounts b (cost=0.41..8.17 rows=1 width=43) (actual time=0.007..0.007 rows=1 loops=13415)
21 | Index Cond: ((address)::text = (a."accountId")::text)
22 | Planning time: 0.639 ms
23 | Trigger protect_mem_accounts: time=13.702 calls=594
24 | Execution time: 2801.446 ms
25 | ```
26 | After creating index fix:
27 | ```
28 | ark_mainnet=> EXPLAIN (ANALYZE) UPDATE mem_accounts m
29 | SET vote = (SELECT COALESCE(SUM(b.balance), 0) AS vote
30 | FROM mem_accounts2delegates a, mem_accounts b
31 | WHERE a."accountId" = b.address AND a."dependentId" = encode(m."publicKey", 'hex'))
32 | WHERE m."isDelegate" = 1;
33 | Update on mem_accounts m (cost=0.00..387200.62 rows=601 width=345) (actual time=120.801..120.801 rows=0 loops=1)
34 | -> Seq Scan on mem_accounts m (cost=0.00..387200.62 rows=601 width=345) (actual time=0.100..106.916 rows=594 loops=1)
35 | Filter: ("isDelegate" = 1)
36 | Rows Removed by Filter: 36990
37 | SubPlan 1
38 | -> Aggregate (cost=640.42..640.43 rows=1 width=8) (actual time=0.164..0.164 rows=1 loops=594)
39 | -> Nested Loop (cost=5.29..640.27 rows=60 width=8) (actual time=0.017..0.160 rows=23 loops=594)
40 | -> Bitmap Heap Scan on mem_accounts2delegates a (cost=4.88..145.72 rows=60 width=35) (actual time=0.013..0.024 rows=23 loops=594)
41 | Recheck Cond: (("dependentId")::text = encode(m."publicKey", 'hex'::text))
42 | Heap Blocks: exact=4125
43 | -> Bitmap Index Scan on "mem_accounts2delegates_dependentId_idx" (cost=0.00..4.86 rows=60 width=0) (actual time=0.011..0.011 rows=23 loops=594)
44 | Index Cond: (("dependentId")::text = encode(m."publicKey", 'hex'::text))
45 | -> Index Scan using mem_accounts_pkey on mem_accounts b (cost=0.41..8.23 rows=1 width=43) (actual time=0.005..0.005 rows=1 loops=13415)
46 | Index Cond: ((address)::text = (a."accountId")::text)
47 | Planning time: 0.517 ms
48 | Trigger protect_mem_accounts: time=7.787 calls=594
49 | Execution time: 120.902 ms
50 | ```
51 | */
52 |
53 | BEGIN;
54 |
55 | CREATE INDEX IF NOT EXISTS "mem_accounts2delegates_dependentId" ON mem_accounts2delegates ("dependentId");
56 |
57 | COMMIT;
--------------------------------------------------------------------------------
/logic/transfer.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var constants = require('../helpers/constants.js');
4 |
5 | // Private fields
6 | var modules, library;
7 |
8 | // Constructor
9 | function Transfer () {}
10 |
11 | // Public methods
12 | //
13 | //__API__ `bind`
14 |
15 | //
16 | Transfer.prototype.bind = function (scope) {
17 | modules = scope.modules;
18 | library = scope.library;
19 | };
20 |
21 | //
22 | //__API__ `create`
23 |
24 | //
25 | Transfer.prototype.create = function (data, trs) {
26 | trs.recipientId = data.recipientId;
27 | trs.amount = data.amount;
28 |
29 | return trs;
30 | };
31 |
32 | //
33 | //__API__ `calculateFee`
34 |
35 | //
36 | Transfer.prototype.calculateFee = function (trs) {
37 | return constants.fees.send;
38 | };
39 |
40 | //
41 | //__API__ `verify`
42 |
43 | //
44 | Transfer.prototype.verify = function (trs, sender, cb) {
45 | var isAddress = /^[1-9A-Za-z]{1,35}$/g;
46 | if (!trs.recipientId || !isAddress.test(trs.recipientId)) {
47 | return cb('Invalid recipient');
48 | }
49 |
50 | if (trs.amount <= 0) {
51 | return cb('Invalid transaction amount');
52 | }
53 |
54 | return cb(null, trs);
55 | };
56 |
57 | //
58 | //__API__ `process`
59 |
60 | //
61 | Transfer.prototype.process = function (trs, sender, cb) {
62 | return cb(null, trs);
63 | };
64 |
65 | //
66 | //__API__ `getBytes`
67 |
68 | //
69 | Transfer.prototype.getBytes = function (trs) {
70 | return null;
71 | };
72 |
73 | //
74 | //__API__ `apply`
75 |
76 | //
77 | Transfer.prototype.apply = function (trs, block, sender, cb) {
78 | modules.accounts.setAccountAndGet({address: trs.recipientId}, function (err, recipient) {
79 | if (err) {
80 | return cb(err);
81 | }
82 |
83 | modules.accounts.mergeAccountAndGet({
84 | address: trs.recipientId,
85 | balance: trs.amount,
86 | u_balance: trs.amount,
87 | blockId: block.id,
88 | round: modules.rounds.getRoundFromHeight(block.height)
89 | }, cb);
90 | });
91 | };
92 |
93 | //
94 | //__API__ `undo`
95 |
96 | //
97 | Transfer.prototype.undo = function (trs, block, sender, cb) {
98 | modules.accounts.setAccountAndGet({address: trs.recipientId}, function (err, recipient) {
99 | if (err) {
100 | return cb(err);
101 | }
102 |
103 | modules.accounts.mergeAccountAndGet({
104 | address: trs.recipientId,
105 | balance: -trs.amount,
106 | u_balance: -trs.amount,
107 | blockId: block.id,
108 | round: modules.rounds.getRoundFromHeight(block.height)
109 | }, cb);
110 | });
111 | };
112 |
113 | //
114 | //__API__ `applyUnconfirmed`
115 |
116 | //
117 | Transfer.prototype.applyUnconfirmed = function (trs, sender, cb) {
118 | return cb(null, trs);
119 | };
120 |
121 | //
122 | //__API__ `undoUnconfirmed`
123 |
124 | //
125 | Transfer.prototype.undoUnconfirmed = function (trs, sender, cb) {
126 | return cb(null, trs);
127 | };
128 |
129 | //
130 | //__API__ `objectNormalize`
131 |
132 | //
133 | Transfer.prototype.objectNormalize = function (trs) {
134 | delete trs.blockId;
135 | return trs;
136 | };
137 |
138 | //
139 | //__API__ `dbRead`
140 |
141 | //
142 | Transfer.prototype.dbRead = function (raw) {
143 | return null;
144 | };
145 |
146 | //
147 | //__API__ `dbSave`
148 |
149 | //
150 | Transfer.prototype.dbSave = function (trs) {
151 | return null;
152 | };
153 |
154 | //
155 | //__API__ `ready`
156 |
157 | //
158 | Transfer.prototype.ready = function (trs, sender) {
159 | if (Array.isArray(sender.multisignatures) && sender.multisignatures.length) {
160 | if (!Array.isArray(trs.signatures)) {
161 | return false;
162 | }
163 | return trs.signatures.length >= sender.multimin;
164 | } else {
165 | return true;
166 | }
167 | };
168 |
169 | // Export
170 | module.exports = Transfer;
171 |
--------------------------------------------------------------------------------
/sql/migrations/20160723182901_createViews.sql:
--------------------------------------------------------------------------------
1 | /* Create Views
2 | *
3 | */
4 |
5 | BEGIN;
6 |
7 | DROP VIEW IF EXISTS blocks_list;
8 |
9 | CREATE VIEW blocks_list AS
10 |
11 | SELECT b."id" AS "b_id",
12 | b."version" AS "b_version",
13 | b."timestamp" AS "b_timestamp",
14 | b."height" AS "b_height",
15 | b."previousBlock" AS "b_previousBlock",
16 | b."numberOfTransactions" AS "b_numberOfTransactions",
17 | b."totalAmount" AS "b_totalAmount",
18 | b."totalFee" AS "b_totalFee",
19 | b."reward" AS "b_reward",
20 | b."payloadLength" AS "b_payloadLength",
21 | ENCODE(b."payloadHash", 'hex') AS "b_payloadHash",
22 | ENCODE(b."generatorPublicKey", 'hex') AS "b_generatorPublicKey",
23 | ENCODE(b."blockSignature", 'hex') AS "b_blockSignature",
24 | (SELECT MAX("height") + 1 FROM blocks) - b."height" AS "b_confirmations"
25 |
26 | FROM blocks b;
27 |
28 | DROP VIEW IF EXISTS full_blocks_list;
29 |
30 | CREATE VIEW full_blocks_list AS
31 |
32 | SELECT b."id" AS "b_id",
33 | b."version" AS "b_version",
34 | b."timestamp" AS "b_timestamp",
35 | b."height" AS "b_height",
36 | b."previousBlock" AS "b_previousBlock",
37 | b."numberOfTransactions" AS "b_numberOfTransactions",
38 | (b."totalAmount")::bigint AS "b_totalAmount",
39 | (b."totalFee")::bigint AS "b_totalFee",
40 | (b."reward")::bigint AS "b_reward",
41 | b."payloadLength" AS "b_payloadLength",
42 | ENCODE(b."payloadHash", 'hex') AS "b_payloadHash",
43 | ENCODE(b."generatorPublicKey", 'hex') AS "b_generatorPublicKey",
44 | ENCODE(b."blockSignature", 'hex') AS "b_blockSignature",
45 | t."id" AS "t_id",
46 | t."rowId" AS "t_rowId",
47 | t."type" AS "t_type",
48 | t."timestamp" AS "t_timestamp",
49 | ENCODE(t."senderPublicKey", 'hex') AS "t_senderPublicKey",
50 | t."senderId" AS "t_senderId",
51 | t."recipientId" AS "t_recipientId",
52 | t."vendorField" AS "t_vendorField",
53 | (t."amount")::bigint AS "t_amount",
54 | (t."fee")::bigint AS "t_fee",
55 | ENCODE(t."signature", 'hex') AS "t_signature",
56 | ENCODE(t."signSignature", 'hex') AS "t_signSignature",
57 | ENCODE(s."publicKey", 'hex') AS "s_publicKey",
58 | d."username" AS "d_username",
59 | v."votes" AS "v_votes",
60 | m."min" AS "m_min",
61 | m."lifetime" AS "m_lifetime",
62 | m."keysgroup" AS "m_keysgroup",
63 | ENCODE(t."requesterPublicKey", 'hex') AS "t_requesterPublicKey",
64 | t."signatures" AS "t_signatures"
65 |
66 | FROM blocks b
67 |
68 | LEFT OUTER JOIN transactions AS t ON t."blockId" = b."id"
69 | LEFT OUTER JOIN delegates AS d ON d."transactionId" = t."id"
70 | LEFT OUTER JOIN votes AS v ON v."transactionId" = t."id"
71 | LEFT OUTER JOIN signatures AS s ON s."transactionId" = t."id"
72 | LEFT OUTER JOIN multisignatures AS m ON m."transactionId" = t."id";
73 |
74 | DROP VIEW IF EXISTS trs_list;
75 |
76 | CREATE VIEW trs_list AS
77 |
78 | SELECT t."id" AS "t_id",
79 | b."height" AS "b_height",
80 | t."blockId" AS "t_blockId",
81 | t."type" AS "t_type",
82 | t."timestamp" AS "t_timestamp",
83 | ENCODE(t."senderPublicKey", 'hex') AS "t_senderPublicKey",
84 | t."senderId" AS "t_senderId",
85 | t."recipientId" AS "t_recipientId",
86 | t."amount" AS "t_amount",
87 | t."fee" AS "t_fee",
88 | t."vendorField" AS "t_vendorField",
89 | ENCODE(t."signature", 'hex') AS "t_signature",
90 | ENCODE(t."signSignature", 'hex') AS "t_SignSignature",
91 | t."signatures" AS "t_signatures",
92 | (SELECT MAX("height") + 1 FROM blocks) - b."height" AS "confirmations"
93 |
94 | FROM transactions t
95 |
96 | INNER JOIN blocks b ON t."blockId" = b."id";
97 |
98 | COMMIT;
99 |
--------------------------------------------------------------------------------
/test/api/peer.transactions.stress.js:
--------------------------------------------------------------------------------
1 | 'use strict'; /*jslint mocha:true, expr:true */
2 |
3 | var node = require('./../node.js');
4 |
5 | function postTransaction (transaction, done) {
6 | node.post('/peer/transactions', {
7 | transactions: [transaction]
8 | }, done);
9 |
10 |
11 | }
12 |
13 | function postTransactions (transactions, done) {
14 | node.post('/peer/transactions', {
15 | transactions: transactions
16 | }, function(err, res){
17 | node.onNewBlock(function (err) {
18 | return done(err, res);
19 | });
20 | });
21 | }
22 |
23 | describe('POST /peer/transactions', function () {
24 |
25 | // describe('sending 1000 bundled transfers to random addresses', function () {
26 | //
27 | // var transactions = [];
28 | // var maximum = 1000;
29 | // var count = 1;
30 | //
31 | // before(function (done) {
32 | // node.async.doUntil(function (next) {
33 | // var bundled = [];
34 | //
35 | // for (var i = 0; i < 100; i++) {
36 | // var transaction = node.ark.transaction.createTransaction(
37 | // node.randomAccount().address,
38 | // node.randomNumber(100000000, 1000000000),
39 | // "stress test",
40 | // node.gAccount.password
41 | // );
42 | //
43 | // transactions.push(transaction);
44 | // bundled.push(transaction);
45 | // count++;
46 | // }
47 | //
48 | // postTransactions(bundled, function (err, res) {
49 | // node.expect(res.body).to.have.property('success').to.be.ok;
50 | // next();
51 | // });
52 | // }, function () {
53 | // return (count >= maximum);
54 | // }, function (err) {
55 | // done(err);
56 | // });
57 | // });
58 | //
59 | // it('should confirm all transactions', function (done) {
60 | // var blocksToWait = maximum / node.constants.maxTxsPerBlock + 1;
61 | // node.waitForBlocks(blocksToWait, function (err) {
62 | // node.async.eachSeries(transactions, function (transaction, eachSeriesCb) {
63 | // node.get('/api/transactions/get?id=' + transaction.id, function (err, res) {
64 | // node.expect(res.body).to.have.property('success').to.be.ok;
65 | // node.expect(res.body).to.have.property('transaction').that.is.an('object');
66 | // return setImmediate(eachSeriesCb);
67 | // });
68 | // }, done);
69 | // });
70 | // }).timeout(500000);
71 | // });
72 |
73 | describe('sending 1000 single transfers to random addresses', function () {
74 |
75 | var transactions = [];
76 | var maximum = 1000;
77 | var count = 1;
78 |
79 | before(function (done) {
80 | node.async.doUntil(function (next) {
81 | var transaction = node.ark.transaction.createTransaction(
82 | node.randomAccount().address,
83 | node.randomNumber(100000000, 1000000000),
84 | "stress test",
85 | node.gAccount.password
86 | );
87 |
88 | postTransaction(transaction, function (err, res) {
89 | node.expect(res.body).to.have.property('success').to.be.ok;
90 | node.expect(res.body).to.have.property('transactionId').to.equal(transaction.id);
91 | transactions.push(transaction);
92 | count++;
93 | next();
94 | });
95 | }, function () {
96 | return (count >= maximum);
97 | }, function (err) {
98 | done(err);
99 | });
100 | });
101 |
102 | it('should confirm all transactions', function (done) {
103 | var blocksToWait = maximum / node.constants.maxTxsPerBlock + 1;
104 | node.waitForBlocks(blocksToWait, function (err) {
105 | node.async.eachSeries(transactions, function (transaction, eachSeriesCb) {
106 | node.get('/api/transactions/get?id=' + transaction.id, function (err, res) {
107 | node.expect(res.body).to.have.property('success').to.be.ok;
108 | node.expect(res.body).to.have.property('transaction').that.is.an('object');
109 | return setImmediate(eachSeriesCb);
110 | });
111 | }, done);
112 | });
113 | }).timeout(500000);
114 | });
115 | });
116 |
--------------------------------------------------------------------------------
/sql/blocks.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var BlocksSql = {
4 | sortFields: [
5 | 'id',
6 | 'timestamp',
7 | 'height',
8 | 'previousBlock',
9 | 'totalAmount',
10 | 'totalFee',
11 | 'reward',
12 | 'numberOfTransactions',
13 | 'generatorPublicKey'
14 | ],
15 |
16 | getGenesisBlockId: 'SELECT "id" FROM blocks WHERE "id" = ${id}',
17 |
18 | deleteBlock: 'DELETE FROM blocks WHERE "id" = ${id};',
19 |
20 | countList: function (params) {
21 | return [
22 | 'SELECT COUNT("b_id")::int FROM blocks_list',
23 | (params.where.length ? 'WHERE ' + params.where.join(' AND ') : '')
24 | ].filter(Boolean).join(' ');
25 | },
26 |
27 | list: function (params) {
28 | return [
29 | 'SELECT * FROM blocks_list',
30 | (params.where.length ? 'WHERE ' + params.where.join(' AND ') : ''),
31 | (params.sortField ? 'ORDER BY ' + [params.sortField, params.sortMethod].join(' ') : ''),
32 | 'LIMIT ${limit} OFFSET ${offset}'
33 | ].filter(Boolean).join(' ');
34 | },
35 |
36 | getById: 'SELECT * FROM blocks_list WHERE "b_id" = ${id}',
37 |
38 | getIdSequence: 'SELECT (ARRAY_AGG("id" ORDER BY "height" ASC))[1] AS "id", MIN("height") AS "height", CAST("height" / ${delegates} AS INTEGER) + (CASE WHEN "height" % ${activeDelegates} > 0 THEN 1 ELSE 0 END) AS "round" FROM blocks WHERE "height" <= ${height} GROUP BY "round" ORDER BY "height" DESC LIMIT ${limit}',
39 |
40 | getCommonBlock: function (params) {
41 | return [
42 | 'SELECT COUNT("id")::int FROM blocks WHERE "id" = ${id}',
43 | (params.previousBlock ? 'AND "previousBlock" = ${previousBlock}' : ''),
44 | 'AND "height" = ${height}'
45 | ].filter(Boolean).join(' ');
46 | },
47 |
48 | countByRowId: 'SELECT COUNT("rowId")::int FROM blocks',
49 |
50 | getHeightByLastId: 'SELECT "height" FROM blocks WHERE "id" = ${lastId}',
51 |
52 | loadBlocksData: function (params) {
53 | var limitPart;
54 |
55 | if (!params.id && !params.lastId) {
56 | limitPart = 'WHERE height < ${limit}';
57 | }
58 |
59 | return [
60 | 'SELECT id, version, height, timestamp, "previousBlock", "numberOfTransactions" ,"totalAmount", "totalFee", reward, "payloadLength", encode("payloadHash", \'hex\') as "payloadHash", encode("generatorPublicKey", \'hex\') as "generatorPublicKey", encode("blockSignature", \'hex\') as "blockSignature", rawtxs::json as transactions from blocks',
61 | limitPart,
62 | (params.id || params.lastId ? 'WHERE' : ''),
63 | (params.id ? 'id = ${id}' : ''),
64 | (params.id && params.lastId ? ' AND ' : ''),
65 | (params.lastId ? 'height > ${height} AND height < ${limit}' : ''),
66 | 'ORDER BY height'
67 | ].filter(Boolean).join(' ');
68 | },
69 |
70 | loadBlocksOffset: 'SELECT id, version, height, timestamp, "previousBlock", "numberOfTransactions" ,"totalAmount", "totalFee", reward, "payloadLength", encode("payloadHash", \'hex\') as "payloadHash", encode("generatorPublicKey", \'hex\') as "generatorPublicKey", encode("blockSignature", \'hex\') as "blockSignature", rawtxs::json as transactions from blocks WHERE height >= ${offset} AND height < ${limit} ORDER BY height',
71 |
72 | loadLastBlock: 'SELECT id, version, height, timestamp, "previousBlock", "numberOfTransactions" ,"totalAmount", "totalFee", reward, "payloadLength", encode("payloadHash", \'hex\') as "payloadHash", encode("generatorPublicKey", \'hex\') as "generatorPublicKey", encode("blockSignature", \'hex\') as "blockSignature", rawtxs::json as transactions from blocks WHERE height = (SELECT MAX("height") FROM blocks)',
73 |
74 | getBlockId: 'SELECT "id" FROM blocks WHERE "id" = ${id}',
75 |
76 | getBlockById: 'SELECT id, version, height, timestamp, "previousBlock", "numberOfTransactions" ,"totalAmount", "totalFee", reward, "payloadLength", encode("payloadHash", \'hex\') as "payloadHash", encode("generatorPublicKey", \'hex\') as "generatorPublicKey", encode("blockSignature", \'hex\') as "blockSignature", rawtxs::json as transactions from blocks WHERE id = ${id}',
77 |
78 | getTransactionId: 'SELECT "id" FROM transactions WHERE "id" = ${id}',
79 |
80 | simpleDeleteAfterBlock: 'DELETE FROM blocks WHERE "height" >= (SELECT "height" FROM blocks WHERE "id" = ${id});'
81 | };
82 |
83 | module.exports = BlocksSql;
84 |
--------------------------------------------------------------------------------
/helpers/database.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var async = require('async');
4 | var bignum = require('./bignum');
5 | var fs = require('fs');
6 | var path = require('path');
7 |
8 | // var isWin = /^win/.test(process.platform);
9 | // var isMac = /^darwin/.test(process.platform);
10 |
11 | function Migrator (pgp, db) {
12 | this.checkMigrations = function (waterCb) {
13 | db.one('SELECT to_regclass(\'migrations\')').then(function (row) {
14 | return waterCb(null, Boolean(row.to_regclass));
15 | }).catch(function (err) {
16 | return waterCb(err);
17 | });
18 | };
19 |
20 | this.getLastMigration = function (hasMigrations, waterCb) {
21 | if (!hasMigrations) {
22 | return waterCb(null, null);
23 | }
24 | db.query('SELECT * FROM migrations ORDER BY "id" DESC LIMIT 1').then(function (rows) {
25 | if (rows[0]) {
26 | rows[0].id = bignum(rows[0].id);
27 | }
28 | return waterCb(null, rows[0]);
29 | }).catch(function (err) {
30 | return waterCb(err);
31 | });
32 | };
33 |
34 | this.readPendingMigrations = function (lastMigration, waterCb) {
35 | var migrationsPath = path.join(__dirname + '/../sql/migrations');
36 | var pendingMigrations = [];
37 |
38 | function matchMigrationName (file) {
39 | var name = file.match(/_.+\.sql$/);
40 |
41 | return Array.isArray(name) ? name[0].replace(/_/, '').replace(/\.sql$/, '') : null;
42 | }
43 |
44 | function matchMigrationId (file) {
45 | var id = file.match(/^[0-9]+/);
46 |
47 | return Array.isArray(id) ? bignum(id[0]): null;
48 | }
49 |
50 | fs.readdir(migrationsPath, function (err, files) {
51 | if (err) {
52 | return waterCb(err);
53 | }
54 |
55 | files.map(function (file) {
56 | return {
57 | id: matchMigrationId(file),
58 | name: matchMigrationName(file),
59 | path: path.join(migrationsPath, file)
60 | };
61 | }).filter(function (file) {
62 | return (
63 | (file.id && file.name) && fs.statSync(file.path).isFile() && /\.sql$/.test(file.path)
64 | );
65 | }).forEach(function (file) {
66 | if (!lastMigration || file.id.greaterThan(lastMigration.id)) {
67 | pendingMigrations.push(file);
68 | }
69 | });
70 |
71 | return waterCb(null, pendingMigrations);
72 | });
73 | };
74 |
75 | this.applyPendingMigrations = function (pendingMigrations, waterCb) {
76 | var appliedMigrations = [];
77 |
78 | async.eachSeries(pendingMigrations, function (file, eachCb) {
79 | var sql = new pgp.QueryFile(file.path, {minify: true});
80 |
81 | db.query(sql).then(function () {
82 | appliedMigrations.push(file);
83 | return eachCb();
84 | }).catch(function (err) {
85 | return eachCb(err);
86 | });
87 | }, function (err) {
88 | return waterCb(err, appliedMigrations);
89 | });
90 | };
91 |
92 | this.insertAppliedMigrations = function (appliedMigrations, waterCb) {
93 | async.eachSeries(appliedMigrations, function (file, eachCb) {
94 | db.query('INSERT INTO migrations(id, name) VALUES($1, $2) ON CONFLICT DO NOTHING', [file.id.toString(), file.name]).then(function () {
95 | return eachCb();
96 | }).catch(function (err) {
97 | return eachCb(err);
98 | });
99 | }, function (err) {
100 | return waterCb(err);
101 | });
102 | };
103 |
104 | this.applyRuntimeQueryFile = function (waterCb) {
105 | var sql = new pgp.QueryFile(path.join(__dirname + '/../sql/runtime.sql'), {minify: true});
106 |
107 | db.query(sql).then(function () {
108 | return waterCb();
109 | }).catch(function (err) {
110 | return waterCb(err);
111 | });
112 | };
113 | }
114 |
115 | module.exports.connect = function (config, logger, cb) {
116 | var pgOptions = {
117 | pgNative: true
118 | };
119 |
120 | var pgp = require('pg-promise')(pgOptions);
121 | var monitor = require('pg-monitor');
122 |
123 | monitor.attach(pgOptions, config.logEvents);
124 | monitor.setTheme('matrix');
125 |
126 | monitor.log = function(msg, info){
127 | logger.log(info.event, info.text);
128 | info.display = false;
129 | };
130 |
131 | config.user = config.user || process.env.USER;
132 |
133 | var db = pgp(config);
134 | var migrator = new Migrator(pgp, db);
135 |
136 | async.waterfall([
137 | migrator.checkMigrations,
138 | migrator.getLastMigration,
139 | migrator.readPendingMigrations,
140 | migrator.applyPendingMigrations,
141 | migrator.insertAppliedMigrations,
142 | migrator.applyRuntimeQueryFile
143 | ], function (err) {
144 | return cb(err, db);
145 | });
146 | };
147 |
--------------------------------------------------------------------------------
/test/unit/helpers/request-limiter.js:
--------------------------------------------------------------------------------
1 | 'use strict'; /*jslint mocha:true, expr:true */
2 |
3 | var chai = require('chai');
4 | var expect = require('chai').expect;
5 | var express = require('express');
6 |
7 | var RequestLimiter = require('../../../helpers/request-limiter.js');
8 |
9 | describe('RequestLimiter', function () {
10 |
11 | var app;
12 |
13 | beforeEach(function () {
14 | app = express();
15 | });
16 |
17 | describe('when config.trustProxy is undefined', function () {
18 |
19 | it('should not enable trust proxy', function () {
20 | RequestLimiter(app, {});
21 | expect(app.enabled('trust proxy')).to.be.false;
22 | });
23 | });
24 |
25 | describe('when config.trustProxy is == false', function () {
26 |
27 | it('should not enable trust proxy', function () {
28 | RequestLimiter(app, { trustProxy: false });
29 | expect(app.enabled('trust proxy')).to.be.false;
30 | });
31 | });
32 |
33 | describe('when config.trustProxy is == true', function () {
34 |
35 | it('should enable trust proxy', function () {
36 | RequestLimiter(app, { trustProxy: true });
37 | expect(app.enabled('trust proxy')).to.be.true;
38 | });
39 | });
40 |
41 | describe('when limits are undefined', function () {
42 |
43 | var limiter;
44 |
45 | beforeEach(function () {
46 | limiter = RequestLimiter(app, {});
47 | });
48 |
49 | it('should return the default client limits', function () {
50 | expect(limiter).to.be.a('object').that.has.property('client').that.is.a('object');
51 | expect(limiter.client).to.have.property('delayAfter').to.eql(0);
52 | expect(limiter.client).to.have.property('delayAfter').to.eql(0);
53 | expect(limiter.client).to.have.property('delayMs').to.eql(0);
54 | expect(limiter.client).to.have.property('max').to.eql(0);
55 | expect(limiter.client).to.have.property('windowMs').to.eql(60000);
56 | });
57 |
58 | it('should return the default peer limits', function () {
59 | expect(limiter).to.be.a('object').that.has.property('peer').that.is.a('object');
60 | expect(limiter.peer).to.have.property('delayAfter').to.eql(0);
61 | expect(limiter.peer).to.have.property('delayAfter').to.eql(0);
62 | expect(limiter.peer).to.have.property('delayMs').to.eql(0);
63 | expect(limiter.peer).to.have.property('max').to.eql(0);
64 | expect(limiter.peer).to.have.property('windowMs').to.eql(60000);
65 | });
66 |
67 | it('should enable the client middleware', function () {
68 | expect(limiter).to.be.a('object').that.has.property('middleware').that.is.a('object');
69 | expect(limiter.middleware).to.have.property('client').that.is.a('function');
70 | });
71 |
72 | it('should enable the peer middleware', function () {
73 | expect(limiter).to.be.a('object').that.has.property('middleware').that.is.a('object');
74 | expect(limiter.middleware).to.have.property('peer').that.is.a('function');
75 | });
76 | });
77 |
78 | describe('when limits are defined', function () {
79 |
80 | var limits, options, limiter;
81 |
82 | beforeEach(function () {
83 | limits = {
84 | max: 1,
85 | delayMs: 2,
86 | delayAfter: 3,
87 | windowMs: 4
88 | };
89 | options = { options: { limits: limits } };
90 | limiter = RequestLimiter(app, { api: options, peers: options });
91 | });
92 |
93 | it('should return the defined client limits', function () {
94 | expect(limiter).to.be.a('object').that.has.property('client').that.is.a('object');
95 | expect(limiter.client).to.have.property('max').to.eql(1);
96 | expect(limiter.client).to.have.property('delayMs').to.eql(2);
97 | expect(limiter.client).to.have.property('delayAfter').to.eql(3);
98 | expect(limiter.client).to.have.property('windowMs').to.eql(4);
99 | });
100 |
101 | it('should return the defined peer limits', function () {
102 | expect(limiter).to.be.a('object').that.has.property('peer').that.is.a('object');
103 | expect(limiter.peer).to.have.property('max').to.eql(1);
104 | expect(limiter.peer).to.have.property('delayMs').to.eql(2);
105 | expect(limiter.peer).to.have.property('delayAfter').to.eql(3);
106 | expect(limiter.peer).to.have.property('windowMs').to.eql(4);
107 | });
108 |
109 | it('should enable the client middleware', function () {
110 | expect(limiter).to.be.a('object').that.has.property('middleware').that.is.a('object');
111 | expect(limiter.middleware).to.have.property('client').that.is.a('function');
112 | });
113 |
114 | it('should enable the peer middleware', function () {
115 | expect(limiter).to.be.a('object').that.has.property('middleware').that.is.a('object');
116 | expect(limiter.middleware).to.have.property('peer').that.is.a('function');
117 | });
118 | });
119 | });
120 |
--------------------------------------------------------------------------------
/sql/migrations/20160723182900_createSchema.sql:
--------------------------------------------------------------------------------
1 | /* Create Schema
2 | *
3 | */
4 |
5 | BEGIN;
6 |
7 | /* Tables */
8 | CREATE TABLE IF NOT EXISTS "migrations"(
9 | "id" VARCHAR(22) NOT NULL PRIMARY KEY,
10 | "name" TEXT NOT NULL
11 | );
12 |
13 | CREATE TABLE IF NOT EXISTS "blocks"(
14 | "id" VARCHAR(64) PRIMARY KEY,
15 | "rowId" SERIAL NOT NULL,
16 | "version" INT NOT NULL,
17 | "timestamp" INT NOT NULL,
18 | "height" INT NOT NULL,
19 | "previousBlock" VARCHAR(64),
20 | "numberOfTransactions" INT NOT NULL,
21 | "totalAmount" BIGINT NOT NULL,
22 | "totalFee" BIGINT NOT NULL,
23 | "reward" BIGINT NOT NULL,
24 | "payloadLength" INT NOT NULL,
25 | "payloadHash" bytea NOT NULL,
26 | "generatorPublicKey" bytea NOT NULL,
27 | "blockSignature" bytea NOT NULL,
28 | "rawtxs" TEXT NOT NULL,
29 | FOREIGN KEY("previousBlock")
30 | REFERENCES "blocks"("id") ON DELETE SET NULL
31 | );
32 |
33 | CREATE TABLE IF NOT EXISTS "transactions"(
34 | "id" VARCHAR(64) PRIMARY KEY,
35 | "rowId" SERIAL NOT NULL,
36 | "blockId" VARCHAR(20) NOT NULL,
37 | "type" SMALLINT NOT NULL,
38 | "timestamp" INT NOT NULL,
39 | "senderPublicKey" bytea NOT NULL,
40 | "senderId" VARCHAR(36) NOT NULL,
41 | "recipientId" VARCHAR(36),
42 | "amount" BIGINT NOT NULL,
43 | "fee" BIGINT NOT NULL,
44 | "signature" bytea NOT NULL,
45 | "signSignature" bytea,
46 | "requesterPublicKey" bytea,
47 | "vendorField" VARCHAR(64),
48 | "signatures" TEXT,
49 | "rawasset" TEXT,
50 | FOREIGN KEY("blockId") REFERENCES "blocks"("id") ON DELETE CASCADE
51 | );
52 |
53 | CREATE TABLE IF NOT EXISTS "signatures"(
54 | "transactionId" VARCHAR(64) NOT NULL PRIMARY KEY,
55 | "publicKey" bytea NOT NULL,
56 | FOREIGN KEY("transactionId") REFERENCES transactions(id) ON DELETE CASCADE
57 | );
58 |
59 | CREATE TABLE IF NOT EXISTS "delegates"(
60 | "username" VARCHAR(20) NOT NULL,
61 | "transactionId" VARCHAR(64) NOT NULL,
62 | FOREIGN KEY("transactionId") REFERENCES "transactions"("id") ON DELETE CASCADE
63 | );
64 |
65 | CREATE TABLE IF NOT EXISTS "votes"(
66 | "votes" TEXT,
67 | "transactionId" VARCHAR(64) NOT NULL,
68 | FOREIGN KEY("transactionId") REFERENCES "transactions"("id") ON DELETE CASCADE
69 | );
70 |
71 | CREATE TABLE IF NOT EXISTS "forks_stat"(
72 | "delegatePublicKey" bytea NOT NULL,
73 | "blockTimestamp" INT NOT NULL,
74 | "blockId" VARCHAR(64) NOT NULL,
75 | "blockHeight" INT NOT NULL,
76 | "previousBlock" VARCHAR(64) NOT NULL,
77 | "cause" INT NOT NULL
78 | );
79 |
80 | CREATE TABLE IF NOT EXISTS "multisignatures"(
81 | "min" INT NOT NULL,
82 | "lifetime" INT NOT NULL,
83 | "keysgroup" TEXT NOT NULL,
84 | "transactionId" VARCHAR(64) NOT NULL,
85 | FOREIGN KEY("transactionId") REFERENCES "transactions"("id") ON DELETE CASCADE
86 | );
87 |
88 | CREATE TABLE IF NOT EXISTS "peers"(
89 | "id" SERIAL NOT NULL PRIMARY KEY,
90 | "ip" INET NOT NULL,
91 | "port" SMALLINT NOT NULL,
92 | "state" SMALLINT NOT NULL,
93 | "os" VARCHAR(64),
94 | "version" VARCHAR(11),
95 | "clock" BIGINT
96 | );
97 |
98 | /* Unique Indexes */
99 | CREATE UNIQUE INDEX IF NOT EXISTS "blocks_height" ON "blocks"("height");
100 | CREATE UNIQUE INDEX IF NOT EXISTS "blocks_previousBlock" ON "blocks"("previousBlock");
101 | CREATE UNIQUE INDEX IF NOT EXISTS "peers_unique" ON "peers"("ip", "port");
102 |
103 | /* Indexes */
104 | CREATE INDEX IF NOT EXISTS "blocks_rowId" ON "blocks"("rowId");
105 | CREATE INDEX IF NOT EXISTS "blocks_generator_public_key" ON "blocks"("generatorPublicKey");
106 | CREATE INDEX IF NOT EXISTS "blocks_reward" ON "blocks"("reward");
107 | CREATE INDEX IF NOT EXISTS "blocks_totalFee" ON "blocks"("totalFee");
108 | CREATE INDEX IF NOT EXISTS "blocks_totalAmount" ON "blocks"("totalAmount");
109 | CREATE INDEX IF NOT EXISTS "blocks_numberOfTransactions" ON "blocks"("numberOfTransactions");
110 | CREATE INDEX IF NOT EXISTS "blocks_timestamp" ON "blocks"("timestamp");
111 | CREATE INDEX IF NOT EXISTS "transactions_rowId" ON "transactions"("rowId");
112 | CREATE INDEX IF NOT EXISTS "transactions_block_id" ON "transactions"("blockId");
113 | CREATE INDEX IF NOT EXISTS "transactions_sender_id" ON "transactions"("senderId");
114 | CREATE INDEX IF NOT EXISTS "transactions_recipient_id" ON "transactions"("recipientId");
115 | CREATE INDEX IF NOT EXISTS "transactions_senderPublicKey" ON "transactions"("senderPublicKey");
116 | CREATE INDEX IF NOT EXISTS "transactions_type" ON "transactions"("type");
117 | CREATE INDEX IF NOT EXISTS "transactions_timestamp" ON "transactions"("timestamp");
118 | CREATE INDEX IF NOT EXISTS "signatures_transactions_id" ON "signatures"("transactionId");
119 | CREATE INDEX IF NOT EXISTS "votes_transactions_id" ON "votes"("transactionId");
120 | CREATE INDEX IF NOT EXISTS "delegates_transactions_id" ON "delegates"("transactionId");
121 | CREATE INDEX IF NOT EXISTS "multisignatures_transactions_id" ON "multisignatures"("transactionId");
122 |
123 | COMMIT;
124 |
--------------------------------------------------------------------------------
/helpers/validator/field.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = Field;
4 |
5 | /**
6 | *
7 | * @param {Validator} validator Validator instance
8 | * @param {string} path Validation field path
9 | * @param {*} value Validated value
10 | * @param {object} rules Set of rules
11 | * @param {*} thisArg Value used as this reference within rule callback calls.
12 | * @constructor
13 | */
14 | function Field (validator, path, value, rules, thisArg) {
15 | this.isAsync = false;
16 | this.hasError = false;
17 | this.rules = rules;
18 | this.value = value;
19 | this.report = [];
20 | this.path = path||[];
21 | this.thisArg = thisArg||null;
22 | this._stack = Object.keys(rules);
23 | this.validator = validator;
24 | this.inProgress = false;
25 | }
26 |
27 | /**
28 | * Create child field.
29 | * @param {string} path Validation field path
30 | * @param {*} value Validated value
31 | * @param {object} rules Set of rules
32 | * @param {*} thisArg Value used as this reference within rule callback calls.
33 | * @returns {Validator.Field}
34 | */
35 | //
36 | //__API__ `child`
37 |
38 | //
39 | Field.prototype.child = function (path, value, rules, thisArg) {
40 | var field = this.validator.createField(this.path.concat(path), value, rules, thisArg);
41 | field.report = this.report;
42 | return field;
43 | };
44 |
45 | /**
46 | * Validate field value and trigger callback on result
47 | * @param callback
48 | */
49 | //
50 | //__API__ `validate`
51 |
52 | //
53 | Field.prototype.validate = function(callback) {
54 | var stack = this._stack;
55 | // TODO copy value
56 | var report = this.report;
57 | var thisArg = this.thisArg;
58 | this.inProgress = true;
59 |
60 | if (typeof callback === 'function') {
61 | this.callback = callback;
62 | this.hasCallback = true;
63 | } else {
64 | this.callback = null;
65 | this.hasCallback = false;
66 | }
67 |
68 | var descriptor, result, accept, value;
69 | while (stack.length) {
70 | var rule = stack.shift();
71 | value = this.value;
72 | accept = this.rules[rule];
73 |
74 | try {
75 | if (!this.validator.hasRule(rule) && !this.validator.skipMissed) {
76 | throw new Error('Rule "' + rule + '" not found for "' + this.path.join('.') + '".');
77 | }
78 |
79 | descriptor = this.validator.getRule(rule);
80 |
81 | if (this.validator.execRules && typeof accept === 'function') {
82 | accept = accept.call(thisArg, value);
83 | }
84 |
85 | if (descriptor.accept) {
86 | accept = descriptor.accept.call(thisArg, accept, value, this);
87 | }
88 |
89 | if (descriptor.filter) {
90 | value = this.value = descriptor.filter.call(thisArg, accept, value, this);
91 | }
92 |
93 | if (descriptor.validate) {
94 | result = descriptor.validate.call(thisArg, accept, value, this);
95 | }
96 |
97 | if (this.isAsync) { return; }
98 |
99 | if (result === false) {
100 | report.push({
101 | path : this.path,
102 | rule : rule,
103 | accept : accept
104 | });
105 |
106 | this.hasError = true;
107 | stack.length = 0;
108 | }
109 | } catch (err) {
110 | if (!err.field) {
111 | Object.defineProperty(err, 'field', {
112 | enumerable : false,
113 | value : this
114 | });
115 | }
116 | this.validator.onError(this, err);
117 | this.end(err, report, value);
118 | return;
119 | }
120 | }
121 |
122 | this.inProgress = false;
123 |
124 | if (!stack.length) {
125 | this.end(null, report, value);
126 | }
127 | };
128 |
129 | /**
130 | * End validation. Drop validation stack.
131 | * @param {Error} err Report and error if passed. Optional
132 | */
133 | //
134 | //__API__ `end`
135 |
136 | //
137 | Field.prototype.end = function(err) {
138 | this._stack = [];
139 |
140 | if (this.hasError) {
141 | this.validator.onInvalid(this);
142 | } else {
143 | this.validator.onValid(this);
144 | }
145 |
146 | if (this.hasCallback) {
147 | this.callback(err, this.report, this.value);
148 | this.callback = null;
149 | }
150 | };
151 |
152 | /**
153 | * Create validation async. Callback get done function to emit validation end.
154 | * @param {function(done:function)} callback
155 | */
156 | //
157 | //__API__ `async`
158 |
159 | //
160 | Field.prototype.async = function(callback) {
161 | this.isAsync = true;
162 | var self = this;
163 | callback(function(err){
164 | if (arguments.length > 1) {
165 | self.value = arguments[1];
166 | }
167 |
168 | self.isAsync = false;
169 |
170 | if (err) {
171 | if (!err.hasOwnProperty('field')) {
172 | Object.defineProperty(err, 'field', {
173 | enumerable : false,
174 | value : self
175 | });
176 | self.validator.onError(self, err);
177 | }
178 | self.end(err);
179 | } else if (!self.inProgress) {
180 | self.validate(self.callback);
181 | }
182 | });
183 | };
184 |
185 | /**
186 | * Report an invalid validation result
187 | * @param {{}} report Validation report object
188 | */
189 | //
190 | //__API__ `issue`
191 |
192 | //
193 | Field.prototype.issue = function(report){
194 | this.hasError = true;
195 | report.path = this.path.concat(report.path);
196 | this.report.push(report);
197 | };
198 |
--------------------------------------------------------------------------------
/test/api/peer.transactions.signatures.js:
--------------------------------------------------------------------------------
1 | 'use strict'; /*jslint mocha:true, expr:true */
2 |
3 | var crypto = require('crypto');
4 | var node = require('./../node.js');
5 |
6 | var account = node.randomAccount();
7 | var account2 = node.randomAccount();
8 | var account3 = node.randomAccount();
9 |
10 | function postTransaction (transaction, done) {
11 | node.post('/peer/transactions', {
12 | transactions: [transaction]
13 | }, function (err, res) {
14 | done(err, res);
15 | });
16 | }
17 |
18 | function sendArk (params, done) {
19 | var transaction = node.ark.transaction.createTransaction(params.recipientId, params.amount, null, params.secret);
20 |
21 | postTransaction(transaction, function (err, res) {
22 | node.expect(res.body).to.have.property('success').to.be.ok;
23 | node.onNewBlock(function (err) {
24 | done(err, res);
25 | });
26 | });
27 | }
28 |
29 | describe('POST /peer/transactions', function () {
30 |
31 |
32 | describe('enabling second signature', function () {
33 |
34 | it('using undefined transaction', function (done) {
35 | postTransaction(undefined, function (err, res) {
36 | node.expect(res.body).to.have.property('success').to.be.not.ok;
37 | node.expect(res.body).to.have.property('error').to.equal("TypeError: Cannot read property 'type' of null");
38 | done();
39 | });
40 | });
41 |
42 | it('using undefined transaction.asset', function (done) {
43 | var transaction = node.ark.signature.createSignature(node.randomPassword(), node.randomPassword());
44 |
45 | delete transaction.asset;
46 |
47 | postTransaction(transaction, function (err, res) {
48 | node.expect(res.body).to.have.property('success').to.be.not.ok;
49 | node.expect(res.body).to.have.property('error').to.equal("TypeError: Cannot read property 'signature' of undefined");
50 | done();
51 | });
52 | });
53 |
54 | describe('when account has no funds', function () {
55 |
56 | it('should fail', function (done) {
57 | var transaction = node.ark.signature.createSignature(node.randomPassword(), node.randomPassword());
58 |
59 | postTransaction(transaction, function (err, res) {
60 | node.expect(res.body).to.have.property('success').to.be.not.ok;
61 | node.expect(res.body).to.have.property('error').to.match(/Account does not have enough ARK: [a-zA-Z0-9]+ balance: 0/);
62 | done();
63 | });
64 | });
65 | });
66 |
67 | describe('when account has funds', function () {
68 |
69 | before(function (done) {
70 | sendArk({
71 | secret: node.gAccount.password,
72 | amount: node.fees.secondPasswordFee + 100000000,
73 | recipientId: account.address
74 | }, done);
75 | });
76 |
77 | it('should be ok', function (done) {
78 | var transaction = node.ark.signature.createSignature(account.password, account.secondPassword);
79 | transaction.fee = node.fees.secondPasswordFee;
80 |
81 | postTransaction(transaction, function (err, res) {
82 | node.expect(res.body).to.have.property('success').to.be.ok;
83 | node.expect(res.body).to.have.property('transactionIds');
84 | node.expect(res.body.transactionIds[0]).to.equal(transaction.id);
85 | done();
86 | });
87 | });
88 | });
89 | });
90 |
91 | describe('using second signature', function () {
92 |
93 | var testaccount = node.randomAccount();
94 |
95 | before(function (done) {
96 | node.onNewBlock(function (err) {
97 | done();
98 | });
99 | });
100 |
101 | it('when account does not have one should fail', function (done) {
102 | var transaction = node.ark.transaction.createTransaction(testaccount.address, 1, null, node.gAccount.password, account.secondPassword);
103 |
104 | postTransaction(transaction, function (err, res) {
105 | node.expect(res.body).to.have.property('success').to.be.not.ok;
106 | done();
107 | });
108 | });
109 |
110 | it('using blank second passphrase should fail', function (done) {
111 | var transaction = node.ark.transaction.createTransaction(testaccount.address, 1, null, account.password, '');
112 |
113 | postTransaction(transaction, function (err, res) {
114 | node.expect(res.body).to.have.property('success').to.be.not.ok;
115 | done();
116 | });
117 | });
118 |
119 | it('using fake second passphrase should fail', function (done) {
120 | var transaction = node.ark.transaction.createTransaction(testaccount.address, 1, null, account.password, account2.secondPassword);
121 | transaction.signSignature = crypto.randomBytes(64).toString('hex');
122 | transaction.id = node.ark.crypto.getId(transaction);
123 |
124 | postTransaction(transaction, function (err, res) {
125 | node.expect(res.body).to.have.property('success').to.be.not.ok;
126 | done();
127 | });
128 | });
129 |
130 | it('using valid second passphrase should be ok', function (done) {
131 | var transaction = node.ark.transaction.createTransaction(testaccount.address, 1, null, account.password, account.secondPassword);
132 |
133 | postTransaction(transaction, function (err, res) {
134 | node.expect(res.body).to.have.property('success').to.be.ok;
135 | node.expect(res.body).to.have.property('transactionIds');
136 | node.expect(res.body.transactionIds[0]).to.equal(transaction.id);
137 | done();
138 | });
139 | });
140 | });
141 | });
142 |
--------------------------------------------------------------------------------
/test/api/signatures.js:
--------------------------------------------------------------------------------
1 | 'use strict'; /*jslint mocha:true, expr:true */
2 |
3 | var node = require('./../node.js');
4 |
5 | var account = node.randomTxAccount();
6 | var account2 = node.randomTxAccount();
7 | var account3 = node.randomTxAccount();
8 |
9 | function putSignature (params, done) {
10 | node.put('/api/signatures', params, done);
11 | }
12 |
13 | function putTransaction (params, done) {
14 | node.put('/api/transactions', params, done);
15 | }
16 |
17 | function putDelegate (params, done) {
18 | node.put('/api/delegates', params, done);
19 | }
20 |
21 | function sendArk (account, done) {
22 | var randomArk = node.randomArk();
23 | var expectedFee = node.expectedFee(randomArk);
24 |
25 | putTransaction({
26 | secret: node.gAccount.password,
27 | amount: randomArk,
28 | recipientId: account.address
29 | }, function (err, res) {
30 | node.expect(res.body).to.have.property('success').to.be.ok;
31 | done(err, res);
32 | });
33 | }
34 |
35 | before(function (done) {
36 | setTimeout(function () {
37 | sendArk(account, done);
38 | }, 2000);
39 | });
40 |
41 | before(function (done) {
42 | setTimeout(function () {
43 | sendArk(account2, done);
44 | }, 2000);
45 | });
46 |
47 | describe('PUT /api/signatures', function () {
48 |
49 | before(function (done) {
50 | node.onNewBlock(done);
51 | });
52 |
53 | var validParams;
54 |
55 | beforeEach(function (done) {
56 | validParams = {
57 | secret: account.password,
58 | secondSecret: account.secondPassword
59 | };
60 | done();
61 | });
62 |
63 | it('when account has no funds should fail', function (done) {
64 | validParams.secret = account3.password;
65 | validParams.secondSecret = account3.password;
66 |
67 | putSignature(validParams, function (err, res) {
68 | node.expect(res.body).to.have.property('success').to.be.not.ok;
69 | node.expect(res.body).to.have.property('error').to.match(/Account does not have enough ARK: [a-zA-Z0-9]+ balance: 0/);
70 | done();
71 | });
72 | });
73 |
74 | it('using invalid passphrase should fail', function (done) {
75 | validParams.secret = 'invalid';
76 |
77 | putSignature(validParams, function (err, res) {
78 | node.expect(res.body).to.have.property('success').to.be.not.ok;
79 | node.expect(res.body).to.have.property('error').to.match(/Account does not have enough ARK: [a-zA-Z0-9]+ balance: 0/);
80 | done();
81 | });
82 | });
83 |
84 | it('using no second passphrase should fail', function (done) {
85 | delete validParams.secondSecret;
86 |
87 | putSignature(validParams, function (err, res) {
88 | node.expect(res.body).to.have.property('success').to.be.not.ok;
89 | node.expect(res.body).to.have.property('error');
90 | done();
91 | });
92 | });
93 |
94 | it('using valid parameters should be ok', function (done) {
95 | putSignature(validParams, function (err, res) {
96 | node.expect(res.body).to.have.property('success').to.be.ok;
97 | node.expect(res.body).to.have.property('transaction').that.is.an('object');
98 | node.expect(res.body.transaction).to.have.property('type').to.equal(node.txTypes.SIGNATURE);
99 | node.expect(res.body.transaction).to.have.property('senderPublicKey').to.equal(account.publicKey);
100 | node.expect(res.body.transaction).to.have.property('senderId').to.equal(account.address);
101 | node.expect(res.body.transaction).to.have.property('fee').to.equal(node.fees.secondPasswordFee);
102 | done();
103 | });
104 | });
105 | });
106 |
107 | describe('PUT /api/transactions from account with second signature enabled', function () {
108 |
109 | before(function (done) {
110 | node.onNewBlock(done);
111 | });
112 |
113 | var validParams;
114 |
115 | beforeEach(function (done) {
116 | validParams = {
117 | secret: account.password,
118 | secondSecret: account.password,
119 | recipientId: account2.address,
120 | amount: 100000000
121 | };
122 | done();
123 | });
124 |
125 | it('using no second passphase should fail', function (done) {
126 | delete validParams.secondSecret;
127 |
128 | putTransaction(validParams, function (err, res) {
129 | node.expect(res.body).to.have.property('success').to.be.not.ok;
130 | node.expect(res.body).to.have.property('error');
131 | done();
132 | });
133 | });
134 |
135 | it('using second passphase but no primary passphase should fail', function (done) {
136 | delete validParams.secret;
137 |
138 | putTransaction(validParams, function (err, res) {
139 | node.expect(res.body).to.have.property('success').to.be.not.ok;
140 | node.expect(res.body).to.have.property('error');
141 | done();
142 | });
143 | });
144 | });
145 |
146 | describe('PUT /api/delegates from account with second signature enabled', function () {
147 |
148 | var validParams;
149 |
150 | beforeEach(function (done) {
151 | validParams = {
152 | secret: account.password,
153 | secondSecret: account.password,
154 | username: account.delegateName
155 | };
156 | done();
157 | });
158 |
159 | it('using no second passphase should fail', function (done) {
160 | delete validParams.secondSecret;
161 |
162 | putDelegate(validParams, function (err, res) {
163 | node.expect(res.body).to.have.property('success').to.be.not.ok;
164 | node.expect(res.body).to.have.property('error');
165 | done();
166 | });
167 | });
168 | });
169 |
--------------------------------------------------------------------------------
/logic/signature.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var ByteBuffer = require('bytebuffer');
4 | var constants = require('../helpers/constants.js');
5 |
6 | // Private fields
7 | var modules, library;
8 |
9 | // Constructor
10 | function Signature () {}
11 |
12 | //
13 | //__API__ `bind`
14 |
15 | //
16 | Signature.prototype.bind = function (scope) {
17 | modules = scope.modules;
18 | library = scope.library;
19 | };
20 |
21 | //
22 | //__API__ `create`
23 |
24 | //
25 | Signature.prototype.create = function (data, trs) {
26 | trs.recipientId = null;
27 | trs.amount = 0;
28 | trs.asset.signature = {
29 | publicKey: data.secondKeypair.publicKey
30 | };
31 |
32 | return trs;
33 | };
34 |
35 | //
36 | //__API__ `calculateFee`
37 |
38 | //
39 | Signature.prototype.calculateFee = function (trs) {
40 | return constants.fees.secondsignature;
41 | };
42 |
43 | //
44 | //__API__ `verify`
45 |
46 | //
47 | Signature.prototype.verify = function (trs, sender, cb) {
48 | if (!trs.asset || !trs.asset.signature) {
49 | return cb('Invalid transaction asset');
50 | }
51 |
52 | if (trs.amount !== 0) {
53 | return cb('Invalid transaction amount');
54 | }
55 |
56 | try {
57 | if (!trs.asset.signature.publicKey || new Buffer(trs.asset.signature.publicKey, 'hex').length !== 33) {
58 | return cb('Invalid public key');
59 | }
60 | } catch (e) {
61 | library.logger.error("stack", e.stack);
62 | return cb('Invalid public key');
63 | }
64 |
65 | return cb(null, trs);
66 | };
67 |
68 | //
69 | //__API__ `process`
70 |
71 | //
72 | Signature.prototype.process = function (trs, sender, cb) {
73 | return cb(null, trs);
74 | };
75 |
76 | //
77 | //__API__ `getBytes`
78 |
79 | //
80 | Signature.prototype.getBytes = function (trs) {
81 | var bb;
82 |
83 | try {
84 | bb = new ByteBuffer(33, true);
85 | var publicKeyBuffer = new Buffer(trs.asset.signature.publicKey, 'hex');
86 |
87 | for (var i = 0; i < publicKeyBuffer.length; i++) {
88 | bb.writeByte(publicKeyBuffer[i]);
89 | }
90 |
91 | bb.flip();
92 | } catch (e) {
93 | throw e;
94 | }
95 | return bb.toBuffer();
96 | };
97 |
98 | //
99 | //__API__ `apply`
100 |
101 | //
102 | Signature.prototype.apply = function (trs, block, sender, cb) {
103 | modules.accounts.setAccountAndGet({
104 | address: sender.address,
105 | secondSignature: 1,
106 | u_secondSignature: 0,
107 | secondPublicKey: trs.asset.signature.publicKey
108 | }, cb);
109 | };
110 |
111 | //
112 | //__API__ `undo`
113 |
114 | //
115 | Signature.prototype.undo = function (trs, block, sender, cb) {
116 | modules.accounts.setAccountAndGet({
117 | address: sender.address,
118 | secondSignature: 0,
119 | u_secondSignature: 1,
120 | secondPublicKey: null
121 | }, cb);
122 | };
123 |
124 | //
125 | //__API__ `applyUnconfirmed`
126 |
127 | //
128 | Signature.prototype.applyUnconfirmed = function (trs, sender, cb) {
129 | if (sender.u_secondSignature || sender.secondSignature) {
130 | return cb('Failed second signature: ' + trs.id);
131 | }
132 |
133 | modules.accounts.setAccountAndGet({address: sender.address, u_secondSignature: 1}, cb);
134 | };
135 |
136 | //
137 | //__API__ `undoUnconfirmed`
138 |
139 | //
140 | Signature.prototype.undoUnconfirmed = function (trs, sender, cb) {
141 | modules.accounts.setAccountAndGet({address: sender.address, u_secondSignature: 0}, cb);
142 | };
143 |
144 | Signature.prototype.schema = {
145 | id: 'Signature',
146 | object: true,
147 | properties: {
148 | publicKey: {
149 | type: 'string',
150 | format: 'publicKey'
151 | }
152 | },
153 | required: ['publicKey']
154 | };
155 |
156 | //
157 | //__API__ `objectNormalize`
158 |
159 | //
160 | Signature.prototype.objectNormalize = function (trs) {
161 | var report = library.schema.validate(trs.asset.signature, Signature.prototype.schema);
162 |
163 | if (!report) {
164 | throw 'Failed to validate signature schema: ' + this.scope.schema.getLastErrors().map(function (err) {
165 | return err.message;
166 | }).join(', ');
167 | }
168 |
169 | return trs;
170 | };
171 |
172 | //
173 | //__API__ `dbRead`
174 |
175 | //
176 | Signature.prototype.dbRead = function (raw) {
177 | if (!raw.s_publicKey) {
178 | return null;
179 | } else {
180 | var signature = {
181 | transactionId: raw.t_id,
182 | publicKey: raw.s_publicKey
183 | };
184 |
185 | return {signature: signature};
186 | }
187 | };
188 |
189 | Signature.prototype.dbTable = 'signatures';
190 |
191 | Signature.prototype.dbFields = [
192 | 'transactionId',
193 | 'publicKey'
194 | ];
195 |
196 | //
197 | //__API__ `dbSave`
198 |
199 | //
200 | Signature.prototype.dbSave = function (trs) {
201 | var publicKey;
202 |
203 | try {
204 | publicKey = new Buffer(trs.asset.signature.publicKey, 'hex');
205 | } catch (e) {
206 | throw e;
207 | }
208 |
209 | return {
210 | table: this.dbTable,
211 | fields: this.dbFields,
212 | values: {
213 | transactionId: trs.id,
214 | publicKey: publicKey
215 | }
216 | };
217 | };
218 |
219 | //
220 | //__API__ `ready`
221 |
222 | //
223 | Signature.prototype.ready = function (trs, sender) {
224 | if (Array.isArray(sender.multisignatures) && sender.multisignatures.length) {
225 | if (!Array.isArray(trs.signatures)) {
226 | return false;
227 | }
228 | return trs.signatures.length >= sender.multimin;
229 | } else {
230 | return true;
231 | }
232 | };
233 |
234 | // Export
235 | module.exports = Signature;
236 |
--------------------------------------------------------------------------------
/test/api/peer.transactions.delegates.js:
--------------------------------------------------------------------------------
1 | 'use strict'; /*jslint mocha:true, expr:true */
2 |
3 | var crypto = require('crypto');
4 | var node = require('./../node.js');
5 |
6 | var account = node.randomAccount();
7 | var account2 = node.randomAccount();
8 |
9 | function postTransaction (transaction, done) {
10 | node.post('/peer/transactions', {
11 | transactions: [transaction]
12 | }, function (err, res) {
13 | done(err, res);
14 | });
15 | }
16 |
17 | function sendArk (params, done) {
18 | var transaction = node.ark.transaction.createTransaction(params.recipientId, params.amount, null, params.secret);
19 |
20 | postTransaction(transaction, function (err, res) {
21 | node.expect(res.body).to.have.property('success').to.be.ok;
22 | node.onNewBlock(function (err) {
23 | done(err, res);
24 | });
25 | });
26 | }
27 |
28 | describe('POST /peer/transactions', function () {
29 |
30 | describe('registering a delegate', function () {
31 |
32 | it('using undefined transaction', function (done) {
33 | postTransaction(undefined, function (err, res) {
34 | node.expect(res.body).to.have.property('success').to.be.not.ok;
35 | node.expect(res.body).to.have.property('error').to.equal("TypeError: Cannot read property 'type' of null");
36 | done();
37 | });
38 | });
39 |
40 | it('using undefined transaction.asset', function (done) {
41 | var transaction = node.ark.delegate.createDelegate(node.randomPassword(), node.randomDelegateName().toLowerCase());
42 | transaction.fee = node.fees.delegateRegistrationFee;
43 |
44 | delete transaction.asset;
45 |
46 | postTransaction(transaction, function (err, res) {
47 | node.expect(res.body).to.have.property('success').to.be.not.ok;
48 | node.expect(res.body).to.have.property('message').to.equal("Invalid transaction detected");
49 | done();
50 | });
51 | });
52 |
53 | describe('when account has no funds', function () {
54 |
55 | it('should fail', function (done) {
56 | var transaction = node.ark.delegate.createDelegate(node.randomPassword(), node.randomDelegateName().toLowerCase());
57 | transaction.fee = node.fees.delegateRegistrationFee;
58 |
59 | postTransaction(transaction, function (err, res) {
60 | node.expect(res.body).to.have.property('success').to.be.not.ok;
61 | node.expect(res.body).to.have.property('error').to.match(/Account does not have enough ARK: [a-zA-Z0-9]+ balance: 0/);
62 | done();
63 | });
64 | });
65 | });
66 |
67 | describe('when account has funds', function () {
68 |
69 | before(function (done) {
70 | sendArk({
71 | secret: node.gAccount.password,
72 | amount: node.fees.delegateRegistrationFee,
73 | recipientId: account.address
74 | }, done);
75 | });
76 |
77 | it('using invalid username should fail', function (done) {
78 | var transaction = node.ark.delegate.createDelegate(account.password, crypto.randomBytes(64).toString('hex'));
79 | transaction.fee = node.fees.delegateRegistrationFee;
80 |
81 | postTransaction(transaction, function (err, res) {
82 | node.expect(res.body).to.have.property('success').to.be.not.ok;
83 | done();
84 | });
85 | });
86 |
87 | it('using uppercase username should fail', function (done) {
88 | account.username = node.randomDelegateName().toUpperCase();
89 | var transaction = node.ark.delegate.createDelegate(account.password, account.username);
90 |
91 | postTransaction(transaction, function (err, res) {
92 | node.expect(res.body).to.have.property('success').to.be.not.ok;
93 | done();
94 | });
95 | });
96 |
97 | describe('when lowercased username already registered', function () {
98 | it('using uppercase username should fail', function (done) {
99 | var transaction = node.ark.delegate.createDelegate(account2.password, account.username.toUpperCase());
100 |
101 | postTransaction(transaction, function (err, res) {
102 | node.expect(res.body).to.have.property('success').to.be.not.ok;
103 | done();
104 | });
105 | });
106 | });
107 |
108 | it('using lowercase username should be ok', function (done) {
109 | account.username = node.randomDelegateName().toLowerCase();
110 | var transaction = node.ark.delegate.createDelegate(account.password, account.username);
111 |
112 | postTransaction(transaction, function (err, res) {
113 | node.expect(res.body).to.have.property('success').to.be.ok;
114 | node.expect(res.body).to.have.property('transactionIds');
115 | node.expect(res.body.transactionIds[0]).to.equal(transaction.id);
116 | done();
117 | });
118 | });
119 | });
120 |
121 | describe('twice within the same block', function () {
122 |
123 | before(function (done) {
124 | sendArk({
125 | secret: node.gAccount.password,
126 | amount: (node.fees.delegateRegistrationFee * 2),
127 | recipientId: account2.address
128 | }, done);
129 | });
130 |
131 | it('should fail', function (done) {
132 | account2.username = node.randomDelegateName().toLowerCase();
133 | var transaction = node.ark.delegate.createDelegate(account2.password, account2.username);
134 |
135 | account2.username = node.randomDelegateName().toLowerCase();
136 | var transaction2 = node.ark.delegate.createDelegate(account2.password, account2.username);
137 |
138 | postTransaction(transaction, function (err, res) {
139 | node.expect(res.body).to.have.property('success').to.be.ok;
140 |
141 | postTransaction(transaction2, function (err, res) {
142 | node.expect(res.body).to.have.property('success').to.be.not.ok;
143 | done();
144 | });
145 | });
146 | });
147 | });
148 | });
149 | });
150 |
--------------------------------------------------------------------------------
/views/example.pug:
--------------------------------------------------------------------------------
1 |
2 | html(lang='en')
3 | head
4 | meta(charset='utf-8')
5 | meta(http-equiv='X-UA-Compatible', content='IE=edge')
6 | meta(name='viewport', content='width=device-width, initial-scale=1')
7 |
8 | meta(name='description', content='')
9 | meta(name='author', content='')
10 |
11 | link(rel="icon", href="https://ark.io/wp-content/uploads/2016/10/welcome-ark-1.png", sizes="32x32")
12 | title Ark Node
13 | // Bootstrap core CSS
14 | link(href='https://cdn.jsdelivr.net/bootstrap/3.3.7/css/bootstrap.min.css', rel='stylesheet')
15 | link(href='https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css', rel='stylesheet')
16 | link(href="//cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.css", rel="stylesheet")
17 | // HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries
18 | //if lt IE 9
19 | script(src='https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js')
20 | script(src='https://oss.maxcdn.com/respond/1.4.2/respond.min.js')
21 |
22 | body(style='background-image:url(https://ark.io/wp-content/uploads/2016/10/ark-planet.jpg); background-size:cover')
23 | block content
24 | nav.navbar.navbar-default.navbar-fixed-top
25 | .container
26 | .navbar-header
27 | button.navbar-toggle.collapsed(type='button', data-toggle='collapse', data-target='#navbar', aria-expanded='false', aria-controls='navbar')
28 | span.sr-only Toggle navigation
29 | span.icon-bar
30 | span.icon-bar
31 | span.icon-bar
32 | a.navbar-brand(href='/app/') Ark Node
33 | #navbar.navbar-collapse.collapse
34 | ul.nav.navbar-nav
35 | li(class=(title == 'Home') ? 'active' : null)
36 | a(href='/app/') Home
37 | li(class=(title == 'About') ? 'active' : null)
38 | a(href='/app/about') About
39 | ul.nav.navbar-nav.navbar-right
40 | if user
41 | li.dropdown
42 | a.navbar-avatar.dropdown-toggle(href='#', data-toggle='dropdown')
43 | span #{user.name || user.email}
44 | i.caret
45 | ul.dropdown-menu
46 | li
47 | a(href='profile') Profile
48 | li.divider(role='separator')
49 | li
50 | a(href='logout') Logout
51 | else
52 | li(class=(title == 'Log in') ? 'active' : null)
53 | a(href='/app/login') Log-in
54 | li(class=(title == 'Register') ? 'active' : null)
55 | a(href='/app/register') Register
56 | // /.nav-collapse
57 | .container(style='margin-top:50px;padding-top:15px;')
58 | .jumbotron(style='opacity:0.8;')
59 | h1
60 | img(src="https://ark.io/wp-content/uploads/2016/10/logo-red.png", alt="alt")
61 | | ARK node
62 | h3 Congratulations you just installed ark-node sucessfully
63 | ul
64 | li nethash
65 | pre #{nethash}
66 | .row
67 | .col-md-6
68 | .well(style='opacity:0.8;')
69 | h3 Transaction pool
70 | #tpoolchart
71 |
72 | .col-md-6
73 | .well(style='opacity:0.8;')
74 | h3 Node height
75 | #heightchart
76 |
77 | .col-md-6
78 | .well(style='opacity:0.8;')
79 | h3 Transactions per block
80 | #txchart
81 |
82 | .col-md-6
83 | .well(style='opacity:0.8;')
84 | h3 TPS
85 | #tpschart
86 |
87 |
88 | script(src='https://cdn.jsdelivr.net/jquery/3.1.1/jquery.min.js')
89 | script(src='https://cdn.jsdelivr.net/bootstrap/3.3.7/js/bootstrap.min.js')
90 | script(src='https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js')
91 | script(src='https://cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.min.js')
92 | script.
93 | var node = {};
94 | var updateTime = 4; //update every 4s
95 |
96 | function updateChart(type, value){
97 | node[type].data.push({
98 | date:new Date().getTime(),
99 | value:value
100 | });
101 | if(node[type].data.length>50){
102 | node[type].data.shift();
103 | }
104 | node[type].chart.setData(node[type].data);
105 | }
106 |
107 | function createChart(type, label, path){
108 | node[type] = {
109 | data: [],
110 | path: path,
111 | label: label
112 | };
113 | node[type].chart = new Morris.Line({
114 | element: type+'chart',
115 | data: node[type].data,
116 | pointSize:0,
117 | xkey: 'date',
118 | ykeys: ['value'],
119 | ymin: 'auto',
120 | smooth: false,
121 | labels: [label]
122 | });
123 | }
124 |
125 | var resolve = function(path, obj) {
126 | return path.split('.').reduce(function(prev, curr) {
127 | return prev ? prev[curr] : undefined
128 | }, obj || self)
129 | }
130 |
131 | var lastTpool;
132 | function updateData(){
133 | $.get("/app/getStats", function(res){
134 | res.tps=(res.transactionPool - lastTpool)/updateTime;
135 | if(!res.tps || res.tps<0) res.tps=0;
136 | lastTpool=res.transactionPool;
137 | for(var type in node){
138 | updateChart(type, resolve(node[type].path, res));
139 | }
140 |
141 | });
142 | };
143 |
144 | createChart('height', 'Height', 'lastBlock.height');
145 | createChart('tpool', 'Transaction pool', 'transactionPool');
146 | createChart('tx', 'Transactions', 'lastBlock.numberOfTransactions');
147 | createChart('tps', 'TPS', 'tps');
148 |
149 | updateData();
150 | setInterval(updateData, updateTime*1000);
151 |
--------------------------------------------------------------------------------
/test/api/accounts.js:
--------------------------------------------------------------------------------
1 | 'use strict'; /*jslint mocha:true, expr:true */
2 |
3 | var node = require('./../node.js');
4 |
5 | var account = node.randomAccount();
6 |
7 | describe('GET /api/accounts/getBalance?address=', function () {
8 |
9 | function getBalance (address, done) {
10 | node.get('/api/accounts/getBalance?address=' + address, done);
11 | }
12 |
13 | it('using known address should be ok', function (done) {
14 | getBalance(node.gAccount.address, function (err, res) {
15 | node.expect(res.body).to.have.property('success').to.be.ok;
16 | node.expect(res.body).to.have.property('balance').that.is.a('string');
17 | node.expect(res.body).to.have.property('unconfirmedBalance').that.is.a('string');
18 | done();
19 | });
20 | });
21 |
22 | it('using unknown address should be ok', function (done) {
23 | getBalance(account.address, function (err, res) {
24 | node.expect(res.body).to.have.property('success').to.be.ok;
25 | node.expect(res.body).to.have.property('balance').that.is.a('string');
26 | node.expect(res.body).to.have.property('unconfirmedBalance').that.is.a('string');
27 | done();
28 | });
29 | });
30 |
31 | it('using invalid address should fail', function (done) {
32 | getBalance('éthisIsNOTAArkAddress', function (err, res) {
33 | node.expect(res.body).to.have.property('success').to.be.not.ok;
34 | node.expect(res.body).to.have.property('error').to.contain('Object didn\'t pass validation for format address');
35 | done();
36 | });
37 | });
38 |
39 | it('using empty address should fail', function (done) {
40 | getBalance('', function (err, res) {
41 | node.expect(res.body).to.have.property('success').to.be.not.ok;
42 | node.expect(res.body).to.have.property('error');
43 | node.expect(res.body.error).to.contain('String is too short (0 chars), minimum 1');
44 | done();
45 | });
46 | });
47 | });
48 |
49 | describe('GET /api/accounts/getPublicKey?address=', function () {
50 |
51 | function getPublicKey (address, done) {
52 | node.get('/api/accounts/getPublicKey?address=' + address, done);
53 | }
54 |
55 | it('using known address should be ok', function (done) {
56 | getPublicKey(node.gAccount.address, function (err, res) {
57 | node.expect(res.body).to.have.property('success').to.be.ok;
58 | node.expect(res.body).to.have.property('publicKey').to.equal(node.gAccount.publicKey);
59 | done();
60 | });
61 | });
62 |
63 | it('using unknown address should be ok', function (done) {
64 | getPublicKey(account.address, function (err, res) {
65 | node.expect(res.body).to.have.property('success').to.be.not.ok;
66 | node.expect(res.body).to.have.property('error').to.contain('Account not found');
67 | done();
68 | });
69 | });
70 |
71 | it('using invalid address should fail', function (done) {
72 | getPublicKey('éthisIsNOTAArkAddress', function (err, res) {
73 | node.expect(res.body).to.have.property('success').to.be.not.ok;
74 | node.expect(res.body).to.have.property('error').to.contain('Object didn\'t pass validation for format address');
75 | done();
76 | });
77 | });
78 |
79 | it('using empty address should fail', function (done) {
80 | getPublicKey('', function (err, res) {
81 | node.expect(res.body).to.have.property('success').to.be.not.ok;
82 | node.expect(res.body).to.have.property('error');
83 | node.expect(res.body.error).to.contain('String is too short (0 chars), minimum 1');
84 | done();
85 | });
86 | });
87 | });
88 |
89 | describe('GET /accounts?address=', function () {
90 |
91 | function getAccounts (address, done) {
92 | node.get('/api/accounts?address=' + address, done);
93 | }
94 |
95 | it('using known address should be ok', function (done) {
96 | getAccounts(node.gAccount.address, function (err, res) {
97 | node.expect(res.body).to.have.property('success').to.be.ok;
98 | node.expect(res.body).to.have.property('account').that.is.an('object');
99 | node.expect(res.body.account).to.have.property('address').to.equal(node.gAccount.address);
100 | node.expect(res.body.account).to.have.property('unconfirmedBalance').that.is.a('string');
101 | node.expect(res.body.account).to.have.property('balance').that.is.a('string');
102 | node.expect(res.body.account).to.have.property('publicKey').to.equal(node.gAccount.publicKey);
103 | node.expect(res.body.account).to.have.property('unconfirmedSignature').to.equal(0);
104 | node.expect(res.body.account).to.have.property('secondSignature').to.equal(0);
105 | node.expect(res.body.account).to.have.property('secondPublicKey').to.equal(null);
106 | node.expect(res.body.account).to.have.property('multisignatures').to.a('array');
107 | node.expect(res.body.account).to.have.property('u_multisignatures').to.a('array');
108 | done();
109 | });
110 | });
111 |
112 | it('using known lowercase address should not be ok', function (done) {
113 | getAccounts(node.gAccount.address.toLowerCase(), function (err, res) {
114 | node.expect(res.body).to.have.property('success').to.be.not.ok;
115 | done();
116 | });
117 | });
118 |
119 | it('using unknown address should fail', function (done) {
120 | getAccounts(account.address, function (err, res) {
121 | node.expect(res.body).to.have.property('success').to.be.not.ok;
122 | node.expect(res.body).to.have.property('error').to.eql('Account not found');
123 | done();
124 | });
125 | });
126 |
127 | it('using invalid address should fail', function (done) {
128 | getAccounts('éthisIsNOTAValidArkAddress', function (err, res) {
129 | node.expect(res.body).to.have.property('success').to.be.not.ok;
130 | node.expect(res.body).to.have.property('error');
131 | node.expect(res.body.error).to.contain('Object didn\'t pass validation for format address');
132 | done();
133 | });
134 | });
135 |
136 | it('using empty address should fail', function (done) {
137 | getAccounts('', function (err, res) {
138 | node.expect(res.body).to.have.property('success').to.be.not.ok;
139 | node.expect(res.body).to.have.property('error');
140 | node.expect(res.body.error).to.contain('String is too short (0 chars), minimum 1');
141 | done();
142 | });
143 | });
144 | });
145 |
--------------------------------------------------------------------------------
/modules/signatures.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var async = require('async');
4 | var constants = require('../helpers/constants.js');
5 | var crypto = require('crypto');
6 | var MilestoneBlocks = require('../helpers/milestoneBlocks.js');
7 | var Router = require('../helpers/router.js');
8 | var schema = require('../schema/signatures.js');
9 | var slots = require('../helpers/slots.js');
10 | var transactionTypes = require('../helpers/transactionTypes.js');
11 |
12 | // Private fields
13 | var modules, library, self, __private = {}, shared = {};
14 |
15 | __private.assetTypes = {};
16 |
17 | // Constructor
18 | function Signatures (cb, scope) {
19 | library = scope;
20 | self = this;
21 |
22 | var Signature = require('../logic/signature.js');
23 | __private.assetTypes[transactionTypes.SIGNATURE] = library.logic.transaction.attachAssetType(
24 | transactionTypes.SIGNATURE, new Signature()
25 | );
26 |
27 | return cb(null, self);
28 | }
29 |
30 | // Private methods
31 | __private.attachApi = function () {
32 | var router = new Router();
33 |
34 | router.use(function (req, res, next) {
35 | if (modules) { return next(); }
36 | res.status(500).send({success: false, error: 'Blockchain is loading'});
37 | });
38 |
39 | router.map(shared, {
40 | 'get /fee': 'getFee',
41 | 'put /': 'addSignature'
42 | });
43 |
44 | router.use(function (req, res, next) {
45 | res.status(500).send({success: false, error: 'API endpoint not found'});
46 | });
47 |
48 | library.network.app.use('/api/signatures', router);
49 | library.network.app.use(function (err, req, res, next) {
50 | if (!err) { return next(); }
51 | library.logger.error('API error ' + req.url, err);
52 | res.status(500).send({success: false, error: 'API error: ' + err.message});
53 | });
54 | };
55 |
56 | // Public methods
57 |
58 | // Events
59 | //
60 | //__EVENT__ `onBind`
61 |
62 | //
63 | Signatures.prototype.onBind = function (scope) {
64 | modules = scope;
65 |
66 | __private.assetTypes[transactionTypes.SIGNATURE].bind({
67 | modules: modules, library: library
68 | });
69 | };
70 |
71 |
72 | //
73 | //__EVENT__ `onAttachPublicApi`
74 |
75 | //
76 | Signatures.prototype.onAttachPublicApi = function () {
77 | __private.attachApi();
78 | };
79 |
80 | // Shared
81 | shared.getFee = function (req, cb) {
82 | var fee = null;
83 |
84 | fee = constants.fees.secondsignature;
85 |
86 | return cb(null, {fee: fee});
87 | };
88 |
89 | shared.addSignature = function (req, cb) {
90 | library.schema.validate(req.body, schema.addSignature, function (err) {
91 | if (err) {
92 | return cb(err[0].message);
93 | }
94 |
95 | var keypair = library.crypto.makeKeypair(req.body.secret);
96 |
97 | if (req.body.publicKey) {
98 | if (keypair.publicKey.toString('hex') !== req.body.publicKey) {
99 | return cb('Invalid passphrase');
100 | }
101 | }
102 |
103 | library.balancesSequence.add(function (cb) {
104 | if (req.body.multisigAccountPublicKey && req.body.multisigAccountPublicKey !== keypair.publicKey.toString('hex')) {
105 | modules.accounts.getAccount({publicKey: req.body.multisigAccountPublicKey}, function (err, account) {
106 | if (err) {
107 | return cb(err);
108 | }
109 |
110 | if (!account || !account.publicKey) {
111 | return cb('Multisignature account not found');
112 | }
113 |
114 | if (!account.multisignatures || !account.multisignatures) {
115 | return cb('Account does not have multisignatures enabled');
116 | }
117 |
118 | if (account.multisignatures.indexOf(keypair.publicKey.toString('hex')) < 0) {
119 | return cb('Account does not belong to multisignature group');
120 | }
121 |
122 | if (account.secondSignature || account.u_secondSignature) {
123 | return cb('Account already has a second passphrase');
124 | }
125 |
126 | modules.accounts.getAccount({publicKey: keypair.publicKey}, function (err, requester) {
127 | if (err) {
128 | return cb(err);
129 | }
130 |
131 | if (!requester || !requester.publicKey) {
132 | return cb('Requester not found');
133 | }
134 |
135 | if (requester.secondSignature && !req.body.secondSecret) {
136 | return cb('Missing requester second passphrase');
137 | }
138 |
139 | if (requester.publicKey === account.publicKey) {
140 | return cb('Invalid requester public key');
141 | }
142 |
143 | var secondKeypair = library.crypto.makeKeypair(req.body.secondSecret);
144 | var transaction;
145 |
146 | try {
147 | transaction = library.logic.transaction.create({
148 | type: transactionTypes.SIGNATURE,
149 | sender: account,
150 | keypair: keypair,
151 | requester: keypair,
152 | secondKeypair: secondKeypair,
153 |
154 | });
155 | } catch (e) {
156 | return cb(e.toString());
157 | }
158 |
159 | library.bus.message("transactionsReceived", [transaction], "api", cb);
160 | });
161 | });
162 | } else {
163 | modules.accounts.setAccountAndGet({publicKey: keypair.publicKey.toString('hex')}, function (err, account) {
164 | if (err) {
165 | return cb(err);
166 | }
167 |
168 | if (!account || !account.publicKey) {
169 | return cb('Account not found');
170 | }
171 |
172 | if (account.secondSignature || account.u_secondSignature) {
173 | return cb('Account already has a second passphrase');
174 | }
175 |
176 | var secondKeypair = library.crypto.makeKeypair(req.body.secondSecret);
177 | var transaction;
178 |
179 | try {
180 | transaction = library.logic.transaction.create({
181 | type: transactionTypes.SIGNATURE,
182 | sender: account,
183 | keypair: keypair,
184 | secondKeypair: secondKeypair
185 | });
186 | } catch (e) {
187 | return cb(e.toString());
188 | }
189 |
190 | library.bus.message("transactionsReceived", [transaction], "api", cb);
191 | });
192 | }
193 |
194 | }, function (err, transaction) {
195 | if (err) {
196 | return cb(err);
197 | }
198 | return cb(null, {transaction: transaction[0]});
199 | });
200 |
201 | });
202 | };
203 |
204 | // Export
205 | module.exports = Signatures;
206 |
--------------------------------------------------------------------------------
/config.testnet.json:
--------------------------------------------------------------------------------
1 | {
2 | "port": 4000,
3 | "address": "0.0.0.0",
4 | "version": "1.0.0",
5 | "fileLogLevel": "info",
6 | "logFileName": "logs/ark.log",
7 | "consoleLogLevel": "debug",
8 | "trustProxy": false,
9 | "db": {
10 | "host": "localhost",
11 | "port": 5432,
12 | "database": "ark_testnet",
13 | "user": null,
14 | "password": "password",
15 | "poolSize": 20,
16 | "poolIdleTimeout": 30000,
17 | "reapIntervalMillis": 1000,
18 | "logEvents": [
19 | "error"
20 | ]
21 | },
22 | "api": {
23 | "mount": true,
24 | "access": {
25 | "whiteList": []
26 | },
27 | "options": {
28 | "limits": {
29 | "max": 0,
30 | "delayMs": 0,
31 | "delayAfter": 0,
32 | "windowMs": 60000
33 | }
34 | }
35 | },
36 | "peers": {
37 | "minimumNetworkReach": 1,
38 | "list": [
39 | {
40 | "ip": "127.0.0.1",
41 | "port": 4000
42 | }
43 | ],
44 | "blackList": [],
45 | "options": {
46 | "limits": {
47 | "max": 0,
48 | "delayMs": 0,
49 | "delayAfter": 0,
50 | "windowMs": 60000
51 | },
52 | "maxUpdatePeers": 20,
53 | "timeout": 5000
54 | }
55 | },
56 | "forging": {
57 | "coldstart": 6,
58 | "force": true,
59 | "secret": [
60 | "height dance bottom plastic circle scrap will creek invest fever degree oven",
61 | "runway home silent mutual blade nose spin marriage fancy main smooth champion",
62 | "elder alter stock acoustic jazz divide toward whip decorate summer wire length",
63 | "deputy shoe pizza eyebrow choice fury emotion morning naive that error opera",
64 | "define end worry style typical basket fresh chapter cloud enroll kidney juice",
65 | "gap open romance sadness north system jump vault debris correct spend exist",
66 | "history hollow pencil marine sentence chat plastic sniff focus final impulse toilet",
67 | "disorder exit caution joke kitchen broccoli response decorate machine outside this again",
68 | "paddle ridge young goddess tackle struggle radio door wink engage inhale pear",
69 | "false man grocery depth thought hammer vital jaguar seminar old tobacco field",
70 | "nerve finger use balcony capable soul mixed shallow hurt bench video below",
71 | "observe champion change history very soft bleak twenty cigar such penalty bunker",
72 | "nurse island tragic margin absorb danger naive whale blouse owner rifle average",
73 | "other nut young essay endorse talent sick flavor panel under brand cancel",
74 | "globe shine sand lonely problem tilt laugh garbage clutch submit distance arrive",
75 | "expose law earn crucial what bridge guard split delay average isolate home",
76 | "shoot they acid whip vicious injury future siren chuckle twin bless wrist",
77 | "property machine actor forest rigid dilemma such globe cross citizen across enemy",
78 | "survey parent uphold shine metal draw normal stove potato melt huge master",
79 | "office sting divorce fragile limit creek maple aware hour clutch code avocado",
80 | "debris city onion achieve screen village orange labor rice item someone much",
81 | "limit shaft canoe sorry domain exclude danger enter invite just meat price",
82 | "lock auto ankle want whip lottery picnic wolf permit start essay wasp",
83 | "dwarf capital weasel insane sauce cake ice toy suspect eye horn ecology",
84 | "gossip wreck cruise fault tortoise convince sauce meat bulk two bicycle host",
85 | "direct joke weapon other acoustic pulse blue word team flush boil supply",
86 | "unit swap skull magic flock bread sudden problem cushion intact able olympic",
87 | "hedgehog notable grant person riot vague recycle local naive debate rib gap",
88 | "into sauce sleep setup super element squeeze employ try decline brass educate",
89 | "wave drop air picnic vicious oil wheat stereo pride artefact food like",
90 | "devote kiss protect cook where welcome unaware express climb catalog wool little",
91 | "inner enforce have caution lawn proof because similar clarify dinner pig sadness",
92 | "hood welcome room pencil pen find must trend cat warfare hip fine",
93 | "deliver one turkey pelican derive sport oval minute poem myth also color",
94 | "secret pepper antique flip airport stadium angry talent act blur bulb hammer",
95 | "solve novel model upset demise thunder select number sun club region become",
96 | "scrub lawsuit dynamic matrix unhappy reason first taste bind dial lonely pair",
97 | "cousin negative reward elevator mosquito remember pizza slab park pulp pencil lock",
98 | "bicycle dignity burger public miss target oblige dragon ready wood velvet spatial",
99 | "fragile vicious movie field dentist sheriff soda sing gloom gold horse antenna",
100 | "dress jeans obscure update wisdom enact person little sting make napkin rural",
101 | "behind choose rally brand worth time coast decide used celery ribbon adapt",
102 | "solve property flash genius subway verb access portion orphan say taste gorilla",
103 | "sick bike dilemma ten room donkey fossil innocent limit learn inhale globe",
104 | "shoot oppose garment dial replace smoke razor loud cube exile story envelope",
105 | "assume harbor must knee shoulder file already apart today october target range",
106 | "mix judge volcano hidden road stumble phrase behave car setup inhale border",
107 | "jazz erase bargain city there thank vintage erase excuse wrong chronic lend",
108 | "client when person chuckle pen balance rescue measure price toy around soft",
109 | "narrow vague cannon home churn want sick rail above letter tourist switch",
110 | "general sock juice birth express dress harbor tobacco limb drive chimney tree"
111 | ],
112 | "access": {
113 | "whiteList": [
114 | "127.0.0.1"
115 | ]
116 | }
117 | },
118 | "loading": {
119 | "verifyOnLoading": false,
120 | "loadPerIteration": 5000
121 | },
122 | "ssl": {
123 | "enabled": false,
124 | "options": {
125 | "port": 443,
126 | "address": "0.0.0.0",
127 | "key": "./ssl/ark.key",
128 | "cert": "./ssl/ark.crt"
129 | }
130 | },
131 | "network": "testnet",
132 | "nethash": "d1c7fa0dcf7584add7a535acd674d3b13fa794345d83c3789cc2436d6ecd80d1"
133 | }
--------------------------------------------------------------------------------
/Vagrantfile:
--------------------------------------------------------------------------------
1 | # -*- mode: ruby -*-
2 | # vi: set ft=ruby :
3 |
4 | # All Vagrant configuration is done below. The "2" in Vagrant.configure
5 | # configures the configuration version (we support older styles for
6 | # backwards compatibility). Please don't change it unless you know what
7 | # you're doing.
8 | Vagrant.configure("2") do |config|
9 | # The most common configuration options are documented and commented below.
10 | # For a complete reference, please see the online documentation at
11 | # https://docs.vagrantup.com.
12 |
13 | # Every Vagrant development environment requires a box. You can search for
14 | # boxes at https://atlas.hashicorp.com/search.
15 | config.vm.box = "ubuntu/trusty64"
16 | config.vm.network "forwarded_port", guest: 4000, host: 4000
17 | config.vm.provision :shell, privileged: false, inline: <<-SHELL
18 |
19 | ##########################################
20 | # #
21 | # Checking and installing prerequisites #
22 | # #
23 | ##########################################
24 |
25 | # Variables and arrays declarations
26 | log="ark-install.log"
27 |
28 | sudo apt-get update
29 | echo -e "Installing tools... "
30 | sudo apt-get install -yyq build-essential wget python git curl jq htop nmon iftop
31 |
32 | if [ $(dpkg-query -W -f='${Status}' postgresql 2>/dev/null | grep -c "ok installed") -eq 0 ];
33 | then
34 | echo -e "Installing postgresql... "
35 | sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main" >> /etc/apt/sources.list.d/pgdg.list'
36 | wget -q https://www.postgresql.org/media/keys/ACCC4CF8.asc -O - | sudo apt-key add -
37 | sudo apt-get update
38 | sudo apt-get install -yyq postgresql postgresql-contrib libpq-dev
39 | else
40 | echo -e "Postgresql is already installed."
41 | fi
42 |
43 | if ! sudo pgrep -x "ntpd" > /dev/null; then
44 | echo -e "No NTP found. Installing... "
45 | sudo apt-get install ntp -yyq &>> $log
46 | sudo service ntp stop &>> $log
47 | sudo ntpd -gq &>> $log
48 | sudo service ntp start &>> $log
49 | if ! sudo pgrep -x "ntpd" > /dev/null; then
50 | echo -e "NTP failed to start! It should be installed and running for ARK.\n Check /etc/ntp.conf for any issues and correct them first! \n Exiting."
51 | exit 1
52 | fi
53 | echo -e "NTP was successfuly installed and started with PID:" `grep -x "ntpd"`
54 | else echo "NTP is up and running with PID:" `pgrep -x "ntpd"`
55 |
56 | fi # if sudo pgrep
57 |
58 | echo "-------------------------------------------------------------------"
59 |
60 | # Installing node
61 | curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
62 | sudo apt-get install nodejs
63 | sudo npm install -g n
64 |
65 | sudo n 6.9.1
66 | sudo npm install forever -g
67 |
68 | echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
69 |
70 | # Creating DB and user
71 | sudo -u postgres psql -c "CREATE USER $USER WITH PASSWORD 'password';"
72 | #sudo -u postgres createuser --createdb --password $USER
73 | sudo -u postgres createdb -O $USER ark_testnet
74 | sudo service postgresql start
75 |
76 | git clone https://github.com/arkecosystem/ark-node.git
77 |
78 | cd /home/vagrant/ark-node
79 | #rm -fr node_modules
80 | npm install grunt-cli
81 | npm install
82 | forever app.js --genesis genesisBlock.testnet.json --config config.testnet.json
83 | SHELL
84 |
85 | config.vm.provider "virtualbox" do |v|
86 | v.memory = 1024
87 | v.customize ["modifyvm", :id, "--cpuexecutioncap", "50"]
88 | v.name = "ark_node_vm"
89 | end
90 |
91 | # Disable automatic box update checking. If you disable this, then
92 | # boxes will only be checked for updates when the user runs
93 | # `vagrant box outdated`. This is not recommended.
94 | # config.vm.box_check_update = false
95 |
96 | # Create a forwarded port mapping which allows access to a specific port
97 | # within the machine from a port on the host machine. In the example below,
98 | # accessing "localhost:8080" will access port 80 on the guest machine.
99 | # config.vm.network "forwarded_port", guest: 80, host: 8080
100 |
101 | # Create a private network, which allows host-only access to the machine
102 | # using a specific IP.
103 | # config.vm.network "private_network", ip: "192.168.33.10"
104 |
105 | # Create a public network, which generally matched to bridged network.
106 | # Bridged networks make the machine appear as another physical device on
107 | # your network.
108 | # config.vm.network "public_network"
109 |
110 | # Share an additional folder to the guest VM. The first argument is
111 | # the path on the host to the actual folder. The second argument is
112 | # the path on the guest to mount the folder. And the optional third
113 | # argument is a set of non-required options.
114 | # config.vm.synced_folder "../data", "/vagrant_data"
115 |
116 | # Provider-specific configuration so you can fine-tune various
117 | # backing providers for Vagrant. These expose provider-specific options.
118 | # Example for VirtualBox:
119 | #
120 | # config.vm.provider "virtualbox" do |vb|
121 | # # Display the VirtualBox GUI when booting the machine
122 | # vb.gui = true
123 | #
124 | # # Customize the amount of memory on the VM:
125 | # vb.memory = "1024"
126 | # end
127 | #
128 | # View the documentation for the provider you are using for more
129 | # information on available options.
130 |
131 | # Define a Vagrant Push strategy for pushing to Atlas. Other push strategies
132 | # such as FTP and Heroku are also available. See the documentation at
133 | # https://docs.vagrantup.com/v2/push/atlas.html for more information.
134 | # config.push.define "atlas" do |push|
135 | # push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME"
136 | # end
137 |
138 | # Enable provisioning with a shell script. Additional provisioners such as
139 | # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
140 | # documentation for more information about their specific syntax and use.
141 | # config.vm.provision "shell", inline: <<-SHELL
142 | # apt-get update
143 | # apt-get install -y apache2
144 | # SHELL
145 | end
146 |
--------------------------------------------------------------------------------
/test/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "port": 4000,
3 | "address": "0.0.0.0",
4 | "version": "0.3.0",
5 | "fileLogLevel": "info",
6 | "logFileName": "logs/ark.log",
7 | "consoleLogLevel": "debug",
8 | "trustProxy": false,
9 | "db": {
10 | "host": "localhost",
11 | "port": 5432,
12 | "database": "ark_test",
13 | "user": null,
14 | "password": "password",
15 | "poolSize": 20,
16 | "poolIdleTimeout": 30000,
17 | "reapIntervalMillis": 1000,
18 | "logEvents": [
19 | "error"
20 | ]
21 | },
22 | "api": {
23 | "mount":true,
24 | "access": {
25 | "whiteList": []
26 | },
27 | "options": {
28 | "limits": {
29 | "max": 0,
30 | "delayMs": 0,
31 | "delayAfter": 0,
32 | "windowMs": 60000
33 | }
34 | }
35 | },
36 | "peers": {
37 | "minimumNetworkReach": 1,
38 | "list": [
39 | {
40 | "ip": "127.0.0.1",
41 | "port": 4000
42 | }
43 | ],
44 | "blackList": [],
45 | "options": {
46 | "limits": {
47 | "max": 0,
48 | "delayMs": 0,
49 | "delayAfter": 0,
50 | "windowMs": 60000
51 | },
52 | "maxUpdatePeers": 20,
53 | "timeout": 5000
54 | }
55 | },
56 | "forging": {
57 | "coldstart": 6,
58 | "force": true,
59 | "secret": [
60 | "clay harbor enemy utility margin pretty hub comic piece aerobic umbrella acquire",
61 | "venue below waste gather spin cruise title still boost mother flash tuna",
62 | "craft imitate step mixture patch forest volcano business charge around girl confirm",
63 | "fatal hat sail asset chase barrel pluck bag approve coral slab bright",
64 | "flash thank strike stove grain remove match reflect excess present beyond matrix",
65 | "various present shine domain outdoor neck soup diesel limit express genuine tuna",
66 | "hurdle pulse sheriff anchor two hope income pattern hazard bacon book night",
67 | "glow boss party require silk interest pyramid marriage try wisdom snow grab",
68 | "direct palace screen shuffle world fit produce rubber jelly gather river ordinary",
69 | "wall ketchup shed word twist flip knock liar merge rural ill pond",
70 | "measure blue volcano month orphan only cupboard found laugh peasant drama monitor",
71 | "scissors sort pause medal target diesel reveal stock maze party gauge vacant",
72 | "hand anchor hip pyramid taxi vote celery clap tribe damage shrimp brave",
73 | "merge thunder detect stove else bottom favorite doll learn festival basic basic",
74 | "educate attitude rely combine treat balcony west reopen coil west grab depth",
75 | "advance silver advance squeeze load stone middle garden perfect invest field lounge",
76 | "prison tobacco acquire stone dignity palace note decade they current lesson robot",
77 | "team impact stadium year security steak harsh vacant fire pelican until olympic",
78 | "walk intact ice prevent fit trial frog glory monkey once grunt gentle",
79 | "same lens parrot suspect just sunset frown exercise lemon two mistake robust",
80 | "skill insect issue crazy erase okay govern upgrade bounce dress motor athlete",
81 | "peasant alert hard deposit naive follow page fiscal normal awful wedding history",
82 | "resemble abandon same total oppose noise dune order fatal rhythm pink science",
83 | "wide mesh ketchup acquire bright day mountain final below hamster scout drive",
84 | "half weasel poet better rocket fan help left blade soda argue system",
85 | "target sort neutral address language spike measure jaguar glance strong drop zone",
86 | "race total stage trap wool believe twin pudding claim claim eternal miss",
87 | "parade isolate wing vague magic husband acid skin skate path fence rib",
88 | "neither fine dry priority example obtain bread reopen afford coyote milk minor",
89 | "token atom lemon game charge area goose hotel excess endless spice oblige",
90 | "pledge buffalo finish pipe mule popular bind clinic draft salon swamp purpose",
91 | "west hat hold stand unique panther cable extend spell shaft injury reopen",
92 | "van impulse pole install profit excuse give auction expire remain skate input",
93 | "wrist maze potato april survey burden bamboo knee foot carry speak prison",
94 | "three toddler copy owner pencil minimum doctor orange bottom ice detail design",
95 | "ceiling warrior person thing whisper jeans black cricket drift ahead tornado typical",
96 | "obvious mutual tone usual valve credit soccer mention also clown main box",
97 | "valve slot soft green scale menu anxiety live drill legend upgrade chimney",
98 | "twist comfort mule weather print oven cabin seek punch rival prepare sphere",
99 | "say tumble glass argue aware service force caution until grocery hammer fetch",
100 | "idea illegal empty frozen canvas arctic number poet rely track size obscure",
101 | "chalk try large tower shed warfare blade clerk fame second charge tobacco",
102 | "category nice verb fox start able brass climb boss luggage voice whale",
103 | "favorite emotion trumpet visual welcome spend fine lock image review garage opera",
104 | "waste axis humor auction next salmon much margin useful glimpse insect rotate",
105 | "remember rose genuine police guard old flavor parent gain cross twelve first",
106 | "coil tray elder mask circle crush anger electric harbor onion grab will",
107 | "shove airport bus gather radio derive below horse canvas crime tribe adjust",
108 | "retire lend burden cricket able sheriff output grocery empty scorpion flat inquiry",
109 | "agree grain record shift fossil summer hunt mutual net vast behind pilot",
110 | "decide rhythm oyster lady they merry betray jelly coyote solve episode then"
111 | ],
112 | "access": {
113 | "whiteList": [
114 | "127.0.0.1"
115 | ]
116 | }
117 | },
118 | "loading": {
119 | "verifyOnLoading": false,
120 | "loadPerIteration": 5000
121 | },
122 | "modules": {
123 | "example":"./optional/example.js"
124 | },
125 | "ssl": {
126 | "enabled": false,
127 | "options": {
128 | "port": 443,
129 | "address": "0.0.0.0",
130 | "key": "./ssl/ark.key",
131 | "cert": "./ssl/ark.crt"
132 | }
133 | },
134 | "nethash": "d1c7fa0dcf7584add7a535acd674d3b13fa794345d83c3789cc2436d6ecd80d1"
135 | }
136 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ark
2 |
3 | **:warning: DEPRECATED IN FAVOR OF https://github.com/ArkEcosystem/core :warning:***
4 |
5 | Ark is a next generation crypto-currency and decentralized application platform, written entirely in JavaScript. For more information please refer to our website: https://ark.io/.
6 |
7 | The Token Exchange Campaign is up at https://tec.ark.io
8 |
9 | This version is still alpha, use at your own risks
10 |
11 | ## Install, Upgrade etc...
12 | You need to provision a linux (ubuntu tested) server (digital ocean, vultur or other).
13 |
14 | Then use the excellent ark-commander script
15 | ```
16 | cd
17 | wget https://ark.io/ARKcommander.sh
18 | bash ARKcommander.sh
19 | ```
20 |
21 | For developers, please read below in section "Developer Installation"
22 |
23 | ## Details
24 |
25 | This is a fork from Lisk with the following features:
26 | - Removed sidechains (deprecated in favor of smartbridge)
27 | - Removed custom node version
28 | - Removed UI for stability and security reasons
29 | - Changed some constants (block rewards, blocktime etc...)
30 | - Added simple PBFT before forging new block
31 | - Ditch addresses from the protocol in favor of bitcoin like system, enabling HD Wallet as for BIP32
32 | - Completely rewritten node management using a single NodeManager and messaging system
33 | - Completely rewritten round management (removed mem_round, reward block fees to forger)
34 | - Added 64 bytes vendorField as first iteration of smart bridge
35 | - Made peers management entirely in-memory for efficiency
36 | - Strengthened the transaction management and broadcast (reject often, reject soon)
37 | - Rearchitect with relay nodes and forging nodes
38 | - Nodes broadcast only block headers.
39 |
40 | ### Planned features:
41 | - Simple blockchain validation for SPV and use in lite clients
42 | - Add IPFS as first class citizen (using smartbridge addressing)
43 | - Protocol improvements (uncle forging, voting weights).
44 | - Remove unsecured API
45 | - Routing tables
46 |
47 | ### Performance
48 | - stable on testnet at 5tx/s
49 | - pushed to 10tx/s on devnet
50 |
51 |
52 | ## Developer Installation
53 |
54 | Install essentials:
55 |
56 | ```
57 | sudo apt-get update
58 | sudo apt-get install -y curl build-essential python git
59 | ```
60 |
61 | Install PostgreSQL (min version: 9.5.2)
62 |
63 | ```
64 | sudo apt-get install -y postgresql postgresql-contrib
65 | sudo -u postgres createuser --createdb --password $USER
66 | createdb ark_test
67 | ```
68 |
69 | Install Node.js (tested with version 6.9.2, but any recent LTS release should do):
70 |
71 | ```
72 | sudo apt-get install -y nodejs
73 | sudo npm install -g n
74 | sudo n 6.9.2
75 | ```
76 |
77 | Install grunt-cli (globally):
78 |
79 | ```
80 | sudo npm install grunt-cli -g
81 | ```
82 |
83 | Clone this repository
84 | ```
85 | git clone https://github.com/arkecosytem/ark-node.git
86 | cd ark-node
87 | ```
88 |
89 | Install node modules:
90 | ```
91 | npm install libpq secp256k1
92 | npm install
93 | ```
94 |
95 | ## Launch
96 | To launch Ark on testnet:
97 | ```
98 | createdb ark_testnet
99 | node run start:testnet
100 | ```
101 |
102 | To launch Ark on devtnet:
103 | ```
104 | createdb ark_devnet
105 | node run start:devnet
106 | ```
107 |
108 | To launch Ark on mainnet (when launched):
109 | ```
110 | createdb ark_mainnet
111 | node run start:mainnet
112 | ```
113 |
114 | **NOTE:** The **port**, **address**, **genesis block** and **config-path** can be overridden by providing the relevant command switch:
115 | ```
116 | node app.js -p [port] -a [address] -c [config-path] -g [genesisBlock-path]
117 | ```
118 | This allow you to run several different networks, or your own private chain
119 |
120 |
121 | ## Launch your own private or public chain
122 | Generate a genesisBlock.json + a default config.json containing all passphrases of genesis delegates
123 | ```
124 | node tasks/createGenesisBlock.js
125 | ```
126 | You can find generated files in tasks/
127 | - genesisBlock.json
128 | - config.json
129 | - delegatesPassphrases.json (containing details about the genesis delegates)
130 | - genesisPassphrase.json (containing the details of account having all premined arks)
131 |
132 | Obviously you can hack away tasks/createGenesisBlock.js for your own custom use.
133 |
134 | You can the start with your own chain on a single node (all delegates will forge on your single node) using:
135 | ```
136 | createdb ark_newtest
137 | npm run start:newtest
138 | ```
139 |
140 | Then you can distribute the config.json (without the delegates secrets inside, and with custom peers settings) to peers to let them join your chain
141 |
142 |
143 | ## Tests
144 | Load git submodule [ark-js](https://github.com/arkecosystem/ark-js):
145 | ```
146 | git submodule init
147 | git submodule update
148 | ```
149 |
150 | You should run using test configurations
151 |
152 | ```
153 | npm run start:test
154 | ```
155 |
156 | Run the test suite:
157 |
158 | ```
159 | npm test
160 | ```
161 |
162 | Run individual tests:
163 |
164 | ```
165 | npm test -- test/api/accounts.js
166 | npm test -- test/api/transactions.js
167 | ```
168 |
169 | **NOTE:** The master passphrase for this test genesis block is as follows:
170 |
171 | ```
172 | peace vanish bleak box tuna woman rally manage undo royal lucky since
173 | ```
174 |
175 |
176 | ## Authors
177 | - FX Thoorens
178 | - Boris Povod
179 | - Pavel Nekrasov
180 | - Sebastian Stupurac
181 | - Oliver Beddows
182 |
183 | ## License
184 |
185 | The MIT License (MIT)
186 |
187 | Copyright (c) 2016 Ark
188 | Copyright (c) 2016 Lisk
189 | Copyright (c) 2014-2015 Crypti
190 |
191 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
192 |
193 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
194 |
195 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
196 |
--------------------------------------------------------------------------------
/logic/vote.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var async = require('async');
4 | var constants = require('../helpers/constants.js');
5 | var exceptions = require('../helpers/exceptions.js');
6 | var Diff = require('../helpers/diff.js');
7 |
8 | // Private fields
9 | var modules, library, self;
10 |
11 | // Constructor
12 | function Vote () {
13 | self = this;
14 | }
15 |
16 |
17 | // Public methods
18 | //
19 | //__API__ `bind`
20 |
21 | //
22 | Vote.prototype.bind = function (scope) {
23 | modules = scope.modules;
24 | library = scope.library;
25 | };
26 |
27 | //
28 | //__API__ `create`
29 |
30 | //
31 | Vote.prototype.create = function (data, trs) {
32 | trs.recipientId = data.sender.address;
33 | trs.asset.votes = data.votes;
34 |
35 | return trs;
36 | };
37 |
38 | //
39 | //__API__ `calculateFee`
40 |
41 | //
42 | Vote.prototype.calculateFee = function (trs) {
43 | return constants.fees.vote;
44 | };
45 |
46 | //
47 | //__API__ `verify`
48 |
49 | //
50 | Vote.prototype.verify = function (trs, sender, cb) {
51 | if (trs.recipientId !== trs.senderId) {
52 | return cb('Invalid recipient');
53 | }
54 |
55 | if (!trs.asset || !trs.asset.votes) {
56 | return cb('Invalid transaction asset');
57 | }
58 |
59 | if (!Array.isArray(trs.asset.votes)) {
60 | return cb('Invalid votes. Must be an array');
61 | }
62 |
63 | if (!trs.asset.votes.length) {
64 | return cb('Invalid votes. Must not be empty');
65 | }
66 |
67 | if (trs.asset.votes && trs.asset.votes.length > constants.maximumVotes) {
68 | return cb('Voting limit exceeded. Maximum is '+constants.maximumVotes+' vote per transaction');
69 | }
70 |
71 | modules.delegates.checkConfirmedDelegates(trs.senderPublicKey, trs.asset.votes, function (err) {
72 | if (err && exceptions.votes.indexOf(trs.id) > -1) {
73 | library.logger.debug(err);
74 | library.logger.debug(JSON.stringify(trs));
75 | err = null;
76 | }
77 | return cb(err, trs);
78 | });
79 | };
80 |
81 | //
82 | //__API__ `process`
83 |
84 | //
85 | Vote.prototype.process = function (trs, sender, cb) {
86 | return cb(null, trs);
87 | };
88 |
89 | //
90 | //__API__ `getBytes`
91 |
92 | //
93 | Vote.prototype.getBytes = function (trs) {
94 | var buf;
95 |
96 | try {
97 | buf = trs.asset.votes ? new Buffer(trs.asset.votes.join(''), 'utf8') : null;
98 | } catch (e) {
99 | throw e;
100 | }
101 |
102 | return buf;
103 | };
104 |
105 |
106 | //
107 | //__API__ `checkConfirmedDelegates`
108 |
109 | //
110 | Vote.prototype.checkConfirmedDelegates = function (trs, cb) {
111 | modules.delegates.checkConfirmedDelegates(trs.senderPublicKey, trs.asset.votes, function (err) {
112 | if (err && exceptions.votes.indexOf(trs.id) > -1) {
113 | library.logger.debug(err);
114 | library.logger.debug(JSON.stringify(trs));
115 | err = null;
116 | }
117 |
118 | return cb(err);
119 | });
120 | };
121 |
122 |
123 | //
124 | //__API__ `checkUnconfirmedDelegates`
125 |
126 | //
127 | Vote.prototype.checkUnconfirmedDelegates = function (trs, cb) {
128 | modules.delegates.checkUnconfirmedDelegates(trs.senderPublicKey, trs.asset.votes, function (err) {
129 | if (err && exceptions.votes.indexOf(trs.id) > -1) {
130 | library.logger.debug(err);
131 | library.logger.debug(JSON.stringify(trs));
132 | err = null;
133 | }
134 |
135 | return cb(err);
136 | });
137 | };
138 |
139 | //
140 | //__API__ `apply`
141 |
142 | //
143 | Vote.prototype.apply = function (trs, block, sender, cb) {
144 | var parent = this;
145 |
146 | async.series([
147 | function (seriesCb) {
148 | self.checkConfirmedDelegates(trs, seriesCb);
149 | },
150 | function (seriesCb) {
151 | parent.scope.account.merge(sender.address, {
152 | delegates: trs.asset.votes,
153 | blockId: block.id,
154 | round: modules.rounds.getRoundFromHeight(block.height)
155 | }, seriesCb);
156 | }
157 | ], cb);
158 | };
159 |
160 |
161 | //
162 | //__API__ `undo`
163 |
164 | //
165 | Vote.prototype.undo = function (trs, block, sender, cb) {
166 | if (trs.asset.votes === null) { return cb(); }
167 |
168 | var votesInvert = Diff.reverse(trs.asset.votes);
169 |
170 | this.scope.account.merge(sender.address, {
171 | delegates: votesInvert,
172 | blockId: block.id,
173 | round: modules.rounds.getRoundFromHeight(block.height)
174 | }, cb);
175 | };
176 |
177 | //
178 | //__API__ `applyUnconfirmed`
179 |
180 | //
181 | Vote.prototype.applyUnconfirmed = function (trs, sender, cb) {
182 | var parent = this;
183 |
184 | async.series([
185 | function (seriesCb) {
186 | self.checkUnconfirmedDelegates(trs, seriesCb);
187 | },
188 | function (seriesCb) {
189 | parent.scope.account.merge(sender.address, {
190 | u_delegates: trs.asset.votes
191 | }, seriesCb);
192 | }
193 | ], cb);
194 | };
195 |
196 | //
197 | //__API__ `undoUnconfirmed`
198 |
199 | //
200 | Vote.prototype.undoUnconfirmed = function (trs, sender, cb) {
201 | if (trs.asset.votes === null) { return cb(); }
202 |
203 | var votesInvert = Diff.reverse(trs.asset.votes);
204 | this.scope.account.merge(sender.address, {u_delegates: votesInvert}, cb);
205 | };
206 |
207 | Vote.prototype.schema = {
208 | id: 'Vote',
209 | type: 'object',
210 | properties: {
211 | votes: {
212 | type: 'array',
213 | items: {
214 | type: 'string',
215 | format: 'voteString',
216 | },
217 | minLength: 1,
218 | maxLength: constants.maximumVotes,
219 | uniqueItems: true
220 | }
221 | },
222 | required: ['votes']
223 | };
224 |
225 | //
226 | //__API__ `objectNormalize`
227 |
228 | //
229 | Vote.prototype.objectNormalize = function (trs) {
230 | var report = library.schema.validate(trs.asset, Vote.prototype.schema);
231 |
232 | if (!report) {
233 | throw 'Failed to validate vote schema: ' + this.scope.schema.getLastErrors().map(function (err) {
234 | return err.message;
235 | }).join(', ');
236 | }
237 |
238 | return trs;
239 | };
240 |
241 | //
242 | //__API__ `dbRead`
243 |
244 | //
245 | Vote.prototype.dbRead = function (raw) {
246 | // console.log(raw.v_votes);
247 |
248 | if (!raw.v_votes) {
249 | return null;
250 | } else {
251 | var votes = raw.v_votes.split(',');
252 |
253 | return {votes: votes};
254 | }
255 | };
256 |
257 | Vote.prototype.dbTable = 'votes';
258 |
259 | Vote.prototype.dbFields = [
260 | 'votes',
261 | 'transactionId'
262 | ];
263 |
264 | //
265 | //__API__ `dbSave`
266 |
267 | //
268 | Vote.prototype.dbSave = function (trs) {
269 | return {
270 | table: this.dbTable,
271 | fields: this.dbFields,
272 | values: {
273 | votes: Array.isArray(trs.asset.votes) ? trs.asset.votes.join(',') : null,
274 | transactionId: trs.id
275 | }
276 | };
277 | };
278 |
279 | //
280 | //__API__ `ready`
281 |
282 | //
283 | Vote.prototype.ready = function (trs, sender) {
284 | if (Array.isArray(sender.multisignatures) && sender.multisignatures.length) {
285 | if (!Array.isArray(trs.signatures)) {
286 | return false;
287 | }
288 | return trs.signatures.length >= sender.multimin;
289 | } else {
290 | return true;
291 | }
292 | };
293 |
294 | // Export
295 | module.exports = Vote;
296 |
--------------------------------------------------------------------------------
/logic/delegate.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var constants = require('../helpers/constants.js');
4 |
5 | // Private fields
6 | var modules, library;
7 |
8 | // Constructor
9 | function Delegate () {}
10 |
11 | // Public methods
12 | //
13 | //__API__ `bind`
14 |
15 | //
16 | Delegate.prototype.bind = function (scope) {
17 | modules = scope.modules;
18 | library = scope.library;
19 | };
20 |
21 | //
22 | //__API__ `create`
23 |
24 | //
25 | Delegate.prototype.create = function (data, trs) {
26 | trs.recipientId = null;
27 | trs.amount = 0;
28 | trs.asset.delegate = {
29 | username: data.username,
30 | publicKey: data.sender.publicKey
31 | };
32 |
33 | if (trs.asset.delegate.username) {
34 | trs.asset.delegate.username = trs.asset.delegate.username.toLowerCase().trim();
35 | }
36 |
37 | return trs;
38 | };
39 |
40 | //
41 | //__API__ `calculateFee`
42 |
43 | //
44 | Delegate.prototype.calculateFee = function (trs) {
45 | return constants.fees.delegate;
46 | };
47 |
48 | //
49 | //__API__ `verify`
50 |
51 | //
52 | Delegate.prototype.verify = function (trs, sender, cb) {
53 | if (trs.recipientId) {
54 | return cb('Invalid recipient');
55 | }
56 |
57 | if (trs.amount !== 0) {
58 | return cb('Invalid transaction amount');
59 | }
60 |
61 | if (sender.isDelegate) {
62 | return cb('Account is already a delegate');
63 | }
64 |
65 | if (!trs.asset || !trs.asset.delegate) {
66 | return cb('Invalid transaction asset');
67 | }
68 |
69 | if (!trs.asset.delegate.username) {
70 | return cb('Username is undefined');
71 | }
72 |
73 | if (trs.asset.delegate.username !== trs.asset.delegate.username.toLowerCase()) {
74 | return cb('Username must be lowercase');
75 | }
76 |
77 | var isAddress = /^[1-9A-Za-z]{1,35}$/g;
78 | var allowSymbols = /^[a-z0-9!@$&_.]+$/g;
79 |
80 | var username = String(trs.asset.delegate.username).toLowerCase().trim();
81 |
82 | if (username === '') {
83 | return cb('Empty username');
84 | }
85 |
86 | if (username.length > 20) {
87 | return cb('Username is too long. Maximum is 20 characters');
88 | }
89 |
90 | // Not relevant anymore
91 | // if (isAddress.test(username)) {
92 | // return cb('Username can not be a potential address');
93 | // }
94 |
95 | if (!allowSymbols.test(username)) {
96 | return cb('Username can only contain alphanumeric characters with the exception of !@$&_.');
97 | }
98 |
99 | modules.accounts.getAccount({
100 | username: username
101 | }, function (err, account) {
102 | if (err) {
103 | return cb(err);
104 | }
105 |
106 | if (account) {
107 | return cb('Username already exists');
108 | }
109 |
110 | return cb(null, trs);
111 | });
112 | };
113 |
114 | //
115 | //__API__ `process`
116 |
117 | //
118 | Delegate.prototype.process = function (trs, sender, cb) {
119 | return cb(null, trs);
120 | };
121 |
122 | //
123 | //__API__ `getBytes`
124 |
125 | //
126 | Delegate.prototype.getBytes = function (trs) {
127 | if (!trs.asset.delegate.username) {
128 | return null;
129 | }
130 |
131 | var buf;
132 |
133 | try {
134 | buf = new Buffer(trs.asset.delegate.username, 'utf8');
135 | } catch (e) {
136 | throw e;
137 | }
138 |
139 | return buf;
140 | };
141 |
142 | //
143 | //__API__ `apply`
144 |
145 | //
146 | Delegate.prototype.apply = function (trs, block, sender, cb) {
147 | var data = {
148 | address: sender.address,
149 | u_isDelegate: 0,
150 | isDelegate: 1,
151 | vote: 0
152 | };
153 |
154 | if (trs.asset.delegate.username) {
155 | data.u_username = null;
156 | data.username = trs.asset.delegate.username;
157 | }
158 |
159 | modules.accounts.setAccountAndGet(data, cb);
160 | };
161 |
162 | //
163 | //__API__ `undo`
164 |
165 | //
166 | Delegate.prototype.undo = function (trs, block, sender, cb) {
167 | var data = {
168 | address: sender.address,
169 | u_isDelegate: 1,
170 | isDelegate: 0,
171 | vote: 0
172 | };
173 |
174 | if (!sender.nameexist && trs.asset.delegate.username) {
175 | data.username = null;
176 | data.u_username = trs.asset.delegate.username;
177 | }
178 |
179 | modules.accounts.setAccountAndGet(data, cb);
180 | };
181 |
182 | //
183 | //__API__ `applyUnconfirmed`
184 |
185 | //
186 | Delegate.prototype.applyUnconfirmed = function (trs, sender, cb) {
187 | if (sender.u_isDelegate) {
188 | return cb('Account is already a delegate');
189 | }
190 |
191 | function done () {
192 | var data = {
193 | address: sender.address,
194 | u_isDelegate: 1,
195 | isDelegate: 0
196 | };
197 |
198 | if (trs.asset.delegate.username) {
199 | data.username = null;
200 | data.u_username = trs.asset.delegate.username;
201 | }
202 |
203 | modules.accounts.setAccountAndGet(data, cb);
204 | }
205 |
206 | modules.accounts.getAccount({
207 | u_username: trs.asset.delegate.username
208 | }, function (err, account) {
209 | if (err) {
210 | return cb(err);
211 | }
212 |
213 | if (account) {
214 | return cb('Username already exists');
215 | }
216 |
217 | done();
218 | });
219 | };
220 |
221 | //
222 | //__API__ `undoUnconfirmed`
223 |
224 | //
225 | Delegate.prototype.undoUnconfirmed = function (trs, sender, cb) {
226 | var data = {
227 | address: sender.address,
228 | u_isDelegate: 0,
229 | isDelegate: 0
230 | };
231 |
232 | if (trs.asset.delegate.username) {
233 | data.username = null;
234 | data.u_username = null;
235 | }
236 |
237 | modules.accounts.setAccountAndGet(data, cb);
238 | };
239 |
240 | Delegate.prototype.schema = {
241 | id: 'Delegate',
242 | type: 'object',
243 | properties: {
244 | publicKey: {
245 | type: 'string',
246 | format: 'publicKey'
247 | }
248 | },
249 | required: ['publicKey']
250 | };
251 |
252 | //
253 | //__API__ `objectNormalize`
254 |
255 | //
256 | Delegate.prototype.objectNormalize = function (trs) {
257 | var report = library.schema.validate(trs.asset.delegate, Delegate.prototype.schema);
258 |
259 | if (!report) {
260 | throw 'Failed to validate delegate schema: ' + this.scope.schema.getLastErrors().map(function (err) {
261 | return err.message;
262 | }).join(', ');
263 | }
264 |
265 | return trs;
266 | };
267 |
268 | //
269 | //__API__ `dbRead`
270 |
271 | //
272 | Delegate.prototype.dbRead = function (raw) {
273 | if (!raw.d_username) {
274 | return null;
275 | } else {
276 | var delegate = {
277 | username: raw.d_username,
278 | publicKey: raw.t_senderPublicKey,
279 | address: raw.t_senderId
280 | };
281 |
282 | return {delegate: delegate};
283 | }
284 | };
285 |
286 | Delegate.prototype.dbTable = 'delegates';
287 |
288 | Delegate.prototype.dbFields = [
289 | 'username',
290 | 'transactionId'
291 | ];
292 |
293 | //
294 | //__API__ `dbSave`
295 |
296 | //
297 | Delegate.prototype.dbSave = function (trs) {
298 | return {
299 | table: this.dbTable,
300 | fields: this.dbFields,
301 | values: {
302 | username: trs.asset.delegate.username,
303 | transactionId: trs.id
304 | }
305 | };
306 | };
307 |
308 | //
309 | //__API__ `ready`
310 |
311 | //
312 | Delegate.prototype.ready = function (trs, sender) {
313 | if (Array.isArray(sender.multisignatures) && sender.multisignatures.length) {
314 | if (!Array.isArray(trs.signatures)) {
315 | return false;
316 | }
317 | return trs.signatures.length >= sender.multimin;
318 | } else {
319 | return true;
320 | }
321 | };
322 |
323 | // Export
324 | module.exports = Delegate;
325 |
--------------------------------------------------------------------------------