├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .github └── CONTRIBUTING.md ├── .gitignore ├── .gitmodules ├── .mocharc.js ├── Gruntfile.js ├── LICENSE ├── README.md ├── api ├── http │ ├── accounts.js │ ├── blocks.js │ ├── chatrooms.js │ ├── chats.js │ ├── dapps.js │ ├── delegates.js │ ├── loader.js │ ├── multisignatures.js │ ├── node.js │ ├── peers.js │ ├── server.js │ ├── signatures.js │ ├── states.js │ ├── transactions.js │ └── transport.js └── ws │ ├── server.js │ └── transport.js ├── app.js ├── build ├── config.default.json ├── genesisBlock.json ├── helpers ├── RoundChanges.js ├── accounts.js ├── bignum.js ├── cache.js ├── checkIpInList.js ├── config.js ├── constants.js ├── dappCategories.js ├── dappTypes.js ├── database.js ├── diff.js ├── ed.js ├── exceptions.js ├── git.js ├── httpApi.js ├── inserts.js ├── jobsQueue.js ├── json-schema │ ├── field.js │ ├── package.json │ └── validator.js ├── milestoneBlocks.js ├── orderBy.js ├── publicKey.js ├── request-limiter.js ├── router.js ├── sandbox.js ├── sequence.js ├── slots.js ├── tranasctionTypesBoundary.js ├── transactionTypes.js ├── validator │ ├── field.js │ ├── package.json │ ├── utils.js │ └── validator.js ├── z_schema-express.js └── z_schema.js ├── img └── adm-nodes.jpeg ├── legacy └── lisk-sandbox.js ├── logger.js ├── logic ├── account.js ├── block.js ├── blockReward.js ├── broadcaster.js ├── chat.js ├── consensus │ ├── activationHeights.js │ └── consensus.js ├── dapp.js ├── delegate.js ├── inTransfer.js ├── multisignature.js ├── outTransfer.js ├── peer.js ├── peers.js ├── round.js ├── signature.js ├── state.js ├── transaction.js ├── transactionPool.js ├── transfer.js └── vote.js ├── logs └── .gitkeep ├── modules ├── accounts.js ├── blocks.js ├── blocks │ ├── api.js │ ├── chain.js │ ├── process.js │ ├── utils.js │ └── verify.js ├── cache.js ├── chatrooms.js ├── chats.js ├── clientWs.js ├── clientWs │ └── transactionSubscription.js ├── crypto.js ├── dapps.js ├── delegates.js ├── loader.js ├── multisignatures.js ├── node.js ├── peers.js ├── rounds.js ├── server.js ├── signatures.js ├── sql.js ├── states.js ├── system.js ├── transactions.js └── transport.js ├── package-lock.json ├── package.json ├── pids └── .gitkeep ├── schema ├── accounts.js ├── blocks.js ├── chatrooms.js ├── chats.js ├── config.js ├── dapps.js ├── delegates.js ├── loader.js ├── multisignatures.js ├── node.js ├── peers.js ├── signatures.js ├── states.js ├── transactions.js └── transport.js ├── sql ├── blocks.js ├── chats.js ├── dapps.js ├── delegates.js ├── loader.js ├── memoryTables.sql ├── migrations │ ├── .gitkeep │ ├── 20160723182900_createSchema.sql │ ├── 20160723182901_createViews.sql │ ├── 20160724114255_createMemoryTables.sql │ ├── 20160724132825_upcaseMemoryTableAddresses.sql │ ├── 20160725173858_alterMemAccountsColumns.sql │ ├── 20160908120022_addVirginColumnToMemAccounts.sql │ ├── 20160908215531_protectMemAccountsColumns.sql │ ├── 20161007153817_createMemoryTableIndexes.sql │ ├── 20161016133824_addBroadhashColumnToPeers.sql │ ├── 20161016133824_addHeightColumnToPeers.sql │ ├── 20170113181857_addConstraintsToPeers.sql │ ├── 20170124071600_recreateTrsListView.sql │ ├── 20170319001337_createIndexes.sql │ ├── 20170321001337_createRoundsFeesTable.sql │ ├── 20170328001337_recreateTrsListView.sql │ ├── 20170403001337_calculateBlocksRewards.sql │ ├── 20170408001337_createIndex.sql │ ├── 20170422001337_recreateCalculateBlocksRewards.sql │ ├── 20170428001337_recreateTrsLiskView.sql │ ├── 20170614155841_uniqueDelegatesConstraint.sql │ ├── 20171013022500_createChats.sql │ ├── 20171013024300_recreateFullBlocksListView.sql │ ├── 20180213160000_updateBlockRewards.sql │ ├── 20180316230000_updateBlockRewards.sql │ ├── 20180330203200_createStates.sql │ ├── 20180330203400_recreateFullBlocksListView.sql │ ├── 20180718123722_addVotesWeightColumnToMemAccounts.sql │ ├── 20190114165600_createLastFirstFunctions.sql │ ├── 20190601142900_createTrsListFullView.sql │ ├── 20190815120200_recreateTrsListFullView.sql │ ├── 20190815120437_recreateTrsListView.sql │ ├── 20221009164928_createGeneratorPublicKeyIndex.sql │ ├── 20221220012528_addConfirmationsFieldForFullBlocksList.sql │ ├── 20241218010631_addTimestampMs.sql │ └── 20250417154132_mergeBlockIntoTransactions.sql ├── multisignatures.js ├── peers.js ├── rounds.js ├── runtime.sql ├── states.js ├── system.js ├── transactions.js └── transport.js ├── tasks ├── .gitkeep └── newMigration.js ├── test ├── api │ ├── accounts.js │ ├── blocks.js │ ├── chatrooms.js │ ├── chats.js │ ├── dapps.js │ ├── delegates.js │ ├── loader.js │ ├── multisignatures.js │ ├── peer.blocks.js │ ├── peer.dapp.js │ ├── peer.js │ ├── peer.signatures.js │ ├── peer.transactions.collision.js │ ├── peer.transactions.delegates.js │ ├── peer.transactions.main.js │ ├── peer.transactions.multisignatures.js │ ├── peer.transactions.signatures.js │ ├── peer.transactions.stress.js │ ├── peer.transactions.votes.js │ ├── peers.js │ ├── signatures.js │ ├── states.js │ └── transactions.js ├── common │ ├── api.js │ ├── assert.js │ ├── globalAfter.js │ ├── globalBefore.js │ ├── initModule.js │ ├── objectStubs.js │ ├── sql │ │ └── blockRewards.js │ ├── stubs │ │ ├── account.js │ │ ├── blocks.js │ │ ├── delegate.js │ │ ├── peers.js │ │ ├── transactions.js │ │ └── transactions │ │ │ ├── chat.js │ │ │ ├── common.js │ │ │ ├── delegate.js │ │ │ ├── state.js │ │ │ ├── transfer.js │ │ │ └── vote.js │ └── utils.js ├── config.default.json ├── genesisBlock.json ├── genesisPasses.json ├── index.js ├── node.js └── unit │ ├── helpers │ ├── RoundChanges.js │ ├── request-limiter.js │ ├── sequence.js │ └── z_schema.js │ ├── logic │ ├── account.js │ ├── block.js │ ├── blockReward.js │ ├── chat.js │ ├── delegate.js │ ├── multisignature.js │ ├── peer.js │ ├── peers.js │ ├── state.js │ ├── transaction.js │ ├── transactionPool.js │ ├── transfer.js │ └── vote.js │ ├── modules │ ├── accounts.js │ ├── blocks.js │ ├── blocks │ │ └── verify.js │ ├── cache.js │ ├── clientWs.js │ ├── delegates.js │ ├── node.js │ ├── peers.js │ ├── rounds.js │ └── transactions.js │ ├── schema │ ├── dapp.js │ ├── multisignatures.js │ └── transactions.js │ └── sql │ └── blockRewards.js └── tools ├── fix_node.sh ├── generate_genesis.js ├── install_node.sh └── install_node_centos.sh /.editorconfig: -------------------------------------------------------------------------------- 1 | # Install EditorConfig Plugin on your IDE for one coding style 2 | # EditorConfig is awesome: http://EditorConfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | indent_style = space 12 | indent_size = 2 13 | [*.md] 14 | max_line_length = off 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dapps 3 | logs/*.log 4 | nodejs 5 | npm-debug.log 6 | release 7 | ssl 8 | stacktrace* 9 | tmp 10 | public/node_modules 11 | public/bower_components 12 | public/static 13 | helpers/bignum.js 14 | docs/jsdoc/scripts/prettify/prettify.js 15 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "commonjs": true, 4 | "es2021": true, 5 | "browser": true, 6 | "node": true 7 | }, 8 | "extends": [ 9 | "google" 10 | ], 11 | "parserOptions": { 12 | "ecmaVersion": 12 13 | }, 14 | "rules": { 15 | "max-len": ["error", 16 | { "code": 200, 17 | "ignoreTrailingComments": true, 18 | "ignoreUrls": true, 19 | "ignoreStrings": true, 20 | "ignoreTemplateLiterals": true, 21 | "ignoreRegExpLiterals": true 22 | }], 23 | "require-jsdoc": ["off"], 24 | "no-var": ["off"], 25 | "comma-dangle": ["error", "never"], 26 | "brace-style": ["error", "1tbs", { "allowSingleLine": true }], 27 | "block-spacing": ["error", "always"], 28 | "new-cap": ["off"], 29 | "prefer-rest-params": ["off"], 30 | "no-unused-vars": ["off"], 31 | "no-invalid-this": ["off"], 32 | "camelcase": ["off"], 33 | "one-var": ["off"], 34 | "no-throw-literal": ["off"], 35 | "object-curly-spacing": ["error", "always"], 36 | "prefer-const": ["off"], 37 | "quote-props": ["off"], 38 | "guard-for-in": ["off"], 39 | "valid-jsdoc": ["off"], 40 | "prefer-spread": ["off"], 41 | "space-before-function-paren": ["error", { 42 | "anonymous": "always", 43 | "named": "always", 44 | "asyncArrow": "ignore" 45 | }], 46 | "space-infix-ops": ["error", { "int32Hint": true }], 47 | "jsdoc/check-access": 1, 48 | "jsdoc/check-alignment": 1, 49 | "jsdoc/check-line-alignment": 1, 50 | "jsdoc/check-param-names": 1, 51 | "jsdoc/check-property-names": 1, 52 | "jsdoc/check-tag-names": 1, 53 | "jsdoc/check-types": 1, 54 | "jsdoc/check-values": 1, 55 | "jsdoc/empty-tags": 1, 56 | "jsdoc/multiline-blocks": 1, 57 | "jsdoc/newline-after-description": 1, 58 | "jsdoc/no-multi-asterisks": 1, 59 | "jsdoc/require-param": 1, 60 | "jsdoc/require-param-name": 1, 61 | "jsdoc/require-param-type": 1, 62 | "jsdoc/require-property": 1, 63 | "jsdoc/require-property-name": 1, 64 | "jsdoc/require-property-type": 1, 65 | "jsdoc/require-returns-type": 1, 66 | "jsdoc/require-yields": 1, 67 | "jsdoc/require-yields-check": 1, 68 | "jsdoc/tag-lines": 1, 69 | "jsdoc/valid-types": 1, 70 | "jsdoc/sort-tags": [ 71 | "warn", 72 | { 73 | "tagSequence": [{ "tags": [ 74 | "global", 75 | "typedef", 76 | "var", 77 | "name", 78 | "namespace", 79 | "constructor", 80 | "callback", 81 | "event", 82 | "function", 83 | "augments", 84 | "lends", 85 | "type", 86 | "prop", 87 | "param", 88 | "throws", 89 | "fires", 90 | "listens", 91 | "ingroup", 92 | "deprecated", 93 | "see", 94 | "todo", 95 | "ignore" 96 | ]}] 97 | } 98 | ] 99 | }, 100 | "globals": { 101 | "PR": true, 102 | "it": true, 103 | "describe": true, 104 | "before": true, 105 | "beforeEach": true, 106 | "after": true, 107 | "afterEach": true 108 | }, 109 | "plugins": [ 110 | "jsdoc" 111 | ] 112 | } 113 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Tests 2 | 3 | To be merged, pull requests **MUST** include tests for any new features or bug fixes. 4 | 5 | ### Structure 6 | 7 | The tests are located in the `test/` directory. The structure of the directory is as follows: 8 | 9 | - `api/` - End-to-end tests that require running a local test node 10 | - `unit/` - Unit tests that DO NOT require running a test node 11 | - `common/` - Contains stub objects and utilities for the tests 12 | - `node.js` - Package for making requests to the local test node 13 | - `config.json` - Configuration file to run a local test node; copy `config.default.json` 14 | - `genesisBlock.json` - Genesis block data 15 | - `genesisDelegates.json` - Genesis delegate accounts 16 | - `genesisPasses.json` - Passphrases for the genesis accounts 17 | 18 | All tests inside `api/` and `unit/` should mirror (as much as possible) the structure of the project. For example, unit tests for the `modules/blocks.js` module should be located in the `test/unit/modules/blocks.js` file. 19 | 20 | ### Commands 21 | 22 | > [!IMPORTANT] 23 | > **API tests** require the `testnet` local node to be running in parallel during their execution: 24 | 25 | ```sh 26 | npm run start:testnet 27 | ``` 28 | 29 | See [Test Environment](../README.md#Test-Environment) for reference. 30 | 31 | > [!CAUTION] 32 | > **Unit tests** should NOT be run in parallel to prevent disruption of the node's state, and the `testnet` should be run at least once before. 33 | 34 | To run a single test file, use the following command: 35 | 36 | ```sh 37 | npm run test:single test/path/to/the/test.js 38 | ``` 39 | 40 | If you have changed any common files (e.g., files inside `test/common/`, `test/node.js` package, etc.), consider running all tests: 41 | 42 | ```sh 43 | # run all unit tests; remember to stop testnet node before 44 | npm run test:unit 45 | 46 | # run only fast unit tests (excluding time-consuming ones) 47 | npm run test:unit:fast 48 | 49 | # run all API tests; remember to run testnet node before 50 | npm run test:api 51 | ``` 52 | 53 | ### Convention for tests 54 | 55 | Since we use the Chai package for assertions, we have a few rules for consistency: 56 | 57 | - **Use proper English grammar in assertions** 58 | 59 | ```js 60 | // ❌ 61 | expect({}).to.be.a('object'); 62 | // ✅ 63 | expect(true).to.be.an('object'); 64 | ``` 65 | 66 | - **Use `to.be.true` instead of `to.be.ok`** 67 | 68 | Boolean values should be strictly asserted: 69 | 70 | ```js 71 | // ❌ 72 | expect(true).to.be.ok; 73 | // ✅ 74 | expect(true).to.be.true; 75 | ``` 76 | 77 | - **Use `not.to` instead of `to.not`** 78 | 79 | Prefer `not.to` for convention: 80 | 81 | ```js 82 | // ❌ 83 | expect(true).to.not.be.false; 84 | // ✅ 85 | expect(true).not.to.be.false; 86 | ``` 87 | 88 | - **Use `.equal()` instead of `.eql()` for `===`** 89 | 90 | Use `.eql()` **only** for deep assertion. 91 | 92 | ```js 93 | // ❌ 94 | expect(true).to.eql(true); 95 | // ✅ 96 | expect(true).to.be.true; 97 | ``` 98 | 99 | - **Use parentheses for functions and methods in `describe` names** 100 | 101 | ```js 102 | // ❌ 103 | describe(`functionName`, () => { /* ... */ }) 104 | // ✅ 105 | describe(`functionName()`, () => { /* ... */ }) 106 | ``` 107 | 108 | Happy testing! 109 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dapps/ 2 | docs/jsdoc/ 3 | release 4 | ssl/ 5 | 6 | # Config files 7 | config.json 8 | test/config.json 9 | sftp-config.json 10 | 11 | # Logs 12 | logs/*.log 13 | stacktrace* 14 | 15 | # Code Editors 16 | .DS_Store 17 | .ed25519-node 18 | .idea/ 19 | .project 20 | .vscode/ 21 | __MACOSX/ 22 | 23 | # Tests 24 | test/.coverage-unit 25 | test/.coverage 26 | test/.nyc_output 27 | 28 | # Temporary data 29 | tmp 30 | *.swp 31 | *.swo 32 | 33 | # npm 34 | node_modules 35 | npm-debug.log 36 | nodejs 37 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Adamant-im/adamant/9cc651471d824471856d4cd4ad04e14c801b9654/.gitmodules -------------------------------------------------------------------------------- /.mocharc.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | timeout: '1200s' 5 | } -------------------------------------------------------------------------------- /api/http/accounts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Router = require('../../helpers/router'); 4 | var httpApi = require('../../helpers/httpApi'); 5 | var schema = require('../../schema/accounts.js'); 6 | 7 | /** 8 | * Binds api with modules and creates common url. 9 | * - End point: `/api/accounts` 10 | * - Public API: 11 | - post /open 12 | - post /new 13 | - get /getBalance 14 | - get /getPublicKey 15 | - post /generatePublicKey 16 | - get /delegates 17 | - get /delegates/fee 18 | - put /delegates 19 | - post /delegates 20 | - get / 21 | * - Private API: 22 | * - get /count 23 | * @memberof module:accounts 24 | * @requires helpers/Router 25 | * @requires helpers/httpApi 26 | * @constructor 27 | * @param {Object} accountsModule - Module account instance. 28 | * @param {scope} app - Network app. 29 | */ 30 | 31 | function AccountsHttpApi (accountsModule, app) { 32 | var router = new Router(); 33 | 34 | router.map(accountsModule.shared, { 35 | 'post /open': 'open', 36 | 'post /new': 'new', 37 | 'get /getBalance': 'getBalance', 38 | 'get /getPublicKey': 'getPublickey', 39 | 'post /generatePublicKey': 'generatePublicKey', 40 | 'get /delegates': 'getDelegates', 41 | 'get /delegates/fee': 'getDelegatesFee', 42 | 'put /delegates': 'addDelegates', 43 | 'post /delegates': 'voteForDelegates', 44 | 'get /': 'getAccount' 45 | }); 46 | 47 | router.map(accountsModule.internal, { 48 | 'get /count': 'count' 49 | }); 50 | 51 | if (process.env.DEBUG && process.env.DEBUG.toUpperCase() === 'TRUE') { 52 | router.map(accountsModule.internal, { 'get /getAllAccounts': 'getAllAccounts' }); 53 | } 54 | 55 | if (process.env.TOP && process.env.TOP.toUpperCase() === 'TRUE') { 56 | router.get('/top', httpApi.middleware.sanitize('query', schema.top, accountsModule.internal.top)); 57 | } 58 | 59 | httpApi.registerEndpoint('/api/accounts', app, router, accountsModule.isLoaded); 60 | } 61 | 62 | module.exports = AccountsHttpApi; 63 | -------------------------------------------------------------------------------- /api/http/blocks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Router = require('../../helpers/router'); 4 | var httpApi = require('../../helpers/httpApi'); 5 | 6 | /** 7 | * Binds api with modules and creates common url. 8 | * - End point: `/api/blocks` 9 | * - Public API: 10 | * - get /get 11 | * - get / 12 | * - get /getBroadhash 13 | * - get /getEpoch 14 | * - get /getHeight 15 | * - get /getNethash 16 | * - get /getFee 17 | * - get /getFees 18 | * - get /getMilestone 19 | * - get /getReward 20 | * - get /getSupply 21 | * - get /getStatus 22 | * @memberof module:blocks 23 | * @requires helpers/Router 24 | * @requires helpers/httpApi 25 | * @constructor 26 | * @param {Object} blocksModule - Module blocks instance. 27 | * @param {scope} app - Network app. 28 | */ 29 | // Constructor 30 | function BlocksHttpApi (blocksModule, app, logger, cache) { 31 | var router = new Router(); 32 | 33 | // attach a middlware to endpoints 34 | router.attachMiddlwareForUrls(httpApi.middleware.useCache.bind(null, logger, cache), [ 35 | 'get /' 36 | ]); 37 | 38 | router.map(blocksModule.shared, { 39 | 'get /get': 'getBlock', 40 | 'get /': 'getBlocks', 41 | 'get /getBroadhash': 'getBroadhash', 42 | 'get /getEpoch': 'getEpoch', 43 | 'get /getHeight': 'getHeight', 44 | 'get /getNethash': 'getNethash', 45 | 'get /getFee': 'getFee', 46 | 'get /getFees': 'getFees', 47 | 'get /getMilestone': 'getMilestone', 48 | 'get /getReward': 'getReward', 49 | 'get /getSupply': 'getSupply', 50 | 'get /getStatus': 'getStatus' 51 | }); 52 | 53 | httpApi.registerEndpoint('/api/blocks', app, router, blocksModule.isLoaded); 54 | } 55 | 56 | module.exports = BlocksHttpApi; 57 | -------------------------------------------------------------------------------- /api/http/chatrooms.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Router = require('../../helpers/router'); 4 | const httpApi = require('../../helpers/httpApi'); 5 | 6 | /** 7 | * Binds api with modules and creates common url. 8 | * - End point: `/api/chatrooms` 9 | * 10 | * - Sanitized 11 | * - get /:ID 12 | * - get /:ID/:ID 13 | * @memberof module:chatrooms 14 | * @requires helpers/Router 15 | * @requires helpers/httpApi 16 | * @constructor 17 | * @param {Object} chatroomsModule - Module chats instance. 18 | * @param {scope} app - Network app. 19 | */ 20 | // Constructor 21 | function ChatroomsHttpApi (chatroomsModule, app) { 22 | const router = new Router({ caseSensitive: false }); 23 | 24 | router.map(chatroomsModule.internal, { 25 | 'get /U*/U*': 'getMessages', 26 | 'get /U*': 'getChats' 27 | }); 28 | 29 | 30 | httpApi.registerEndpoint('/api/chatrooms', app, router, chatroomsModule.isLoaded); 31 | } 32 | 33 | module.exports = ChatroomsHttpApi; 34 | -------------------------------------------------------------------------------- /api/http/chats.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Router = require('../../helpers/router'); 4 | var httpApi = require('../../helpers/httpApi'); 5 | var schema = require('../../schema/dapps'); 6 | 7 | /** 8 | * Binds api with modules and creates common url. 9 | * - End point: `/api/chats` 10 | * - Private API: 11 | * - post /normalize 12 | * - post /finalize 13 | * 14 | * - Sanitized 15 | * - get / 16 | * - put / 17 | * - get /get 18 | * @memberof module:chats 19 | * @requires helpers/Router 20 | * @requires helpers/httpApi 21 | * @constructor 22 | * @param {Object} chatsModule - Module chats instance. 23 | * @param {scope} app - Network app. 24 | */ 25 | // Constructor 26 | function ChatsHttpApi (chatsModule, app) { 27 | var router = new Router(); 28 | 29 | router.map(chatsModule.internal, { 30 | 'get /senders': 'senders', 31 | 'get /get': 'getTransactions', 32 | 'get /messages': 'messages', 33 | 'post /normalize': 'normalize', 34 | 'post /process': 'process' 35 | }); 36 | 37 | 38 | httpApi.registerEndpoint('/api/chats', app, router, chatsModule.isLoaded); 39 | } 40 | 41 | module.exports = ChatsHttpApi; 42 | -------------------------------------------------------------------------------- /api/http/dapps.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Router = require('../../helpers/router'); 4 | var httpApi = require('../../helpers/httpApi'); 5 | var schema = require('../../schema/dapps'); 6 | 7 | /** 8 | * Binds api with modules and creates common url. 9 | * - End point: `/api/dapps` 10 | * - Private API: 11 | * - get /categories 12 | * - get /installed 13 | * - get /installedIds 14 | * - get /ismasterpasswordenabled 15 | * - get /installing 16 | * - get /uninstalling 17 | * - get /launched 18 | * - post /launch 19 | * - put /transaction 20 | * - put /withdrawal 21 | * 22 | * - Sanitized 23 | * - get / 24 | * - put / 25 | * - get /get 26 | * - get /search 27 | * - post /install 28 | * - post /uninstall 29 | * - post /stop 30 | * @memberof module:dapps 31 | * @requires helpers/Router 32 | * @requires helpers/httpApi 33 | * @constructor 34 | * @param {Object} dappsModule - Module dapps instance. 35 | * @param {scope} app - Network app. 36 | */ 37 | // Constructor 38 | function DappsHttpApi (dappsModule, app) { 39 | var router = new Router(); 40 | 41 | router.map(dappsModule.internal, { 42 | 'get /categories': 'categories', 43 | 'get /installed': 'installed', 44 | 'get /installedIds': 'installedIds', 45 | 'get /ismasterpasswordenabled': 'isMasterPasswordEnabled', 46 | 'get /installing': 'installing', 47 | 'get /uninstalling': 'uninstalling', 48 | 'get /launched': 'launched', 49 | 'post /launch': 'launch', 50 | 'put /transaction': 'addTransactions', 51 | 'put /withdrawal': 'sendWithdrawal' 52 | }); 53 | 54 | router.get('/', httpApi.middleware.sanitize('query', schema.list, dappsModule.internal.list)); 55 | router.put('/', httpApi.middleware.sanitize('body', schema.put, dappsModule.internal.put)); 56 | router.get('/get', httpApi.middleware.sanitize('query', schema.get, dappsModule.internal.get)); 57 | router.get('/search', httpApi.middleware.sanitize('query', schema.search, dappsModule.internal.search)); 58 | router.post('/install', httpApi.middleware.sanitize('body', schema.install, dappsModule.internal.install)); 59 | router.post('/uninstall', httpApi.middleware.sanitize('body', schema.uninstall, dappsModule.internal.uninstall)); 60 | router.post('/stop', httpApi.middleware.sanitize('body', schema.stop, dappsModule.internal.stop)); 61 | 62 | httpApi.registerEndpoint('/api/dapps', app, router, dappsModule.isLoaded); 63 | } 64 | 65 | module.exports = DappsHttpApi; 66 | -------------------------------------------------------------------------------- /api/http/delegates.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Router = require('../../helpers/router'); 4 | var httpApi = require('../../helpers/httpApi'); 5 | 6 | /** 7 | * Binds api with modules and creates common url. 8 | * - End point: `/api/delegates` 9 | * - Public API: 10 | - get /count 11 | - get /search 12 | - get /voters 13 | - get /get 14 | - get / 15 | - get /fee 16 | - get /forging/getForgedByAccount 17 | - put / 18 | - post / 19 | - get /getNextForgers 20 | * - Private API: 21 | * - post /forging/enable 22 | * - post /forging/disable 23 | * - get /forging/status 24 | * - Debug API: 25 | * - get /forging/disableAll 26 | * - get /forging/enableAll 27 | * @memberof module:delegates 28 | * @requires helpers/Router 29 | * @requires helpers/httpApi 30 | * @constructor 31 | * @param {Object} delegatesModule - Module delegate instance. 32 | * @param {scope} app - Network app. 33 | */ 34 | // Constructor 35 | function DelegatesHttpApi (delegatesModule, app, logger, cache) { 36 | var router = new Router(); 37 | 38 | // attach a middlware to endpoints 39 | router.attachMiddlwareForUrls(httpApi.middleware.useCache.bind(null, logger, cache), ['get /']); 40 | 41 | router.map(delegatesModule.shared, { 42 | 'get /count': 'count', 43 | 'get /search': 'search', 44 | 'get /voters': 'getVoters', 45 | 'get /get': 'getDelegate', 46 | 'get /': 'getDelegates', 47 | 'get /fee': 'getFee', 48 | 'get /forging/getForgedByAccount': 'getForgedByAccount', 49 | 'put /': 'addDelegate', 50 | 'post /': 'registerDelegate', 51 | 'get /getNextForgers': 'getNextForgers' 52 | }); 53 | 54 | router.map(delegatesModule.internal, { 55 | 'post /forging/enable': 'forgingEnable', 56 | 'post /forging/disable': 'forgingDisable', 57 | 'get /forging/status': 'forgingStatus' 58 | }); 59 | 60 | if (process.env.DEBUG) { 61 | router.map(delegatesModule.internal, { 62 | 'get /forging/disableAll': 'forgingDisableAll', 63 | 'get /forging/enableAll': 'forgingEnableAll' 64 | }); 65 | } 66 | 67 | httpApi.registerEndpoint('/api/delegates', app, router, delegatesModule.isLoaded); 68 | } 69 | 70 | module.exports = DelegatesHttpApi; 71 | -------------------------------------------------------------------------------- /api/http/loader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Router = require('../../helpers/router'); 4 | var httpApi = require('../../helpers/httpApi'); 5 | /** 6 | * Binds api with modules and creates common url. 7 | * - End point: `/api/loader` 8 | * - Public API: 9 | * - get /status 10 | * - get /status/sync 11 | * - Private API: 12 | * - get /status/ping 13 | * @memberof module:loader 14 | * @requires helpers/Router 15 | * @requires helpers/httpApi 16 | * @constructor 17 | * @param {Object} loaderModule - Module loader instance. 18 | * @param {scope} app - Network app. 19 | */ 20 | // Constructor 21 | function LoaderHttpApi (loaderModule, app) { 22 | var router = new Router(); 23 | 24 | router.map(loaderModule.shared, { 25 | 'get /status': 'status', 26 | 'get /status/sync': 'sync' 27 | }); 28 | 29 | router.get('/status/ping', function (req, res) { 30 | var status = loaderModule.internal.statusPing(); 31 | return res.status(status ? 200 : 503).json({ success: status }); 32 | }); 33 | 34 | httpApi.registerEndpoint('/api/loader', app, router, loaderModule.isLoaded); 35 | } 36 | 37 | module.exports = LoaderHttpApi; 38 | -------------------------------------------------------------------------------- /api/http/multisignatures.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Router = require('../../helpers/router'); 4 | var httpApi = require('../../helpers/httpApi'); 5 | 6 | /** 7 | * Binds api with modules and creates common url. 8 | * - End point: `/api/multisignatures` 9 | * - Public API: 10 | * - get /pending 11 | * - post /sign 12 | * - put / 13 | * - get /accounts 14 | * @memberof module:multisignatures 15 | * @requires helpers/Router 16 | * @requires helpers/httpApi 17 | * @constructor 18 | * @param {Object} mutlisignaturesModule - Module multisignatures instance. 19 | * @param {scope} app - Network app. 20 | * @todo correct typo mutlisignaturesModule 21 | */ 22 | // Constructor 23 | function MultisignaturesHttpApi (mutlisignaturesModule, app) { 24 | var router = new Router(); 25 | 26 | router.map(mutlisignaturesModule.shared, { 27 | 'get /pending': 'pending', 28 | 'post /sign': 'sign', 29 | 'put /': 'addMultisignature', 30 | 'get /accounts': 'getAccounts' 31 | }); 32 | 33 | httpApi.registerEndpoint('/api/multisignatures', app, router, mutlisignaturesModule.isLoaded); 34 | } 35 | 36 | module.exports = MultisignaturesHttpApi; 37 | -------------------------------------------------------------------------------- /api/http/node.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Router = require('../../helpers/router'); 4 | var httpApi = require('../../helpers/httpApi'); 5 | var schema = require('../../schema/node.js'); 6 | 7 | /** 8 | * Binds api with modules and creates common url. 9 | * - End point: `/api/node` 10 | * - Public API: 11 | - get /status 12 | * @memberof module:node 13 | * @requires helpers/Router 14 | * @requires helpers/httpApi 15 | * @constructor 16 | * @param {Object} nodeModule - Module node instance. 17 | * @param {scope} app - Network app. 18 | */ 19 | 20 | function NodeHttpApi (nodeModule, app) { 21 | var router = new Router(); 22 | 23 | router.map(nodeModule.shared, { 24 | 'get /status': 'getStatus' 25 | }); 26 | 27 | 28 | httpApi.registerEndpoint('/api/node', app, router, nodeModule.isLoaded); 29 | } 30 | 31 | module.exports = NodeHttpApi; 32 | -------------------------------------------------------------------------------- /api/http/peers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Router = require('../../helpers/router'); 4 | var httpApi = require('../../helpers/httpApi'); 5 | 6 | /** 7 | * Binds api with modules and creates common url. 8 | * - End point: `/api/peers` 9 | * - Public API: 10 | * - get / 11 | * - get /version 12 | * - get /get 13 | * - get /count 14 | * @memberof module:peers 15 | * @requires helpers/Router 16 | * @requires helpers/httpApi 17 | * @constructor 18 | * @param {Object} peersModule - Module peers instance. 19 | * @param {scope} app - Network app. 20 | */ 21 | // Constructor 22 | function PeersHttpApi (peersModule, app) { 23 | var router = new Router(); 24 | 25 | router.map(peersModule.shared, { 26 | 'get /': 'getPeers', 27 | 'get /version': 'version', 28 | 'get /get': 'getPeer', 29 | 'get /count': 'count' 30 | }); 31 | 32 | httpApi.registerEndpoint('/api/peers', app, router, peersModule.isLoaded); 33 | } 34 | 35 | module.exports = PeersHttpApi; 36 | -------------------------------------------------------------------------------- /api/http/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Router = require('../../helpers/router'); 4 | var httpApi = require('../../helpers/httpApi'); 5 | 6 | /** 7 | * Renders main page wallet from public folder. 8 | * - Public API: 9 | * - get / 10 | * @memberof module:server 11 | * @requires helpers/Router 12 | * @requires helpers/httpApi 13 | * @constructor 14 | * @param {Object} serverModule - Module server instance. 15 | * @param {scope} app - Main app. 16 | */ 17 | // Constructor 18 | function ServerHttpApi (serverModule, app) { 19 | var router = new Router(); 20 | 21 | router.use(function (req, res, next) { 22 | if (serverModule.areModulesReady()) { return next(); } 23 | res.status(500).send({ success: false, error: 'Blockchain is loading' }); 24 | }); 25 | 26 | router.get('/', function (req, res) { 27 | if (serverModule.isLoaded()) { 28 | res.render('wallet.html', { layout: false }); 29 | } else { 30 | res.render('loading.html'); 31 | } 32 | }); 33 | 34 | router.use(function (req, res, next) { 35 | if (req.url.indexOf('/api/') === -1 && req.url.indexOf('/peer/') === -1) { 36 | return res.redirect('/'); 37 | } 38 | next(); 39 | }); 40 | 41 | app.use('/', router); 42 | } 43 | 44 | module.exports = ServerHttpApi; 45 | -------------------------------------------------------------------------------- /api/http/signatures.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Router = require('../../helpers/router'); 4 | var httpApi = require('../../helpers/httpApi'); 5 | 6 | /** 7 | * Binds api with modules and creates common url. 8 | * - End point: `/api/signatures` 9 | * - Public API: 10 | * - get /fee 11 | * - put / 12 | * @memberof module:signatures 13 | * @requires helpers/Router 14 | * @requires helpers/httpApi 15 | * @constructor 16 | * @param {Object} signaturesModule - Module signatures instance. 17 | * @param {scope} app - Network app. 18 | */ 19 | // Constructor 20 | function SignaturesHttpApi (signaturesModule, app) { 21 | var router = new Router(); 22 | 23 | router.map(signaturesModule.shared, { 24 | 'get /fee': 'getFee', 25 | 'put /': 'addSignature' 26 | }); 27 | 28 | httpApi.registerEndpoint('/api/signatures', app, router, signaturesModule.isLoaded); 29 | } 30 | 31 | module.exports = SignaturesHttpApi; 32 | -------------------------------------------------------------------------------- /api/http/states.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Router = require('../../helpers/router'); 4 | var httpApi = require('../../helpers/httpApi'); 5 | 6 | 7 | /** 8 | * Binds api with modules and creates common url. 9 | * - End point: `/api/states` 10 | * - Private API: 11 | * - post /normalize 12 | * - post /finalize 13 | * 14 | * - Sanitized 15 | * - get /get 16 | * @memberof module:states 17 | * @requires helpers/Router 18 | * @requires helpers/httpApi 19 | * @constructor 20 | * @param {Object} statesModule - Module storing options state 21 | * @param {scope} app - Network app. 22 | */ 23 | // Constructor 24 | function StatesHttpApi (statesModule, app) { 25 | var router = new Router(); 26 | 27 | router.map(statesModule.internal, { 28 | 'get /get': 'getTransactions', 29 | 'post /get': 'getTransactions', 30 | 'post /normalize': 'normalize', 31 | 'post /store': 'store' 32 | }); 33 | 34 | 35 | httpApi.registerEndpoint('/api/states', app, router, statesModule.isLoaded); 36 | } 37 | 38 | module.exports = StatesHttpApi; 39 | -------------------------------------------------------------------------------- /api/http/transactions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Router = require('../../helpers/router'); 4 | var httpApi = require('../../helpers/httpApi'); 5 | 6 | /** 7 | * Binds api with modules and creates common url. 8 | * - End point: `/api/transactions` 9 | * - Public API: 10 | * - get / 11 | * - post / 12 | * - get /get 13 | * - get /count 14 | * - get /queued/get 15 | * - get /queued 16 | * - get /multisignatures/get 17 | * - get /multisignatures 18 | * - get /unconfirmed/get 19 | * - get /unconfirmed 20 | * - put / 21 | * - post /normalize 22 | * - post /process 23 | * @memberof module:transactions 24 | * @requires helpers/Router 25 | * @requires helpers/httpApi 26 | * @constructor 27 | * @param {Object} transactionsModule - Module transaction instance. 28 | * @param {scope} app - Network app. 29 | */ 30 | // Constructor 31 | function TransactionsHttpApi (transactionsModule, app, logger, cache) { 32 | var router = new Router(); 33 | 34 | // attach a middleware to endpoints 35 | router.attachMiddlwareForUrls(httpApi.middleware.useCache.bind(null, logger, cache), [ 36 | 'get /' 37 | ]); 38 | 39 | router.map(transactionsModule.shared, { 40 | 'get /': 'getTransactions', 41 | 'post /': 'postTransactions', 42 | 'get /get': 'getTransaction', 43 | 'get /count': 'getTransactionsCount', 44 | 'get /queued/get': 'getQueuedTransaction', 45 | 'get /queued': 'getQueuedTransactions', 46 | 'get /multisignatures/get': 'getMultisignatureTransaction', 47 | 'get /multisignatures': 'getMultisignatureTransactions', 48 | 'get /unconfirmed/get': 'getUnconfirmedTransaction', 49 | 'get /unconfirmed': 'getUnconfirmedTransactions', 50 | 'put /': 'addTransactions', 51 | 'post /normalize': 'normalizeTransactions', 52 | 'post /process': 'processTransactions' 53 | }); 54 | 55 | httpApi.registerEndpoint('/api/transactions', app, router, transactionsModule.isLoaded); 56 | } 57 | 58 | module.exports = TransactionsHttpApi; 59 | -------------------------------------------------------------------------------- /api/ws/server.js: -------------------------------------------------------------------------------- 1 | const { Server } = require('socket.io'); 2 | const Peer = require('../../logic/peer'); 3 | 4 | /** 5 | * Creates a WebSocket server to broadcast transactions/blocks/signature changes 6 | */ 7 | class WebSocketServer { 8 | constructor(server, appConfig) { 9 | this.io = new Server(server, { 10 | allowEIO3: true, 11 | cors: appConfig.cors, 12 | }); 13 | 14 | this.enabled = appConfig.wsNode.enabled; 15 | this.max = appConfig.wsNode.maxBroadcastConnections; 16 | } 17 | 18 | /** 19 | * Initializes the server and authorizes connections 20 | * @param {{ peers: Peers }} logic logic modules 21 | */ 22 | initialize(logic) { 23 | if (!this.enabled) { 24 | return; 25 | } 26 | 27 | this.io.on('connection', (socket) => { 28 | const peerIp = socket.handshake.address || socket.request.socket.remoteAddress; 29 | const { nonce } = socket.handshake.auth; 30 | 31 | if (!nonce) { 32 | socket.disconnect(true); 33 | return; 34 | } 35 | 36 | const existingPeer = logic.peers.getByNonce(nonce); 37 | 38 | // Handle IPv6-mapped IPv4 addresses 39 | const normalizeIp = (ip) => ip.replace(/^::ffff:/, ''); 40 | 41 | if ( 42 | !existingPeer || 43 | normalizeIp(peerIp) !== normalizeIp(existingPeer.ip) || 44 | existingPeer.state === Peer.STATE.BANNED 45 | ) { 46 | socket.disconnect(true); 47 | return; 48 | } 49 | 50 | if (logic.peers.getSocketCount() >= this.max) { 51 | const reason = 'Server connection limit exceeded'; 52 | socket.emit('disconnect_reason', reason); 53 | socket.disconnect(true); 54 | return; 55 | } 56 | 57 | existingPeer.isBroadcastingViaSocket = true; 58 | 59 | socket.on('disconnect', () => { 60 | const disconnectedPeer = logic.peers.getByNonce(nonce); 61 | 62 | if (disconnectedPeer) { 63 | disconnectedPeer.isBroadcastingViaSocket = false; 64 | } 65 | }); 66 | }); 67 | } 68 | 69 | /** 70 | * Emits data to all socket connections 71 | * @param {string} eventName emitting event name 72 | * @param {any} data data to emit 73 | */ 74 | emit(eventName, data) { 75 | if (this.enabled) { 76 | this.io.sockets.emit(eventName, data); 77 | } 78 | } 79 | } 80 | 81 | module.exports = WebSocketServer; 82 | -------------------------------------------------------------------------------- /build: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Adamant-im/adamant/9cc651471d824471856d4cd4ad04e14c801b9654/build -------------------------------------------------------------------------------- /config.default.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": 36666, 3 | "address": "0.0.0.0", 4 | "fileLogLevel": "warn", 5 | "logFileName": "logs/adamant.log", 6 | "consoleLogLevel": "info", 7 | "trustProxy": false, 8 | "topAccounts": false, 9 | "cacheEnabled": false, 10 | "db": { 11 | "host": "localhost", 12 | "port": 5432, 13 | "database": "adamant_main", 14 | "user": "adamant", 15 | "password": "password", 16 | "poolSize": 95, 17 | "poolIdleTimeout": 30000, 18 | "reapIntervalMillis": 1000, 19 | "logEvents": [ 20 | "error" 21 | ] 22 | }, 23 | "redis": { 24 | "url": "redis://127.0.0.1:6379/0", 25 | "password": null 26 | }, 27 | "api": { 28 | "enabled": true, 29 | "access": { 30 | "public": false, 31 | "whiteList": [ 32 | "127.0.0.1" 33 | ] 34 | }, 35 | "options": { 36 | "limits": { 37 | "max": 0, 38 | "delayMs": 0, 39 | "delayAfter": 0, 40 | "windowMs": 60000 41 | } 42 | } 43 | }, 44 | "peers": { 45 | "enabled": true, 46 | "list": [ 47 | { 48 | "ip": "5.161.68.61", 49 | "port": 36666 50 | }, 51 | { 52 | "ip": "149.102.157.15", 53 | "port": 36666 54 | }, 55 | { 56 | "ip": "78.47.205.206", 57 | "port": 36666 58 | }, 59 | { 60 | "ip": "107.161.26.184", 61 | "port": 36666 62 | }, 63 | { 64 | "ip": "138.201.152.191", 65 | "port": 36666 66 | }, 67 | { 68 | "ip": "184.94.215.92", 69 | "port": 45555 70 | }, 71 | { 72 | "ip": "207.244.243.23", 73 | "port": 45555 74 | } 75 | ], 76 | "access": { 77 | "blackList": [] 78 | }, 79 | "options": { 80 | "limits": { 81 | "max": 0, 82 | "delayMs": 0, 83 | "delayAfter": 0, 84 | "windowMs": 60000 85 | }, 86 | "timeout": 5000 87 | } 88 | }, 89 | "broadcasts": { 90 | "broadcastInterval": 1500, 91 | "broadcastLimit": 20, 92 | "parallelLimit": 20, 93 | "releaseLimit": 25, 94 | "relayLimit": 4 95 | }, 96 | "transactions": { 97 | "maxTxsPerQueue": 1000 98 | }, 99 | "forging": { 100 | "force": false, 101 | "secret": [], 102 | "access": { 103 | "whiteList": [ 104 | "127.0.0.1" 105 | ] 106 | } 107 | }, 108 | "loading": { 109 | "verifyOnLoading": false, 110 | "loadPerIteration": 5000 111 | }, 112 | "ssl": { 113 | "enabled": false, 114 | "options": { 115 | "port": 443, 116 | "address": "0.0.0.0", 117 | "key": "./ssl/adamant.key", 118 | "cert": "./ssl/adamant.crt" 119 | } 120 | }, 121 | "dapp": { 122 | "masterrequired": true, 123 | "masterpassword": "", 124 | "autoexec": [] 125 | }, 126 | "wsClient": { 127 | "portWS": 36668, 128 | "enabled": true 129 | }, 130 | "wsNode": { 131 | "enabled": true, 132 | "maxBroadcastConnections": 15, 133 | "maxReceiveConnections": 25 134 | }, 135 | "nethash": "bd330166898377fb28743ceef5e43a5d9d0a3efd9b3451fb7bc53530bb0a6d64" 136 | } 137 | -------------------------------------------------------------------------------- /helpers/RoundChanges.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var bignum = require('./bignum'); 4 | var slots = require('./slots'); 5 | var exceptions = require('./exceptions'); 6 | 7 | /** 8 | * Sets round fees and rewards 9 | * @requires helpers/bignum 10 | * @requires helpers/slots 11 | * @memberof module:helpers 12 | * @constructor 13 | * @param {Object} scope 14 | */ 15 | // Constructor 16 | function RoundChanges (scope) { 17 | this.roundFees = Math.floor(scope.roundFees) || 0; 18 | this.roundRewards = (scope.roundRewards || []); 19 | 20 | // Apply exception for round if required 21 | if (exceptions.rounds[scope.round]) { 22 | // Apply rewards factor 23 | this.roundRewards.forEach(function (reward, index) { 24 | this.roundRewards[index] = new bignum(reward.toPrecision(15)) 25 | .times(exceptions.rounds[scope.round].rewards_factor) 26 | .integerValue(bignum.ROUND_FLOOR); 27 | }.bind(this)); 28 | 29 | // Apply fees factor and bonus 30 | this.roundFees = new bignum(this.roundFees.toPrecision(15)) 31 | .times(exceptions.rounds[scope.round].fees_factor) 32 | .plus(exceptions.rounds[scope.round].fees_bonus) 33 | .integerValue(bignum.ROUND_FLOOR); 34 | } 35 | } 36 | 37 | // Public methods 38 | /** 39 | * Calculates rewards at round position. 40 | * Fees and feesRemaining based on slots 41 | * @implements bignum 42 | * @implements slots 43 | * @param {number} index 44 | * @return {Object} Contains fees, feesRemaining, rewards, balance 45 | */ 46 | RoundChanges.prototype.at = function (index) { 47 | var fees = new bignum(this.roundFees.toPrecision(15)).dividedBy(slots.delegates).integerValue(bignum.ROUND_FLOOR); 48 | var feesRemaining = new bignum(this.roundFees.toPrecision(15)).minus(fees.times(slots.delegates)); 49 | var rewards = new bignum(this.roundRewards[index].toPrecision(15)).integerValue(bignum.ROUND_FLOOR) || 0; 50 | 51 | return { 52 | fees: Number(fees.toFixed()), 53 | feesRemaining: Number(feesRemaining.toFixed()), 54 | rewards: Number(rewards.toFixed()), 55 | balance: Number(fees.plus(rewards).toFixed()) 56 | }; 57 | }; 58 | 59 | module.exports = RoundChanges; 60 | -------------------------------------------------------------------------------- /helpers/accounts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var crypto = require('crypto'); 4 | 5 | var bignum = require('./bignum.js'); 6 | 7 | // let sodium = require('sodium'); 8 | var sodium = require('sodium-browserify-tweetnacl'); 9 | 10 | let Mnemonic = require('bitcore-mnemonic'); 11 | const { isPublicKey } = require('./publicKey.js'); 12 | 13 | var accounts = {}; 14 | 15 | /** 16 | * Gets address by public key 17 | * @private 18 | * @implements {crypto.createHash} 19 | * @implements {bignum.fromBuffer} 20 | * @param {string} publicKey 21 | * @return {string} The address matching the public key, or an empty string if an invalid public key was provided 22 | */ 23 | accounts.getAddressByPublicKey = function (publicKey) { 24 | if (!isPublicKey(publicKey)) { 25 | return ''; 26 | } 27 | 28 | var publicKeyHash = crypto.createHash('sha256').update(publicKey, 'hex').digest(); 29 | var temp = Buffer.alloc(8); 30 | 31 | for (var i = 0; i < 8; i++) { 32 | temp[i] = publicKeyHash[7 - i]; 33 | } 34 | 35 | return 'U' + bignum.fromBuffer(temp).toString(); 36 | }; 37 | 38 | accounts.makeKeypair = function (hash) { 39 | let keypair = sodium.crypto_sign_seed_keypair(hash); 40 | 41 | return { 42 | publicKey: keypair.publicKey, 43 | privateKey: keypair.secretKey 44 | }; 45 | }; 46 | 47 | accounts.createPassPhraseHash = function (passPhrase) { 48 | let secretMnemonic = new Mnemonic(passPhrase, Mnemonic.Words.ENGLISH); 49 | return crypto.createHash('sha256').update(secretMnemonic.toSeed().toString('hex'), 'hex').digest(); 50 | }; 51 | 52 | module.exports = accounts; 53 | -------------------------------------------------------------------------------- /helpers/bignum.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Buffer functions that implements bignumber. 5 | * @memberof module:helpers 6 | * @requires bignumber 7 | * @constructor 8 | */ 9 | var BigNumber = require('bignumber.js'); 10 | 11 | /** 12 | * Creates an instance from a Buffer. 13 | * @param {ArrayBuffer} buf 14 | * @param {Object} opts 15 | * @return {ArrayBuffer} new BigNumber instance 16 | * @throws {RangeError} error description multiple of size 17 | */ 18 | BigNumber.fromBuffer = function (buf, opts) { 19 | if (!opts) opts = {}; 20 | 21 | var endian = { 1 : 'big', '-1' : 'little' }[opts.endian] || opts.endian || 'big'; 22 | 23 | var size = opts.size === 'auto' ? Math.ceil(buf.length) : (opts.size || 1); 24 | 25 | if (buf.length % size !== 0) { 26 | throw new RangeError('Buffer length (' + buf.length + ')' 27 | + ' must be a multiple of size (' + size + ')' 28 | ); 29 | } 30 | 31 | var hex = []; 32 | for (var i = 0; i < buf.length; i += size) { 33 | var chunk = []; 34 | for (var j = 0; j < size; j++) { 35 | chunk.push(buf[ 36 | i + (endian === 'big' ? j : (size - j - 1)) 37 | ]); 38 | } 39 | 40 | hex.push(chunk 41 | .map(function (c) { 42 | return (c < 16 ? '0' : '') + c.toString(16); 43 | }) 44 | .join('') 45 | ); 46 | } 47 | 48 | return new BigNumber(hex.join(''), 16); 49 | }; 50 | 51 | /** 52 | * Returns an instance as Buffer. 53 | * @param {Object} opts 54 | * @return {ArrayBuffer} new buffer | error message invalid option 55 | */ 56 | BigNumber.prototype.toBuffer = function ( opts ) { 57 | if (typeof opts === 'string') { 58 | if (opts !== 'mpint') return 'Unsupported Buffer representation'; 59 | 60 | var abs = this.abs(); 61 | var buf = abs.toBuffer({ size : 1, endian : 'big' }); 62 | var len = buf.length === 1 && buf[0] === 0 ? 0 : buf.length; 63 | if (buf[0] & 0x80) len ++; 64 | 65 | var ret = Buffer.alloc(4 + len); 66 | if (len > 0) buf.copy(ret, 4 + (buf[0] & 0x80 ? 1 : 0)); 67 | if (buf[0] & 0x80) ret[4] = 0; 68 | 69 | ret[0] = len & (0xff << 24); 70 | ret[1] = len & (0xff << 16); 71 | ret[2] = len & (0xff << 8); 72 | ret[3] = len & (0xff << 0); 73 | 74 | // Two's compliment for negative integers 75 | var isNeg = this.lt(0); 76 | if (isNeg) { 77 | for (var i = 4; i < ret.length; i++) { 78 | ret[i] = 0xff - ret[i]; 79 | } 80 | } 81 | ret[4] = (ret[4] & 0x7f) | (isNeg ? 0x80 : 0); 82 | if (isNeg) ret[ret.length - 1] ++; 83 | 84 | return ret; 85 | } 86 | 87 | if (!opts) opts = {}; 88 | 89 | var endian = { 1 : 'big', '-1' : 'little' }[opts.endian] || opts.endian || 'big'; 90 | 91 | var hex = this.toString(16); 92 | if (hex.charAt(0) === '-') throw new Error( 93 | 'Converting negative numbers to Buffers not supported yet' 94 | ); 95 | 96 | var size = opts.size === 'auto' ? Math.ceil(hex.length / 2) : (opts.size || 1); 97 | 98 | var len = Math.ceil(hex.length / (2 * size)) * size; 99 | var buf = Buffer.alloc(len); 100 | 101 | // Zero-pad the hex string so the chunks are all `size` long 102 | while (hex.length < 2 * len) hex = '0' + hex; 103 | 104 | var hx = hex 105 | .split(new RegExp('(.{' + (2 * size) + '})')) 106 | .filter(function (s) { return s.length > 0 }); 107 | 108 | hx.forEach(function (chunk, i) { 109 | for (var j = 0; j < size; j++) { 110 | var ix = i * size + (endian === 'big' ? j : size - j - 1); 111 | buf[ix] = parseInt(chunk.slice(j*2,j*2+2), 16); 112 | } 113 | }); 114 | 115 | return buf; 116 | }; 117 | 118 | module.exports = BigNumber; 119 | -------------------------------------------------------------------------------- /helpers/cache.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var redis = require('redis'); 4 | 5 | /** 6 | * Connects with redis server using the config provided via parameters 7 | * @param {Boolean} cacheEnabled 8 | * @param {Object} config - Redis configuration 9 | * @param {Object} logger 10 | * @param {Function} cb 11 | */ 12 | module.exports.connect = function (cacheEnabled, config, logger, cb) { 13 | var isRedisLoaded = false; 14 | 15 | if (!cacheEnabled) { 16 | return cb(null, { cacheEnabled: cacheEnabled, client: null }); 17 | } 18 | 19 | // delete password key if it's value is null 20 | if (config.password === null) { 21 | delete config.password; 22 | } 23 | 24 | var client = redis.createClient(config); 25 | 26 | client.connect() 27 | .then(() => { 28 | logger.info('App connected with redis server'); 29 | 30 | if (!isRedisLoaded) { 31 | isRedisLoaded = true; 32 | client.ready = isRedisLoaded; 33 | return cb(null, { cacheEnabled: cacheEnabled, client: client }); 34 | } 35 | }) 36 | .catch((err) => { 37 | logger.error('Redis:', err); 38 | // Only throw an error if cache was enabled in config but were unable to load it properly 39 | if (!isRedisLoaded) { 40 | isRedisLoaded = true; 41 | return cb(null, { cacheEnabled: cacheEnabled, client: null }); 42 | } 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /helpers/checkIpInList.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var ip = require('neoip'); 5 | 6 | /** 7 | * Checks if ip address is in list (e.g. whitelist, blacklist). 8 | * @memberof module:helpers 9 | * @function 10 | * @param {array} list - An array of ip addresses or ip subnets. 11 | * @param {string} addr - The ip address to check if in array. 12 | * @param {boolean} returnListIsEmpty - The return value, if list is empty. 13 | * @return {boolean} True if ip is in the list, false otherwise. 14 | */ 15 | function CheckIpInList (list, addr, returnListIsEmpty) { 16 | var i, n; 17 | 18 | if (!_.isBoolean(returnListIsEmpty)) { 19 | returnListIsEmpty = true; 20 | } 21 | 22 | if (!_.isArray(list) || list.length === 0) { 23 | return returnListIsEmpty; 24 | } 25 | 26 | if (!list._subNets) { // First call, create subnet list 27 | list._subNets = []; 28 | for (i = list.length - 1; i >= 0; i--) { 29 | var entry = list[i]; 30 | if (ip.isV4Format(entry)) { // IPv4 host entry 31 | entry = entry + '/32'; 32 | } else if (ip.isV6Format(entry)) { // IPv6 host entry 33 | entry = entry + '/128'; 34 | } 35 | try { 36 | var subnet = ip.cidrSubnet(entry); 37 | list._subNets.push(subnet); 38 | } catch (err) { 39 | console.error('CheckIpInList:', err.toString()); 40 | } 41 | } 42 | } 43 | 44 | if (list._subNets.length === 0) { 45 | return returnListIsEmpty; 46 | } 47 | 48 | // Check subnets 49 | for (i = 0, n = list._subNets.length; i < n; i++) { 50 | if (list._subNets[i].contains(addr)) { 51 | return true; 52 | } 53 | } 54 | 55 | // IP address not found 56 | return false; 57 | } 58 | 59 | module.exports = CheckIpInList; 60 | -------------------------------------------------------------------------------- /helpers/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var z_schema = require('./z_schema.js'); 6 | var configSchema = require('../schema/config.js'); 7 | var constants = require('../helpers/constants.js'); 8 | 9 | /** 10 | * Loads config.json file 11 | * @memberof module:helpers 12 | * @implements {validateForce} 13 | * @param {string} configPath 14 | * @return {Object} configData 15 | */ 16 | function Config (configPath) { 17 | var configData = fs.readFileSync(path.resolve(process.cwd(), (configPath || 'config.json')), 'utf8'); 18 | 19 | if (!configData.length) { 20 | console.log('Failed to read config file'); 21 | process.exit(1); 22 | } else { 23 | configData = JSON.parse(configData); 24 | } 25 | 26 | var validator = new z_schema(); 27 | var valid = validator.validate(configData, configSchema.config); 28 | 29 | if (!valid) { 30 | console.log('Failed to validate config data', validator.getLastErrors()); 31 | process.exit(1); 32 | } else { 33 | validateForce(configData); 34 | return configData; 35 | } 36 | } 37 | 38 | /** 39 | * Validates nethash value from constants and sets forging force to false if any. 40 | * @private 41 | * @param {Object} configData 42 | */ 43 | function validateForce (configData) { 44 | if (configData.forging.force) { 45 | var index = constants.nethashes.indexOf(configData.nethash); 46 | 47 | if (index !== -1) { 48 | console.log('Forced forging disabled for nethash', configData.nethash); 49 | configData.forging.force = false; 50 | } 51 | } 52 | } 53 | 54 | // Exports 55 | module.exports = Config; 56 | -------------------------------------------------------------------------------- /helpers/dappCategories.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @typedef {Object} dappCategory 5 | * - 0: Education 6 | * - 1: Entertainment 7 | * - 2: Finance 8 | * - 3: Games 9 | * - 4: Miscellaneous 10 | * - 5: Networking 11 | * - 6: Science 12 | * - 7: Social 13 | * - 8: Utilities 14 | */ 15 | module.exports = { 16 | Education: 0, 17 | Entertainment: 1, 18 | Finance: 2, 19 | Games: 3, 20 | Miscellaneous: 4, 21 | Networking: 5, 22 | Science: 6, 23 | Social: 7, 24 | Utilities: 8 25 | }; 26 | -------------------------------------------------------------------------------- /helpers/dappTypes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @typedef {Object} dappType 4 | * - 0: DAPP 5 | * - 1: FILE 6 | */ 7 | module.exports = { 8 | DAPP: 0, 9 | FILE: 1 10 | }; 11 | -------------------------------------------------------------------------------- /helpers/diff.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @memberof module:helpers 4 | * @module helpers/diff 5 | */ 6 | module.exports = { 7 | /** 8 | * Changes operation sign. 9 | * @param {Array} diff 10 | * @return {Array} reverse sign. 11 | */ 12 | reverse: function (diff) { 13 | var copyDiff = diff.slice(); 14 | for (var i = 0; i < copyDiff.length; i++) { 15 | var math = copyDiff[i][0] === '-' ? '+' : '-'; 16 | copyDiff[i] = math + copyDiff[i].slice(1); 17 | } 18 | return copyDiff; 19 | }, 20 | 21 | /** 22 | * Acts over source content adding(+) or deleting(-) public keys based on diff content. 23 | * @param {Array} source 24 | * @param {Array} diff 25 | * @return {Array} Source data without -publicKeys and with +publicKeys from diff. 26 | */ 27 | merge: function (source, diff) { 28 | var res = source ? source.slice() : []; 29 | var index; 30 | 31 | for (var i = 0; i < diff.length; i++) { 32 | var math = diff[i][0]; 33 | var publicKey = diff[i].slice(1); 34 | 35 | if (math === '+') { 36 | res = res || []; 37 | 38 | index = -1; 39 | if (res) { 40 | index = res.indexOf(publicKey); 41 | } 42 | if (index !== -1) { 43 | return false; 44 | } 45 | 46 | res.push(publicKey); 47 | } 48 | if (math === '-') { 49 | index = -1; 50 | if (res) { 51 | index = res.indexOf(publicKey); 52 | } 53 | if (index === -1) { 54 | return false; 55 | } 56 | res.splice(index, 1); 57 | if (!res.length) { 58 | res = null; 59 | } 60 | } 61 | } 62 | return res; 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /helpers/ed.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var sodium = require('sodium').api; 4 | 5 | var mnemonic = require('bitcore-mnemonic'); 6 | 7 | var crypto = require('crypto'); 8 | 9 | /** 10 | * Crypto functions that implements sodium. 11 | * @memberof module:helpers 12 | * @requires sodium 13 | * @namespace 14 | */ 15 | var ed = {}; 16 | 17 | /** 18 | * Returns whether the passphrase is valid mnemonic 19 | * @param {string} passphrase passhraase to test 20 | * @returns {boolean} 21 | */ 22 | ed.isValidPassphrase = function(passphrase) { 23 | return mnemonic.isValid(passphrase, mnemonic.Words.ENGLISH); 24 | } 25 | 26 | /** 27 | * Generates a new passphrase 28 | * @returns {string} passphrase 29 | */ 30 | ed.generatePassphrase = function() { 31 | const secretMnemonic = new mnemonic(mnemonic.Words.ENGLISH); 32 | return secretMnemonic.phrase; 33 | } 34 | 35 | /** 36 | * Creates a hash based on a passphrase. 37 | * @param {string} passPhrase 38 | * @return {string} hash 39 | */ 40 | 41 | ed.createPassPhraseHash = function (passPhrase) { 42 | var secretMnemonic = new mnemonic(passPhrase, mnemonic.Words.ENGLISH); 43 | return crypto.createHash('sha256').update(secretMnemonic.toSeed().toString('hex'), 'hex').digest(); 44 | }; 45 | 46 | 47 | /** 48 | * Creates a keypair based on a hash. 49 | * @implements {sodium} 50 | * @param {hash} hash 51 | * @return {Object} publicKey, privateKey 52 | */ 53 | ed.makeKeypair = function (hash) { 54 | var keypair = sodium.crypto_sign_seed_keypair(hash); 55 | 56 | return { 57 | publicKey: keypair.publicKey, 58 | privateKey: keypair.secretKey 59 | }; 60 | }; 61 | 62 | /** 63 | * Creates a signature based on a hash and a keypair. 64 | * @implements {sodium} 65 | * @param {hash} hash 66 | * @param {keypair} keypair 67 | * @return {signature} signature 68 | */ 69 | ed.sign = function (hash, keypair) { 70 | return sodium.crypto_sign_detached(hash, Buffer.from(keypair.privateKey, 'hex')); 71 | }; 72 | 73 | /** 74 | * Verifies a signature based on a hash and a publicKey. 75 | * @implements {sodium} 76 | * @param {hash} hash 77 | * @param {keypair} keypair 78 | * @return {Boolean} true id verified 79 | */ 80 | ed.verify = function (hash, signatureBuffer, publicKeyBuffer) { 81 | return sodium.crypto_sign_verify_detached(signatureBuffer, hash, publicKeyBuffer); 82 | }; 83 | 84 | module.exports = ed; 85 | -------------------------------------------------------------------------------- /helpers/exceptions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @namespace exceptions 4 | * @memberof module:helpers 5 | * @property {object} genesisPublicKey 6 | * @property {string} genesisPublicKey.mainnet 7 | * @property {string} genesisPublicKey.testnet 8 | * @property {String[]} senderPublicKey 9 | * @property {String[]} signatures 10 | * @property {String[]} multisignatures 11 | * @property {String[]} votes 12 | */ 13 | module.exports = { 14 | blockRewards: [], 15 | genesisPublicKey: { 16 | mainnet: '2efef768fc41949aaf5124d7a3663ae843fec87c930494ce37a54d83383b634d', 17 | testnet: 'b80bb6459608dcdeb9a98d1f2b0111b2bf11e53ef2933e6769bb0198e3a97aae' 18 | }, 19 | rounds: { 20 | '27040': { rewards_factor: 2, fees_factor: 2, fees_bonus: 10000000 } 21 | }, 22 | commentTransfers: [ 23 | '8641230861933359277', 24 | '10507274037528725945', 25 | '7235642948145568840', 26 | '14946390314703104642', 27 | '7249658269503790510', 28 | '14162701694028292835', 29 | '16427543247907609679', 30 | '18017833051900207372', 31 | '10472823779960469421', 32 | '9357583226439783592', 33 | '14960034546638267768', 34 | '18236690536623530817', 35 | '8825640246122242020', 36 | '1684406432082646965', 37 | '13298202744985628048', 38 | '5695513007348788766', 39 | '2356215295967522487', 40 | '14732568540226894720', 41 | '4355509966380545994', 42 | '56849833991487113', 43 | '1292459093278109347', 44 | '14827684078269063586', 45 | '7978312845511798180' 46 | ], 47 | fee: [ 48 | '8641230861933359277', // 1900178 49 | '10507274037528725945', // 1900186 50 | '7235642948145568840', // 1900186 51 | '14946390314703104642', // 1900186 52 | '7249658269503790510' // 1900187 53 | ], 54 | senderPublicKey: [ 55 | 56 | ], 57 | signatures: [ 58 | 59 | ], 60 | multisignatures: [ 61 | 62 | ], 63 | votes: [ 64 | 65 | ] 66 | }; 67 | -------------------------------------------------------------------------------- /helpers/git.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* 3 | * Helper module for parsing git commit information 4 | * 5 | * @class git.js 6 | */ 7 | 8 | var childProcess = require('child_process'); 9 | 10 | /** 11 | * Return hash of last git commit if available 12 | * @memberof module:helpers 13 | * @function 14 | * @return {String} Hash of last git commit 15 | * @throws {Error} Throws error if cannot get last git commit 16 | */ 17 | function getLastCommit () { 18 | var spawn = childProcess.spawnSync('git', ['rev-parse', 'HEAD']); 19 | var err = spawn.stderr.toString().trim(); 20 | 21 | if (err) { 22 | throw new Error(err); 23 | } else { 24 | return spawn.stdout.toString().trim(); 25 | } 26 | } 27 | 28 | module.exports = { 29 | getLastCommit: getLastCommit 30 | }; 31 | -------------------------------------------------------------------------------- /helpers/inserts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var pgp = require('pg-promise'); 4 | 5 | /** 6 | * Creates and returns an insert instance 7 | * @memberof module:helpers 8 | * @requires pg-promise 9 | * @class 10 | * @param {Object} record 11 | * @param {Object} values 12 | * @param {boolean} [concat] 13 | * @return {function} True if ip is in the list, false otherwise. 14 | * @throws {string} Error description 15 | */ 16 | 17 | function Inserts (record, values, concat) { 18 | if (!(this instanceof Inserts)) { 19 | return new Inserts(record, values, concat); 20 | } 21 | 22 | var self = this; 23 | 24 | if (!record || !record.table || !record.values) { 25 | throw 'Inserts: Invalid record argument'; 26 | } 27 | 28 | if (!values) { 29 | throw 'Inserts: Invalid values argument'; 30 | } 31 | 32 | this.namedTemplate = function () { 33 | return record.fields.map(function (field, index) { 34 | return '${' + field + '}'; 35 | }).join(','); 36 | }; 37 | 38 | this._template = this.namedTemplate(); 39 | /** 40 | * Creates pg insert sentence. 41 | * @method 42 | * @return {string} Sql sentence 43 | */ 44 | this.template = function () { 45 | var values; 46 | var fields = record.fields.map(pgp.as.name).join(','); 47 | if (concat) { 48 | values = '$1'; 49 | } else { 50 | values = '(' + this.namedTemplate() + ')'; 51 | } 52 | return pgp.as.format('INSERT INTO $1~($2^) VALUES $3^', [record.table, fields, values]); 53 | }; 54 | 55 | this.rawType = true; 56 | 57 | this.toPostgres = function () { 58 | return values.map(function (v) { 59 | return '(' + pgp.as.format(self._template, v) + ')'; 60 | }).join(','); 61 | }; 62 | } 63 | 64 | module.exports = Inserts; 65 | -------------------------------------------------------------------------------- /helpers/jobsQueue.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var jobsQueue = { 4 | 5 | jobs: {}, 6 | 7 | register: function (name, job, time) { 8 | if (this.jobs[name]) { 9 | // return this.jobs[name]; 10 | throw new Error('Synchronous job ' + name + ' already registered'); 11 | } 12 | 13 | var nextJob = function () { 14 | return job(function () { 15 | jobsQueue.jobs[name] = setTimeout(nextJob, time); 16 | }); 17 | }; 18 | 19 | nextJob(); 20 | return this.jobs[name]; 21 | } 22 | 23 | }; 24 | 25 | module.exports = jobsQueue; 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /helpers/json-schema/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json-schema", 3 | "version": "0.0.1", 4 | "main":"validator.js" 5 | } 6 | -------------------------------------------------------------------------------- /helpers/milestoneBlocks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | }; 5 | -------------------------------------------------------------------------------- /helpers/orderBy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Validates sort options, methods and fields. 5 | * @memberof module:helpers 6 | * @function 7 | * @param {array} orderBy 8 | * @param {string} options 9 | * @return {Object} error | {sortField, sortMethod}. 10 | */ 11 | function OrderBy (orderBy, options) { 12 | options = (typeof options === 'object') ? options : {}; 13 | options.sortField = options.sortField || null; 14 | options.sortMethod = options.sortMethod || null; 15 | options.sortFields = Array.isArray(options.sortFields) ? options.sortFields : []; 16 | 17 | if (typeof options.quoteField === 'undefined') { 18 | options.quoteField = true; 19 | } else { 20 | options.quoteField = Boolean(options.quoteField); 21 | } 22 | 23 | var sortField, sortMethod; 24 | 25 | if (orderBy) { 26 | var sort = String(orderBy).split(':'); 27 | sortField = sort[0].replace(/[^\w\s]/gi, ''); 28 | 29 | if (sort.length === 2) { 30 | sortMethod = sort[1] === 'desc' ? 'DESC' : 'ASC'; 31 | } 32 | } 33 | 34 | function prefixField (sortField) { 35 | if (!sortField) { 36 | return sortField; 37 | } else if (typeof options.fieldPrefix === 'string') { 38 | return options.fieldPrefix + sortField; 39 | } else if (typeof options.fieldPrefix === 'function') { 40 | return options.fieldPrefix(sortField); 41 | } else { 42 | return sortField; 43 | } 44 | } 45 | 46 | function quoteField (sortField) { 47 | if (sortField && options.quoteField) { 48 | return ('"' + sortField + '"'); 49 | } else { 50 | return sortField; 51 | } 52 | } 53 | 54 | var emptyWhiteList = options.sortFields.length === 0; 55 | 56 | var inWhiteList = options.sortFields.length >= 1 && options.sortFields.indexOf(sortField) > -1; 57 | 58 | if (sortField) { 59 | if (emptyWhiteList || inWhiteList) { 60 | sortField = prefixField(sortField); 61 | } else { 62 | return { 63 | error: 'Invalid sort field' 64 | }; 65 | } 66 | } else { 67 | sortField = prefixField(options.sortField); 68 | } 69 | 70 | if (!sortMethod) { 71 | sortMethod = options.sortMethod; 72 | } 73 | 74 | return { 75 | sortField: quoteField(sortField), 76 | sortMethod: sortMethod 77 | }; 78 | } 79 | 80 | module.exports = OrderBy; 81 | -------------------------------------------------------------------------------- /helpers/publicKey.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tests if the provided value is a valid public key 3 | * @param {any} str string to test 4 | * @returns {boolean} whether the string can be considered a public key 5 | */ 6 | exports.isPublicKey = (value) => { 7 | return typeof value === "string" && Buffer.from(value, "hex").length === 32; 8 | }; 9 | -------------------------------------------------------------------------------- /helpers/request-limiter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var rateLimit = require('express-rate-limit'); 4 | var slowDown = require('express-slow-down'); 5 | 6 | /** 7 | * Allow all requests through 8 | * @returns {true} 9 | */ 10 | function skip() { 11 | return true; 12 | } 13 | 14 | var defaults = { 15 | skip: skip, // Disabled 16 | delayMs: 0, // Disabled 17 | delayAfter: 0, // Disabled 18 | windowMs: 60000 // 1 minute window 19 | }; 20 | 21 | /** 22 | * Returns limits object from input or default values. 23 | * @private 24 | * @param {Object} [limits] 25 | * @return {Object} max, delayMs, delayAfter, windowMs 26 | */ 27 | function applyLimits(config) { 28 | const limits = config ?? defaults; 29 | 30 | if (typeof limits === 'object') { 31 | const settings = { 32 | max: Math.floor(limits.max) || defaults.max, 33 | delayMs: function(used) { 34 | return (used - this.delayAfter) * (Math.floor(limits.delayMs) || defaults.delayMs); 35 | }, 36 | delayAfter: Math.floor(limits.delayAfter) || defaults.delayAfter, 37 | windowMs: Math.floor(limits.windowMs) || defaults.windowMs 38 | }; 39 | 40 | if (!limits.delayAfter) { 41 | settings.skip = skip; 42 | } 43 | 44 | return settings; 45 | } else { 46 | return defaults; 47 | } 48 | } 49 | 50 | /** 51 | * Applies limits config to app. 52 | * @memberof module:helpers 53 | * @function request-limiter 54 | * @implements applyLimits 55 | * @param {Object} app - Application instance 56 | * @param {Object} config 57 | * @return {Object} limits per client and peer 58 | */ 59 | module.exports = function (app, config) { 60 | if (config.trustProxy) { 61 | app.enable('trust proxy'); 62 | } 63 | 64 | config.api = config.api || {}; 65 | config.api.options = config.api.options || {}; 66 | 67 | config.peers = config.peers || {}; 68 | config.peers.options = config.peers.options || {}; 69 | 70 | var limits = { 71 | client: applyLimits(config.api.options.limits), 72 | peer: applyLimits(config.peers.options.limits) 73 | }; 74 | 75 | limits.middleware = { 76 | client: app.use('/api/', rateLimit(limits.client), slowDown(limits.client)), 77 | peer: app.use('/peer/', rateLimit(limits.peer), slowDown(limits.peer)) 78 | }; 79 | 80 | return limits; 81 | }; 82 | -------------------------------------------------------------------------------- /helpers/router.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var httpApi = require('./httpApi'); 4 | var extend = require('extend'); 5 | 6 | /** 7 | * Express.js router wrapper. 8 | * @memberof module:helpers 9 | * @function 10 | * @return {Object} router express 11 | * @throws {Error} If config is invalid 12 | */ 13 | var Router = function () { 14 | var router = require('express').Router(); 15 | 16 | router.map = function (root, config) { 17 | var router = this; 18 | 19 | Object.keys(config).forEach(function (params) { 20 | var route = params.split(' '); 21 | if (route.length !== 2 || ['post', 'get', 'put'].indexOf(route[0]) === -1) { 22 | throw Error('Invalid map config'); 23 | } 24 | router[route[0]](route[1], function (req, res, next) { 25 | var reqRelevantInfo = { 26 | ip: req.ip, 27 | method: req.method, 28 | path: req.path 29 | }; 30 | root[config[params]](extend({}, reqRelevantInfo, { 'body': route[0] === 'get' ? req.query : req.body }), httpApi.respond.bind(null, res)); 31 | }); 32 | }); 33 | }; 34 | /** 35 | * Adds one middleware to an array of routes. 36 | * @param {Function} middleware 37 | * @param {String} routes 38 | */ 39 | router.attachMiddlwareForUrls = function (middleware, routes) { 40 | routes.forEach(function (entry) { 41 | var route = entry.split(' '); 42 | 43 | if (route.length !== 2 || ['post', 'get', 'put'].indexOf(route[0]) === -1) { 44 | throw Error('Invalid map config'); 45 | } 46 | router[route[0]](route[1], middleware); 47 | }); 48 | }; 49 | 50 | return router; 51 | }; 52 | 53 | module.exports = Router; 54 | -------------------------------------------------------------------------------- /helpers/sandbox.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Applies methods from parameters. 5 | * @memberof module:helpers 6 | * @function 7 | * @param {function} shared - List of methods. 8 | * @param {function} call - Method to call. 9 | * @param {function} args - List of arguments. 10 | * @param {function} cb - Callback function. 11 | * @return {function} Returns cb() for error. 12 | */ 13 | function callMethod (shared, call, args, cb) { 14 | if (typeof shared[call] !== 'function') { 15 | return cb('Function not found in module: ' + call); 16 | } 17 | 18 | var callArgs = [args, cb]; 19 | shared[call].apply(null, callArgs); 20 | } 21 | 22 | module.exports = { 23 | callMethod: callMethod 24 | }; 25 | -------------------------------------------------------------------------------- /helpers/sequence.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var extend = require('extend'); 4 | var util = require('util'); 5 | 6 | /** 7 | * Creates a FIFO sequence array and default settings with config values. 8 | * Calls __tick with 3 9 | * @memberof module:helpers 10 | * @constructor 11 | * @param {string} config 12 | */ 13 | function Sequence (config) { 14 | var _default = { 15 | onWarning: null, 16 | warningLimit: 50 17 | }; 18 | _default = extend(_default, config); 19 | var self = this; 20 | this.sequence = []; 21 | this.isTicking = false; 22 | 23 | this.nextSequenceTick = function () { 24 | if (!self.sequence.length) { 25 | self.isTicking = false; 26 | return; 27 | } 28 | 29 | if (_default.onWarning && self.sequence.length >= _default.warningLimit) { 30 | _default.onWarning(self.sequence.length, _default.warningLimit); 31 | } 32 | 33 | self.__tick(self.nextSequenceTick); 34 | }; 35 | } 36 | 37 | /** 38 | * Removes the first task from sequence and execute it with args. 39 | * @param {function} cb 40 | * @return {setImmediateCallback} With cb or task.done 41 | */ 42 | Sequence.prototype.__tick = function (cb) { 43 | var task = this.sequence.shift(); 44 | if (!task) { 45 | return setImmediate(cb); 46 | } 47 | var args = [function (err, res) { 48 | if (task.done) { 49 | setImmediate(task.done, err, res); 50 | } 51 | setImmediate(cb); 52 | }]; 53 | if (task.args) { 54 | args = args.concat(task.args); 55 | } 56 | task.worker.apply(task.worker, args); 57 | }; 58 | 59 | /** 60 | * Adds a new task to sequence. 61 | * @param {function} worker 62 | * @param {Array} args 63 | * @param {function} done 64 | */ 65 | Sequence.prototype.add = function (worker, args, done) { 66 | if (!done && args && typeof(args) === 'function') { 67 | done = args; 68 | args = undefined; 69 | } 70 | if (worker && typeof(worker) === 'function') { 71 | var task = { worker: worker, done: done }; 72 | if (util.isArray(args)) { 73 | task.args = args; 74 | } 75 | this.sequence.push(task); 76 | 77 | if (!this.isTicking) { 78 | this.isTicking = true; 79 | setImmediate(this.nextSequenceTick); 80 | } 81 | } 82 | }; 83 | 84 | /** 85 | * Gets pending task in sequence. 86 | * @return {number} sequence length. 87 | */ 88 | Sequence.prototype.count = function () { 89 | return this.sequence.length; 90 | }; 91 | 92 | module.exports = Sequence; 93 | -------------------------------------------------------------------------------- /helpers/slots.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var constants = require('./constants.js'); 4 | /** 5 | * @memberof module:helpers 6 | * @module helpers/slots 7 | */ 8 | /** 9 | * Gets constant time from ADAMANT epoch. 10 | * @return {number} epochTime from constants. 11 | */ 12 | function beginEpochTime () { 13 | var d = constants.epochTime; 14 | 15 | return d; 16 | } 17 | 18 | /** 19 | * Calculates time since ADAMANT epoch. 20 | * @param {number|undefined} time - Time in unix seconds. 21 | * @return {number} current time - ADAMANT epoch time. 22 | */ 23 | function getEpochTime (time) { 24 | if (time === undefined) { 25 | time = Date.now(); 26 | } 27 | 28 | var d = beginEpochTime(); 29 | var t = d.getTime(); 30 | 31 | return time - t; 32 | } 33 | /** 34 | * @namespace 35 | */ 36 | module.exports = { 37 | /** 38 | * @property {number} interval - Slot time interval in seconds. 39 | */ 40 | interval: 5, 41 | 42 | /** 43 | * @property {number} delegates - Active delegates from constants. 44 | */ 45 | delegates: constants.activeDelegates, 46 | 47 | /** 48 | * Converts a timestamp in ms to ADAMANT epoch time in seconds. 49 | * Returns the current time if the `time` parameter is omitted. 50 | * @param {number?} time - Timestamp in ms 51 | * @return {number} ADAMANT epoch time (passed since ADM blockchain creation) constant in sec. 52 | */ 53 | getTime: function (time) { 54 | return Math.floor(getEpochTime(time) / 1000); 55 | }, 56 | 57 | /** 58 | * Converts a timestamp in ms to ADAMANT epoch time in ms. 59 | * Returns the current time if the `time` parameter is omitted. 60 | * @param {number?} time - Timestamp in ms 61 | * @return {number} ADAMANT epoch time (passed since ADM blockchain creation) constant in ms. 62 | */ 63 | getTimeMs: function (time) { 64 | return getEpochTime(time); 65 | }, 66 | 67 | /** 68 | * @method 69 | * @param {number} [epochTime] 70 | * @return {number} constant time from ADAMANT epoch + input time. 71 | */ 72 | getRealTime: function (epochTime) { 73 | if (epochTime === undefined) { 74 | epochTime = this.getTime(); 75 | } 76 | 77 | var d = beginEpochTime(); 78 | var t = Math.floor(d.getTime() / 1000) * 1000; 79 | 80 | return t + epochTime * 1000; 81 | }, 82 | 83 | /** 84 | * @method 85 | * @param {number} [epochTime] - time or 86 | * @return {number} input time / slot interval. 87 | */ 88 | getSlotNumber: function (epochTime) { 89 | if (epochTime === undefined) { 90 | epochTime = this.getTime(); 91 | } 92 | 93 | return Math.floor(epochTime / this.interval); 94 | }, 95 | 96 | /** 97 | * @method 98 | * @param {number} slot - slot number 99 | * @return {number} input slot * slot interval. 100 | */ 101 | getSlotTime: function (slot) { 102 | return slot * this.interval; 103 | }, 104 | 105 | /** 106 | * @method 107 | * @return {number} current slot number + 1. 108 | */ 109 | getNextSlot: function () { 110 | var slot = this.getSlotNumber(); 111 | 112 | return slot + 1; 113 | }, 114 | 115 | /** 116 | * @method 117 | * @param {number} nextSlot 118 | * @return {number} input next slot + delegates. 119 | */ 120 | getLastSlot: function (nextSlot) { 121 | return nextSlot + this.delegates; 122 | }, 123 | 124 | roundTime: function (date) { 125 | return Math.floor(date.getTime() / 1000) * 1000; 126 | } 127 | }; 128 | -------------------------------------------------------------------------------- /helpers/tranasctionTypesBoundary.js: -------------------------------------------------------------------------------- 1 | const TransactionTypes = require('./transactionTypes'); 2 | 3 | /** 4 | * List of transaction types excluding chat message types 5 | */ 6 | const transactionValues = Object 7 | .values(TransactionTypes) 8 | .filter((type) => typeof type === 'number'); 9 | 10 | /** 11 | * List of chat message transaction types 12 | */ 13 | const transactionChatAssetValues = Object.values(TransactionTypes.CHAT_MESSAGE_TYPES); 14 | 15 | exports.MIN_TRANSACTION_TYPE = Math.min(...transactionValues); 16 | exports.MAX_TRANSACTION_TYPE = Math.max(...transactionValues); 17 | 18 | exports.MIN_CHAT_MESSAGE_TRANSACTION_TYPE = Math.min(...transactionChatAssetValues); 19 | exports.MAX_CHAT_MESSAGE_TRANSACTION_TYPE = Math.max(...transactionChatAssetValues); 20 | -------------------------------------------------------------------------------- /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 | DAPP: 5, 10 | IN_TRANSFER: 6, 11 | OUT_TRANSFER: 7, 12 | CHAT_MESSAGE: 8, 13 | STATE: 9, 14 | CHAT_MESSAGE_TYPES: { 15 | LEGACY_MESSAGE: 0, 16 | ORDINARY_MESSAGE: 1, 17 | RICH_TEXT_MESSAGE: 2, 18 | SIGNAL_MESSAGE: 3 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /helpers/validator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "validator", 3 | "version": "0.0.1", 4 | "main":"validator.js" 5 | } 6 | -------------------------------------------------------------------------------- /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/z_schema-express.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Applies z_schema to validate schema. 5 | * @memberof module:helpers 6 | * @function z_schema-express 7 | * @param {function} z_schema 8 | * @return {function} 9 | */ 10 | module.exports = function (z_schema) { 11 | return function (req, res, next) { 12 | req.sanitize = sanitize; 13 | 14 | function sanitize (value, schema, callback) { 15 | return z_schema.validate(value, schema, function (err, valid) { 16 | return callback(null, { 17 | isValid: valid, 18 | issues: err ? err[0].message + ': ' + err[0].path : null 19 | }, value); 20 | }); 21 | } 22 | 23 | next(); 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /helpers/z_schema.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var ip = require('neoip'); 4 | /** 5 | * Uses JSON Schema validator z_schema to register custom formats. 6 | * - id 7 | * - address 8 | * - username 9 | * - hex 10 | * - publicKey 11 | * - csv 12 | * - signature 13 | * - queryList 14 | * - delegatesList 15 | * - parsedInt 16 | * - ip 17 | * - os 18 | * - version 19 | * @see {@link https://github.com/zaggino/z-schema} 20 | * @memberof module:helpers 21 | * @requires ip 22 | * @constructor 23 | * @return {Boolean} True if the format is valid 24 | */ 25 | const z_schema = require('z-schema'); 26 | const semver = require('semver'); 27 | const { isPublicKey } = require('./publicKey.js'); 28 | 29 | z_schema.registerFormat('id', function (str) { 30 | if (str.length === 0) { 31 | return true; 32 | } 33 | 34 | return /^[0-9]+$/g.test(str); 35 | }); 36 | 37 | z_schema.registerFormat('address', function (str) { 38 | return ( 39 | typeof str === 'string' && 40 | /^[U][0-9]{1,21}$/i.test(str) 41 | ); 42 | }); 43 | 44 | z_schema.registerFormat('username', function (str) { 45 | if (str.length === 0) { 46 | return true; 47 | } 48 | 49 | return /^[a-z0-9!@$&_.]+$/ig.test(str); 50 | }); 51 | 52 | z_schema.registerFormat('hex', function (str) { 53 | try { 54 | Buffer.from(str, 'hex'); 55 | } catch (e) { 56 | return false; 57 | } 58 | 59 | return true; 60 | }); 61 | 62 | z_schema.registerFormat('publicKey', function (str) { 63 | if (str.length === 0) { 64 | return true; 65 | } 66 | 67 | return isPublicKey(str) 68 | }); 69 | 70 | z_schema.registerFormat('csv', function (str) { 71 | try { 72 | var a = str.split(','); 73 | if (a.length > 0 && a.length <= 1000) { 74 | return true; 75 | } else { 76 | return false; 77 | } 78 | } catch (e) { 79 | return false; 80 | } 81 | }); 82 | 83 | z_schema.registerFormat('signature', function (str) { 84 | if (str.length === 0) { 85 | return true; 86 | } 87 | 88 | try { 89 | var signature = Buffer.from(str, 'hex'); 90 | return signature.length === 64; 91 | } catch (e) { 92 | return false; 93 | } 94 | }); 95 | 96 | z_schema.registerFormat('queryList', function (obj) { 97 | obj.limit = 100; 98 | return true; 99 | }); 100 | 101 | z_schema.registerFormat('delegatesList', function (obj) { 102 | obj.limit = 101; 103 | return true; 104 | }); 105 | 106 | z_schema.registerFormat('parsedInt', function (value) { 107 | /* eslint-disable eqeqeq */ 108 | if (isNaN(value) || parseInt(value) != value || isNaN(parseInt(value, 10))) { 109 | return false; 110 | } 111 | /* eslint-enable eqeqeq */ 112 | value = parseInt(value); 113 | return true; 114 | }); 115 | 116 | z_schema.registerFormat('ip', function (str) { 117 | return ip.isV4Format(str); 118 | }); 119 | 120 | z_schema.registerFormat('os', function (str) { 121 | if (str.length === 0) { 122 | return true; 123 | } 124 | 125 | return /^[a-z0-9-_.+]+$/ig.test(str); 126 | }); 127 | 128 | z_schema.registerFormat('version', function (str) { 129 | if (str.length === 0) { 130 | return true; 131 | } 132 | 133 | return !!semver.valid(str); 134 | }); 135 | 136 | // var registeredFormats = z_schema.getRegisteredFormats(); 137 | // console.log(registeredFormats); 138 | 139 | // Exports 140 | module.exports = z_schema; 141 | -------------------------------------------------------------------------------- /img/adm-nodes.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Adamant-im/adamant/9cc651471d824471856d4cd4ad04e14c801b9654/img/adm-nodes.jpeg -------------------------------------------------------------------------------- /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 | none: 99, 14 | trace: 0, 15 | debug: 1, 16 | log: 2, 17 | info: 3, 18 | warn: 4, 19 | error: 5, 20 | fatal: 6 21 | }; 22 | 23 | config.level_abbr = config.level_abbr || { 24 | trace: 'trc', 25 | debug: 'dbg', 26 | log: 'log', 27 | info: 'inf', 28 | warn: 'WRN', 29 | error: 'ERR', 30 | fatal: 'FTL' 31 | }; 32 | 33 | config.filename = config.filename || __dirname + '/logs.log'; 34 | 35 | config.errorLevel = config.errorLevel || 'log'; 36 | 37 | var log_file = fs.createWriteStream(config.filename, { flags: 'a' }); 38 | 39 | exports.setLevel = function (errorLevel) { 40 | config.errorLevel = errorLevel; 41 | }; 42 | 43 | function snipsecret (data) { 44 | for (var key in data) { 45 | if (key.search(/secret/i) > -1) { 46 | data[key] = 'XXXXXXXXXX'; 47 | } 48 | } 49 | return data; 50 | } 51 | 52 | Object.keys(config.levels).forEach(function (name) { 53 | function log (message, data) { 54 | var log = { 55 | level: name, 56 | timestamp: strftime('%F %T', new Date()) 57 | }; 58 | 59 | if (message instanceof Error) { 60 | log.message = message.stack; 61 | } else { 62 | log.message = message; 63 | } 64 | 65 | if (data && util.isObject(data)) { 66 | log.data = JSON.stringify(snipsecret(data)); 67 | } else { 68 | log.data = data; 69 | } 70 | 71 | log.symbol = config.level_abbr[log.level] ? config.level_abbr[log.level] : '???'; 72 | 73 | if (config.levels[config.errorLevel] <= config.levels[log.level]) { 74 | if (log.data) { 75 | log_file.write(util.format('[%s] %s | %s - %s\n', log.symbol, log.timestamp, log.message, log.data)); 76 | } else { 77 | log_file.write(util.format('[%s] %s | %s\n', log.symbol, log.timestamp, log.message)); 78 | } 79 | } 80 | 81 | if (config.echo && config.levels[config.echo] <= config.levels[log.level]) { 82 | if (log.data) { 83 | console.log('[' + log.symbol.bgYellow.black + ']', log.timestamp.grey, '|', log.message, '-', log.data); 84 | } else { 85 | console.log('[' + log.symbol.bgYellow.black + ']', log.timestamp.grey, '|', log.message); 86 | } 87 | } 88 | } 89 | 90 | exports[name] = log; 91 | }); 92 | 93 | return exports; 94 | }; 95 | -------------------------------------------------------------------------------- /logic/blockReward.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var constants = require('../helpers/constants.js'); 4 | 5 | // Private fields 6 | var __private = {}; 7 | 8 | /** 9 | * Initializes variables: 10 | * - milestones 11 | * - distance 12 | * - rewardOffset 13 | * @memberof module:blocks 14 | * @class 15 | * @classdesc Main BlockReward logic. 16 | */ 17 | // Constructor 18 | function BlockReward () { 19 | // Array of milestones 20 | this.milestones = constants.rewards.milestones; 21 | 22 | // Distance between each milestone 23 | this.distance = Math.floor(constants.rewards.distance); 24 | 25 | // Start rewards at block (n) 26 | this.rewardOffset = Math.floor(constants.rewards.offset); 27 | } 28 | 29 | // Private methods 30 | /** 31 | * Returns absolute value from number. 32 | * @private 33 | * @param {number} height 34 | * @return {number} 35 | * @throws Invalid block height 36 | */ 37 | __private.parseHeight = function (height) { 38 | if (isNaN(height)) { 39 | throw 'Invalid block height'; 40 | } else { 41 | return Math.abs(height); 42 | } 43 | }; 44 | 45 | // Public methods 46 | /** 47 | * @implements {__private.parseHeight} 48 | * @param {number} height 49 | * @return {number} 50 | */ 51 | BlockReward.prototype.calcMilestone = function (height) { 52 | height = __private.parseHeight(height); 53 | 54 | var location = Math.trunc((height - this.rewardOffset) / this.distance); 55 | var lastMile = this.milestones[this.milestones.length - 1]; 56 | 57 | if (location > (this.milestones.length - 1)) { 58 | return this.milestones.lastIndexOf(lastMile); 59 | } else { 60 | return location; 61 | } 62 | }; 63 | 64 | /** 65 | * @implements {__private.parseHeight} 66 | * @implements {BlockReward.calcMilestone} 67 | * @param {number} height 68 | * @return {number} 69 | */ 70 | BlockReward.prototype.calcReward = function (height) { 71 | height = __private.parseHeight(height); 72 | 73 | if (height < this.rewardOffset) { 74 | return 0; 75 | } else { 76 | return this.milestones[this.calcMilestone(height)]; 77 | } 78 | }; 79 | 80 | /** 81 | * @implements {__private.parseHeight} 82 | * @implements {BlockReward.calcMilestone} 83 | * @param {number} height 84 | * @return {number} 85 | */ 86 | BlockReward.prototype.calcSupply = function (height) { 87 | height = __private.parseHeight(height); 88 | 89 | if (height < this.rewardOffset) { 90 | // Rewards not started yet 91 | return constants.totalAmount; 92 | } 93 | 94 | var milestone = this.calcMilestone(height); 95 | var supply = constants.totalAmount; 96 | var rewards = []; 97 | 98 | var amount = 0, multiplier = 0; 99 | 100 | // Remove offset from height 101 | height -= this.rewardOffset - 1; 102 | 103 | for (var i = 0; i < this.milestones.length; i++) { 104 | if (milestone >= i) { 105 | multiplier = this.milestones[i]; 106 | 107 | if (height < this.distance) { 108 | // Measure this.distance thus far 109 | amount = height % this.distance; 110 | } else { 111 | amount = this.distance; // Assign completed milestone 112 | height -= this.distance; // Deduct from total height 113 | 114 | // After last milestone 115 | if (height > 0 && i === this.milestones.length - 1) { 116 | amount += height; 117 | } 118 | } 119 | 120 | rewards.push([amount, multiplier]); 121 | } else { 122 | break; // Milestone out of bounds 123 | } 124 | } 125 | 126 | for (i = 0; i < rewards.length; i++) { 127 | var reward = rewards[i]; 128 | supply += reward[0] * reward[1]; 129 | } 130 | 131 | return supply; 132 | }; 133 | 134 | // Export 135 | module.exports = BlockReward; 136 | -------------------------------------------------------------------------------- /logic/consensus/activationHeights.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Consensus activation heights for different protocol upgrades. 3 | * Each key represents a protocol name, and its value is the block height 4 | * at which the consensus change is activated. 5 | */ 6 | const consensusActivationHeights = { 7 | /** 8 | * The "spaceship" upgrade introduces: 9 | * - `timestampMs` field for transactions, validated during synchronization. 10 | */ 11 | spaceship: 100_000_000, 12 | }; 13 | 14 | module.exports = { 15 | consensusActivationHeights, 16 | }; 17 | -------------------------------------------------------------------------------- /logic/consensus/consensus.js: -------------------------------------------------------------------------------- 1 | const { consensusActivationHeights } = require('./activationHeights.js'); 2 | 3 | /** 4 | * Manages consensus activation status based on blockchain height 5 | * Determines whether specific protocol upgrades have been activated 6 | */ 7 | class Consensus { 8 | constructor() { 9 | this.loader = null; 10 | } 11 | 12 | /** 13 | * Binds required modules to the Consensus instance 14 | * @param {object} modules - The modules to bind 15 | * @param {object} modules.loader - The loader module providing blockchain height 16 | */ 17 | bindModules(modules) { 18 | this.loader = modules.loader; 19 | } 20 | 21 | /** 22 | * Checks if a given consensus upgrade is activated based on the current blockchain height 23 | * @param {string} codeName - The name of the consensus upgrade 24 | * @throws {Error} If `codeName` is not a string 25 | * @return {boolean} `true` if the upgrade is activated, otherwise `false` 26 | */ 27 | isActivated(codeName) { 28 | if (typeof codeName !== 'string') { 29 | throw new Error(`Expected code name to be a string but got ${typeof codeName}`); 30 | } 31 | 32 | const activationHeight = consensusActivationHeights[codeName]; 33 | 34 | if (activationHeight === undefined) { 35 | return false; 36 | } 37 | 38 | const currentHeight = this.loader.getHeight(); 39 | 40 | return currentHeight >= activationHeight; 41 | } 42 | } 43 | 44 | module.exports = Consensus; 45 | -------------------------------------------------------------------------------- /logs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Adamant-im/adamant/9cc651471d824471856d4cd4ad04e14c801b9654/logs/.gitkeep -------------------------------------------------------------------------------- /modules/clientWs.js: -------------------------------------------------------------------------------- 1 | const { Server } = require('socket.io'); 2 | const TransactionSubscription = require('./clientWs/transactionSubscription') 3 | 4 | class ClientWs { 5 | constructor (config, logger, cb) { 6 | if (!config || !config.enabled) { 7 | return false; 8 | } 9 | const port = config.portWS; 10 | const io = new Server(port, { 11 | allowEIO3: true, 12 | cors: config.cors 13 | }); 14 | 15 | this.describes = {}; 16 | this.logger = logger; 17 | io.sockets.on('connection', (socket) => { 18 | try { 19 | const describe = new TransactionSubscription(socket); 20 | 21 | socket.on('address', (address) => { 22 | const addresses = Array.isArray(address) ? address : [address]; 23 | const subscribed = describe.subscribeToAddresses(...addresses) 24 | 25 | if (subscribed) { 26 | this.describes[socket.id] = describe; 27 | } 28 | }); 29 | 30 | socket.on('types', (type) => { 31 | const types = Array.isArray(type) ? type : [type]; 32 | const subscribed = describe.subscribeToTypes(...types) 33 | 34 | if (subscribed) { 35 | this.describes[socket.id] = describe; 36 | } 37 | }); 38 | 39 | socket.on('assetChatTypes', (type) => { 40 | const types = Array.isArray(type) ? type : [type]; 41 | const subscribed = describe.subscribeToAssetChatTypes(...types) 42 | 43 | if (subscribed) { 44 | this.describes[socket.id] = describe; 45 | } 46 | }) 47 | 48 | socket.on('disconnect', () => { 49 | delete this.describes[socket.id]; 50 | }); 51 | } catch (e) { 52 | logger.debug('Error Connection socket: ' + e); 53 | } 54 | }); 55 | 56 | if (cb) { 57 | return setImmediate(cb, null, this); 58 | } 59 | } 60 | 61 | emit (t) { 62 | if (lastTransactionsIds[t.id]) { 63 | return; 64 | } 65 | lastTransactionsIds[t.id] = getUTime(); 66 | try { 67 | const subs = findSubs(t, Object.values(this.describes)); 68 | subs.forEach((s) => { 69 | s.socket.emit('newTrans', t); 70 | }); 71 | } catch (e) { 72 | this.logger.debug('Socket error emit ' + e); 73 | } 74 | } 75 | } 76 | 77 | const lastTransactionsIds = {}; 78 | 79 | setInterval(() => { 80 | for (let id in lastTransactionsIds) { 81 | if (getUTime() - lastTransactionsIds[id] >= 60) { 82 | delete lastTransactionsIds[id]; 83 | } 84 | } 85 | }, 60 * 1000); 86 | 87 | function getUTime () { 88 | return new Date().getTime() / 1000; 89 | } 90 | 91 | function findSubs (transaction, subs) { 92 | return subs.filter((sub) => 93 | sub.impliesTransaction(transaction), 94 | ); 95 | } 96 | 97 | module.exports = ClientWs; 98 | -------------------------------------------------------------------------------- /modules/crypto.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var crypto = require('crypto'); 4 | var fs = require('fs'); 5 | var sandboxHelper = require('../helpers/sandbox.js'); 6 | 7 | // Private fields 8 | var self, __private = {}, shared = {}; 9 | 10 | __private.loaded = false; 11 | 12 | /** 13 | * @class 14 | * @classdesc Main Crypto methods. 15 | * @param {setImmediateCallback} cb - Callback function. 16 | * @param {scope} scope - App instance. 17 | */ 18 | // Constructor 19 | function Crypto (cb, scope) { 20 | self = this; 21 | 22 | setImmediate(cb, null, self); 23 | } 24 | 25 | // Public methods 26 | /** 27 | * Calls helpers.sandbox.callMethod(). 28 | * @implements module:helpers#callMethod 29 | * @param {function} call - Method to call. 30 | * @param {*} args - List of arguments. 31 | * @param {function} cb - Callback function. 32 | */ 33 | Crypto.prototype.sandboxApi = function (call, args, cb) { 34 | sandboxHelper.callMethod(shared, call, args, cb); 35 | }; 36 | 37 | // Events 38 | /** 39 | * Modules are not required in this file. 40 | * @param {modules} scope - Loaded modules. 41 | */ 42 | Crypto.prototype.onBind = function (scope) { 43 | }; 44 | 45 | /** 46 | * Sets to true private variable loaded. 47 | */ 48 | Crypto.prototype.onBlockchainReady = function () { 49 | __private.loaded = true; 50 | }; 51 | 52 | // Export 53 | module.exports = Crypto; 54 | -------------------------------------------------------------------------------- /modules/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var async = require('async'); 4 | var path = require('path'); 5 | var sandboxHelper = require('../helpers/sandbox.js'); 6 | 7 | // Private fields 8 | var modules, self, __private = {}, shared = {}; 9 | 10 | __private.loaded = false; 11 | 12 | /** 13 | * Initializes Server. 14 | * @memberof module:server 15 | * @class 16 | * @classdesc Main server methods. 17 | * @param {scope} scope - App instance. 18 | * @param {function} cb - Callback function. 19 | * @return {setImmediateCallback} Callback function with `self` as data. 20 | */ 21 | // Constructor 22 | function Server (cb, scope) { 23 | self = this; 24 | 25 | setImmediate(cb, null, self); 26 | } 27 | 28 | // Public methods 29 | /** 30 | * Calls helpers.sandbox.callMethod(). 31 | * @implements {sandboxHelper.callMethod} 32 | * @param {function} call - Method to call. 33 | * @param {} args - List of arguments. 34 | * @param {function} cb - Callback function. 35 | */ 36 | Server.prototype.sandboxApi = function (call, args, cb) { 37 | sandboxHelper.callMethod(shared, call, args, cb); 38 | }; 39 | 40 | // Events 41 | /** 42 | * Modules are not required in this file. 43 | * @param {modules} scope - Loaded modules. 44 | */ 45 | Server.prototype.onBind = function (scope) { 46 | modules = true; 47 | }; 48 | 49 | /** 50 | * Sets private variable loaded to true. 51 | */ 52 | Server.prototype.onBlockchainReady = function () { 53 | __private.loaded = true; 54 | }; 55 | 56 | /** 57 | * Sets private variable loaded to false. 58 | * @param {function} cb 59 | * @return {setImmediateCallback} cb 60 | */ 61 | Server.prototype.cleanup = function (cb) { 62 | __private.loaded = false; 63 | return setImmediate(cb); 64 | }; 65 | 66 | /** 67 | * Returns private loaded value 68 | * @return {boolean} loaded 69 | */ 70 | Server.prototype.isLoaded = function () { 71 | return __private.loaded; 72 | }; 73 | 74 | /** 75 | * Returns true if modules are loaded. 76 | * @return {boolean} modules loaded 77 | */ 78 | Server.prototype.areModulesReady = function () { 79 | return !!modules; 80 | }; 81 | 82 | // Export 83 | module.exports = Server; 84 | -------------------------------------------------------------------------------- /pids/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Adamant-im/adamant/9cc651471d824471856d4cd4ad04e14c801b9654/pids/.gitkeep -------------------------------------------------------------------------------- /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 | new: { 17 | id: 'accounts.newAccount', 18 | type: 'object', 19 | properties: { 20 | publicKey: { 21 | type: 'string', 22 | format: 'publicKey' 23 | } 24 | }, 25 | required: ['publicKey'] 26 | }, 27 | getBalance: { 28 | id: 'accounts.getBalance', 29 | type: 'object', 30 | properties: { 31 | address: { 32 | type: 'string', 33 | format: 'address', 34 | minLength: 1, 35 | maxLength: 22 36 | } 37 | }, 38 | required: ['address'] 39 | }, 40 | getPublicKey: { 41 | id: 'accounts.getPublickey', 42 | type: 'object', 43 | properties: { 44 | address: { 45 | type: 'string', 46 | format: 'address', 47 | minLength: 1, 48 | maxLength: 22 49 | } 50 | }, 51 | required: ['address'] 52 | }, 53 | generatePublicKey: { 54 | id: 'accounts.generatePublickey', 55 | type: 'object', 56 | properties: { 57 | secret: { 58 | type: 'string', 59 | minLength: 1, 60 | maxLength: 100 61 | } 62 | }, 63 | required: ['secret'] 64 | }, 65 | getDelegates: { 66 | id: 'accounts.getDelegates', 67 | type: 'object', 68 | properties: { 69 | address: { 70 | type: 'string', 71 | format: 'address', 72 | minLength: 1, 73 | maxLength: 22 74 | } 75 | }, 76 | required: ['address'] 77 | }, 78 | addDelegates: { 79 | id: 'accounts.addDelegates', 80 | type: 'object', 81 | properties: { 82 | secret: { 83 | type: 'string', 84 | minLength: 1, 85 | maxLength: 100 86 | }, 87 | publicKey: { 88 | type: 'string', 89 | format: 'publicKey' 90 | }, 91 | secondSecret: { 92 | type: 'string', 93 | minLength: 1, 94 | maxLength: 100 95 | } 96 | } 97 | }, 98 | voteForDelegates: { 99 | id: 'accounts.voteForDelegates', 100 | type: 'object', 101 | properties: { 102 | senderPublicKey: { 103 | type: 'string', 104 | format: 'publicKey' 105 | } 106 | }, 107 | required: ['senderPublicKey'] 108 | }, 109 | getAccount: { 110 | id: 'accounts.getAccount', 111 | type: 'object', 112 | properties: { 113 | address: { 114 | type: 'string', 115 | format: 'address', 116 | minLength: 1, 117 | maxLength: 22 118 | }, 119 | publicKey: { 120 | type: 'string', 121 | format: 'publicKey' 122 | } 123 | } 124 | }, 125 | top: { 126 | id: 'accounts.top', 127 | type: 'object', 128 | properties: { 129 | limit: { 130 | type: 'integer', 131 | minimum: 0, 132 | maximum: 100 133 | }, 134 | offset: { 135 | type: 'integer', 136 | minimum: 0 137 | } 138 | } 139 | } 140 | }; 141 | -------------------------------------------------------------------------------- /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 | format: 'id', 17 | minLength: 1, 18 | maxLength: 20 19 | } 20 | }, 21 | required: ['id'] 22 | }, 23 | getBlocks: { 24 | id: 'blocks.getBlocks', 25 | type: 'object', 26 | properties: { 27 | limit: { 28 | type: 'integer', 29 | minimum: 1, 30 | maximum: 100 31 | }, 32 | orderBy: { 33 | type: 'string' 34 | }, 35 | offset: { 36 | type: 'integer', 37 | minimum: 0 38 | }, 39 | generatorPublicKey: { 40 | type: 'string', 41 | format: 'publicKey' 42 | }, 43 | totalAmount: { 44 | type: 'integer', 45 | minimum: 0, 46 | maximum: constants.totalAmount 47 | }, 48 | totalFee: { 49 | type: 'integer', 50 | minimum: 0, 51 | maximum: constants.totalAmount 52 | }, 53 | reward: { 54 | type: 'integer', 55 | minimum: 0 56 | }, 57 | previousBlock: { 58 | type: 'string', 59 | format: 'id', 60 | minLength: 1, 61 | maxLength: 20 62 | }, 63 | height: { 64 | type: 'integer', 65 | minimum: 1 66 | } 67 | } 68 | }, 69 | getCommonBlock: { 70 | id: 'blocks.getCommonBlock', 71 | type: 'object', 72 | properties: { 73 | id: { 74 | type: 'string', 75 | format: 'id', 76 | minLength: 1, 77 | maxLength: 20 78 | }, 79 | previousBlock: { 80 | type: 'string', 81 | format: 'id', 82 | minLength: 1, 83 | maxLength: 20 84 | }, 85 | height: { 86 | type: 'integer', 87 | minimum: 1 88 | } 89 | }, 90 | required: ['id', 'previousBlock', 'height'] 91 | } 92 | }; 93 | -------------------------------------------------------------------------------- /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 | maxItems: 100 12 | } 13 | }, 14 | required: ['signatures'] 15 | }, 16 | loadTransactions: { 17 | id: 'loader.loadTransactions', 18 | type: 'object', 19 | properties: { 20 | transactions: { 21 | type: 'array', 22 | uniqueItems: true, 23 | maxItems: 100 24 | } 25 | }, 26 | required: ['transactions'] 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /schema/multisignatures.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var constants = require('../helpers/constants.js'); 4 | 5 | module.exports = { 6 | getAccounts: { 7 | id: 'multisignatures.getAccounts', 8 | type: 'object', 9 | properties: { 10 | publicKey: { 11 | type: 'string', 12 | format: 'publicKey' 13 | } 14 | }, 15 | required: ['publicKey'] 16 | }, 17 | pending: { 18 | id: 'multisignatures.pending', 19 | type: 'object', 20 | properties: { 21 | publicKey: { 22 | type: 'string', 23 | format: 'publicKey' 24 | } 25 | }, 26 | required: ['publicKey'] 27 | }, 28 | sign: { 29 | id: 'multisignatures.sign', 30 | type: 'object', 31 | properties: { 32 | secret: { 33 | type: 'string', 34 | minLength: 1, 35 | maxLength: 100 36 | }, 37 | secondSecret: { 38 | type: 'string', 39 | minLength: 1, 40 | maxLength: 100 41 | }, 42 | publicKey: { 43 | type: 'string', 44 | format: 'publicKey' 45 | }, 46 | transactionId: { 47 | type: 'string', 48 | format: 'id', 49 | minLength: 1, 50 | maxLength: 20 51 | } 52 | }, 53 | required: ['transactionId', 'secret'] 54 | }, 55 | addMultisignature: { 56 | id: 'multisignatures.addMultisignature', 57 | type: 'object', 58 | properties: { 59 | secret: { 60 | type: 'string', 61 | minLength: 1, 62 | maxLength: 100 63 | }, 64 | publicKey: { 65 | type: 'string', 66 | format: 'publicKey' 67 | }, 68 | secondSecret: { 69 | type: 'string', 70 | minLength: 1, 71 | maxLength: 100 72 | }, 73 | min: { 74 | type: 'integer', 75 | minimum: constants.multisigConstraints.min.minimum, 76 | maximum: constants.multisigConstraints.min.maximum 77 | }, 78 | lifetime: { 79 | type: 'integer', 80 | minimum: constants.multisigConstraints.lifetime.minimum, 81 | maximum: constants.multisigConstraints.lifetime.maximum 82 | }, 83 | keysgroup: { 84 | type: 'array', 85 | minItems: constants.multisigConstraints.keysgroup.minItems, 86 | maxItems: constants.multisigConstraints.keysgroup.maxItems 87 | } 88 | }, 89 | required: ['min', 'lifetime', 'keysgroup', 'secret'] 90 | } 91 | }; 92 | -------------------------------------------------------------------------------- /schema/node.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | 5 | }; 6 | -------------------------------------------------------------------------------- /schema/peers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | discover: { 5 | peers: { 6 | id: 'peers.discover.peers', 7 | type: 'object', 8 | properties: { 9 | peers: { 10 | type: 'array' 11 | } 12 | }, 13 | required: ['peers'] 14 | }, 15 | peer: { 16 | id: 'peers.discover.peer', 17 | type: 'object', 18 | properties: { 19 | ip: { 20 | type: 'string', 21 | format: 'ip' 22 | }, 23 | port: { 24 | type: 'integer', 25 | minimum: 1, 26 | maximum: 65535 27 | }, 28 | state: { 29 | type: 'integer', 30 | minimum: 0, 31 | maximum: 2 32 | }, 33 | os: { 34 | type: 'string', 35 | format: 'os', 36 | minLength: 1, 37 | maxLength: 64 38 | }, 39 | version: { 40 | type: 'string', 41 | format: 'version', 42 | minLength: 5, 43 | maxLength: 12 44 | }, 45 | broadhash: { 46 | type: 'string', 47 | format: 'hex' 48 | }, 49 | height: { 50 | type: 'integer', 51 | minimum: 1 52 | } 53 | }, 54 | required: ['ip', 'port'] 55 | } 56 | }, 57 | getPeers: { 58 | id: 'peer.getPeers', 59 | type: 'object', 60 | properties: { 61 | ip: { 62 | type: 'string', 63 | format: 'ip' 64 | }, 65 | port: { 66 | type: 'integer', 67 | minimum: 1, 68 | maximum: 65535 69 | }, 70 | state: { 71 | type: 'integer', 72 | minimum: 0, 73 | maximum: 2 74 | }, 75 | os: { 76 | type: 'string', 77 | format: 'os', 78 | minLength: 1, 79 | maxLength: 64 80 | }, 81 | version: { 82 | type: 'string', 83 | format: 'version', 84 | minLength: 5, 85 | maxLength: 12 86 | }, 87 | broadhash: { 88 | type: 'string', 89 | format: 'hex' 90 | }, 91 | height: { 92 | type: 'integer', 93 | minimum: 1 94 | }, 95 | orderBy: { 96 | type: 'string' 97 | }, 98 | limit: { 99 | type: 'integer', 100 | minimum: 1, 101 | maximum: 100 102 | }, 103 | offset: { 104 | type: 'integer', 105 | minimum: 0 106 | } 107 | } 108 | }, 109 | getPeer: { 110 | id: 'peer.getPeer', 111 | type: 'object', 112 | properties: { 113 | ip: { 114 | type: 'string', 115 | format: 'ip' 116 | }, 117 | port: { 118 | type: 'integer', 119 | minimum: 0, 120 | maximum: 65535 121 | } 122 | }, 123 | required: ['ip', 'port'] 124 | } 125 | }; 126 | -------------------------------------------------------------------------------- /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 | maxLength: 100 12 | }, 13 | secondSecret: { 14 | type: 'string', 15 | minLength: 1, 16 | maxLength: 100 17 | }, 18 | publicKey: { 19 | type: 'string', 20 | format: 'publicKey' 21 | }, 22 | multisigAccountPublicKey: { 23 | type: 'string', 24 | format: 'publicKey' 25 | } 26 | }, 27 | required: ['secret', 'secondSecret'] 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /schema/states.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var constants = require('../helpers/constants.js'); 4 | 5 | module.exports = { 6 | getTransactions: { 7 | id: 'states.getTransactions', 8 | type: 'object', 9 | properties: { 10 | blockId: { 11 | type: 'string', 12 | format: 'id', 13 | minLength: 1, 14 | maxLength: 20 15 | }, 16 | type: { 17 | type: 'integer', 18 | minimum: 0, 19 | maximum: 10 20 | }, 21 | senderId: { 22 | type: 'string', 23 | format: 'address', 24 | minLength: 1, 25 | maxLength: 22 26 | }, 27 | senderPublicKey: { 28 | type: 'string', 29 | format: 'publicKey' 30 | }, 31 | ownerPublicKey: { 32 | type: 'string', 33 | format: 'publicKey' 34 | }, 35 | ownerAddress: { 36 | type: 'string', 37 | format: 'address', 38 | minLength: 1, 39 | maxLength: 22 40 | }, 41 | senderPublicKeys: { 42 | type: 'array', 43 | minItems: 1, 44 | 'items': { 45 | type: 'string', 46 | format: 'publicKey' 47 | } 48 | }, 49 | keysIds: { 50 | type: 'array', 51 | minItems: 1, 52 | 'items': { 53 | type: 'string', 54 | format: 'address', 55 | minLength: 1, 56 | maxLength: 22 57 | } 58 | }, 59 | senderIds: { 60 | type: 'array', 61 | minItems: 1, 62 | 'items': { 63 | type: 'string', 64 | format: 'address', 65 | minLength: 1, 66 | maxLength: 22 67 | } 68 | }, 69 | fromHeight: { 70 | type: 'integer', 71 | minimum: 1 72 | }, 73 | toHeight: { 74 | type: 'integer', 75 | minimum: 1 76 | }, 77 | fromTimestamp: { 78 | type: 'integer', 79 | minimum: 0 80 | }, 81 | toTimestamp: { 82 | type: 'integer', 83 | minimum: 1 84 | }, 85 | fromUnixTime: { 86 | type: 'integer', 87 | minimum: (constants.epochTime.getTime() / 1000) 88 | }, 89 | toUnixTime: { 90 | type: 'integer', 91 | minimum: (constants.epochTime.getTime() / 1000 + 1) 92 | }, 93 | minConfirmations: { 94 | type: 'integer', 95 | minimum: 0 96 | }, 97 | orderBy: { 98 | type: 'string' 99 | }, 100 | limit: { 101 | type: 'integer', 102 | minimum: 1, 103 | maximum: 1000 104 | }, 105 | offset: { 106 | type: 'integer', 107 | minimum: 0 108 | } 109 | } 110 | }, 111 | get: { 112 | id: 'states.get', 113 | type: 'object', 114 | properties: { 115 | id: { 116 | type: 'string', 117 | format: 'id', 118 | minLength: 1, 119 | maxLength: 20 120 | } 121 | }, 122 | required: ['id'] 123 | }, 124 | normalize: { 125 | id: 'states.normalize', 126 | type: 'object', 127 | properties: { 128 | value: { 129 | type: 'string', 130 | minLength: 1 131 | }, 132 | key: { 133 | type: 'string', 134 | minLength: 0 135 | }, 136 | recipientId: { 137 | type: 'string', 138 | format: 'address', 139 | minLength: 1, 140 | maxLength: 40 141 | }, 142 | publicKey: { 143 | type: 'string', 144 | format: 'publicKey' 145 | } 146 | }, 147 | required: ['value', 'publicKey'] 148 | }, 149 | store: { 150 | id: 'states.store', 151 | type: 'object', 152 | properties: { 153 | signature: { 154 | type: 'string', 155 | format: 'signature' 156 | } 157 | }, 158 | required: ['signature'] 159 | } 160 | }; 161 | -------------------------------------------------------------------------------- /schema/transport.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var constants = require('../helpers/constants.js'); 4 | 5 | module.exports = { 6 | headers: { 7 | id: 'transport.headers', 8 | type: 'object', 9 | properties: { 10 | port: { 11 | type: 'integer', 12 | minimum: 1, 13 | maximum: 65535 14 | }, 15 | os: { 16 | type: 'string', 17 | format: 'os', 18 | minLength: 1, 19 | maxLength: 64 20 | }, 21 | version: { 22 | type: 'string', 23 | format: 'version', 24 | minLength: 5, 25 | maxLength: 12 26 | }, 27 | nethash: { 28 | type: 'string', 29 | maxLength: 64 30 | }, 31 | broadhash: { 32 | type: 'string', 33 | format: 'hex' 34 | }, 35 | height: { 36 | type: 'integer', 37 | minimum: 1 38 | }, 39 | nonce: { 40 | type: 'string', 41 | minimum: 16, 42 | max: 16 43 | } 44 | }, 45 | required: ['port', 'version', 'nethash'] 46 | }, 47 | commonBlock: { 48 | id: 'transport.commonBlock', 49 | type: 'object', 50 | properties: { 51 | ids: { 52 | type: 'string', 53 | format: 'csv' 54 | } 55 | }, 56 | required: ['ids'] 57 | }, 58 | blocks: { 59 | id: 'transport.blocks', 60 | type: 'object', 61 | properties: { 62 | lastBlockId: { 63 | type: 'string', 64 | format: 'id', 65 | minLength: 1, 66 | maxLength: 20 67 | } 68 | } 69 | }, 70 | transactions: { 71 | id: 'transport.transactions', 72 | type: 'object', 73 | properties: { 74 | transactions: { 75 | type: 'array', 76 | minItems: 1, 77 | maxItems: 1000 78 | } 79 | }, 80 | required: ['transactions'] 81 | }, 82 | signatures: { 83 | id: 'transport.signatures', 84 | type: 'object', 85 | properties: { 86 | signatures: { 87 | type: 'array', 88 | minItems: 1, 89 | maxItems: 25 90 | } 91 | }, 92 | required: ['signatures'] 93 | }, 94 | signature: { 95 | id: 'transport.signature', 96 | type: 'object', 97 | properties: { 98 | signature: { 99 | type: 'object', 100 | properties: { 101 | transaction: { 102 | type: 'string', 103 | format: 'id', 104 | minLength: 1, 105 | maxLength: 20 106 | }, 107 | signature: { 108 | type: 'string', 109 | format: 'signature' 110 | } 111 | }, 112 | required: ['transaction', 'signature'] 113 | } 114 | }, 115 | required: ['signature'] 116 | } 117 | }; 118 | -------------------------------------------------------------------------------- /sql/dapps.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var DappsSql = { 4 | sortFields: ['type', 'name', 'category', 'link'], 5 | 6 | countByTransactionId: 'SELECT COUNT(*)::int AS "count" FROM dapps WHERE "transactionId" = ${id}', 7 | 8 | countByOutTransactionId: 'SELECT COUNT(*)::int AS "count" FROM outtransfer WHERE "outTransactionId" = ${transactionId}', 9 | 10 | getExisting: 'SELECT "name", "link" FROM dapps WHERE ("name" = ${name} OR "link" = ${link}) AND "transactionId" != ${transactionId}', 11 | 12 | search: function (params) { 13 | return [ 14 | 'SELECT "transactionId", "name", "description", "tags", "link", "type", "category", "icon"', 15 | 'FROM dapps WHERE to_tsvector("name" || \' \' || "description" || \' \' || "tags") @@ to_tsquery(${q})', 16 | (params.category ? 'AND "category" = ${category}' : ''), 17 | 'LIMIT ${limit}' 18 | ].filter(Boolean).join(' '); 19 | }, 20 | 21 | get: 'SELECT "name", "description", "tags", "link", "type", "category", "icon", "transactionId" FROM dapps WHERE "transactionId" = ${id}', 22 | 23 | getByIds: 'SELECT "name", "description", "tags", "link", "type", "category", "icon", "transactionId" FROM dapps WHERE "transactionId" IN ($1:csv)', 24 | 25 | // Need to fix "or" or "and" in query 26 | list: function (params) { 27 | return [ 28 | 'SELECT "name", "description", "tags", "link", "type", "category", "icon", "transactionId" FROM dapps', 29 | (params.where.length ? 'WHERE ' + params.where.join(' OR ') : ''), 30 | (params.sortField ? 'ORDER BY ' + [params.sortField, params.sortMethod].join(' ') : ''), 31 | 'LIMIT ${limit} OFFSET ${offset}' 32 | ].filter(Boolean).join(' '); 33 | }, 34 | 35 | getGenesis: 'SELECT b."height" AS "height", b."id" AS "id", t."senderId" AS "authorId" FROM trs t INNER JOIN blocks b ON t."blockId" = b."id" WHERE t."id" = ${id}', 36 | 37 | getCommonBlock: 'SELECT b."height" AS "height", t."id" AS "id", t."senderId" AS "senderId", t."amount" AS "amount" FROM trs t INNER JOIN blocks b ON t."blockId" = b."id" AND t."id" = ${id} AND t."type" = ${type} INNER JOIN intransfer dt ON dt."transactionId" = t."id" AND dt."dappid" = ${dappid}', 38 | 39 | getWithdrawalLastTransaction: 'SELECT ot."outTransactionId" FROM trs t INNER JOIN blocks b ON t."blockId" = b."id" AND t."type" = ${type} INNER JOIN outtransfer ot ON ot."transactionId" = t."id" AND ot."dappId" = ${dappid} ORDER BY b."height" DESC LIMIT 1', 40 | 41 | getBalanceTransactions: function (params) { 42 | return [ 43 | 'SELECT t."id" AS "id", ENCODE(t."senderPublicKey", \'hex\') AS "senderPublicKey", t."amount" AS "amount" FROM trs t', 44 | 'INNER JOIN blocks b ON t."blockId" = b."id" AND t."type" = ${type}', 45 | 'INNER JOIN intransfer dt ON dt."transactionId" = t."id" AND dt."dappId" = ${dappid}', 46 | (params.lastId ? 'WHERE b."height" > (SELECT "height" FROM blocks ib INNER JOIN trs it ON ib."id" = it."blockId" AND it."id" = ${lastId})' : ''), 47 | 'ORDER BY b."height"' 48 | ].filter(Boolean).join(' '); 49 | } 50 | }; 51 | 52 | module.exports = DappsSql; 53 | -------------------------------------------------------------------------------- /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 | 'approval', 14 | 'productivity', 15 | 'voters_cnt', 16 | 'register_timestamp' 17 | ], 18 | 19 | count: 'SELECT COUNT(*)::int FROM delegates', 20 | 21 | search: function (params) { 22 | var sql = [ 23 | 'WITH', 24 | 'supply AS (SELECT calcSupply((SELECT height FROM blocks ORDER BY height DESC LIMIT 1))::numeric),', 25 | 'delegates AS (SELECT row_number() OVER (ORDER BY m."votesWeight" DESC, m."publicKey" ASC)::int AS rank,', 26 | 'm.username,', 27 | 'm.address,', 28 | 'ENCODE(m."publicKey", \'hex\') AS "publicKey",', 29 | 'm.vote,', 30 | 'm."votesWeight",', 31 | 'm.producedblocks,', 32 | 'm.missedblocks,', 33 | 'ROUND(vote / (SELECT * FROM supply) * 100, 2)::float AS approval,', 34 | '(CASE WHEN producedblocks + missedblocks = 0 THEN 0.00 ELSE', 35 | 'ROUND(100 - (missedblocks::numeric / (producedblocks + missedblocks) * 100), 2)', 36 | 'END)::float AS productivity,', 37 | 'COALESCE(v.voters_cnt, 0) AS voters_cnt,', 38 | 't.timestamp AS register_timestamp', 39 | 'FROM delegates d', 40 | 'LEFT JOIN mem_accounts m ON d.username = m.username', 41 | 'LEFT JOIN trs t ON d."transactionId" = t.id', 42 | 'LEFT JOIN (SELECT "dependentId", COUNT(1)::int AS voters_cnt from mem_accounts2delegates GROUP BY "dependentId") v ON v."dependentId" = ENCODE(m."publicKey", \'hex\')', 43 | 'WHERE m."isDelegate" = 1', 44 | 'ORDER BY ' + [params.sortField, params.sortMethod].join(' ') + ')', 45 | 'SELECT * FROM delegates WHERE username LIKE ${q} LIMIT ${limit}' 46 | ].join(' '); 47 | 48 | params.q = '%' + String(params.q).toLowerCase() + '%'; 49 | return pgp.as.format(sql, params); 50 | }, 51 | 52 | insertFork: 'INSERT INTO forks_stat ("delegatePublicKey", "blockTimestamp", "blockId", "blockHeight", "previousBlock", "cause") VALUES (${delegatePublicKey}, ${blockTimestamp}, ${blockId}, ${blockHeight}, ${previousBlock}, ${cause});', 53 | 54 | getVoters: 'SELECT ARRAY_AGG("accountId") AS "accountIds" FROM mem_accounts2delegates WHERE "dependentId" = ${publicKey}' 55 | }; 56 | 57 | module.exports = DelegatesSql; 58 | -------------------------------------------------------------------------------- /sql/loader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var LoaderSql = { 4 | countBlocks: 'SELECT COUNT("rowId")::int FROM blocks', 5 | 6 | getGenesisBlock: 'SELECT "id", "payloadHash", "blockSignature" FROM blocks WHERE "height" = 1', 7 | 8 | countMemAccounts: 'SELECT COUNT(*)::int FROM mem_accounts WHERE "blockId" = (SELECT "id" FROM "blocks" ORDER BY "height" DESC LIMIT 1)', 9 | 10 | getMemRounds: 'SELECT "round" FROM mem_round GROUP BY "round"', 11 | 12 | updateMemAccounts: '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" WHERE "u_isDelegate" <> "isDelegate" OR "u_secondSignature" <> "secondSignature" OR "u_username" <> "username" OR "u_balance" <> "balance" OR "u_delegates" <> "delegates" OR "u_multisignatures" <> "multisignatures" OR "u_multimin" <> "multimin" OR "u_multilifetime" <> "multilifetime";', 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 | countDuplicatedDelegates: 'WITH duplicates AS (SELECT COUNT(1) FROM delegates GROUP BY "transactionId" HAVING COUNT(1) > 1) SELECT count(1) FROM duplicates' 19 | }; 20 | 21 | module.exports = LoaderSql; 22 | -------------------------------------------------------------------------------- /sql/memoryTables.sql: -------------------------------------------------------------------------------- 1 | /* ADAMANT 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(22) 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" BIGINT DEFAULT 0, 26 | "u_multimin" BIGINT DEFAULT 0, 27 | "multilifetime" BIGINT DEFAULT 0, 28 | "u_multilifetime" BIGINT DEFAULT 0, 29 | "blockId" VARCHAR(20), 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_round"( 42 | "address" VARCHAR(22), 43 | "amount" BIGINT, 44 | "delegate" VARCHAR(64), 45 | "blockId" VARCHAR(20), 46 | "round" BIGINT 47 | ); 48 | 49 | CREATE INDEX IF NOT EXISTS "mem_round_address" ON "mem_round"("address"); 50 | CREATE INDEX IF NOT EXISTS "mem_round_round" ON "mem_round"("round"); 51 | 52 | CREATE TABLE IF NOT EXISTS "mem_accounts2delegates"( 53 | "accountId" VARCHAR(22) NOT NULL, 54 | "dependentId" VARCHAR(64) NOT NULL, 55 | FOREIGN KEY ("accountId") REFERENCES mem_accounts("address") ON DELETE CASCADE 56 | ); 57 | 58 | CREATE INDEX IF NOT EXISTS "mem_accounts2delegates_accountId" ON "mem_accounts2delegates"("accountId"); 59 | 60 | CREATE TABLE IF NOT EXISTS "mem_accounts2u_delegates"( 61 | "accountId" VARCHAR(22) NOT NULL, 62 | "dependentId" VARCHAR(64) NOT NULL, 63 | FOREIGN KEY ("accountId") REFERENCES mem_accounts("address") ON DELETE CASCADE 64 | ); 65 | 66 | CREATE INDEX IF NOT EXISTS "mem_accounts2u_delegates_accountId" ON "mem_accounts2u_delegates"("accountId"); 67 | 68 | CREATE TABLE IF NOT EXISTS "mem_accounts2multisignatures"( 69 | "accountId" VARCHAR(22) NOT NULL, 70 | "dependentId" VARCHAR(64) NOT NULL, 71 | FOREIGN KEY ("accountId") REFERENCES mem_accounts("address") ON DELETE CASCADE 72 | ); 73 | 74 | CREATE INDEX IF NOT EXISTS "mem_accounts2multisignatures_accountId" ON "mem_accounts2multisignatures"("accountId"); 75 | 76 | CREATE TABLE IF NOT EXISTS "mem_accounts2u_multisignatures"( 77 | "accountId" VARCHAR(22) NOT NULL, 78 | "dependentId" VARCHAR(64) NOT NULL, 79 | FOREIGN KEY ("accountId") REFERENCES mem_accounts("address") ON DELETE CASCADE 80 | ); 81 | 82 | CREATE INDEX IF NOT EXISTS "mem_accounts2u_multisignatures_accountId" ON "mem_accounts2u_multisignatures"("accountId"); 83 | 84 | DELETE FROM "mem_accounts2u_delegates"; 85 | DELETE FROM "mem_accounts2u_multisignatures"; 86 | 87 | INSERT INTO "mem_accounts2u_delegates" SELECT * FROM "mem_accounts2delegates"; 88 | INSERT INTO "mem_accounts2u_multisignatures" SELECT * FROM "mem_accounts2multisignatures"; 89 | 90 | COMMIT; 91 | -------------------------------------------------------------------------------- /sql/migrations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Adamant-im/adamant/9cc651471d824471856d4cd4ad04e14c801b9654/sql/migrations/.gitkeep -------------------------------------------------------------------------------- /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(22) 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" BIGINT DEFAULT 0, 26 | "u_multimin" BIGINT DEFAULT 0, 27 | "multilifetime" BIGINT DEFAULT 0, 28 | "u_multilifetime" BIGINT DEFAULT 0, 29 | "blockId" VARCHAR(20), 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 | ); 37 | 38 | CREATE INDEX IF NOT EXISTS "mem_accounts_balance" ON "mem_accounts"("balance"); 39 | 40 | CREATE TABLE IF NOT EXISTS "mem_round"( 41 | "address" VARCHAR(22), 42 | "amount" BIGINT, 43 | "delegate" VARCHAR(64), 44 | "blockId" VARCHAR(20), 45 | "round" BIGINT 46 | ); 47 | 48 | CREATE TABLE IF NOT EXISTS "mem_accounts2delegates"( 49 | "accountId" VARCHAR(22) NOT NULL, 50 | "dependentId" VARCHAR(64) NOT NULL, 51 | FOREIGN KEY ("accountId") REFERENCES mem_accounts("address") ON DELETE CASCADE 52 | ); 53 | 54 | CREATE TABLE IF NOT EXISTS "mem_accounts2u_delegates"( 55 | "accountId" VARCHAR(22) NOT NULL, 56 | "dependentId" VARCHAR(64) NOT NULL, 57 | FOREIGN KEY ("accountId") REFERENCES mem_accounts("address") ON DELETE CASCADE 58 | ); 59 | 60 | CREATE TABLE IF NOT EXISTS "mem_accounts2multisignatures"( 61 | "accountId" VARCHAR(22) NOT NULL, 62 | "dependentId" VARCHAR(64) NOT NULL, 63 | FOREIGN KEY ("accountId") REFERENCES mem_accounts("address") ON DELETE CASCADE 64 | ); 65 | 66 | CREATE TABLE IF NOT EXISTS "mem_accounts2u_multisignatures"( 67 | "accountId" VARCHAR(22) NOT NULL, 68 | "dependentId" VARCHAR(64) NOT NULL, 69 | FOREIGN KEY ("accountId") REFERENCES mem_accounts("address") ON DELETE CASCADE 70 | ); 71 | 72 | COMMIT; 73 | -------------------------------------------------------------------------------- /sql/migrations/20160724132825_upcaseMemoryTableAddresses.sql: -------------------------------------------------------------------------------- 1 | /* Upcase Memory Table Addresses 2 | * 3 | */ 4 | 5 | BEGIN; 6 | 7 | UPDATE "mem_accounts" AS m 8 | SET "balance" = (m."balance" + l."balance"), 9 | "u_balance" = (m."u_balance" + l."u_balance") 10 | FROM ( 11 | SELECT "address", "balance", "u_balance" 12 | FROM "mem_accounts" 13 | WHERE "address" LIKE '%l' 14 | ) AS "l" 15 | WHERE m."address" = UPPER(l."address"); 16 | 17 | DELETE FROM "mem_accounts" WHERE "address" LIKE '%l' AND "publicKey" IS NULL; 18 | 19 | UPDATE "mem_accounts" SET "address" = UPPER("address") WHERE "address" LIKE '%l'; 20 | 21 | UPDATE "mem_round" SET "address" = UPPER("address") WHERE "address" LIKE '%l'; 22 | 23 | UPDATE "mem_accounts2delegates" SET "accountId" = UPPER("accountId") WHERE "accountId" LIKE '%l'; 24 | 25 | UPDATE "mem_accounts2u_delegates" SET "accountId" = UPPER("accountId") WHERE "accountId" LIKE '%l'; 26 | 27 | UPDATE "mem_accounts2multisignatures" SET "accountId" = UPPER("accountId") WHERE "accountId" LIKE '%l'; 28 | 29 | UPDATE "mem_accounts2u_multisignatures" SET "accountId" = UPPER("accountId") WHERE "accountId" LIKE '%l'; 30 | 31 | COMMIT; 32 | -------------------------------------------------------------------------------- /sql/migrations/20160725173858_alterMemAccountsColumns.sql: -------------------------------------------------------------------------------- 1 | /* Alter Mem Accounts Columns 2 | * 3 | */ 4 | 5 | BEGIN; 6 | 7 | ALTER TABLE "mem_accounts" ALTER COLUMN "multimin" TYPE SMALLINT; 8 | 9 | ALTER TABLE "mem_accounts" ALTER COLUMN "u_multimin" TYPE SMALLINT; 10 | 11 | ALTER TABLE "mem_accounts" ALTER COLUMN "multilifetime" TYPE SMALLINT; 12 | 13 | ALTER TABLE "mem_accounts" ALTER COLUMN "u_multilifetime" TYPE SMALLINT; 14 | 15 | COMMIT; 16 | -------------------------------------------------------------------------------- /sql/migrations/20160908120022_addVirginColumnToMemAccounts.sql: -------------------------------------------------------------------------------- 1 | /* Add Virgin Column to Mem Accounts 2 | * 3 | */ 4 | 5 | BEGIN; 6 | 7 | ALTER TABLE "mem_accounts" ADD COLUMN "virgin" SMALLINT DEFAULT 1; 8 | 9 | -- Delete accounts which have never received or sent funds 10 | -- e.g. Created using /api/accounts/open 11 | DELETE FROM "mem_accounts" 12 | WHERE "publicKey" IS NULL 13 | AND "balance" = 0 AND "u_balance" = 0; 14 | 15 | -- Reflect on virginity of existing accounts 16 | UPDATE "mem_accounts" AS m SET "virgin" = 0 17 | FROM (SELECT "senderId" FROM "trs" GROUP BY "senderId") AS t 18 | WHERE m."publicKey" IS NOT NULL 19 | AND t."senderId" = m."address"; 20 | 21 | COMMIT; 22 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /sql/migrations/20161007153817_createMemoryTableIndexes.sql: -------------------------------------------------------------------------------- 1 | /* Create Memory Table Indexes 2 | * 3 | */ 4 | 5 | BEGIN; 6 | 7 | CREATE INDEX IF NOT EXISTS "mem_round_address" ON "mem_round"("address"); 8 | 9 | CREATE INDEX IF NOT EXISTS "mem_round_round" ON "mem_round"("round"); 10 | 11 | CREATE INDEX IF NOT EXISTS "mem_accounts2delegates_accountId" ON "mem_accounts2delegates"("accountId"); 12 | 13 | CREATE INDEX IF NOT EXISTS "mem_accounts2u_delegates_accountId" ON "mem_accounts2u_delegates"("accountId"); 14 | 15 | CREATE INDEX IF NOT EXISTS "mem_accounts2multisignatures_accountId" ON "mem_accounts2multisignatures"("accountId"); 16 | 17 | CREATE INDEX IF NOT EXISTS "mem_accounts2u_multisignatures_accountId" ON "mem_accounts2u_multisignatures"("accountId"); 18 | 19 | COMMIT; 20 | -------------------------------------------------------------------------------- /sql/migrations/20161016133824_addBroadhashColumnToPeers.sql: -------------------------------------------------------------------------------- 1 | /* Add Broadhash Column to Peers 2 | * 3 | */ 4 | 5 | BEGIN; 6 | 7 | ALTER TABLE "peers" ADD COLUMN "broadhash" bytea; 8 | 9 | CREATE INDEX IF NOT EXISTS "peers_broadhash" ON "peers"("broadhash"); 10 | 11 | COMMIT; 12 | -------------------------------------------------------------------------------- /sql/migrations/20161016133824_addHeightColumnToPeers.sql: -------------------------------------------------------------------------------- 1 | /* Add Height Column to Peers 2 | * 3 | */ 4 | 5 | BEGIN; 6 | 7 | ALTER TABLE "peers" ADD COLUMN "height" INT; 8 | 9 | CREATE INDEX IF NOT EXISTS "peers_height" ON "peers"("height"); 10 | 11 | COMMIT; 12 | -------------------------------------------------------------------------------- /sql/migrations/20170113181857_addConstraintsToPeers.sql: -------------------------------------------------------------------------------- 1 | /* Add constraints to improve upserts 2 | * 3 | */ 4 | 5 | BEGIN; 6 | 7 | ALTER TABLE "peers" 8 | ADD CONSTRAINT "address_unique" UNIQUE 9 | USING INDEX "peers_unique"; 10 | 11 | COMMIT; -------------------------------------------------------------------------------- /sql/migrations/20170124071600_recreateTrsListView.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Add 'm_recipientPublicKey' column to 'trs_list' view 3 | * Change 't_senderPublicKey' data type from 'string' to 'bytea' 4 | */ 5 | 6 | BEGIN; 7 | 8 | DROP VIEW IF EXISTS trs_list; 9 | 10 | CREATE VIEW trs_list AS 11 | 12 | SELECT t."id" AS "t_id", 13 | b."height" AS "b_height", 14 | t."blockId" AS "t_blockId", 15 | t."type" AS "t_type", 16 | t."timestamp" AS "t_timestamp", 17 | t."senderPublicKey" AS "t_senderPublicKey", 18 | m."publicKey" AS "m_recipientPublicKey", 19 | t."senderId" AS "t_senderId", 20 | t."recipientId" AS "t_recipientId", 21 | t."amount" AS "t_amount", 22 | t."fee" AS "t_fee", 23 | ENCODE(t."signature", 'hex') AS "t_signature", 24 | ENCODE(t."signSignature", 'hex') AS "t_SignSignature", 25 | t."signatures" AS "t_signatures", 26 | (SELECT MAX("height") + 1 FROM blocks) - b."height" AS "confirmations" 27 | 28 | FROM trs t 29 | 30 | INNER JOIN blocks b ON t."blockId" = b."id" 31 | LEFT JOIN mem_accounts m ON t."recipientId" = m."address"; 32 | 33 | COMMIT; 34 | -------------------------------------------------------------------------------- /sql/migrations/20170319001337_createIndexes.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Add various indexes for performance 3 | */ 4 | 5 | BEGIN; 6 | 7 | -- Add 'mem_accounts_address' index for 'address' 8 | CREATE INDEX IF NOT EXISTS "mem_accounts_address" ON "mem_accounts" ("address"); 9 | 10 | -- Add 'mem_accounts_address_upper' index for upper case 'address' 11 | CREATE INDEX IF NOT EXISTS "mem_accounts_address_upper" ON "mem_accounts" (UPPER("address")); 12 | 13 | -- Add 'mem_accounts_is_delegate' index for 'isDelegate' 14 | CREATE INDEX IF NOT EXISTS "mem_accounts_is_delegate" ON "mem_accounts" ("isDelegate"); 15 | 16 | -- Add 'mem_accounts_get_delegates' index for retrieving list of 101 delegates 17 | CREATE INDEX IF NOT EXISTS "mem_accounts_get_delegates" ON "mem_accounts" ("vote" DESC, ENCODE("publicKey", 'hex') ASC) WHERE "isDelegate" = 1; 18 | 19 | -- Add 'mem_accounts_block_id' index for 'blockId' 20 | CREATE INDEX IF NOT EXISTS "mem_accounts_block_id" ON "mem_accounts" ("blockId"); 21 | 22 | -- Add 'blocks_rounds' index for dealing with rounds 23 | CREATE INDEX IF NOT EXISTS "blocks_rounds" ON "blocks" ((CEIL(height / 101::float)::int)); 24 | 25 | COMMIT; 26 | -------------------------------------------------------------------------------- /sql/migrations/20170328001337_recreateTrsListView.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Recreate 'trs_list' view for performance 3 | */ 4 | 5 | BEGIN; 6 | 7 | DROP VIEW IF EXISTS trs_list; 8 | 9 | CREATE VIEW trs_list AS 10 | 11 | SELECT t."id" AS "t_id", 12 | b."height" AS "b_height", 13 | t."blockId" AS "t_blockId", 14 | t."type" AS "t_type", 15 | t."timestamp" AS "t_timestamp", 16 | t."senderPublicKey" AS "t_senderPublicKey", 17 | m."publicKey" AS "m_recipientPublicKey", 18 | t."senderId" AS "t_senderId", 19 | t."recipientId" AS "t_recipientId", 20 | t."amount" AS "t_amount", 21 | t."fee" AS "t_fee", 22 | ENCODE(t."signature", 'hex') AS "t_signature", 23 | ENCODE(t."signSignature", 'hex') AS "t_SignSignature", 24 | t."signatures" AS "t_signatures", 25 | (SELECT height + 1 FROM blocks ORDER BY height DESC LIMIT 1) - b."height" AS "confirmations" 26 | 27 | FROM trs t 28 | 29 | LEFT JOIN blocks b ON t."blockId" = b."id" 30 | LEFT JOIN mem_accounts m ON t."recipientId" = m."address"; 31 | 32 | COMMIT; 33 | -------------------------------------------------------------------------------- /sql/migrations/20170408001337_createIndex.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Add index for performance 3 | */ 4 | 5 | BEGIN; 6 | 7 | -- Add 'mem_accounts2delegates_depId' index for fast delegates voters counting 8 | CREATE INDEX IF NOT EXISTS "mem_accounts2delegates_depId" ON mem_accounts2delegates("dependentId"); 9 | 10 | COMMIT; 11 | -------------------------------------------------------------------------------- /sql/migrations/20170428001337_recreateTrsLiskView.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Recreate 'trs_list' view, normalize addresses, add indexes 3 | */ 4 | 5 | BEGIN; 6 | 7 | DROP VIEW IF EXISTS trs_list; 8 | 9 | CREATE VIEW trs_list AS 10 | 11 | SELECT t."id" AS "t_id", 12 | b."height" AS "b_height", 13 | t."blockId" AS "t_blockId", 14 | t."type" AS "t_type", 15 | t."timestamp" AS "t_timestamp", 16 | t."senderPublicKey" AS "t_senderPublicKey", 17 | m."publicKey" AS "m_recipientPublicKey", 18 | UPPER(t."senderId") AS "t_senderId", 19 | UPPER(t."recipientId") AS "t_recipientId", 20 | t."amount" AS "t_amount", 21 | t."fee" AS "t_fee", 22 | ENCODE(t."signature", 'hex') AS "t_signature", 23 | ENCODE(t."signSignature", 'hex') AS "t_SignSignature", 24 | t."signatures" AS "t_signatures", 25 | (SELECT height + 1 FROM blocks ORDER BY height DESC LIMIT 1) - b."height" AS "confirmations" 26 | 27 | FROM trs t 28 | 29 | LEFT JOIN blocks b ON t."blockId" = b."id" 30 | LEFT JOIN mem_accounts m ON t."recipientId" = m."address"; 31 | 32 | CREATE INDEX IF NOT EXISTS "trs_upper_sender_id" ON "trs"(UPPER("senderId")); 33 | CREATE INDEX IF NOT EXISTS "trs_upper_recipient_id" ON "trs"(UPPER("recipientId")); 34 | 35 | COMMIT; 36 | -------------------------------------------------------------------------------- /sql/migrations/20170614155841_uniqueDelegatesConstraint.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Setting unique constraints on delegates table 3 | */ 4 | 5 | BEGIN; 6 | 7 | ALTER TABLE delegates ADD CONSTRAINT delegates_unique UNIQUE ("username", "transactionId"); 8 | 9 | COMMIT; 10 | -------------------------------------------------------------------------------- /sql/migrations/20171013022500_createChats.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS "chats"( 2 | "message" TEXT, 3 | "own_message" TEXT, 4 | "type" INT NOT NULL, 5 | "transactionId" VARCHAR(20) NOT NULL, 6 | FOREIGN KEY("transactionId") REFERENCES "trs"("id") ON DELETE CASCADE 7 | ); 8 | 9 | CREATE INDEX IF NOT EXISTS "chats_trs_id" ON "chats"("transactionId"); -------------------------------------------------------------------------------- /sql/migrations/20171013024300_recreateFullBlocksListView.sql: -------------------------------------------------------------------------------- 1 | DROP VIEW IF EXISTS full_blocks_list; 2 | 3 | CREATE VIEW full_blocks_list AS 4 | 5 | SELECT b."id" AS "b_id", 6 | b."version" AS "b_version", 7 | b."timestamp" AS "b_timestamp", 8 | b."height" AS "b_height", 9 | b."previousBlock" AS "b_previousBlock", 10 | b."numberOfTransactions" AS "b_numberOfTransactions", 11 | (b."totalAmount")::bigint AS "b_totalAmount", 12 | (b."totalFee")::bigint AS "b_totalFee", 13 | (b."reward")::bigint AS "b_reward", 14 | b."payloadLength" AS "b_payloadLength", 15 | ENCODE(b."payloadHash", 'hex') AS "b_payloadHash", 16 | ENCODE(b."generatorPublicKey", 'hex') AS "b_generatorPublicKey", 17 | ENCODE(b."blockSignature", 'hex') AS "b_blockSignature", 18 | t."id" AS "t_id", 19 | t."rowId" AS "t_rowId", 20 | t."type" AS "t_type", 21 | t."timestamp" AS "t_timestamp", 22 | ENCODE(t."senderPublicKey", 'hex') AS "t_senderPublicKey", 23 | t."senderId" AS "t_senderId", 24 | t."recipientId" AS "t_recipientId", 25 | (t."amount")::bigint AS "t_amount", 26 | (t."fee")::bigint AS "t_fee", 27 | ENCODE(t."signature", 'hex') AS "t_signature", 28 | ENCODE(t."signSignature", 'hex') AS "t_signSignature", 29 | ENCODE(s."publicKey", 'hex') AS "s_publicKey", 30 | d."username" AS "d_username", 31 | v."votes" AS "v_votes", 32 | m."min" AS "m_min", 33 | m."lifetime" AS "m_lifetime", 34 | m."keysgroup" AS "m_keysgroup", 35 | dapp."name" AS "dapp_name", 36 | dapp."description" AS "dapp_description", 37 | dapp."tags" AS "dapp_tags", 38 | dapp."type" AS "dapp_type", 39 | dapp."link" AS "dapp_link", 40 | dapp."category" AS "dapp_category", 41 | dapp."icon" AS "dapp_icon", 42 | it."dappId" AS "in_dappId", 43 | ot."dappId" AS "ot_dappId", 44 | ot."outTransactionId" AS "ot_outTransactionId", 45 | ENCODE(t."requesterPublicKey", 'hex') AS "t_requesterPublicKey", 46 | t."signatures" AS "t_signatures", 47 | c."message" AS "c_message", 48 | c."own_message" AS "c_own_message", 49 | c."type" AS "c_type" 50 | 51 | FROM blocks b 52 | 53 | LEFT OUTER JOIN trs AS t ON t."blockId" = b."id" 54 | LEFT OUTER JOIN delegates AS d ON d."transactionId" = t."id" 55 | LEFT OUTER JOIN votes AS v ON v."transactionId" = t."id" 56 | LEFT OUTER JOIN signatures AS s ON s."transactionId" = t."id" 57 | LEFT OUTER JOIN multisignatures AS m ON m."transactionId" = t."id" 58 | LEFT OUTER JOIN dapps AS dapp ON dapp."transactionId" = t."id" 59 | LEFT OUTER JOIN intransfer AS it ON it."transactionId" = t."id" 60 | LEFT OUTER JOIN outtransfer AS ot ON ot."transactionId" = t."id" 61 | LEFT OUTER JOIN chats AS c ON c."transactionId" = t."id"; 62 | 63 | -------------------------------------------------------------------------------- /sql/migrations/20180330203200_createStates.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS "states"( 2 | "stored_value" TEXT, 3 | "stored_key" VARCHAR(20), 4 | "type" INT NOT NULL, 5 | "transactionId" VARCHAR(20) NOT NULL, 6 | FOREIGN KEY("transactionId") REFERENCES "trs"("id") ON DELETE CASCADE 7 | ); 8 | 9 | CREATE INDEX IF NOT EXISTS "states_trs_id" ON "states"("transactionId"); -------------------------------------------------------------------------------- /sql/migrations/20180330203400_recreateFullBlocksListView.sql: -------------------------------------------------------------------------------- 1 | DROP VIEW IF EXISTS full_blocks_list; 2 | 3 | CREATE VIEW full_blocks_list AS 4 | 5 | SELECT b."id" AS "b_id", 6 | b."version" AS "b_version", 7 | b."timestamp" AS "b_timestamp", 8 | b."height" AS "b_height", 9 | b."previousBlock" AS "b_previousBlock", 10 | b."numberOfTransactions" AS "b_numberOfTransactions", 11 | (b."totalAmount")::bigint AS "b_totalAmount", 12 | (b."totalFee")::bigint AS "b_totalFee", 13 | (b."reward")::bigint AS "b_reward", 14 | b."payloadLength" AS "b_payloadLength", 15 | ENCODE(b."payloadHash", 'hex') AS "b_payloadHash", 16 | ENCODE(b."generatorPublicKey", 'hex') AS "b_generatorPublicKey", 17 | ENCODE(b."blockSignature", 'hex') AS "b_blockSignature", 18 | t."id" AS "t_id", 19 | t."rowId" AS "t_rowId", 20 | t."type" AS "t_type", 21 | t."timestamp" AS "t_timestamp", 22 | ENCODE(t."senderPublicKey", 'hex') AS "t_senderPublicKey", 23 | t."senderId" AS "t_senderId", 24 | t."recipientId" AS "t_recipientId", 25 | (t."amount")::bigint AS "t_amount", 26 | (t."fee")::bigint AS "t_fee", 27 | ENCODE(t."signature", 'hex') AS "t_signature", 28 | ENCODE(t."signSignature", 'hex') AS "t_signSignature", 29 | ENCODE(s."publicKey", 'hex') AS "s_publicKey", 30 | d."username" AS "d_username", 31 | v."votes" AS "v_votes", 32 | m."min" AS "m_min", 33 | m."lifetime" AS "m_lifetime", 34 | m."keysgroup" AS "m_keysgroup", 35 | dapp."name" AS "dapp_name", 36 | dapp."description" AS "dapp_description", 37 | dapp."tags" AS "dapp_tags", 38 | dapp."type" AS "dapp_type", 39 | dapp."link" AS "dapp_link", 40 | dapp."category" AS "dapp_category", 41 | dapp."icon" AS "dapp_icon", 42 | it."dappId" AS "in_dappId", 43 | ot."dappId" AS "ot_dappId", 44 | ot."outTransactionId" AS "ot_outTransactionId", 45 | ENCODE(t."requesterPublicKey", 'hex') AS "t_requesterPublicKey", 46 | t."signatures" AS "t_signatures", 47 | c."message" AS "c_message", 48 | c."own_message" AS "c_own_message", 49 | c."type" AS "c_type", 50 | st."type" as "st_type", 51 | st."stored_value" as "st_stored_value", 52 | st."stored_key" as "st_stored_key" 53 | 54 | FROM blocks b 55 | 56 | LEFT OUTER JOIN trs AS t ON t."blockId" = b."id" 57 | LEFT OUTER JOIN delegates AS d ON d."transactionId" = t."id" 58 | LEFT OUTER JOIN votes AS v ON v."transactionId" = t."id" 59 | LEFT OUTER JOIN signatures AS s ON s."transactionId" = t."id" 60 | LEFT OUTER JOIN multisignatures AS m ON m."transactionId" = t."id" 61 | LEFT OUTER JOIN dapps AS dapp ON dapp."transactionId" = t."id" 62 | LEFT OUTER JOIN intransfer AS it ON it."transactionId" = t."id" 63 | LEFT OUTER JOIN outtransfer AS ot ON ot."transactionId" = t."id" 64 | LEFT OUTER JOIN chats AS c ON c."transactionId" = t."id" 65 | LEFT OUTER JOIN states AS st ON st."transactionId" = t."id"; 66 | 67 | -------------------------------------------------------------------------------- /sql/migrations/20180718123722_addVotesWeightColumnToMemAccounts.sql: -------------------------------------------------------------------------------- 1 | /* Add votesWeight Column to Mem Accounts 2 | * 3 | */ 4 | 5 | BEGIN; 6 | 7 | ALTER TABLE "mem_accounts" ADD COLUMN IF NOT EXISTS "votesWeight" BIGINT NOT NULL DEFAULT 0; 8 | 9 | -- Set votesWeight for existing accounts 10 | UPDATE "mem_accounts" AS m SET "votesWeight" = 0; 11 | 12 | UPDATE "mem_accounts" AS m SET "votesWeight" = COALESCE(vote_weight,0) FROM ( SELECT ma."address", SUM("total_balance"::bigint) * (CASE WHEN ma.producedblocks + ma.missedblocks < 200 THEN 100.00 ELSE ROUND(100 - (ma.missedblocks::numeric / (ma.producedblocks + ma.missedblocks + 1) * 100), 2) END)::float/100 AS vote_weight FROM mem_accounts ma LEFT JOIN mem_accounts2delegates ma2d ON ENCODE(ma."publicKey", 'hex')=ma2d."dependentId" LEFT JOIN (SELECT ma_group.divider, floor("balance"::bigint/ ma_group.divider) AS total_balance, ma2."address" AS address FROM mem_accounts ma2 LEFT JOIN (SELECT COUNT("accountId") as divider, "accountId" FROM mem_accounts2delegates ma2d GROUP BY "accountId" ) as ma_group ON ma_group."accountId"=ma2."address" WHERE ma_group.divider>0) ma3 ON ma2d."accountId"=ma3."address" WHERE ma."isDelegate"=1 GROUP BY ma."address") as vv WHERE vv."address"=m."address" AND m."isDelegate"=1; 13 | 14 | COMMIT; 15 | -------------------------------------------------------------------------------- /sql/migrations/20190114165600_createLastFirstFunctions.sql: -------------------------------------------------------------------------------- 1 | -- Create a function that always returns the first non-NULL item 2 | CREATE OR REPLACE FUNCTION public.first_agg ( anyelement, anyelement ) 3 | RETURNS anyelement LANGUAGE SQL IMMUTABLE STRICT AS $$ 4 | SELECT $1; 5 | $$; 6 | 7 | -- And then wrap an aggregate around it 8 | CREATE AGGREGATE public.FIRST ( 9 | sfunc = public.first_agg, 10 | basetype = anyelement, 11 | stype = anyelement 12 | ); 13 | 14 | -- Create a function that always returns the last non-NULL item 15 | CREATE OR REPLACE FUNCTION public.last_agg ( anyelement, anyelement ) 16 | RETURNS anyelement LANGUAGE SQL IMMUTABLE STRICT AS $$ 17 | SELECT $2; 18 | $$; 19 | 20 | -- And then wrap an aggregate around it 21 | CREATE AGGREGATE public.LAST ( 22 | sfunc = public.last_agg, 23 | basetype = anyelement, 24 | stype = anyelement 25 | ); -------------------------------------------------------------------------------- /sql/migrations/20190601142900_createTrsListFullView.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * create 'trs_list_full' view, normalize addresses, add indexes 3 | */ 4 | 5 | BEGIN; 6 | 7 | DROP VIEW IF EXISTS trs_list_full; 8 | 9 | CREATE VIEW trs_list_full AS 10 | 11 | SELECT t."id" AS "t_id", 12 | b."height" AS "b_height", 13 | t."blockId" AS "t_blockId", 14 | t."type" AS "t_type", 15 | t."timestamp" AS "t_timestamp", 16 | t."senderPublicKey" AS "t_senderPublicKey", 17 | m."publicKey" AS "m_recipientPublicKey", 18 | UPPER(t."senderId") AS "t_senderId", 19 | UPPER(t."recipientId") AS "t_recipientId", 20 | t."amount" AS "t_amount", 21 | t."fee" AS "t_fee", 22 | ENCODE(t."signature", 'hex') AS "t_signature", 23 | ENCODE(t."signSignature", 'hex') AS "t_SignSignature", 24 | t."signatures" AS "t_signatures", 25 | (SELECT height + 1 FROM blocks ORDER BY height DESC LIMIT 1) - b."height" AS "confirmations", 26 | d."username" AS "d_username", 27 | v."votes" AS "v_votes", 28 | ms."min" AS "m_min", 29 | ms."lifetime" AS "m_lifetime", 30 | ms."keysgroup" AS "m_keysgroup", 31 | c."message" AS "c_message", 32 | c."own_message" AS "c_own_message", 33 | c."type" AS "c_type", 34 | st."type" as "st_type", 35 | st."stored_value" as "st_stored_value", 36 | st."stored_key" as "st_stored_key" 37 | FROM trs t 38 | 39 | LEFT JOIN blocks b ON t."blockId" = b."id" 40 | LEFT JOIN mem_accounts m ON t."recipientId" = m."address" 41 | LEFT OUTER JOIN delegates AS d ON d."transactionId" = t."id" 42 | LEFT OUTER JOIN votes AS v ON v."transactionId" = t."id" 43 | LEFT OUTER JOIN signatures AS s ON s."transactionId" = t."id" 44 | LEFT OUTER JOIN multisignatures AS ms ON ms."transactionId" = t."id" 45 | LEFT OUTER JOIN chats AS c ON c."transactionId" = t."id" 46 | LEFT OUTER JOIN states AS st ON st."transactionId" = t."id"; 47 | 48 | 49 | COMMIT; 50 | -------------------------------------------------------------------------------- /sql/migrations/20190815120200_recreateTrsListFullView.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * create 'trs_list_full' view, normalize addresses, add indexes 3 | */ 4 | 5 | BEGIN; 6 | 7 | DROP VIEW IF EXISTS trs_list_full; 8 | 9 | CREATE VIEW trs_list_full AS 10 | 11 | SELECT t."id" AS "t_id", 12 | b."height" AS "b_height", 13 | t."blockId" AS "t_blockId", 14 | t."type" AS "t_type", 15 | t."timestamp" AS "t_timestamp", 16 | b."timestamp" AS "b_timestamp", 17 | t."senderPublicKey" AS "t_senderPublicKey", 18 | m."publicKey" AS "m_recipientPublicKey", 19 | UPPER(t."senderId") AS "t_senderId", 20 | UPPER(t."recipientId") AS "t_recipientId", 21 | t."amount" AS "t_amount", 22 | t."fee" AS "t_fee", 23 | ENCODE(t."signature", 'hex') AS "t_signature", 24 | ENCODE(t."signSignature", 'hex') AS "t_SignSignature", 25 | t."signatures" AS "t_signatures", 26 | (SELECT height + 1 FROM blocks ORDER BY height DESC LIMIT 1) - b."height" AS "confirmations", 27 | d."username" AS "d_username", 28 | v."votes" AS "v_votes", 29 | ms."min" AS "m_min", 30 | ms."lifetime" AS "m_lifetime", 31 | ms."keysgroup" AS "m_keysgroup", 32 | c."message" AS "c_message", 33 | c."own_message" AS "c_own_message", 34 | c."type" AS "c_type", 35 | st."type" as "st_type", 36 | st."stored_value" as "st_stored_value", 37 | st."stored_key" as "st_stored_key" 38 | FROM trs t 39 | 40 | LEFT JOIN blocks b ON t."blockId" = b."id" 41 | LEFT JOIN mem_accounts m ON t."recipientId" = m."address" 42 | LEFT OUTER JOIN delegates AS d ON d."transactionId" = t."id" 43 | LEFT OUTER JOIN votes AS v ON v."transactionId" = t."id" 44 | LEFT OUTER JOIN signatures AS s ON s."transactionId" = t."id" 45 | LEFT OUTER JOIN multisignatures AS ms ON ms."transactionId" = t."id" 46 | LEFT OUTER JOIN chats AS c ON c."transactionId" = t."id" 47 | LEFT OUTER JOIN states AS st ON st."transactionId" = t."id"; 48 | 49 | 50 | COMMIT; 51 | -------------------------------------------------------------------------------- /sql/migrations/20190815120437_recreateTrsListView.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Recreate 'trs_list' view, normalize addresses, add indexes 3 | */ 4 | 5 | BEGIN; 6 | 7 | DROP VIEW IF EXISTS trs_list; 8 | 9 | CREATE VIEW trs_list AS 10 | 11 | SELECT t."id" AS "t_id", 12 | b."height" AS "b_height", 13 | t."blockId" AS "t_blockId", 14 | t."type" AS "t_type", 15 | t."timestamp" AS "t_timestamp", 16 | b."timestamp" AS "b_timestamp", 17 | t."senderPublicKey" AS "t_senderPublicKey", 18 | m."publicKey" AS "m_recipientPublicKey", 19 | UPPER(t."senderId") AS "t_senderId", 20 | UPPER(t."recipientId") AS "t_recipientId", 21 | t."amount" AS "t_amount", 22 | t."fee" AS "t_fee", 23 | ENCODE(t."signature", 'hex') AS "t_signature", 24 | ENCODE(t."signSignature", 'hex') AS "t_SignSignature", 25 | t."signatures" AS "t_signatures", 26 | (SELECT height + 1 FROM blocks ORDER BY height DESC LIMIT 1) - b."height" AS "confirmations" 27 | 28 | FROM trs t 29 | 30 | LEFT JOIN blocks b ON t."blockId" = b."id" 31 | LEFT JOIN mem_accounts m ON t."recipientId" = m."address"; 32 | 33 | CREATE INDEX IF NOT EXISTS "trs_upper_sender_id" ON "trs"(UPPER("senderId")); 34 | CREATE INDEX IF NOT EXISTS "trs_upper_recipient_id" ON "trs"(UPPER("recipientId")); 35 | 36 | COMMIT; 37 | -------------------------------------------------------------------------------- /sql/migrations/20221220012528_addConfirmationsFieldForFullBlocksList.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Create "confirmations" column to full_blocks_list 3 | */ 4 | 5 | BEGIN; 6 | 7 | DROP VIEW IF EXISTS full_blocks_list; 8 | 9 | CREATE VIEW full_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")::bigint AS "b_totalAmount", 18 | (b."totalFee")::bigint AS "b_totalFee", 19 | (b."reward")::bigint AS "b_reward", 20 | b."payloadLength" AS "b_payloadLength", 21 | ENCODE(b."payloadHash", 'hex') AS "b_payloadHash", 22 | b."text_generatorPublicKey" AS "b_generatorPublicKey", 23 | ENCODE(b."blockSignature", 'hex') AS "b_blockSignature", 24 | t."id" AS "t_id", 25 | t."rowId" AS "t_rowId", 26 | t."type" AS "t_type", 27 | t."timestamp" AS "t_timestamp", 28 | ENCODE(t."senderPublicKey", 'hex') AS "t_senderPublicKey", 29 | t."senderId" AS "t_senderId", 30 | t."recipientId" AS "t_recipientId", 31 | (t."amount")::bigint AS "t_amount", 32 | (t."fee")::bigint AS "t_fee", 33 | ENCODE(t."signature", 'hex') AS "t_signature", 34 | ENCODE(t."signSignature", 'hex') AS "t_signSignature", 35 | ENCODE(s."publicKey", 'hex') AS "s_publicKey", 36 | d."username" AS "d_username", 37 | v."votes" AS "v_votes", 38 | m."min" AS "m_min", 39 | m."lifetime" AS "m_lifetime", 40 | m."keysgroup" AS "m_keysgroup", 41 | dapp."name" AS "dapp_name", 42 | dapp."description" AS "dapp_description", 43 | dapp."tags" AS "dapp_tags", 44 | dapp."type" AS "dapp_type", 45 | dapp."link" AS "dapp_link", 46 | dapp."category" AS "dapp_category", 47 | dapp."icon" AS "dapp_icon", 48 | it."dappId" AS "in_dappId", 49 | ot."dappId" AS "ot_dappId", 50 | ot."outTransactionId" AS "ot_outTransactionId", 51 | ENCODE(t."requesterPublicKey", 'hex') AS "t_requesterPublicKey", 52 | t."signatures" AS "t_signatures", 53 | c."message" AS "c_message", 54 | c."own_message" AS "c_own_message", 55 | c."type" AS "c_type", 56 | st."type" as "st_type", 57 | st."stored_value" as "st_stored_value", 58 | st."stored_key" as "st_stored_key", 59 | (SELECT MAX("height") + 1 FROM blocks) - b."height" AS "confirmations" 60 | 61 | FROM blocks b 62 | 63 | LEFT OUTER JOIN trs AS t ON t."blockId" = b."id" 64 | LEFT OUTER JOIN delegates AS d ON d."transactionId" = t."id" 65 | LEFT OUTER JOIN votes AS v ON v."transactionId" = t."id" 66 | LEFT OUTER JOIN signatures AS s ON s."transactionId" = t."id" 67 | LEFT OUTER JOIN multisignatures AS m ON m."transactionId" = t."id" 68 | LEFT OUTER JOIN dapps AS dapp ON dapp."transactionId" = t."id" 69 | LEFT OUTER JOIN intransfer AS it ON it."transactionId" = t."id" 70 | LEFT OUTER JOIN outtransfer AS ot ON ot."transactionId" = t."id" 71 | LEFT OUTER JOIN chats AS c ON c."transactionId" = t."id" 72 | LEFT OUTER JOIN states AS st ON st."transactionId" = t."id"; 73 | 74 | 75 | COMMIT; 76 | -------------------------------------------------------------------------------- /sql/migrations/20241218010631_addTimestampMs.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Add `timestampMs` Column to Transactions 3 | */ 4 | 5 | BEGIN; 6 | 7 | -- Add timestampMs column to Transactions 8 | ALTER TABLE "trs" ADD COLUMN "timestampMs" BIGINT; 9 | 10 | -- Add timestampMs index 11 | CREATE INDEX IF NOT EXISTS "timestamp_ms" ON "trs"("timestampMs"); 12 | 13 | -- Add timestampMs column to trs_list view 14 | DROP VIEW IF EXISTS trs_list; 15 | 16 | CREATE VIEW trs_list AS 17 | 18 | SELECT t."id" AS "t_id", 19 | b."height" AS "b_height", 20 | t."blockId" AS "t_blockId", 21 | t."type" AS "t_type", 22 | t."timestamp" AS "t_timestamp", 23 | t."timestampMs" AS "t_timestampMs", 24 | b."timestamp" AS "b_timestamp", 25 | t."senderPublicKey" AS "t_senderPublicKey", 26 | m."publicKey" AS "m_recipientPublicKey", 27 | UPPER(t."senderId") AS "t_senderId", 28 | UPPER(t."recipientId") AS "t_recipientId", 29 | t."amount" AS "t_amount", 30 | t."fee" AS "t_fee", 31 | ENCODE(t."signature", 'hex') AS "t_signature", 32 | ENCODE(t."signSignature", 'hex') AS "t_SignSignature", 33 | t."signatures" AS "t_signatures", 34 | (SELECT height + 1 FROM blocks ORDER BY height DESC LIMIT 1) - b."height" AS "confirmations" 35 | 36 | FROM trs t 37 | LEFT JOIN blocks b ON t."blockId" = b."id" 38 | LEFT JOIN mem_accounts m ON t."recipientId" = m."address"; 39 | 40 | -- Add timestampMs column to trs_list_full view 41 | DROP VIEW IF EXISTS trs_list_full; 42 | 43 | CREATE VIEW trs_list_full AS 44 | 45 | SELECT t."id" AS "t_id", 46 | b."height" AS "b_height", 47 | t."blockId" AS "t_blockId", 48 | t."type" AS "t_type", 49 | t."timestamp" AS "t_timestamp", 50 | b."timestamp" AS "b_timestamp", 51 | t."senderPublicKey" AS "t_senderPublicKey", 52 | m."publicKey" AS "m_recipientPublicKey", 53 | UPPER(t."senderId") AS "t_senderId", 54 | UPPER(t."recipientId") AS "t_recipientId", 55 | t."amount" AS "t_amount", 56 | t."fee" AS "t_fee", 57 | ENCODE(t."signature", 'hex') AS "t_signature", 58 | ENCODE(t."signSignature", 'hex') AS "t_SignSignature", 59 | t."signatures" AS "t_signatures", 60 | (SELECT height + 1 FROM blocks ORDER BY height DESC LIMIT 1) - b."height" AS "confirmations", 61 | d."username" AS "d_username", 62 | v."votes" AS "v_votes", 63 | ms."min" AS "m_min", 64 | ms."lifetime" AS "m_lifetime", 65 | ms."keysgroup" AS "m_keysgroup", 66 | c."message" AS "c_message", 67 | c."own_message" AS "c_own_message", 68 | c."type" AS "c_type", 69 | st."type" as "st_type", 70 | st."stored_value" as "st_stored_value", 71 | st."stored_key" as "st_stored_key" 72 | FROM trs t 73 | 74 | LEFT JOIN blocks b ON t."blockId" = b."id" 75 | LEFT JOIN mem_accounts m ON t."recipientId" = m."address" 76 | LEFT OUTER JOIN delegates AS d ON d."transactionId" = t."id" 77 | LEFT OUTER JOIN votes AS v ON v."transactionId" = t."id" 78 | LEFT OUTER JOIN signatures AS s ON s."transactionId" = t."id" 79 | LEFT OUTER JOIN multisignatures AS ms ON ms."transactionId" = t."id" 80 | LEFT OUTER JOIN chats AS c ON c."transactionId" = t."id" 81 | LEFT OUTER JOIN states AS st ON st."transactionId" = t."id"; 82 | 83 | 84 | 85 | COMMIT; 86 | -------------------------------------------------------------------------------- /sql/multisignatures.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var MultisignaturesSql = { 4 | getAccountIds: 'SELECT ARRAY_AGG("accountId") AS "accountIds" FROM mem_accounts2multisignatures WHERE "dependentId" = ${publicKey}' 5 | }; 6 | 7 | module.exports = MultisignaturesSql; 8 | -------------------------------------------------------------------------------- /sql/peers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var PeersSql = { 4 | 5 | getAll: 'SELECT ip, port, state, os, version, ENCODE(broadhash, \'hex\') AS broadhash, height, clock, (SELECT ARRAY_AGG(dappid) FROM peers_dapp WHERE "peerId" = peers.id) as dappid FROM peers', 6 | 7 | clear: 'DELETE FROM peers', 8 | 9 | truncate: 'TRUNCATE peers CASCADE', 10 | 11 | addDapp: 'INSERT INTO peers_dapp ("peerId", dappid) VALUES ((SELECT id FROM peers WHERE ip = ${ip} AND port = ${port}), ${dappid}) ON CONFLICT DO NOTHING' 12 | }; 13 | 14 | module.exports = PeersSql; 15 | -------------------------------------------------------------------------------- /sql/rounds.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var RoundsSql = { 4 | flush: 'DELETE FROM mem_round WHERE "round" = (${round})::bigint;', 5 | 6 | reCalcVotes: 'UPDATE "mem_accounts" AS m SET "votesWeight" = COALESCE(vote_weight,0) FROM ( SELECT ma."address", SUM("total_balance"::bigint) * (CASE WHEN ma.producedblocks + ma.missedblocks < 200 THEN 100.00 ELSE ROUND(100 - (ma.missedblocks::numeric / (ma.producedblocks + ma.missedblocks + 1) * 100), 2) END)::float/100 AS vote_weight FROM mem_accounts ma LEFT JOIN mem_accounts2delegates ma2d ON ENCODE(ma."publicKey", \'hex\')=ma2d."dependentId" LEFT JOIN (SELECT ma_group.divider, floor("balance"::bigint/ ma_group.divider) AS total_balance, ma2."address" AS address FROM mem_accounts ma2 LEFT JOIN (SELECT COUNT("accountId") as divider, "accountId" FROM mem_accounts2delegates ma2d GROUP BY "accountId" ) as ma_group ON ma_group."accountId"=ma2."address" WHERE ma_group.divider>0) ma3 ON ma2d."accountId"=ma3."address" WHERE ma."isDelegate"=1 GROUP BY ma."address") as vv WHERE vv."address"=m."address" AND m."isDelegate"=1;', 7 | 8 | truncateBlocks: 'DELETE FROM blocks WHERE "height" > (${height})::bigint;', 9 | 10 | updateMissedBlocks: function (backwards) { 11 | return [ 12 | 'UPDATE mem_accounts SET "missedblocks" = "missedblocks"', 13 | (backwards ? '- 1' : '+ 1'), 14 | 'WHERE "address" IN ($1:csv);' 15 | ].join(' '); 16 | }, 17 | 18 | getVotes: 'SELECT d."delegate", d."amount" FROM (SELECT m."delegate", SUM(m."amount") AS "amount", "round" FROM mem_round m GROUP BY m."delegate", m."round") AS d WHERE "round" = (${round})::bigint', 19 | 20 | updateVotes: 'UPDATE mem_accounts SET "vote" = "vote" + (${amount})::bigint WHERE "address" = ${address};', 21 | 22 | updateBlockId: 'UPDATE mem_accounts SET "blockId" = ${newId} WHERE "blockId" = ${oldId};', 23 | 24 | summedRound: 'SELECT SUM(r.fee)::bigint AS "fees", ARRAY_AGG(r.reward) AS rewards, ARRAY_AGG(r.pk) AS delegates FROM (SELECT b."totalFee" AS fee, b.reward, ENCODE(b."generatorPublicKey", \'hex\') AS pk FROM blocks b WHERE CEIL(b.height / ${activeDelegates}::float)::int = ${round} ORDER BY b.height ASC) r;', 25 | 26 | clearRoundSnapshot: 'DROP TABLE IF EXISTS mem_round_snapshot', 27 | 28 | performRoundSnapshot: 'CREATE TABLE mem_round_snapshot AS TABLE mem_round', 29 | 30 | restoreRoundSnapshot: 'INSERT INTO mem_round SELECT * FROM mem_round_snapshot', 31 | 32 | clearVotesSnapshot: 'DROP TABLE IF EXISTS mem_votes_snapshot', 33 | 34 | performVotesSnapshot: 'CREATE TABLE mem_votes_snapshot AS SELECT address, vote FROM mem_accounts WHERE "isDelegate" = 1', 35 | 36 | restoreVotesSnapshot: 'UPDATE mem_accounts m SET vote = b.vote FROM mem_votes_snapshot b WHERE m.address = b.address' 37 | }; 38 | 39 | module.exports = RoundsSql; 40 | -------------------------------------------------------------------------------- /sql/runtime.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Runtime queries - executed at node start (right after migrations) 3 | */ 4 | 5 | BEGIN; 6 | 7 | UPDATE "peers" SET "state" = 1, "clock" = NULL WHERE "state" != 0; 8 | 9 | -- Overwrite unconfirmed tables with state from confirmed tables 10 | DELETE FROM mem_accounts2u_delegates; 11 | INSERT INTO mem_accounts2u_delegates ("accountId", "dependentId") SELECT "accountId", "dependentId" FROM mem_accounts2delegates; 12 | 13 | DELETE FROM mem_accounts2u_multisignatures; 14 | INSERT INTO mem_accounts2u_multisignatures ("accountId", "dependentId") SELECT "accountId", "dependentId" FROM mem_accounts2multisignatures; 15 | 16 | COMMIT; 17 | -------------------------------------------------------------------------------- /sql/states.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var StatesSql = { 4 | sortFields: ['type', 'timestamp'], 5 | 6 | countByTransactionId: 'SELECT COUNT(*)::int AS "count" FROM states WHERE "transactionId" = ${id}', 7 | 8 | 9 | search: function (params) { 10 | return [ 11 | 'SELECT "transactionId", "stored_value", "stored_key", "type" ', 12 | 'FROM states WHERE to_tsvector("stored_value" || \' \' || "stored_key" || \' \' ) @@ to_tsquery(${q})', 13 | '', 14 | 'LIMIT ${limit}' 15 | ].filter(Boolean).join(' '); 16 | }, 17 | 18 | get: 'SELECT "stored_value", "stored_key", "type", "transactionId" FROM states WHERE "transactionId" = ${id}', 19 | 20 | getByIds: 'SELECT "stored_value", "stored_key", "type", "transactionId" FROM states WHERE "transactionId" IN ($1:csv)', 21 | countList: function (params) { 22 | return [ 23 | 24 | 'SELECT COUNT(1)::INT FROM full_blocks_list', 25 | (params.where.length ? 'WHERE ' + params.where.join(' AND ') : ''), 26 | (params.sortField ? 'ORDER BY ' + [params.sortField, params.sortMethod].join(' ') : '') 27 | ].filter(Boolean).join(' '); 28 | }, 29 | 30 | // Need to fix "or" or "and" in query 31 | list: function (params) { 32 | return [ 33 | 34 | 'SELECT *, t_timestamp as timestamp, b_timestamp as block_timestamp FROM full_blocks_list', 35 | (params.where.length ? 'WHERE ' + params.where.join(' AND ') : ''), 36 | (params.sortField ? 'ORDER BY ' + [params.sortField, params.sortMethod].join(' ') : ''), 37 | 'LIMIT ${limit} OFFSET ${offset}' 38 | ].filter(Boolean).join(' '); 39 | }, 40 | 41 | getGenesis: 'SELECT b."height" AS "height", b."id" AS "id", t."senderId" AS "authorId" FROM trs t INNER JOIN blocks b ON t."blockId" = b."id" WHERE t."id" = ${id}' 42 | 43 | }; 44 | 45 | module.exports = StatesSql; 46 | -------------------------------------------------------------------------------- /sql/system.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var SystemSql = { 4 | getBroadhash: 'SELECT "id" FROM blocks ORDER BY "height" DESC LIMIT ${limit}' 5 | }; 6 | 7 | module.exports = SystemSql; 8 | -------------------------------------------------------------------------------- /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 | count: 'SELECT COUNT("id")::int AS "count" FROM trs', 19 | 20 | countById: 'SELECT COUNT("id")::int AS "count" FROM trs WHERE "id" = ${id}', 21 | 22 | countList: function (params) { 23 | return [ 24 | 'SELECT COUNT(1)::INT FROM trs_list', 25 | (params.where.length || params.owner ? 'WHERE' : ''), 26 | (params.where.length ? '(' + params.where.join(' ') + ')' : ''), 27 | // FIXME: Backward compatibility, should be removed after transitional period 28 | (params.where.length && params.owner ? ' AND ' + params.owner : params.owner) 29 | ].filter(Boolean).join(' '); 30 | }, 31 | 32 | list: function (params) { 33 | return [ 34 | 'SELECT "t_id", "b_height", "t_blockId", "t_type", "t_timestamp", "b_timestamp" as "block_timestamp", "t_senderId", "t_recipientId",', 35 | '"t_amount", "t_fee", "t_signature", "t_SignSignature", "t_signatures", "confirmations",', 36 | 'ENCODE ("t_senderPublicKey", \'hex\') AS "t_senderPublicKey", ENCODE ("m_recipientPublicKey", \'hex\') AS "m_recipientPublicKey"', 37 | 'FROM trs_list', 38 | (params.where.length || params.owner ? 'WHERE' : ''), 39 | (params.where.length ? '(' + params.where.join(' ') + ')' : ''), 40 | // FIXME: Backward compatibility, should be removed after transitional period 41 | (params.where.length && params.owner ? ' AND ' + params.owner : params.owner), 42 | (params.sortField ? 'ORDER BY ' + [params.sortField, params.sortMethod].join(' ') : ''), 43 | 'LIMIT ${limit} OFFSET ${offset}' 44 | ].filter(Boolean).join(' '); 45 | }, 46 | listFull: function (params) { 47 | return [ 48 | 'SELECT "t_id", "b_height", "t_blockId", "t_type", "t_timestamp", "b_timestamp" as "block_timestamp", "t_senderId", "t_recipientId",', 49 | '"t_amount", "t_fee", "t_signature", "t_SignSignature", "t_signatures", "confirmations",', 50 | 'ENCODE ("t_senderPublicKey", \'hex\') AS "t_senderPublicKey", ENCODE ("m_recipientPublicKey", \'hex\') AS "m_recipientPublicKey",', 51 | '"d_username", "v_votes", "m_min", "m_lifetime", "m_keysgroup", "c_message", "c_own_message", "c_type", "st_type", "st_stored_value", "st_stored_key" ', 52 | 'FROM trs_list_full', 53 | (params.where.length || params.owner ? 'WHERE' : ''), 54 | (params.where.length ? '(' + params.where.join(' ') + ')' : ''), 55 | // FIXME: Backward compatibility, should be removed after transitional period 56 | (params.where.length && params.owner ? ' AND ' + params.owner : params.owner), 57 | (params.sortField ? 'ORDER BY ' + [params.sortField, params.sortMethod].join(' ') : ''), 58 | 'LIMIT ${limit} OFFSET ${offset}' 59 | ].filter(Boolean).join(' '); 60 | }, 61 | 62 | getById: 'SELECT *, ENCODE ("t_senderPublicKey", \'hex\') AS "t_senderPublicKey", ENCODE ("m_recipientPublicKey", \'hex\') AS "m_recipientPublicKey" FROM trs_list WHERE "t_id" = ${id}', 63 | getByIdFull: 'SELECT *, ENCODE ("t_senderPublicKey", \'hex\') AS "t_senderPublicKey", ENCODE ("m_recipientPublicKey", \'hex\') AS "m_recipientPublicKey" FROM trs_list_full WHERE "t_id" = ${id}', 64 | 65 | getVotesById: 'SELECT * FROM votes WHERE "transactionId" = ${id}' 66 | }; 67 | 68 | module.exports = TransactionsSql; 69 | -------------------------------------------------------------------------------- /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 | }; 6 | 7 | module.exports = TransportSql; 8 | -------------------------------------------------------------------------------- /tasks/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Adamant-im/adamant/9cc651471d824471856d4cd4ad04e14c801b9654/tasks/.gitkeep -------------------------------------------------------------------------------- /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 | /** 9 | * Usage: 10 | * ``` 11 | * npx grunt newMigration:YOUR_MIGRATION_NAME 12 | * ``` 13 | */ 14 | grunt.registerTask('newMigration', 'Create a new migration file.', function (name) { 15 | if (!name) { 16 | grunt.fail.fatal('The name is required.'); 17 | } 18 | 19 | var migration = { 20 | id: moment().format('YYYYMMDDHHmmss'), 21 | name: String(name) 22 | }; 23 | 24 | if (!migration.name.match(/^[a-z]+$/i)) { 25 | grunt.fail.fatal('Invalid migration name'); 26 | } 27 | 28 | migration.filename = ( 29 | migration.id + '_' + migration.name + '.sql' 30 | ); 31 | 32 | grunt.log.write('Creating migration file: ' + migration.filename); 33 | 34 | fs.writeFile(path.join('sql', 'migrations', migration.filename), '', function (err) { 35 | if (err) { grunt.fail.fatal(err); } 36 | }); 37 | }); 38 | }; 39 | -------------------------------------------------------------------------------- /test/api/loader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var node = require('./../node.js'); 4 | 5 | describe('GET /api/loader/status/ping', function () { 6 | it('should be ok', function (done) { 7 | node.get('/api/loader/status/ping', function (err, res) { 8 | node.expect(res.body).to.have.property('success').to.be.true; 9 | done(); 10 | }); 11 | }); 12 | }); 13 | 14 | describe('GET /api/loader/status/sync', function () { 15 | it('should be ok', function (done) { 16 | node.get('/api/loader/status/sync', function (err, res) { 17 | node.expect(res.body).to.have.property('success').to.be.true; 18 | node.expect(res.body).to.have.property('syncing').to.a('boolean'); 19 | node.expect(res.body).to.have.property('blocks').to.be.a('number'); 20 | node.expect(res.body).to.have.property('height').to.be.a('number'); 21 | node.expect(res.body).to.have.property('broadhash').to.be.a('string'); 22 | // node.expect(res.body).to.not.have.property('consensus'); // Indicates forced forging 23 | done(); 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/api/peer.dapp.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var node = require('./../node.js'); 4 | 5 | describe('POST /peer/dapp/message', function () { 6 | 7 | }); 8 | 9 | describe('POST /peer/dapp/request', function () { 10 | 11 | }); 12 | -------------------------------------------------------------------------------- /test/api/peer.transactions.multisignatures.js: -------------------------------------------------------------------------------- 1 | // Multisignatures tests are disabled currently 2 | 3 | /* 4 | 'use strict'; 5 | 6 | var crypto = require('crypto'); 7 | var node = require('./../node.js'); 8 | 9 | var multisigAccount = node.randomAccount(); 10 | 11 | function postTransaction (transaction, done) { 12 | node.post('/peer/transactions', { 13 | transaction: transaction 14 | }, function (err, res) { 15 | done(err, res); 16 | }); 17 | } 18 | 19 | function sendADM (params, done) { 20 | var transaction = node.createSendTransaction({ 21 | keyPair: account.keypair, 22 | amount: 100000000, 23 | recipientId: randomAccount.address 24 | }); 25 | var transaction = node.lisk.transaction.createTransaction(params.recipientId, params.amount, params.secret); 26 | 27 | postTransaction(transaction, function (err, res) { 28 | node.expect(res.body).to.have.property('success').to.be.true; 29 | node.onNewBlock(function (err) { 30 | done(err, res); 31 | }); 32 | }); 33 | } 34 | 35 | describe('POST /peer/transactions', function () { 36 | 37 | describe('creating multisignature group', function () { 38 | 39 | describe('when account has no funds', function () { 40 | 41 | it('should fail', function (done) { 42 | var multiSigTx = node.lisk.multisignature.createMultisignature(multisigAccount.password, null, [node.randomAccount().publicKey], 1, 2); 43 | 44 | postTransaction(multiSigTx, function (err, res) { 45 | node.expect(res.body).to.have.property('success').to.be.false; 46 | node.expect(res.body).to.have.property('message').to.match(/Account does not have enough ADM: U[0-9]+ balance: 0/); 47 | done(); 48 | }); 49 | }); 50 | }); 51 | 52 | describe('when account has funds', function () { 53 | 54 | before(function (done) { 55 | sendADM({ 56 | secret: node.iAccount.password, 57 | amount: node.fees.multisignatureRegistrationFee * 10, 58 | recipientId: multisigAccount.address 59 | }, done); 60 | }); 61 | 62 | it('using null member in keysgroup should fail', function (done) { 63 | var multiSigTx = node.lisk.multisignature.createMultisignature(multisigAccount.password, null, ['+' + node.eAccount.publicKey, null], 1, 2); 64 | 65 | postTransaction(multiSigTx, function (err, res) { 66 | node.expect(res.body).to.have.property('success').to.be.false; 67 | node.expect(res.body).to.have.property('message').to.equal('Invalid member in keysgroup'); 68 | done(); 69 | }); 70 | }); 71 | 72 | it('using invalid member in keysgroup should fail', function (done) { 73 | var memberAccount1 = node.randomAccount(); 74 | var memberAccount2 = node.randomAccount(); 75 | 76 | var multiSigTx = node.lisk.multisignature.createMultisignature( 77 | multisigAccount.password, null, ['+' + node.eAccount.publicKey + 'A', 78 | '+' + memberAccount1.publicKey, '+' + memberAccount2.publicKey], 1, 2); 79 | 80 | postTransaction(multiSigTx, function (err, res) { 81 | node.expect(res.body).to.have.property('success').to.be.false; 82 | node.expect(res.body).to.have.property('message').to.equal('Invalid public key in multisignature keysgroup'); 83 | done(); 84 | }); 85 | }); 86 | }); 87 | }); 88 | }); 89 | */ 90 | -------------------------------------------------------------------------------- /test/api/peer.transactions.signatures.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 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 | transaction: transaction 12 | }, function (err, res) { 13 | done(err, res); 14 | }); 15 | } 16 | 17 | function postSignatureTransaction (transaction, done) { 18 | node.put('/api/signatures', transaction, function (err, res) { 19 | done(err, res); 20 | }); 21 | } 22 | 23 | function sendADM (params, done) { 24 | var transaction = node.createSendTransaction({ 25 | keyPair: node.createKeypairFromPassphrase(params.secret), 26 | amount: params.amount, 27 | recipientId: params.recipientId 28 | }); 29 | 30 | postTransaction(transaction, function (err, res) { 31 | node.expect(res.body).to.have.property('success').to.be.true; 32 | node.onNewBlock(function (err) { 33 | done(err, res); 34 | }); 35 | }); 36 | } 37 | 38 | describe('POST /peer/transactions', function () { 39 | describe('enabling second signature', function () { 40 | it('using undefined transaction', function (done) { 41 | postTransaction(undefined, function (err, res) { 42 | node.expect(res.body).to.have.property('success').to.be.false; 43 | node.expect(res.body).to.have.property('message').to.contain('Invalid transaction body'); 44 | done(); 45 | }); 46 | }); 47 | 48 | // createSignatureTransaction doesn't work as ADAMANT doesn't use second signatures 49 | // it('using undefined transaction.asset', function (done) { 50 | // var transaction = node.lisk.signature.createSignature(node.randomAccount().password, node.randomAccount().password); 51 | 52 | // delete transaction.asset; 53 | 54 | // postTransaction(transaction, function (err, res) { 55 | // node.expect(res.body).to.have.property('success').to.be.false; 56 | // node.expect(res.body).to.have.property('message').to.contain('Invalid transaction body'); 57 | // done(); 58 | // }); 59 | // }); 60 | 61 | describe('when account has no funds', function () { 62 | it('should fail', function (done) { 63 | var transaction = { 64 | secret: account.password, 65 | secondSecret: account.secondPassword 66 | }; 67 | 68 | postSignatureTransaction(transaction, function (err, res) { 69 | node.expect(res.body).to.have.property('success').to.be.false; 70 | node.expect(res.body).to.have.property('error').to.match(/Account does not have enough ADM: U[0-9]+ balance: 0/); 71 | done(); 72 | }); 73 | }); 74 | }); 75 | 76 | describe('when account has funds', function () { 77 | before(function (done) { 78 | sendADM({ 79 | secret: node.iAccount.password, 80 | amount: node.fees.secondPasswordFee + 100000000, 81 | recipientId: account.address 82 | }, done); 83 | }); 84 | 85 | it('should be ok', function (done) { 86 | // var transaction = node.createSignatureTransaction({ 87 | // keyPair: account.keypair, 88 | // secondKeypair: account2.keypair, 89 | // secret: account.secondPassword, 90 | // secondSecret: account.secondPassword 91 | // }); 92 | var transaction = { 93 | secret: account.password, 94 | secondSecret: account.secondPassword 95 | }; 96 | 97 | postSignatureTransaction(transaction, function (err, res) { 98 | node.expect(res.body).to.have.property('success').to.be.true; 99 | // node.expect(res.body).to.have.property('transactionId').to.equal(transaction.id); 100 | done(); 101 | }); 102 | }); 103 | }); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /test/api/states.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const node = require('../node.js'); 3 | 4 | function getStates(senderId, options, done) { 5 | const params = { 6 | senderId, 7 | ...options, 8 | }; 9 | 10 | const args = Object.keys(params).map((key) => `${key}=${params[key]}`); 11 | node.get(`/api/states/get?${args.join('&')}`, done); 12 | } 13 | 14 | function storeState(params, done) { 15 | node.post('/api/states/store', params, done); 16 | } 17 | 18 | function sendADM(params, done) { 19 | node.put('/api/transactions/', params, done); 20 | } 21 | 22 | describe('GET /api/states/get', () => { 23 | const testAccount = node.randomAccount(); 24 | 25 | before((done) => { 26 | sendADM({ 27 | secret: node.iAccount.password, 28 | amount: node.fees.stateFee * 2, 29 | recipientId: testAccount.address, 30 | }, () => { 31 | done() 32 | }); 33 | }); 34 | 35 | before(function (done) { 36 | node.onNewBlock(function () { 37 | done(); 38 | }); 39 | }); 40 | 41 | before((done) => { 42 | const stateTransaction = node.createStateTransaction({ 43 | key: 'testkey', 44 | value: 'testvalue', 45 | keyPair: testAccount.keypair, 46 | }); 47 | 48 | storeState({ transaction: stateTransaction }, (err, res) => { 49 | expect(err).to.not.exist; 50 | expect(res.body).to.have.property('success').that.is.true; 51 | 52 | // wait a second for node to process the transaction 53 | setTimeout(() => done(), 1000); 54 | }); 55 | }); 56 | 57 | it('should NOT return unconfirmed transactions by default', (done) => { 58 | getStates(testAccount.address, {}, (err, res) => { 59 | expect(err).to.not.exist; 60 | expect(res.body). 61 | to.have.property('transactions'). 62 | that.is.an('array'); 63 | 64 | const { transactions } = res.body; 65 | transactions.forEach((transaction) => { 66 | expect(transaction).to.have.property('confirmations').that.is.a('number'); 67 | }); 68 | 69 | done(); 70 | }); 71 | }); 72 | 73 | it('should return unconfirmed transaction with ?returnUnconfirmed=1 flag', (done) => { 74 | getStates(testAccount.address, { returnUnconfirmed: 1 }, (err, res) => { 75 | expect(err).to.not.exist; 76 | expect(res.body).to.have.property('transactions').that.is.an('array').that.is.not.empty 77 | const includesUnconfirmedTransactions = res.body.transactions.some((transaction) => { 78 | return !('confirmations' in transaction); 79 | }); 80 | 81 | expect(includesUnconfirmedTransactions).to.be.true; 82 | 83 | node.onNewBlock(() => { 84 | done(); 85 | }); 86 | }); 87 | }); 88 | 89 | it('should only return confirmed transaction with limit 1 and order by timestamp', (done) => { 90 | const stateTransaction = node.createStateTransaction({ 91 | key: 'anotherkey', 92 | value: 'testvalue', 93 | keyPair: testAccount.keypair, 94 | }); 95 | 96 | storeState({ transaction: stateTransaction }, (err, res) => { 97 | expect(err).to.not.exist; 98 | expect(res.body).to.have.property('success').that.is.true; 99 | 100 | setTimeout(() => { 101 | getStates(testAccount.address, { orderBy: 'timestamp:asc', limit: 1, returnUnconfirmed: 1 }, (err, res) => { 102 | expect(err).to.not.exist; 103 | expect(res.body).to.have.property('transactions').that.is.an('array').that.has.lengthOf(1); 104 | expect(res.body.transactions[0]).to.have.property('confirmations'); 105 | 106 | done(); 107 | }); 108 | }, 1000); 109 | }); 110 | }); 111 | }) 112 | -------------------------------------------------------------------------------- /test/common/api.js: -------------------------------------------------------------------------------- 1 | const node = require('./../node.js'); 2 | 3 | const apiUtils = { 4 | sendADM(params, done) { 5 | node.put('/api/transactions/', params, done); 6 | }, 7 | postMessage (transaction, done) { 8 | node.post('/api/transactions', { transaction: transaction }, done); 9 | } 10 | }; 11 | 12 | module.exports = apiUtils; 13 | -------------------------------------------------------------------------------- /test/common/assert.js: -------------------------------------------------------------------------------- 1 | exports.isHex = (str) => Buffer.from(str, 'hex').length !== 0; 2 | -------------------------------------------------------------------------------- /test/common/globalAfter.js: -------------------------------------------------------------------------------- 1 | const jobsQueue = require('../../helpers/jobsQueue.js'); 2 | 3 | function removeQueuedJobs() { 4 | Object.keys(jobsQueue.jobs).forEach((name) => { 5 | const timeout = jobsQueue.jobs[name]; 6 | clearTimeout(timeout); 7 | delete jobsQueue.jobs[name]; 8 | }); 9 | } 10 | 11 | module.exports = { 12 | removeQueuedJobs, 13 | }; 14 | -------------------------------------------------------------------------------- /test/common/globalBefore.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var node = require('./../node.js'); 4 | 5 | /** 6 | * @param {string} table 7 | * @param {Logger} logger 8 | * @param {Object} db 9 | * @param {Function} cb 10 | */ 11 | function clearDatabaseTable (db, logger, table, cb) { 12 | db.query('DELETE FROM ' + table).then(function (result) { 13 | cb(null, result); 14 | }).catch(function (err) { 15 | logger.err('Failed to clear database table: ' + table); 16 | throw err; 17 | }); 18 | } 19 | 20 | /** 21 | * @param {Function} cb 22 | * @param {Number} [retries=10] retries 23 | * @param {Number} [timeout=200] timeout 24 | */ 25 | function waitUntilBlockchainReady (cb, retries, timeout) { 26 | if (!retries) { 27 | retries = 10; 28 | } 29 | if (!timeout) { 30 | timeout = 1000; 31 | } 32 | (function fetchBlockchainStatus () { 33 | node.get('/api/loader/status', function (err, res) { 34 | node.expect(err).to.not.exist; 35 | retries -= 1; 36 | if (!res.body.loaded && retries >= 0) { 37 | return setTimeout(function () { 38 | fetchBlockchainStatus(); 39 | }, timeout); 40 | } else if (res.body.success && res.body.loaded) { 41 | return cb(); 42 | } 43 | return cb('Failed to load blockchain'); 44 | }); 45 | })(); 46 | } 47 | 48 | module.exports = { 49 | clearDatabaseTable: clearDatabaseTable, 50 | waitUntilBlockchainReady: waitUntilBlockchainReady 51 | }; 52 | -------------------------------------------------------------------------------- /test/common/objectStubs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | dummyBlock, 5 | randomPeer, 6 | }; 7 | -------------------------------------------------------------------------------- /test/common/sql/blockRewards.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var BlockRewards = { 4 | getBlockRewards: 'SELECT * FROM getBlockRewards();', 5 | 6 | calcBlockReward: 'SELECT calcBlockReward AS reward FROM calcBlockReward(${height});', 7 | 8 | calcSupply: 'SELECT calcSupply AS supply FROM calcSupply(${height});', 9 | 10 | calcSupply_test: 'SELECT calcSupply_test AS result FROM calcSupply_test(${height_start}, ${height_end}, ${expected_reward});', 11 | 12 | calcBlockReward_test: 'WITH heights AS (SELECT generate_series(${height_start}, ${height_end}) AS height), results AS (SELECT height, ${expected_reward} AS expected_reward, calcBlockReward(height) AS reward FROM heights) SELECT COUNT(1) AS result FROM results WHERE reward <> expected_reward;' 13 | }; 14 | 15 | module.exports = BlockRewards; 16 | -------------------------------------------------------------------------------- /test/common/stubs/account.js: -------------------------------------------------------------------------------- 1 | const accounts = require('../../../helpers/accounts.js') 2 | 3 | // Account, holding 19.6 mln ADM, received from Genesis 4 | const iAccount = { 5 | address: 'U5338684603617333081', 6 | publicKey: '9184c87b846dec0dc4010def579fecf5dad592a59b37a013c7e6975597681f58', 7 | password: 'floor myself rather hidden pepper make isolate vintage review flight century label', 8 | balance: '1960000000000000' 9 | }; 10 | 11 | const nonExistingAccount = { 12 | address: 'U123456789012345678', 13 | publicKey: 'a1234567bcde8f9abcd01e2345fa67bcd8e901f2345a6bc7d89e0123f45abc67' 14 | } 15 | 16 | const validAccount = { 17 | address: 'U777355171330060015', 18 | unconfirmedBalance: '4509718944753', 19 | balance: '4509718944753', 20 | publicKey: 'a9407418dafb3c8aeee28f3263fd55bae0f528a5697a9df0e77e6568b19dfe34', 21 | unconfirmedSignature: 0, 22 | secondSignature: 0, 23 | secondPublicKey: null, 24 | }; 25 | 26 | const testAccount = { 27 | username: 'market', 28 | address: 'U810656636599221322', 29 | publicKey: 'f4011a1360ac2769e066c789acaaeffa9d707690d4d3f6085a7d52756fbc30d0', 30 | secret: 31 | 'rally clean ladder crane gadget century timber jealous shine scorpion beauty salon', 32 | }; 33 | 34 | const testAccountHash = accounts.createPassPhraseHash(testAccount.secret); 35 | const testAccountKeypair = accounts.makeKeypair(testAccountHash); 36 | 37 | const genesisAccount = { 38 | secret: 39 | 'neck want coast appear army smile palm major crumble upper void warm', 40 | publicKey: 'b80bb6459608dcdeb9a98d1f2b0111b2bf11e53ef2933e6769bb0198e3a97aae', 41 | address: 'U15365455923155964650', 42 | }; 43 | 44 | const genesisHash = accounts.createPassPhraseHash(genesisAccount.secret); 45 | const genesisKeypair = accounts.makeKeypair(genesisHash); 46 | 47 | const delegateAccount = { 48 | address: 'U12559234133690317086', 49 | publicKey: 'd365e59c9880bd5d97c78475010eb6d96c7a3949140cda7e667f9513218f9089', 50 | secret: 'weather play vibrant large edge clean notable april fire smoke drift hidden', 51 | u_balance: 10000000000000, 52 | balance: 100000000000000 53 | } 54 | 55 | const delegateAccountHash = accounts.createPassPhraseHash(delegateAccount.secret); 56 | const delegateAccountKeypair = accounts.makeKeypair(delegateAccountHash); 57 | 58 | const nonExistingAddress = 'U1234567890'; 59 | const notAMnemonicPassphrase = 'not a mnemonic passphrase'; 60 | 61 | const invalidPublicKey = 'bd330166898377fb'; 62 | const invalidAddress = 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080'; 63 | 64 | module.exports = { 65 | iAccount, 66 | delegateAccount, 67 | delegateAccountHash, 68 | delegateAccountKeypair, 69 | nonExistingAccount, 70 | nonExistingAddress, 71 | genesisAccount, 72 | genesisHash, 73 | genesisKeypair, 74 | testAccount, 75 | testAccountHash, 76 | testAccountKeypair, 77 | validAccount, 78 | notAMnemonicPassphrase, 79 | invalidPublicKey, 80 | invalidAddress, 81 | }; 82 | -------------------------------------------------------------------------------- /test/common/stubs/delegate.js: -------------------------------------------------------------------------------- 1 | const ed = require('../../../helpers/ed.js'); 2 | 3 | // 'market' delegate 4 | const delegatePassphrase = 'rally clean ladder crane gadget century timber jealous shine scorpion beauty salon'; 5 | const delegateHash = ed.createPassPhraseHash(delegatePassphrase); 6 | const delegateKeyPair = ed.makeKeypair(delegateHash); 7 | 8 | module.exports = { 9 | delegatePassphrase, 10 | delegateHash, 11 | delegateKeyPair 12 | }; 13 | -------------------------------------------------------------------------------- /test/common/stubs/peers.js: -------------------------------------------------------------------------------- 1 | const validPeer = { 2 | broadhash: '198f2b61a8eb95fbeed58b8216780b68f697f26b849acf00c8c93bb9b24f783d', 3 | dappid: null, 4 | height: 1, 5 | ip: '40.40.40.40', 6 | os: 'unknown', 7 | port: 4000, 8 | state: 2, 9 | version: '0.0.0', 10 | }; 11 | 12 | module.exports = { 13 | validPeer, 14 | }; 15 | -------------------------------------------------------------------------------- /test/common/stubs/transactions/chat.js: -------------------------------------------------------------------------------- 1 | const rawValidTransaction = { 2 | srt: 'U15365455923155964650U5338684603617333081', 3 | t_id: '2459326385388619210', 4 | t_senderPublicKey: 5 | '9184c87b846dec0dc4010def579fecf5dad592a59b37a013c7e6975597681f58', 6 | m_recipientPublicKey: 7 | 'b80bb6459608dcdeb9a98d1f2b0111b2bf11e53ef2933e6769bb0198e3a97aae', 8 | t_senderId: 'U5338684603617333081', 9 | t_recipientId: 'U15365455923155964650', 10 | t_timestamp: 226474809, 11 | timestamp: 226474809, 12 | block_timestamp: 226474815, 13 | t_amount: '0', 14 | t_fee: '100000', 15 | c_message: '1451787721dd28b69ec768825b2f9e5473b580347f42', 16 | c_own_message: '543ee6e48b4348439b2d839d5cab876938c7e 42b6f8d9587', 17 | c_type: 1, 18 | t_type: 8, 19 | b_height: 541701, 20 | confirmations: 18, 21 | b_id: '17768103885289794518', 22 | }; 23 | 24 | const validTransactionData = { 25 | message_type: 1, 26 | recipientId: 'U2707535059340134112', 27 | message: 28 | '9ae819297240f00bdc3627133c2e41efd27b022fcd0d011dfdda0941ba08399697f3e3bb5c46a43aff714ae1bac616b84617ce446d808523a14f278e5d88909837848e7aa69d9d4f9a95baae56df6ad4c274248d3d01a2cfccae51367dfab265a055d5ce991af654ee418839f94885876638863d172226b0369cd488c5727e6b1a42ba46fed014c1bf586dd2cab3afe7f10cb54864c099a680d5963778c9c4052df305497edc43082a7d60193650c331c6db9c9d9c0c8bbc004e53ac56586331453164b984c57a495810d709c9b984e4f367888d8a8ce1b26f528c1abdec08747e', 29 | own_message: '6802a9e744aa3ba570d7e48fce5fe0f49184d0ce38ea40f7', 30 | }; 31 | 32 | const validTransaction = { 33 | id: '9175562912139726777', 34 | height: 10288885, 35 | blockId: '10475460465898092643', 36 | type: 8, 37 | block_timestamp: 58773245, 38 | timestamp: 58773228, 39 | senderPublicKey: 40 | '2ac5eef60303003c90f662d89e60570d8661c8ba569e667296f5c7c97a0413ee', 41 | senderId: 'U8916295525136600565', 42 | recipientPublicKey: 43 | '5a3c1da429ae925422892e69dc4f0ab6d7ac00cef229d2d992242dcfeca27b91', 44 | recipientId: 'U2707535059340134112', 45 | fee: 100000, 46 | signature: 47 | '287dc2554025d8074d674d50ec785d530588e2b828f2d3f29687a4f05c8afc623e185896abc739ea2af8db199ec6e31c57426937343ff5ec154341cee8f72f0a', 48 | signatures: [], 49 | confirmations: 32801518, 50 | asset: {}, 51 | }; 52 | 53 | const validUnconfirmedTransaction = { 54 | type: 8, 55 | amount: 0, 56 | senderId: 'U7771441689362721578', 57 | senderPublicKey: 58 | 'e16e624fd0a5123294b448c21f30a07a0435533c693b146b14e66830e4e20404', 59 | asset: { 60 | chat: { 61 | message: '75582d940f2c4093929c99a6c1911b4753', 62 | own_message: '58dceaa227b3fb1dd1c7d3fbf3eb5db6aeb6a03cb7e2ec91', 63 | type: 1, 64 | }, 65 | }, 66 | recipientId: 'U810656636599221322', 67 | timestamp: 63137661, 68 | signature: 69 | 'e25f1aba994c7f07c03099edcbe0ada19df371ddf1a829dae8dee36ab809ce8a438111bf65056c813e9dc832a890a081ba1cd295d37e509f62f042149e62e30d', 70 | id: '8958126469643732641', 71 | fee: 100000, 72 | relays: 1, 73 | receivedAt: '2019-09-03T11:14:22.638Z', 74 | }; 75 | 76 | module.exports = { 77 | rawValidTransaction, 78 | validTransactionData, 79 | validTransaction, 80 | validUnconfirmedTransaction, 81 | }; 82 | -------------------------------------------------------------------------------- /test/common/stubs/transactions/common.js: -------------------------------------------------------------------------------- 1 | const accounts = require('../../../../helpers/accounts.js'); 2 | 3 | const validSender = { 4 | balance: 8067474861277, 5 | u_balance: 8067474861277, 6 | password: 7 | 'rally clean ladder crane gadget century timber jealous shine scorpion beauty salon', 8 | username: 'market', 9 | publicKey: 'f4011a1360ac2769e066c789acaaeffa9d707690d4d3f6085a7d52756fbc30d0', 10 | multimin: 0, 11 | address: 'U810656636599221322', 12 | }; 13 | 14 | const validSenderHash = accounts.createPassPhraseHash(validSender.password); 15 | const validSenderKeyPair = accounts.makeKeypair(validSenderHash); 16 | 17 | const senderDefault = { 18 | username: null, 19 | isDelegate: 0, 20 | secondSignature: 0, 21 | secondPublicKey: null, 22 | vote: 0, 23 | multisignatures: null, 24 | multimin: 0, 25 | multilifetime: 0, 26 | nameexist: 0, 27 | producedblocks: 0, 28 | missedblocks: 0, 29 | fees: 0, 30 | rewards: 0, 31 | virgin: 0, 32 | balance: 0 33 | }; 34 | 35 | module.exports = { 36 | validSender, 37 | validSenderKeyPair, 38 | senderDefault 39 | }; 40 | -------------------------------------------------------------------------------- /test/common/stubs/transactions/delegate.js: -------------------------------------------------------------------------------- 1 | const { validSender } = require('./common.js'); 2 | 3 | const validTransactionData = { 4 | username: 'system', 5 | sender: validSender, 6 | }; 7 | 8 | const validTransaction = { 9 | type: 2, 10 | amount: 0, 11 | fee: 0, 12 | recipientId: null, 13 | timestamp: 0, 14 | asset: {}, 15 | senderId: 'U14384059672307251353', 16 | senderPublicKey: 17 | 'cd67fb7bc27d727636b6fc725aa4a03a4dfcd68990f5aa10c98b8c97dd9ceeae', 18 | signature: 19 | 'b8961823346bb9049536fbb3a5ce36b3e937fdcb80e75b0ae82a26d941663f802139c1991c9c259ff88abe33779400f7580d3564c938684dc38d6d43cd375f0c', 20 | id: '8869103705291559476', 21 | }; 22 | 23 | const validUnconfirmedTransaction = { 24 | type: 2, 25 | timestamp: 0, 26 | amount: 0, 27 | senderPublicKey: 28 | 'f4011a1360ac2769e066c789acaaeffa9d707690d4d3f6085a7d52756fbc30d0', 29 | senderId: 'U810656636599221322', 30 | asset: { 31 | delegate: { 32 | username: 'market', 33 | publicKey: 34 | 'f4011a1360ac2769e066c789acaaeffa9d707690d4d3f6085a7d52756fbc30d0', 35 | }, 36 | }, 37 | signature: 38 | '1a4e3167185346f2cba0be57119670a0c737d63ca9e02ce3ff2a9a9e9dad0cccc53e4ced8c2b8a8fa32ce2e95c08c5e95f68a67b4040e75e339dfda6ed554b0a', 39 | id: '8786873494391552220', 40 | fee: 300000000000, 41 | relays: 1, 42 | receivedAt: '2022-12-16T07:45:53.717Z', 43 | }; 44 | 45 | const rawValidTransaction = { 46 | d_username: 'market', 47 | t_senderPublicKey: 48 | 'f4011a1360ac2769e066c789acaaeffa9d707690d4d3f6085a7d52756fbc30d0', 49 | t_senderId: 'U810656636599221322', 50 | }; 51 | 52 | module.exports = { 53 | validTransaction, 54 | validTransactionData, 55 | validUnconfirmedTransaction, 56 | rawValidTransaction, 57 | }; 58 | -------------------------------------------------------------------------------- /test/common/stubs/transactions/state.js: -------------------------------------------------------------------------------- 1 | 2 | const validTransactionData = { 3 | value: '0x84609a38fedbcd02b657233340e6a8cb09db61a8', 4 | key: 'eth:address', 5 | state_type: 0, 6 | }; 7 | 8 | const validTransaction = { 9 | type: 9, 10 | timestamp: 226647468, 11 | amount: 0, 12 | senderPublicKey: 13 | 'f4011a1360ac2769e066c789acaaeffa9d707690d4d3f6085a7d52756fbc30d0', 14 | senderId: 'U810656636599221322', 15 | asset: {}, 16 | recipientId: null, 17 | signature: 18 | 'e3d569ec587dd0a47ff3c7fffa85506f98f5dd3ce56deb1e1108db3ac6c3c77c404f399cb8d1d712cbceb82e83fe8c9c818e76e3e2734d1f821b78496af91904', 19 | height: 6361977, 20 | blockId: '14557933175886918347', 21 | block_timestamp: 39015790, 22 | timestamp: 39015780, 23 | requesterPublicKey: null, 24 | recipientPublicKey: null, 25 | fee: 100000, 26 | signSignature: null, 27 | signatures: [], 28 | confirmations: null, 29 | asset: {}, 30 | }; 31 | 32 | const rawValidTransaction = { 33 | st_stored_value: '0x84609a38fedbcd02b657233340e6a8cb09db61a8', 34 | st_stored_key: 'eth:address', 35 | st_type: 0, 36 | }; 37 | 38 | module.exports = { 39 | validTransactionData, 40 | validTransaction, 41 | rawValidTransaction 42 | } 43 | -------------------------------------------------------------------------------- /test/common/stubs/transactions/transfer.js: -------------------------------------------------------------------------------- 1 | const { delegateAccountKeypair, delegateAccount } = require('../account'); 2 | 3 | const validTransactionData = { 4 | type: 0, 5 | amount: 8067474861277, 6 | keypair: delegateAccountKeypair, 7 | sender: delegateAccount, 8 | senderId: delegateAccount.address, 9 | senderPublicKey: delegateAccount.publicKey, 10 | recipientId: 'U7771441689362721578', 11 | fee: 50000000, 12 | timestamp: 1000, 13 | }; 14 | 15 | const validTransaction = { 16 | id: '17190511997607511181', 17 | blockId: '6438017970172540087', 18 | type: 0, 19 | block_timestamp: null, 20 | timestamp: 0, 21 | timestampMs: 0, 22 | senderPublicKey: 23 | 'b80bb6459608dcdeb9a98d1f2b0111b2bf11e53ef2933e6769bb0198e3a97aae', 24 | senderId: 'U15365455923155964650', 25 | recipientId: 'U9781760580710719871', 26 | amount: 490000000000000, 27 | fee: 0, 28 | signature: 29 | '85dc703a2b82698193ecbd86fd7aff1b057dfeb86e2a390ef42c1998bf1e9269c0048f42285e208a1e14a63843defbabece1bc96730f317f0cc16e23bb1b4d01', 30 | signatures: [], 31 | asset: {}, 32 | }; 33 | 34 | const rawValidTransaction = { 35 | t_id: '17190511997607511181', 36 | b_height: 981, 37 | t_blockId: '6438017970172540087', 38 | t_type: 0, 39 | t_timestamp: 33363661, 40 | t_senderPublicKey: 41 | 'c094ebee7ec0c50ebee32918655e089f6e1a604b83bcaa760293c61e0f18ab6f', 42 | m_recipientPublicKey: null, 43 | t_senderId: 'U810656636599221322', 44 | t_recipientId: 'U7771441689362721578', 45 | t_amount: 490000000000000, 46 | t_fee: 0, 47 | t_signature: 48 | '85dc703a2b82698193ecbd86fd7aff1b057dfeb86e2a390ef42c1998bf1e9269c0048f42285e208a1e14a63843defbabece1bc96730f317f0cc16e23bb1b4d01', 49 | confirmations: 8343, 50 | }; 51 | 52 | const validUnconfirmedTransaction = { 53 | type: 0, 54 | amount: 100, 55 | senderId: delegateAccount.address, 56 | senderPublicKey: delegateAccount.publicKey, 57 | recipientId: 'U7771441689362721578', 58 | fee: 50000000, 59 | timestamp: 1000, 60 | timestampMs: 1000000, 61 | asset: {} 62 | }; 63 | 64 | module.exports = { 65 | validUnconfirmedTransaction, 66 | rawValidTransaction, 67 | validTransaction, 68 | validTransactionData, 69 | }; 70 | -------------------------------------------------------------------------------- /test/common/stubs/transactions/vote.js: -------------------------------------------------------------------------------- 1 | const { testAccount, testAccountKeypair } = require('../account.js'); 2 | 3 | const transactionVotes = [ 4 | '-9d3058175acab969f41ad9b86f7a2926c74258670fe56b37c429c01fca9f2f0f', 5 | ]; 6 | 7 | const validTransactionData = { 8 | type: 3, 9 | amount: 8067474861277, 10 | sender: testAccount, 11 | senderId: 'U810656636599221322', 12 | fee: 10000000, 13 | keypair: testAccountKeypair, 14 | publicKey: 'f4011a1360ac2769e066c789acaaeffa9d707690d4d3f6085a7d52756fbc30d0', 15 | votes: transactionVotes, 16 | }; 17 | 18 | const validTransaction = { 19 | type: 3, 20 | amount: 0, 21 | senderPublicKey: 22 | 'f4011a1360ac2769e066c789acaaeffa9d707690d4d3f6085a7d52756fbc30d0', 23 | requesterPublicKey: null, 24 | timestamp: 34253582, 25 | asset: { 26 | votes: [ 27 | '-9d3058175acab969f41ad9b86f7a2926c74258670fe56b37c429c01fca9f2f0f', 28 | ], 29 | }, 30 | data: undefined, 31 | recipientId: 'U810656636599221322', 32 | signature: 33 | 'de668e2722fbc2fd02bac1bb66ff1238d75354f64ca0adc5b1967f5f4e67038336cee6a85af43ed9fa5f3a091890738de14c857bd7b1f9bade7ff1da1c395a0e', 34 | id: '5962289265698105102', 35 | fee: 100000000, 36 | senderId: 'U810656636599221322', 37 | }; 38 | 39 | const existedDelegateKey = 40 | '81dd616f47bda681c929b9035aa1cbc9c41ba9d4af91f04744d1325e1b1af099'; 41 | const invalidDelegateKey = 42 | 'f4011a1360ac2769e066c789acaaeffa9d707690d4d3f6085a7d52756fbc30fg'; 43 | 44 | module.exports = { 45 | transactionVotes, 46 | validTransactionData, 47 | validTransaction, 48 | existedDelegateKey, 49 | invalidDelegateKey, 50 | }; 51 | -------------------------------------------------------------------------------- /test/common/utils.js: -------------------------------------------------------------------------------- 1 | const Mnemonic = require('bitcore-mnemonic'); 2 | const accounts = require('../../helpers/accounts.js'); 3 | 4 | exports.randomAccount = function () { 5 | const account = { 6 | balance: '1000' 7 | }; 8 | 9 | const passphrase = new Mnemonic(Mnemonic.Words.ENGLISH).toString() 10 | const keypair = accounts.makeKeypair(accounts.createPassPhraseHash(account.password)); 11 | 12 | account.password = passphrase; 13 | account.publicKey = keypair.publicKey; 14 | account.publicKeyHex = keypair.publicKey.toString('hex'); 15 | account.address = accounts.getAddressByPublicKey(account.publicKey); 16 | account.keypair = keypair; 17 | 18 | return account; 19 | }; 20 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | require('./unit/helpers/request-limiter.js'); 2 | require('./unit/logic/blockReward.js'); 3 | require('./unit/sql/blockRewards.js'); 4 | require('./unit/modules/peers.js'); 5 | require('./unit/modules/blocks.js'); 6 | require('./unit/modules/blocks/verify.js'); 7 | 8 | require('./api/accounts'); 9 | require('./api/blocks'); 10 | require('./api/dapps'); 11 | require('./api/delegates'); 12 | require('./api/loader'); 13 | require('./api/multisignatures'); 14 | require('./api/peer'); 15 | require('./api/peer.transactions.main'); 16 | require('./api/peer.transactions.collision'); 17 | require('./api/peer.transactions.delegates'); 18 | require('./api/peer.transactions.multisignatures'); 19 | require('./api/peer.transactions.signatures'); 20 | require('./api/peer.transactions.votes'); 21 | require('./api/peers'); 22 | require('./api/signatures'); 23 | require('./api/transactions'); 24 | -------------------------------------------------------------------------------- /test/unit/helpers/z_schema.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const z_schema = require('../../../helpers/z_schema.js'); 3 | 4 | const test = it; 5 | 6 | describe('Schema formats', () => { 7 | /** 8 | * @type {z_schema} 9 | */ 10 | let validator; 11 | 12 | beforeEach(() => { 13 | validator = new z_schema(); 14 | }); 15 | 16 | function expectFormatToBeRegistered(formatName) { 17 | const registeredFormats = z_schema.getRegisteredFormats(); 18 | expect(registeredFormats).to.include(formatName); 19 | } 20 | 21 | describe('version', () => { 22 | it('should be registered', () => 23 | expectFormatToBeRegistered('version'), 24 | ); 25 | 26 | const schema = { 27 | id: 'test.schema', 28 | type: 'object', 29 | properties: { 30 | version: { 31 | type: 'string', 32 | format: 'version', 33 | }, 34 | }, 35 | required: ['version'], 36 | }; 37 | 38 | describe('should pass for valid versions', () => { 39 | const validVersions = [ 40 | '', 41 | '0.8.3', 42 | '0.6.0', 43 | '0.8.4-dev.0', 44 | '1.0.0', 45 | '1.2.3-beta', 46 | '2.1.0-alpha.1', 47 | '3.3.3-rc.2', 48 | '4.0.0+build.123', 49 | '1.0.0-alpha+001', 50 | ]; 51 | 52 | validVersions.forEach((version) => { 53 | test(`"${version}"`, () => { 54 | const isValid = validator.validate({ version }, schema); 55 | expect(isValid).to.be.true; 56 | }); 57 | }); 58 | }); 59 | 60 | describe('should fail for invalid versions', () => { 61 | const invalidVersions = [ 62 | '0..1', 63 | '.1.2', 64 | '1.2.3.4', 65 | '1.2.-3', 66 | '1.2.3-rc.01.1', 67 | 'version1.2.3', 68 | '1.0', 69 | '01.2.3', 70 | '1.2.3+', 71 | '1.2.3+build.@@@', 72 | ]; 73 | 74 | invalidVersions.forEach((version) => { 75 | test(`"${version}"`, () => { 76 | const isValid = validator.validate({ version }, schema); 77 | expect(isValid).to.be.false; 78 | }); 79 | }); 80 | }); 81 | 82 | describe('should fail miserably for invalid types', () => { 83 | const invalidTypes = [ 84 | null, 85 | undefined, 86 | 123, 87 | 0.8, 88 | true, 89 | false, 90 | {}, 91 | [], 92 | ]; 93 | 94 | invalidTypes.forEach((type) => { 95 | test(`${JSON.stringify(type)}`, () => { 96 | const isValid = validator.validate({ version: type }, schema); 97 | expect(isValid).to.be.false; 98 | }); 99 | }); 100 | }); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /test/unit/modules/node.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { expect } = require('chai'); 4 | const semver = require('semver'); 5 | 6 | const { modulesLoader } = require('../../common/initModule.js'); 7 | const { isHex } = require('../../common/assert.js'); 8 | 9 | const Node = require('../../../modules/node.js'); 10 | const constants = require('../../../helpers/constants.js'); 11 | 12 | describe('node', function () { 13 | /** 14 | * @type {Node} 15 | */ 16 | let nodeModule; 17 | let modules; 18 | 19 | const dummyBlock = { 20 | id: '9314232245035524467', 21 | height: 1, 22 | timestamp: 0, 23 | }; 24 | 25 | const library = { 26 | lastCommit: '07757855c143e69e417da6e3918e0e57a3dd1864', 27 | build: '', 28 | }; 29 | 30 | before(function (done) { 31 | modulesLoader.initAllModules((err, __modules) => { 32 | if (err) { 33 | return done(err); 34 | } 35 | 36 | const blocks = __modules.blocks; 37 | blocks.lastBlock.set(dummyBlock); 38 | 39 | modules = __modules; 40 | 41 | const scope = { 42 | ...modulesLoader.scope, 43 | ...library, 44 | }; 45 | 46 | modulesLoader.initModuleWithDb( 47 | Node, 48 | (err, module) => { 49 | if (err) { 50 | return done(err); 51 | } 52 | 53 | nodeModule = module; 54 | done(); 55 | }, 56 | scope 57 | ); 58 | }); 59 | }); 60 | 61 | describe('isLoaded()', () => { 62 | it('should return false before delegates.onBind() was called', (done) => { 63 | expect(nodeModule.isLoaded()).to.be.false; 64 | done(); 65 | }); 66 | }); 67 | 68 | describe('onBind()', () => { 69 | it('should initialize modules', (done) => { 70 | nodeModule.onBind(modules); 71 | expect(nodeModule.isLoaded()).to.be.true; 72 | done(); 73 | }); 74 | }); 75 | 76 | describe('shared', () => { 77 | describe('getStatus()', () => { 78 | it('should return valid node status', (done) => { 79 | nodeModule.shared.getStatus({}, (err, response) => { 80 | expect(err).not.to.exist; 81 | const keys = ['loader', 'network', 'version', 'wsClient']; 82 | expect(response).to.have.keys(keys); 83 | 84 | const loaderKeys = [ 85 | 'loaded', 86 | 'now', 87 | 'syncing', 88 | 'consensus', 89 | 'blocks', 90 | 'blocksCount', 91 | ]; 92 | expect(response.loader).to.be.an('object').that.has.keys(loaderKeys); 93 | 94 | expect(isHex(response.network.broadhash)).to.be.true; 95 | 96 | expect(response.network.epoch).to.equal(constants.epochTime); 97 | expect(response.network.height).to.equal(dummyBlock.height); 98 | expect(response.network.fee).to.be.greaterThan(0); 99 | expect(response.network.milestone).to.satisfy(Number.isInteger); 100 | 101 | const { nethash } = modulesLoader.scope.config; 102 | expect(response.network.nethash).to.equal(nethash); 103 | 104 | expect(response.network.reward).to.satisfy(Number.isInteger); 105 | expect(response.network.supply).to.satisfy(Number.isInteger); 106 | 107 | const versionKeys = ['build', 'commit', 'version']; 108 | expect(response.version).to.have.all.keys(versionKeys); 109 | expect(response.version.commit).to.equal(library.lastCommit); 110 | expect(response.version.build).to.equal(library.build); 111 | 112 | expect(semver.valid(response.version.version)).not.to.be.null; 113 | 114 | done(); 115 | }); 116 | }); 117 | }); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /test/unit/modules/rounds.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { expect } = require('chai'); 4 | var express = require('express'); 5 | var _ = require('lodash'); 6 | var Rounds = require('../../../modules/rounds.js'); 7 | var modulesLoader = require('../../common/initModule').modulesLoader; 8 | 9 | describe('rounds', function () { 10 | var rounds; 11 | 12 | before(function (done) { 13 | modulesLoader.initModuleWithDb(Rounds, function (err, __rounds) { 14 | if (err) { 15 | return done(err); 16 | } 17 | rounds = __rounds; 18 | done(); 19 | }); 20 | }); 21 | 22 | describe('calc', function () { 23 | it('should calculate round number from given block height', function () { 24 | expect(rounds.calc(100)).equal(1); 25 | expect(rounds.calc(200)).equal(2); 26 | expect(rounds.calc(303)).equal(3); 27 | expect(rounds.calc(304)).equal(4); 28 | }); 29 | 30 | it('should calculate round number from Number.MAX_VALUE', function () { 31 | var res = rounds.calc(Number.MAX_VALUE); 32 | expect(_.isNumber(res)).to.be.true; 33 | expect(res).to.be.below(Number.MAX_VALUE); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/unit/schema/dapp.js: -------------------------------------------------------------------------------- 1 | var ZSchema = require('../../../helpers/z_schema.js'); 2 | var schema = require('../../../schema/dapps.js'); 3 | var expect = require('chai').expect; 4 | 5 | var validator = new ZSchema(); 6 | 7 | describe('dapp', function () { 8 | // TODO: Add tests for other dapps schemas 9 | 10 | /* 11 | describe('put', function () { 12 | it('tests for schema'); 13 | }); 14 | 15 | describe('get', function () { 16 | it('tests for schema'); 17 | }); 18 | 19 | describe('list', function () { 20 | it('tests for schema'); 21 | }); 22 | 23 | describe('addTransactions', function () { 24 | it('tests for schema'); 25 | }); 26 | 27 | describe('sendWithdrawal', function () { 28 | it('tests for schema'); 29 | }); 30 | 31 | describe('search', function () { 32 | it('tests for schema'); 33 | }); 34 | 35 | describe('install', function () { 36 | it('tests for schema'); 37 | }); 38 | 39 | describe('uninstall', function () { 40 | it('tests for schema'); 41 | }); 42 | 43 | describe('stop', function () { 44 | it('tests for schema'); 45 | }); 46 | 47 | */ 48 | 49 | describe('launch', function () { 50 | var testBody; 51 | 52 | beforeEach(function () { 53 | testBody = { 54 | params: ['-x', 'localhost'], 55 | id: '1465651642158264047', 56 | master: 'pluto' 57 | }; 58 | }); 59 | 60 | it('should return error when params field is not an array', function () { 61 | testBody.params = ''; 62 | validator.validate(testBody, schema.launch); 63 | expect(validator.getLastErrors().map(function (e) { 64 | return e.message; 65 | })).to.eql(['Expected type array but found type string']); 66 | }); 67 | 68 | it('should return error when params field length is less than minimum length', function () { 69 | testBody.params = []; 70 | validator.validate(testBody, schema.launch); 71 | expect(validator.getLastErrors().map(function (e) { 72 | return e.message; 73 | })).to.eql(['Array is too short (0), minimum 1']); 74 | }); 75 | 76 | it('should be ok when params field length valid', function () { 77 | validator.validate(testBody, schema.launch); 78 | expect(validator.getLastErrors()).to.not.exist; 79 | }); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /test/unit/schema/multisignatures.js: -------------------------------------------------------------------------------- 1 | const { randomAccount } = require('../../common/utils.js'); 2 | 3 | var ZSchema = require('../../../helpers/z_schema.js'); 4 | var schema = require('../../../schema/multisignatures.js'); 5 | var expect = require('chai').expect; 6 | 7 | var validator = new ZSchema(); 8 | 9 | describe('multisignatures', function () { 10 | // TODO: Add tests for other multisignature schemas 11 | 12 | /* 13 | describe('getAccounts', function () { 14 | it('tests for schema'); 15 | }); 16 | 17 | describe('pending', function () { 18 | it('tests for schema'); 19 | }); 20 | 21 | describe('sign', function () { 22 | it('tests for schema'); 23 | }); 24 | */ 25 | 26 | describe('addMultisignatures', function () { 27 | var testBody; 28 | 29 | beforeEach(function () { 30 | var account = randomAccount(); 31 | testBody = { 32 | secret: account.password, 33 | publicKey: account.publicKeyHex, 34 | min: 2, 35 | lifetime: 1, 36 | keysgroup: Array.apply(null, Array(4)).map(function () { return '+' + randomAccount().publicKey; }) 37 | }; 38 | }); 39 | 40 | describe('min', function () { 41 | it('should return error when min is not an integer', function () { 42 | testBody.min = ''; 43 | validator.validate(testBody, schema.addMultisignature); 44 | expect(validator.getLastErrors().map(function (e) { 45 | return e.message; 46 | })).to.eql(['Expected type integer but found type string']); 47 | }); 48 | 49 | it('should return error when min value is less than acceptable value', function () { 50 | testBody.min = 0; 51 | validator.validate(testBody, schema.addMultisignature); 52 | expect(validator.getLastErrors().map(function (e) { 53 | return e.message; 54 | })).to.eql(['Value 0 is less than minimum 1']); 55 | }); 56 | 57 | it('should return error when min value is greater than acceptable value', function () { 58 | testBody.min = 16; 59 | validator.validate(testBody, schema.addMultisignature); 60 | expect(validator.getLastErrors().map(function (e) { 61 | return e.message; 62 | })).to.eql(['Value 16 is greater than maximum 15']); 63 | }); 64 | }); 65 | 66 | describe('keysgroup', function () { 67 | it('should return error when keysgroup is not an array', function () { 68 | testBody.keysgroup = ''; 69 | validator.validate(testBody, schema.addMultisignature); 70 | expect(validator.getLastErrors().map(function (e) { 71 | return e.message; 72 | })).to.eql(['Expected type array but found type string']); 73 | }); 74 | 75 | it('should return error when keysgroup length is less than minimum acceptable length', function () { 76 | testBody.keysgroup = []; 77 | validator.validate(testBody, schema.addMultisignature); 78 | expect(validator.getLastErrors().map(function (e) { 79 | return e.message; 80 | })).to.eql(['Array is too short (0), minimum 1']); 81 | }); 82 | 83 | it('should return error when keysgroup length is greater than maximum acceptable length', function () { 84 | testBody.keysgroup = Array.apply(null, Array(16)).map(function () { return randomAccount().publicKey; }); 85 | validator.validate(testBody, schema.addMultisignature); 86 | expect(validator.getLastErrors().map(function (e) { 87 | return e.message; 88 | })).to.eql(['Array is too long (16), maximum 15']); 89 | }); 90 | }); 91 | 92 | it('should be ok when params field length valid', function () { 93 | validator.validate(testBody, schema.addMultisignature); 94 | expect(validator.getLastErrors()).to.not.exist; 95 | }); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /tools/fix_node.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | printf "\n" 4 | printf "Greetings!\n" 5 | printf "Ensure you got this file from the adamant.im website or GitHub.\n" 6 | printf "Use this node repair tool v1.0 if your ADM node lost the current blockchain height and restarted, rising from the beginning.\n" 7 | printf "Though validating blocks from 0 height is a decent option, catching up with the current height may take time.\n" 8 | printf "If your node is a forging delegate, you probably prefer using an up-to-date blockchain image and enabling it back in ten minutes.\n" 9 | printf "This script deletes the ADM blockchain database, downloads its fresh image, and restarts the node.\n" 10 | printf "We still recommend consulting an IT specialist if you are unfamiliar with Linux systems.\n" 11 | printf "Alternatively, follow these steps manually. Also, see full node installation instructions at https://news.adamant.im/how-to-run-your-adamant-node-on-ubuntu-990e391e8fcc.\n\n" 12 | 13 | read -r -p "WARNING! Use the script only if you initially set up the node using the ADAMANT node installer, as it expects a specific server environment. Run it under the root user. If you agree to continue, type \"yes\": " agreement 14 | if [[ $agreement != "yes" ]] 15 | then 16 | printf "\nExecution cancelled.\n\n" 17 | exit 1 18 | fi 19 | 20 | printf "\n\n" 21 | 22 | network="mainnet" 23 | port=36666 #Default, re-assign later 24 | username="adamant" 25 | databasename="adamant_main" 26 | 27 | #Users 28 | if [ "$(id -u)" -ne 0 ]; then 29 | printf "Run the script under a user with sudo permission as it modifies the ADM Postgres database." 30 | printf "\nExecution cancelled.\n\n" 31 | exit 1 32 | fi 33 | 34 | if ! id "$username" &>/dev/null; then 35 | printf "System user named '%s' is not found. Use the script only if you initially set up the node using the ADAMANT node installer, as it expects a specific server environment." "$username" 36 | printf "\nExecution cancelled.\n\n" 37 | exit 1 38 | fi 39 | 40 | #Stop adamant node 41 | su - adamant -c "source ~/.nvm/nvm.sh; pm2 stop adamant" 42 | 43 | #Postgres 44 | printf "\n\nDeleting and recreating database '%s'…\n\n" "$databasename" 45 | sudo -u postgres psql -c "DROP DATABASE ${databasename};" 46 | sudo -u postgres psql -c "CREATE DATABASE ${databasename};" 47 | sudo -u postgres psql -c "ALTER DATABASE ${databasename} OWNER TO ${username};" 48 | 49 | #Run next commands as user 50 | su - "$username" <