├── .dockerignore ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── .prettierrc ├── CONTRIBUTING.md ├── Dockerfile ├── README.md ├── README.rus.md ├── baseFactory.js ├── conciliums ├── baseConciliumDefinition.js ├── conciliumPoS.js └── conciliumRr.js ├── config ├── devel.conf.js ├── prod.conf.js └── test.conf.js ├── contributing.md ├── crypto └── crypto.js ├── db ├── .bannedBlocks.json └── .gitkeep ├── factory.js ├── hookSetup.sh ├── index.js ├── messages ├── includes │ └── peerInfo.js ├── index.js ├── msgCommon.js ├── node │ ├── msgAddr.js │ ├── msgBlock.js │ ├── msgGetBlocks.js │ ├── msgGetData.js │ ├── msgInv.js │ ├── msgReject.js │ ├── msgTx.js │ └── msgVersion.js └── witness │ ├── msgWitnessBlock.js │ ├── msgWitnessBlockVote.js │ ├── msgWitnessCommon.js │ ├── msgWitnessExpose.js │ └── msgWitnessNextRound.js ├── network ├── ipv6Connection.js ├── ipv6Transport.js ├── messageAssembler.js ├── peer.js ├── peerManager.js ├── publicAddresses.json ├── serializer.js ├── testConnection.js └── testTransport.js ├── node ├── app.js ├── bftConsensus.js ├── mainDag.js ├── mempool.js ├── node.js ├── pendingBlocksManager.js ├── requestsCache.js ├── rpc.js ├── storedWallets.js ├── wallet.js └── witness.js ├── package-lock.json ├── package.json ├── proto ├── network.proto ├── predefinedClasses.js ├── structures.proto └── witness.proto ├── runScripts ├── auto.restart.sh ├── joinConcilium │ └── joinConcilium.sh ├── node.devel │ ├── run.node.sh │ └── sample.node.env ├── node.rpc │ ├── run.node.sh │ └── sample.node.env ├── node │ ├── run.node.sh │ └── sample.node.env ├── witness.devel │ ├── run.dev.witness.sh │ ├── sample.pk │ └── sample.witness.env └── witness │ ├── run.witness.no.update.sh │ ├── run.witness.sh │ ├── sample.pk │ └── sample.witness.env ├── scripts ├── decryptKeyStore.js ├── joinConcilium.js ├── leaveConcilium.js ├── nodeStatus.js └── savePrivateKey.js ├── storage ├── patch.js ├── persistentStorage.js └── storageWithCache.js ├── structures ├── arrayOf.js ├── block.js ├── blockInfo.js ├── coins.js ├── contract.js ├── inventory.js ├── transaction.js ├── txReceipt.js └── utxo.js ├── tests ├── integration │ ├── bft.integration.spec.js │ ├── contract.integration.spec.js │ ├── genesis.integration.pos.2witness.spec.js │ ├── genesis.integration.proxy.contract.spec.js │ ├── genesis.integration.spec.js │ ├── genesisIp6.integration.spec.js │ ├── node.integration.spec.js │ ├── peerSameHost.integration.spec.js │ └── witness.integration.spec.js ├── testFactory.js ├── testFactoryIpV6.js ├── testUtil.js └── unit │ ├── app.spec.js │ ├── arrayOfAddresses.spec.js │ ├── arrayOfHashes.spec.js │ ├── block.spec.js │ ├── blockInfo.spec.js │ ├── btf.general.spec.js │ ├── coins.spec.js │ ├── concilium-contracts │ ├── conciliumContract.js │ └── conciliumContract.spec.js │ ├── concilium.spec.js │ ├── contract.spec.js │ ├── crypto.spec.js │ ├── inventory.spec.js │ ├── mainDag.spec.js │ ├── mempool.spec.js │ ├── messageAssembler.spec.js │ ├── messages │ ├── messageAddr.spec.js │ ├── messageBlock.spec.js │ ├── messageCommon.spec.js │ ├── messageGetBlocks.spec.js │ ├── messageGetData.spec.js │ ├── messageInventory.spec.js │ ├── messageTx.spec.js │ ├── messageVersion.spec.js │ ├── messageWitnessBlock.spec.js │ ├── messageWitnessBlockVote.spec.js │ ├── messageWitnessCommon.spec.js │ ├── peerInfo.spec.js │ ├── witnessExpose.spec.js │ └── witnessNextRound.spec.js │ ├── node.spec.js │ ├── offer-contract │ ├── offerContract.js │ └── offerContract.spec.js │ ├── oracle │ ├── oracle.js │ └── oracleContract.spec.js │ ├── patch.spec.js │ ├── peer.spec.js │ ├── peerManager.spec.js │ ├── pendingBlocks.spec.js │ ├── requestCache.spec.js │ ├── rpc.spec.js │ ├── serializer.spec.js │ ├── storage.spec.js │ ├── storedWallets.spec.js │ ├── testTransport.spec.js │ ├── token10 │ ├── token10.js │ └── token10.spec.js │ ├── transaction.spec.js │ ├── transport.spec.js │ ├── txReceipt.spec.js │ ├── utils.spec.js │ ├── utxo.spec.js │ ├── wallet.spec.js │ └── witness.spec.js ├── types.js └── utils.js /.dockerignore: -------------------------------------------------------------------------------- 1 | **/*.tgz 2 | **/LAW* 3 | **/prod* 4 | **/private* 5 | !config/prod.conf.js 6 | !**/private*.js 7 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | proto/predefinedClasses.js 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "node": true, 7 | "mocha": true 8 | }, 9 | "extends": ["eslint:recommended", "prettier"], 10 | "plugins": ["prettier"], 11 | "parserOptions": { 12 | "ecmaVersion": 12 13 | }, 14 | "rules": { 15 | "prettier/prettier": ["error"], 16 | "linebreak-style": ["error", "unix"], 17 | "no-prototype-builtins": "off", 18 | "no-async-promise-executor": "off", 19 | "no-useless-escape": "off", 20 | "no-empty": ["error", { "allowEmptyCatch": true }] 21 | }, 22 | "globals": { 23 | "logger": true, 24 | "callerAddress": true, 25 | "block": true, 26 | "contractTx": true, 27 | "delegatecall": true, 28 | "value": true, 29 | "send": true 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | .vscode 4 | .jscsrc 5 | prod.* 6 | !config/prod.conf.js 7 | 8 | private.* 9 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npm run lint 5 | npm run check 6 | npm run unit 7 | 8 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package*.json 2 | .eslintrc 3 | .prettierrc 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "tabWidth": 4, 4 | "bracketSpacing": false, 5 | "printWidth": 120, 6 | "trailingComma": "none", 7 | "arrowParens": "avoid" 8 | } 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | We are glad to welcome all NodeJS developers who want to improve their professional skills. 2 | The Ubikiri project is looking for active NodeJS contributors willing to support and develop our project. 3 | The Ubikiri platform is made for business and professional people who are ready to implement the most ambitious business ideas and concepts. Our distributed social network can give the possibility to enter into contracts, confirm information of any type. It allows you to adjust the amount of information provided about yourself for each transaction. 4 | Our platform Chain-in-law is an OPEN-SORCE project. CiL (Chain-in-Law) is a directed acyclic graph (DAG) and has great advantages in terms of speed and scalability. The platform is fully public and open. The code is available to any member on our GitHub page (https://github.com/SilentNotaryEcosystem/Cil-core). 5 | You can use the code placed there in open access to create your own decentralized applications, as well as to develop segments of the platform itself. 6 | Please note that the platform code is written in Node JS, a JavaScript smart contract language with some limitations. In fact, our proposal is unique for developers working with these languages. The project alone will enable JS developers to get into the "heart" of the blockchain and crypto technologies without changing the programming language. 7 | 8 | 1. You have the opportunity to “look under the hood” of a really unique project built on the blockchain Chain-in-Law (CiL) and written on Node JS. 9 | 10 | - Ubikiri is an expansion of horizons for advanced developers. 11 | - For beginners - this is a huge new layer of knowledge and experience. 12 | 13 | 2. You can have full freedom of action. 14 | 15 | - You can work and decompose by solving problems according to our sections. 16 | - At the same time, all your wildest ideas, interesting and useful software solutions will be considered and rewarded. 17 | 18 | 3. We are ready for cooperation at different levels: 19 | 20 | - Full-time - you understand the project from the inside and you are ready to solve complex and non-standard tasks. The project developers assess your qualification, we make you an offer and add motivation. 21 | - Part-time - you take different "parts" of tasks and give out "pieces" of their solutions. 22 | - Hackathon format - several developers do a collective task and offer a solution. 23 | 24 | 4. The payment for contributions are made in the tokens of the project Ubikiri (CiL). There is a possibility of their swapping to the tokens of the basic Silent Notary project (SNTR), which was stable during the period of a general fall in the market and is listed in the main cryptoexchanges. Our tokens has icreased by more than a double-digit percentage over the last month. We are in the post-ICO period and continue to grow rapidly. The project has been working for more than a year, we have already working platforms and a name on the market - the reliability and prospects of the token are confirmed by a large and active community of holders and traders that are working with the crypto component of the project. 25 | 26 | 5. You also have the opportunity among the first to "deploy" your node/masternode for receiving passive income in the process of our blockchain Chain-in-Law functioning. This will require a certain number of tokens, which is quite possible to purchase or earn under this program. 27 | Here is the list of so-called "work sections": 28 | 29 | - The finalization of the language of smart contracts (this is either JS with AST or webassembly). 30 | - Thr improvement of the network stability mechanisms: the consilium participants or the whole consilium "punishment" for misbehave. 31 | - The additional rules of creation/validation for consiliums (this is PoS, PoW, etc.). 32 | - The security audit. The network vulnerability to various attacks. 33 | - The dependencies audit (the packages used in the project for vulnerabilities. The using packages with fewer dependencies). 34 | - The aggregated signatures under the block. The evaluation of the creating blocks mechanism of vulnerability to fingerprinting (many signatures from witnesses under the blocks - will it be possible to restore the witnesses' private keys with these signatures)? 35 | - The sharding in order to increase the number of transactions processed by the network. 36 | 37 | Our project Github: 38 | https://github.com/SilentNotaryEcosystem/Cil-core 39 | All questions can be answered by us in the open Telegram chat, JOIN: https://t.me/cil_contributors 40 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nikolaik/python-nodejs:python3.10-nodejs16 2 | 3 | STOPSIGNAL SIGTERM 4 | 5 | RUN mkdir /app/ 6 | WORKDIR /app/ 7 | 8 | COPY . /app/ 9 | RUN npm install 10 | 11 | SHELL ["/bin/bash"] 12 | CMD runScripts/auto.restart.sh 13 | 14 | EXPOSE 8222 15 | EXPOSE 18222 16 | EXPOSE 8223 17 | EXPOSE 18223 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [Russian version of document](README.rus.md) 2 | 3 | ## The installation process (Linux, macOS, Windows): 4 | 5 | #### 1. Git clone the project 6 | 7 | ``` 8 | git clone https://github.com/SilentNotaryEcosystem/Cil-core.git 9 | cd Cil-core 10 | git checkout tags/latest 11 | ``` 12 | 13 | #### 2. Setup [Node.js (16.17.0) and npm](https://nodejs.org/dist/v16.17.0/node-v16.17.0.pkg) 14 | 15 | #### 3. Setup dependencies and run a Node.js App 16 | 17 | ``` 18 | npm install 19 | npm run husky // optional. only if you want to contribute the project install git hooks for dev mode 20 | node index.js // node start 21 | node savePrivateKey.js // write private key to file (keystore analog) 22 | ``` 23 | 24 | ## The installation process (Docker): 25 | 26 | setup docker first [manual for Digical Ocean](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-18-04) 27 | then 28 | 29 | ``` 30 | sudo docker pull trueshura/cil-core-prod 31 | ``` 32 | 33 | then download & untar helper scripts 34 | 35 | ```$xslt 36 | wget -t0 -c https://github.com/SilentNotaryEcosystem/Cil-core/releases/download/v0.2.0-staging/docker-scripts.tgz 37 | tar fxz docker-scripts.tgz 38 | ``` 39 | 40 | pick desired scenario & run script from corresponding directory 41 | 42 | ## Settings for launch 43 | 44 | The default options are set in file [prod.conf.js](https://github.com/SilentNotaryEcosystem/Cil-core/blob/devel/config/prod.conf.js) (for production net) and [devel.conf.js](https://github.com/SilentNotaryEcosystem/Cil-core/blob/devel/config/devel.conf.js) (for development net). 45 | 46 | | Parameter | Description | 47 | | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | 48 | | listenAddr | URL | 49 | | listenPort | Use specific port (instead of predefined) | 50 | | seedAddr | Seed address to run Node | 51 | | rpcUser | Username used to call the functions from Node | 52 | | rpcPass | Password used to call the functions from Node | 53 | | rpcPort | Port used to call the functions from Node | 54 | | rpcAddress | Address used to call the functions from Node | 55 | | genesisHash | The genesis block's hash to set up a test environment | 56 | | conciliumDefContract | The genesis block's contract to set up a test environment | 57 | | privateKey | Private key file to run a witness node | 58 | | dbPath | Directory for storing database files | 59 | | seed | Running node as a seed (It will store and distribute the addresses of those who are connected to it (peers)) | 60 | | strictAddresses | Source address from tcp connection should match address advertised via MSG_VERSION | 61 | | trustAnnounce | Use MSG_VERSION to determine node address | 62 | | txIndex | Function used to get transaction index by its hash | 63 | | watchAddress | Function used to operate with local wallets. Used for adding wallet address to Node to track all incoming and outgoing transactions | 64 | | reIndexWallet | Function used to operate with old wallets. Used to receive all transactions in the database by the specified wallet address | 65 | | walletSupport | Boolean function used by Node to support the wallet | 66 | | listWallets | Service function used to see the list of addresses that are added to the Node | 67 | | suppressJoinTx | Set to to prevent witness to create joinTx | 68 | 69 | ## Node install for development net 70 | 71 | Set the environment variable `NODE_ENV=Devel`. 72 | 73 | To display debug information, you must set a variable `DEBUG=peer:*,node:*`. 74 | 75 | In components that support debugging at the beginning of the file there is a tag that is used for debugging. 76 | 77 | Example (Linux): 78 | 79 | ``` 80 | NODE_ENV=Devel DEBUG=peer:*,node:* node index.js 81 | ``` 82 | 83 | ## Testing 84 | 85 | #### Running tests 86 | 87 | ``` 88 | npm test 89 | npm run test2 90 | ``` 91 | 92 | #### Running tests with debug output (\*nix) 93 | 94 | ``` 95 | npm run testDebugNix 96 | npm run testDebugNix2 97 | ``` 98 | 99 | #### Running tests with debug output (Windows) 100 | 101 | ``` 102 | npm run testDebugWin 103 | npm run testDebugWin2 104 | ``` 105 | -------------------------------------------------------------------------------- /README.rus.md: -------------------------------------------------------------------------------- 1 | ## Процесс установки (Linux, macOS, Windows): 2 | 3 | #### 1. Склонировать проект 4 | 5 | ``` 6 | git clone https://github.com/SilentNotaryEcosystem/Cil-core.git 7 | cd Cil-core 8 | git checkout tags/latest 9 | ``` 10 | 11 | #### 2. Установить [Node.js (16.17.0) и npm](https://nodejs.org/dist/v16.17.0/node-v16.17.0.pkg) 12 | 13 | #### 3. Установить зависимости и запустить ноду 14 | 15 | ``` 16 | npm install 17 | npm run husky // опционально. только если вы хотите развивать проект: установка git hooks для dev режима 18 | node index.js // запуск ноды 19 | node savePrivateKey.js // запись приватного ключа в файл (аналог keystore) 20 | ``` 21 | 22 | ## Процесс установки (Docker): 23 | 24 | #### ...Comming soon 25 | 26 | ## Параметры для запуска ноды 27 | 28 | Параметры по умолчанию заданы в файле [prod.conf.js](https://github.com/SilentNotaryEcosystem/Cil-core/blob/devel/config/prod.conf.js) (для production сети) и [devel.conf.js](https://github.com/SilentNotaryEcosystem/Cil-core/blob/devel/config/devel.conf.js) (для development сети). 29 | 30 | | Параметр | Описание | 31 | | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | 32 | | listenAddr | Заданный адрес | 33 | | listenPort | Использовать указанный порт (вместо "по умолчанию") | 34 | | seedAddr | Адрес seed'а для загрузки ноды | 35 | | rpcUser | Имя пользователя для вызова функций из ноды | 36 | | rpcPass | Пароль для вызова функций из ноды | 37 | | rpcPort | Порт для вызова функций из ноды | 38 | | rpcAddress | Адрес для вызова функций из ноды | 39 | | genesisHash | Хэш генезис блока для настройки тестового окружения | 40 | | conciliumDefContract | Контракт генезис блока для настройки тестового окружения | 41 | | privateKey | Файл с приватным ключом для запуска ноды свидетеля | 42 | | dbPath | Директория для хранения файлов с базами данных | 43 | | seed | Опция, что запускаемая нода будет являться seed'ом (будет хранить и раздавать адреса тех, кто к ней подключен (peers)) | 44 | | strictAddresses | Сбрасывать соединения с нодами у которых tcp source address не совпадает с MSG_VERSION | 45 | | trustAnnounce | Наоборот (см strictAddresses) использовать MSG_VERSION для определения адреса ноды | 46 | | txIndex | Опция получения индекса транзакции по хешу | 47 | | watchAddress | Опция для работы с локальными кошельками. Используется для добавления адреса кошелька в ноду для отслеживания входящих и исходящих транзакций на этот адрес | 48 | | reIndexWallet | Опция для работы с старыми кошельками. Используется для получения всех транзакций в базе по заданному адресу кошелька | 49 | | walletSupport | Булевая функция для поддержки нодой кошельков | 50 | | listWallets | Служебная функция для просмотра списка адресов, которые добавлены в ноду | 51 | | suppressJoinTx | Установите для предотвращения создания joinTx | 52 | 53 | ## Запуск ноды development сети 54 | 55 | Необходимо установить переменную окружения `NODE_ENV=Devel`. 56 | 57 | Для вывода отладочной информации необходимо установить переменную `DEBUG=peer:*,node:*`. 58 | 59 | В компонентах которые поддерживают отладку в начале файла есть тэг, который используется для отладки. 60 | 61 | Пример (Linux): 62 | 63 | ``` 64 | NODE_ENV=Devel DEBUG=peer:*,node:* node index.js 65 | ``` 66 | 67 | ## Тестирование 68 | 69 | #### Запуск тестов 70 | 71 | ``` 72 | npm test 73 | npm run test2 74 | ``` 75 | 76 | #### Запуск тестов c выводом отладочной информации (\*nix) 77 | 78 | ``` 79 | npm run testDebugNix 80 | npm run testDebugNix2 81 | ``` 82 | 83 | #### Запуск тестов c выводом отладочной информации (Windows) 84 | 85 | ``` 86 | npm run testDebugWin 87 | npm run testDebugWin2 88 | ``` 89 | -------------------------------------------------------------------------------- /conciliums/baseConciliumDefinition.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('assert'); 3 | 4 | const CONCILIUM_TYPE_RR = 0; 5 | const CONCILIUM_TYPE_POS = 1; 6 | 7 | const {deepCloneObject} = require('../utils'); 8 | 9 | // common parameters 10 | //const def = { 11 | // conciliumId: 0, // will be set by Concilium management contract 12 | // type: Constants.CONCILIUM_TYPE_POS | Constants.CONCILIUM_TYPE_RR, 13 | // isOpen: false, // this means - can anybody join concilium or creator should add them 14 | // parameters: { 15 | // fees: { 16 | // feeTxSize: 111, 17 | // feeContractCreation: 111, 18 | // feeContractInvocation: 111, 19 | // feeStorage: 111, 20 | // feeInternalTx: 111, 21 | // }, 22 | // isEnabled: true, 23 | // 24 | // SN hash of document with concilium description. 25 | // document: 'cf60920089b7db942206e6484ea7df51b01e7b1f77dd99c1ecdc766cf5c6a77a' 26 | // } 27 | //}; 28 | 29 | module.exports = class BaseConciliumDefinition { 30 | constructor(data, nSeqLength = 20) { 31 | if (Buffer.isBuffer(data)) throw new Error('BaseConciliumDefinition. Unexpected construction from buffer'); 32 | if (typeof data !== 'object') { 33 | throw new Error(`BaseConciliumDefinition. Unexpected construction from ${typeof data}`); 34 | } 35 | 36 | this._data = deepCloneObject(data); 37 | 38 | if (!this._data.parameters) { 39 | this._data.parameters = { 40 | fees: {}, 41 | document: [] 42 | }; 43 | } 44 | 45 | if (!this._data.parameters.hasOwnProperty('isEnabled')) this._data.parameters.isEnabled = true; 46 | if (!this._data.hasOwnProperty('isOpen')) this._data.parameters.isOpen = false; 47 | 48 | this.changeSeed(0); 49 | 50 | this._nSeqLength = nSeqLength; 51 | } 52 | 53 | getType() { 54 | return this._data.type; 55 | } 56 | 57 | _setType(type) { 58 | return (this._data.type = type); 59 | } 60 | 61 | toObject() { 62 | return this._data; 63 | } 64 | 65 | getConciliumId() { 66 | return this._data.conciliumId; 67 | } 68 | 69 | getFeeTxSize() { 70 | return this._data.parameters.fees ? this._data.parameters.fees.feeTxSize : undefined; 71 | } 72 | 73 | getFeeContractCreation() { 74 | return this._data.parameters.fees ? this._data.parameters.fees.feeContractCreation : undefined; 75 | } 76 | 77 | getFeeContractInvocation() { 78 | return this._data.parameters.fees ? this._data.parameters.fees.feeContractInvocation : undefined; 79 | } 80 | 81 | getFeeStorage() { 82 | return this._data.parameters.fees ? this._data.parameters.fees.feeStorage : undefined; 83 | } 84 | 85 | getFeeInternalTx() { 86 | return this._data.parameters.fees ? this._data.parameters.fees.feeInternalTx : undefined; 87 | } 88 | 89 | validateBlock(/*block*/) { 90 | throw new Error('Implement!'); 91 | } 92 | 93 | /** 94 | * We plan to use it punish scenario 95 | * 96 | * @returns {boolean} 97 | */ 98 | isEnabled() { 99 | return this._data.parameters.isEnabled; 100 | } 101 | 102 | isRoundRobin() { 103 | return this._data.type === CONCILIUM_TYPE_RR; 104 | } 105 | 106 | isPoS() { 107 | return this._data.type === CONCILIUM_TYPE_POS; 108 | } 109 | 110 | static get CONCILIUM_TYPE_POS() { 111 | return CONCILIUM_TYPE_POS; 112 | } 113 | 114 | static get CONCILIUM_TYPE_RR() { 115 | return CONCILIUM_TYPE_RR; 116 | } 117 | 118 | /** 119 | * Redefine this to change proposing behavior 120 | * 121 | * @returns {String} 122 | */ 123 | getProposerKey() { 124 | throw new Error('Implement!'); 125 | } 126 | 127 | initRounds() { 128 | // 2 variables, because this._nSeed could change asynchronously 129 | this._nRoundBase = this._nSeed; 130 | this._nLocalRound = 0; 131 | } 132 | 133 | getRound() { 134 | throw new Error('Implement!'); 135 | } 136 | 137 | nextRound() { 138 | assert(this._nLocalRound !== undefined, 'InitRounds first'); 139 | 140 | if (++this._nLocalRound >= this._nSeqLength) this.initRounds(); 141 | return this.getRound(); 142 | } 143 | 144 | changeSeed(nSeed) { 145 | this._nSeed = nSeed; 146 | } 147 | 148 | getMembersCount() { 149 | throw new Error('Implement!'); 150 | } 151 | 152 | getDocument() { 153 | return this._data.parameters.document; 154 | } 155 | 156 | adjustRound(nRoundNo) { 157 | if (this._nRoundBase === 0 && this._nSeed !== 0) this.initRounds(); 158 | 159 | const nRoundDiff = Math.abs(nRoundNo - this._nRoundBase); 160 | if (nRoundDiff < this._nSeqLength) this._nLocalRound = nRoundDiff; 161 | } 162 | }; 163 | -------------------------------------------------------------------------------- /conciliums/conciliumRr.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const typeforce = require('typeforce'); 4 | const assert = require('assert'); 5 | 6 | const BaseConciliumDefinition = require('./baseConciliumDefinition'); 7 | 8 | //--------------- Witness concilium definition --------- 9 | 10 | //const closedRR = { 11 | // parameters: { 12 | // // see base class 13 | // }, 14 | // 15 | // // members information 16 | // addresses: [ 17 | // ], 18 | // quorum: 1 | addresses.length*2/3 19 | //}; 20 | 21 | module.exports = (/*{Constants}*/) => 22 | class ConciliumRrDefinition extends BaseConciliumDefinition { 23 | constructor(data) { 24 | super(data, data.addresses ? data.addresses.length : 1); 25 | 26 | this._setType(BaseConciliumDefinition.CONCILIUM_TYPE_RR); 27 | if (!Array.isArray(this._data.addresses)) this._data.addresses = []; 28 | } 29 | 30 | static create(conciliumId, arrAddresses, quorum) { 31 | typeforce(typeforce.tuple('Number', 'Array'), arguments); 32 | 33 | return new this({ 34 | addresses: arrAddresses, 35 | conciliumId, 36 | quorum 37 | }); 38 | } 39 | 40 | /** 41 | * 42 | * @return {Array} 43 | */ 44 | getAddresses(bConvertToBuffer = true) { 45 | if (!Array.isArray(this._data.addresses)) return undefined; 46 | return this._data.addresses.map(addr => 47 | bConvertToBuffer ? Buffer.from(addr, 'hex') : addr.toString('hex') 48 | ); 49 | } 50 | 51 | setQuorum(quorum) { 52 | this._data.quorum = quorum; 53 | } 54 | 55 | getQuorum() { 56 | if (this._data.quorum) return this._data.quorum; 57 | const arr = this._data.addresses; 58 | return parseInt(arr.length / 2) + 1; 59 | } 60 | 61 | toObject() { 62 | return this._data; 63 | } 64 | 65 | /** 66 | * Redefine this to change proposing behavior 67 | * 68 | * @returns {String} 69 | */ 70 | getProposerAddress() { 71 | const arrAddresses = this.getAddresses(); 72 | const idx = this.getRound() % arrAddresses.length; 73 | return arrAddresses[idx].toString('hex'); 74 | } 75 | 76 | getWitnessWeight() { 77 | return 1; 78 | } 79 | 80 | isEnabled() { 81 | return super.isEnabled() && !!this._data.addresses.length; 82 | } 83 | 84 | getRound() { 85 | assert(this._nLocalRound !== undefined, 'InitRounds first'); 86 | 87 | return this._nRoundBase + this._nLocalRound; 88 | } 89 | 90 | getMembersCount() { 91 | return this._data.addresses.length; 92 | } 93 | }; 94 | -------------------------------------------------------------------------------- /config/devel.conf.js: -------------------------------------------------------------------------------- 1 | const commonConfig = require('./prod.conf'); 2 | 3 | module.exports = { 4 | // some of constants will be injected from prototypes in Factory! 5 | constants: { 6 | ...commonConfig.constants, 7 | 8 | // fees 9 | fees: { 10 | // money send fee per Kbyte 11 | TX_FEE: 4000, 12 | 13 | // contract creation 14 | CONTRACT_CREATION_FEE: 1e4, 15 | 16 | // contract invocation 17 | CONTRACT_INVOCATION_FEE: 1e4, 18 | 19 | // contract send moneys 20 | INTERNAL_TX_FEE: 300, 21 | 22 | STORAGE_PER_BYTE_FEE: 10 23 | }, 24 | 25 | strIdent: 'Devel', 26 | 27 | protocolVersion: 0x0133, 28 | network: 0x12880004, 29 | port: 18223, 30 | rpcPort: 18222, 31 | 32 | DNS_SEED: ['dev-seed.silentnotary.io'], 33 | 34 | DEV_FOUNDATION_ADDRESS: '087b7b06bfc8f198eb25655c358355692187f9d1', 35 | CONCILIUM_DEFINITION_CONTRACT_ADDRESS: 'a46b92916bc8db1d4b403198f557b987b88f5ae2', 36 | GENESIS_BLOCK: '2bf44e4c0602b8b3c9184fd78bb44b049cfb483727c765638ec0b08afaec3509', 37 | 38 | // how much we suppress creating empty blocks 39 | 40 | WITNESS_HOLDOFF: 1 * 60 * 1000, 41 | 42 | INV_REQUEST_HOLDOFF: 0.5 * 60 * 1000, 43 | 44 | concilium: { 45 | HEIGHT_TO_RELEASE_ADD_ON: 50, 46 | POS_CONCILIUM_ROUNDS: 10 47 | }, 48 | 49 | forks: { 50 | HEIGHT_FORK_SERIALIZER: 8970, 51 | HEIGHT_FORK_CHANGE: 8992, 52 | HEIGHT_FORK_SERIALIZER_FIX2: 9145, 53 | HEIGHT_FORK_SERIALIZER_FIX3: 55700 54 | }, 55 | 56 | WITNESS_UTXOS_JOIN: 5, 57 | MAX_UTXO_PER_TX: 1000 58 | } 59 | }; 60 | -------------------------------------------------------------------------------- /config/prod.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // some of constants will be injected from prototypes in Factory! 3 | constants: { 4 | LOCAL_TX_FILE_NAME: 'txns.json', 5 | 6 | ADDRESS_PREFIX: 'Ux', 7 | 8 | DEV_FOUNDATION_ADDRESS: '961d7815df8cc96d27aa2c483f55c6c163682775', 9 | DEV_FOUNDATION_SHARE: 0.1, 10 | 11 | CONCILIUM_DEFINITION_CONTRACT_ADDRESS: '6ab4ab8161f954be88af699435f3514485ab8f9a', 12 | GENESIS_BLOCK: '8b6d259ee3ee1acd524654d9b27286188c982e3764f1ef3f6db98c6382e6d777', 13 | 14 | strIdent: 'Prod', 15 | 16 | network: 0x12882304, 17 | protocolVersion: 0x0130, 18 | port: 8223, 19 | 20 | rpcPort: 8222, 21 | 22 | DNS_SEED: ['seed.silentnotary.io'], 23 | 24 | // How many peers we'll send in one 'addr' message 25 | ADDR_MAX_LENGTH: 1000, 26 | 27 | // maximum connected peers 28 | MAX_PEERS: 10, 29 | 30 | // minimum connected peers 31 | MIN_PEERS: 3, 32 | 33 | // milliseconds 34 | PEER_QUERY_TIMEOUT: 30000, 35 | CONNECTION_TIMEOUT: 60000, 36 | 37 | // 1 day 38 | BAN_PEER_SCORE: 100, 39 | BAN_PEER_TIME: 24 * 60 * 60 * 1000, 40 | PEER_TICK_TIMEOUT: 1000, 41 | 42 | // bytes count to force peer disconnect 43 | PEER_MAX_BYTES_COUNT: 10 * 1024 * 1024, 44 | 45 | // time to force peer disconnect 46 | PEER_CONNECTION_LIFETIME: 60 * 60 * 1000, 47 | 48 | // time to restrict reconnection with peer 49 | PEER_RESTRICT_TIME: 2 * 60 * 1000, 50 | 51 | PEER_HEARTBEAT_TIMEOUT: 2 * 60 * 1000, 52 | PEER_DEAD_TIME: 6 * 60 * 1000, 53 | 54 | PEER_RECONNECT_INTERVAL: 2 * 60 * 1000, 55 | 56 | // exclude node from node an active list if we fail N times to connect it 57 | PEER_FAILED_CONNECTIONS_LIMIT: 10, 58 | 59 | PEERMANAGER_BACKUP_TIMEOUT: 10 * 60 * 1000, 60 | 61 | // maximum block hashes in MSG_INV 62 | MAX_BLOCKS_INV: 300, 63 | 64 | // we expect that peer will respond with requested INV within this period 65 | INV_REQUEST_HOLDOFF: 1 * 60 * 1000, 66 | 67 | messageTypes: { 68 | MSG_VERSION: 'version', 69 | MSG_VERACK: 'verack', 70 | MSG_GET_ADDR: 'getaddr', 71 | MSG_ADDR: 'addr', 72 | MSG_REJECT: 'reject', 73 | MSG_BLOCK: 'block', 74 | MSG_TX: 'tx', 75 | MSG_INV: 'inv', 76 | MSG_GET_DATA: 'getdata', 77 | MSG_GET_BLOCKS: 'getblocks', 78 | MSG_GET_MEMPOOL: 'getmempool', 79 | MSG_PING: 'ping', 80 | MSG_PONG: 'pong', 81 | 82 | MSG_WITNESS_HANDSHAKE: 'w_handshake', 83 | MSG_WITNESS_NEXT_ROUND: 'w_nextround', 84 | MSG_WITNESS_EXPOSE: 'w_expose', 85 | MSG_WITNESS_BLOCK: 'w_block', 86 | MSG_WITNESS_BLOCK_VOTE: 'w_block_vote' 87 | }, 88 | 89 | consensusStates: { 90 | ROUND_CHANGE: 'ROUND_CHANGE', 91 | BLOCK: 'BLOCK', 92 | VOTE_BLOCK: 'VOTE_BLOCK', 93 | COMMIT: 'COMMIT' 94 | }, 95 | 96 | consensusTimeouts: { 97 | ROUND_CHANGE: 20000, 98 | BLOCK: 30000, 99 | VOTE_BLOCK: 20000, 100 | COMMIT: 30000 101 | }, 102 | 103 | // maximum time offset for nodes we tolerate 104 | TOLERATED_TIME_DIFF: 60 * 60 * 1000, 105 | 106 | // how much we suppress creating empty blocks 107 | WITNESS_HOLDOFF: 2 * 60 * 1000, 108 | MAX_BLOCK_SIZE: 1024 * 1024, 109 | 110 | // fees 111 | fees: { 112 | // money send fee per Kbyte 113 | TX_FEE: 4000, 114 | 115 | // contract creation 116 | CONTRACT_CREATION_FEE: 1e4, 117 | 118 | // contract invocation 119 | CONTRACT_INVOCATION_FEE: 1e4, 120 | 121 | // contract send moneys 122 | INTERNAL_TX_FEE: 300, 123 | 124 | STORAGE_PER_BYTE_FEE: 10 125 | }, 126 | 127 | MEMPOOL_TX_QTY: 500, 128 | MEMPOOL_TX_LIFETIME: 24 * 60 * 60 * 1000, 129 | MEMPOOL_BAD_TX_CACHE: 10 * 60 * 1000, 130 | 131 | // TODO: review it. Heavy code will be terminated on slow nodes. And node become unsynced 132 | TIMEOUT_CODE: 10000, 133 | 134 | TX_STATUS_OK: 1, 135 | TX_STATUS_FAILED: 0, 136 | 137 | DB_PATH_PREFIX: './db', 138 | DB_CHAINSTATE_DIR: 'chainstate', 139 | DB_BLOCKSTATE_DIR: 'blockstate', 140 | DB_PEERSTATE_DIR: 'peerstate', 141 | 142 | DB_TXINDEX_DIR: 'txindex', 143 | DB_WALLET_DIR: 'wallet', 144 | 145 | concilium: { 146 | HEIGHT_TO_RELEASE_ADD_ON: 1000, 147 | POS_CONCILIUM_ROUNDS: 20 148 | }, 149 | 150 | forks: { 151 | HEIGHT_FORK_SERIALIZER: 3775, 152 | HEIGHT_FORK_CHANGE: 3775, 153 | HEIGHT_FORK_SERIALIZER_FIX2: 6100, 154 | HEIGHT_FORK_SERIALIZER_FIX3: 48000 155 | }, 156 | 157 | BLOCK_CREATION_TIME_LIMIT: 1500, 158 | 159 | // if block older than it's parent (any) more than X second - prevent auto witnessing 160 | BLOCK_AUTO_WITNESSING_TIMESTAMP_DIFF: 30 * 60, 161 | 162 | CONTRACT_V_JSON: 2, 163 | CONTRACT_V_V8: 0, 164 | CONTRACT_MIN_CASHING_SIZE: 102400, 165 | 166 | WITNESS_UTXOS_JOIN: 30, 167 | MAX_UTXO_PER_TX: 1000 168 | } 169 | }; 170 | -------------------------------------------------------------------------------- /config/test.conf.js: -------------------------------------------------------------------------------- 1 | const commonConfig = require('./prod.conf'); 2 | 3 | module.exports = { 4 | // some of constants will be injected from prototypes in Factory! 5 | constants: { 6 | ...commonConfig.constants, 7 | 8 | CONCILIUM_DEFINITION_CONTRACT_ADDRESS: undefined, 9 | GENESIS_BLOCK: undefined, 10 | 11 | strIdent: 'Test', 12 | 13 | port: 28223, 14 | 15 | // IMPORTANT for tests (or it starts failing on RPC listen) 16 | rpcPort: undefined, 17 | 18 | DNS_SEED: [], 19 | 20 | // how much we suppress creating empty blocks 21 | WITNESS_HOLDOFF: 5 * 60 * 1000, 22 | 23 | DB_PATH_PREFIX: './testDb', 24 | 25 | concilium: { 26 | HEIGHT_TO_RELEASE_ADD_ON: 1, 27 | POS_CONCILIUM_ROUNDS: 2 28 | }, 29 | 30 | fees: { 31 | // money send fee per Kbyte 32 | TX_FEE: 4000, 33 | 34 | // contract creation 35 | CONTRACT_CREATION_FEE: 1e6, 36 | 37 | // contract invocation 38 | CONTRACT_INVOCATION_FEE: 10000, 39 | 40 | // contract send moneys 41 | INTERNAL_TX_FEE: 300, 42 | 43 | STORAGE_PER_BYTE_FEE: 10 44 | }, 45 | TIMEOUT_CODE: 100000 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | We are glad to welcome all NodeJS developers who want to improve their professional skills. 2 | The Ubikiri project is looking for active NodeJS contributors willing to support and develop our project. 3 | The Ubikiri platform is made for business and professional people who are ready to implement the most ambitious business ideas and concepts. Our distributed social network can give the possibility to enter into contracts, confirm information of any type. It allows you to adjust the amount of information provided about yourself for each transaction. 4 | Our platform Chain-in-law is an OPEN-SORCE project. CiL (Chain-in-Law) is a directed acyclic graph (DAG) and has great advantages in terms of speed and scalability. The platform is fully public and open. The code is available to any member on our GitHub page (https://github.com/SilentNotaryEcosystem/Cil-core). 5 | You can use the code placed there in open access to create your own decentralized applications, as well as to develop segments of the platform itself. 6 | Please note that the platform code is written in Node JS, a JavaScript smart contract language with some limitations. In fact, our proposal is unique for developers working with these languages. The project alone will enable JS developers to get into the "heart" of the blockchain and crypto technologies without changing the programming language. 7 | 8 | 1. You have the opportunity to “look under the hood” of a really unique project built on the blockchain Chain-in-Law (CiL) and written on Node JS. 9 | 10 | - Ubikiri is an expansion of horizons for advanced developers. 11 | - For beginners - this is a huge new layer of knowledge and experience. 12 | 13 | 2. You can have full freedom of action. 14 | 15 | - You can work and decompose by solving problems according to our sections. 16 | - At the same time, all your wildest ideas, interesting and useful software solutions will be considered and rewarded. 17 | 18 | 3. We are ready for cooperation at different levels: 19 | 20 | - Full-time - you understand the project from the inside and you are ready to solve complex and non-standard tasks. The project developers assess your qualification, we make you an offer and add motivation. 21 | - Part-time - you take different "parts" of tasks and give out "pieces" of their solutions. 22 | - Hackathon format - several developers do a collective task and offer a solution. 23 | 24 | 4. The payment for contributions are made in the tokens of the project Ubikiri (CiL). There is a possibility of their swapping to the tokens of the basic Silent Notary project (SNTR), which was stable during the period of a general fall in the market and is listed in the main cryptoexchanges. Our tokens has icreased by more than a double-digit percentage over the last month. We are in the post-ICO period and continue to grow rapidly. The project has been working for more than a year, we have already working platforms and a name on the market - the reliability and prospects of the token are confirmed by a large and active community of holders and traders that are working with the crypto component of the project. 25 | 26 | 5. You also have the opportunity among the first to "deploy" your node/masternode for receiving passive income in the process of our blockchain Chain-in-Law functioning. This will require a certain number of tokens, which is quite possible to purchase or earn under this program. 27 | Here is the list of so-called "work sections": 28 | 29 | - The finalization of the language of smart contracts (this is either JS with AST or webassembly). 30 | - Thr improvement of the network stability mechanisms: the consilium participants or the whole consilium "punishment" for misbehave. 31 | - The additional rules of creation/validation for consiliums (this is PoS, PoW, etc.). 32 | - The security audit. The network vulnerability to various attacks. 33 | - The dependencies audit (the packages used in the project for vulnerabilities. The using packages with fewer dependencies). 34 | - The aggregated signatures under the block. The evaluation of the creating blocks mechanism of vulnerability to fingerprinting (many signatures from witnesses under the blocks - will it be possible to restore the witnesses' private keys with these signatures)? 35 | - The sharding in order to increase the number of transactions processed by the network. 36 | 37 | Our project Github: 38 | https://github.com/SilentNotaryEcosystem/Cil-core 39 | All questions can be answered by us in the open Telegram chat, JOIN: https://t.me/cil_contributors 40 | -------------------------------------------------------------------------------- /db/.bannedBlocks.json: -------------------------------------------------------------------------------- 1 | ["8fb48ce96449bc1ca414f54df3947d0f93e22d90373392c2fddc44827e4f8148"] 2 | -------------------------------------------------------------------------------- /db/.gitkeep: -------------------------------------------------------------------------------- 1 | "" 2 | -------------------------------------------------------------------------------- /factory.js: -------------------------------------------------------------------------------- 1 | const configProd = require('./config/prod.conf'); 2 | const configDev = require('./config/devel.conf'); 3 | const BaseFactory = require('./baseFactory'); 4 | 5 | const config = process.env.NODE_ENV === 'Devel' ? configDev : configProd; 6 | 7 | /** 8 | * Class to easy replacement used components 9 | */ 10 | 11 | const Ipv6TransportWrapper = require('./network/ipv6Transport'); 12 | 13 | class ProdFactory extends BaseFactory { 14 | constructor(options, objConstants) { 15 | super(options, objConstants); 16 | } 17 | 18 | initSpecific() { 19 | this._transportImplemetation = Ipv6TransportWrapper(this); 20 | } 21 | } 22 | 23 | module.exports = new ProdFactory({}, config.constants); 24 | -------------------------------------------------------------------------------- /hookSetup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ./hook_setup.sh 3 | set -e 4 | 5 | HERE=$(realpath $(dirname "$0")) 6 | GITDIR="$HERE/.git" 7 | HOOKDIR="$GITDIR/hooks" 8 | HUSKY_HOOKS="$HERE/.husky" 9 | 10 | for F in `ls "$HUSKY_HOOKS"`; do 11 | P="$HUSKY_HOOKS/$F" 12 | if [ -f "$P" ] ; then 13 | HOOK_FILE="$HOOKDIR/$F" 14 | echo "#!/bin/sh" > "$HOOK_FILE" 15 | echo "sh \"$P\"" >> "$HOOK_FILE" 16 | chmod +x "$HOOK_FILE" 17 | fi 18 | done 19 | -------------------------------------------------------------------------------- /messages/includes/peerInfo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {Object} Constants 4 | * @param {Object} PeerInfoProto - protobuf compiled message PeerInfo 5 | * @return {{new(*): MessageCommon}} 6 | */ 7 | module.exports = (Constants, PeerInfoProto) => 8 | class PeerInfo { 9 | constructor(data) { 10 | if (Buffer.isBuffer(data)) { 11 | this._data = {...PeerInfoProto.decode(data)}; 12 | } else if (typeof data === 'object') { 13 | // transform address to internal representation 14 | if (Buffer.isBuffer(data.address)) { 15 | data.address = this.constructor.fromAddress(data.address); 16 | } 17 | if (!data.port) data.port = Constants.port; 18 | if (!data.capabilities) data.capabilities = [{service: Constants.NODE}]; 19 | 20 | const errMsg = PeerInfoProto.verify(data); 21 | if (errMsg) throw new Error(`PeerInfo: ${errMsg}`); 22 | 23 | this._data = PeerInfoProto.create(data); 24 | } else { 25 | throw new Error('Use buffer or object to initialize PeerInfo'); 26 | } 27 | } 28 | 29 | get data() { 30 | return this._data; 31 | } 32 | 33 | /** 34 | * 35 | * @return {Buffer} 36 | */ 37 | get address() { 38 | // TODO add cache for address? 39 | if (!this._data || !this._data.address) throw new Error('PeerInfo not initialized!'); 40 | return this.constructor.toAddress(this._data.address); 41 | } 42 | 43 | /** 44 | * 45 | * @param {Buffer} buff 46 | */ 47 | set address(buff) { 48 | if (!this._data) throw new Error('PeerInfo not initialized!'); 49 | this._data.address = this.constructor.fromAddress(buff); 50 | } 51 | 52 | get port() { 53 | if (!this._data || !this._data.port) throw new Error('PeerInfo not initialized!'); 54 | return this._data.port; 55 | } 56 | 57 | set port(value) { 58 | if (!this._data) throw new Error('PeerInfo not initialized!'); 59 | this._data.port = value; 60 | } 61 | 62 | addCapability(objCapability) { 63 | return this._data.capabilities.push(objCapability); 64 | } 65 | 66 | get capabilities() { 67 | return this._data.capabilities; 68 | } 69 | 70 | set capabilities(objValue) { 71 | this._data.capabilities = objValue; 72 | } 73 | 74 | get lifetimeMisbehaveScore() { 75 | return this._data.lifetimeMisbehaveScore; 76 | } 77 | 78 | set lifetimeMisbehaveScore(score) { 79 | this._data.lifetimeMisbehaveScore = score; 80 | } 81 | 82 | get lifetimeTransmittedBytes() { 83 | return this._data.lifetimeTransmittedBytes; 84 | } 85 | 86 | set lifetimeTransmittedBytes(bytesCount) { 87 | this._data.lifetimeTransmittedBytes = bytesCount; 88 | } 89 | 90 | get lifetimeReceivedBytes() { 91 | return this._data.lifetimeReceivedBytes; 92 | } 93 | 94 | set lifetimeReceivedBytes(bytesCount) { 95 | this._data.lifetimeReceivedBytes = bytesCount; 96 | } 97 | 98 | get failedConnectionsCount() { 99 | return this._data.failedConnectionsCount; 100 | } 101 | 102 | set failedConnectionsCount(count) { 103 | this._data.failedConnectionsCount = count; 104 | } 105 | 106 | /** 107 | * 108 | * @param {Object} objAddress - {addr0, addr1, addr2, addr3} 109 | * @return {Buffer} 110 | */ 111 | static toAddress(objAddress) { 112 | const buffer = Buffer.alloc(16); 113 | buffer.writeUInt32BE(objAddress.addr0, 0); 114 | buffer.writeUInt32BE(objAddress.addr1, 4); 115 | buffer.writeUInt32BE(objAddress.addr2, 8); 116 | buffer.writeUInt32BE(objAddress.addr3, 12); 117 | return buffer; 118 | } 119 | 120 | /** 121 | * 122 | * @param {Buffer} buff 123 | * @return {Object} {addr0, addr1, addr2, addr3} 124 | */ 125 | static fromAddress(buff) { 126 | const objAddress = {}; 127 | objAddress.addr0 = buff.readUInt32BE(0); 128 | objAddress.addr1 = buff.readUInt32BE(4); 129 | objAddress.addr2 = buff.readUInt32BE(8); 130 | objAddress.addr3 = buff.readUInt32BE(12); 131 | return objAddress; 132 | } 133 | 134 | /** 135 | * ATTENTION! JUST encode 136 | * 137 | * @return {Uint8Array} 138 | */ 139 | encode() { 140 | return PeerInfoProto.encode(this._data).finish(); 141 | } 142 | 143 | equals(peerInfo) { 144 | return ( 145 | this.constructor.toAddress(this._data.address).equals(this.constructor.toAddress(peerInfo.address)) && 146 | this._data.port === peerInfo.port 147 | ); 148 | } 149 | }; 150 | -------------------------------------------------------------------------------- /messages/index.js: -------------------------------------------------------------------------------- 1 | const MsgCommonWrapper = require('./msgCommon'); 2 | 3 | const MsgAddrWrapper = require('./node/msgAddr'); 4 | const MsgVersionWrapper = require('./node/msgVersion'); 5 | const MsgRejectWrapper = require('./node/msgReject'); 6 | const MsgBlockWrapper = require('./node/msgBlock'); 7 | const MsgTxWrapper = require('./node/msgTx'); 8 | const MsgInvWrapper = require('./node/msgInv'); 9 | const MsgGetDataWrapper = require('./node/msgGetData'); 10 | const MsgGetBlocksWrapper = require('./node/msgGetBlocks'); 11 | 12 | const PeerInfoWrapper = require('./includes/peerInfo'); 13 | 14 | const MsgWitnessCommonWrapper = require('./witness/msgWitnessCommon.js'); 15 | const MsgWitnessNextRoundWrapper = require('./witness/msgWitnessNextRound.js'); 16 | const MsgWitnessExposeWrapper = require('./witness/msgWitnessExpose.js'); 17 | const MsgWitnessBlockWrapper = require('./witness/msgWitnessBlock.js'); 18 | const MsgWitnessBlockVoteWrapper = require('./witness/msgWitnessBlockVote.js'); 19 | 20 | module.exports = (factory, objPrototypes) => { 21 | const {Constants, Crypto, Block, Transaction, Inventory, ArrayOfHashes} = factory; 22 | const {messageProto, versionPayloadProto, addrPayloadProto, rejectPayloadProto, getBlocksPayloadProto} = 23 | objPrototypes; 24 | const {witnessMessageProto, witnessNextRoundProto, witnessBlockVoteProto} = objPrototypes; 25 | const {peerInfoProto} = objPrototypes; 26 | 27 | const MsgCommon = MsgCommonWrapper(Constants, Crypto, messageProto); 28 | const MsgWitnessCommon = MsgWitnessCommonWrapper(Constants, Crypto, MsgCommon, witnessMessageProto); 29 | const MsgBlock = MsgBlockWrapper(Constants, Crypto, MsgCommon, Block); 30 | const MsgInv = MsgInvWrapper(Constants, Crypto, MsgCommon, Inventory); 31 | 32 | return { 33 | MsgCommon, 34 | MsgVersion: MsgVersionWrapper(Constants, MsgCommon, versionPayloadProto), 35 | MsgAddr: MsgAddrWrapper(Constants, MsgCommon, addrPayloadProto), 36 | MsgReject: MsgRejectWrapper(Constants, MsgCommon, rejectPayloadProto), 37 | MsgBlock, 38 | MsgTx: MsgTxWrapper(Constants, Crypto, MsgCommon, Transaction), 39 | MsgInv, 40 | MsgGetData: MsgGetDataWrapper(Constants, Crypto, MsgInv), 41 | MsgGetBlocks: MsgGetBlocksWrapper(Constants, ArrayOfHashes, MsgCommon, getBlocksPayloadProto), 42 | 43 | PeerInfo: PeerInfoWrapper(Constants, peerInfoProto), 44 | 45 | MsgWitnessCommon, 46 | MsgWitnessNextRound: MsgWitnessNextRoundWrapper(Constants, Crypto, MsgWitnessCommon, witnessNextRoundProto), 47 | MsgWitnessWitnessExpose: MsgWitnessExposeWrapper(Constants, Crypto, MsgWitnessCommon), 48 | MsgWitnessBlock: MsgWitnessBlockWrapper(Constants, MsgWitnessCommon, Block), 49 | MsgWitnessBlockVote: MsgWitnessBlockVoteWrapper(Constants, Crypto, MsgWitnessCommon, witnessBlockVoteProto) 50 | }; 51 | }; 52 | -------------------------------------------------------------------------------- /messages/node/msgAddr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {Object} Constants 4 | * @param {Object} MessageCommon 5 | * @param {Object} AddrPayloadProto - protobuf compiled AddrPayload prototype 6 | * @return {{new(*): MessageAddr}} 7 | */ 8 | module.exports = (Constants, MessageCommon, AddrPayloadProto) => { 9 | const {MSG_ADDR} = Constants.messageTypes; 10 | 11 | return class MessageAddr extends MessageCommon { 12 | /** 13 | * 14 | * @param {Object|Buffer} data 15 | * @param {Number} data.peers - array of peerInfo 16 | */ 17 | constructor(data) { 18 | if (data instanceof MessageCommon || Buffer.isBuffer(data)) { 19 | super(data); 20 | if (!this.isAddr()) { 21 | throw new Error(`Wrong message type. Expected "${MSG_ADDR}" got "${this.message}"`); 22 | } 23 | 24 | this._data = {...AddrPayloadProto.decode(this.payload)}; 25 | // TODO: free this.message.payload after decode to reduce memory usage 26 | } else { 27 | super(); 28 | if (typeof data === 'object') { 29 | const errMsg = AddrPayloadProto.verify(data); 30 | if (errMsg) throw new Error(`MessageAddr: ${errMsg}`); 31 | 32 | this._data = AddrPayloadProto.create(data); 33 | } 34 | this.message = MSG_ADDR; 35 | } 36 | } 37 | 38 | /** 39 | * 40 | * @return {Array} of PeerInfo 41 | */ 42 | get peers() { 43 | return this._data.peers; 44 | } 45 | 46 | /** 47 | * ATTENTION! for payload we'll use encode NOT encodeDelimited as for entire Message 48 | * 49 | * @return {Uint8Array} 50 | */ 51 | encode() { 52 | this.payload = AddrPayloadProto.encode(this._data).finish(); 53 | return super.encode(); 54 | } 55 | }; 56 | }; 57 | -------------------------------------------------------------------------------- /messages/node/msgBlock.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {Object} Constants 4 | * @param {Crypto} Crypto 5 | * @param {Object} MessageCommon 6 | * @param {Block} Block 7 | * @return {{new(*): MessageBlock}} 8 | */ 9 | module.exports = (Constants, Crypto, MessageCommon, Block) => { 10 | const {MSG_BLOCK} = Constants.messageTypes; 11 | 12 | return class MessageBlock extends MessageCommon { 13 | /** 14 | * 15 | * @param {Block | Buffer} data 16 | */ 17 | constructor(data) { 18 | if (data instanceof MessageCommon || Buffer.isBuffer(data)) { 19 | super(data); 20 | if (!this.isBlock()) { 21 | throw new Error(`Wrong message type. Expected "${MSG_BLOCK}" got "${this.message}"`); 22 | } 23 | } else { 24 | super(); 25 | if (data instanceof Block) this.block = data; 26 | this.message = MSG_BLOCK; 27 | } 28 | } 29 | 30 | get block() { 31 | let block; 32 | try { 33 | if (!this.payload) throw TypeError(`Message payload is empty!`); 34 | block = new Block(this.payload); 35 | } catch (e) { 36 | logger.error(`Bad block payload: ${e}`); 37 | } 38 | return block; 39 | } 40 | 41 | /** 42 | * 43 | * @param {Block} cBlock 44 | */ 45 | set block(cBlock) { 46 | if (!(cBlock instanceof Block)) { 47 | throw TypeError(`Bad block. Expected instance of Block, got ${cBlock}`); 48 | } 49 | this.payload = cBlock.encode(); 50 | } 51 | }; 52 | }; 53 | -------------------------------------------------------------------------------- /messages/node/msgGetBlocks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {Object} Constants 4 | * @param {ArrayOfHashes} ArrayOfHashes 5 | * @param {Object} MessageCommon 6 | * @param {Object} GetBlocksPayloadProto - protobuf compiled AddrPayload prototype 7 | * @return {{new(*): MessageAddr}} 8 | */ 9 | module.exports = (Constants, ArrayOfHashes, MessageCommon, GetBlocksPayloadProto) => { 10 | const {MSG_GET_BLOCKS} = Constants.messageTypes; 11 | 12 | return class MessageGetBlocks extends MessageCommon { 13 | /** 14 | * 15 | * @param {Object|Buffer} data 16 | */ 17 | constructor(data) { 18 | if (data instanceof MessageCommon || Buffer.isBuffer(data)) { 19 | super(data); 20 | if (!this.isGetBlocks()) { 21 | throw new Error(`Wrong message type. Expected "${MSG_GET_BLOCKS}" got "${this.message}"`); 22 | } 23 | 24 | this._data = {...GetBlocksPayloadProto.decode(this.payload)}; 25 | } else { 26 | super(); 27 | this._data = {}; 28 | 29 | if (typeof data === 'object') { 30 | const errMsg = GetBlocksPayloadProto.verify(data); 31 | if (errMsg) throw new Error(`MessageGetBlocks: ${errMsg}`); 32 | 33 | this._data = GetBlocksPayloadProto.create(data); 34 | } 35 | this.message = MSG_GET_BLOCKS; 36 | } 37 | } 38 | 39 | /** 40 | * 41 | * @returns {string[]} 42 | */ 43 | get arrHashes() { 44 | return new ArrayOfHashes(this._data.arrHashes).getArray(); 45 | } 46 | 47 | set arrHashes(arrHashes) { 48 | this._data.arrHashes = new ArrayOfHashes(arrHashes).encode(); 49 | } 50 | 51 | /** 52 | * ATTENTION! for payload we'll use encode NOT encodeDelimited as for entire Message 53 | * 54 | * @return {Uint8Array} 55 | */ 56 | encode() { 57 | this.payload = GetBlocksPayloadProto.encode(this._data).finish(); 58 | return super.encode(); 59 | } 60 | }; 61 | }; 62 | -------------------------------------------------------------------------------- /messages/node/msgGetData.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {Object} Constants 4 | * @param {Crypto} Crypto 5 | * @param {MessageInv} MessageInv 6 | * @param {Inventory} Inventory 7 | * @return {{new(*): MessageGetData}} 8 | */ 9 | module.exports = (Constants, Crypto, MessageInv) => { 10 | const {MSG_GET_DATA} = Constants.messageTypes; 11 | 12 | return class MessageGetData extends MessageInv { 13 | /** 14 | * 15 | * @param {Inventory | Buffer} data 16 | */ 17 | constructor(data) { 18 | super(data); 19 | this.message = MSG_GET_DATA; 20 | } 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /messages/node/msgInv.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {Object} Constants 4 | * @param {Crypto} Crypto 5 | * @param {MessageCommon} MessageCommon 6 | * @param {Inventory} Inventory 7 | * @return {{new(*): MessageInv}} 8 | */ 9 | module.exports = (Constants, Crypto, MessageCommon, Inventory) => { 10 | const {MSG_INV, MSG_GET_DATA} = Constants.messageTypes; 11 | 12 | return class MessageInv extends MessageCommon { 13 | /** 14 | * 15 | * @param {Inventory | Buffer} data 16 | */ 17 | constructor(data) { 18 | super(data); 19 | 20 | if ((data instanceof MessageCommon || Buffer.isBuffer(data)) && !(this.isInv() || this.isGetData())) { 21 | throw new Error(`Wrong message type. Expected "${MSG_INV} | ${MSG_GET_DATA}" got "${this.message}"`); 22 | } else { 23 | if (data instanceof Inventory) this.inventory = data; 24 | this.message = MSG_INV; 25 | } 26 | } 27 | 28 | get inventory() { 29 | let inventory; 30 | try { 31 | if (!this.payload) throw TypeError(`Message payload is empty!`); 32 | inventory = new Inventory(this.payload); 33 | } catch (e) { 34 | logger.error(`Bad Inventory payload: ${e}`); 35 | throw new Error(e); 36 | } 37 | return inventory; 38 | } 39 | 40 | set inventory(cInventory) { 41 | if (!(cInventory instanceof Inventory)) { 42 | throw TypeError(`Bad block. Expected instance of Block, got ${cInventory}`); 43 | } 44 | this.payload = cInventory.encode(); 45 | } 46 | }; 47 | }; 48 | -------------------------------------------------------------------------------- /messages/node/msgReject.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {Object} Constants 4 | * @param {Object} MessageCommon 5 | * @param {Object} RejectProto - protobuf compiled AddrPayload prototype 6 | * @return {{new(*): MessageReject}} 7 | */ 8 | module.exports = (Constants, MessageCommon, RejectProto) => { 9 | const {MSG_REJECT} = Constants.messageTypes; 10 | 11 | return class MessageReject extends MessageCommon { 12 | /** 13 | * 14 | * @param {Object|Buffer} data 15 | * @param {Number} data.peers - array of peerInfo 16 | */ 17 | constructor(data) { 18 | if (data instanceof MessageCommon || Buffer.isBuffer(data)) { 19 | super(data); 20 | if (!this.isReject()) { 21 | throw new Error(`Wrong message type. Expected "${MSG_REJECT}" got "${this.message}"`); 22 | } 23 | 24 | this._data = {...RejectProto.decode(this.payload)}; 25 | // TODO: free this.message.payload after decode to reduce memory usage 26 | } else { 27 | super(); 28 | if (typeof data === 'object') { 29 | const errMsg = RejectProto.verify(data); 30 | if (errMsg) throw new Error(`RejectProto: ${errMsg}`); 31 | 32 | this._data = RejectProto.create(data); 33 | } 34 | this.message = MSG_REJECT; 35 | } 36 | } 37 | 38 | get reason() { 39 | return this._data.reason; 40 | } 41 | 42 | /** 43 | * ATTENTION! for payload we'll use encode NOT encodeDelimited as for entire Message 44 | * 45 | * @return {Uint8Array} 46 | */ 47 | encode() { 48 | this.payload = RejectProto.encode(this._data).finish(); 49 | return super.encode(); 50 | } 51 | }; 52 | }; 53 | -------------------------------------------------------------------------------- /messages/node/msgTx.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {Object} Constants 4 | * @param {Crypto} Crypto 5 | * @param {Object} MessageCommon 6 | * @param {Transaction} Transaction 7 | * @return {{new(*): MessageTx}} 8 | */ 9 | module.exports = (Constants, Crypto, MessageCommon, Transaction) => { 10 | const {MSG_TX} = Constants.messageTypes; 11 | 12 | return class MessageTx extends MessageCommon { 13 | /** 14 | * 15 | * @param {Block | Buffer} data 16 | */ 17 | constructor(data) { 18 | if (data instanceof MessageCommon || Buffer.isBuffer(data)) { 19 | super(data); 20 | if (!this.isTx()) { 21 | throw new Error(`Wrong message type. Expected "${MSG_TX}" got "${this.message}"`); 22 | } 23 | } else { 24 | super(); 25 | if (data instanceof Transaction) this.tx = data; 26 | this.message = MSG_TX; 27 | } 28 | } 29 | 30 | get tx() { 31 | let block; 32 | try { 33 | if (!this.payload) throw TypeError(`Message payload is empty!`); 34 | block = new Transaction(this.payload); 35 | } catch (e) { 36 | logger.error(`Bad TX payload: ${e}`); 37 | } 38 | return block; 39 | } 40 | 41 | /** 42 | * 43 | * @param {Transaction} cTransaction 44 | */ 45 | set tx(cTransaction) { 46 | if (!(cTransaction instanceof Transaction)) { 47 | throw TypeError(`Bad block. Expected instance of TX, got ${cTransaction}`); 48 | } 49 | this.payload = cTransaction.encode(); 50 | } 51 | }; 52 | }; 53 | -------------------------------------------------------------------------------- /messages/node/msgVersion.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {Object} Constants 4 | * @param {Object} MessageCommon 5 | * @param {Object} VersionPayloadProto - protobuf compiled VersionPayload prototype 6 | * @return {{new(*): MessageVersion}} 7 | */ 8 | module.exports = (Constants, MessageCommon, VersionPayloadProto) => { 9 | const {MSG_VERSION} = Constants.messageTypes; 10 | 11 | return class MessageVersion extends MessageCommon { 12 | /** 13 | * 14 | * @param {Object|Buffer|MessageCommon} data 15 | * @param {Number} data.protocolVersion - current protocol version 16 | * @param {Object} data.peerInfo - @see network.proto.PeerInfo 17 | * @param {Number} data.height - curent DB height (length of MainChain) 18 | */ 19 | constructor(data) { 20 | if (!data) throw new Error('You should pass data to constructor'); 21 | if (data instanceof MessageCommon || Buffer.isBuffer(data)) { 22 | super(data); 23 | if (!this.isVersion()) { 24 | throw new Error(`Wrong message type. Expected "${MSG_VERSION}" got "${this.message}"`); 25 | } 26 | 27 | this._data = {...VersionPayloadProto.decode(this.payload)}; 28 | // TODO: free this.message.payload after decode to reduce memory usage 29 | } else { 30 | super(); 31 | if (typeof data === 'object') { 32 | if (!data.nonce) throw new Error('You should specify nonce!'); 33 | 34 | const errMsg = VersionPayloadProto.verify(data); 35 | if (errMsg) throw new Error(`MessageVersion: ${errMsg}`); 36 | 37 | const payload = VersionPayloadProto.create(data); 38 | this._data = { 39 | ...payload, 40 | timeStamp: parseInt(Date.now() / 1000), 41 | protocolVersion: Constants.protocolVersion 42 | }; 43 | } 44 | this.message = MSG_VERSION; 45 | } 46 | } 47 | 48 | get nonce() { 49 | return this._data.nonce; 50 | } 51 | 52 | get data() { 53 | return this._data; 54 | } 55 | 56 | get protocolVersion() { 57 | return this._data.protocolVersion; 58 | } 59 | 60 | get peerInfo() { 61 | return this._data.peerInfo; 62 | } 63 | 64 | get msecTime() { 65 | return 1000 * this._data.timeStamp; 66 | } 67 | /** 68 | * ATTENTION! for payload we'll use encode NOT encodeDelimited as for entire Message 69 | * 70 | * @return {Uint8Array} 71 | */ 72 | encode() { 73 | this.payload = VersionPayloadProto.encode(this._data).finish(); 74 | return super.encode(); 75 | } 76 | }; 77 | }; 78 | -------------------------------------------------------------------------------- /messages/witness/msgWitnessBlock.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {Object} Constants 4 | * @param {WitnessMessageCommon} WitnessMessageCommon 5 | * @param {Block} Block 6 | * @return {{new(*): WitnessMessageBlock}} 7 | */ 8 | module.exports = (Constants, WitnessMessageCommon, Block) => { 9 | const {MSG_WITNESS_BLOCK} = Constants.messageTypes; 10 | 11 | return class WitnessMessageBlock extends WitnessMessageCommon { 12 | constructor(data) { 13 | super(data); 14 | if (data instanceof WitnessMessageCommon || Buffer.isBuffer(data)) { 15 | if (!this.isWitnessBlock()) { 16 | throw new Error(`Wrong message type. Expected "${MSG_WITNESS_BLOCK}" got "${this.message}"`); 17 | } 18 | } 19 | this.message = MSG_WITNESS_BLOCK; 20 | } 21 | 22 | get block() { 23 | let block; 24 | try { 25 | if (!this.content) throw TypeError(`Message content is empty!`); 26 | block = new Block(this.content); 27 | } catch (e) { 28 | logger.error(`Bad block payload: ${e}`); 29 | } 30 | return block; 31 | } 32 | 33 | /** 34 | * 35 | * @param {Block} cBlock 36 | */ 37 | set block(cBlock) { 38 | if (!(cBlock instanceof Block)) { 39 | throw TypeError(`Bad block. Expected instance of Block, got ${cBlock}`); 40 | } 41 | this.content = cBlock.encode(); 42 | } 43 | }; 44 | }; 45 | -------------------------------------------------------------------------------- /messages/witness/msgWitnessBlockVote.js: -------------------------------------------------------------------------------- 1 | const typeforce = require('typeforce'); 2 | 3 | const BLOCK_HASH = typeforce.oneOf(typeforce.BufferN(32), typeforce.BufferN(6)); 4 | 5 | /** 6 | * 7 | * @param {Object} Constants 8 | * @param {Crypto} Crypto 9 | * @param {WitnessMessageCommon} WitnessMessageCommon 10 | * @param {Object} WitnessBlockVoteProto - protobuf compiled Message prototype 11 | * @return {{new(*): WitnessMessageBlockAck}} 12 | */ 13 | module.exports = (Constants, Crypto, WitnessMessageCommon, WitnessBlockVoteProto) => { 14 | const {MSG_WITNESS_BLOCK_VOTE} = Constants.messageTypes; 15 | 16 | return class WitnessMessageBlockAck extends WitnessMessageCommon { 17 | constructor(data) { 18 | super(data); 19 | if (data instanceof WitnessMessageCommon || Buffer.isBuffer(super.content)) { 20 | this._data = {...WitnessBlockVoteProto.decode(super.content)}; 21 | 22 | if (!this.isWitnessBlockVote()) { 23 | throw new Error(`Wrong message type. Expected "${MSG_WITNESS_BLOCK_VOTE}" got "${this.message}"`); 24 | } 25 | } else { 26 | if (!data.blockHash) { 27 | throw new Error('Specify "blockHash"'); 28 | } 29 | 30 | // it could be hash256 or 'reject' 31 | typeforce(BLOCK_HASH, data.blockHash); 32 | 33 | this._data = { 34 | blockHash: data.blockHash, 35 | signature: null 36 | }; 37 | } 38 | this.message = MSG_WITNESS_BLOCK_VOTE; 39 | } 40 | 41 | get blockHash() { 42 | return this._data.blockHash; 43 | } 44 | 45 | get hashSignature() { 46 | return this._data.signature; 47 | } 48 | 49 | /** 50 | * We override it for consensus.processMessage 51 | * @returns {*} 52 | */ 53 | get content() { 54 | return this._data; 55 | } 56 | 57 | static reject(conciliumId) { 58 | return new this({conciliumId, blockHash: Buffer.from('reject')}); 59 | } 60 | 61 | sign(privateKey) { 62 | // sign blockHash 63 | if (!this.blockHash) throw new Error('Set blockHash first!'); 64 | typeforce(BLOCK_HASH, this.blockHash); 65 | this._data.signature = Crypto.sign(this.blockHash, privateKey); 66 | 67 | // sign entire message @see msgCommon.sign (it will call this.encode) 68 | super.sign(privateKey); 69 | } 70 | 71 | encode() { 72 | super.content = WitnessBlockVoteProto.encode(this._data).finish(); 73 | return super.encode(); 74 | } 75 | }; 76 | }; 77 | -------------------------------------------------------------------------------- /messages/witness/msgWitnessCommon.js: -------------------------------------------------------------------------------- 1 | const typeforce = require('typeforce'); 2 | 3 | /** 4 | * Scenario: 5 | * - Decode MessageCommon from wire 6 | * - Detect signature 7 | * - Decode WitnessMessageCommon from MessageCommon 8 | * - Get conciliumId from WitnessMessageCommon and pass it to respective BFT 9 | * - Decode specific Witness msg from WitnessMessageCommon 10 | * 11 | * @param {Object} Constants 12 | * @param {CryptoLib} Crypto 13 | * @param {MessageCommon} MessageCommon 14 | * @param {Object} WitnessMessageProto - protobuf compiled Message prototype 15 | * @return {{new(*): WitnessMessageCommon}} 16 | */ 17 | module.exports = (Constants, Crypto, MessageCommon, WitnessMessageProto) => { 18 | const { 19 | MSG_WITNESS_NEXT_ROUND, 20 | MSG_WITNESS_EXPOSE, 21 | MSG_WITNESS_BLOCK, 22 | MSG_WITNESS_HANDSHAKE, 23 | MSG_WITNESS_BLOCK_VOTE 24 | } = Constants.messageTypes; 25 | 26 | return class WitnessMessageCommon extends MessageCommon { 27 | constructor(data) { 28 | if (data instanceof WitnessMessageCommon) { 29 | super(data); 30 | 31 | // invoked from descendant classes via super() 32 | // copy content (it's not deep copy, possibly better use encode/decode) 33 | this._msgData = Object.assign({}, data._msgData); 34 | } else if (Buffer.isBuffer(data)) { 35 | super(data); 36 | 37 | // this.payload filled with super(date) (NOTE we'r parsing THIS.payload) 38 | this._msgData = {...WitnessMessageProto.decode(this.payload)}; 39 | } else if (data instanceof MessageCommon) { 40 | super(data); 41 | 42 | // we received it from wire (NOTE we'r parsing data.payload) 43 | this._msgData = {...WitnessMessageProto.decode(data.payload)}; 44 | } else { 45 | super(); 46 | 47 | // constructing it manually 48 | if (data.conciliumId === undefined) { 49 | throw new Error('Specify "conciliumId"'); 50 | } 51 | this._msgData = { 52 | conciliumId: data.conciliumId 53 | }; 54 | } 55 | } 56 | 57 | get conciliumId() { 58 | return this._msgData.conciliumId; 59 | } 60 | 61 | get content() { 62 | return this._msgData.content; 63 | } 64 | 65 | /** 66 | * used for encoding by descendants 67 | * 68 | * @param {Buffer} value 69 | * @return {*} 70 | */ 71 | set content(value) { 72 | typeforce('Buffer', value); 73 | 74 | this._msgData.content = value; 75 | } 76 | 77 | set handshakeMessage(unused) { 78 | this.message = MSG_WITNESS_HANDSHAKE; 79 | } 80 | 81 | encode() { 82 | this.payload = WitnessMessageProto.encode(this._msgData).finish(); 83 | return super.encode(); 84 | } 85 | 86 | isHandshake() { 87 | return this.message === MSG_WITNESS_HANDSHAKE; 88 | } 89 | 90 | isNextRound() { 91 | return this.message === MSG_WITNESS_NEXT_ROUND; 92 | } 93 | 94 | isExpose() { 95 | return this.message === MSG_WITNESS_EXPOSE; 96 | } 97 | 98 | isWitnessBlock() { 99 | return this.message === MSG_WITNESS_BLOCK; 100 | } 101 | 102 | isWitnessBlockVote() { 103 | return this.message === MSG_WITNESS_BLOCK_VOTE; 104 | } 105 | }; 106 | }; 107 | -------------------------------------------------------------------------------- /messages/witness/msgWitnessExpose.js: -------------------------------------------------------------------------------- 1 | /** 2 | * We'll put serialized message to WitnessMessage.content 3 | * 4 | * @param {Object} Constants 5 | * @param {CryptoLib} Crypto 6 | * @param {WitnessMessageCommon} WitnessMessageCommon 7 | * @return {{new(*): WitnessMessageCommon}} 8 | */ 9 | module.exports = (Constants, Crypto, WitnessMessageCommon) => { 10 | const {MSG_WITNESS_EXPOSE} = Constants.messageTypes; 11 | 12 | return class WitnessExpose extends WitnessMessageCommon { 13 | /** 14 | * 15 | * @param {WitnessMessageCommon} msg 16 | */ 17 | constructor(msg) { 18 | super(msg); 19 | 20 | if (msg instanceof WitnessMessageCommon) { 21 | if (msg.isBlock() || msg.isExpose()) { 22 | throw new Error(`Message "${msg.message}" could not be exposed!`); 23 | } 24 | this.content = msg.encode(); 25 | this.message = MSG_WITNESS_EXPOSE; 26 | } else { 27 | throw new Error('This type of message could be constructed only from other witness message!'); 28 | } 29 | } 30 | 31 | static extract(msgExpose) { 32 | return new WitnessMessageCommon(msgExpose.content); 33 | } 34 | }; 35 | }; 36 | -------------------------------------------------------------------------------- /messages/witness/msgWitnessNextRound.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Scenario: 3 | * - Decode MessageCommon from wire 4 | * - Detect signature 5 | * - Decode WitnessMessageCommon from MessageCommon 6 | * - Get conciliumId from WitnessMessageCommon and pass it to respective BFT 7 | * - Decode specific Witness msg from WitnessMessageCommon 8 | * 9 | * @param {Object} Constants 10 | * @param {CryptoLib} Crypto 11 | * @param {WitnessMessageCommon} WitnessMessageCommon 12 | * @param {Object} WitnessNextRoundProto - protobuf compiled Message prototype 13 | * @return {{new(*): WitnessMessageCommon}} 14 | */ 15 | module.exports = (Constants, Crypto, WitnessMessageCommon, WitnessNextRoundProto) => { 16 | const {MSG_WITNESS_NEXT_ROUND} = Constants.messageTypes; 17 | 18 | return class WitnessNextRound extends WitnessMessageCommon { 19 | constructor(data) { 20 | super(data); 21 | 22 | if (data instanceof WitnessMessageCommon || Buffer.isBuffer(super.content)) { 23 | this._data = {...WitnessNextRoundProto.decode(super.content)}; 24 | 25 | if (!this.isNextRound()) { 26 | throw new Error(`Wrong message type. Expected "${MSG_WITNESS_NEXT_ROUND}" got "${this.message}"`); 27 | } 28 | } else { 29 | if (data.roundNo === undefined) { 30 | throw new Error('Specify "roundNo"'); 31 | } 32 | 33 | this._data = { 34 | roundNo: data.roundNo 35 | }; 36 | } 37 | this.message = MSG_WITNESS_NEXT_ROUND; 38 | } 39 | 40 | /** 41 | * We override it for consensus.processMessage 42 | * @returns {*} 43 | */ 44 | get content() { 45 | return this._data; 46 | } 47 | 48 | get roundNo() { 49 | return this._data.roundNo; 50 | } 51 | 52 | encode() { 53 | super.content = WitnessNextRoundProto.encode(this._data).finish(); 54 | return super.encode(); 55 | } 56 | }; 57 | }; 58 | -------------------------------------------------------------------------------- /network/ipv6Connection.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events'); 2 | const debug = require('debug')('transport:connection'); 3 | 4 | const {sleep} = require('../utils'); 5 | 6 | /** 7 | 8 | */ 9 | 10 | module.exports = (Serializer, MessageAssembler, Transport, Constants) => 11 | class Connection extends EventEmitter { 12 | constructor(options) { 13 | super(); 14 | 15 | this._timeout = options.timeout || Constants.CONNECTION_TIMEOUT; 16 | this._socket = options.socket; 17 | 18 | if (!this._socket) throw new Error('No socket!'); 19 | // this._socket.write = util.promisify(this._socket.write); 20 | 21 | this._nonce = parseInt(Math.random() * 10000); 22 | this._socket.on('data', this._incomingMessage.bind(this)); 23 | this._socket.on('end', this.close.bind(this)); 24 | this._socket.on('error', this.close.bind(this)); 25 | 26 | this._messageAssembler = new MessageAssembler(); 27 | } 28 | 29 | /** 30 | * 31 | * @return {String} !! 32 | */ 33 | get remoteAddress() { 34 | return this._socket.remoteAddress; 35 | } 36 | 37 | /** 38 | * 39 | * @return {Number} !! 40 | */ 41 | get remotePort() { 42 | return this._socket.remotePort; 43 | } 44 | 45 | /** 46 | * 47 | * @param {MsgCommon} message - message to send to peer 48 | */ 49 | async sendMessage(message) { 50 | debug(`(Nonce: ${this._nonce}) sendMessage "${message.message}"`); 51 | return this._socket.write(Serializer.serialize(message)); 52 | } 53 | 54 | async _incomingMessage(data) { 55 | const arrMessages = this._messageAssembler.extractMessages(data); 56 | 57 | // incomplete message 58 | if (!arrMessages) return; 59 | 60 | let msgBuffer; 61 | while ((msgBuffer = arrMessages.shift())) { 62 | try { 63 | const msg = Serializer.deSerialize(msgBuffer); 64 | debug(`(Nonce: ${this._nonce}) incomingMessage: "${msg.message}". Done`); 65 | this.emit('message', msg); 66 | 67 | // for receiveSync only. 68 | this.emit('messageSync', msg); 69 | } catch (err) { 70 | err.log(); 71 | this.close(); 72 | } 73 | } 74 | } 75 | 76 | /** 77 | * 78 | * @return {Promise} - message | undefined if timeout reached 79 | */ 80 | receiveSync() { 81 | const prom = new Promise(resolve => { 82 | this.once('messageSync', async objMessage => { 83 | resolve(objMessage); 84 | }); 85 | }); 86 | return Promise.race([prom, sleep(this._timeout)]); 87 | } 88 | 89 | close() { 90 | this._socket.destroy(); 91 | this.emit('close'); 92 | } 93 | }; 94 | -------------------------------------------------------------------------------- /network/messageAssembler.js: -------------------------------------------------------------------------------- 1 | const debug = require('debug')('msgassembler:'); 2 | 3 | module.exports = Serializer => 4 | class MessageAssembler { 5 | get isDone() { 6 | return !this._messageBuffer; 7 | } 8 | 9 | /** 10 | * There are situations, when one chunk could contain multiple messages! 11 | * 12 | * @private 13 | */ 14 | extractMessages(data) { 15 | const arrMessagesBuffers = []; 16 | if (!this._messageBuffer) { 17 | // new message, let's assemby it 18 | const {length, dataOffset} = Serializer.readMsgLength(data); 19 | const totalMsgBufferLength = length + dataOffset; 20 | debug(`New message. Total length: ${totalMsgBufferLength}. Chunk length: ${data.length}.`); 21 | 22 | const messageBuffer = Buffer.alloc(totalMsgBufferLength); 23 | const toCopyBytes = data.length > totalMsgBufferLength ? totalMsgBufferLength : data.length; 24 | data.copy(messageBuffer, 0, 0, toCopyBytes); 25 | 26 | if (data.length === messageBuffer.length) { 27 | // exactly one message 28 | return [messageBuffer]; 29 | } else if (data.length > messageBuffer.length) { 30 | // we have another message (possibly part of it) in 'data' 31 | // let's recursively get it 32 | arrMessagesBuffers.push(messageBuffer); 33 | const subBuffer = data.slice(totalMsgBufferLength); 34 | const arrRestOfMessages = this.extractMessages(subBuffer); 35 | if (arrRestOfMessages) { 36 | return arrMessagesBuffers.concat(arrRestOfMessages); 37 | } else { 38 | return arrMessagesBuffers; 39 | } 40 | } else if (data.length < messageBuffer.length) { 41 | // we need more chunks for it! 42 | this._messageBuffer = messageBuffer; 43 | this._bytesToGo = messageBuffer.length - data.length; 44 | return null; 45 | } 46 | } else { 47 | debug(` next chunk. length: ${data.length}.`); 48 | 49 | // next chunks for current message 50 | const toCopyBytes = this._bytesToGo < data.length ? this._bytesToGo : data.length; 51 | data.copy(this._messageBuffer, this._messageBuffer.length - this._bytesToGo, 0, toCopyBytes); 52 | if (toCopyBytes === this._bytesToGo) { 53 | // we are done with this message 54 | arrMessagesBuffers.push(this._messageBuffer); 55 | this._messageBuffer = undefined; 56 | 57 | // no more messages in this chunk 58 | if (toCopyBytes === data.length) return arrMessagesBuffers; 59 | 60 | const subBuffer = data.slice(toCopyBytes); 61 | const arrRestOfMessages = this.extractMessages(subBuffer); 62 | if (arrRestOfMessages) { 63 | return arrMessagesBuffers.concat(arrRestOfMessages); 64 | } else { 65 | return arrMessagesBuffers; 66 | } 67 | } else { 68 | this._bytesToGo -= toCopyBytes; 69 | return null; 70 | } 71 | } 72 | } 73 | }; 74 | -------------------------------------------------------------------------------- /network/publicAddresses.json: -------------------------------------------------------------------------------- 1 | [ 2 | "2001:0000::/23", 3 | "2001:0200::/23", 4 | "2001:0400::/23", 5 | "2001:0600::/23", 6 | "2001:0800::/23", 7 | "2001:0a00::/23", 8 | "2001:0c00::/23", 9 | "2001:0e00::/23", 10 | "2001:1200::/23", 11 | "2001:1400::/23", 12 | "2001:1600::/23", 13 | "2001:1800::/23", 14 | "2001:1a00::/23", 15 | "2001:1c00::/22", 16 | "2001:2000::/20", 17 | "2001:3000::/21", 18 | "2001:3800::/22", 19 | "2001:4000::/23", 20 | "2001:4200::/23", 21 | "2001:4400::/23", 22 | "2001:4600::/23", 23 | "2001:4800::/23", 24 | "2001:4a00::/23", 25 | "2001:4c00::/23", 26 | "2001:5000::/20", 27 | "2001:8000::/19", 28 | "2001:a000::/20", 29 | "2001:b000::/20", 30 | "2002:0000::/16", 31 | "2003:0000::/18", 32 | "2400:0000::/12", 33 | "2600:0000::/12", 34 | "2610:0000::/23", 35 | "2620:0000::/23", 36 | "2800:0000::/12", 37 | "2a00:0000::/12", 38 | "2c00:0000::/12" 39 | ] 40 | -------------------------------------------------------------------------------- /network/serializer.js: -------------------------------------------------------------------------------- 1 | const {BufferReader} = require('protobufjs'); 2 | 3 | module.exports = MessagesImplementation => { 4 | const {MsgCommon} = MessagesImplementation; 5 | return class Serializer { 6 | /** 7 | * 8 | * @param {MessageCommon} message - to send to network 9 | * @return {Buffer} 10 | */ 11 | static serialize(message) { 12 | return message.encode(); 13 | } 14 | 15 | /** 16 | * 17 | * @param {Buffer} buffer - to decode 18 | * @return {MessageCommon} 19 | */ 20 | static deSerialize(buffer) { 21 | // was message completly downloaded? 22 | const {length, dataOffset} = Serializer.readMsgLength(buffer); 23 | if (buffer.length - dataOffset !== length) { 24 | throw new Error(`Buffer length ${buffer.length} not equal to expected ${length}`); 25 | } 26 | 27 | return new MsgCommon(buffer); 28 | } 29 | 30 | /** 31 | * 32 | * @param {Buffer} firstChunk - first chunk (or whole) of serialized data 33 | * @return {{length: number, dataOffset: number}} length - data length, dataOffset - position of payload in chunk 34 | */ 35 | static readMsgLength(firstChunk) { 36 | const buffReader = new BufferReader(firstChunk); 37 | return {length: buffReader.int32(), dataOffset: buffReader.pos}; 38 | } 39 | }; 40 | }; 41 | -------------------------------------------------------------------------------- /network/testConnection.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events'); 2 | const debug = require('debug')('transport:connection'); 3 | 4 | const {sleep} = require('../utils'); 5 | 6 | /** 7 | * Это тестовый транспорт Socket'ах. 8 | * Нужно фактически заменить path на address для обычного TCP/IP 9 | * Может эмулировать задержку через options.delay 10 | */ 11 | 12 | module.exports = (Serializer, MessageAssembler, Constants) => 13 | class Connection extends EventEmitter { 14 | constructor(options) { 15 | super(); 16 | 17 | this._timeout = options.timeout || Constants.CONNECTION_TIMEOUT; 18 | this._delay = options.delay !== undefined ? options.delay : parseInt(Math.random() * 10 * 1000); 19 | this._socket = options.socket; 20 | 21 | if (!options.remoteAddress) throw new Error('Remote address unknown!'); 22 | this._remoteAddress = options.remoteAddress; 23 | 24 | if (!this._socket) throw new Error('No socket!'); 25 | // this._socket.write = util.promisify(this._socket.write); 26 | 27 | this._nonce = parseInt(Math.random() * 10000); 28 | this._socket.on('data', this._incomingMessage.bind(this)); 29 | this._socket.on('end', this.close.bind(this)); 30 | this._socket.on('error', this.close.bind(this)); 31 | 32 | this._messageAssembler = new MessageAssembler(); 33 | } 34 | 35 | get myAddress() { 36 | return this._socket.localAddress; 37 | } 38 | 39 | /** 40 | * 41 | * @return {String} !! 42 | */ 43 | get remoteAddress() { 44 | return this._remoteAddress; 45 | } 46 | 47 | /** 48 | * it's a stub! 49 | * 50 | * @return {String} !! 51 | */ 52 | get remotePort() { 53 | return Constants.port; 54 | } 55 | 56 | /** 57 | * 58 | * @param {MsgCommon} message - message to send to peer 59 | */ 60 | async sendMessage(message) { 61 | if (this._delay) await sleep(this._delay); 62 | debug(`(Nonce: ${this._nonce}, delay ${this._delay}) sendMessage "${message.message}"`); 63 | const result = this._socket.write(Serializer.serialize(message)); 64 | return result; 65 | } 66 | 67 | async _incomingMessage(data) { 68 | const arrMessages = this._messageAssembler.extractMessages(data); 69 | 70 | // incomplete message 71 | if (!arrMessages) return; 72 | 73 | let msgBuffer; 74 | while ((msgBuffer = arrMessages.shift())) { 75 | try { 76 | const msg = Serializer.deSerialize(msgBuffer); 77 | debug(`(Nonce: ${this._nonce}, delay ${this._delay}) incomingMessage: "${msg.message}". Done`); 78 | if (this._delay) await sleep(this._delay); 79 | this.emit('message', msg); 80 | 81 | // for receiveSync only. 82 | this.emit('messageSync', msg); 83 | } catch (err) { 84 | logger.error(err); 85 | } 86 | } 87 | } 88 | 89 | /** 90 | * 91 | * @return {Promise} - message | undefined if timeout reached 92 | */ 93 | receiveSync() { 94 | const prom = new Promise(resolve => { 95 | this.once('messageSync', async objMessage => { 96 | resolve(objMessage); 97 | }); 98 | }); 99 | return Promise.race([prom, sleep(this._timeout)]); 100 | } 101 | 102 | close() { 103 | this._socket.destroy(); 104 | this.emit('close'); 105 | } 106 | 107 | /** 108 | * DON'T implement in prod connection 109 | * 110 | * Return at least 16 bytes (length of ipv6 address) buffer created from address 111 | * If needed it will be padded with 0 from start 112 | * Will be replaced with real ipv6 buffer 113 | * 114 | * @param {String} address 115 | * @return {Buffer} 116 | */ 117 | static strToAddress(address) { 118 | const buffer = Buffer.from(address); 119 | const bytestoPadd = buffer.length > 16 ? 0 : 16 - buffer.length; 120 | return bytestoPadd ? Buffer.concat([Buffer.alloc(bytestoPadd), buffer]) : buffer; 121 | } 122 | }; 123 | -------------------------------------------------------------------------------- /node/mainDag.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const typeforce = require('typeforce'); 4 | const {Dag} = require('dagjs'); 5 | 6 | const types = require('../types'); 7 | 8 | module.exports = ({Constants}) => 9 | class MainDag { 10 | constructor() { 11 | this._dag = new Dag(); 12 | this._dag.testForCyclic = false; 13 | } 14 | 15 | get order() { 16 | return this._dag.order; 17 | } 18 | 19 | get size() { 20 | return this._dag.size; 21 | } 22 | 23 | addBlock(blockInfo) { 24 | typeforce(types.BlockInfo, blockInfo); 25 | const blockHash = blockInfo.getHash(); 26 | 27 | if (blockHash !== Constants.GENESIS_BLOCK) { 28 | for (let strHash of blockInfo.parentHashes) { 29 | this._dag.add(blockHash, strHash); 30 | } 31 | } else { 32 | if (!this._dag.hasVertex(blockHash)) this._dag.addVertex(blockHash); 33 | } 34 | this._dag.saveObj(blockHash, blockInfo); 35 | } 36 | 37 | setBlockInfo(blockInfo) { 38 | typeforce(types.BlockInfo, blockInfo); 39 | 40 | this._dag.saveObj(blockInfo.getHash(), blockInfo); 41 | } 42 | 43 | getBlockInfo(strHash) { 44 | typeforce(types.Str64, strHash); 45 | return this._dag.readObj(strHash); 46 | } 47 | 48 | getParents(strHash) { 49 | typeforce(types.Str64, strHash); 50 | 51 | return this._dag.readObj(strHash).parentHashes; 52 | } 53 | 54 | getChildren(strHash) { 55 | typeforce(types.Str64, strHash); 56 | 57 | return this._dag.edgesTo(strHash).tips; 58 | } 59 | 60 | getBlockHeight(strHash) { 61 | typeforce(types.Str64, strHash); 62 | 63 | return this._dag.readObj(strHash).getHeight(); 64 | } 65 | 66 | removeBlock(strHash) { 67 | typeforce(types.Str64, strHash); 68 | 69 | this._dag.removeVertex(strHash); 70 | } 71 | }; 72 | -------------------------------------------------------------------------------- /node/requestsCache.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const typeforce = require('typeforce'); 4 | 5 | const types = require('../types'); 6 | 7 | module.exports = ({Constants}) => 8 | class RequestCache { 9 | constructor() { 10 | this._mapRequests = new Map(); 11 | } 12 | 13 | /** 14 | * 15 | * @param {Buffer | String} hash - hash to be requested 16 | * @returns {boolean} 17 | * true - means we should request (not requested yet, or previous request was timed out 18 | * false - request is already pending 19 | */ 20 | request(hash) { 21 | typeforce(types.Hash256bit, hash); 22 | 23 | hash = Buffer.isBuffer(hash) ? hash.toString('hex') : hash; 24 | if (this._mapRequests.has(hash) && this._mapRequests.get(hash) > Date.now()) return false; 25 | 26 | this._mapRequests.set(hash, Date.now() + Constants.INV_REQUEST_HOLDOFF); 27 | return true; 28 | } 29 | 30 | isRequested(hash) { 31 | typeforce(types.Hash256bit, hash); 32 | 33 | hash = Buffer.isBuffer(hash) ? hash.toString('hex') : hash; 34 | const awaitTill = this._mapRequests.get(hash); 35 | return awaitTill && awaitTill > Date.now(); 36 | } 37 | 38 | /** 39 | * 40 | * @param {Buffer | String} hash - hash successfully requested 41 | */ 42 | done(hash) { 43 | typeforce(types.Hash256bit, hash); 44 | 45 | hash = Buffer.isBuffer(hash) ? hash.toString('hex') : hash; 46 | this._mapRequests.delete(hash); 47 | } 48 | 49 | isEmpty() { 50 | this._purgeOutdated(); 51 | return this._mapRequests.size === 0; 52 | } 53 | 54 | _purgeOutdated() { 55 | for (let hash of this._mapRequests.keys()) { 56 | const awaitTill = this._mapRequests.get(hash); 57 | if (!awaitTill || awaitTill < Date.now()) this._mapRequests.delete(hash); 58 | } 59 | } 60 | }; 61 | -------------------------------------------------------------------------------- /node/wallet.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = Crypto => 4 | class Wallet { 5 | constructor(privateKey) { 6 | if (!privateKey) throw new Error('You need private key to init wallet'); 7 | if (typeof privateKey === 'string') { 8 | this._keyPair = Crypto.keyPairFromPrivate(privateKey, 'hex'); 9 | } else { 10 | this._keyPair = Crypto.keyPairFromPrivate(privateKey); 11 | } 12 | this._address = Crypto.getAddress(this._keyPair.getPublic(true, 'hex')); 13 | } 14 | 15 | get address() { 16 | return this._address; 17 | } 18 | 19 | get publicKey() { 20 | return this._keyPair.getPublic(true, 'hex'); 21 | } 22 | 23 | get privateKey() { 24 | return this._keyPair.getPrivate('hex'); 25 | } 26 | 27 | sign(data) { 28 | return Crypto.sign(data, this._keyPair.getPrivate('hex')); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chain-in-law", 3 | "engines": { 4 | "node": "^16.17.0" 5 | }, 6 | "version": "0.7.3", 7 | "main": "factory.js", 8 | "dependencies": { 9 | "command-line-args": "^5.1.1", 10 | "dagjs": "git+https://github.com/trueshura/DAG.git", 11 | "elliptic": "^6.5.4", 12 | "ipaddr.js": "^1.9.1", 13 | "js-sha3": "^0.8.0", 14 | "json-rpc2": "git+https://github.com/trueshura/node-jsonrpc2.git", 15 | "leveldown": "^6.1.1", 16 | "levelup": "^5.1.1", 17 | "merkletreejs": "0.0.11", 18 | "mutex": "git+https://github.com/trueshura/MUTEX.git", 19 | "nat-upnp": "^1.1.1", 20 | "protobufjs": "^6.11.3", 21 | "tick-tock": "^1.0.0", 22 | "typeforce": "^1.18.0", 23 | "vm2": "^3.9.10" 24 | }, 25 | "devDependencies": { 26 | "chai": "^4.2.0", 27 | "chai-as-promised": "^7.1.1", 28 | "debug": "^3.2.7", 29 | "eslint": "^7.32.0", 30 | "eslint-config-prettier": "^8.5.0", 31 | "eslint-plugin-prettier": "^4.2.1", 32 | "husky": "^8.0.1", 33 | "memdown": "^3.0.0", 34 | "mocha": "^5.2.0", 35 | "nock": "^11.9.1", 36 | "node-uuid": "^1.4.8", 37 | "prettier": "^2.7.1", 38 | "sinon": "^6.3.5" 39 | }, 40 | "scripts": { 41 | "unit": "mocha --exit tests/unit/*.spec.js tests/unit/**/*.spec.js", 42 | "bft": "mocha --exit tests/integration/bft.integration.spec.js", 43 | "node": "mocha --exit tests/integration/node.integration.spec.js", 44 | "contract": "mocha --exit tests/integration/contract.integration.spec.js", 45 | "witness": "mocha --exit tests/integration/witness.integration.spec.js", 46 | "test": "npm run unit && npm run bft && npm run node && npm run contract && npm run witness", 47 | "test2": "mocha --exit tests/integration/genesis.integration*.spec.js", 48 | "testDebugWin": "set DEBUG=*,-mocha:* && npm test", 49 | "testDebugWin2": "set DEBUG=*,-mocha:* && npm run test2", 50 | "testDebugNix": "DEBUG=*,-mocha:* npm test", 51 | "testDebugNix2": "DEBUG=*,-mocha:* npm run test2", 52 | "lint": "./node_modules/.bin/eslint './**/*.js'", 53 | "check": "npx prettier --check .", 54 | "fix": "npx prettier --write .", 55 | "husky": "husky install && ./hookSetup.sh" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /proto/network.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package network; 4 | 5 | // ------------------------------ Common ------------------- 6 | // IPv6 or IPv4Mapped2IPv6 7 | message Address { 8 | uint32 addr0 = 1; 9 | uint32 addr1 = 2; 10 | uint32 addr2 = 3; 11 | uint32 addr3 = 4; 12 | } 13 | 14 | message Message { 15 | uint32 network = 1; 16 | string message = 3; 17 | uint32 checksum = 4; 18 | 19 | // will be filled only for signed connections 20 | bytes signature = 5; 21 | bytes payload = 6; 22 | } 23 | 24 | // ------------------------------ Version message ------------------- 25 | message PeerInfo { 26 | repeated NodeCapabilities capabilities =1; 27 | Address address = 2; 28 | uint32 port = 3; 29 | uint32 lifetimeMisbehaveScore = 4; 30 | uint32 lifetimeTransmittedBytes = 5; 31 | uint32 lifetimeReceivedBytes = 6; 32 | uint32 failedConnectionsCount = 7; 33 | } 34 | 35 | enum Services { 36 | NODE = 1; 37 | WITNESS = 2; 38 | } 39 | 40 | message NodeCapabilities { 41 | Services service =1; 42 | 43 | // for witness it will be his publicKey 44 | bytes data =2; 45 | } 46 | 47 | message VersionPayload { 48 | uint32 protocolVersion = 1; 49 | uint32 timeStamp = 2; 50 | PeerInfo peerInfo = 3; 51 | uint32 nonce = 4; 52 | uint64 height = 5; 53 | } 54 | 55 | // -------------------------------- Address group ------------------- 56 | // TODO: implement timestamps https://en.bitcoin.it/wiki/Protocol_documentation#addr 57 | 58 | message AddrPayload { 59 | uint32 count = 1; 60 | repeated PeerInfo peers = 2; 61 | } 62 | 63 | // -------------------------------- ------------------- 64 | 65 | enum RejectCodes { 66 | REJECT_DUPLICATE = 1; 67 | REJECT_BAD_WITNESS = 2; 68 | REJECT_BANNED = 3; 69 | REJECT_TIMEOFFSET = 4; 70 | REJECT_RESTRICTED = 5; 71 | REJECT_REWRITE_DEAD = 6; 72 | } 73 | 74 | message RejectPayload { 75 | RejectCodes code = 1; 76 | string reason = 2; 77 | } 78 | 79 | // ------------------------- getblocks ------------- 80 | 81 | message GetBlocksPayload { 82 | 83 | // it's serialized to buffer 84 | bytes arrHashes=1; 85 | bytes hashStop=2; 86 | } 87 | -------------------------------------------------------------------------------- /proto/predefinedClasses.js: -------------------------------------------------------------------------------- 1 | class Base { 2 | constructor() { 3 | this._ownerAddress = callerAddress; 4 | } 5 | 6 | __getCode() { 7 | const arrFunctionsToPropagateFromBase = ['_checkOwner', '_transferOwnership', '_validateAddress']; 8 | 9 | const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(this)) 10 | .filter(name => name !== 'constructor' && typeof this[name] === 'function') 11 | .concat(arrFunctionsToPropagateFromBase); 12 | const objCode = {}; 13 | methods.forEach(strFuncName => { 14 | const strCodeMethod = this[strFuncName].toString(); 15 | 16 | // we prepend code of asynс function with '<' 17 | const codePrefix = Object.getPrototypeOf(this[strFuncName]).constructor.name === 'AsyncFunction' ? '<' : ''; 18 | const re = new RegExp(`${strFuncName}.*?(\(.*?\).*?\{.*\})`, 'ms'); 19 | const arrMatches = strCodeMethod.match(re); 20 | if (!arrMatches) throw new Error(`Bad code for ${strFuncName}`); 21 | objCode[strFuncName] = codePrefix + arrMatches[1]; 22 | }); 23 | return objCode; 24 | } 25 | 26 | _validateAddress(strAddress) { 27 | if (strAddress.length !== 40) throw 'Bad address'; 28 | } 29 | 30 | _checkOwner() { 31 | if (this._ownerAddress !== callerAddress) throw 'Unauthorized call'; 32 | } 33 | 34 | _transferOwnership(strNewAddress) { 35 | this._checkOwner(); 36 | this._validateAddress(strNewAddress); 37 | 38 | this._ownerAddress = strNewAddress; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /proto/structures.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package structures; 4 | 5 | //------------------- Tx --------- 6 | message input{ 7 | bytes txHash=1; 8 | uint32 nTxOutput=2; 9 | } 10 | 11 | // TODO: change to oneOf ... one for regular payment another for Contracts 12 | message output{ 13 | 14 | // possibly here should be uint64 or bytes (BN) 15 | fixed64 amount=1; 16 | 17 | // payment receiver 18 | bytes receiverAddr=2; 19 | 20 | // if receiverAddr is AddrContractCreation or function call (not used otherwise) 21 | string contractCode=3; 22 | bytes addrChangeReceiver=4; 23 | } 24 | 25 | message TransactionPayload { 26 | uint32 version=3; 27 | uint32 conciliumId=4; 28 | 29 | // place here all fields that will be hashed 30 | repeated input ins=1; 31 | repeated output outs=2; 32 | } 33 | 34 | // implement it 35 | enum ClaimHashMethod{ 36 | 37 | // SIGHASH_ALL is default now 38 | SIGHASH_ALL = 0; 39 | SIGHASH_NONE = 100; 40 | SIGHASH_SINGLE = 101; 41 | SIGHASH_ANYONECANPAY = 102; 42 | } 43 | 44 | message Transaction { 45 | 46 | // place here non hashed fields 47 | TransactionPayload payload=1; 48 | repeated bytes claimProofs=2; 49 | 50 | // used only when contractOwner plan to send moneys or transfer ownership 51 | bytes txSignature=3; 52 | } 53 | 54 | //------------------- Block --------- 55 | 56 | message BlockHeader{ 57 | repeated bytes parentHashes=1; 58 | bytes merkleRoot =2; 59 | uint32 conciliumId=3; 60 | uint32 timestamp=6; 61 | uint32 version=7; 62 | uint32 height=8; 63 | } 64 | 65 | message Block{ 66 | BlockHeader header=1; 67 | repeated Transaction txns=2; 68 | repeated bytes signatures=3; 69 | } 70 | 71 | //------------------- Inventory --------- 72 | 73 | enum InventoryTypes{ 74 | INV_TX = 11; 75 | INV_BLOCK = 21; 76 | } 77 | 78 | message InventoryVector{ 79 | InventoryTypes type =1; 80 | bytes hash =2; 81 | } 82 | 83 | message Inventory{ 84 | repeated InventoryVector invVector=1; 85 | } 86 | 87 | //------------------- UTXO --------- 88 | 89 | message UTXO{ 90 | repeated uint32 arrIndexes =1; 91 | repeated output arrOutputs =2; 92 | } 93 | 94 | //--------------- Block summary info (for storage) ----- 95 | 96 | message BlockInfo{ 97 | BlockHeader header=1; 98 | uint32 flags=2; 99 | } 100 | 101 | //--------------- Contract info (for storage) ----- 102 | 103 | message Contract{ 104 | bytes contractData =1; 105 | string contractCode=2; 106 | uint32 conciliumId=3; 107 | fixed64 balance=4; 108 | uint32 version=5; 109 | } 110 | 111 | //--------------- TX Receipt ----- 112 | 113 | enum TxStatuses{ 114 | TX_STATUS_FAILED = 0; 115 | TX_STATUS_OK = 1; 116 | } 117 | 118 | message TxReceipt{ 119 | 120 | // if contract was created 121 | bytes contractAddress =1; 122 | 123 | // actual fee for contract exec (i think unit32 should be enough) 124 | uint32 coinsUsed =2; 125 | 126 | // 127 | TxStatuses status =3; 128 | 129 | // if status != TX_STATUS_OK here will be a message 130 | string message =5; 131 | 132 | // hashes 133 | repeated bytes internalTxns=4; 134 | 135 | // coins from internalTxns (i.e. same indexes) 136 | repeated output coins=6; 137 | } 138 | -------------------------------------------------------------------------------- /proto/witness.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package witness; 4 | 5 | //TODO: replace bytes content to oneof 6 | message WitnessMessage { 7 | uint32 conciliumId=1; 8 | bytes content=2; 9 | } 10 | 11 | // -------------- NEXT_ROUND ----- 12 | message NextRound { 13 | uint32 roundNo =1; 14 | } 15 | 16 | // -------------- MSG_WITNESS_BLOCK_VOTE ----- 17 | message BlockVote { 18 | bytes blockHash=1; 19 | 20 | // unused for reject 21 | bytes signature=2; 22 | } 23 | -------------------------------------------------------------------------------- /runScripts/auto.restart.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # this script will check for new commits at remote, pull changes, restart container 4 | 5 | function startProcess() { 6 | echo "Node started" 7 | node --max-old-space-size=4096 index.js & 8 | child=$! 9 | } 10 | 11 | function restartIfNeeded() { 12 | echo "Checking is alive ..." 13 | if ! ps -p $child >/dev/null; then 14 | echo "Node is dead. Restarting" 15 | startProcess 16 | fi 17 | } 18 | 19 | function checkForUpdate() { 20 | echo "Checking for update ..." 21 | git remote update 22 | behind=$(git status -uno | grep behind | wc -l | awk '{print $1}') 23 | [[ $behind == "1" ]] && git pull && _term 24 | } 25 | 26 | function justSleep() { 27 | # это должно быть так, потому что wait - это часть bash, и поскольку управление у него 28 | # то работает trap. А если sleep без & то обработчик trap не работает 29 | sleep 5m & 30 | wait "$!" 31 | } 32 | 33 | function _term() { 34 | echo "Requested CONTAINER SHUTDOWN !" 35 | kill -TERM "$child" 2>/dev/null 36 | wait "$child" 37 | exit 100 38 | } 39 | 40 | #------------------------------------------------ 41 | 42 | startProcess 43 | i=0 44 | trap _term SIGTERM 45 | 46 | while :; do 47 | ((i++)) 48 | restartIfNeeded 49 | 50 | if [[ ${AUTO_UPDATE} == "true" ]] && ! (($i % 6)); then 51 | checkForUpdate 52 | fi 53 | 54 | justSleep 55 | done 56 | -------------------------------------------------------------------------------- /runScripts/joinConcilium/joinConcilium.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | node scripts/joinConcilium.js 4 | -------------------------------------------------------------------------------- /runScripts/node.devel/run.node.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CONTAINER_NAME=$1 4 | CONTAINER_NAME="${CONTAINER_NAME:-cil-node-devel}" 5 | 6 | sudo docker run \ 7 | --restart always \ 8 | -d \ 9 | -p 18222:18222 -p 18223:18223 \ 10 | --env-file sample.node.env \ 11 | --name $CONTAINER_NAME \ 12 | trueshura/cil-core-staging:latest 13 | -------------------------------------------------------------------------------- /runScripts/node.devel/sample.node.env: -------------------------------------------------------------------------------- 1 | # devel options 2 | DEBUG=node:* 3 | NODE_ENV=Devel 4 | 5 | # see utils.js/mapEnvToOptions 6 | 7 | RPC_USER=my_secret_user 8 | RPC_PASS=my_secret_password 9 | 10 | WALLET_SUPPORT=true 11 | BUILD_TX_INDEX=true 12 | -------------------------------------------------------------------------------- /runScripts/node.rpc/run.node.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CONTAINER_NAME=$1 4 | CONTAINER_NAME="${CONTAINER_NAME:-cil-rpc}" 5 | 6 | sudo docker run \ 7 | --restart always \ 8 | -d \ 9 | -p 8222:8222 -p 8223:8223 \ 10 | --env-file sample.node.env \ 11 | -e AUTO_UPDATE=true \ 12 | --name $CONTAINER_NAME \ 13 | trueshura/cil-core-prod:latest 14 | -------------------------------------------------------------------------------- /runScripts/node.rpc/sample.node.env: -------------------------------------------------------------------------------- 1 | # see utils.js/mapEnvToOptions 2 | 3 | RPC_ADDRESS=0.0.0.0 4 | RPC_USER=my_secret_user 5 | RPC_PASS=my_secret_password 6 | RPC_RATE=1000 7 | FIX_LEVEL_DB=true 8 | AUTO_UPDATE=false 9 | 10 | WALLET_SUPPORT=true 11 | 12 | # mandatory for check_tx_done! 13 | BUILD_TX_INDEX=true 14 | -------------------------------------------------------------------------------- /runScripts/node/run.node.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CONTAINER_NAME=$1 4 | CONTAINER_NAME="${CONTAINER_NAME:-cil-node}" 5 | 6 | sudo docker run \ 7 | --restart always \ 8 | -d \ 9 | -p 8222:8222 -p 8223:8223 \ 10 | --env-file sample.node.env \ 11 | -e AUTO_UPDATE=true \ 12 | --name $CONTAINER_NAME \ 13 | trueshura/cil-core-prod:latest 14 | -------------------------------------------------------------------------------- /runScripts/node/sample.node.env: -------------------------------------------------------------------------------- 1 | # see utils.js/mapEnvToOptions 2 | 3 | RPC_USER=my_secret_user 4 | RPC_PASS=my_secret_password 5 | 6 | WALLET_SUPPORT=true 7 | BUILD_TX_INDEX=true 8 | -------------------------------------------------------------------------------- /runScripts/witness.devel/run.dev.witness.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CONTAINER_NAME=$1 4 | CONTAINER_NAME="${CONTAINER_NAME:-cil-witness-devel}" 5 | 6 | echo "Enter your password for PK (press Ctrl+D when done)" 7 | cat >temp.pk.password 8 | 9 | sudo PK_PASSWORD=`cat temp.pk.password` docker run --restart always \ 10 | -p 18223:18223 \ 11 | -d \ 12 | -v $(pwd)/sample.pk:/app/private \ 13 | --env-file sample.witness.env -e DEBUG="node:app, node:messages, node:messages:full" -e NODE_ENV=Devel -e PK_PASSWORD \ 14 | --name $CONTAINER_NAME \ 15 | trueshura/cil-core-staging:latest 16 | 17 | rm temp.pk.password 18 | -------------------------------------------------------------------------------- /runScripts/witness.devel/sample.pk: -------------------------------------------------------------------------------- 1 | 9003b3a7af0ff553d4e716b8b3c7cc823696fdcfae431c8fcc049a32444fd8c8gYkDoz15J7su/Y/RhywnjrDzmEDXlzoaGFI4Uz0CEF2LmYf4ZvINj7C1AFkojXp0DraELnJuCu7y3HQA21H7Klnc4q2zi333m6ViI0g8bfc= 2 | -------------------------------------------------------------------------------- /runScripts/witness.devel/sample.witness.env: -------------------------------------------------------------------------------- 1 | # see utils.js/mapEnvToOptions 2 | 3 | #RPC_USER=my_secret_user 4 | #RPC_PASS=my_secret_password 5 | 6 | RPC_ADDRESS=0.0.0.0 7 | WITNESS_NODE=true 8 | BUILD_TX_INDEX=true 9 | -------------------------------------------------------------------------------- /runScripts/witness/run.witness.no.update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CONTAINER_NAME=$1 4 | CONTAINER_NAME="${CONTAINER_NAME:-cil-witness}" 5 | 6 | echo "Enter your password for PK (press Ctrl+D when done)" 7 | cat >temp.pk.password 8 | 9 | sudo PK_PASSWORD=`cat temp.pk.password` docker run \ 10 | --restart always \ 11 | -p 8223:8223 \ 12 | -d \ 13 | -v $(pwd)/sample.pk:/app/private \ 14 | --env-file sample.witness.env -e PK_PASSWORD -e AUTO_UPDATE=false\ 15 | --name $CONTAINER_NAME \ 16 | trueshura/cil-core-prod:latest 17 | 18 | rm temp.pk.password 19 | -------------------------------------------------------------------------------- /runScripts/witness/run.witness.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CONTAINER_NAME=$1 4 | CONTAINER_NAME="${CONTAINER_NAME:-cil-witness}" 5 | 6 | echo "Enter your password for PK (press Ctrl+D when done)" 7 | cat >temp.pk.password 8 | 9 | sudo PK_PASSWORD=`cat temp.pk.password` docker run \ 10 | --restart always \ 11 | -p 8223:8223 \ 12 | -d \ 13 | -v $(pwd)/sample.pk:/app/private \ 14 | --env-file sample.witness.env -e PK_PASSWORD -e AUTO_UPDATE=true \ 15 | --name $CONTAINER_NAME \ 16 | trueshura/cil-core-prod:latest 17 | 18 | rm temp.pk.password 19 | -------------------------------------------------------------------------------- /runScripts/witness/sample.pk: -------------------------------------------------------------------------------- 1 | 9003b3a7af0ff553d4e716b8b3c7cc823696fdcfae431c8fcc049a32444fd8c8gYkDoz15J7su/Y/RhywnjrDzmEDXlzoaGFI4Uz0CEF2LmYf4ZvINj7C1AFkojXp0DraELnJuCu7y3HQA21H7Klnc4q2zi333m6ViI0g8bfc= 2 | -------------------------------------------------------------------------------- /runScripts/witness/sample.witness.env: -------------------------------------------------------------------------------- 1 | # see utils.js/mapEnvToOptions 2 | 3 | #RPC_USER=my_secret_user 4 | #RPC_PASS=my_secret_password 5 | RPC_ADDRESS=0.0.0.0 6 | 7 | WITNESS_NODE=true 8 | BUILD_TX_INDEX=true 9 | -------------------------------------------------------------------------------- /scripts/decryptKeyStore.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const factory = require('../factory'); 4 | const {questionAsync, readPrivateKeyFromFile} = require('../utils'); 5 | 6 | (async () => { 7 | await factory.asyncLoad(); 8 | 9 | const filename = await questionAsync('Enter filename with PK: '); 10 | try { 11 | const pk = await readPrivateKeyFromFile(factory.Crypto, path.resolve('../' + filename)); 12 | const kp = factory.Crypto.keyPairFromPrivate(pk); 13 | console.log(`Private key is: ${pk}`); 14 | console.log(`Address is: ${kp.address}`); 15 | console.error('Password is ok!'); 16 | } catch (e) { 17 | console.error(e); 18 | } 19 | })() 20 | .then(() => { 21 | process.exit(0); 22 | }) 23 | .catch(error => { 24 | console.error(error); 25 | process.exit(1); 26 | }); 27 | -------------------------------------------------------------------------------- /scripts/joinConcilium.js: -------------------------------------------------------------------------------- 1 | const url = require('url'); 2 | 3 | const factory = require('../factory'); 4 | const {questionAsync, readPrivateKeyFromFile, prepareForStringifyObject, queryRpc, getHttpData} = require('../utils'); 5 | 6 | let urlApi; 7 | let urlRpc; 8 | if (process.env.NODE_ENV === 'Devel') { 9 | urlApi = 'https://test-explorer.ubikiri.com/api/'; 10 | urlRpc = 'http://localhost:18222'; 11 | } else { 12 | urlApi = 'https://explorer.ubikiri.com/api/'; 13 | urlRpc = 'http://localhost:8222'; 14 | } 15 | 16 | const nConciliumId = process.env.CONCILIUM_ID ? parseInt(process.env.CONCILIUM_ID) : 1; 17 | const nMinAmount = process.env.MIN_AMOUNT ? parseFloat(process.env.MIN_AMOUNT) : 2e7; 18 | 19 | main() 20 | .then(() => { 21 | process.exit(0); 22 | }) 23 | .catch(err => { 24 | console.error(err); 25 | process.exit(1); 26 | }); 27 | 28 | async function main() { 29 | const privateKey = await readPrivateKeyFromFile(factory.Crypto, './private'); 30 | const wallet = new factory.Wallet(privateKey); 31 | 32 | const amount = parseFloat(await questionAsync(`Enter amount to deposit (minimum ${nMinAmount} coins):`)); 33 | if (amount < nMinAmount) { 34 | throw new Error(`You want to deposit (${amount} coins). It's less than minumum (${nMinAmount})`); 35 | } 36 | 37 | const fees = 4e4; 38 | const arrUtxos = await getUtxos(wallet.address); 39 | const {arrCoins} = gatherInputsForAmount(arrUtxos, amount + fees); 40 | 41 | const tx = joinConcilium(nConciliumId, amount, wallet, arrCoins); 42 | console.error(`Here is TX containment: ${JSON.stringify(prepareForStringifyObject(tx.rawData), undefined, 2)}`); 43 | console.log(`Tx hash ${tx.getHash()}`); 44 | // console.log(tx.encode().toString('hex')); 45 | await sendTx(tx.encode().toString('hex')); 46 | } 47 | 48 | /** 49 | * 50 | * @param {Number} conciliumId 51 | * @param {Number} amount 52 | * @param {Wallet} wallet 53 | * @param {Array} arrUtxos - [{"hash", "nOut", "amount","isStable"}] 54 | * @returns {*} 55 | */ 56 | 57 | function joinConcilium(conciliumId, amount, wallet, arrUtxos) { 58 | const contractCode = { 59 | method: 'joinConcilium', 60 | arrArguments: [conciliumId] 61 | }; 62 | 63 | const tx = factory.Transaction.invokeContract( 64 | factory.Constants.CONCILIUM_DEFINITION_CONTRACT_ADDRESS, 65 | contractCode, 66 | amount, 67 | wallet.address 68 | ); 69 | 70 | for (let utxo of arrUtxos) { 71 | console.log(`Using UTXo ${utxo.hash} idx ${utxo.nOut}`); 72 | tx.addInput(utxo.hash, utxo.nOut); 73 | } 74 | 75 | tx.signForContract(wallet.privateKey); 76 | 77 | return tx; 78 | } 79 | 80 | async function getUtxos(strAddress) { 81 | return await queryApi('Unspent', strAddress); 82 | } 83 | 84 | async function queryApi(endpoint, strParam) { 85 | const result = await getHttpData(url.resolve(urlApi, `${endpoint}/${strParam}`)); 86 | return result; 87 | } 88 | 89 | async function sendTx(strTx) { 90 | return queryRpc(urlRpc, 'sendRawTx', {strTx}); 91 | } 92 | 93 | /** 94 | * Well use big inputs first 95 | * 96 | * @param {Array} arrUtxos of {hash, nOut, amount} 97 | * @param {Number} amount TO SEND (not including fees) 98 | * @return {arrCoins, gathered} 99 | */ 100 | function gatherInputsForAmount(arrUtxos, amount) { 101 | const nFeePerInput = factory.Constants.fees.TX_FEE * 0.12; 102 | const arrCoins = []; 103 | let gathered = 0; 104 | 105 | for (let coins of arrUtxos.sort((a, b) => b.amount - a.amount)) { 106 | if (!coins.amount) continue; 107 | gathered += coins.amount; 108 | arrCoins.push(coins); 109 | if (gathered > amount + nFeePerInput * arrCoins.length) return {arrCoins, gathered}; 110 | } 111 | throw new Error('Not enough coins!'); 112 | } 113 | -------------------------------------------------------------------------------- /scripts/leaveConcilium.js: -------------------------------------------------------------------------------- 1 | const url = require('url'); 2 | 3 | const factory = require('../factory'); 4 | const {readPrivateKeyFromFile, prepareForStringifyObject, queryRpc, getHttpData} = require('../utils'); 5 | 6 | let urlApi; 7 | let urlRpc; 8 | if (process.env.NODE_ENV === 'Devel') { 9 | urlApi = 'https://test-explorer.ubikiri.com/api/'; 10 | urlRpc = 'http://localhost:18222'; 11 | } else { 12 | urlApi = 'https://explorer.ubikiri.com/api/'; 13 | urlRpc = 'http://localhost:8222'; 14 | } 15 | 16 | const nConciliumId = process.env.CONCILIUM_ID ? parseInt(process.env.CONCILIUM_ID) : 1; 17 | const nEnoughCoinsToLeave = 4e4; 18 | 19 | main() 20 | .then(() => { 21 | process.exit(0); 22 | }) 23 | .catch(err => { 24 | console.error(err); 25 | process.exit(1); 26 | }); 27 | 28 | async function main() { 29 | const privateKey = await readPrivateKeyFromFile(factory.Crypto, './private'); 30 | const wallet = new factory.Wallet(privateKey); 31 | 32 | const arrUtxos = await getUtxos(wallet.address); 33 | const {arrCoins} = gatherInputsForAmount( 34 | arrUtxos.sort((a, b) => b.amount - a.amount), 35 | nEnoughCoinsToLeave 36 | ); 37 | 38 | const tx = leaveConcilium(nConciliumId, wallet, arrCoins); 39 | console.error(`Here is TX containment: ${JSON.stringify(prepareForStringifyObject(tx.rawData), undefined, 2)}`); 40 | // console.log(tx.encode().toString('hex')); 41 | console.log(tx.getHash()); 42 | await sendTx(tx.encode().toString('hex')); 43 | } 44 | 45 | /** 46 | * 47 | * @param {Number} conciliumId 48 | * @param {Wallet} wallet 49 | * @param {Array} arrUtxos - [{"hash", "nOut", "amount","isStable"}] 50 | * @returns {*} 51 | */ 52 | 53 | function leaveConcilium(conciliumId, wallet, arrUtxos) { 54 | const contractCode = { 55 | method: 'leaveConcilium', 56 | arrArguments: [conciliumId] 57 | }; 58 | 59 | const tx = factory.Transaction.invokeContract( 60 | factory.Constants.CONCILIUM_DEFINITION_CONTRACT_ADDRESS, 61 | contractCode, 62 | 0, 63 | wallet.address 64 | ); 65 | 66 | let gathered = 0; 67 | for (let utxo of arrUtxos) { 68 | console.log(`Using UTXo ${utxo.hash} idx ${utxo.nOut}`); 69 | tx.addInput(utxo.hash, utxo.nOut); 70 | gathered += utxo.amount; 71 | } 72 | 73 | // magic constant 2000 - some sorta "minimal useful" UTXO 74 | if (gathered - nEnoughCoinsToLeave > 2000) { 75 | tx.addReceiver(gathered - nEnoughCoinsToLeave, Buffer.from(wallet.address, 'hex')); 76 | } 77 | tx.signForContract(wallet.privateKey); 78 | 79 | return tx; 80 | } 81 | 82 | async function getUtxos(strAddress) { 83 | return await queryApi('Unspent', strAddress); 84 | } 85 | 86 | async function queryApi(endpoint, strParam) { 87 | const result = await getHttpData(url.resolve(urlApi, `${endpoint}/${strParam}`)); 88 | return result; 89 | } 90 | 91 | async function sendTx(strTx) { 92 | return queryRpc(urlRpc, 'sendRawTx', {strTx}); 93 | } 94 | 95 | /** 96 | * 97 | * @param {Array} arrUtxos of {hash, nOut, amount} 98 | * @param {Number} amount TO SEND (not including fees) 99 | * @return {arrCoins, gathered} 100 | */ 101 | function gatherInputsForAmount(arrUtxos, amount) { 102 | const nFeePerInput = factory.Constants.fees.TX_FEE * 0.12; 103 | const arrCoins = []; 104 | let gathered = 0; 105 | for (let coins of arrUtxos) { 106 | if (!coins.amount) continue; 107 | gathered += coins.amount; 108 | arrCoins.push(coins); 109 | if (gathered > amount + nFeePerInput * arrCoins.length) return {arrCoins, gathered}; 110 | } 111 | throw new Error('Not enough coins!'); 112 | } 113 | -------------------------------------------------------------------------------- /scripts/nodeStatus.js: -------------------------------------------------------------------------------- 1 | const {queryRpc} = require('../utils'); 2 | 3 | let urlRpc; 4 | if (process.env.NODE_ENV === 'Devel') { 5 | urlRpc = 'http://localhost:18222'; 6 | } else { 7 | urlRpc = 'http://localhost:8222'; 8 | } 9 | 10 | main() 11 | .then(() => { 12 | process.exit(0); 13 | }) 14 | .catch(err => { 15 | console.error(err); 16 | process.exit(1); 17 | }); 18 | 19 | async function main() { 20 | const arrResult = await queryRpc(urlRpc, 'getTips'); 21 | 22 | let objLastBlock = undefined; 23 | let strLastHash = undefined; 24 | arrResult.forEach(({hash, block}) => { 25 | if (!objLastBlock || (objLastBlock && objLastBlock.header.timestamp < block.header.timestamp)) { 26 | objLastBlock = block; 27 | strLastHash = hash; 28 | } 29 | }); 30 | 31 | const nSecDiff = parseInt(Date.now() / 1000) - objLastBlock.header.timestamp; 32 | console.log(`Last block "${strLastHash}" received ${nSecDiff} seconds ago.`); 33 | 34 | const strStatus = nSecDiff < 600 ? 'Alive' : 'Syncing or DEAD'; 35 | console.log(`Status: ${strStatus}`); 36 | } 37 | -------------------------------------------------------------------------------- /scripts/savePrivateKey.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const factory = require('../factory'); 4 | const {questionAsync, prepareForStringifyObject} = require('../utils'); 5 | 6 | (async () => { 7 | await factory.asyncLoad(); 8 | 9 | const pk = await questionAsync('Enter private key: '); 10 | 11 | // TODO suppress echo 12 | const password = await questionAsync('Enter password: '); 13 | const passwordCheck = await questionAsync('Repeat password: '); 14 | if (password !== passwordCheck) throw 'Passwords are not same!'; 15 | 16 | const filename = await questionAsync('Enter filename (empty for docker): '); 17 | const keyGenFunction = await questionAsync( 18 | 'Enter key generation mechanism (avail: "pbkdf2", "scrypt". default: "scrypt"): ' 19 | ); 20 | 21 | const objEncryptedPk = await factory.Crypto.encrypt( 22 | password, 23 | Buffer.from(pk, 'hex'), 24 | keyGenFunction === '' ? 'scrypt' : keyGenFunction 25 | ); 26 | 27 | const kp = factory.Crypto.keyPairFromPrivate(pk); 28 | 29 | const objKeyFileContent = JSON.stringify({ 30 | address: 'Ux' + kp.address, 31 | ...prepareForStringifyObject(objEncryptedPk), 32 | version: 1.1 33 | }); 34 | 35 | console.error(objKeyFileContent); 36 | if (filename && filename.length) fs.writeFileSync(filename, objKeyFileContent); 37 | })() 38 | .then(() => { 39 | process.exit(0); 40 | }) 41 | .catch(error => { 42 | console.error(error); 43 | process.exit(1); 44 | }); 45 | -------------------------------------------------------------------------------- /storage/storageWithCache.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (PersistentStorage, factory) => 4 | class StorageWithCache extends PersistentStorage { 5 | constructor(options) { 6 | super(options); 7 | this._cachedContracts = { 8 | object: {}, 9 | raw: {} 10 | }; 11 | } 12 | 13 | /** 14 | * 15 | * @param {Buffer | String} address 16 | * @param {Boolean} raw 17 | * @return {Promise} 18 | */ 19 | async getContract(address, raw = false) { 20 | const contracts = raw ? this._cachedContracts.raw : this._cachedContracts.object; 21 | if (contracts[address] !== undefined) { 22 | return contracts[address]; 23 | } 24 | 25 | const contract = await super.getContract(address, raw); 26 | 27 | if (!contract) return undefined; 28 | 29 | const objContract = raw ? new factory.Contract(contract) : contract; 30 | 31 | if (objContract.getDataSize() <= factory.Constants.CONTRACT_MIN_CASHING_SIZE) { 32 | return contract; 33 | } 34 | 35 | contracts[address] = contract; 36 | 37 | return contract; 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /structures/arrayOf.js: -------------------------------------------------------------------------------- 1 | const typeforce = require('typeforce'); 2 | 3 | module.exports = nEntityLength => { 4 | typeforce(typeforce.Number, nEntityLength); 5 | 6 | /** 7 | * Used for serialization in storage & MsgGetBlocks 8 | * this._arrOf contains Buffers, 9 | * BUT 10 | * this.getArray returns Strings! 11 | */ 12 | return class ArrayOf { 13 | /** 14 | * 15 | * @param {Array | Buffer} data 16 | */ 17 | constructor(data) { 18 | if (Buffer.isBuffer(data)) { 19 | this._ensureLength(data); 20 | 21 | this._arrOf = []; 22 | for (let start = 0; start < data.length; start += nEntityLength) { 23 | this._arrOf.push(data.slice(start, start + nEntityLength)); 24 | } 25 | } else if (Array.isArray(data)) { 26 | this._arrOf = data.map(e => { 27 | this._ensureLength(e); 28 | return Buffer.isBuffer(e) ? e : Buffer.from(e, 'hex'); 29 | }); 30 | } else { 31 | throw new Error('Construct Array of (HASHES | ADDRESSES) or decode from buffer'); 32 | } 33 | } 34 | 35 | /** 36 | * 37 | * @return {string[]} !!!! 38 | */ 39 | getArray() { 40 | return this._arrOf.map(e => e.toString('hex')); 41 | } 42 | 43 | /** 44 | * 45 | * @return {Buffer} 46 | */ 47 | encode() { 48 | return Buffer.concat(this._arrOf); 49 | } 50 | 51 | _ensureLength(element) { 52 | if (element.length % nEntityLength) { 53 | throw new Error(`Buffer you trying to decode not ${nEntityLength} bytes aligned!`); 54 | } 55 | } 56 | }; 57 | }; 58 | -------------------------------------------------------------------------------- /structures/blockInfo.js: -------------------------------------------------------------------------------- 1 | const typeforce = require('typeforce'); 2 | 3 | // block awaits for parents to be executed 4 | const IN_FLIGHT_BLOCK = 1 << 1; 5 | 6 | // block executed successfully 7 | const EXECUTED_BLOCK = 1 << 2; 8 | 9 | // block processed and it's UTXO are stored in DB 10 | const FINAL_BLOCK = 1 << 3; 11 | 12 | // block cannot be executed (validation failed) 13 | const BAD_BLOCK = 1 << 8; 14 | // Class to store block header + additional info in DB 15 | module.exports = ({/*Constants,*/ Crypto}, {blockInfoProto, blockHeaderProto}) => 16 | class BlockInfo { 17 | /** 18 | * 19 | * @param {Object | Buffer} data - block header or serialized data 20 | */ 21 | constructor(data) { 22 | typeforce(typeforce.oneOf('Object', 'Buffer'), data); 23 | 24 | if (Buffer.isBuffer(data)) { 25 | this._data = blockInfoProto.decode(data); 26 | } else if (typeof data === 'object') { 27 | const errMsg = blockHeaderProto.verify(data); 28 | if (errMsg) throw new Error(`BlockInfo: ${errMsg}`); 29 | this._data = { 30 | header: blockHeaderProto.create(data), 31 | flags: EXECUTED_BLOCK 32 | }; 33 | } 34 | } 35 | 36 | /** 37 | * 38 | * @returns {Array} of strings! 39 | */ 40 | get parentHashes() { 41 | return this._data.header.parentHashes.map(hash => hash.toString('hex')); 42 | } 43 | 44 | /** 45 | * 46 | * @returns {String} 47 | */ 48 | getHash() { 49 | return Crypto.createHash(blockHeaderProto.encode(this._data.header).finish()); 50 | } 51 | 52 | getHeight() { 53 | return this._data.header.height; 54 | } 55 | 56 | get conciliumId() { 57 | return this._data.header.conciliumId; 58 | } 59 | 60 | getConciliumId() { 61 | return this._data.header.conciliumId; 62 | } 63 | 64 | getHeader() { 65 | return this._data.header; 66 | } 67 | 68 | markAsBad() { 69 | this._data.flags = BAD_BLOCK; 70 | } 71 | 72 | isBad() { 73 | // return !!(this._data.flags & BAD_BLOCK); 74 | return this._data.flags === BAD_BLOCK; 75 | } 76 | 77 | markAsFinal() { 78 | this._data.flags = FINAL_BLOCK; 79 | } 80 | 81 | isFinal() { 82 | return this._data.flags === FINAL_BLOCK; 83 | } 84 | 85 | markAsInFlight() { 86 | this._data.flags = IN_FLIGHT_BLOCK; 87 | } 88 | 89 | isInFlight() { 90 | return this._data.flags === IN_FLIGHT_BLOCK; 91 | } 92 | 93 | encode() { 94 | return blockInfoProto.encode(this._data).finish(); 95 | } 96 | 97 | getState() { 98 | return this._data.flags; 99 | } 100 | }; 101 | -------------------------------------------------------------------------------- /structures/coins.js: -------------------------------------------------------------------------------- 1 | // part of protobuff 2 | const Long = require('long'); 3 | const typeforce = require('typeforce'); 4 | const types = require('../types'); 5 | 6 | module.exports = () => 7 | class Coins { 8 | constructor(amount, receiverAddr) { 9 | typeforce(typeforce.tuple(types.Amount, types.Address), arguments); 10 | 11 | this._data = { 12 | amount, 13 | receiverAddr: Buffer.isBuffer(receiverAddr) ? receiverAddr : Buffer.from(receiverAddr, 'hex') 14 | }; 15 | } 16 | 17 | static createFromData({amount, receiverAddr}) { 18 | if (Long.isLong(amount)) amount = amount.toNumber(); 19 | 20 | return new this(amount, receiverAddr); 21 | } 22 | 23 | getAmount() { 24 | return this._data.amount; 25 | } 26 | 27 | /** 28 | * 29 | * @return {Buffer} address 30 | */ 31 | getReceiverAddr() { 32 | return this._data.receiverAddr; 33 | } 34 | 35 | /** 36 | * 37 | * @return {{amount: *, receiverAddr: *}|*} 38 | */ 39 | getRawData() { 40 | return this._data; 41 | } 42 | 43 | /** 44 | * 45 | * @param {Coins} coin 46 | * @returns {boolean|*} 47 | */ 48 | equals(coin) { 49 | return this.getAmount() === coin.getAmount() && this.getReceiverAddr().equals(coin.getReceiverAddr()); 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /structures/inventory.js: -------------------------------------------------------------------------------- 1 | const typeforce = require('typeforce'); 2 | const types = require('../types'); 3 | 4 | module.exports = ({Constants /*Crypto*/}, {inventoryProto}) => 5 | class Inventory { 6 | constructor(data) { 7 | typeforce(typeforce.oneOf('Number', 'Buffer', types.Empty), data); 8 | 9 | this._setHashes = new Set(); 10 | if (data === undefined) data = {}; 11 | 12 | if (Buffer.isBuffer(data)) { 13 | this._data = {...inventoryProto.decode(data)}; 14 | for (let elem of this._data.invVector) { 15 | this._setHashes.add(elem.hash.toString('hex')); 16 | } 17 | } else if (typeof data === 'object') { 18 | const errMsg = inventoryProto.verify(data); 19 | if (errMsg) throw new Error(`Inventory: ${errMsg}`); 20 | 21 | this._data = inventoryProto.create(data); 22 | } 23 | } 24 | 25 | encode() { 26 | return inventoryProto.encode(this._data).finish(); 27 | } 28 | 29 | /** 30 | * 31 | * @param {Transaction} tx 32 | */ 33 | addTx(tx) { 34 | if (this._wasAlreadyAdded(tx.hash())) return; 35 | 36 | const vector = {type: Constants.INV_TX, hash: Buffer.from(tx.hash(), 'hex')}; 37 | this._data.invVector.push(vector); 38 | this._markAsAdded(tx.hash()); 39 | } 40 | 41 | /** 42 | * 43 | * @param {String | Buffer} hash 44 | */ 45 | addTxHash(hash) { 46 | typeforce(types.Hash256bit, hash); 47 | hash = Buffer.isBuffer(hash) ? hash : Buffer.from(hash, 'hex'); 48 | const strHash = Buffer.isBuffer(hash) ? hash.toString('hex') : hash; 49 | 50 | if (this._wasAlreadyAdded(strHash)) return; 51 | 52 | const vector = {type: Constants.INV_TX, hash}; 53 | this._data.invVector.push(vector); 54 | this._markAsAdded(strHash); 55 | } 56 | /** 57 | * 58 | * @param {Block} block 59 | */ 60 | addBlock(block) { 61 | if (this._wasAlreadyAdded(block.hash())) return; 62 | 63 | const vector = {type: Constants.INV_BLOCK, hash: Buffer.from(block.getHash(), 'hex')}; 64 | this._data.invVector.push(vector); 65 | this._markAsAdded(block.hash()); 66 | } 67 | 68 | /** 69 | * 70 | * @param {String | Buffer} hash 71 | */ 72 | addBlockHash(hash) { 73 | typeforce(types.Hash256bit, hash); 74 | hash = Buffer.isBuffer(hash) ? hash : Buffer.from(hash, 'hex'); 75 | const strHash = Buffer.isBuffer(hash) ? hash.toString('hex') : hash; 76 | 77 | if (this._wasAlreadyAdded(strHash)) return; 78 | 79 | const vector = {type: Constants.INV_BLOCK, hash}; 80 | this._data.invVector.push(vector); 81 | this._markAsAdded(strHash); 82 | } 83 | 84 | get vector() { 85 | return this._data.invVector; 86 | } 87 | 88 | addVector(vector) { 89 | typeforce(types.InvVector, vector); 90 | 91 | this._data.invVector.push(vector); 92 | } 93 | 94 | _wasAlreadyAdded(strHash) { 95 | return this._setHashes.has(strHash); 96 | } 97 | 98 | _markAsAdded(strHash) { 99 | this._setHashes.add(strHash); 100 | } 101 | }; 102 | -------------------------------------------------------------------------------- /structures/txReceipt.js: -------------------------------------------------------------------------------- 1 | const typeforce = require('typeforce'); 2 | const types = require('../types'); 3 | const assert = require('assert'); 4 | 5 | module.exports = ({Constants, Coins}, {txReceiptProto}) => 6 | /** 7 | * Used for serialization in storage & MsgGetBlocks 8 | */ 9 | class TxReceipt { 10 | /** 11 | * 12 | * @param {Array | Buffer} data 13 | */ 14 | constructor(data) { 15 | typeforce(typeforce.oneOf('Object', 'Buffer'), data); 16 | 17 | if (Buffer.isBuffer(data)) { 18 | this._data = txReceiptProto.decode(data); 19 | } else { 20 | const errMsg = txReceiptProto.verify(data); 21 | if (errMsg) throw new Error(`TxReceipt: ${errMsg}`); 22 | 23 | this._data = txReceiptProto.create(data); 24 | } 25 | } 26 | 27 | /** 28 | * 29 | * @param {Object} data - raw data of this class 30 | * @returns {this} 31 | */ 32 | static createFromData(data) { 33 | data.__proto__ = this.prototype; 34 | return data; 35 | } 36 | 37 | /** 38 | * 39 | * @param {TxReceipt} receiptToMerge 40 | * @returns {TxReceipt} 41 | */ 42 | merge(receiptToMerge) { 43 | this._data.internalTxns = this._data.internalTxns.concat(receiptToMerge._data.internalTxns); 44 | this._data.coins = this._data.coins.concat(receiptToMerge._data.coins); 45 | 46 | // Scenario is following: 47 | // - we already have receipt for some tx 48 | // - and "receiptToMerge" expected to have cumulative coinsUsed 49 | assert(receiptToMerge.getCoinsUsed() >= this.getCoinsUsed(), 'receiptToMerge have more coinsUsed'); 50 | this._updateCoinsUsed(receiptToMerge.getCoinsUsed()); 51 | 52 | this.setStatus( 53 | receiptToMerge.isSuccessful() && this.isSuccessful() 54 | ? Constants.TX_STATUS_OK 55 | : Constants.TX_STATUS_FAILED 56 | ); 57 | 58 | return this; 59 | } 60 | 61 | /** 62 | * 63 | * @return {String} 64 | */ 65 | getContractAddress() { 66 | return this._data.contractAddress.toString('hex'); 67 | } 68 | 69 | /** 70 | * 71 | * @return {Number} 72 | */ 73 | getCoinsUsed() { 74 | return this._data.coinsUsed; 75 | } 76 | 77 | /** 78 | * 79 | */ 80 | _updateCoinsUsed(nNewValue) { 81 | this._data.coinsUsed = nNewValue; 82 | } 83 | 84 | /** 85 | * 86 | * @return {Number} 87 | */ 88 | getStatus() { 89 | return this._data.status; 90 | } 91 | 92 | setStatus(newStatus) { 93 | this._data.status = newStatus; 94 | } 95 | 96 | isSuccessful() { 97 | return this._data.status === Constants.TX_STATUS_OK; 98 | } 99 | 100 | /** 101 | * 102 | * @return {String} 103 | */ 104 | getMessage() { 105 | return this._data.message; 106 | } 107 | 108 | /** 109 | * 110 | * @return {Buffer} 111 | */ 112 | encode() { 113 | return txReceiptProto.encode(this._data).finish(); 114 | } 115 | 116 | equals(receipt) { 117 | return this.encode().equals(receipt.encode()); 118 | } 119 | 120 | /** 121 | * 122 | * @param {UTXO} utxo with only ONE COIN! @see node._createInternalTx 123 | */ 124 | addInternalUtxo(utxo) { 125 | typeforce(types.UTXO, utxo); 126 | 127 | this._data.internalTxns.push(Buffer.from(utxo.getTxHash(), 'hex')); 128 | this._data.coins.push(utxo.coinsAtIndex(0).getRawData()); 129 | } 130 | 131 | /** 132 | * 133 | * @returns {Array} of BUFFERS! 134 | */ 135 | getInternalTxns() { 136 | return this._data.internalTxns; 137 | } 138 | 139 | /** 140 | * 141 | * @param {Buffer | String} hash - internal TX hash 142 | * @return {Coins} 143 | */ 144 | getCoinsForTx(hash) { 145 | typeforce(types.Hash256bit, hash); 146 | 147 | hash = Buffer.isBuffer(hash) ? hash : Buffer.from(hash, 'hex'); 148 | 149 | const idx = this._data.internalTxns.findIndex(buffHashI => buffHashI.equals(hash)); 150 | if (!~idx) throw new Error(`"${hash.toString('hex')}" not found in receipt`); 151 | 152 | return Coins.createFromData(this._data.coins[idx]); 153 | } 154 | 155 | toObject() { 156 | return { 157 | ...this._data, 158 | contractAddress: this._data.contractAddress ? this._data.contractAddress.toString('hex') : undefined, 159 | internalTxns: this._data.internalTxns.map(buffHash => buffHash.toString('hex')) 160 | }; 161 | } 162 | }; 163 | -------------------------------------------------------------------------------- /tests/integration/bft.integration.spec.js: -------------------------------------------------------------------------------- 1 | const {describe, it} = require('mocha'); 2 | const {assert} = require('chai'); 3 | 4 | const factory = require('../testFactory'); 5 | 6 | const conciliumId = 11; 7 | 8 | const createDummyBFT = (conciliumId = 0, numOfKeys = 2) => { 9 | const arrKeyPairs = []; 10 | const arrAddresses = []; 11 | for (let i = 0; i < numOfKeys; i++) { 12 | const keyPair = factory.Crypto.createKeyPair(); 13 | arrKeyPairs.push(keyPair); 14 | arrAddresses.push(keyPair.address); 15 | } 16 | const newWallet = new factory.Wallet(arrKeyPairs[0].privateKey); 17 | 18 | const concilium = factory.ConciliumRr.create(conciliumId, arrAddresses); 19 | 20 | const newBft = new factory.BFT({ 21 | concilium, 22 | wallet: newWallet 23 | }); 24 | newBft._stopTimer(); 25 | 26 | return {arrKeyPairs, newWallet, concilium, newBft}; 27 | }; 28 | 29 | const createBlockAckMessage = (conciliumId, privateKey, blockHash) => { 30 | const msgBlockAck = new factory.Messages.MsgWitnessBlockVote({conciliumId, blockHash}); 31 | msgBlockAck.sign(privateKey); 32 | return msgBlockAck; 33 | }; 34 | 35 | describe('BFT consensus integration tests', () => { 36 | before(async function () { 37 | this.timeout(15000); 38 | 39 | await factory.asyncLoad(); 40 | }); 41 | 42 | after(async function () { 43 | this.timeout(15000); 44 | }); 45 | 46 | it('should fail to get signatures (voted for different blocks)', async () => { 47 | const { 48 | newBft, 49 | concilium, 50 | arrKeyPairs: [keyPair1, keyPair2] 51 | } = createDummyBFT(); 52 | const [myWalletAddress, anotherAddress] = concilium.getAddresses(); 53 | 54 | const fakeBlockHash = Buffer.from(factory.Crypto.randomBytes(32)); 55 | const fakeBlockHash2 = Buffer.from(factory.Crypto.randomBytes(32)); 56 | 57 | newBft._resetState(); 58 | newBft._block = { 59 | hash: () => fakeBlockHash.toString('hex') 60 | }; 61 | 62 | const createBlockAckMessage = (conciliumId, privateKey, blockHash) => { 63 | const msgBlockAck = new factory.Messages.MsgWitnessBlockVote({conciliumId, blockHash}); 64 | msgBlockAck.sign(privateKey); 65 | return msgBlockAck; 66 | }; 67 | 68 | // Message received from party 69 | const msgParty = createBlockAckMessage(conciliumId, keyPair2.privateKey, fakeBlockHash); 70 | newBft._addViewOfNodeWithAddr(anotherAddress, anotherAddress, {...msgParty.content}); 71 | 72 | // My message 73 | const msgMy = createBlockAckMessage(conciliumId, keyPair1.privateKey, fakeBlockHash2); 74 | newBft._addViewOfNodeWithAddr(myWalletAddress, myWalletAddress, {...msgMy.content}); 75 | 76 | // My message returned by party 77 | newBft._addViewOfNodeWithAddr(anotherAddress, myWalletAddress, {...msgMy.content}); 78 | 79 | // Party message exposed by me 80 | newBft._addViewOfNodeWithAddr(myWalletAddress, anotherAddress, {...msgParty.content}); 81 | 82 | const arrSignatures = newBft._getSignaturesForBlock(); 83 | assert.isNotOk(arrSignatures); 84 | }); 85 | 86 | it('should get signatures', async () => { 87 | const { 88 | newBft, 89 | concilium, 90 | arrKeyPairs: [keyPair1, keyPair2] 91 | } = createDummyBFT(); 92 | const [myWalletAddress, anotherAddress] = concilium.getAddresses(); 93 | 94 | const fakeBlockHash = Buffer.from(factory.Crypto.randomBytes(32)); 95 | 96 | newBft._resetState(); 97 | newBft._block = { 98 | hash: () => fakeBlockHash.toString('hex') 99 | }; 100 | 101 | // Message received from party 102 | const msgParty = createBlockAckMessage(conciliumId, keyPair2.privateKey, fakeBlockHash); 103 | newBft._addViewOfNodeWithAddr(anotherAddress, anotherAddress, {...msgParty.content}); 104 | 105 | // My message 106 | const msgMy = createBlockAckMessage(conciliumId, keyPair1.privateKey, fakeBlockHash); 107 | newBft._addViewOfNodeWithAddr(myWalletAddress, myWalletAddress, {...msgMy.content}); 108 | 109 | // My message returned by party 110 | newBft._addViewOfNodeWithAddr(anotherAddress, myWalletAddress, {...msgMy.content}); 111 | 112 | // Party message exposed by me 113 | newBft._addViewOfNodeWithAddr(myWalletAddress, anotherAddress, {...msgParty.content}); 114 | 115 | // emulate workflow, state will be reset and _getSignaturesForBlock will use stored _prevViews 116 | newBft._resetState(); 117 | const arrSignatures = newBft._getSignaturesForBlock(); 118 | assert.isOk(arrSignatures); 119 | assert.equal(arrSignatures.length, 2); 120 | 121 | // it depends on sorting 122 | assert.isOk(arrSignatures[0].equals(msgMy.hashSignature) || arrSignatures[1].equals(msgMy.hashSignature)); 123 | assert.isOk(arrSignatures[0].equals(msgParty.hashSignature) || arrSignatures[1].equals(msgParty.hashSignature)); 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /tests/integration/peerSameHost.integration.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {describe, it} = require('mocha'); 4 | const {assert} = require('chai'); 5 | 6 | const factory = require('../testFactoryIpV6'); 7 | 8 | let nodeSeed; 9 | let node2; 10 | let node3; 11 | 12 | const sleep = delay => { 13 | return new Promise(resolve => { 14 | setTimeout(() => resolve(), delay); 15 | }); 16 | }; 17 | 18 | describe('Same host, different port peers', () => { 19 | before(async function () { 20 | this.timeout(15000); 21 | 22 | await factory.asyncLoad(); 23 | 24 | nodeSeed = new factory.Node({ 25 | listenAddr: '127.0.0.1', 26 | trustAnnounce: true, 27 | seed: true 28 | }); 29 | 30 | node2 = new factory.Node({ 31 | listenAddr: '127.0.0.1', 32 | listenPort: 22222, 33 | trustAnnounce: true, 34 | arrSeedAddresses: ['127.0.0.1'] 35 | }); 36 | 37 | node3 = new factory.Node({ 38 | listenAddr: '127.0.0.1', 39 | listenPort: 33333, 40 | trustAnnounce: true, 41 | arrSeedAddresses: ['127.0.0.1'] 42 | }); 43 | 44 | await nodeSeed.ensureLoaded(); 45 | await nodeSeed.bootstrap(); 46 | 47 | await node2.ensureLoaded(); 48 | await node2.bootstrap(); 49 | 50 | await sleep(2000); 51 | 52 | await node3.ensureLoaded(); 53 | await node3.bootstrap(); 54 | }); 55 | 56 | after(async function () { 57 | this.timeout(15000); 58 | }); 59 | 60 | it('should connect both node to seed', async () => { 61 | const arrPeers = nodeSeed._peerManager.getConnectedPeers(); 62 | 63 | assert.equal(arrPeers.length, 2); 64 | }); 65 | 66 | it('should announce node2 to node3 via seed', async () => { 67 | const arrPeers = node3._peerManager.filterPeers(undefined, true); 68 | 69 | assert.equal(arrPeers.length, 2); 70 | assert.equal(arrPeers[0].port, factory.Constants.port); 71 | assert.equal(arrPeers[1].port, 22222); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /tests/testFactory.js: -------------------------------------------------------------------------------- 1 | const Mutex = require('mutex'); 2 | const BaseFactory = require('../baseFactory'); 3 | const config = require('../config/test.conf'); 4 | 5 | global.logger = console; 6 | global.logger.debug = console.log; 7 | global.logger.error = console.error; 8 | 9 | /** 10 | * Class to easy replacement used components 11 | */ 12 | 13 | const TransportWrapper = require('../network/testTransport'); 14 | 15 | class TestFactory extends BaseFactory { 16 | constructor(options, objConstants) { 17 | super(options, objConstants); 18 | } 19 | 20 | initSpecific() { 21 | this._transportImplemetation = TransportWrapper(this); 22 | } 23 | } 24 | 25 | module.exports = new TestFactory( 26 | { 27 | testStorage: true, 28 | mutex: new Mutex(), 29 | workerSuspended: true 30 | }, 31 | config.constants 32 | ); 33 | -------------------------------------------------------------------------------- /tests/testFactoryIpV6.js: -------------------------------------------------------------------------------- 1 | const Mutex = require('mutex'); 2 | const debugLib = require('debug'); 3 | 4 | const config = require('../config/test.conf'); 5 | const BaseFactory = require('../baseFactory'); 6 | 7 | // Uncomment in prod!! 8 | const error = console.error; 9 | const log = console.log; 10 | const info = console.info; 11 | info.log = console.info.bind(console); 12 | 13 | const debug = debugLib('node:app'); 14 | debug.log = console.log.bind(console); 15 | 16 | // simple logger 17 | global.logger = { 18 | error: (...msgs) => error(msgs), 19 | log: (...msgs) => log(msgs), 20 | info: (...msgs) => info(msgs), 21 | debug: (...msgs) => debug(msgs) 22 | }; 23 | 24 | /** 25 | * Class to easy replacement used components 26 | */ 27 | 28 | const Ipv6TransportWrapper = require('../network/ipv6Transport'); 29 | 30 | class TestIpV6Factory extends BaseFactory { 31 | constructor(options, objConstants) { 32 | super(options, objConstants); 33 | } 34 | 35 | initSpecific() { 36 | this._transportImplemetation = Ipv6TransportWrapper(this); 37 | } 38 | } 39 | 40 | module.exports = new TestIpV6Factory( 41 | { 42 | testStorage: true, 43 | mutex: new Mutex(), 44 | workerSuspended: true 45 | }, 46 | config.constants 47 | ); 48 | -------------------------------------------------------------------------------- /tests/testUtil.js: -------------------------------------------------------------------------------- 1 | const pseudoRandomBuffer = (length = 32) => { 2 | const pseudoRandomBytes = Buffer.allocUnsafe(length); 3 | 4 | // this will prevent all zeroes buffer (it will make tx invalid 5 | pseudoRandomBytes[0] = parseInt(Math.random() * 255); 6 | return pseudoRandomBytes; 7 | }; 8 | 9 | const generateAddress = () => { 10 | return pseudoRandomBuffer(20); 11 | }; 12 | 13 | const createDummyTx = (hash, conciliumId) => { 14 | return { 15 | payload: { 16 | ins: [{txHash: hash ? hash : pseudoRandomBuffer(), nTxOutput: parseInt(Math.random() * 1000) + 1}], 17 | outs: [{amount: parseInt(Math.random() * 1000) + 1, receiverAddr: generateAddress()}], 18 | conciliumId: conciliumId !== undefined ? conciliumId : 0 19 | }, 20 | claimProofs: [pseudoRandomBuffer()] 21 | }; 22 | }; 23 | 24 | const createDummyBlock = (factory, nConciliumId = 0, nTxCount = 0) => { 25 | const block = new factory.Block(nConciliumId); 26 | block.parentHashes = [pseudoRandomBuffer().toString('hex')]; 27 | 28 | for (let i = 0; i < nTxCount; i++) block.addTx(new factory.Transaction(createDummyTx(undefined, nConciliumId))); 29 | 30 | block.finish(factory.Constants.fees.TX_FEE, generateAddress()); 31 | return block; 32 | }; 33 | 34 | const createDummyBlockInfo = factory => { 35 | const block = createDummyBlock(factory); 36 | return new factory.BlockInfo(block.header); 37 | }; 38 | 39 | module.exports = { 40 | generateAddress, 41 | sleep: delay => { 42 | return new Promise(resolve => { 43 | setTimeout(() => resolve(), delay); 44 | }); 45 | }, 46 | createDummyTx, 47 | createDummyPeer: factory => ({ 48 | peerInfo: { 49 | capabilities: [{service: factory.Constants.WITNESS, data: pseudoRandomBuffer()}], 50 | address: {addr0: 0x2001, addr1: 0xdb8, addr2: 0x1234, addr3: 0x5} 51 | } 52 | }), 53 | 54 | createDummyBlock, 55 | createDummyBlockInfo, 56 | pseudoRandomBuffer, 57 | 58 | createDummyBlockWithTx: (factory, nConciliumId = 0) => { 59 | const block = new factory.Block(nConciliumId); 60 | const tx = new factory.Transaction(createDummyTx()); 61 | block.addTx(tx); 62 | block.parentHashes = [pseudoRandomBuffer().toString('hex')]; 63 | block.finish(factory.Constants.fees.TX_FEE, generateAddress()); 64 | return block; 65 | }, 66 | 67 | createNonMergeablePatch: factory => { 68 | const patchThatWouldntMerge = new factory.PatchDB(0); 69 | patchThatWouldntMerge._data = undefined; 70 | return patchThatWouldntMerge; 71 | }, 72 | 73 | processBlock: async (node, block) => { 74 | await node._blockInFlight(block); 75 | const patch = await node._execBlock(block); 76 | await node._acceptBlock(block, patch); 77 | await node._postAcceptBlock(block); 78 | await node._informNeighbors(block); 79 | 80 | return patch; 81 | }, 82 | createObjInvocationCode(strMethod, arrArguments) { 83 | return { 84 | method: strMethod, 85 | arrArguments 86 | }; 87 | } 88 | }; 89 | -------------------------------------------------------------------------------- /tests/unit/arrayOfAddresses.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {describe, it} = require('mocha'); 4 | const {assert} = require('chai'); 5 | 6 | const factory = require('../testFactory'); 7 | const {pseudoRandomBuffer, generateAddress} = require('../testUtil'); 8 | 9 | describe('Array of addresses (serialization)', () => { 10 | before(async function () { 11 | this.timeout(15000); 12 | await factory.asyncLoad(); 13 | }); 14 | 15 | it('should fail to create', async () => { 16 | assert.throws(() => new factory.ArrayOfAddresses()); 17 | }); 18 | 19 | it('should fail to create (bad address length)', async () => { 20 | assert.throws(() => new factory.ArrayOfAddresses([pseudoRandomBuffer(10)])); 21 | assert.throws(() => new factory.ArrayOfAddresses([generateAddress(), pseudoRandomBuffer(10)])); 22 | }); 23 | 24 | it('should create from array', async () => { 25 | new factory.ArrayOfAddresses([generateAddress()]); 26 | new factory.ArrayOfAddresses([generateAddress(), generateAddress()]); 27 | }); 28 | 29 | it('should create from buffer', async () => { 30 | const cArr = new factory.ArrayOfAddresses(pseudoRandomBuffer(80)); 31 | assert.isOk(Array.isArray(cArr.getArray())); 32 | assert.equal(cArr.getArray().length, 4); 33 | }); 34 | 35 | it('should encode', async () => { 36 | const cArr = new factory.ArrayOfAddresses([generateAddress(), generateAddress()]); 37 | const buffEncoded = cArr.encode(); 38 | assert.isOk(Buffer.isBuffer(buffEncoded)); 39 | assert.equal(buffEncoded.length, 40); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /tests/unit/arrayOfHashes.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {describe, it} = require('mocha'); 4 | const {assert} = require('chai'); 5 | 6 | const factory = require('../testFactory'); 7 | const {pseudoRandomBuffer} = require('../testUtil'); 8 | 9 | describe('Array of hashes (serialization)', () => { 10 | before(async function () { 11 | this.timeout(15000); 12 | await factory.asyncLoad(); 13 | }); 14 | 15 | it('should fail to create', async () => { 16 | assert.throws(() => new factory.ArrayOfHashes()); 17 | }); 18 | 19 | it('should fail to create (bad hash length)', async () => { 20 | assert.throws(() => new factory.ArrayOfHashes([pseudoRandomBuffer(10)])); 21 | assert.throws(() => new factory.ArrayOfHashes([pseudoRandomBuffer(), pseudoRandomBuffer(10)])); 22 | }); 23 | 24 | it('should create from array', async () => { 25 | new factory.ArrayOfHashes([pseudoRandomBuffer()]); 26 | new factory.ArrayOfHashes([pseudoRandomBuffer(), pseudoRandomBuffer()]); 27 | }); 28 | 29 | it('should create from buffer', async () => { 30 | const cArr = new factory.ArrayOfHashes(pseudoRandomBuffer(96)); 31 | assert.isOk(Array.isArray(cArr.getArray())); 32 | assert.equal(cArr.getArray().length, 3); 33 | }); 34 | 35 | it('should encode', async () => { 36 | const cArr = new factory.ArrayOfHashes([pseudoRandomBuffer(), pseudoRandomBuffer()]); 37 | const buffEncoded = cArr.encode(); 38 | assert.isOk(Buffer.isBuffer(buffEncoded)); 39 | assert.equal(buffEncoded.length, 64); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /tests/unit/blockInfo.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {describe, it} = require('mocha'); 4 | const {assert} = require('chai'); 5 | 6 | const factory = require('../testFactory'); 7 | const {createDummyBlock} = require('../testUtil'); 8 | 9 | describe('BlockInfo tests', () => { 10 | let blockInfo; 11 | let block; 12 | 13 | before(async function () { 14 | await factory.asyncLoad(); 15 | }); 16 | 17 | beforeEach(async function () { 18 | block = createDummyBlock(factory); 19 | blockInfo = new factory.BlockInfo(block.header); 20 | }); 21 | 22 | it('should fail to CREATE from empty', async () => { 23 | assert.throws(() => new factory.BlockInfo()); 24 | }); 25 | 26 | it('should CREATE from block header', async () => { 27 | assert.isNotOk(blockInfo.isBad()); 28 | }); 29 | 30 | it('should calculate hash', async () => { 31 | assert.equal(blockInfo.getHash(), block.getHash()); 32 | }); 33 | 34 | it('should mark as BAD', async () => { 35 | assert.isNotOk(blockInfo.isBad()); 36 | blockInfo.markAsBad(); 37 | assert.isOk(blockInfo.isBad()); 38 | }); 39 | 40 | it('should encode/decode', async () => { 41 | blockInfo.markAsBad(); 42 | const buff = blockInfo.encode(); 43 | 44 | const restored = new factory.BlockInfo(buff); 45 | assert.isOk(restored.isBad()); 46 | assert.isOk(blockInfo.getHeader().merkleRoot.equals(restored.getHeader().merkleRoot)); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /tests/unit/coins.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {describe, it} = require('mocha'); 4 | const {assert} = require('chai'); 5 | 6 | const factory = require('../testFactory'); 7 | const {generateAddress} = require('../testUtil'); 8 | 9 | describe('Coins', () => { 10 | before(async function () { 11 | this.timeout(15000); 12 | await factory.asyncLoad(); 13 | }); 14 | 15 | after(async function () { 16 | this.timeout(15000); 17 | }); 18 | 19 | it('should create Coins', async () => { 20 | new factory.Coins(10, generateAddress()); 21 | new factory.Coins(10, generateAddress().toString('hex')); 22 | }); 23 | 24 | it('should pass coins EQUALITY', async () => { 25 | const address = generateAddress(); 26 | const coin1 = new factory.Coins(10, address); 27 | const coin2 = new factory.Coins(10, address); 28 | assert.isOk(coin1.equals(coin2)); 29 | assert.isOk(coin2.equals(coin1)); 30 | }); 31 | 32 | it('should fail coins EQUALITY', async () => { 33 | const coin1 = new factory.Coins(10, generateAddress()); 34 | const coin2 = new factory.Coins(10, generateAddress()); 35 | assert.isNotOk(coin1.equals(coin2)); 36 | assert.isNotOk(coin2.equals(coin1)); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /tests/unit/crypto.spec.js: -------------------------------------------------------------------------------- 1 | const {describe, it} = require('mocha'); 2 | const {assert} = require('chai'); 3 | 4 | const Crypto = require('../../crypto/crypto'); 5 | const {prepareForStringifyObject} = require('../../utils'); 6 | 7 | describe('Crypto library', () => { 8 | before(async function () { 9 | this.timeout(15000); 10 | }); 11 | 12 | after(async function () { 13 | this.timeout(15000); 14 | }); 15 | 16 | it('create KeyPair from private key (hex)', async () => { 17 | const strPrivKey = 'b7760a01705490e5e153a6ef7732369a72dbf9aaafb5c482cdfd960546909ec1'; 18 | const strPublicKey = '03ee7b7818bdc27be0030c2edf44ec1cce20c1f7561fc8412e467320b77e20f716'; 19 | const address = '6e2a3a4b77e682b6b9dda5a889304a9d80e4a9c7'; 20 | const keyPair = Crypto.keyPairFromPrivate(strPrivKey, 'hex'); 21 | 22 | assert.equal(strPrivKey, keyPair.getPrivate()); 23 | assert.equal(address, Crypto.getAddress(keyPair.publicKey)); 24 | assert.equal(strPublicKey, keyPair.getPublic()); 25 | }); 26 | 27 | it('should create signature', async () => { 28 | const strPrivKey = 'b7760a01705490e5e153a6ef7732369a72dbf9aaafb5c482cdfd960546909ec1'; 29 | const keyPair = Crypto.keyPairFromPrivate(strPrivKey, 'hex'); 30 | 31 | const buffSignature1 = Crypto.sign('string', keyPair.getPrivate(), 'hex'); 32 | assert.isOk(Buffer.isBuffer(buffSignature1)); 33 | assert.equal(buffSignature1.length, 65); 34 | }); 35 | 36 | it('sign & verify string', async () => { 37 | const keyPair = Crypto.createKeyPair(); 38 | 39 | const buffSignature = Crypto.sign('string', keyPair.getPrivate(), 'hex'); 40 | assert.isOk(Crypto.verify('string', buffSignature, keyPair.getPublic(), 'hex')); 41 | }); 42 | 43 | it('encrypt/decrypt key (pbkdf2)', async () => { 44 | const keyPair = Crypto.createKeyPair(); 45 | const strPrivKey = keyPair.getPrivate(); 46 | const objEncryptedKey = await Crypto.encrypt('234', Buffer.from(strPrivKey, 'hex'), 'pbkdf2'); 47 | 48 | console.log(strPrivKey); 49 | console.dir(prepareForStringifyObject(objEncryptedKey), {colors: true, depth: null}); 50 | 51 | { 52 | // from object 53 | const decryptedKey = await Crypto.decrypt('234', objEncryptedKey); 54 | assert.equal(strPrivKey, decryptedKey.toString('hex')); 55 | } 56 | { 57 | // from stringifyed options 58 | const decryptedKey = await Crypto.decrypt('234', prepareForStringifyObject(objEncryptedKey)); 59 | assert.equal(strPrivKey, decryptedKey.toString('hex')); 60 | } 61 | }); 62 | 63 | it('encrypt/decrypt key (scrypt)', async function () { 64 | this.timeout(10000); 65 | const keyPair = Crypto.createKeyPair(); 66 | const strPrivKey = keyPair.getPrivate(); 67 | const objEncryptedKey = await Crypto.encrypt('234', Buffer.from(strPrivKey, 'hex'), 'scrypt'); 68 | 69 | { 70 | // from object 71 | const decryptedKey = await Crypto.decrypt('234', objEncryptedKey); 72 | assert.equal(strPrivKey, decryptedKey.toString('hex')); 73 | } 74 | { 75 | // from stringified options 76 | const decryptedKey = await Crypto.decrypt('234', prepareForStringifyObject(objEncryptedKey)); 77 | assert.equal(strPrivKey, decryptedKey.toString('hex')); 78 | } 79 | }); 80 | 81 | it('Decrypt wrong key (wrong password)', async function () { 82 | this.timeout(15000); 83 | const keyPair = Crypto.createKeyPair(); 84 | const strPrivKey = keyPair.getPrivate(); 85 | const objEncryptedKey = await Crypto.encrypt('234', Buffer.from(strPrivKey, 'hex')); 86 | 87 | const decryptedKey = await Crypto.decrypt('111', objEncryptedKey); 88 | assert.isNotOk(decryptedKey.equals(Buffer.from(strPrivKey, 'hex'))); 89 | }); 90 | 91 | it('Fail to decrypt (padded data + wrong password)', async function () { 92 | this.timeout(15000); 93 | const strPrivKey = 'Some key'; 94 | const objEncryptedKey = await Crypto.encrypt('234', Buffer.from(strPrivKey)); 95 | 96 | const decryptedKey = await Crypto.decrypt('111', objEncryptedKey); 97 | assert.isNotOk(decryptedKey); 98 | }); 99 | 100 | it('should recover public key from signature buffer', async () => { 101 | const keyPair = Crypto.createKeyPair(); 102 | 103 | const msg = 'string'; 104 | const buffSignature = Crypto.sign(msg, keyPair.getPrivate(), 'hex'); 105 | const pubKey = Crypto.recoverPubKey(msg, buffSignature); 106 | assert.isOk(pubKey); 107 | assert.isOk(typeof pubKey === 'string'); 108 | assert.equal(keyPair.getPublic(), pubKey); 109 | }); 110 | 111 | it('should get ADDRESS', async () => { 112 | const keyPair = Crypto.createKeyPair(); 113 | 114 | assert.isOk(Buffer.isBuffer(keyPair.getAddress())); 115 | assert.isOk(typeof keyPair.address === 'string'); 116 | 117 | assert.equal(keyPair.address, Crypto.getAddress(keyPair.publicKey, false)); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /tests/unit/inventory.spec.js: -------------------------------------------------------------------------------- 1 | const {describe, it} = require('mocha'); 2 | const {assert} = require('chai'); 3 | 4 | const factory = require('../testFactory'); 5 | const {createDummyTx, pseudoRandomBuffer, generateAddress} = require('../testUtil'); 6 | 7 | describe('Inventory', () => { 8 | before(async function () { 9 | this.timeout(15000); 10 | await factory.asyncLoad(); 11 | }); 12 | 13 | after(async function () { 14 | this.timeout(15000); 15 | }); 16 | 17 | it('should create empty inventory', async () => { 18 | new factory.Inventory(); 19 | }); 20 | 21 | it('should add tx', async () => { 22 | const inv = new factory.Inventory(); 23 | const tx = new factory.Transaction(createDummyTx()); 24 | inv.addTx(tx); 25 | assert.isOk(inv.vector[0]); 26 | assert.isOk(inv.vector[0].type, factory.Constants.INV_TX); 27 | }); 28 | 29 | it('should add TX (by hash)', async () => { 30 | const inv = new factory.Inventory(); 31 | const txHash = pseudoRandomBuffer(); 32 | inv.addTxHash(txHash); 33 | assert.isOk(inv.vector[0]); 34 | assert.isOk(inv.vector[0].type, factory.Constants.INV_TX); 35 | }); 36 | 37 | it('should add block', async () => { 38 | const inv = new factory.Inventory(); 39 | const tx = new factory.Transaction(createDummyTx()); 40 | const block = new factory.Block(0); 41 | 42 | block.addTx(tx); 43 | block.finish(factory.Constants.fees.TX_FEE, generateAddress()); 44 | 45 | inv.addBlock(block); 46 | assert.isOk(inv.vector[0]); 47 | assert.isOk(inv.vector[0].type, factory.Constants.INV_BLOCK); 48 | }); 49 | 50 | it('should add block (by hash)', async () => { 51 | const inv = new factory.Inventory(); 52 | const blockHash = pseudoRandomBuffer(); 53 | inv.addBlockHash(blockHash); 54 | assert.isOk(inv.vector[0]); 55 | assert.isOk(inv.vector[0].type, factory.Constants.INV_BLOCK); 56 | }); 57 | 58 | it('should encode/decode inventory', async () => { 59 | const inv = new factory.Inventory(); 60 | const tx = new factory.Transaction(createDummyTx()); 61 | inv.addTx(tx); 62 | 63 | const block = new factory.Block(0); 64 | block.addTx(tx); 65 | block.finish(factory.Constants.fees.TX_FEE, generateAddress()); 66 | 67 | inv.addBlock(block); 68 | 69 | const buffer = inv.encode(); 70 | const restoredInv = new factory.Inventory(buffer); 71 | assert.isOk(restoredInv.vector[0]); 72 | assert.isOk(restoredInv.vector[0].type, factory.Constants.INV_TX); 73 | assert.isOk(restoredInv.vector[1]); 74 | assert.isOk(restoredInv.vector[1].type, factory.Constants.INV_BLOCK); 75 | 76 | // should restore internal cache upon decoding, to prevent adding element more than one time 77 | restoredInv.addTx(tx); 78 | assert.equal(restoredInv.vector.length, 2); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /tests/unit/mainDag.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {describe, it} = require('mocha'); 4 | const {assert} = require('chai'); 5 | 6 | const factory = require('../testFactory'); 7 | const {createDummyBlockInfo} = require('../testUtil'); 8 | 9 | // let fakeResult = { 10 | // fake: 1, 11 | // toObject: function() { 12 | // return this; 13 | // }, 14 | // getHash: function() { 15 | // return 'dead'; 16 | // } 17 | // }; 18 | 19 | // let node; 20 | describe('Main Dag', () => { 21 | before(async function () { 22 | this.timeout(15000); 23 | await factory.asyncLoad(); 24 | }); 25 | 26 | beforeEach(() => { 27 | // node = { 28 | // rpcHandler: sinon.fake.resolves(fakeResult) 29 | // }; 30 | }); 31 | 32 | after(async function () { 33 | this.timeout(15000); 34 | }); 35 | 36 | it('should create instance', async () => { 37 | new factory.MainDag(); 38 | }); 39 | 40 | it('should rewrite vertex (add multiple times)', async () => { 41 | const dag = new factory.MainDag(); 42 | const bi = createDummyBlockInfo(factory); 43 | 44 | dag.addBlock(bi); 45 | 46 | // this block & parent 47 | assert.equal(dag.order, 2); 48 | assert.equal(dag.size, 1); 49 | 50 | await dag.addBlock(bi); 51 | await dag.addBlock(bi); 52 | await dag.addBlock(bi); 53 | 54 | // this block & parent 55 | assert.equal(dag.order, 2); 56 | assert.equal(dag.size, 1); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /tests/unit/messageAssembler.spec.js: -------------------------------------------------------------------------------- 1 | const {describe, it} = require('mocha'); 2 | const {assert} = require('chai'); 3 | 4 | const factory = require('../testFactory'); 5 | 6 | describe('Message assembly (part of connection)', () => { 7 | before(async function () { 8 | this.timeout(15000); 9 | await factory.asyncLoad(); 10 | }); 11 | 12 | after(async function () { 13 | this.timeout(15000); 14 | }); 15 | 16 | it('should pass (1 message - 1 chunk)', async () => { 17 | const msg = new factory.Messages.MsgCommon(); 18 | msg.payload = Buffer.from('asdasdasd'); 19 | const assembler = new factory.MessageAssembler(); 20 | const encodedMsg = msg.encode(); 21 | 22 | const arrMessages = assembler.extractMessages(encodedMsg); 23 | assert.isOk(arrMessages && arrMessages.length === 1); 24 | assert.isOk(Buffer.isBuffer(arrMessages[0])); 25 | assert.isOk(arrMessages[0].equals(encodedMsg)); 26 | assert.isOk(assembler.isDone); 27 | }); 28 | 29 | it('should pass (1 message - 2 chunks)', async () => { 30 | const msg = new factory.Messages.MsgCommon(); 31 | msg.payload = Buffer.from('asdasdasd'); 32 | const assembler = new factory.MessageAssembler(); 33 | const encodedMsg = msg.encode(); 34 | const part1 = encodedMsg.slice(0, 3); 35 | const part2 = encodedMsg.slice(3); 36 | 37 | const arrResult1 = assembler.extractMessages(part1); 38 | assert.isNotOk(arrResult1); 39 | assert.isNotOk(assembler.isDone); 40 | const arrResult2 = assembler.extractMessages(part2); 41 | assert.isOk(arrResult2 && arrResult2.length === 1); 42 | assert.isOk(Buffer.isBuffer(arrResult2[0])); 43 | assert.isOk(arrResult2[0].equals(encodedMsg)); 44 | assert.isOk(assembler.isDone); 45 | }); 46 | 47 | it('should pass (2 messages - 1 chunk. wait for next chunk)', async () => { 48 | const msg = new factory.Messages.MsgCommon(); 49 | msg.payload = Buffer.from('asdasdasd'); 50 | const assembler = new factory.MessageAssembler(); 51 | const encodedMsg = msg.encode(); 52 | 53 | // full message + 3 bytes of next 54 | const chunk = Buffer.concat([encodedMsg, encodedMsg.slice(0, 3)]); 55 | 56 | const arrMessages = assembler.extractMessages(chunk); 57 | assert.isOk(arrMessages && arrMessages.length === 1); 58 | assert.isOk(Buffer.isBuffer(arrMessages[0])); 59 | assert.isOk(arrMessages[0].equals(encodedMsg)); 60 | assert.isNotOk(assembler.isDone); 61 | }); 62 | 63 | it('should pass (2 message - 1 chunks)', async () => { 64 | const msg = new factory.Messages.MsgCommon(); 65 | msg.payload = Buffer.from('asdasdasd'); 66 | const assembler = new factory.MessageAssembler(); 67 | const encodedMsg = msg.encode(); 68 | 69 | // full message + 3 bytes of next 70 | const chunk = Buffer.concat([encodedMsg, encodedMsg]); 71 | 72 | const arrMessages = assembler.extractMessages(chunk); 73 | assert.isOk(arrMessages && arrMessages.length === 2); 74 | assert.isOk(Buffer.isBuffer(arrMessages[0]) && Buffer.isBuffer(arrMessages[1])); 75 | assert.isOk(arrMessages[0].equals(encodedMsg)); 76 | assert.isOk(arrMessages[1].equals(encodedMsg)); 77 | assert.isOk(assembler.isDone); 78 | }); 79 | 80 | it('should (3 messages - 1 chunk. wait for next chunk)', async () => { 81 | const msg = new factory.Messages.MsgCommon(); 82 | msg.payload = Buffer.from('asdasdasd'); 83 | const assembler = new factory.MessageAssembler(); 84 | const encodedMsg = msg.encode(); 85 | 86 | // 2 full message + 3 bytes of next 87 | const chunk = Buffer.concat([encodedMsg, encodedMsg, encodedMsg.slice(0, 3)]); 88 | 89 | const arrMessages = assembler.extractMessages(chunk); 90 | assert.isOk(arrMessages && arrMessages.length === 2); 91 | assert.isOk(Buffer.isBuffer(arrMessages[0]) && Buffer.isBuffer(arrMessages[1])); 92 | assert.isOk(arrMessages[0].equals(encodedMsg)); 93 | assert.isOk(arrMessages[1].equals(encodedMsg)); 94 | assert.isNotOk(assembler.isDone); 95 | }); 96 | 97 | it('should pass (2 message - 3 chunks)', async () => { 98 | const msg = new factory.Messages.MsgCommon(); 99 | msg.payload = Buffer.from('asdasdasd'); 100 | const msg2 = new factory.Messages.MsgCommon(); 101 | msg2.payload = Buffer.from('1234567890'); 102 | const assembler = new factory.MessageAssembler(); 103 | const encodedMsg = msg.encode(); 104 | const encodedMsg2 = msg2.encode(); 105 | const part1 = encodedMsg.slice(0, 3); 106 | const part2 = Buffer.concat([encodedMsg.slice(3), encodedMsg2.slice(0, 3)]); 107 | const part3 = encodedMsg2.slice(3); 108 | 109 | const arrResult1 = assembler.extractMessages(part1); 110 | assert.isNotOk(arrResult1); 111 | assert.isNotOk(assembler.isDone); 112 | 113 | const arrResult2 = assembler.extractMessages(part2); 114 | assert.isOk(arrResult2 && arrResult2.length === 1); 115 | assert.isOk(Buffer.isBuffer(arrResult2[0])); 116 | assert.isOk(arrResult2[0].equals(encodedMsg)); 117 | assert.isNotOk(assembler.isDone); 118 | 119 | const arrResult3 = assembler.extractMessages(part3); 120 | assert.isOk(arrResult3 && arrResult3.length === 1); 121 | assert.isOk(Buffer.isBuffer(arrResult3[0])); 122 | assert.isOk(arrResult3[0].equals(encodedMsg2)); 123 | assert.isOk(assembler.isDone); 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /tests/unit/messages/messageAddr.spec.js: -------------------------------------------------------------------------------- 1 | const {describe, it} = require('mocha'); 2 | const {assert} = require('chai'); 3 | 4 | const factory = require('../../testFactory'); 5 | 6 | let templateMsg; 7 | 8 | describe('Addr Message', () => { 9 | before(async function () { 10 | this.timeout(15000); 11 | await factory.asyncLoad(); 12 | 13 | templateMsg = { 14 | peers: [ 15 | { 16 | capabilities: [ 17 | {service: factory.Constants.NODE, data: null}, 18 | {service: factory.Constants.WITNESS, data: Buffer.from('asdasdasd')} 19 | ], 20 | address: { 21 | addr0: 0x2001, 22 | addr1: 0xdb8, 23 | addr2: 0x1234, 24 | addr3: 0x3 25 | }, 26 | port: 12345 27 | } 28 | ] 29 | }; 30 | }); 31 | 32 | after(async function () { 33 | this.timeout(15000); 34 | }); 35 | 36 | it('should create empty MsgAddr', async () => { 37 | const msgAddr = new factory.Messages.MsgAddr(); 38 | assert.isOk(msgAddr); 39 | assert.isOk(msgAddr.isAddr()); 40 | }); 41 | 42 | it('should create MsgAddr from object', async () => { 43 | const msgAddr = new factory.Messages.MsgAddr(templateMsg); 44 | assert.isOk(msgAddr); 45 | assert.isOk(msgAddr.isAddr()); 46 | }); 47 | 48 | it('should create MsgAddr from MsgCommon', async () => { 49 | const msgAddr = new factory.Messages.MsgAddr(templateMsg); 50 | assert.isOk(msgAddr); 51 | const buff = msgAddr.encode(); 52 | const msgCommon = new factory.Messages.MsgCommon(buff); 53 | assert.isOk(msgCommon); 54 | const reconstructedAddr = new factory.Messages.MsgAddr(msgCommon); 55 | 56 | assert.isOk(reconstructedAddr.isAddr()); 57 | }); 58 | 59 | it('tests getters/setters', async () => { 60 | const msgAddr = new factory.Messages.MsgAddr(templateMsg); 61 | assert.isOk(msgAddr.peers); 62 | }); 63 | 64 | it('should pass encoding/decoding MsgAddr', async () => { 65 | const msgAddr = new factory.Messages.MsgAddr(templateMsg); 66 | const result = msgAddr.encode(); 67 | assert.isOk(result); 68 | assert.isOk(Buffer.isBuffer(result)); 69 | assert.isOk(result.length); 70 | 71 | const decodedMessage = new factory.Messages.MsgAddr(result); 72 | 73 | const reEncoded = decodedMessage.encode(); 74 | assert.isOk(result.equals(reEncoded)); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /tests/unit/messages/messageBlock.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {describe, it} = require('mocha'); 4 | const {assert} = require('chai'); 5 | const {createDummyBlock} = require('../../testUtil'); 6 | 7 | const factory = require('../../testFactory'); 8 | 9 | describe('MessageBlock', () => { 10 | before(async function () { 11 | this.timeout(15000); 12 | await factory.asyncLoad(); 13 | }); 14 | 15 | after(async function () { 16 | this.timeout(15000); 17 | }); 18 | 19 | it('should create empty message', async () => { 20 | const msg = new factory.Messages.MsgBlock(); 21 | assert.isOk(msg.network); 22 | assert.equal(msg.network, factory.Constants.network); 23 | assert.isOk(msg.isBlock()); 24 | }); 25 | 26 | it('should create from block', async () => { 27 | const block = createDummyBlock(factory); 28 | const msg = new factory.Messages.MsgBlock(block); 29 | assert.isOk(new factory.Transaction(msg.block.txns[0]).isCoinbase()); 30 | }); 31 | 32 | it('should set/get block', async () => { 33 | const msg = new factory.Messages.MsgBlock(); 34 | const block = createDummyBlock(factory); 35 | msg.block = block; 36 | 37 | const restoredBlock = msg.block; 38 | assert.equal(block.hash(), restoredBlock.hash()); 39 | assert.isOk(Array.isArray(restoredBlock.txns)); 40 | assert.equal(restoredBlock.txns.length, 1); 41 | 42 | const restoredTx = new factory.Transaction(restoredBlock.txns[0]); 43 | assert.isOk(restoredTx.isCoinbase()); 44 | }); 45 | 46 | it('should encode/decode message', async () => { 47 | const msg = new factory.Messages.MsgBlock(); 48 | 49 | const block = createDummyBlock(factory); 50 | msg.block = block; 51 | 52 | const buffMsg = msg.encode(); 53 | assert.isOk(Buffer.isBuffer(buffMsg)); 54 | const msgCommon = new factory.Messages.MsgCommon(buffMsg); 55 | 56 | const restoredMsg = new factory.Messages.MsgBlock(msgCommon); 57 | 58 | const restoredBlock = restoredMsg.block; 59 | assert.equal(block.hash(), restoredBlock.hash()); 60 | assert.isOk(Array.isArray(restoredBlock.txns)); 61 | assert.equal(restoredBlock.txns.length, 1); 62 | 63 | const restoredTx = new factory.Transaction(restoredBlock.txns[0]); 64 | assert.isOk(restoredTx.isCoinbase()); 65 | }); 66 | 67 | it('should fail to decode block message', async () => { 68 | const msg = new factory.Messages.MsgBlock(); 69 | msg.payload = Buffer.from('123'); 70 | 71 | const restoredBlock = msg.block; 72 | assert.isNotOk(restoredBlock); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /tests/unit/messages/messageCommon.spec.js: -------------------------------------------------------------------------------- 1 | const {describe, it} = require('mocha'); 2 | const {assert} = require('chai'); 3 | 4 | const factory = require('../../testFactory'); 5 | 6 | describe('MessageCommon', () => { 7 | before(async function () { 8 | this.timeout(15000); 9 | await factory.asyncLoad(); 10 | }); 11 | 12 | after(async function () { 13 | this.timeout(15000); 14 | }); 15 | 16 | it('should create message', async () => { 17 | const msg = new factory.Messages.MsgCommon(); 18 | assert.isOk(msg.network); 19 | assert.equal(msg.network, factory.Constants.network); 20 | }); 21 | 22 | it('should create message', async () => { 23 | const msg = new factory.Messages.MsgCommon(); 24 | assert.isOk(msg.network); 25 | }); 26 | 27 | it('should set/get payload', async () => { 28 | const msg = new factory.Messages.MsgCommon(); 29 | msg.payload = Buffer.from('1235'); 30 | assert.isOk(msg.payload); 31 | assert.equal(msg.payload.toString(), '1235'); 32 | }); 33 | 34 | it('should sign/verify payload', async () => { 35 | const keyPair = factory.Crypto.createKeyPair(); 36 | 37 | const msg = new factory.Messages.MsgCommon(); 38 | msg.payload = Buffer.from('1235'); 39 | msg.sign(keyPair.getPrivate()); 40 | 41 | assert.isOk(msg.signature); 42 | assert.isOk(msg.verifySignature(keyPair.publicKey)); 43 | }); 44 | 45 | it('should get pubKey of signed message', async () => { 46 | const keyPair = factory.Crypto.createKeyPair(); 47 | 48 | const msg = new factory.Messages.MsgCommon(); 49 | msg.payload = Buffer.from('1235'); 50 | msg.sign(keyPair.getPrivate()); 51 | 52 | assert.isOk(msg.address); 53 | assert.equal(msg.address, keyPair.address); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /tests/unit/messages/messageGetBlocks.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {describe, it} = require('mocha'); 4 | const {assert} = require('chai'); 5 | 6 | const factory = require('../../testFactory'); 7 | const {pseudoRandomBuffer} = require('../../testUtil'); 8 | const {arrayEquals} = require('../../../utils'); 9 | 10 | describe('MessageGetBlocks', () => { 11 | before(async function () { 12 | this.timeout(15000); 13 | await factory.asyncLoad(); 14 | }); 15 | 16 | after(async function () { 17 | this.timeout(15000); 18 | }); 19 | 20 | it('should create empty message', async () => { 21 | const msg = new factory.Messages.MsgGetBlocks(); 22 | assert.isOk(msg.isGetBlocks()); 23 | }); 24 | 25 | it('should create from object', async () => { 26 | const arrHashes = [pseudoRandomBuffer(), pseudoRandomBuffer()]; 27 | new factory.Messages.MsgGetBlocks({ 28 | arrHashes 29 | }); 30 | }); 31 | 32 | it('should get arrHashes', async () => { 33 | const arrHashes = [pseudoRandomBuffer().toString('hex'), pseudoRandomBuffer().toString('hex')]; 34 | const msg = new factory.Messages.MsgGetBlocks({ 35 | arrHashes 36 | }); 37 | 38 | assert.isOk(Array.isArray(msg.arrHashes)); 39 | assert.isOk(arrayEquals(msg.arrHashes, arrHashes)); 40 | }); 41 | 42 | it('should set arrHashes', async () => { 43 | const arrHashes = [pseudoRandomBuffer().toString('hex'), pseudoRandomBuffer().toString('hex')]; 44 | const msg = new factory.Messages.MsgGetBlocks(); 45 | msg.arrHashes = arrHashes; 46 | 47 | assert.isOk(Array.isArray(msg.arrHashes)); 48 | assert.isOk(arrayEquals(msg.arrHashes, arrHashes)); 49 | }); 50 | 51 | it('should encode/decode message', async () => { 52 | const arrHashes = [pseudoRandomBuffer().toString('hex'), pseudoRandomBuffer().toString('hex')]; 53 | const msg = new factory.Messages.MsgGetBlocks(); 54 | msg.arrHashes = arrHashes; 55 | 56 | const buff = msg.encode(); 57 | 58 | const restored = new factory.Messages.MsgGetBlocks(buff); 59 | assert.isOk(Array.isArray(restored.arrHashes)); 60 | assert.isOk(arrayEquals(restored.arrHashes, arrHashes)); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /tests/unit/messages/messageGetData.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {describe, it} = require('mocha'); 4 | const {assert} = require('chai'); 5 | const {createDummyTx, createDummyBlock} = require('../../testUtil'); 6 | 7 | const factory = require('../../testFactory'); 8 | 9 | describe('MessageGetData', () => { 10 | before(async function () { 11 | this.timeout(15000); 12 | await factory.asyncLoad(); 13 | }); 14 | 15 | after(async function () { 16 | this.timeout(15000); 17 | }); 18 | 19 | it('should create empty message', async () => { 20 | const msg = new factory.Messages.MsgGetData(); 21 | assert.isOk(msg.network); 22 | assert.equal(msg.network, factory.Constants.network); 23 | assert.isOk(msg.isGetData()); 24 | }); 25 | 26 | it('should create from inventory', async () => { 27 | const inv = new factory.Inventory(); 28 | const tx = new factory.Transaction(createDummyTx()); 29 | inv.addTx(tx); 30 | 31 | const msg = new factory.Messages.MsgGetData(inv); 32 | assert.isOk(msg.inventory.vector); 33 | assert.equal(msg.inventory.vector.length, 1); 34 | }); 35 | 36 | it('should set/get inventory', async () => { 37 | const msg = new factory.Messages.MsgGetData(); 38 | 39 | const inv = new factory.Inventory(); 40 | const tx = new factory.Transaction(createDummyTx()); 41 | inv.addTx(tx); 42 | 43 | const block = createDummyBlock(factory); 44 | inv.addBlock(block); 45 | 46 | msg.inventory = inv; 47 | 48 | const restoredInv = msg.inventory; 49 | assert.isOk(restoredInv); 50 | }); 51 | 52 | it('should encode/decode message', async () => { 53 | const msg = new factory.Messages.MsgGetData(); 54 | 55 | const inv = new factory.Inventory(); 56 | const tx = new factory.Transaction(createDummyTx()); 57 | inv.addTx(tx); 58 | 59 | const block = createDummyBlock(factory); 60 | inv.addBlock(block); 61 | 62 | msg.inventory = inv; 63 | const buffMsg = msg.encode(); 64 | assert.isOk(Buffer.isBuffer(buffMsg)); 65 | 66 | const msgCommon = new factory.Messages.MsgCommon(buffMsg); 67 | const restoredMsg = new factory.Messages.MsgGetData(msgCommon); 68 | 69 | const wrapper = () => restoredMsg.inventory; 70 | assert.doesNotThrow(wrapper); 71 | }); 72 | 73 | it('should fail to decode malformed message', async () => { 74 | const msg = new factory.Messages.MsgGetData(); 75 | msg.payload = Buffer.from('123'); 76 | 77 | const wrapper = () => msg.inventory; 78 | assert.throws(wrapper); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /tests/unit/messages/messageInventory.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {describe, it} = require('mocha'); 4 | const {assert} = require('chai'); 5 | const {createDummyTx, createDummyBlock} = require('../../testUtil'); 6 | 7 | const factory = require('../../testFactory'); 8 | 9 | describe('MessageInventory', () => { 10 | before(async function () { 11 | this.timeout(15000); 12 | await factory.asyncLoad(); 13 | }); 14 | 15 | after(async function () { 16 | this.timeout(15000); 17 | }); 18 | 19 | it('should create empty message', async () => { 20 | const msg = new factory.Messages.MsgInv(); 21 | assert.isOk(msg.network); 22 | assert.equal(msg.network, factory.Constants.network); 23 | assert.isOk(msg.isInv()); 24 | }); 25 | 26 | it('should create from inventory', async () => { 27 | const inv = new factory.Inventory(); 28 | const tx = new factory.Transaction(createDummyTx()); 29 | inv.addTx(tx); 30 | 31 | const msg = new factory.Messages.MsgInv(inv); 32 | assert.isOk(msg.inventory.vector); 33 | assert.equal(msg.inventory.vector.length, 1); 34 | }); 35 | 36 | it('should set/get inventory', async () => { 37 | const msg = new factory.Messages.MsgInv(); 38 | 39 | const inv = new factory.Inventory(); 40 | const tx = new factory.Transaction(createDummyTx()); 41 | inv.addTx(tx); 42 | 43 | const block = createDummyBlock(factory); 44 | inv.addBlock(block); 45 | 46 | msg.inventory = inv; 47 | 48 | assert.isOk(msg.inventory); 49 | assert.isOk(msg.inventory.vector); 50 | assert.equal(msg.inventory.vector.length, 2); 51 | }); 52 | 53 | it('should encode/decode message', async () => { 54 | const msg = new factory.Messages.MsgInv(); 55 | 56 | const inv = new factory.Inventory(); 57 | const tx = new factory.Transaction(createDummyTx()); 58 | inv.addTx(tx); 59 | 60 | const block = createDummyBlock(factory); 61 | inv.addBlock(block); 62 | 63 | msg.inventory = inv; 64 | const buffMsg = msg.encode(); 65 | assert.isOk(Buffer.isBuffer(buffMsg)); 66 | 67 | const msgCommon = new factory.Messages.MsgCommon(buffMsg); 68 | const restoredMsg = new factory.Messages.MsgInv(msgCommon); 69 | 70 | const wrapper = () => restoredMsg.inventory; 71 | assert.doesNotThrow(wrapper); 72 | }); 73 | 74 | it('should fail to decode malformed message', async () => { 75 | const msg = new factory.Messages.MsgInv(); 76 | msg.payload = Buffer.from('123'); 77 | 78 | const wrapper = () => msg.inventory; 79 | assert.throws(wrapper); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /tests/unit/messages/messageTx.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {describe, it} = require('mocha'); 4 | const {assert} = require('chai'); 5 | const {createDummyTx} = require('../../testUtil'); 6 | 7 | const factory = require('../../testFactory'); 8 | 9 | describe('Message Transaction', () => { 10 | before(async function () { 11 | this.timeout(15000); 12 | await factory.asyncLoad(); 13 | }); 14 | 15 | after(async function () { 16 | this.timeout(15000); 17 | }); 18 | 19 | it('should create empty message', async () => { 20 | const msg = new factory.Messages.MsgTx(); 21 | assert.isOk(msg.network); 22 | assert.equal(msg.network, factory.Constants.network); 23 | assert.isOk(msg.isTx()); 24 | }); 25 | 26 | it('should create from tx', async () => { 27 | const tx = new factory.Transaction(createDummyTx()); 28 | const msgTx = new factory.Messages.MsgTx(tx); 29 | 30 | assert.isOk(tx.equals(msgTx.tx)); 31 | }); 32 | 33 | it('should set/get block', async () => { 34 | const msg = new factory.Messages.MsgTx(); 35 | 36 | const keyPair = factory.Crypto.createKeyPair(); 37 | const tx = new factory.Transaction(createDummyTx()); 38 | tx.claim(0, keyPair.privateKey); 39 | msg.tx = tx; 40 | 41 | assert.isOk(Buffer.isBuffer(msg.payload)); 42 | 43 | const restoredTx = msg.tx; 44 | assert.equal(tx.hash, restoredTx.hash); 45 | assert.isOk(restoredTx.equals(tx)); 46 | }); 47 | 48 | it('should encode/decode message', async () => { 49 | const msg = new factory.Messages.MsgTx(); 50 | 51 | const keyPair = factory.Crypto.createKeyPair(); 52 | const tx = new factory.Transaction(createDummyTx()); 53 | tx.claim(0, keyPair.privateKey); 54 | msg.tx = tx; 55 | 56 | const buffMsg = msg.encode(); 57 | assert.isOk(Buffer.isBuffer(buffMsg)); 58 | const msgCommon = new factory.Messages.MsgCommon(buffMsg); 59 | const restoredMsg = new factory.Messages.MsgTx(msgCommon); 60 | 61 | const restoredTx = restoredMsg.tx; 62 | assert.equal(tx.hash, restoredTx.hash); 63 | assert.isOk(restoredTx.equals(tx)); 64 | }); 65 | 66 | it('should fail to decode block message', async () => { 67 | const msg = new factory.Messages.MsgTx(); 68 | msg.payload = Buffer.from('123'); 69 | 70 | const restoredTx = msg.tx; 71 | assert.isNotOk(restoredTx); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /tests/unit/messages/messageVersion.spec.js: -------------------------------------------------------------------------------- 1 | const {describe, it} = require('mocha'); 2 | const {assert} = require('chai'); 3 | 4 | const factory = require('../../testFactory'); 5 | 6 | let msgTemplate; 7 | 8 | describe('Version Message', () => { 9 | before(async function () { 10 | this.timeout(15000); 11 | await factory.asyncLoad(); 12 | 13 | msgTemplate = { 14 | nonce: 12, 15 | peerInfo: { 16 | capabilities: [ 17 | {service: factory.Constants.NODE}, 18 | {service: factory.Constants.WITNESS, data: Buffer.from('asdasdasd')} 19 | ], 20 | address: { 21 | addr0: 0x2001, 22 | addr1: 0xdb8, 23 | addr2: 0x1234, 24 | addr3: 0x3 25 | } 26 | } 27 | }; 28 | }); 29 | 30 | after(async function () { 31 | this.timeout(15000); 32 | }); 33 | 34 | it('should create empty MsgVersion', async () => { 35 | const msgVersion = new factory.Messages.MsgVersion({nonce: 1}); 36 | assert.isOk(msgVersion); 37 | assert.isOk(msgVersion.isVersion()); 38 | }); 39 | 40 | it('should create MsgVersion from object', async () => { 41 | const msgVersion = new factory.Messages.MsgVersion(msgTemplate); 42 | assert.isOk(msgVersion); 43 | assert.isOk(msgVersion.isVersion()); 44 | assert.isOk(msgVersion.protocolVersion && msgVersion.protocolVersion === factory.Constants.protocolVersion); 45 | assert.isOk(msgVersion.data); 46 | assert.isOk(msgVersion.data.peerInfo); 47 | assert.isOk(msgVersion.data.timeStamp); 48 | assert.isOk(msgVersion.data.nonce); 49 | }); 50 | 51 | it('should create MsgVersion from MsgCommon', async () => { 52 | const msgVersion = new factory.Messages.MsgVersion(msgTemplate); 53 | const buff = msgVersion.encode(); 54 | const msgCommon = new factory.Messages.MsgCommon(buff); 55 | assert.isOk(msgCommon); 56 | const reconstructedVersion = new factory.Messages.MsgVersion(msgCommon); 57 | 58 | assert.isOk(reconstructedVersion); 59 | assert.isOk(msgVersion.isVersion()); 60 | assert.isOk(msgVersion.protocolVersion && msgVersion.protocolVersion === factory.Constants.protocolVersion); 61 | assert.isOk(reconstructedVersion.data); 62 | assert.isOk(reconstructedVersion.data.peerInfo); 63 | assert.isOk(reconstructedVersion.data.timeStamp); 64 | assert.isOk(reconstructedVersion.data.nonce); 65 | }); 66 | 67 | it('should pass encoding/decoding MsgVersion', async () => { 68 | const msgVersion = new factory.Messages.MsgVersion(msgTemplate); 69 | assert.isOk(msgVersion); 70 | const buff = msgVersion.encode(); 71 | assert.isOk(buff); 72 | assert.isOk(Buffer.isBuffer(buff)); 73 | assert.isOk(buff.length); 74 | 75 | const decodedMessage = new factory.Messages.MsgVersion(buff); 76 | 77 | const reEncoded = decodedMessage.encode(); 78 | assert.isOk(buff.equals(reEncoded)); 79 | }); 80 | 81 | it('should sign/verify payload', async () => { 82 | const keyPair = factory.Crypto.createKeyPair(); 83 | const message = new factory.Messages.MsgVersion(msgTemplate); 84 | message.encode(); 85 | message.sign(keyPair.getPrivate()); 86 | assert.isOk(message.signature); 87 | assert.isOk(message.verifySignature(keyPair.getPublic(false, false))); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /tests/unit/messages/messageWitnessBlock.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {describe, it} = require('mocha'); 4 | const {assert} = require('chai'); 5 | const {createDummyBlock} = require('../../testUtil'); 6 | 7 | const factory = require('../../testFactory'); 8 | 9 | describe('MessageWitnessBlock', () => { 10 | before(async function () { 11 | this.timeout(15000); 12 | await factory.asyncLoad(); 13 | }); 14 | 15 | after(async function () { 16 | this.timeout(15000); 17 | }); 18 | 19 | it('should NOT create message', async () => { 20 | const wrapper = () => new factory.Messages.MsgWitnessBlock(); 21 | assert.throws(wrapper); 22 | }); 23 | 24 | it('should create message', async () => { 25 | const msg = new factory.Messages.MsgWitnessBlock({conciliumId: 0}); 26 | assert.isOk(msg.isWitnessBlock()); 27 | }); 28 | 29 | it('should encode/decode message', async () => { 30 | const msg = new factory.Messages.MsgWitnessBlock({conciliumId: 0}); 31 | 32 | const block = createDummyBlock(factory); 33 | const keyPair = factory.Crypto.createKeyPair(); 34 | 35 | msg.block = block; 36 | msg.sign(keyPair.privateKey); 37 | 38 | const buffMsg = msg.encode(); 39 | assert.isOk(Buffer.isBuffer(buffMsg)); 40 | 41 | const restoredMsg = new factory.Messages.MsgWitnessBlock(buffMsg); 42 | assert.isOk(restoredMsg.signature); 43 | assert.isOk(restoredMsg.address); 44 | assert.equal(restoredMsg.address, keyPair.address); 45 | 46 | const restoredBlock = restoredMsg.block; 47 | assert.equal(block.hash(), restoredBlock.hash()); 48 | assert.isOk(Array.isArray(restoredBlock.txns)); 49 | assert.equal(restoredBlock.txns.length, 1); 50 | 51 | const restoredTx = new factory.Transaction(restoredBlock.txns[0]); 52 | assert.isOk(restoredTx.isCoinbase()); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /tests/unit/messages/messageWitnessBlockVote.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {describe, it} = require('mocha'); 4 | const {assert} = require('chai'); 5 | const {pseudoRandomBuffer} = require('../../testUtil'); 6 | 7 | const factory = require('../../testFactory'); 8 | 9 | describe('MessageWitnessBlockVote', () => { 10 | before(async function () { 11 | this.timeout(15000); 12 | await factory.asyncLoad(); 13 | }); 14 | 15 | after(async function () { 16 | this.timeout(15000); 17 | }); 18 | 19 | it('should NOT create message', async () => { 20 | const wrapper = () => new factory.Messages.MsgWitnessBlockVote(); 21 | assert.throws(wrapper); 22 | }); 23 | 24 | it('should NOT create message (bad block hash)', async () => { 25 | const wrapper = () => new factory.Messages.MsgWitnessBlockVote({conciliumId: 'test', blockHash: '123'}); 26 | assert.throws(wrapper); 27 | }); 28 | 29 | it('should create message', async () => { 30 | new factory.Messages.MsgWitnessBlockVote({conciliumId: 0, blockHash: pseudoRandomBuffer()}); 31 | }); 32 | 33 | it('should get blockHash', async () => { 34 | const blockHash = pseudoRandomBuffer(); 35 | const msg = new factory.Messages.MsgWitnessBlockVote({conciliumId: 0, blockHash}); 36 | assert.isOk(blockHash.equals(msg.blockHash)); 37 | }); 38 | 39 | it('should verify hash & message signatures', async () => { 40 | const keyPair = factory.Crypto.createKeyPair(); 41 | const blockHash = pseudoRandomBuffer(); 42 | const msg = new factory.Messages.MsgWitnessBlockVote({conciliumId: 0, blockHash}); 43 | msg.sign(keyPair.privateKey); 44 | 45 | assert.isOk(factory.Crypto.verify(blockHash, msg.hashSignature, keyPair.publicKey)); 46 | assert.isOk(msg.verifySignature(keyPair.publicKey)); 47 | }); 48 | 49 | it('should encode/decode message', async () => { 50 | const blockHash = pseudoRandomBuffer(); 51 | const msg = new factory.Messages.MsgWitnessBlockVote({conciliumId: 0, blockHash}); 52 | const keyPair = factory.Crypto.createKeyPair(); 53 | msg.sign(keyPair.privateKey); 54 | const buffMsg = msg.encode(); 55 | 56 | const recoveredMsg = new factory.Messages.MsgWitnessBlockVote(buffMsg); 57 | assert.isOk(recoveredMsg.isWitnessBlockVote()); 58 | assert.isOk(recoveredMsg.verifySignature(keyPair.publicKey)); 59 | assert.isOk(blockHash.equals(recoveredMsg.blockHash)); 60 | assert.isOk(factory.Crypto.verify(recoveredMsg.blockHash, recoveredMsg.hashSignature, keyPair.publicKey)); 61 | }); 62 | 63 | it('should create "REJECT" vote', async () => { 64 | const msg = factory.Messages.MsgWitnessBlockVote.reject('test'); 65 | assert.isOk(msg.isWitnessBlockVote()); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /tests/unit/messages/messageWitnessCommon.spec.js: -------------------------------------------------------------------------------- 1 | const {describe, it} = require('mocha'); 2 | const {assert} = require('chai'); 3 | 4 | const factory = require('../../testFactory'); 5 | 6 | describe('MessageWitnessCommon', () => { 7 | before(async function () { 8 | this.timeout(15000); 9 | await factory.asyncLoad(); 10 | }); 11 | 12 | after(async function () { 13 | this.timeout(15000); 14 | }); 15 | 16 | it('should NOT create message (missed concilium)', async () => { 17 | assert.throws(() => new factory.Messages.MsgWitnessCommon()); 18 | }); 19 | 20 | it('should create message', async () => { 21 | new factory.Messages.MsgWitnessCommon({conciliumId: 0}); 22 | }); 23 | 24 | it('should FAIL set content (requires buffer)', async () => { 25 | const msg = new factory.Messages.MsgWitnessCommon({conciliumId: 0}); 26 | assert.throws(() => (msg.content = '123')); 27 | }); 28 | 29 | it('should set/get content', async () => { 30 | const msg = new factory.Messages.MsgWitnessCommon({conciliumId: 0}); 31 | const value = Buffer.from([1, 2, 3, 4]); 32 | msg.content = value; 33 | assert.isOk(msg.content.equals(value)); 34 | }); 35 | 36 | it('should sign/verify payload', async () => { 37 | const keyPair = factory.Crypto.createKeyPair(); 38 | 39 | const msg = new factory.Messages.MsgWitnessCommon({conciliumId: 0}); 40 | msg.content = Buffer.from([1, 2, 3, 4]); 41 | msg.sign(keyPair.getPrivate()); 42 | 43 | assert.isOk(msg.signature); 44 | assert.isOk(msg.verifySignature(keyPair.publicKey)); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /tests/unit/messages/peerInfo.spec.js: -------------------------------------------------------------------------------- 1 | const {describe, it} = require('mocha'); 2 | const {assert} = require('chai'); 3 | const debug = require('debug')('peerInfo:'); 4 | 5 | const factory = require('../../testFactory'); 6 | 7 | let templatePeer; 8 | 9 | describe('PeerInfo Message', () => { 10 | before(async function () { 11 | this.timeout(15000); 12 | await factory.asyncLoad(); 13 | 14 | templatePeer = { 15 | capabilities: [ 16 | {service: factory.Constants.NODE, data: null}, 17 | {service: factory.Constants.WITNESS, data: Buffer.from('asdasdasd')} 18 | ], 19 | address: { 20 | addr0: 0x2001, 21 | addr1: 0xdb8, 22 | addr2: 0x1234, 23 | addr3: 0x3 24 | }, 25 | port: 12345 26 | }; 27 | }); 28 | 29 | after(async function () { 30 | this.timeout(15000); 31 | }); 32 | 33 | it('should NOT create empty PeerInfo', async () => { 34 | try { 35 | new factory.Messages.PeerInfo(); 36 | assert.isOk(false, 'Unexpected success!'); 37 | } catch (err) { 38 | debug(err); 39 | } 40 | }); 41 | 42 | it('should create PeerInfo', async () => { 43 | const peerInfo = new factory.Messages.PeerInfo(templatePeer); 44 | assert.isOk(peerInfo); 45 | assert.equal(peerInfo.port, templatePeer.port); 46 | assert.isOk(Array.isArray(peerInfo.capabilities)); 47 | assert.equal(peerInfo.capabilities.length, templatePeer.capabilities.length); 48 | assert.isOk(Buffer.isBuffer(peerInfo.address)); 49 | }); 50 | 51 | it('should create with address from Buffer with default port', function () { 52 | const peerInfo = new factory.Messages.PeerInfo({ 53 | capabilities: [{service: factory.Constants.NODE}], 54 | address: factory.Transport.strToAddress(factory.Transport.generateAddress()) 55 | }); 56 | assert.equal(peerInfo.port, factory.Constants.port); 57 | assert.isOk(Array.isArray(peerInfo.capabilities)); 58 | assert.isOk(Buffer.isBuffer(peerInfo.address)); 59 | }); 60 | 61 | it('tests getters/setters', async () => { 62 | const peerInfo = new factory.Messages.PeerInfo(templatePeer); 63 | 64 | assert.equal(peerInfo.port, 12345); 65 | const buffAddr = peerInfo.address; 66 | assert.isOk(Buffer.isBuffer(buffAddr)); 67 | 68 | const addrRaw = peerInfo.data.address; 69 | assert.isOk(addrRaw); 70 | assert.deepEqual(addrRaw, templatePeer.address); 71 | 72 | peerInfo.address = buffAddr; 73 | assert.isOk(peerInfo.address); 74 | assert.isOk(Buffer.isBuffer(peerInfo.address)); 75 | assert.deepEqual(peerInfo.data.address, templatePeer.address); 76 | }); 77 | 78 | it('should transform addreess to buffer and back', async () => { 79 | const addr = { 80 | addr0: 0x2001, 81 | addr1: 0xdb8, 82 | addr2: 0x1234, 83 | addr3: 0x3 84 | }; 85 | const buff = factory.Messages.PeerInfo.toAddress(addr); 86 | assert.isOk(buff && Buffer.isBuffer(buff)); 87 | const objRevertedAddr = factory.Messages.PeerInfo.fromAddress(buff); 88 | assert.deepEqual(addr, objRevertedAddr); 89 | }); 90 | 91 | it('should add capability', async () => { 92 | const peerInfo = new factory.Messages.PeerInfo(templatePeer); 93 | peerInfo.addCapability({service: factory.Constants.WITNESS, data: Buffer.from('123')}); 94 | assert.isOk(Array.isArray(peerInfo.capabilities)); 95 | assert.equal(peerInfo.capabilities.length, 3); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /tests/unit/messages/witnessExpose.spec.js: -------------------------------------------------------------------------------- 1 | const {describe, it} = require('mocha'); 2 | const {assert} = require('chai'); 3 | 4 | const factory = require('../../testFactory'); 5 | 6 | describe('Witness expose message', () => { 7 | before(async function () { 8 | this.timeout(15000); 9 | await factory.asyncLoad(); 10 | }); 11 | 12 | after(async function () { 13 | this.timeout(15000); 14 | }); 15 | 16 | it('should FAIL to create empty', async () => { 17 | const wrapper = () => new factory.Messages.MsgWitnessWitnessExpose(); 18 | assert.throws(wrapper); 19 | }); 20 | 21 | it('should create from MsgNextRound', async () => { 22 | const strPrivKey = 'b7760a01705490e5e153a6ef7732369a72dbf9aaafb5c482cdfd960546909ec1'; 23 | 24 | // create message we plan to expose 25 | const msgToExpose = new factory.Messages.MsgWitnessNextRound({roundNo: 1, conciliumId: 0}); 26 | msgToExpose.sign(strPrivKey); 27 | const msg = new factory.Messages.MsgWitnessWitnessExpose(msgToExpose); 28 | msg.sign(strPrivKey); 29 | assert.isOk(msg); 30 | assert.isOk(msg.isExpose()); 31 | assert.isOk(Buffer.isBuffer(msg.content)); 32 | 33 | // simulate receiving from wire 34 | const msgWitnessCommon = new factory.Messages.MsgWitnessCommon(new factory.Messages.MsgCommon(msg.encode())); 35 | assert.isOk(msgWitnessCommon.isExpose()); 36 | 37 | // extract original message 38 | const exposedMessage = factory.Messages.MsgWitnessWitnessExpose.extract(msgWitnessCommon); 39 | assert.isOk(exposedMessage.isNextRound()); 40 | const msgNextRound = new factory.Messages.MsgWitnessNextRound(exposedMessage); 41 | assert.isOk(msgNextRound.roundNo && msgNextRound.roundNo === 1); 42 | assert.isOk(msgNextRound.conciliumId === 0); 43 | assert.isOk(msgNextRound.isNextRound()); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /tests/unit/messages/witnessNextRound.spec.js: -------------------------------------------------------------------------------- 1 | const {describe, it} = require('mocha'); 2 | const {assert} = require('chai'); 3 | 4 | const factory = require('../../testFactory'); 5 | 6 | describe('Witness NextRound message', () => { 7 | before(async function () { 8 | this.timeout(15000); 9 | await factory.asyncLoad(); 10 | }); 11 | 12 | after(async function () { 13 | this.timeout(15000); 14 | }); 15 | 16 | it('should FAIL to create empty', async () => { 17 | try { 18 | new factory.Messages.MsgWitnessNextRound(); 19 | } catch (e) { 20 | return; 21 | } 22 | assert.isOk(false, 'Unexpected success'); 23 | }); 24 | 25 | it('should create message', async () => { 26 | const msg = new factory.Messages.MsgWitnessNextRound({roundNo: 1, conciliumId: 0}); 27 | assert.isOk(msg.roundNo && msg.roundNo === 1); 28 | assert.isOk(msg.conciliumId === 0); 29 | assert.isOk(msg.isNextRound()); 30 | }); 31 | 32 | it('should encode/decode', async () => { 33 | const msg = new factory.Messages.MsgWitnessNextRound({roundNo: 1, conciliumId: 0}); 34 | const mockReceivedMsg = new factory.Messages.MsgCommon(msg.encode()); 35 | assert.isOk(mockReceivedMsg); 36 | const msgNextRound = new factory.Messages.MsgWitnessNextRound(mockReceivedMsg); 37 | assert.isOk(msgNextRound); 38 | assert.isOk(msgNextRound.isNextRound()); 39 | assert.isOk(msgNextRound.roundNo && msgNextRound.roundNo === 1); 40 | assert.isOk(msgNextRound.conciliumId === 0); 41 | }); 42 | 43 | it('should get content as roundNo', async () => { 44 | const sampleMsg = new factory.Messages.MsgWitnessNextRound({roundNo: 13, conciliumId: 0}); 45 | assert.equal(sampleMsg.roundNo, sampleMsg.content.roundNo); 46 | assert.equal(sampleMsg.roundNo, 13); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /tests/unit/offer-contract/offerContract.js: -------------------------------------------------------------------------------- 1 | class Base { 2 | constructor() { 3 | this._ownerAddress = callerAddress; 4 | } 5 | 6 | __getCode() { 7 | const arrFunctionsToPropagateFromBase = ['_checkOwner', '_transferOwnership', '_validateAddress']; 8 | 9 | const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(this)) 10 | .filter(name => name !== 'constructor' && typeof this[name] === 'function') 11 | .concat(arrFunctionsToPropagateFromBase); 12 | const objCode = {}; 13 | methods.forEach(strFuncName => { 14 | const strCodeMethod = this[strFuncName].toString(); 15 | 16 | // we prepend code of asynс function with '<' 17 | const codePrefix = Object.getPrototypeOf(this[strFuncName]).constructor.name === 'AsyncFunction' ? '<' : ''; 18 | const re = new RegExp(`${strFuncName}.*?(\(.*?\).*?\{.*\})`, 'ms'); 19 | const arrMatches = strCodeMethod.match(re); 20 | if (!arrMatches) throw new Error(`Bad code for ${strFuncName}`); 21 | objCode[strFuncName] = codePrefix + arrMatches[1]; 22 | }); 23 | return objCode; 24 | } 25 | 26 | _validateAddress(strAddress) { 27 | if (strAddress.length !== 40) throw 'Bad address'; 28 | } 29 | 30 | _checkOwner() { 31 | if (this._ownerAddress !== callerAddress) throw 'Unauthorized call'; 32 | } 33 | 34 | _transferOwnership(strNewAddress) { 35 | this._checkOwner(); 36 | this._validateAddress(strNewAddress); 37 | 38 | this._ownerAddress = strNewAddress; 39 | } 40 | } 41 | 42 | class PublicOffer extends Base { 43 | constructor(text, bAutoOpen = false) { 44 | super(); 45 | 46 | if (!this._ownerAddress) throw 'You should sign offer creation!'; 47 | 48 | this._bOpen = false; 49 | this.setText(text, bAutoOpen); 50 | this._objJoinedAddrs = {}; 51 | } 52 | 53 | setText(text, bAutoOpen = false) { 54 | this._checkOwner(); 55 | 56 | if (!text || !text.length) return; 57 | if (this._text) throw "You can't change already published text!"; 58 | 59 | this._text = text; 60 | this._bOpen = bAutoOpen; 61 | } 62 | 63 | open() { 64 | this._checkOwner(); 65 | 66 | if (!this._text) throw 'Offer contain no text!'; 67 | this._bOpen = true; 68 | } 69 | 70 | close() { 71 | this._checkOwner(); 72 | 73 | this._bOpen = false; 74 | } 75 | 76 | join() { 77 | if (!this.isOpen()) throw "Can't join. Offer closed."; 78 | if (!callerAddress) throw 'You should sign offer.'; 79 | if (this.wasAcceptedBy(callerAddress)) throw 'Already accepted'; 80 | 81 | // if you need some money transfer here - you could check value 82 | this._objJoinedAddrs[callerAddress] = contractTx; 83 | } 84 | 85 | isOpen() { 86 | return this._bOpen; 87 | } 88 | 89 | wasAcceptedBy(strAddr) { 90 | this._validateAddress(strAddr); 91 | return this._objJoinedAddrs[strAddr]; 92 | } 93 | 94 | getOfferText() { 95 | return this._text; 96 | } 97 | } 98 | 99 | module.exports = PublicOffer; 100 | -------------------------------------------------------------------------------- /tests/unit/oracle/oracle.js: -------------------------------------------------------------------------------- 1 | class Base { 2 | constructor() { 3 | this._ownerAddress = callerAddress; 4 | } 5 | 6 | __getCode() { 7 | const arrFunctionsToPropagateFromBase = [ 8 | '_checkOwner', 9 | '_transferOwnership', 10 | '_validateAddress', 11 | 'addManager', 12 | 'removeManager' 13 | ]; 14 | 15 | const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(this)) 16 | .filter(name => name !== 'constructor' && typeof this[name] === 'function') 17 | .concat(arrFunctionsToPropagateFromBase); 18 | const objCode = {}; 19 | methods.forEach(strFuncName => { 20 | const strCodeMethod = this[strFuncName].toString(); 21 | 22 | // we prepend code of asynс function with '<' 23 | const codePrefix = Object.getPrototypeOf(this[strFuncName]).constructor.name === 'AsyncFunction' ? '<' : ''; 24 | const re = new RegExp(`${strFuncName}.*?(\(.*?\).*?\{.*\})`, 'ms'); 25 | const arrMatches = strCodeMethod.match(re); 26 | if (!arrMatches) throw new Error(`Bad code for ${strFuncName}`); 27 | objCode[strFuncName] = codePrefix + arrMatches[1]; 28 | }); 29 | return objCode; 30 | } 31 | 32 | _validateAddress(strAddress) { 33 | if (strAddress.length !== 40) throw 'Bad address'; 34 | } 35 | 36 | _checkOwner() { 37 | if (this._ownerAddress !== callerAddress) throw 'Unauthorized call'; 38 | } 39 | 40 | _transferOwnership(strNewAddress) { 41 | this._checkOwner(); 42 | this._validateAddress(strNewAddress); 43 | 44 | this._ownerAddress = strNewAddress; 45 | } 46 | 47 | addManager(strManagerAddress) { 48 | this._validateAddress(strManagerAddress); 49 | this._checkOwner(); 50 | 51 | if (!this._managers) this._managers = []; 52 | this._managers.push(strManagerAddress); 53 | } 54 | 55 | removeManager(strManagerAddress) { 56 | this._validateAddress(strManagerAddress); 57 | this._checkOwner(); 58 | 59 | if (!this._managers) return; 60 | this._managers = this._managers.filter(strAddr => strAddr !== strManagerAddress); 61 | } 62 | 63 | _checkManager() { 64 | if (this._ownerAddress === callerAddress) return; 65 | 66 | if (!this._managers) throw 'Unauthorized call'; 67 | if (!~this._managers.findIndex(strAddr => strAddr === callerAddress)) throw 'Unauthorized call'; 68 | } 69 | } 70 | 71 | class RatesOracle extends Base { 72 | // TODO: add purger for old data? 73 | constructor() { 74 | super(); 75 | 76 | this._data = {}; 77 | } 78 | 79 | publish(strTicker, value) { 80 | this._checkManager(); 81 | 82 | this._ensureTicker(strTicker); 83 | 84 | // this will save a storage for us 85 | const nTimeBase = block.timestamp - this._data[strTicker].timeBase; 86 | 87 | this._data[strTicker].arrData.push([nTimeBase, value]); 88 | } 89 | 90 | publishBatch(arrValues) { 91 | this._checkManager(); 92 | 93 | arrValues.forEach(([strTicker, value]) => this.publish(strTicker, value)); 94 | } 95 | 96 | _ensureTicker(strTicker) { 97 | if (this._data[strTicker]) return; 98 | 99 | this._data[strTicker] = { 100 | timeBase: block.timestamp, 101 | arrData: [] 102 | }; 103 | } 104 | 105 | getDataForTicker(strTicker, nCount = 1440) { 106 | if (!this._data[strTicker]) throw `Ticker ${strTicker} not found`; 107 | 108 | const nTimeBase = this._data[strTicker].timeBase; 109 | return this._data[strTicker].arrData.slice(0 - nCount).map(([nOffset, value]) => [nTimeBase + nOffset, value]); 110 | } 111 | } 112 | 113 | module.exports = { 114 | Base, 115 | RatesOracle 116 | }; 117 | -------------------------------------------------------------------------------- /tests/unit/requestCache.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {describe, it} = require('mocha'); 4 | const {assert} = require('chai'); 5 | 6 | const factory = require('../testFactory'); 7 | const {pseudoRandomBuffer} = require('../testUtil'); 8 | 9 | let cache; 10 | 11 | describe('Request Cache', () => { 12 | before(async function () { 13 | this.timeout(15000); 14 | await factory.asyncLoad(); 15 | }); 16 | 17 | beforeEach(() => { 18 | cache = new factory.RequestCache(); 19 | }); 20 | 21 | it('should signal to request hash (buffer)', async () => { 22 | assert.isOk(cache.request(pseudoRandomBuffer())); 23 | }); 24 | 25 | it('should signal to request hash (string)', async () => { 26 | assert.isOk(cache.request(pseudoRandomBuffer().toString('hex'))); 27 | }); 28 | 29 | it('should NOT signal to request hash second time', async () => { 30 | const hash = pseudoRandomBuffer(); 31 | assert.isOk(cache.request(hash)); 32 | assert.isNotOk(cache.request(hash)); 33 | }); 34 | 35 | it('should REREQUEST after HOLDOFF', async () => { 36 | const strHash = pseudoRandomBuffer().toString('hex'); 37 | assert.isOk(cache.request(strHash)); 38 | cache._mapRequests.set(strHash, Date.now() - 1); 39 | assert.isOk(cache.request(strHash)); 40 | }); 41 | 42 | it('should clear successfully requested item', async () => { 43 | const hash = pseudoRandomBuffer(); 44 | assert.isOk(cache.request(hash)); 45 | cache.done(hash); 46 | 47 | // we can request it again 48 | assert.isOk(cache.request(hash)); 49 | }); 50 | 51 | it('should PASS isRequested for HOLDOFF period', async () => { 52 | const strHash = pseudoRandomBuffer().toString('hex'); 53 | cache.request(strHash); 54 | 55 | assert.isOk(cache.isRequested(strHash)); 56 | 57 | cache._mapRequests.set(strHash, Date.now() - 1); 58 | assert.isNotOk(cache.isRequested(strHash)); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /tests/unit/serializer.spec.js: -------------------------------------------------------------------------------- 1 | const {describe, it} = require('mocha'); 2 | const {assert} = require('chai'); 3 | const {inspect} = require('util'); 4 | const debug = require('debug')('serializer'); 5 | 6 | const factory = require('../testFactory'); 7 | 8 | describe('Serializer', () => { 9 | before(async function () { 10 | this.timeout(15000); 11 | await factory.asyncLoad(); 12 | }); 13 | 14 | after(async function () { 15 | this.timeout(15000); 16 | }); 17 | 18 | it('should serialize message', async () => { 19 | const msgVersion = new factory.Messages.MsgVersion({ 20 | nonce: 12, 21 | peerInfo: { 22 | capabilities: [ 23 | {service: factory.Constants.NODE, data: null}, 24 | {service: factory.Constants.WITNESS, data: Buffer.from('asdasdasd')} 25 | ], 26 | address: { 27 | addr0: 0x2001, 28 | addr1: 0xdb8, 29 | addr2: 0x1234, 30 | addr3: 0x3 31 | } 32 | } 33 | }); 34 | const buff = factory.Serializer.serialize(msgVersion); 35 | const buff2 = msgVersion.encode(); 36 | assert.isOk(buff); 37 | assert.isOk(Buffer.isBuffer(buff)); 38 | assert.isOk(buff.equals(buff2)); 39 | assert.isOk(buff.length); 40 | debug(inspect(buff, {colors: true, depth: null, breakLength: Infinity, compact: false})); 41 | }); 42 | 43 | it('should deserialize message', async () => { 44 | const serializedMessage = [ 45 | 61, 8, 132, 198, 160, 148, 1, 26, 7, 118, 101, 114, 115, 105, 111, 110, 50, 44, 16, 204, 235, 250, 218, 5, 46 | 26, 32, 10, 2, 8, 1, 10, 13, 8, 2, 18, 9, 97, 115, 100, 97, 115, 100, 97, 115, 100, 18, 11, 8, 129, 64, 16, 47 | 184, 27, 24, 180, 36, 32, 3, 32, 133, 228, 3 48 | ]; 49 | const msg = factory.Serializer.deSerialize(Buffer.from(serializedMessage)); 50 | assert.isOk(msg); 51 | assert.isOk(msg.isVersion()); 52 | debug(msg); 53 | }); 54 | 55 | it('should FAIL to deserialize PARTIAL message', async () => { 56 | const serializedMessage = [ 57 | 61, 8, 132, 198, 160, 148, 1, 26, 7, 118, 101, 114, 115, 105, 111, 110, 50, 44, 16, 204, 235, 250, 218, 5, 58 | 26, 32, 10, 2 59 | ]; 60 | try { 61 | factory.Serializer.deSerialize(Buffer.from(serializedMessage)); 62 | } catch (err) { 63 | debug(err); 64 | return; 65 | } 66 | assert.isOk(false, 'Unexpected success'); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /tests/unit/wallet.spec.js: -------------------------------------------------------------------------------- 1 | const {describe, it} = require('mocha'); 2 | const {assert} = require('chai'); 3 | const debug = require('debug')('wallet:test'); 4 | 5 | const factory = require('../testFactory'); 6 | 7 | describe('Wallet tests', () => { 8 | before(async function () { 9 | this.timeout(15000); 10 | await factory.asyncLoad(); 11 | }); 12 | 13 | after(async function () { 14 | this.timeout(15000); 15 | }); 16 | 17 | it('should NOT create wallet', async () => { 18 | try { 19 | new factory.Wallet(); 20 | } catch (err) { 21 | debug(err); 22 | return; 23 | } 24 | assert.isOk(false, 'Unexpected success'); 25 | }); 26 | 27 | it('should create wallet', function () { 28 | const wallet = new factory.Wallet('b7760a01705490e5e153a6ef7732369a72dbf9aaafb5c482cdfd960546909ec1'); 29 | assert.isOk(wallet.publicKey); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /types.js: -------------------------------------------------------------------------------- 1 | const typeforce = require('typeforce'); 2 | 3 | // it's some sorta strange, but i saw 31 byte length keys 4 | function PrivateKey(value) { 5 | if (!typeforce.String(value)) { 6 | if (!typeforce.Buffer(value)) { 7 | return false; 8 | } else { 9 | return value.length >= 31 || value.length <= 32; 10 | } 11 | } else { 12 | return value.length >= 62 || value.length <= 64; 13 | } 14 | } 15 | 16 | function PublicKey(value) { 17 | if (!typeforce.String(value)) { 18 | if (!typeforce.Buffer(value)) { 19 | return false; 20 | } else { 21 | return value.length === 33; 22 | } 23 | } else { 24 | return value.length === 66; 25 | } 26 | } 27 | 28 | function Empty(value) { 29 | return value === undefined; 30 | } 31 | 32 | function Str64(value) { 33 | return typeof value === 'string' && value.length === 64; 34 | } 35 | 36 | function Str40(value) { 37 | return typeof value === 'string' && value.length === 40; 38 | } 39 | 40 | function Amount(value) { 41 | return typeof value === 'number'; 42 | } 43 | 44 | const Hash256bit = typeforce.oneOf(typeforce.BufferN(32), Str64); 45 | 46 | module.exports = { 47 | Str64, 48 | Buf32: typeforce.BufferN(32), 49 | Hash256bit, 50 | Address: typeforce.oneOf(typeforce.BufferN(20), Str40), 51 | StrAddress: Str40, 52 | PrivateKey, 53 | PublicKey, 54 | Empty, 55 | InvVector: typeforce.compile({type: 'Number', hash: Hash256bit}), 56 | Coins: typeforce.quacksLike('Coins'), 57 | Contract: typeforce.quacksLike('Contract'), 58 | Patch: typeforce.quacksLike('PatchDB'), 59 | Block: typeforce.quacksLike('Block'), 60 | BlockInfo: typeforce.quacksLike('BlockInfo'), 61 | Transaction: typeforce.quacksLike('Transaction'), 62 | UTXO: typeforce.quacksLike('UTXO'), 63 | Amount, 64 | Signature: typeforce.BufferN(65), 65 | Input: typeforce.compile({nTxOutput: 'Number', txHash: Hash256bit}) 66 | }; 67 | --------------------------------------------------------------------------------