diff --git a/.eslintignore b/.eslintignore index 052737334621d89f18f1b6e66abf9c0fa60712a4..55fc3e6732effa174a7d7a8d92dc89c8b7ebd80f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,49 +1,4 @@ -app/cli.js -app/lib/blockchain/*.js -app/lib/blockchain/interfaces/*.js -app/lib/computation/*.js -app/lib/common-libs/*.js -app/lib/common-libs/**/*.js -app/lib/db/*.js -app/lib/dto/*.js -app/lib/indexer.js -app/lib/common.js -app/lib/dal/drivers/*.js -app/lib/dal/sqliteDAL/*.js -app/lib/dal/sqliteDAL/index/*.js -app/lib/dal/fileDALs/*.js -app/lib/dal/fileDAL.js -app/service/*.js -app/lib/rules/*.js -app/lib/system/*.js -app/lib/streams/*.js -app/lib/helpers/*.js -app/modules/ws2p/*.js -app/modules/ws2p/lib/*.js -app/modules/ws2p/lib/*/*.js -app/lib/*.js -app/modules/wizard.js -app/modules/router.js -app/modules/revert.js -app/modules/reset.js -app/modules/reapply.js -app/modules/peersignal.js -app/modules/plugin.js -app/modules/daemon.js -app/modules/export-bc.js -app/modules/check-config.js -app/modules/config.js -app/modules/prover/*.js -app/modules/prover/lib/*.js -app/modules/keypair/*.js -app/modules/keypair/lib/*.js -app/modules/bma/*.js -app/modules/bma/lib/*.js -app/modules/bma/lib/entity/*.js -app/modules/bma/lib/controllers/*.js -app/modules/crawler/*.js -app/modules/crawler/lib/*.js -app/ProcessCpuProfiler.js -app/lib/common/package.js +app/*.js +app/**/*.js test/*.js test/**/*.js \ No newline at end of file diff --git a/.eslintrc b/.eslintrc index 1fe67b0998ca3011b46cfb79179b79d73f9db416..809451a60a39336d7fa7ffc020a76c9534d6f18a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,11 +1,14 @@ { + "parserOptions": { + "ecmaVersion": 8 + }, "plugins": [ "mocha" ], -// "ecmaFeatures": { -// "arrowFunctions": true, -// "generators": true -// }, + "ecmaFeatures": { + "arrowFunctions": true, + "generators": true + }, "rules": { "curly": 0, diff --git a/.gitignore b/.gitignore index 0a79bb0ce1ddaf1033f74641aff5c1ba8f6ce180..6dd12606baa2fd31f558354b9063d39f2ed1e903 100644 --- a/.gitignore +++ b/.gitignore @@ -35,48 +35,14 @@ vagrant/duniter .nyc_output coverage/ -# TS migration -test/blockchain/*.js* -test/blockchain/*.d.ts -test/blockchain/lib/*.js* -test/blockchain/lib/*.d.ts +# typecode +typedoc/ + /index.js* /index.d.ts /server.js* /server.d.ts app/**/*.js* app/**/*.d.ts -test/integration/revoked_pubkey_replay.js -test/integration/server-shutdown.js -test/integration/transactions-csv-cltv-sig.js -test/integration/ws2p*js -test/integration/*.js.map -test/integration/*.d.ts -test/integration/membership_chainability.js* -test/integration/membership_chainability.d.ts -test/integration/tools/toolbox.js* -test/integration/tools/toolbox.d.ts -test/integration/tools/TestUser.js* -test/integration/tools/TestUser.d.ts -test/integration/documents-currency.js* -test/integration/documents-currency.d.ts -test/integration/forwarding.js -test/integration/branches_switch.js -test/integration/branches2.js -test/integration/transactions-chaining.js -test/fast/modules/crawler/block_pulling.js* -test/fast/modules/crawler/block_pulling.d.ts -test/fast/fork*.js* -test/fast/fork*.d.ts -test/fast/proxies*.js* -test/fast/proxies*.d.ts -test/fast/modules/ws2p/*.js* -test/fast/modules/ws2p/*.d.ts -test/fast/modules/common/grammar.js* -test/fast/modules/common/grammar.d.ts -test/fast/prover/pow-1-cluster.d.ts -test/fast/prover/pow-1-cluster.js -test/fast/prover/pow-1-cluster.js.map -test/fast/protocol-local-rule-chained-tx-depth.js -test/fast/protocol-local-rule-chained-tx-depth.js.map -test/fast/protocol-local-rule-chained-tx-depth.d.ts +test/**/*.d.ts +test/**/*.js* \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2a23ae32731bf69c13a352cbcdca577bcde1e928..db9f62ce5939febc664a7fff20ac80eea7ca1195 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,6 +2,7 @@ stages: - github-sync - build - test + - pages - package - prerelease - release @@ -33,28 +34,81 @@ push_to_github: before_script: - export NVM_DIR="$HOME/.nvm" - . "$NVM_DIR/nvm.sh" + +.cached_nvm: &cached_nvm + <<: *nvm_env + cache: + untracked: true + paths: + - node_modules/ build: - <<: *nvm_env + <<: *cached_nvm stage: build script: - yarn +pages: + <<: *nvm_env + stage: pages + cache: {} + script: + - yarn + - yarn doc + - mkdir -p public + - cp .gitlab/pages/pages-index.html public/index.html + - sed -i "s/{BRANCH}/$CI_COMMIT_REF_NAME/g" public/index.html + - mv typedoc public/ + - echo "$CI_JOB_ID" + - curl "https://git.duniter.org/nodes/typescript/duniter/-/jobs/$CI_JOB_ID/artifacts/raw/coverage.tar.gz" + - tar xzf coverage.tar.gz + - mv coverage "public/coverage" + - ls public + artifacts: + untracked: true + paths: + - public + only: + - loki + - dev + test: + <<: *cached_nvm + stage: test + script: + - yarn test + # Push coverage to GitLab pages + - tar cvzf coverage.tar.gz coverage/ + # Code coverage display in GitLab + - sed -n 23p coverage/index.html | grep -Po "\d+.\d+" | sed -e "s/\(.*\)/<coverage>\1%<\/coverage>/" + coverage: '/<coverage>(\d+.\d+\%)<\/coverage>/' + artifacts: + paths: + - coverage.tar.gz + expire_in: 4h + +sync_g1: <<: *nvm_env stage: test script: - yarn - - yarn test - - sed -n 23p coverage/index.html + - bash .gitlab/test/check_g1_sync.sh + +sync_gtest: + <<: *nvm_env + stage: test + script: + - yarn + - bash .gitlab/test/check_gt_sync.sh .build_releases: &build_releases stage: package allow_failure: false - image: duniter/release-builder:v1.0.1 + image: duniter/release-builder:v1.2.0 + cache: {} + when: manual tags: - redshift-duniter-builder - when: manual artifacts: paths: &releases_artifacts - work/bin/ @@ -62,6 +116,7 @@ test: releases:test: <<: *build_releases script: + - rm -rf node_modules/ - bash "release/arch/linux/build-lin.sh" "$(date +%Y%m%d).$(date +%H%M).$(date +%S)" artifacts: paths: *releases_artifacts @@ -72,6 +127,7 @@ releases:test: releases:x64: <<: *build_releases script: + - rm -rf node_modules/ - bash "release/arch/linux/build-lin.sh" "${CI_COMMIT_TAG#v}" artifacts: paths: *releases_artifacts diff --git a/.gitlab/pages/pages-index.html b/.gitlab/pages/pages-index.html new file mode 100644 index 0000000000000000000000000000000000000000..9e756d7754de4ec66024a899318502fc4c63df7a --- /dev/null +++ b/.gitlab/pages/pages-index.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title>CI pages</title> + <style> + html, body { + font-family: "Courier New", Consolas, monospace; + } + + li { + line-height: 25px; + } + </style> +</head> +<body> +<p> + <img src="https://raw.github.com/duniter/duniter/master/images/250%C3%97250.png"/> +</p> +<h1>Continuous Integration (CI) pages:</h1> +<p> + <ul> + <li><a href="https://nodes.duniter.io/typescript/duniter/typedoc/">Typedoc (`{BRANCH}` branch)</a></li> + <li><a href="https://nodes.duniter.io/typescript/duniter/coverage/">Coverage (`{BRANCH}` branch)</a></li> + </ul> +</p> +</body> +</html> \ No newline at end of file diff --git a/.gitlab/test/check_g1_sync.sh b/.gitlab/test/check_g1_sync.sh new file mode 100755 index 0000000000000000000000000000000000000000..52f0e8e521306c90a1a6923afc728b73403ef321 --- /dev/null +++ b/.gitlab/test/check_g1_sync.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +G1_TARGET_BLOCK=132446 # This is a fixed block# which determines to the sha1 hashes +G1_IINDEX_CS=26393b64cdb9abb8e4012d6914f475635cba4c60 +G1_MINDEX_CS=7c5f07c7705647365b8965fcfc5a084c2f82a388 +G1_CINDEX_CS=3803c1ed8d3dd8f31558666d8dfd30272a6d0b74 +G1_SINDEX_CS=551bdba1855d5c49cd503fcb8ad787b2a24c2c42 + +.gitlab/test/check_indexes.sh /tmp/duniter_ci_dump/ g1 ${G1_TARGET_BLOCK} ${G1_IINDEX_CS} ${G1_MINDEX_CS} ${G1_CINDEX_CS} ${G1_SINDEX_CS} diff --git a/.gitlab/test/check_gt_sync.sh b/.gitlab/test/check_gt_sync.sh new file mode 100755 index 0000000000000000000000000000000000000000..b2c187f8d7839b40c7305fb85af65b7a8520f30c --- /dev/null +++ b/.gitlab/test/check_gt_sync.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +GT_TARGET_BLOCK=210000 # This is a fixed block# which determines to the sha1 hashes +GT_IINDEX_CS=dfd2dfc3d4d0ced4c101badb4d4a1ab85de8cbde +GT_MINDEX_CS=9d8f665f5fcf1f21082278c4787bb3df085ff109 +GT_CINDEX_CS=b141361fb40f4c13f03f4640151c7674e190a4dd +GT_SINDEX_CS=7c6801027e39b9fea9be973d8773ac77d2c9a1f9 + +.gitlab/test/check_indexes.sh /tmp/duniter_ci_dump/ gt ${GT_TARGET_BLOCK} ${GT_IINDEX_CS} ${GT_MINDEX_CS} ${GT_CINDEX_CS} ${GT_SINDEX_CS} diff --git a/.gitlab/test/check_indexes.sh b/.gitlab/test/check_indexes.sh new file mode 100755 index 0000000000000000000000000000000000000000..b4b103ea7a88cc61a2dfc56a7f79344bdca8f536 --- /dev/null +++ b/.gitlab/test/check_indexes.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash + +ORIGIN_DIR=`pwd` +mkdir -p $1 +DUMP_DIR=`cd $1 && pwd` +CURRENCY=$2 +ARCHIVES="$DUMP_DIR/archives_$CURRENCY" +DB_TEST="gitlab_ci_sync_test_$CURRENCY" +G1_TARGET_BLOCK=$3 # This is a fixed block# which determines to the sha1 hashes +G1_IINDEX_CS=$4 +G1_MINDEX_CS=$5 +G1_CINDEX_CS=$6 +G1_SINDEX_CS=$7 + +checksum_test() { + local table=$1 + local correct_hash=$2 + local db=$3 + echo "Checking $table's checksum..." + bin/duniter --mdb ${db} dump table "$table" > "$DUMP_DIR/$table" + result_hash=`sha1sum "$DUMP_DIR/$table" | grep -Po ".* " | grep -Po "[a-f0-9]+"` +# rm -f "$DUMP_DIR/$table" + if [ "$result_hash" == "$correct_hash" ]; then + echo "OK"; + else + echo "Error! Wrong hash detected. ($result_hash != $correct_hash)" + exit 1 + fi +} + +sync_data() { + local db=$1 + local target=$2 + local target_block=$3 + local reset_data="bin/duniter --mdb ${db} reset all" + local sync="bin/duniter --mdb ${db} sync ${target} --nointeractive ${target_block}" + echo "$reset_data" + ${reset_data} + echo "$sync" + ${sync} +} + +if [ -d ${ARCHIVES} ]; then + echo "Updating archives..." + cd ${ARCHIVES} + git checkout master + git pull origin master +else + echo "Cloning archives..." + git clone https://git.duniter.org/c-geek/blockchain-archives.git ${ARCHIVES} +fi + +echo "Positionnement dans $ORIGIN_DIR" + +cd ${ORIGIN_DIR} + +sync_data ${DB_TEST} "$ARCHIVES/$CURRENCY" ${G1_TARGET_BLOCK} +checksum_test i_index ${G1_IINDEX_CS} ${DB_TEST} +checksum_test m_index ${G1_MINDEX_CS} ${DB_TEST} +checksum_test c_index ${G1_CINDEX_CS} ${DB_TEST} +checksum_test s_index ${G1_SINDEX_CS} ${DB_TEST} diff --git a/README.md b/README.md index 55670b0f10b18a575759d00146d5cb28d3be16e8..af456b1f7c3231554d6cdd9a3633707ab0e6020a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - + # Duniter [](https://travis-ci.org/duniter/duniter) [](https://coveralls.io/github/duniter/duniter?branch=master) [](https://david-dm.org/duniter/duniter) @@ -6,7 +6,7 @@ Duniter (previously uCoin) is a libre software allowing to create a new kind of Inspired by [Bitcoin](https://github.com/bitcoin/bitcoin) and [OpenUDC](https://github.com/Open-UDC/open-udc) projects. -<p align="center"><img src="https://github.com/duniter/duniter/blob/master/images/duniter_admin_g1.png" /></p> +<p align="center"><img src="https://git.duniter.org/nodes/typescript/duniter/raw/dev/images/duniter_admin_g1.png" /></p> ## Development state diff --git a/app/ProcessCpuProfiler.ts b/app/ProcessCpuProfiler.ts index 163f784307dba1c7a37a2527abc030dfd0c34c44..42b9999269e7b6ca6c00098b213dfdcb9b1b6fc9 100644 --- a/app/ProcessCpuProfiler.ts +++ b/app/ProcessCpuProfiler.ts @@ -14,11 +14,20 @@ const SAMPLING_PERIOD = 150 // milliseconds const MAX_SAMPLES_DISTANCE = 20 * 1000000 // seconds -function getMicrosecondsTime() { +export function getMicrosecondsTime() { const [ seconds, nanoseconds ] = process.hrtime() return seconds * 1000000 + nanoseconds / 1000 } +export function getNanosecondsTime() { + const [ seconds, nanoseconds ] = process.hrtime() + return seconds * 1000000 + nanoseconds +} + +export function getDurationInMicroSeconds(before:number) { + return parseInt(String(getMicrosecondsTime() - before)) +} + interface CpuUsage { user: number system:number diff --git a/app/cli.ts b/app/cli.ts index 0060f3718be8a6d7f45749efa228847ec27076e0..33472bb2ac0cfea4e2b8eb955bb2fddb82b02a97 100644 --- a/app/cli.ts +++ b/app/cli.ts @@ -74,6 +74,7 @@ export const ExecuteCommand = () => { .option('--nohttplogs', 'Disable HTTP logs') .option('--isolate', 'Avoid the node to send peering or status informations to the network') .option('--forksize <size>', 'Maximum size of fork window', parseInt) + .option('--notrim', 'Disable the INDEX trimming.') .option('--memory', 'Memory mode') ; diff --git a/app/lib/blockchain/BasicBlockchain.ts b/app/lib/blockchain/BasicBlockchain.ts deleted file mode 100644 index 40cd0d668fb5fd2c1d71c06539038ec22ff4cea0..0000000000000000000000000000000000000000 --- a/app/lib/blockchain/BasicBlockchain.ts +++ /dev/null @@ -1,71 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict" -import {BlockchainOperator} from "./interfaces/BlockchainOperator" - -export class BasicBlockchain { - - constructor(private op:BlockchainOperator) { - } - - /** - * Adds a block at the end of the blockchain. - */ - pushBlock(b:any) { - return this.op.store(b) - } - - /** - * Get the block identified by `number` - * @param number block ID. - * @returns {*} Promise<Block> - */ - getBlock(number:number) { - return this.op.read(number) - } - - /** - * Get the nth block from the top of the blockchain. - * @param index Index from top. Defaults to `0`. E.g. `0` = HEAD, `1` = HEAD~1, etc. - * @returns {*} Promise<Block> - */ - head(index = 0) { - return this.op.head(index) - } - - /** - * Blockchain size, in number of blocks. - * @returns {*} Size. - */ - height() { - return this.op.height() - } - - /** - * Get the (n+1)th blocks top blocks of the blockchain, ordered by number ascending. - * @param n Quantity from top. E.g. `1` = [HEAD], `3` = [HEAD, HEAD~1, HEAD~2], etc. - * @returns {*} Promise<Block> - */ - headRange(n:number) { - return this.op.headRange(n) - } - - /** - * Pops the blockchain HEAD. - * @returns {*} Promise<Block> The reverted block. - */ - revertHead() { - return this.op.revertHead() - } -} diff --git a/app/lib/blockchain/DuniterBlockchain.ts b/app/lib/blockchain/DuniterBlockchain.ts index 48a3215e80e231c5e07bd6ba20190101edf69025..074af4420bf5099d69dc27d99576f1f58ce6a94e 100644 --- a/app/lib/blockchain/DuniterBlockchain.ts +++ b/app/lib/blockchain/DuniterBlockchain.ts @@ -11,9 +11,16 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -import {MiscIndexedBlockchain} from "./MiscIndexedBlockchain" -import {IindexEntry, IndexEntry, Indexer, MindexEntry, SindexEntry} from "../indexer" -import {BlockchainOperator} from "./interfaces/BlockchainOperator" +import { + BasedAmount, + FullIindexEntry, + IindexEntry, + IndexEntry, + Indexer, + MindexEntry, + SimpleTxEntryForWallet, + SimpleUdEntryForWallet +} from "../indexer" import {ConfDTO} from "../dto/ConfDTO" import {BlockDTO} from "../dto/BlockDTO" import {DBHead} from "../db/DBHead" @@ -25,16 +32,15 @@ import {CertificationDTO} from "../dto/CertificationDTO" import {MembershipDTO} from "../dto/MembershipDTO" import {TransactionDTO} from "../dto/TransactionDTO" import {CommonConstants} from "../common-libs/constants" +import {FileDAL} from "../dal/fileDAL" +import {NewLogger} from "../logger" +import {DBTx} from "../db/DBTx" +import {Underscore} from "../common-libs/underscore" +import {OtherConstants} from "../other_constants" -const _ = require('underscore') +export class DuniterBlockchain { -export class DuniterBlockchain extends MiscIndexedBlockchain { - - constructor(blockchainStorage:BlockchainOperator, dal:any) { - super(blockchainStorage, dal.mindexDAL, dal.iindexDAL, dal.sindexDAL, dal.cindexDAL) - } - - static async checkBlock(block:BlockDTO, withPoWAndSignature:boolean, conf: ConfDTO, dal:any) { + static async checkBlock(block:BlockDTO, withPoWAndSignature:boolean, conf: ConfDTO, dal:FileDAL) { const index = Indexer.localIndex(block, conf) if (withPoWAndSignature) { await CHECK.ASYNC.ALL_LOCAL(block, conf, index) @@ -55,7 +61,9 @@ export class DuniterBlockchain extends MiscIndexedBlockchain { // BR_G98 if (Indexer.ruleCurrency(block, HEAD) === false) throw Error('ruleCurrency'); // BR_G51 - if (Indexer.ruleNumber(block, HEAD) === false) throw Error('ruleNumber'); + if (Indexer.ruleNumber(block, HEAD) === false) { + throw Error('ruleNumber') + } // BR_G52 if (Indexer.rulePreviousHash(block, HEAD) === false) throw Error('rulePreviousHash'); // BR_G53 @@ -103,7 +111,9 @@ export class DuniterBlockchain extends MiscIndexedBlockchain { // BR_G70 if (Indexer.ruleCertificationToLeaver(cindex) === false) throw Error('ruleCertificationToLeaver'); // BR_G71 - if (Indexer.ruleCertificationReplay(cindex) === false) throw Error('ruleCertificationReplay'); + if (Indexer.ruleCertificationReplay(cindex) === false) { + throw Error('ruleCertificationReplay') + } // BR_G72 if (Indexer.ruleCertificationSignature(cindex) === false) throw Error('ruleCertificationSignature'); // BR_G73 @@ -123,7 +133,9 @@ export class DuniterBlockchain extends MiscIndexedBlockchain { // BR_G80 if (Indexer.ruleMembershipLeaverIsMember(mindex) === false) throw Error('ruleMembershipLeaverIsMember'); // BR_G81 - if (Indexer.ruleMembershipActiveIsMember(mindex) === false) throw Error('ruleMembershipActiveIsMember'); + if (Indexer.ruleMembershipActiveIsMember(mindex) === false) { + throw Error('ruleMembershipActiveIsMember') + } // BR_G82 if (Indexer.ruleMembershipRevokedIsMember(mindex) === false) throw Error('ruleMembershipRevokedIsMember'); // BR_G83 @@ -133,7 +145,9 @@ export class DuniterBlockchain extends MiscIndexedBlockchain { // BR_G85 if (Indexer.ruleMembershipExcludedIsMember(iindex) === false) throw Error('ruleMembershipExcludedIsMember'); // BR_G86 - if ((await Indexer.ruleToBeKickedArePresent(iindex, dal)) === false) throw Error('ruleToBeKickedArePresent'); + if ((await Indexer.ruleToBeKickedArePresent(iindex, dal)) === false) { + throw Error('ruleToBeKickedArePresent') + } // BR_G103 if (Indexer.ruleTxWritability(sindex) === false) throw Error('ruleTxWritability'); // BR_G87 @@ -174,7 +188,7 @@ export class DuniterBlockchain extends MiscIndexedBlockchain { return { index, HEAD } } - async pushTheBlock(obj: BlockDTO, index: IndexEntry[], HEAD: DBHead | null, conf: ConfDTO, dal: any, logger: any, trim = true) { + static async pushTheBlock(obj:BlockDTO, index:IndexEntry[], HEAD:DBHead | null, conf:ConfDTO, dal:FileDAL, logger:any, trim = true) { const start = Date.now(); const block = BlockDTO.fromJSONObject(obj) try { @@ -189,6 +203,13 @@ export class DuniterBlockchain extends MiscIndexedBlockchain { } logger.info('Block #' + block.number + ' added to the blockchain in %s ms', (Date.now() - start)); + + // Periodically, we trim the blockchain + if (block.number % CommonConstants.BLOCKS_COLLECT_THRESHOLD === 0) { + // Database trimming + await dal.loki.flushAndTrimData() + } + return BlockDTO.fromJSONObject(added) } catch(err) { @@ -201,7 +222,7 @@ export class DuniterBlockchain extends MiscIndexedBlockchain { // await supra.recordIndex(index) } - async saveBlockData(current: DBBlock, block: BlockDTO, conf: ConfDTO, dal: any, logger: any, index: IndexEntry[], HEAD: DBHead | null, trim: boolean) { + static async saveBlockData(current:DBBlock|null, block:BlockDTO, conf:ConfDTO, dal:FileDAL, logger:any, index:IndexEntry[], HEAD:DBHead | null, trim: boolean) { if (block.number == 0) { await this.saveParametersForRoot(block, conf, dal); } @@ -212,17 +233,14 @@ export class DuniterBlockchain extends MiscIndexedBlockchain { await this.createNewcomers(indexes.iindex, dal, logger); // Save indexes - await dal.bindexDAL.saveEntity(indexes.HEAD); - await dal.mindexDAL.insertBatch(indexes.mindex); - await dal.iindexDAL.insertBatch(indexes.iindex); - await dal.sindexDAL.insertBatch(indexes.sindex); - await dal.cindexDAL.insertBatch(indexes.cindex); + await dal.bindexDAL.insert(indexes.HEAD); + await dal.flushIndexes(indexes) // Create/Update nodes in wotb await this.updateMembers(block, dal); // Update the wallets' blances - await this.updateWallets(indexes.sindex, dal) + await this.updateWallets(indexes.sindex, indexes.dividends, dal) if (trim) { const TAIL = await dal.bindexDAL.tail(); @@ -237,6 +255,7 @@ export class DuniterBlockchain extends MiscIndexedBlockchain { const MAX_BINDEX_SIZE = conf.forksize + bindexSize const currentSize = indexes.HEAD.number - TAIL.number + 1 if (currentSize > MAX_BINDEX_SIZE) { + await dal.archiveBlocks() await dal.trimIndexes(indexes.HEAD.number - MAX_BINDEX_SIZE); } } @@ -248,7 +267,7 @@ export class DuniterBlockchain extends MiscIndexedBlockchain { await dal.updateWotbLinks(indexes.cindex); // Create/Update certifications - await this.removeCertificationsFromSandbox(block, dal); + await DuniterBlockchain.removeCertificationsFromSandbox(block, dal); // Create/Update memberships await this.removeMembershipsFromSandbox(block, dal); // Compute to be revoked members @@ -260,10 +279,12 @@ export class DuniterBlockchain extends MiscIndexedBlockchain { // Saves the block (DAL) await dal.saveBlock(dbb); + await dal.loki.commitData() + return dbb } - async saveParametersForRoot(block:BlockDTO, conf:ConfDTO, dal:any) { + static async saveParametersForRoot(block:BlockDTO, conf:ConfDTO, dal:FileDAL) { if (block.parameters) { const bconf = BlockDTO.getConf(block) conf.c = bconf.c; @@ -293,9 +314,10 @@ export class DuniterBlockchain extends MiscIndexedBlockchain { } } - async createNewcomers(iindex:IindexEntry[], dal:any, logger:any) { - for (const entry of iindex) { - if (entry.op == CommonConstants.IDX_CREATE) { + static async createNewcomers(iindex:IindexEntry[], dal:FileDAL, logger:any) { + for (const i of iindex) { + if (i.op == CommonConstants.IDX_CREATE) { + const entry = i as FullIindexEntry // Reserves a wotb ID entry.wotb_id = dal.wotb.addNode(); logger.trace('%s was affected wotb_id %s', entry.uid, entry.wotb_id); @@ -306,58 +328,64 @@ export class DuniterBlockchain extends MiscIndexedBlockchain { } } - async updateMembers(block:BlockDTO, dal:any) { + static async updateMembers(block:BlockDTO, dal:FileDAL) { // Joiners (come back) for (const inlineMS of block.joiners) { let ms = MembershipDTO.fromInline(inlineMS) - const idty = await dal.getWrittenIdtyByPubkey(ms.issuer); + const idty = await dal.getWrittenIdtyByPubkeyForWotbID(ms.issuer); dal.wotb.setEnabled(true, idty.wotb_id); + await dal.dividendDAL.setMember(true, ms.issuer) } // Revoked for (const inlineRevocation of block.revoked) { let revocation = RevocationDTO.fromInline(inlineRevocation) - await dal.revokeIdentity(revocation.pubkey, block.number); + await dal.revokeIdentity(revocation.pubkey) } // Excluded for (const excluded of block.excluded) { - const idty = await dal.getWrittenIdtyByPubkey(excluded); + const idty = await dal.getWrittenIdtyByPubkeyForWotbID(excluded); dal.wotb.setEnabled(false, idty.wotb_id); + await dal.dividendDAL.setMember(false, excluded) } } - async updateWallets(sindex:SindexEntry[], aDal:any, reverse = false) { - const differentConditions = _.uniq(sindex.map((entry) => entry.conditions)) + static async updateWallets(sindex:SimpleTxEntryForWallet[], dividends:SimpleUdEntryForWallet[], aDal:any, reverse = false) { + const differentConditions = Underscore.uniq(sindex.map((entry) => entry.conditions).concat(dividends.map(d => d.conditions))) for (const conditions of differentConditions) { - const creates = _.filter(sindex, (entry:SindexEntry) => entry.conditions === conditions && entry.op === CommonConstants.IDX_CREATE) - const updates = _.filter(sindex, (entry:SindexEntry) => entry.conditions === conditions && entry.op === CommonConstants.IDX_UPDATE) - const positives = creates.reduce((sum:number, src:SindexEntry) => sum + src.amount * Math.pow(10, src.base), 0) - const negatives = updates.reduce((sum:number, src:SindexEntry) => sum + src.amount * Math.pow(10, src.base), 0) + const udsOfKey: BasedAmount[] = dividends.filter(d => d.conditions === conditions).map(d => ({ amount: d.amount, base: d.base })) + const creates: BasedAmount[] = sindex.filter(entry => entry.conditions === conditions && entry.op === CommonConstants.IDX_CREATE) + const updates: BasedAmount[] = sindex.filter(entry => entry.conditions === conditions && entry.op === CommonConstants.IDX_UPDATE) + const positives = creates.concat(udsOfKey).reduce((sum, src) => sum + src.amount * Math.pow(10, src.base), 0) + const negatives = updates.reduce((sum, src) => sum + src.amount * Math.pow(10, src.base), 0) const wallet = await aDal.getWallet(conditions) let variation = positives - negatives if (reverse) { // To do the opposite operations, for a reverted block variation *= -1 } + if (OtherConstants.TRACE_BALANCES) { + NewLogger().trace('Balance of %s: %s (%s %s %s)', wallet.conditions, wallet.balance + variation, wallet.balance, variation < 0 ? '-' : '+', Math.abs(variation)) + } wallet.balance += variation await aDal.saveWallet(wallet) } } - async revertBlock(number:number, hash:string, dal:any) { + static async revertBlock(number:number, hash:string, dal:FileDAL, block?: DBBlock) { const blockstamp = [number, hash].join('-'); // Revert links const writtenOn = await dal.cindexDAL.getWrittenOn(blockstamp); for (const entry of writtenOn) { - const from = await dal.getWrittenIdtyByPubkey(entry.issuer); - const to = await dal.getWrittenIdtyByPubkey(entry.receiver); + const from = await dal.getWrittenIdtyByPubkeyForWotbID(entry.issuer); + const to = await dal.getWrittenIdtyByPubkeyForWotbID(entry.receiver); if (entry.op == CommonConstants.IDX_CREATE) { // We remove the created link - dal.wotb.removeLink(from.wotb_id, to.wotb_id, true); + dal.wotb.removeLink(from.wotb_id, to.wotb_id); } else { // We add the removed link - dal.wotb.addLink(from.wotb_id, to.wotb_id, true); + dal.wotb.addLink(from.wotb_id, to.wotb_id); } } @@ -366,37 +394,41 @@ export class DuniterBlockchain extends MiscIndexedBlockchain { // Get the money movements to revert in the balance const REVERSE_BALANCE = true - const sindexOfBlock = await dal.sindexDAL.getWrittenOn(blockstamp) + const sindexOfBlock = await dal.sindexDAL.getWrittenOnTxs(blockstamp) - await dal.bindexDAL.removeBlock(number); + await dal.bindexDAL.removeBlock(blockstamp); await dal.mindexDAL.removeBlock(blockstamp); await dal.iindexDAL.removeBlock(blockstamp); await dal.cindexDAL.removeBlock(blockstamp); await dal.sindexDAL.removeBlock(blockstamp); + const { createdUDsDestroyedByRevert, consumedUDsRecoveredByRevert } = await dal.dividendDAL.revertUDs(number) // Then: normal updates - const block = await dal.getBlockByBlockstampOrNull(blockstamp); - const previousBlock = await dal.getBlock(number - 1); + const previousBlock = await dal.getFullBlockOf(number - 1) // Set the block as SIDE block (equivalent to removal from main branch) await dal.blockDAL.setSideBlock(number, previousBlock); + // Update the dividends in our wallet + await this.updateWallets([], createdUDsDestroyedByRevert, dal, REVERSE_BALANCE) + await this.updateWallets([], consumedUDsRecoveredByRevert, dal) // Revert the balances variations for this block - await this.updateWallets(sindexOfBlock, dal, REVERSE_BALANCE) + await this.updateWallets(sindexOfBlock, [], dal, REVERSE_BALANCE) // Restore block's transaction as incoming transactions - await this.undoDeleteTransactions(block, dal) - - return block + if (block) { + await this.undoDeleteTransactions(block, dal) + } } - async undoMembersUpdate(blockstamp:string, dal:any) { + static async undoMembersUpdate(blockstamp:string, dal:FileDAL) { const joiners = await dal.iindexDAL.getWrittenOn(blockstamp); for (const entry of joiners) { // Undo 'join' which can be either newcomers or comebackers // => equivalent to i_index.member = true AND i_index.op = 'UPDATE' if (entry.member === true && entry.op === CommonConstants.IDX_UPDATE) { - const idty = await dal.getWrittenIdtyByPubkey(entry.pub); + const idty = await dal.getWrittenIdtyByPubkeyForWotbID(entry.pub); dal.wotb.setEnabled(false, idty.wotb_id); + await dal.dividendDAL.setMember(false, entry.pub) } } const newcomers = await dal.iindexDAL.getWrittenOn(blockstamp); @@ -405,7 +437,9 @@ export class DuniterBlockchain extends MiscIndexedBlockchain { // => equivalent to i_index.op = 'CREATE' if (entry.op === CommonConstants.IDX_CREATE) { // Does not matter which one it really was, we pop the last X identities + NewLogger().trace('removeNode') dal.wotb.removeNode(); + await dal.dividendDAL.deleteMember(entry.pub) } } const excluded = await dal.iindexDAL.getWrittenOn(blockstamp); @@ -413,17 +447,18 @@ export class DuniterBlockchain extends MiscIndexedBlockchain { // Undo excluded (make them become members again in wotb) // => equivalent to m_index.member = false if (entry.member === false && entry.op === CommonConstants.IDX_UPDATE) { - const idty = await dal.getWrittenIdtyByPubkey(entry.pub); + const idty = await dal.getWrittenIdtyByPubkeyForWotbID(entry.pub); dal.wotb.setEnabled(true, idty.wotb_id); + await dal.dividendDAL.setMember(true, entry.pub) } } } - async undoDeleteTransactions(block:BlockDTO, dal:any) { + static async undoDeleteTransactions(block:DBBlock, dal:FileDAL) { for (const obj of block.transactions) { obj.currency = block.currency; let tx = TransactionDTO.fromJSONObject(obj) - await dal.saveTransaction(tx); + await dal.saveTransaction(DBTx.fromTransactionDTO(tx)) } } @@ -433,10 +468,10 @@ export class DuniterBlockchain extends MiscIndexedBlockchain { * @param block Block in which are contained the certifications to remove from sandbox. * @param dal The DAL */ - async removeCertificationsFromSandbox(block:BlockDTO, dal:any) { + static async removeCertificationsFromSandbox(block:BlockDTO, dal:FileDAL) { for (let inlineCert of block.certifications) { let cert = CertificationDTO.fromInline(inlineCert) - let idty = await dal.getWritten(cert.to); + let idty = await dal.getWrittenIdtyByPubkeyForHashing(cert.to); await dal.deleteCert({ from: cert.from, target: IdentityDTO.getTargetHash(idty), @@ -451,7 +486,7 @@ export class DuniterBlockchain extends MiscIndexedBlockchain { * @param block Block in which are contained the certifications to remove from sandbox. * @param dal The DAL */ - async removeMembershipsFromSandbox(block:BlockDTO, dal:any) { + static async removeMembershipsFromSandbox(block:BlockDTO, dal:FileDAL) { const mss = block.joiners.concat(block.actives).concat(block.leavers); for (const inlineMS of mss) { let ms = MembershipDTO.fromInline(inlineMS) @@ -462,14 +497,14 @@ export class DuniterBlockchain extends MiscIndexedBlockchain { } } - async computeToBeRevoked(mindex:MindexEntry[], dal:any) { - const revocations = _.filter(mindex, (entry:MindexEntry) => entry.revoked_on); + static async computeToBeRevoked(mindex:MindexEntry[], dal:FileDAL) { + const revocations = Underscore.filter(mindex, (entry:MindexEntry) => !!(entry.revoked_on)) for (const revoked of revocations) { - await dal.setRevoked(revoked.pub, true); + await dal.setRevoked(revoked.pub) } } - async deleteTransactions(block:BlockDTO, dal:any) { + static async deleteTransactions(block:BlockDTO, dal:FileDAL) { for (const obj of block.transactions) { obj.currency = block.currency; const tx = TransactionDTO.fromJSONObject(obj) @@ -478,7 +513,7 @@ export class DuniterBlockchain extends MiscIndexedBlockchain { } } - updateBlocksComputedVars( + static updateBlocksComputedVars( current:{ unitbase:number, monetaryMass:number }|null, block:{ number:number, unitbase:number, dividend:number|null, membersCount:number, monetaryMass:number }): void { // Unit Base @@ -499,7 +534,7 @@ export class DuniterBlockchain extends MiscIndexedBlockchain { } } - static pushStatsForBlocks(blocks:BlockDTO[], dal:any) { + static pushStatsForBlocks(blocks:BlockDTO[], dal:FileDAL) { const stats: { [k:string]: { blocks: number[], lastParsedBlock:number }} = {}; // Stats for (const block of blocks) { @@ -530,7 +565,7 @@ export class DuniterBlockchain extends MiscIndexedBlockchain { return dal.pushStats(stats); } - async pushSideBlock(obj:BlockDTO, dal:any, logger:any) { + static async pushSideBlock(obj:BlockDTO, dal:FileDAL, logger:any) { const start = Date.now(); const block = DBBlock.fromBlockDTO(BlockDTO.fromJSONObject(obj)) block.fork = true; @@ -544,11 +579,4 @@ export class DuniterBlockchain extends MiscIndexedBlockchain { throw err; } } - - async revertHead() { - const indexRevert = super.indexRevert - const headf = super.head - const head = await headf() - await indexRevert(head.number) - } } diff --git a/app/lib/blockchain/IndexedBlockchain.ts b/app/lib/blockchain/IndexedBlockchain.ts deleted file mode 100644 index dd81222d526e201922e47a47cdef90f666c212ba..0000000000000000000000000000000000000000 --- a/app/lib/blockchain/IndexedBlockchain.ts +++ /dev/null @@ -1,139 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict" -import {BasicBlockchain} from "./BasicBlockchain" -import {IndexOperator} from "./interfaces/IndexOperator" -import {BlockchainOperator} from "./interfaces/BlockchainOperator" - -const _ = require('underscore') - -export class IndexedBlockchain extends BasicBlockchain { - - private initIndexer: Promise<void> - - constructor(bcOperations: BlockchainOperator, private indexOperations: IndexOperator, private numberField: string, private pkFields: any) { - super(bcOperations) - this.initIndexer = indexOperations.initIndexer(pkFields) - } - - async recordIndex(index: { [index: string]: any }) { - // Wait indexer init - await this.initIndexer - - return this.indexOperations.recordIndex(index) - } - - async indexTrim(maxNumber:number) { - - // Wait indexer init - await this.initIndexer - - const subIndexes = await this.indexOperations.getSubIndexes() - // Trim the subIndexes - const records: { [index: string]: any } = {} - for (const subIndex of subIndexes) { - records[subIndex] = [] - const pks = typeof this.pkFields[subIndex].pk !== 'string' && this.pkFields[subIndex].pk.length ? Array.from(this.pkFields[subIndex].pk) : [this.pkFields[subIndex].pk] - const rm = this.pkFields[subIndex].remove - let potentialRecords = await this.indexOperations.findTrimable(subIndex, this.numberField, maxNumber) - potentialRecords = reduceBy(potentialRecords, pks) - for (const potential of potentialRecords) { - const subCriteriasRowsToDelete = criteriasFromPks(pks, potential) - subCriteriasRowsToDelete[this.numberField] = { $lt: maxNumber } - const rowsToReduce = await this.indexOperations.findWhere(subIndex, subCriteriasRowsToDelete) - // No reduction if 1 line to delete - if (rowsToReduce.length > 1) { - const reduced = reduce(rowsToReduce) - const subCriteriasRowsToKeep = criteriasFromPks(pks, potential) - subCriteriasRowsToKeep[this.numberField] = { $gte: maxNumber } - const toKeep = await this.indexOperations.findWhere(subIndex, subCriteriasRowsToKeep) - const subCriteriasAllRowsOfObject = criteriasFromPks(pks, potential) - await this.indexOperations.removeWhere(subIndex, subCriteriasAllRowsOfObject) - // Add the reduced row + rows to keep - if (!rm || !reduced[rm]) { - records[subIndex] = records[subIndex].concat([reduced]).concat(toKeep) - } - } - } - } - await this.recordIndex(records) - return Promise.resolve() - } - - async indexCount(indexName: string, criterias: { [index: string]: any }) { - - // Wait indexer init - await this.initIndexer - - const records = await this.indexOperations.findWhere(indexName, criterias) - return records.length - } - - async indexReduce(indexName: string, criterias: { [index: string]: any }) { - - // Wait indexer init - await this.initIndexer - - const records = await this.indexOperations.findWhere(indexName, criterias) - return reduce(records) - } - - async indexReduceGroupBy(indexName: string, criterias: { [index: string]: any }, properties: string[]) { - - // Wait indexer init - await this.initIndexer - - const records = await this.indexOperations.findWhere(indexName, criterias) - return reduceBy(records, properties) - } - - async indexRevert(blockNumber:number) { - const subIndexes = await this.indexOperations.getSubIndexes() - for (const subIndex of subIndexes) { - const removeCriterias: { [index: string]: any } = {} - removeCriterias[this.numberField] = blockNumber - await this.indexOperations.removeWhere(subIndex, removeCriterias) - } - } -} - -function reduce(records: any[]) { - return records.reduce((obj, record) => { - const keys = Object.keys(record); - for (const k of keys) { - if (record[k] !== undefined && record[k] !== null) { - obj[k] = record[k]; - } - } - return obj; - }, {}); -} - -function reduceBy(reducables: any[], properties: string[]) { - const reduced = reducables.reduce((map, entry) => { - const id = properties.map((prop) => entry[prop]).join('-'); - map[id] = map[id] || []; - map[id].push(entry); - return map; - }, {}); - return _.values(reduced).map((rows: any[]) => reduce(rows)) -} - -function criteriasFromPks(pks: string[], values: any): { [index: string]: any } { - const criterias: { [index: string]: any } = {} - for (const key of pks) { - criterias[key] = values[key] - } - return criterias -} diff --git a/app/lib/blockchain/MiscIndexedBlockchain.ts b/app/lib/blockchain/MiscIndexedBlockchain.ts deleted file mode 100644 index 5f881ba0d0dad2f6dea1b27a734c5c4949e02cc3..0000000000000000000000000000000000000000 --- a/app/lib/blockchain/MiscIndexedBlockchain.ts +++ /dev/null @@ -1,50 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict" -import {IndexedBlockchain} from "./IndexedBlockchain" -import {SQLIndex} from "./SqlIndex" -import {BlockchainOperator} from "./interfaces/BlockchainOperator" - -export class MiscIndexedBlockchain extends IndexedBlockchain { - - constructor(blockchainStorage: BlockchainOperator, mindexDAL:any, iindexDAL:any, sindexDAL:any, cindexDAL:any) { - super(blockchainStorage, new SQLIndex(null, { - m_index: { handler: mindexDAL }, - i_index: { handler: iindexDAL }, - s_index: { - handler: sindexDAL, - findTrimable: (maxNumber:number) => sindexDAL.query('SELECT * FROM s_index WHERE consumed AND writtenOn < ?', [maxNumber]) - }, - c_index: { - handler: cindexDAL, - findTrimable: (maxNumber:number) => cindexDAL.query('SELECT * FROM c_index WHERE expired_on > 0 AND writtenOn < ?', [maxNumber]) - } - }), 'writtenOn', { - m_index: { - pk: ['pub'] - }, - i_index: { - pk: ['pub'] - }, - s_index: { - pk: ['identifier', 'pos'], - remove: 'consumed' - }, - c_index: { - pk: ['issuer', 'receiver', 'created_on'], - remove: 'expired_on' - } - }) - } -} diff --git a/app/lib/blockchain/SqlBlockchain.ts b/app/lib/blockchain/SqlBlockchain.ts deleted file mode 100644 index 84d2f5b97fac12abe67e335d692e1a8845ff58c0..0000000000000000000000000000000000000000 --- a/app/lib/blockchain/SqlBlockchain.ts +++ /dev/null @@ -1,76 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict" -import {BlockchainOperator} from "./interfaces/BlockchainOperator" - -const indexer = require('../../lib/indexer').Indexer - -export class SQLBlockchain implements BlockchainOperator { - - constructor(private dal: { bindexDAL:any }) { - } - - store(b: any): Promise<any> { - return this.dal.bindexDAL.saveEntity(b) - } - - read(i: number): Promise<any> { - return this.dal.bindexDAL.sqlFindOne({ number: i }) - } - - async head(n = 0): Promise<any> { - /** - * IMPORTANT NOTICE - * ---------------- - * - * There is a difference between the protocol's HEAD (P_HEAD) and the database's HEAD (DB_HEAD). The relation is: - * - * DB_HEAD~<i> = P_HEAD~<i+1> - * - * In this class methods, we expose DB_HEAD logic. But the data is stored/retrieved by DAL objects using P_HEAD logic. - * - * So if we want DB_HEAD~0 for example, we must ask P_HEAD~(0+1). The DAL object will then retrieve P_HEAD~1, which - * is the latest stored block in the blockchain. - * - * Note: the DAL object cannot retrieve P_HEAD~0, this value does not exist and refers to the potential incoming block. - * - * Why this behavior? - * ------------------ - * - * Because the DAL was wrongly coded. It will be easy to fix this problem once indexer.js will only use **this class' - * methods**. Then, we will be able to replace (n + 1) by just (n), and replace also the complementary behavior in - * the DAL (BIndexDAL). - */ - return this.dal.bindexDAL.head(n + 1) - } - - async height(): Promise<number> { - const head = await this.dal.bindexDAL.head(1) // We do not use head(0). See the above notice. - if (head) { - return head.number + 1 - } else { - return 0 - } - } - - headRange(m: number): Promise<any[]> { - return this.dal.bindexDAL.range(1, m) // We do not use range(0, m). See the above notice. - } - - async revertHead(): Promise<any> { - const head = await this.dal.bindexDAL.head(1) // We do not use head(0). See the above notice. - await this.dal.bindexDAL.removeBlock(head.number) - return head - } -} diff --git a/app/lib/blockchain/SqlIndex.ts b/app/lib/blockchain/SqlIndex.ts deleted file mode 100644 index 53cb379c94934e700abc4bab8097691533085db4..0000000000000000000000000000000000000000 --- a/app/lib/blockchain/SqlIndex.ts +++ /dev/null @@ -1,86 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict" -import {IndexOperator} from "./interfaces/IndexOperator" -import {AbstractIndex} from "../dal/sqliteDAL/AbstractIndex"; - -const _ = require('underscore') - -export class SQLIndex implements IndexOperator { - - private indexes: { [k:string]: any } = {} - - constructor(private db:any, private definitions: any) { - } - - async initIndexer(pkFields: any): Promise<void> { - const keys = _.keys(pkFields) - for (const k of keys) { - if (this.definitions[k].handler) { - // External table: managed by another object - this.indexes[k] = this.definitions[k].handler - } else { - // Internal table: managed here - const indexTable = new AbstractIndex<any>(this.db, k, [], this.definitions[k].fields, [], this.definitions[k].booleans) - this.indexes[k] = indexTable - indexTable.init = () => { - return indexTable.exec('BEGIN;' + - 'CREATE TABLE IF NOT EXISTS ' + indexTable.table + ' (' + - this.definitions[k].sqlFields.join(',') + - ');' + - 'COMMIT;') - } - await indexTable.init() - } - } - } - - getSubIndexes(): Promise<string[]> { - return Promise.resolve(_.keys(this.indexes)) - } - - findTrimable(subIndex: string, numberField: string, maxNumber: number): Promise<any[]> { - if (this.definitions[subIndex].findTrimable) { - return this.definitions[subIndex].findTrimable(maxNumber) - } else { - const criterias:any = {} - criterias[numberField] = { $lt: maxNumber } - return this.indexes[subIndex].sqlFind(criterias) - } - } - - removeWhere(subIndex: string, criterias: {}): Promise<void> { - if (!this.indexes[subIndex]) { - return Promise.resolve() - } - return this.indexes[subIndex].sqlRemoveWhere(criterias) - } - - async recordIndex(index: any): Promise<void> { - const subIndexes = _.keys(index) - // Feed the this.indexes - for (const subIndex of subIndexes) { - await this.indexes[subIndex].insertBatch(index[subIndex]) - } - return Promise.resolve() - } - - - findWhere(subIndex: string, criterias: {}): Promise<any[]> { - if (!this.indexes[subIndex]) { - return Promise.resolve([]) - } - return this.indexes[subIndex].sqlFind(criterias) - } -} diff --git a/app/lib/blockchain/Switcher.ts b/app/lib/blockchain/Switcher.ts index 4346fd1af0c8a1e5ef95f19f9b0994e35de0692f..083aae273a887e55cd40b75e9a8928b250b97048 100644 --- a/app/lib/blockchain/Switcher.ts +++ b/app/lib/blockchain/Switcher.ts @@ -12,6 +12,7 @@ // GNU Affero General Public License for more details. import {BlockDTO} from "../dto/BlockDTO" + export interface SwitchBlock { number:number @@ -22,10 +23,10 @@ export interface SwitchBlock { export interface SwitcherDao<T extends SwitchBlock> { - getCurrent(): Promise<T> + getCurrent(): Promise<T|null> getPotentials(numberStart:number, timeStart:number, maxNumber:number): Promise<T[]> getBlockchainBlock(number:number, hash:string): Promise<T|null> - getSandboxBlock(number:number, hash:string): Promise<T|null> + getAbsoluteBlockInForkWindow(number:number, hash:string): Promise<T|null> revertTo(number:number): Promise<T[]> addBlock(block:T): Promise<T> } @@ -73,7 +74,7 @@ export class Switcher<T extends SwitchBlock> { * Find all the suites' HEAD that we could potentially fork on, in the current fork window. * @param current */ - async findPotentialSuitesHeads(current:T) { + async findPotentialSuitesHeads(current:{ number:number, medianTime:number }) { const numberStart = current.number - this.forkWindowSize const timeStart = current.medianTime - this.forkWindowSize * this.avgGenTime const suites = await this.findPotentialSuites(numberStart, timeStart) @@ -125,7 +126,7 @@ export class Switcher<T extends SwitchBlock> { commonRootFound = true } else { // Have a look in sandboxes - previous = await this.dao.getSandboxBlock(previousNumber, previousHash) + previous = await this.dao.getAbsoluteBlockInForkWindow(previousNumber, previousHash) if (previous) { knownForkBlocks[BlockDTO.fromJSONObject(previous).blockstamp] = true const alreadyKnownInvalidBlock = this.invalidForks.indexOf([previous.number, previous.hash].join('-')) !== -1 diff --git a/app/lib/blockchain/interfaces/BlockchainOperator.ts b/app/lib/blockchain/interfaces/BlockchainOperator.ts deleted file mode 100644 index f3f50177b91ea18f8598742f516bc76f601c4b55..0000000000000000000000000000000000000000 --- a/app/lib/blockchain/interfaces/BlockchainOperator.ts +++ /dev/null @@ -1,51 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict" - -export interface BlockchainOperator { - - /** - * Pushes a new block at the top of the blockchain. - * @param b Block. - */ - store(b:any):Promise<any> - - /** - * Reads the block at index `i`. - * @param i Block number. - */ - read(i:number):Promise<any> - - /** - * Reads the block at index `n` from the top of the blockchain. - * @param n Reverse index. - */ - head(n:number):Promise<any> - - /** - * Gives the number of blocks in the blockchain. - */ - height():Promise<number> - - /** - * Reads the blocks from head(0) to head(m) - * @param m Quantity. - */ - headRange(m:number):Promise<any[]> - - /** - * Pops the top block. - */ - revertHead():Promise<any> -} diff --git a/app/lib/common-libs/constants.ts b/app/lib/common-libs/constants.ts old mode 100644 new mode 100755 index 54d3d879b74569eb1e04ee0c51ac8e8e0bc8abd2..3b16ff3e87429ced051393a61703a58f250746cb --- a/app/lib/common-libs/constants.ts +++ b/app/lib/common-libs/constants.ts @@ -299,7 +299,16 @@ export const CommonConstants = { SPECIAL_BLOCK }, - BLOCK_MAX_TX_CHAINING_DEPTH: 5 + BLOCK_MAX_TX_CHAINING_DEPTH: 5, + + CONST_BLOCKS_CHUNK: 250, + CHUNK_PREFIX: 'chunk_', + BLOCKS_IN_MEMORY_MAX: 288 * 60, // 60 days of blocks + + MAX_AGE_OF_PEER_IN_BLOCKS: 200, // blocks + INITIAL_DOWNLOAD_SLOTS: 1, // 1 peer + + BLOCKS_COLLECT_THRESHOLD: 30, // Number of blocks to wait before trimming the loki data } function exact (regexpContent:string) { diff --git a/app/lib/common-libs/crypto/map.ts b/app/lib/common-libs/crypto/map.ts new file mode 100644 index 0000000000000000000000000000000000000000..a178fde884f70191fa00e56a5e4d2e91ba2f9eaa --- /dev/null +++ b/app/lib/common-libs/crypto/map.ts @@ -0,0 +1,4 @@ + +export interface Map<T> { + [k:string]: T +} diff --git a/app/lib/common-libs/errors.ts b/app/lib/common-libs/errors.ts new file mode 100755 index 0000000000000000000000000000000000000000..ffaa416c8ce74aeed8426d044402354fdc43f33f --- /dev/null +++ b/app/lib/common-libs/errors.ts @@ -0,0 +1,19 @@ + +export enum DataErrors { + TOO_OLD_PEER, + LOKI_DIVIDEND_GET_WRITTEN_ON_SHOULD_NOT_BE_USED, + LOKI_DIVIDEND_REMOVE_BLOCK_SHOULD_NOT_BE_USED, + NEGATIVE_BALANCE, + BLOCK_WASNT_COMMITTED, + CANNOT_ARCHIVE_CHUNK_WRONG_SIZE, + CORRUPTED_DATABASE, + BLOCKCHAIN_NOT_INITIALIZED_YET, + CANNOT_DETERMINATE_MEMBERSHIP_AGE, + CANNOT_DETERMINATE_IDENTITY_AGE, + CERT_BASED_ON_UNKNOWN_BLOCK, + NO_TRANSACTION_POSSIBLE_IF_NOT_CURRENT_BLOCK, + CANNOT_REAPPLY_NO_CURRENT_BLOCK, + CANNOT_REVERT_NO_CURRENT_BLOCK, + BLOCK_TO_REVERT_NOT_FOUND, + MEMBER_NOT_FOUND +} diff --git a/app/lib/common-libs/manual-promise.ts b/app/lib/common-libs/manual-promise.ts new file mode 100644 index 0000000000000000000000000000000000000000..ea7f4c436cd80bdc43c48da2aa3ec16f451f572e --- /dev/null +++ b/app/lib/common-libs/manual-promise.ts @@ -0,0 +1,25 @@ +import {Querable} from "./querable" + +const querablePromise = require('querablep'); + +export interface ManualPromise<T> extends Querable<T> { + resolve: (data: T) => void + reject: (error: Error) => void +} + +/** + * Creates a new querable promise that can is manually triggered. + * @returns {ManualPromise<T>} + */ +export function newManualPromise<T>() { + let resolveCb: (data: T) => void = () => {} + let rejectCb: (error: Error) => void = () => {} + const p = new Promise((res, rej) => { + resolveCb = res + rejectCb = rej + }) + const q: ManualPromise<T> = querablePromise(p) + q.resolve = resolveCb + q.reject = rejectCb + return q +} diff --git a/test/integration/tools/shutDownEngine.js b/app/lib/common-libs/moment.ts similarity index 77% rename from test/integration/tools/shutDownEngine.js rename to app/lib/common-libs/moment.ts index ade3f2350e7ec857dce49a590b49be61f8a0810f..ca05144233a3364fc44985f8cee8947870af7521 100644 --- a/test/integration/tools/shutDownEngine.js +++ b/app/lib/common-libs/moment.ts @@ -11,11 +11,6 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -const co = require('co') +const _moment_ = require("moment") -module.exports = (server) => co(function*() { - if (server._utProver) { - const farm = yield server._utProver.getWorker(); - return farm.shutDownEngine(); - } -}) +export const moment = _moment_ \ No newline at end of file diff --git a/app/lib/common-libs/programOptions.ts b/app/lib/common-libs/programOptions.ts new file mode 100644 index 0000000000000000000000000000000000000000..d26f63ea0958443113d37a40f89aebbd513b4e58 --- /dev/null +++ b/app/lib/common-libs/programOptions.ts @@ -0,0 +1,38 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +const opts = require('optimist').argv + +export interface ProgramOptions { + mdb?: string + home?: string + notrim?: boolean + nosbx?: boolean + nopeers?: boolean + syncTrace?: string + isSync: boolean + noSources: boolean + slow?: boolean +} + +export const cliprogram: ProgramOptions = { + mdb: opts.mdb, + home: opts.home, + notrim: opts.notrim, + nosbx: opts.nosbx, + nopeers: opts.nopeers, + noSources: !!opts.nosources, + syncTrace: opts['sync-trace'], + isSync: opts._[0] === 'sync', + slow: opts.slow, +} diff --git a/app/lib/common-libs/querable.ts b/app/lib/common-libs/querable.ts new file mode 100644 index 0000000000000000000000000000000000000000..b5cba5da23170c599d46d7dadd138376b0d9f20f --- /dev/null +++ b/app/lib/common-libs/querable.ts @@ -0,0 +1,14 @@ +const querablePromise = require('querablep'); + +export interface Querable<T> extends Promise<T> { + isFulfilled(): boolean + isResolved(): boolean + isRejected(): boolean + startedOn: number +} + +export function querablep<T>(p: Promise<T>): Querable<T> { + const qp = querablePromise(p) + qp.startedOn = Date.now() + return qp +} diff --git a/app/lib/common-libs/rawer.ts b/app/lib/common-libs/rawer.ts index 568199c92a3a68b1db6c3666b8d0f2dd7ea2a576..9ed172071cad9d086bc01db5d13e3df19440d02b 100644 --- a/app/lib/common-libs/rawer.ts +++ b/app/lib/common-libs/rawer.ts @@ -31,7 +31,17 @@ export const getOfficialIdentity = (json:any, withSig = true) => { } } -export const getOfficialCertification = (json:any) => { +export const getOfficialCertification = (json:{ + version?:number + currency:string + issuer:string + idty_issuer:string + idty_uid:string + idty_buid:string + idty_sig:string + buid:string + sig?:string +}) => { let raw = getNormalHeader('Certification', json); raw += "IdtyIssuer: " + json.idty_issuer + '\n'; raw += "IdtyUniqueID: " + json.idty_uid + '\n'; @@ -91,7 +101,11 @@ export const getTransaction = (json:any) => { return TransactionDTO.toRAW(json) } -function getNormalHeader(doctype:string, json:any) { +function getNormalHeader(doctype:string, json:{ + version?:number + currency:string + issuer:string +}) { let raw = ""; raw += "Version: " + (json.version || DOCUMENTS_VERSION) + "\n"; raw += "Type: " + doctype + "\n"; diff --git a/app/lib/common-libs/underscore.ts b/app/lib/common-libs/underscore.ts new file mode 100644 index 0000000000000000000000000000000000000000..2592220474e242b23f4e9af5b59609f132c6e8eb --- /dev/null +++ b/app/lib/common-libs/underscore.ts @@ -0,0 +1,87 @@ +import {Map} from "./crypto/map" + +const _underscore_ = require("underscore") + +export interface UnderscoreClass<T> { + filter(filterFunc: (t: T) => boolean): UnderscoreClass<T> + where(props: { [k in keyof T]?: T[k] }): UnderscoreClass<T> + sortBy(sortFunc:(element:T) => number): UnderscoreClass<T> + pluck<K extends keyof T>(k:K): UnderscoreClass<T> + uniq<K extends keyof T>(isSorted?:boolean, iteratee?:(t:T) => K): UnderscoreClass<T> + value(): T[] +} + +export const Underscore = { + + filter: <T>(elements:T[], filterFunc: (t:T) => boolean): T[] => { + return _underscore_.filter(elements, filterFunc) + }, + + where: <T>(elements:T[], props: { [k in keyof T]?: T[k] }): T[] => { + return _underscore_.where(elements, props) + }, + + findWhere: <T>(elements:T[], props: { [k in keyof T]?: T[k] }): T|null => { + return _underscore_.findWhere(elements, props) + }, + + keys: <T>(map:T): (keyof T)[] => { + return _underscore_.keys(map) + }, + + values: <T>(map:{ [k:string]: T }): T[] => { + return _underscore_.values(map) + }, + + pluck: <T, K extends keyof T>(elements:T[], k:K): T[K][] => { + return _underscore_.pluck(elements, k) + }, + + pick: <T, K extends keyof T>(elements:T, ...k:K[]): T[K][] => { + return _underscore_.pick(elements, ...k) + }, + + omit: <T, K extends keyof T>(element:T, ...k:K[]): T[K][] => { + return _underscore_.omit(element, ...k) + }, + + uniq: <T, K>(elements:T[], isSorted:boolean = false, iteratee?:(t:T) => K): T[] => { + return _underscore_.uniq(elements, isSorted, iteratee) + }, + + clone: <T>(t:T): T => { + return _underscore_.clone(t) + }, + + mapObject: <T, K extends keyof T, L extends keyof (T[K])>(t:T, cb:(k:K) => (T[K])[L]): Map<T[K][L]> => { + return _underscore_.mapObject(t, cb) + }, + + mapObjectByProp: <T, K extends keyof T, L extends keyof (T[K])>(t:T, prop:L): Map<T[K][L]> => { + return _underscore_.mapObject(t, (o:T[K]) => o[prop]) + }, + + sortBy: <T, K extends keyof T>(elements:T[], sortFunc:((element:T) => number|string)|K): T[] => { + return _underscore_.sortBy(elements, sortFunc) + }, + + difference: <T>(array1:T[], array2:T[]): T[] => { + return _underscore_.difference(array1, array2) + }, + + shuffle: <T>(elements:T[]): T[] => { + return _underscore_.shuffle(elements) + }, + + extend: <T, U>(t1:T, t2:U): T|U => { + return _underscore_.extend(t1, t2) + }, + + range: (count:number, end?:number): number[] => { + return _underscore_.range(count, end) + }, + + chain: <T>(element:T[]): UnderscoreClass<T> => { + return _underscore_.chain(element) + }, +} diff --git a/app/lib/common-libs/websocket.ts b/app/lib/common-libs/websocket.ts new file mode 100644 index 0000000000000000000000000000000000000000..a9589d2aba1efa73ac575be99124f706f49f17f3 --- /dev/null +++ b/app/lib/common-libs/websocket.ts @@ -0,0 +1,38 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import * as WS from 'ws' + +export class WebSocket extends WS { + constructor(url: string, options?: { agent: any }) { + super(url, { + agent: options && options.agent, + }) + } +} + +export class WebSocketServer extends WS.Server { + constructor(options: { + server?: any, + host?: string, + port?: number, + path?: string + }) { + super({ + server: options.server, + path: options.path, + host: options.host, + port: options.port, + }) + } +} diff --git a/app/lib/common/Tristamp.ts b/app/lib/common/Tristamp.ts new file mode 100644 index 0000000000000000000000000000000000000000..6c3232a66b55197c7c03ce3f49b11a17bbd92015 --- /dev/null +++ b/app/lib/common/Tristamp.ts @@ -0,0 +1,18 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +export interface Tristamp { + number: number + hash: string + medianTime: number +} diff --git a/app/lib/computation/BlockchainContext.ts b/app/lib/computation/BlockchainContext.ts index 5a511b3e2198c061fad7e26aad53e4961eece04d..daf505a3a8c9660106ddd0e3f4e01325fb3f112e 100644 --- a/app/lib/computation/BlockchainContext.ts +++ b/app/lib/computation/BlockchainContext.ts @@ -11,22 +11,23 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; import {BlockDTO} from "../dto/BlockDTO" import {DuniterBlockchain} from "../blockchain/DuniterBlockchain" import {QuickSynchronizer} from "./QuickSync" import {DBHead} from "../db/DBHead" +import {FileDAL} from "../dal/fileDAL" +import {DBBlock} from "../db/DBBlock" +import {Underscore} from "../common-libs/underscore" +import {DataErrors} from "../common-libs/errors" -const _ = require('underscore'); const indexer = require('../indexer').Indexer const constants = require('../constants'); export class BlockchainContext { private conf:any - private dal:any + private dal:FileDAL private logger:any - private blockchain:DuniterBlockchain private quickSynchronizer:QuickSynchronizer /** @@ -40,14 +41,14 @@ export class BlockchainContext { */ private vHEAD_1:any - private HEADrefreshed: Promise<any> | null = Promise.resolve(); + private HEADrefreshed: Promise<void> = Promise.resolve(); /** * Refresh the virtual HEAD value for determined variables of the next coming block, avoiding to recompute them * each time a new block arrives to check if the values are correct. We can know and store them early on, in vHEAD. */ private refreshHead(): Promise<void> { - this.HEADrefreshed = (async (): Promise<void> => { + this.HEADrefreshed = (async () => { this.vHEAD_1 = await this.dal.head(1); // We suppose next block will have same version #, and no particular data in the block (empty index) let block; @@ -82,7 +83,7 @@ export class BlockchainContext { for (const k of keys) { copy[k] = this.vHEAD[k]; } - _.extend(copy, props); + Underscore.extend(copy, props); return copy; } @@ -106,10 +107,9 @@ export class BlockchainContext { return local_vHEAD.issuerDiff; } - setConfDAL(newConf: any, newDAL: any, theBlockchain: DuniterBlockchain, theQuickSynchronizer: QuickSynchronizer): void { + setConfDAL(newConf: any, newDAL: any, theQuickSynchronizer: QuickSynchronizer): void { this.dal = newDAL; this.conf = newConf; - this.blockchain = theBlockchain this.quickSynchronizer = theQuickSynchronizer this.logger = require('../logger').NewLogger(this.dal.profile); } @@ -118,24 +118,36 @@ export class BlockchainContext { return DuniterBlockchain.checkBlock(block, withPoWAndSignature, this.conf, this.dal) } - private async addBlock(obj: BlockDTO, index: any = null, HEAD: DBHead | null = null, trim: boolean): Promise<any> { - const block = await this.blockchain.pushTheBlock(obj, index, HEAD, this.conf, this.dal, this.logger, trim) - this.vHEAD_1 = this.vHEAD = this.HEADrefreshed = null + private async addBlock(obj: BlockDTO, index: any = null, HEAD: DBHead | null = null, trim: boolean): Promise<BlockDTO> { + const block = await DuniterBlockchain.pushTheBlock(obj, index, HEAD, this.conf, this.dal, this.logger, trim) + this.vHEAD_1 = this.vHEAD = null return block } async addSideBlock(obj:BlockDTO): Promise<BlockDTO> { - const dbb = await this.blockchain.pushSideBlock(obj, this.dal, this.logger) + const dbb = await DuniterBlockchain.pushSideBlock(obj, this.dal, this.logger) return dbb.toBlockDTO() } - async revertCurrentBlock(): Promise<BlockDTO> { + async revertCurrentBlock(): Promise<DBBlock> { const head_1 = await this.dal.bindexDAL.head(1); this.logger.debug('Reverting block #%s...', head_1.number); - const res = await this.blockchain.revertBlock(head_1.number, head_1.hash, this.dal) + const block = await this.dal.getAbsoluteValidBlockInForkWindow(head_1.number, head_1.hash) + if (!block) { + throw DataErrors[DataErrors.BLOCK_TO_REVERT_NOT_FOUND] + } + await DuniterBlockchain.revertBlock(head_1.number, head_1.hash, this.dal, block) + // Invalidates the head, since it has changed. + await this.refreshHead(); + return block + } + + async revertCurrentHead() { + const head_1 = await this.dal.bindexDAL.head(1); + this.logger.debug('Reverting HEAD~1... (b#%s)', head_1.number); + await DuniterBlockchain.revertBlock(head_1.number, head_1.hash, this.dal) // Invalidates the head, since it has changed. await this.refreshHead(); - return res; } async applyNextAvailableFork(): Promise<any> { @@ -146,7 +158,7 @@ export class BlockchainContext { throw constants.ERRORS.NO_POTENTIAL_FORK_AS_NEXT; } const block = forks[0]; - await this.checkAndAddBlock(block) + await this.checkAndAddBlock(BlockDTO.fromJSONObject(block)) this.logger.debug('Applied block #%s', block.number); } diff --git a/app/lib/computation/QuickSync.ts b/app/lib/computation/QuickSync.ts index ca6c897cdb7f8c4f8c9b847dd2ac960fc98c5313..0e9c79ff3b8a1367feb9c3403badd2639896be57 100644 --- a/app/lib/computation/QuickSync.ts +++ b/app/lib/computation/QuickSync.ts @@ -11,14 +11,16 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict" import {DuniterBlockchain} from "../blockchain/DuniterBlockchain"; import {BlockDTO} from "../dto/BlockDTO"; -import {DBTransaction} from "../db/DBTransaction"; -import {Indexer} from "../indexer"; +import {AccountsGarbagingDAL, FullSindexEntry, Indexer} from "../indexer"; import {CurrencyConfDTO} from "../dto/ConfDTO"; +import {FileDAL} from "../dal/fileDAL" +import {DBBlock} from "../db/DBBlock" +import {Underscore} from "../common-libs/underscore" +import {CommonConstants} from "../common-libs/constants" +import {cliprogram} from "../common-libs/programOptions" -const _ = require('underscore') const constants = require('../constants') let sync_bindex: any [] = []; @@ -27,12 +29,11 @@ let sync_mindex: any[] = []; let sync_cindex: any[] = []; let sync_sindex: any[] = []; let sync_bindexSize = 0; -let sync_allBlocks: BlockDTO[] = []; let sync_expires: number[] = []; let sync_nextExpiring = 0; let sync_currConf: CurrencyConfDTO; const sync_memoryWallets: any = {} -const sync_memoryDAL = { +const sync_memoryDAL:AccountsGarbagingDAL = { getWallet: (conditions: string) => Promise.resolve(sync_memoryWallets[conditions] || { conditions, balance: 0 }), saveWallet: async (wallet: any) => { // Make a copy @@ -42,72 +43,36 @@ const sync_memoryDAL = { } }, sindexDAL: { - getAvailableForConditions: (conditions:string) => null + getAvailableForConditions: (conditions:string) => Promise.resolve([]) } } export class QuickSynchronizer { - constructor(private blockchain:DuniterBlockchain, private conf: any, private dal: any, private logger: any) { + constructor(private conf: any, private dal:FileDAL, private logger: any) { } - async saveBlocksInMainBranch(blocks: BlockDTO[]): Promise<void> { - // VERY FIRST: parameters, otherwise we compute wrong variables such as UDTime - if (blocks[0].number == 0) { - await this.blockchain.saveParametersForRoot(blocks[0], this.conf, this.dal) - } - // Helper to retrieve a block with local cache - const getBlock = (number: number): Promise<BlockDTO> => { - const firstLocalNumber = blocks[0].number; - if (number >= firstLocalNumber) { - let offset = number - firstLocalNumber; - return Promise.resolve(blocks[offset]) - } - return this.dal.getBlock(number); - }; - const getBlockByNumberAndHash = async (number: number, hash: string): Promise<BlockDTO> => { - const block = await getBlock(number); - if (!block || block.hash != hash) { - throw 'Block #' + [number, hash].join('-') + ' not found neither in DB nor in applying blocks'; - } - return block; - } - for (const block of blocks) { - block.fork = false; - const current:BlockDTO|null = await getBlock(block.number - 1) - this.blockchain.updateBlocksComputedVars(current, block) - } - // Transactions recording - await this.updateTransactionsForBlocks(blocks, getBlockByNumberAndHash); - await this.dal.blockDAL.saveBunch(blocks); - await DuniterBlockchain.pushStatsForBlocks(blocks, this.dal); - } + async quickApplyBlocks(blocks:BlockDTO[], to: number): Promise<void> { - private async updateTransactionsForBlocks(blocks: BlockDTO[], getBlockByNumberAndHash: (number: number, hash: string) => Promise<BlockDTO>): Promise<any> { - let txs: DBTransaction[] = []; - for (const block of blocks) { - const newOnes: DBTransaction[] = []; - for (const tx of block.transactions) { - const [number, hash] = tx.blockstamp.split('-') - const refBlock: BlockDTO = (await getBlockByNumberAndHash(parseInt(number), hash)) - // We force the usage of the reference block's currency - tx.currency = refBlock.currency - tx.hash = tx.getHash() - const dbTx: DBTransaction = DBTransaction.fromTransactionDTO(tx, refBlock.medianTime, true, false, refBlock.number, refBlock.medianTime) - newOnes.push(dbTx) - } - txs = txs.concat(newOnes); + sync_memoryDAL.sindexDAL = { + getAvailableForConditions: (conditions:string) => this.dal.sindexDAL.getAvailableForConditions(conditions) } - return this.dal.updateTransactions(txs); - } - async quickApplyBlocks(blocks:BlockDTO[], to: number): Promise<void> { + await this.dal.blockDAL.insertBatch(blocks.map((b:any) => { + const block = DBBlock.fromBlockDTO(b) + block.fork = false + return block + })) - sync_memoryDAL.sindexDAL = { getAvailableForConditions: (conditions:string) => this.dal.sindexDAL.getAvailableForConditions(conditions) } - let blocksToSave: BlockDTO[] = []; + // We only keep approx 2 months of blocks in memory, so memory consumption keeps approximately constant during the sync + await this.dal.blockDAL.trimBlocks(blocks[blocks.length - 1].number - CommonConstants.BLOCKS_IN_MEMORY_MAX) for (const block of blocks) { - sync_allBlocks.push(block); + + // VERY FIRST: parameters, otherwise we compute wrong variables such as UDTime + if (block.number == 0) { + await DuniterBlockchain.saveParametersForRoot(block, this.conf, this.dal) + } // The new kind of object stored const dto = BlockDTO.fromJSONObject(block) @@ -116,22 +81,14 @@ export class QuickSynchronizer { sync_currConf = BlockDTO.getConf(block); } - if (block.number <= to - this.conf.forksize) { - blocksToSave.push(dto); + if (block.number <= to - this.conf.forksize || cliprogram.noSources) { // If we require nosources option, this blockchain can't be valid so we don't make checks const index:any = Indexer.localIndex(dto, sync_currConf); const local_iindex = Indexer.iindex(index); const local_cindex = Indexer.cindex(index); - const local_sindex = Indexer.sindex(index); + const local_sindex = cliprogram.noSources ? [] : Indexer.sindex(index); const local_mindex = Indexer.mindex(index); - const HEAD = await Indexer.quickCompleteGlobalScope(block, sync_currConf, sync_bindex, local_iindex, local_mindex, local_cindex, { - getBlock: (number: number) => { - return Promise.resolve(sync_allBlocks[number]); - }, - getBlockByBlockstamp: (blockstamp: string) => { - return Promise.resolve(sync_allBlocks[parseInt(blockstamp)]); - } - }); + const HEAD = await Indexer.quickCompleteGlobalScope(block, sync_currConf, sync_bindex, local_iindex, local_mindex, local_cindex, this.dal) sync_bindex.push(HEAD); // Remember expiration dates @@ -143,17 +100,16 @@ export class QuickSynchronizer { sync_expires.push(entry.revokes_on) } } - sync_expires = _.uniq(sync_expires); - await this.blockchain.createNewcomers(local_iindex, this.dal, this.logger) + await DuniterBlockchain.createNewcomers(local_iindex, this.dal, this.logger) - if (block.dividend + if ((block.dividend && !cliprogram.noSources) || block.joiners.length || block.actives.length || block.revoked.length || block.excluded.length || block.certifications.length - || block.transactions.length + || (block.transactions.length && !cliprogram.noSources) || block.medianTime >= sync_nextExpiring) { const nextExpiringChanged = block.medianTime >= sync_nextExpiring @@ -167,25 +123,33 @@ export class QuickSynchronizer { sync_nextExpiring = sync_expires.reduce((max, value) => max ? Math.min(max, value) : value, 9007199254740991); // Far far away date // Fills in correctly the SINDEX - await Promise.all(_.where(sync_sindex.concat(local_sindex), { op: 'UPDATE' }).map(async (entry: any) => { - if (!entry.conditions) { - const src = await this.dal.sindexDAL.getSource(entry.identifier, entry.pos); - entry.conditions = src.conditions; - } - })) + if (!cliprogram.noSources) { + await Promise.all(Underscore.where(sync_sindex.concat(local_sindex), {op: 'UPDATE'}).map(async entry => { + if (!entry.conditions) { + const src = (await this.dal.getSource(entry.identifier, entry.pos, entry.srcType === 'D')) as FullSindexEntry + entry.conditions = src.conditions; + } + })) + } // Flush the INDEX (not bindex, which is particular) - await this.dal.mindexDAL.insertBatch(sync_mindex); - await this.dal.iindexDAL.insertBatch(sync_iindex); - await this.dal.sindexDAL.insertBatch(sync_sindex); - await this.dal.cindexDAL.insertBatch(sync_cindex); + await this.dal.flushIndexes({ + mindex: sync_mindex, + iindex: sync_iindex, + sindex: sync_sindex, + cindex: sync_cindex, + }) sync_iindex = local_iindex sync_cindex = local_cindex sync_mindex = local_mindex sync_sindex = local_sindex - sync_sindex = sync_sindex.concat(await Indexer.ruleIndexGenDividend(HEAD, local_iindex, this.dal)); - sync_sindex = sync_sindex.concat(await Indexer.ruleIndexGarbageSmallAccounts(HEAD, sync_sindex, sync_memoryDAL)); + // Dividends and account garbaging + const dividends = cliprogram.noSources ? [] : await Indexer.ruleIndexGenDividend(HEAD, local_iindex, this.dal) + if (!cliprogram.noSources) { + sync_sindex = sync_sindex.concat(await Indexer.ruleIndexGarbageSmallAccounts(HEAD, sync_sindex, dividends, sync_memoryDAL)); + } + if (nextExpiringChanged) { sync_cindex = sync_cindex.concat(await Indexer.ruleIndexGenCertificationExpiry(HEAD, this.dal)); sync_mindex = sync_mindex.concat(await Indexer.ruleIndexGenMembershipExpiry(HEAD, this.dal)); @@ -193,14 +157,19 @@ export class QuickSynchronizer { sync_iindex = sync_iindex.concat(await Indexer.ruleIndexGenExclusionByCertificatons(HEAD, sync_cindex, local_iindex, this.conf, this.dal)); sync_mindex = sync_mindex.concat(await Indexer.ruleIndexGenImplicitRevocation(HEAD, this.dal)); } - // Update balances with UD + local garbagings - await this.blockchain.updateWallets(sync_sindex, sync_memoryDAL) + + if (!cliprogram.noSources) { + // Update balances with UD + local garbagings + await DuniterBlockchain.updateWallets(sync_sindex, dividends, sync_memoryDAL) + } // Flush the INDEX again (needs to be done *before* the update of wotb links because of block#0) - await this.dal.iindexDAL.insertBatch(sync_iindex); - await this.dal.mindexDAL.insertBatch(sync_mindex); - await this.dal.sindexDAL.insertBatch(sync_sindex); - await this.dal.cindexDAL.insertBatch(sync_cindex); + await this.dal.flushIndexes({ + mindex: sync_mindex, + iindex: sync_iindex, + sindex: sync_sindex, + cindex: sync_cindex, + }) // --> Update links await this.dal.updateWotbLinks(local_cindex.concat(sync_cindex)); @@ -210,7 +179,7 @@ export class QuickSynchronizer { sync_sindex = []; // Create/Update nodes in wotb - await this.blockchain.updateMembers(block, this.dal) + await DuniterBlockchain.updateMembers(block, this.dal) } else { // Concat the results to the pending data sync_iindex = sync_iindex.concat(local_iindex); @@ -233,26 +202,23 @@ export class QuickSynchronizer { // We trim it, not necessary to store it all (we already store the full blocks) sync_bindex.splice(0, sync_bindexSize); - // Process triming continuously to avoid super long ending of sync + // Process triming & archiving continuously to avoid super long ending of sync await this.dal.trimIndexes(sync_bindex[0].number); } } else { - if (blocksToSave.length) { - await this.saveBlocksInMainBranch(blocksToSave); - } - blocksToSave = []; - // Save the INDEX await this.dal.bindexDAL.insertBatch(sync_bindex); - await this.dal.mindexDAL.insertBatch(sync_mindex); - await this.dal.iindexDAL.insertBatch(sync_iindex); - await this.dal.sindexDAL.insertBatch(sync_sindex); - await this.dal.cindexDAL.insertBatch(sync_cindex); + await this.dal.flushIndexes({ + mindex: sync_mindex, + iindex: sync_iindex, + sindex: sync_sindex, + cindex: sync_cindex, + }) // Save the intermediary table of wallets - const conditions = _.keys(sync_memoryWallets) - const nonEmptyKeys = _.filter(conditions, (k: any) => sync_memoryWallets[k] && sync_memoryWallets[k].balance > 0) + const conditions = Underscore.keys(sync_memoryWallets) + const nonEmptyKeys = Underscore.filter(conditions, (k: any) => sync_memoryWallets[k] && sync_memoryWallets[k].balance > 0) const walletsToRecord = nonEmptyKeys.map((k: any) => sync_memoryWallets[k]) await this.dal.walletDAL.insertBatch(walletsToRecord) for (const cond of conditions) { @@ -260,12 +226,12 @@ export class QuickSynchronizer { } if (block.number === 0) { - await this.blockchain.saveParametersForRoot(block, this.conf, this.dal) + await DuniterBlockchain.saveParametersForRoot(block, this.conf, this.dal) } // Last block: cautious mode to trigger all the INDEX expiry mechanisms const { index, HEAD } = await DuniterBlockchain.checkBlock(dto, constants.WITH_SIGNATURES_AND_POW, this.conf, this.dal) - await this.blockchain.pushTheBlock(dto, index, HEAD, this.conf, this.dal, this.logger) + await DuniterBlockchain.pushTheBlock(dto, index, HEAD, this.conf, this.dal, this.logger) // Clean temporary variables sync_bindex = []; @@ -274,14 +240,10 @@ export class QuickSynchronizer { sync_cindex = []; sync_sindex = []; sync_bindexSize = 0; - sync_allBlocks = []; sync_expires = []; sync_nextExpiring = 0; // sync_currConf = {}; } } - if (blocksToSave.length) { - await this.saveBlocksInMainBranch(blocksToSave); - } } } diff --git a/app/lib/constants.ts b/app/lib/constants.ts index 5b9f76d72d3869b2dff9cfd99208c93498d0f83d..7092d69e5ff3ccb15b66161198ad571936edd2ac 100644 --- a/app/lib/constants.ts +++ b/app/lib/constants.ts @@ -14,7 +14,7 @@ "use strict"; import {CommonConstants} from "./common-libs/constants" import {OtherConstants} from "./other_constants" -import { ProverConstants } from '../modules/prover/lib/constants'; +import {ProverConstants} from '../modules/prover/lib/constants'; const UDID2 = "udid2;c;([A-Z-]*);([A-Z-]*);(\\d{4}-\\d{2}-\\d{2});(e\\+\\d{2}\\.\\d{2}(\\+|-)\\d{3}\\.\\d{2});(\\d+)(;?)"; const PUBKEY = CommonConstants.FORMATS.PUBKEY @@ -176,7 +176,6 @@ module.exports = { NO_FORK_ALLOWED: false, SAFE_FACTOR: 3, - BLOCKS_COLLECT_THRESHOLD: 30, // Blocks to collect from memory and persist MUTE_LOGS_DURING_UNIT_TESTS: OtherConstants.MUTE_LOGS_DURING_UNIT_TESTS, @@ -185,8 +184,6 @@ module.exports = { SANDBOX_SIZE_CERTIFICATIONS: 12, SANDBOX_SIZE_MEMBERSHIPS: 5000, - CURRENT_BLOCK_CACHE_DURATION: 10 * 1000, // 30 seconds - // With `logs` command, the number of tail lines to show NB_INITIAL_LINES_TO_SHOW: 100 }; diff --git a/app/lib/dal/drivers/LokiFsAdapter.ts b/app/lib/dal/drivers/LokiFsAdapter.ts new file mode 100644 index 0000000000000000000000000000000000000000..e7f5342cce566331d4db9be0fe4757f438a5dc52 --- /dev/null +++ b/app/lib/dal/drivers/LokiFsAdapter.ts @@ -0,0 +1,350 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import {FileSystem} from "../../system/directory" +import {DataErrors} from "../../common-libs/errors" +import {CFSCore} from "../fileDALs/CFSCore" +import {getNanosecondsTime} from "../../../ProcessCpuProfiler" +import {NewLogger} from "../../logger" + +export interface Iterator<T> { + next(value?: any): IteratorResult<T> + return?(value?: any): IteratorResult<T> + throw?(e?: any): IteratorResult<T> +} + +export interface IteratorResult<T> { + done: boolean + value: T +} + +export interface DBCommit { + indexFile:string, + changes: string[] + collections: { + [coll:string]: string + } +} + +export class LokiFsAdapter { + + private static COMMIT_FILE = "commit.json" + private cfs:CFSCore + + protected mode = "reference" + protected dbref = null + protected dirtyPartitions: string[] = []; + + constructor(dbDir:string, fs:FileSystem) { + this.cfs = new CFSCore(dbDir, fs) + } + + /** + * Main method to manually pilot the full DB saving to disk. + * @param loki + * @returns {Promise} + */ + async dbDump(loki:any) { + return new Promise(res => loki.saveDatabaseInternal(res)) + } + + async listPendingChanges(): Promise<string[]> { + if (!(await this.cfs.exists(LokiFsAdapter.COMMIT_FILE))) { + return [] + } + const commitObj = await this.cfs.readJSON(LokiFsAdapter.COMMIT_FILE) + return commitObj.changes + } + + /** + * Flushes the DB changes to disk. + * @param loki + * @returns {Promise<number>} The number of changes detected. + */ + async flush(loki:any): Promise<number> { + // If the database already has a commit file: incremental changes + if (await this.cfs.exists(LokiFsAdapter.COMMIT_FILE)) { + const commit = (await this.cfs.readJSON(LokiFsAdapter.COMMIT_FILE)) as DBCommit + const changesFilename = 'changes.' + getNanosecondsTime() + ".json" + const changes = JSON.parse(loki.serializeChanges()) + await this.cfs.writeJSON(changesFilename, changes) + // Mark the changes as commited + commit.changes.push(changesFilename) + await this.cfs.writeJSON(LokiFsAdapter.COMMIT_FILE, commit) + // Forget about the changes now that we saved them + loki.clearChanges() + return changes.length + } else { + // Otherwise we make a full dump + await this.dbDump(loki) + loki.clearChanges() + return 0 + } + } + + /** + * + * Method indirectly called by `flush`. + * + * Loki reference adapter interface function. Saves structured json via loki database object reference. + * + * @param {string} dbname - the name to give the serialized database within the catalog. + * @param {object} dbref - the loki database object reference to save. + * @param {function} callback - callback passed obj.success with true or false + * @memberof LokiFsStructuredAdapter + */ + public async exportDatabase(dbname:string, dbref:any, callback:any) { + + this.dbref = dbref + + // create (dirty) partition generator/iterator + let pi = this.getPartition() + + // Prepare the commit: inherit from existing commit + let commit:DBCommit = { + indexFile: 'index.db.' + getNanosecondsTime() + ".json", + changes: [], + collections: {} + } + if (await this.cfs.exists(LokiFsAdapter.COMMIT_FILE)) { + commit.collections = ((await this.cfs.readJSON(LokiFsAdapter.COMMIT_FILE)) as DBCommit).collections + } + + // Eventually create the tree + await this.cfs.makeTree('/') + + this.saveNextPartition(commit, pi, async () => { + + // Write the new commit file. If the process gets interrupted during this phase, the DB will likely get corrupted. + await this.cfs.writeJSON(LokiFsAdapter.COMMIT_FILE, commit) + + const remainingFiles = [ + LokiFsAdapter.COMMIT_FILE, + commit.indexFile + ].concat(Object.keys(commit.collections).map(k => commit.collections[k])) + + // Clean obsolete DB files + const list = await this.cfs.list('/') + for (const f of list) { + if (remainingFiles.indexOf(f) === -1) { + await this.cfs.remove(f) + } + } + + // Finish + callback(null) + }) + } + + /** + * Generator for yielding sequence of dirty partition indices to iterate. + * + * @memberof LokiFsStructuredAdapter + */ + private *getPartition(): Iterator<string> { + let idx, + clen = (this.dbref as any).collections.length + + // since database container (partition -1) doesn't have dirty flag at db level, always save + yield ""; + + // yield list of dirty partitions for iterateration + for(idx=0; idx<clen; idx++) { + const coll:any = (this.dbref as any).collections[idx] + if (coll.dirty) { + yield coll.name + } + } + } + + /** + * Utility method for queueing one save at a time + */ + private async saveNextPartition(commit:DBCommit, pi:Iterator<string>, callback:any) { + let li; + let filename; + let self = this; + let pinext = pi.next(); + + if (pinext.done) { + callback(); + return; + } + + // db container (partition -1) uses just dbname for filename, + // otherwise append collection array index to filename + filename = (pinext.value === "") ? commit.indexFile : ((pinext.value + "." + getNanosecondsTime()) + ".json") + + // We map the collection name to a particular file + if (pinext.value) { + commit.collections[pinext.value] = filename + } + + li = this.generateDestructured({ partition: pinext.value }); + + // iterate each of the lines generated by generateDestructured() + await this.cfs.fsStreamTo(filename, li) + + self.saveNextPartition(commit, pi, callback) + }; + + /** + * Generator for constructing lines for file streaming output of db container or collection. + * + * @param {object=} options - output format options for use externally to loki + * @param {int=} options.partition - can be used to only output an individual collection or db (-1) + * + * @returns {string|array} A custom, restructured aggregation of independent serializations. + * @memberof LokiFsStructuredAdapter + */ + *generateDestructured(options = { partition: "" }) { + let idx + let dbcopy; + + // if partition is -1 we will return database container with no data + if (options.partition === "") { + // instantiate lightweight clone and remove its collection data + dbcopy = (this.dbref as any).copy(); + + for(idx=0; idx < dbcopy.collections.length; idx++) { + dbcopy.collections[idx].data = []; + dbcopy.collections[idx].changes = []; + } + + yield dbcopy.serialize({ + serializationMethod: "normal" + }); + + return; + } + + // 'partitioned' along with 'partition' of 0 or greater is a request for single collection serialization + if (options.partition) { + let doccount, + docidx; + + // dbref collections have all data so work against that + const coll = (this.dbref as any).collections.filter((c:any) => c.name === options.partition)[0] + doccount = coll.data.length; + + for(docidx=0; docidx<doccount; docidx++) { + yield JSON.stringify(coll.data[docidx]); + } + + if (doccount === 0) { + yield '' + } + } + }; + + /** + * + * Automatically called on startup. + * + * Loki persistence adapter interface function which outputs un-prototype db object reference to load from. + * + * @memberof LokiFsStructuredAdapter + */ + public async loadDatabase(loki:any) { + let instream, + outstream, + rl, + self=this; + + this.dbref = null; + + // Load the database according to the commit file (lock for valid DB files) + let commitObj:DBCommit + if (!(await this.cfs.exists(LokiFsAdapter.COMMIT_FILE))) { + return + } + commitObj = await this.cfs.readJSON(LokiFsAdapter.COMMIT_FILE) + + // make sure file exists + const dbname = commitObj.indexFile + + // Trimmed data first + if (await this.cfs.exists(dbname)) { + const line = await this.cfs.read(dbname) + // it should single JSON object (a one line file) + if (self.dbref === null && line) { + self.dbref = JSON.parse(line) + } + + // when that is done, examine its collection array to sequence loading each + if ((self.dbref as any).collections.length > 0) { + await self.loadNextCollection(commitObj.collections, 0) + loki.loadJSONObject(self.dbref) + } + } else { + // file does not exist, we throw as the commit file is not respected + throw Error(DataErrors[DataErrors.CORRUPTED_DATABASE]) + } + + // Changes data + for (const changeFile of commitObj.changes) { + const changes = await this.cfs.readJSON(changeFile) + let len = changes.length + for (let i = 1; i <= len; i++) { + const c = changes[i - 1] + const coll = loki.getCollection(c.name) + if (c.operation === 'I') { + c.obj.$loki = undefined + await coll.insert(c.obj) + } + else if (c.operation === 'U') { + await coll.update(c.obj) + } + else if (c.operation === 'R') { + await coll.remove(c.obj) + } + NewLogger().trace('[loki] Processed change %s (%s/%s)', c.name, i, len) + } + } + }; + + + /** + * Recursive function to chain loading of each collection one at a time. + * If at some point i can determine how to make async driven generator, this may be converted to generator. + * + * @param {object} collectionsMap - Map between the names of the collections and their matching file of the filesystem. + * @param {int} collectionIndex - the ordinal position of the collection to load. + * @param {function} callback - callback to pass to next invocation or to call when done + * @memberof LokiFsStructuredAdapter + */ + async loadNextCollection(collectionsMap:{ [coll:string]: string }, collectionIndex:any) { + let self=this, + obj; + const coll = (self.dbref as any).collections[collectionIndex] + if (!collectionsMap[coll.name] || !(await this.cfs.exists(collectionsMap[coll.name]))) { + throw Error(DataErrors[DataErrors.CORRUPTED_DATABASE]) + } + const filename = collectionsMap[coll.name] + const content = await this.cfs.read(filename) + if (content) { + const lines = content.split('\n') + for (const line of lines) { + if (line !== "") { + obj = JSON.parse(line); + coll.data.push(obj); + } + } + } + + // if there are more collections, load the next one + if (++collectionIndex < (self.dbref as any).collections.length) { + await self.loadNextCollection(collectionsMap, collectionIndex) + } + }; +} \ No newline at end of file diff --git a/app/lib/dal/drivers/LokiJsDriver.ts b/app/lib/dal/drivers/LokiJsDriver.ts new file mode 100644 index 0000000000000000000000000000000000000000..7a3dfc549285da3a774f3fc942dab4f2d9da008f --- /dev/null +++ b/app/lib/dal/drivers/LokiJsDriver.ts @@ -0,0 +1,42 @@ +import {LokiFsAdapter} from "./LokiFsAdapter" +import {MemFS, RealFS} from "../../system/directory" + +const loki = require('lokijs') + +export class LokiJsDriver { + + private readonly lokiInstance:any + private adapter: LokiFsAdapter + + constructor( + private dbFilePath:string = '' + ) { + this.adapter = new LokiFsAdapter(dbFilePath, dbFilePath ? RealFS() : MemFS()) + this.lokiInstance = new loki(dbFilePath + '/loki.db' || 'mem' + Date.now() + '.db', { + adapter: this.adapter + }) + } + + async loadDatabase() { + // We load only non-memory DB + if (this.dbFilePath) { + await this.adapter.loadDatabase(this.lokiInstance) + } + } + + getLokiInstance() { + return this.lokiInstance + } + + async commitData() { + return this.adapter.flush(this.lokiInstance) + } + + async flushAndTrimData() { + return this.adapter.dbDump(this.lokiInstance) + } + + async listChangesFilesPending(): Promise<string[]> { + return this.adapter.listPendingChanges() + } +} diff --git a/app/lib/dal/drivers/SQLiteDriver.ts b/app/lib/dal/drivers/SQLiteDriver.ts index b7e52b00eb19aa3fc20a6f95f14ea02b1271cda6..1b2c3896d7867e8ddee0b304e2684d13980f0e33 100644 --- a/app/lib/dal/drivers/SQLiteDriver.ts +++ b/app/lib/dal/drivers/SQLiteDriver.ts @@ -11,7 +11,9 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -const qfs = require('q-io/fs') +import {OtherConstants} from "../../other_constants" +import {RealFS} from "../../system/directory" + const sqlite3 = require("sqlite3").verbose() const MEMORY_PATH = ':memory:' @@ -34,6 +36,11 @@ export class SQLiteDriver { let sqlite = new sqlite3.Database(this.path) await new Promise<any>((resolve) => sqlite.once('open', resolve)) // Database is opened + if (OtherConstants.SQL_TRACES) { + sqlite.on('trace', (trace:any) => { + this.logger.trace(trace) + }) + } // Force case sensitiveness on LIKE operator const sql = 'PRAGMA case_sensitive_like=ON' @@ -75,7 +82,7 @@ export class SQLiteDriver { this.logger.debug('Removing SQLite database...') await this.closeConnection() if (this.path !== MEMORY_PATH) { - await qfs.remove(this.path) + await RealFS().fsUnlink(this.path) } this.logger.debug('Database removed') } diff --git a/app/lib/dal/fileDAL.ts b/app/lib/dal/fileDAL.ts index 4e441a89b32026b162f0621f6933a9bdcca29cbd..f5e556edb57f5f5424ae7b2eca139783c2d26924 100644 --- a/app/lib/dal/fileDAL.ts +++ b/app/lib/dal/fileDAL.ts @@ -11,93 +11,163 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. +import * as fs from 'fs' +import * as path from 'path' import {SQLiteDriver} from "./drivers/SQLiteDriver" import {ConfDAL} from "./fileDALs/ConfDAL" import {StatDAL} from "./fileDALs/StatDAL" import {ConfDTO} from "../dto/ConfDTO" import {BlockDTO} from "../dto/BlockDTO" import {DBHead} from "../db/DBHead" -import {DBIdentity} from "./sqliteDAL/IdentityDAL" -import {CindexEntry, IindexEntry, IndexEntry, MindexEntry, SindexEntry} from "../indexer" -import {DBPeer} from "./sqliteDAL/PeerDAL" +import {DBIdentity, IdentityDAL} from "./sqliteDAL/IdentityDAL" +import { + CindexEntry, + FullCindexEntry, + FullIindexEntry, + FullMindexEntry, + IindexEntry, + IndexEntry, + MindexEntry, + SimpleTxInput, + SimpleUdEntryForWallet, + SindexEntry +} from "../indexer" import {TransactionDTO} from "../dto/TransactionDTO" -import {DBCert} from "./sqliteDAL/CertDAL" -import {DBWallet} from "./sqliteDAL/WalletDAL" -import {DBTx} from "./sqliteDAL/TxsDAL" +import {CertDAL, DBCert} from "./sqliteDAL/CertDAL" import {DBBlock} from "../db/DBBlock" -import {DBMembership} from "./sqliteDAL/MembershipDAL" +import {DBMembership, MembershipDAL} from "./sqliteDAL/MembershipDAL" import {MerkleDTO} from "../dto/MerkleDTO" import {CommonConstants} from "../common-libs/constants" import {PowDAL} from "./fileDALs/PowDAL"; +import {Initiable} from "./sqliteDAL/Initiable" +import {MetaDAL} from "./sqliteDAL/MetaDAL" +import {DataErrors} from "../common-libs/errors" +import {BasicRevocableIdentity, IdentityDTO} from "../dto/IdentityDTO" +import {FileSystem} from "../system/directory" +import {WoTBInstance} from "../wot" +import {IIndexDAO} from "./indexDAL/abstract/IIndexDAO" +import {LokiIIndex} from "./indexDAL/loki/LokiIIndex" +import {BIndexDAO} from "./indexDAL/abstract/BIndexDAO" +import {MIndexDAO} from "./indexDAL/abstract/MIndexDAO" +import {SIndexDAO} from "./indexDAL/abstract/SIndexDAO" +import {CIndexDAO} from "./indexDAL/abstract/CIndexDAO" +import {IdentityForRequirements} from "../../service/BlockchainService" +import {LokiSIndex} from "./indexDAL/loki/LokiSIndex" +import {LokiCIndex} from "./indexDAL/loki/LokiCIndex" +import {LokiMIndex} from "./indexDAL/loki/LokiMIndex"; +import {LokiBIndex} from "./indexDAL/loki/LokiBIndex" +import {NewLogger} from "../logger" +import {LokiBlockchain} from "./indexDAL/loki/LokiBlockchain" +import {BlockchainDAO} from "./indexDAL/abstract/BlockchainDAO" +import {LokiTransactions} from "./indexDAL/loki/LokiTransactions" +import {TxsDAO} from "./indexDAL/abstract/TxsDAO" +import {LokiJsDriver} from "./drivers/LokiJsDriver" +import {WalletDAO} from "./indexDAL/abstract/WalletDAO" +import {LokiWallet} from "./indexDAL/loki/LokiWallet" +import {PeerDAO} from "./indexDAL/abstract/PeerDAO" +import {LokiPeer} from "./indexDAL/loki/LokiPeer" +import {DBTx} from "../db/DBTx" +import {DBWallet} from "../db/DBWallet" +import {Tristamp} from "../common/Tristamp" +import {CFSBlockchainArchive} from "./indexDAL/CFSBlockchainArchive" +import {CFSCore} from "./fileDALs/CFSCore" +import {BlockchainArchiveDAO} from "./indexDAL/abstract/BlockchainArchiveDAO" +import {Underscore} from "../common-libs/underscore" +import {DBPeer} from "../db/DBPeer" +import {MonitorFlushedIndex} from "../debug/MonitorFlushedIndex" +import {cliprogram} from "../common-libs/programOptions" +import {DividendDAO, UDSource} from "./indexDAL/abstract/DividendDAO" +import {LokiDividend} from "./indexDAL/loki/LokiDividend" +import {HttpSource, HttpUD} from "../../modules/bma/lib/dtos" +import {GenericDAO} from "./indexDAL/abstract/GenericDAO" +import {LokiDAO} from "./indexDAL/loki/LokiDAO" -const fs = require('fs') -const path = require('path') const readline = require('readline') -const _ = require('underscore'); const indexer = require('../indexer').Indexer const logger = require('../logger').NewLogger('filedal'); const constants = require('../constants'); export interface FileDALParams { home:string - fs:any + fs:FileSystem dbf:() => SQLiteDriver - wotb:any + dbf2: () => LokiJsDriver + wotb:WoTBInstance +} + +export interface IndexBatch { + mindex: MindexEntry[] + iindex: IindexEntry[] + sindex: SindexEntry[] + cindex: CindexEntry[] } export class FileDAL { rootPath:string - myFS:any + fs: FileSystem + loki:LokiJsDriver sqliteDriver:SQLiteDriver - wotb:any + wotb:WoTBInstance profile:string + // Simple file accessors powDAL:PowDAL - confDAL:any - metaDAL:any - peerDAL:any - blockDAL:any - txsDAL:any - statDAL:any - idtyDAL:any - certDAL:any - msDAL:any - walletDAL:any - bindexDAL:any - mindexDAL:any - iindexDAL:any - sindexDAL:any - cindexDAL:any - newDals:any + confDAL:ConfDAL + statDAL:StatDAL + blockchainArchiveDAL:BlockchainArchiveDAO<DBBlock> + + // SQLite DALs + metaDAL:MetaDAL + idtyDAL:IdentityDAL + certDAL:CertDAL + msDAL:MembershipDAL + + // New DAO entities + blockDAL:BlockchainDAO + txsDAL:TxsDAO + peerDAL:PeerDAO + walletDAL:WalletDAO + bindexDAL:BIndexDAO + mindexDAL:MIndexDAO + iindexDAL:IIndexDAO + sindexDAL:SIndexDAO + cindexDAL:CIndexDAO + dividendDAL:DividendDAO + newDals:{ [k:string]: Initiable } + private dals:(BlockchainArchiveDAO<any>|PeerDAO|WalletDAO|GenericDAO<any>)[] + private daos:LokiDAO[] loadConfHook: (conf:ConfDTO) => Promise<void> saveConfHook: (conf:ConfDTO) => Promise<ConfDTO> constructor(params:FileDALParams) { this.rootPath = params.home - this.myFS = params.fs this.sqliteDriver = params.dbf() + this.loki = params.dbf2() this.wotb = params.wotb this.profile = 'DAL' + this.fs = params.fs // DALs - this.powDAL = new PowDAL(this.rootPath, this.myFS) - this.confDAL = new ConfDAL(this.rootPath, this.myFS) + this.powDAL = new PowDAL(this.rootPath, params.fs) + this.confDAL = new ConfDAL(this.rootPath, params.fs) this.metaDAL = new (require('./sqliteDAL/MetaDAL').MetaDAL)(this.sqliteDriver); - this.peerDAL = new (require('./sqliteDAL/PeerDAL').PeerDAL)(this.sqliteDriver); - this.blockDAL = new (require('./sqliteDAL/BlockDAL').BlockDAL)(this.sqliteDriver); - this.txsDAL = new (require('./sqliteDAL/TxsDAL').TxsDAL)(this.sqliteDriver); - this.statDAL = new StatDAL(this.rootPath, this.myFS) + this.blockchainArchiveDAL = new CFSBlockchainArchive(new CFSCore(path.join(this.rootPath, '/archives'), params.fs), CommonConstants.CONST_BLOCKS_CHUNK) + this.blockDAL = new LokiBlockchain(this.loki.getLokiInstance()) + this.txsDAL = new LokiTransactions(this.loki.getLokiInstance()) + this.statDAL = new StatDAL(this.rootPath, params.fs) this.idtyDAL = new (require('./sqliteDAL/IdentityDAL').IdentityDAL)(this.sqliteDriver); this.certDAL = new (require('./sqliteDAL/CertDAL').CertDAL)(this.sqliteDriver); this.msDAL = new (require('./sqliteDAL/MembershipDAL').MembershipDAL)(this.sqliteDriver); - this.walletDAL = new (require('./sqliteDAL/WalletDAL').WalletDAL)(this.sqliteDriver); - this.bindexDAL = new (require('./sqliteDAL/index/BIndexDAL').BIndexDAL)(this.sqliteDriver); - this.mindexDAL = new (require('./sqliteDAL/index/MIndexDAL').MIndexDAL)(this.sqliteDriver); - this.iindexDAL = new (require('./sqliteDAL/index/IIndexDAL').IIndexDAL)(this.sqliteDriver); - this.sindexDAL = new (require('./sqliteDAL/index/SIndexDAL').SIndexDAL)(this.sqliteDriver); - this.cindexDAL = new (require('./sqliteDAL/index/CIndexDAL').CIndexDAL)(this.sqliteDriver); + this.peerDAL = new LokiPeer(this.loki.getLokiInstance()) + this.walletDAL = new LokiWallet(this.loki.getLokiInstance()) + this.bindexDAL = new LokiBIndex(this.loki.getLokiInstance()) + this.mindexDAL = new LokiMIndex(this.loki.getLokiInstance()) + this.iindexDAL = new LokiIIndex(this.loki.getLokiInstance()) + this.sindexDAL = new LokiSIndex(this.loki.getLokiInstance()) + this.cindexDAL = new LokiCIndex(this.loki.getLokiInstance()) + this.dividendDAL = new LokiDividend(this.loki.getLokiInstance()) this.newDals = { 'powDAL': this.powDAL, @@ -115,12 +185,52 @@ export class FileDAL { 'mindexDAL': this.mindexDAL, 'iindexDAL': this.iindexDAL, 'sindexDAL': this.sindexDAL, - 'cindexDAL': this.cindexDAL + 'cindexDAL': this.cindexDAL, + 'dividendDAL': this.dividendDAL, + 'blockchainArchiveDAL': this.blockchainArchiveDAL, } } + public enableChangesAPI() { + this.daos.map(d => d.enableChangesAPI()) + } + + public disableChangesAPI() { + this.daos.map(d => d.disableChangesAPI()) + } + async init(conf:ConfDTO) { - const dalNames = _.keys(this.newDals); + // Init LokiJS + await this.loki.loadDatabase() + this.daos = [ + this.blockDAL, + this.txsDAL, + this.peerDAL, + this.walletDAL, + this.bindexDAL, + this.mindexDAL, + this.iindexDAL, + this.sindexDAL, + this.cindexDAL, + this.dividendDAL + ] + this.dals = [ + this.blockDAL, + this.txsDAL, + this.peerDAL, + this.walletDAL, + this.bindexDAL, + this.mindexDAL, + this.iindexDAL, + this.sindexDAL, + this.cindexDAL, + this.dividendDAL, + this.blockchainArchiveDAL, + ] + for (const indexDAL of this.dals) { + indexDAL.triggerInit() + } + const dalNames = Underscore.keys(this.newDals); for (const dalName of dalNames) { const dal = this.newDals[dalName]; await dal.init(); @@ -138,6 +248,33 @@ export class FileDAL { return this.metaDAL.getVersion() } + /** + * Transfer a chunk of blocks from memory DB to archives if the memory DB overflows. + * @returns {Promise<void>} + */ + async archiveBlocks() { + const lastArchived = await this.blockchainArchiveDAL.getLastSavedBlock() + const current = await this.blockDAL.getCurrent() + const lastNumber = lastArchived ? lastArchived.number : -1 + const currentNumber = current ? current.number : -1 + const difference = currentNumber - lastNumber + if (difference > CommonConstants.BLOCKS_IN_MEMORY_MAX) { + const CHUNK_SIZE = this.blockchainArchiveDAL.chunkSize + const nbBlocksOverflow = difference - CommonConstants.BLOCKS_IN_MEMORY_MAX + const chunks = (nbBlocksOverflow - (nbBlocksOverflow % CHUNK_SIZE)) / CHUNK_SIZE + for (let i = 0; i < chunks; i++) { + const start = lastNumber + (i*CHUNK_SIZE) + 1 + const end = lastNumber + (i*CHUNK_SIZE) + CHUNK_SIZE + const memBlocks = await this.blockDAL.getNonForkChunk(start, end) + if (memBlocks.length !== CHUNK_SIZE) { + throw Error(DataErrors[DataErrors.CANNOT_ARCHIVE_CHUNK_WRONG_SIZE]) + } + await this.blockchainArchiveDAL.archive(memBlocks) + await this.blockDAL.trimBlocks(end) + } + } + } + writeFileOfBlock(block:DBBlock) { return this.blockDAL.saveBlock(block) } @@ -152,7 +289,7 @@ export class FileDAL { async getPeer(pubkey:string) { try { - return this.peerDAL.getPeer(pubkey) + return await this.peerDAL.getPeer(pubkey) } catch (err) { throw Error('Unknown peer ' + pubkey); } @@ -162,73 +299,91 @@ export class FileDAL { return this.peerDAL.getPeersWithEndpointsLike('WS2P') } - async getBlock(number:number) { - const block = await this.blockDAL.getBlock(number) - return block || null; - } - - getAbsoluteBlockByNumberAndHash(number:number, hash:string) { - return this.blockDAL.getAbsoluteBlock(number, hash) - } - - getAbsoluteBlockByBlockstamp(blockstamp:string) { - if (!blockstamp) throw "Blockstamp is required to find the block" - const sp = blockstamp.split('-') - const number = parseInt(sp[0]) - const hash = sp[1] - return this.getAbsoluteBlockByNumberAndHash(number, hash) - } - - getBlockByBlockstampOrNull(blockstamp:string) { + getAbsoluteBlockInForkWindowByBlockstamp(blockstamp:string) { if (!blockstamp) throw "Blockstamp is required to find the block"; const sp = blockstamp.split('-'); const number = parseInt(sp[0]); const hash = sp[1]; - return this.getBlockByNumberAndHashOrNull(number, hash); + return this.getAbsoluteBlockInForkWindow(number, hash) } - getBlockByBlockstamp(blockstamp:string) { + getAbsoluteValidBlockInForkWindowByBlockstamp(blockstamp:string) { if (!blockstamp) throw "Blockstamp is required to find the block"; const sp = blockstamp.split('-'); const number = parseInt(sp[0]); const hash = sp[1]; - return this.getBlockByNumberAndHash(number, hash); + return this.getAbsoluteValidBlockInForkWindow(number, hash) } - async getBlockByNumberAndHash(number:number, hash:string) { - try { - const block = await this.getBlock(number); - if (!block || block.hash != hash) - throw "Not found"; - else - return block; - } catch (err) { - throw 'Block ' + [number, hash].join('-') + ' not found'; + async getBlockWeHaveItForSure(number:number): Promise<DBBlock> { + return (await this.blockDAL.getBlock(number)) as DBBlock || (await this.blockchainArchiveDAL.getBlockByNumber(number)) + } + + // Duniter-UI dependency + async getBlock(number: number): Promise<DBBlock|null> { + return this.getFullBlockOf(number) + } + + async getFullBlockOf(number: number): Promise<DBBlock|null> { + return (await this.blockDAL.getBlock(number)) || (await this.blockchainArchiveDAL.getBlockByNumber(number)) + } + + async getBlockstampOf(number: number): Promise<string|null> { + const block = await this.getTristampOf(number) + if (block) { + return [block.number, block.hash].join('-') } + return null } - async getBlockByNumberAndHashOrNull(number:number, hash:string) { - try { - return await this.getBlockByNumberAndHash(number, hash) - } catch (e) { - return null; + async getTristampOf(number: number): Promise<Tristamp|null> { + return (await this.blockDAL.getBlock(number)) || (await this.blockchainArchiveDAL.getBlockByNumber(number)) + } + + async existsAbsoluteBlockInForkWindow(number:number, hash:string): Promise<boolean> { + return !!(await this.getAbsoluteBlockByNumberAndHash(number, hash)) + } + + async getAbsoluteBlockInForkWindow(number:number, hash:string): Promise<DBBlock|null> { + return this.getAbsoluteBlockByNumberAndHash(number, hash) + } + + async getAbsoluteValidBlockInForkWindow(number:number, hash:string): Promise<DBBlock|null> { + const block = await this.getAbsoluteBlockByNumberAndHash(number, hash) + if (block && !block.fork) { + return block } + return null + } + + async getAbsoluteBlockByNumberAndHash(number:number, hash:string): Promise<DBBlock|null> { + if (number > 0) { + return (await this.blockDAL.getAbsoluteBlock(number, hash)) || (await this.blockchainArchiveDAL.getBlock(number, hash)) + } else { + // Block#0 is special + return (await this.blockDAL.getBlock(number)) || (await this.blockchainArchiveDAL.getBlockByNumber(number)) + } + } + + async getAbsoluteBlockByBlockstamp(blockstamp: string): Promise<DBBlock|null> { + const sp = blockstamp.split('-') + return this.getAbsoluteBlockByNumberAndHash(parseInt(sp[0]), sp[1]) } async existsNonChainableLink(from:string, vHEAD_1:DBHead, sigStock:number) { // Cert period rule const medianTime = vHEAD_1 ? vHEAD_1.medianTime : 0; - const linksFrom = await this.cindexDAL.reducablesFrom(from) - const unchainables = _.filter(linksFrom, (link:CindexEntry) => link.chainable_on > medianTime); + const linksFrom:FullCindexEntry[] = await this.cindexDAL.reducablesFrom(from) + const unchainables = Underscore.filter(linksFrom, (link:CindexEntry) => link.chainable_on > medianTime); if (unchainables.length > 0) return true; // Max stock rule - let activeLinks = _.filter(linksFrom, (link:CindexEntry) => !link.expired_on); + let activeLinks = Underscore.filter(linksFrom, (link:CindexEntry) => !link.expired_on); return activeLinks.length >= sigStock; } async getCurrentBlockOrNull() { - let current = null; + let current:DBBlock|null = null; try { current = await this.getBlockCurrent() } catch (e) { @@ -240,17 +395,10 @@ export class FileDAL { } getPromoted(number:number) { - return this.getBlock(number) + return this.getFullBlockOf(number) } // Block - lastUDBlock() { - return this.blockDAL.lastBlockWithDividend() - } - - getRootBlock() { - return this.getBlock(0) - } getPotentialRootBlocks() { return this.blockDAL.getPotentialRoots() @@ -265,7 +413,7 @@ export class FileDAL { } getBlocksBetween (start:number, end:number) { - return Promise.resolve(this.blockDAL.getBlocks(Math.max(0, start), end)) + return this.blockDAL.getBlocks(Math.max(0, start), end) } getForkBlocksFollowing(current:DBBlock) { @@ -287,79 +435,273 @@ export class FileDAL { return this.cindexDAL.getValidLinksTo(to) } - getAvailableSourcesByPubkey(pubkey:string) { - return this.sindexDAL.getAvailableForPubkey(pubkey) + async getAvailableSourcesByPubkey(pubkey:string): Promise<HttpSource[]> { + const txAvailable = await this.sindexDAL.getAvailableForPubkey(pubkey) + const sources: UDSource[] = await this.dividendDAL.getUDSources(pubkey) + return sources.map(d => { + return { + type: 'D', + noffset: d.pos, + identifier: pubkey, + amount: d.amount, + base: d.base, + conditions: 'SIG(' + pubkey + ')' + } + }).concat(txAvailable.map(s => { + return { + type: 'T', + noffset: s.pos, + identifier: s.identifier, + amount: s.amount, + base: s.base, + conditions: s.conditions + } + })) + } + + async findByIdentifierPosAmountBase(identifier: string, pos: number, amount: number, base: number, isDividend: boolean): Promise<SimpleTxInput[]> { + if (isDividend) { + return this.dividendDAL.findUdSourceByIdentifierPosAmountBase(identifier, pos, amount, base) + } else { + return this.sindexDAL.findTxSourceByIdentifierPosAmountBase(identifier, pos, amount, base) + } + } + + async getGlobalIdentityByHashForExistence(hash:string): Promise<boolean> { + const pending = await this.idtyDAL.getByHash(hash) + if (!pending) { + const idty = await this.iindexDAL.getFullFromHash(hash) + if (!idty) { + return false + } + } + return true + } + + async getGlobalIdentityByHashForHashingAndSig(hash:string): Promise<{ pubkey:string, uid:string, buid:string, sig:string }|null> { + const pending = await this.idtyDAL.getByHash(hash) + if (!pending) { + const idty = await this.iindexDAL.getFullFromHash(hash) + if (!idty) { + return null + } + return { + pubkey: idty.pub, + uid: idty.uid, + buid: idty.created_on, + sig: idty.sig + } + } + return pending + } + + async getGlobalIdentityByHashForLookup(hash:string): Promise<{ pubkey:string, uid:string, buid:string, sig:string, member:boolean, wasMember:boolean }|null> { + const pending = await this.idtyDAL.getByHash(hash) + if (!pending) { + const idty = await this.iindexDAL.getFullFromHash(hash) + if (!idty) { + return null + } + return { + pubkey: idty.pub, + uid: idty.uid, + buid: idty.created_on, + sig: idty.sig, + member: idty.member, + wasMember: idty.wasMember + } + } + return pending + } + + async getGlobalIdentityByHashForJoining(hash:string): Promise<{ pubkey:string, uid:string, buid:string, sig:string, member:boolean, wasMember:boolean, revoked:boolean }|null> { + const pending = await this.idtyDAL.getByHash(hash) + if (!pending) { + const idty = await this.iindexDAL.getFullFromHash(hash) + if (!idty) { + return null + } + const membership = await this.mindexDAL.getReducedMS(idty.pub) as FullMindexEntry + return { + pubkey: idty.pub, + uid: idty.uid, + buid: idty.created_on, + sig: idty.sig, + member: idty.member, + wasMember: idty.wasMember, + revoked: !!(membership.revoked_on) + } + } + return pending + } + + async getGlobalIdentityByHashForIsMember(hash:string): Promise<{ pub:string, member:boolean }|null> { + const pending = await this.idtyDAL.getByHash(hash) + if (!pending) { + const idty = await this.iindexDAL.getFullFromHash(hash) + if (!idty) { + return null + } + return { + pub: idty.pub, + member: idty.member + } + } + return { + pub: pending.pubkey, + member: pending.member + } } - async getIdentityByHashOrNull(hash:string) { - const pending = await this.idtyDAL.getByHash(hash); + async getGlobalIdentityByHashForRevocation(hash:string): Promise<{ pub:string, uid:string, created_on:string, sig:string, member:boolean, wasMember:boolean, revoked:boolean, revocation_sig:string|null, expires_on:number }|null> { + const pending = await this.idtyDAL.getByHash(hash) if (!pending) { - return this.iindexDAL.getFromHash(hash); + const idty = await this.iindexDAL.getFullFromHash(hash) + if (!idty) { + return null + } + const membership = await this.mindexDAL.getReducedMS(idty.pub) as FullMindexEntry + return { + pub: idty.pub, + uid: idty.uid, + sig: idty.sig, + member: idty.member, + wasMember: idty.wasMember, + expires_on: membership.expires_on, + created_on: idty.created_on, + revoked: !!(membership.revoked_on), + revocation_sig: membership.revocation + } + } + return { + pub: pending.pubkey, + uid: pending.uid, + sig: pending.sig, + expires_on: pending.expires_on, + created_on: pending.buid, + member: pending.member, + wasMember: pending.wasMember, + revoked: pending.revoked, + revocation_sig: pending.revocation_sig } - return pending; } getMembers() { return this.iindexDAL.getMembers() } - // TODO: this should definitely be reduced by removing fillInMembershipsOfIdentity - async getWritten(pubkey:string) { - try { - return await this.fillInMembershipsOfIdentity(this.iindexDAL.getFromPubkey(pubkey)); - } catch (err) { - logger.error(err); - return null; + async getWrittenIdtyByPubkeyForHash(pubkey:string): Promise<{ hash:string }> { + return this.getWrittenForSureIdtyByPubkey(pubkey) + } + + async getWrittenIdtyByPubkeyForHashing(pubkey:string): Promise<{ uid:string, created_on:string, pub:string }> { + return this.getWrittenForSureIdtyByPubkey(pubkey) + } + + async getWrittenIdtyByPubkeyForWotbID(pubkey:string): Promise<{ wotb_id:number }> { + return this.getWrittenForSureIdtyByPubkey(pubkey) + } + + async getWrittenIdtyByPubkeyForUidAndPubkey(pubkey:string): Promise<{ pub:string, uid:string }> { + return this.getWrittenForSureIdtyByPubkey(pubkey) + } + + async getWrittenIdtyByPubkeyForIsMember(pubkey:string): Promise<{ member:boolean }|null> { + return this.iindexDAL.getFromPubkey(pubkey) + } + + async getWrittenIdtyByPubkeyForUidAndIsMemberAndWasMember(pubkey:string): Promise<{ uid:string, member:boolean, wasMember:boolean }|null> { + return this.iindexDAL.getFromPubkey(pubkey) + } + + async getWrittenIdtyByPubkeyOrUidForIsMemberAndPubkey(search:string): Promise<{ pub:string, member:boolean }|null> { + return this.iindexDAL.getFromPubkeyOrUid(search) + } + + async getWrittenIdtyByPubkeyOrUIdForHashingAndIsMember(search:string): Promise<{ uid:string, created_on:string, pub:string, member:boolean }|null> { + return await this.iindexDAL.getFromPubkeyOrUid(search) + } + + async getWrittenIdtyByPubkeyForRevocationCheck(pubkey:string): Promise<{ pub:string, uid:string, created_on:string, sig:string, revoked_on:number|null }|null> { + const idty = await this.iindexDAL.getFromPubkey(pubkey) + if (!idty) { + return null + } + const membership = await this.mindexDAL.getReducedMS(pubkey) as FullMindexEntry + return { + pub: idty.pub, + uid: idty.uid, + sig: idty.sig, + created_on: idty.created_on, + revoked_on: membership.revoked_on } } - async getWrittenIdtyByPubkey(pubkey:string) { + async getWrittenIdtyByPubkeyForCertificationCheck(pubkey:string): Promise<{ pub:string, uid:string, created_on:string, sig:string }|null> { const idty = await this.iindexDAL.getFromPubkey(pubkey) if (!idty) { - return null; + return null + } + return { + pub: idty.pub, + uid: idty.uid, + sig: idty.sig, + created_on: idty.created_on, } - const membership = await this.mindexDAL.getReducedMS(pubkey) - idty.revoked_on = membership.revoked_on - return idty; } - async getWrittenIdtyByUID(uid:string) { - const idty = await this.iindexDAL.getFromUID(uid) + async getWrittenIdtyByPubkeyForUidAndMemberAndCreatedOn(pubkey:string): Promise<{ uid:string, member:boolean, created_on:string }|null> { + const idty = await this.iindexDAL.getFromPubkey(pubkey) if (!idty) { - return null; + return null + } + return { + uid: idty.uid, + member: idty.member, + created_on: idty.created_on, } - const membership = await this.mindexDAL.getReducedMS(idty.pub) - idty.revoked_on = membership.revoked_on - return idty; } - async fillInMembershipsOfIdentity(queryPromise:Promise<DBIdentity>) { - try { - const idty:any = await Promise.resolve(queryPromise) - if (idty) { - const mss = await this.msDAL.getMembershipsOfIssuer(idty.pubkey); - const mssFromMindex = await this.mindexDAL.reducable(idty.pubkey); - idty.memberships = mss.concat(mssFromMindex.map((ms:MindexEntry) => { - const sp = ms.created_on.split('-'); - return { - blockstamp: ms.created_on, - membership: ms.leaving ? 'OUT' : 'IN', - number: sp[0], - fpr: sp[1], - written_number: parseInt(ms.written_on) - } - })); - return idty; - } - } catch (err) { - logger.error(err); + private async getWrittenForSureIdtyByPubkey(pubkey:string) { + const idty = await this.iindexDAL.getFromPubkey(pubkey) + if (!idty) { + throw Error(DataErrors[DataErrors.MEMBER_NOT_FOUND]) } - return null; + return idty + } + + private async getWrittenForSureIdtyByUid(pubkey:string) { + const idty = (await this.iindexDAL.getFullFromUID(pubkey)) + if (!idty) { + throw Error(DataErrors[DataErrors.MEMBER_NOT_FOUND]) + } + return idty + } + + // Duniter-UI dependency + async getWrittenIdtyByPubkey(pub:string): Promise<FullIindexEntry | null> { + return await this.iindexDAL.getFromPubkey(pub) + } + + async getWrittenIdtyByPubkeyForExistence(uid:string) { + return !!(await this.iindexDAL.getFromPubkey(uid)) + } + + async getWrittenIdtyByUIDForExistence(uid:string) { + return !!(await this.iindexDAL.getFromUID(uid)) + } + + async getWrittenIdtyByUidForHashing(uid:string): Promise<{ uid:string, created_on:string, pub:string }> { + return this.getWrittenForSureIdtyByUid(uid) + } + + async getWrittenIdtyByUIDForWotbId(uid:string): Promise<{ wotb_id:number }> { + return this.getWrittenForSureIdtyByUid(uid) } async findPeersWhoseHashIsIn(hashes:string[]) { const peers = await this.peerDAL.listAll(); - return _.chain(peers).filter((p:DBPeer) => hashes.indexOf(p.hash) !== -1).value(); + return Underscore.chain(peers).filter((p:DBPeer) => hashes.indexOf(p.hash) !== -1).value() } getTxByHash(hash:string) { @@ -376,15 +718,15 @@ export class FileDAL { async getNonWritten(pubkey:string) { const pending = await this.idtyDAL.getPendingIdentities(); - return _.chain(pending).where({pubkey: pubkey}).value(); + return Underscore.chain(pending).where({pubkey: pubkey}).value() } async getRevocatingMembers() { const revoking = await this.idtyDAL.getToRevoke(); const toRevoke = []; for (const pending of revoking) { - const idty = await this.getWrittenIdtyByPubkey(pending.pubkey); - if (!idty.revoked_on) { + const idty = await this.getWrittenIdtyByPubkeyForRevocationCheck(pending.pubkey) + if (idty && !idty.revoked_on) { toRevoke.push(pending); } } @@ -399,21 +741,21 @@ export class FileDAL { return this.mindexDAL.getRevokedPubkeys() } - async searchJustIdentities(search:string) { + async searchJustIdentities(search:string): Promise<DBIdentity[]> { const pendings = await this.idtyDAL.searchThoseMatching(search); const writtens = await this.iindexDAL.searchThoseMatching(search); - const nonPendings = _.filter(writtens, (w:IindexEntry) => { - return _.where(pendings, { pubkey: w.pub }).length == 0; + const nonPendings = Underscore.filter(writtens, (w:IindexEntry) => { + return Underscore.where(pendings, { pubkey: w.pub }).length == 0; }); const found = pendings.concat(nonPendings.map((i:any) => { // Use the correct field i.pubkey = i.pub return i })); - return await Promise.all(found.map(async (f:any) => { + return await Promise.all<DBIdentity>(found.map(async (f:any) => { const ms = await this.mindexDAL.getReducedMS(f.pub); if (ms) { - f.revoked_on = ms.revoked_on ? parseInt(ms.revoked_on) : null; + f.revoked_on = ms.revoked_on ? ms.revoked_on : null; f.revoked = !!f.revoked_on; f.revocation_sig = ms.revocation || null; } @@ -426,19 +768,9 @@ export class FileDAL { const links = await this.cindexDAL.getValidLinksTo(pub); let matching = certs; await Promise.all(links.map(async (entry:any) => { - entry.from = entry.issuer; - const wbt = entry.written_on.split('-'); - const blockNumber = parseInt(entry.created_on); // created_on field of `c_index` does not have the full blockstamp - const basedBlock = await this.getBlock(blockNumber); - entry.block = blockNumber; - entry.block_number = blockNumber; - entry.block_hash = basedBlock ? basedBlock.hash : null; - entry.linked = true; - entry.written_block = parseInt(wbt[0]); - entry.written_hash = wbt[1]; - matching.push(entry); + matching.push(await this.cindexEntry2DBCert(entry)) })) - matching = _.sortBy(matching, (c:DBCert) => -c.block); + matching = Underscore.sortBy(matching, (c:DBCert) => -c.block); matching.reverse(); return matching; } @@ -447,26 +779,36 @@ export class FileDAL { const certs = await this.certDAL.getFromPubkeyCerts(pubkey); const links = await this.cindexDAL.getValidLinksFrom(pubkey); let matching = certs; - await Promise.all(links.map(async (entry:any) => { - const idty = await this.getWrittenIdtyByPubkey(entry.receiver); - entry.from = entry.issuer; - entry.to = entry.receiver; - const cbt = entry.created_on.split('-'); - const wbt = entry.written_on.split('-'); - entry.block = parseInt(cbt[0]); - entry.block_number = parseInt(cbt[0]); - entry.block_hash = cbt[1]; - entry.target = idty.hash; - entry.linked = true; - entry.written_block = parseInt(wbt[0]); - entry.written_hash = wbt[1]; - matching.push(entry); + await Promise.all(links.map(async (entry:CindexEntry) => { + matching.push(await this.cindexEntry2DBCert(entry)) })) - matching = _.sortBy(matching, (c:DBCert) => -c.block); + matching = Underscore.sortBy(matching, (c:DBCert) => -c.block); matching.reverse(); return matching; } + async cindexEntry2DBCert(entry:CindexEntry): Promise<DBCert> { + const idty = await this.getWrittenIdtyByPubkeyForHash(entry.receiver) + const wbt = entry.written_on.split('-') + const block = (await this.blockDAL.getBlock(entry.created_on)) as DBBlock + return { + issuers: [entry.issuer], + linked: true, + written: true, + written_block: parseInt(wbt[0]), + written_hash: wbt[1], + sig: entry.sig, + block_number: block.number, + block_hash: block.hash, + target: idty.hash, + to: entry.receiver, + from: entry.issuer, + block: block.number, + expired: !!entry.expired_on, + expires_on: entry.expires_on, + } + } + async isSentry(pubkey:string, conf:ConfDTO) { const current = await this.getCurrentBlockOrNull(); if (current) { @@ -480,12 +822,12 @@ export class FileDAL { async certsFindNew() { const certs = await this.certDAL.getNotLinked(); - return _.chain(certs).where({linked: false}).sortBy((c:DBCert) => -c.block).value(); + return Underscore.chain(certs).where({linked: false}).sortBy((c:DBCert) => -c.block).value() } async certsNotLinkedToTarget(hash:string) { const certs = await this.certDAL.getNotLinkedToTarget(hash); - return _.chain(certs).sortBy((c:any) => -c.block).value(); + return Underscore.chain(certs).sortBy((c:any) => -c.block).value(); } async getMostRecentMembershipNumberForIssuer(issuer:string) { @@ -500,36 +842,34 @@ export class FileDAL { async lastJoinOfIdentity(target:string) { let pending = await this.msDAL.getPendingINOfTarget(target); - return _(pending).sortBy((ms:any) => -ms.number)[0]; + return Underscore.sortBy(pending, (ms:any) => -ms.number)[0]; } - async findNewcomers(blockMedianTime = 0) { + async findNewcomers(blockMedianTime = 0): Promise<DBMembership[]> { const pending = await this.msDAL.getPendingIN() - const mss = await Promise.all(pending.map(async (p:any) => { + const mss: DBMembership[] = await Promise.all<DBMembership>(pending.map(async (p:any) => { const reduced = await this.mindexDAL.getReducedMS(p.issuer) if (!reduced || !reduced.chainable_on || blockMedianTime >= reduced.chainable_on || blockMedianTime < constants.TIME_TO_TURN_ON_BRG_107) { return p } return null })) - return _.chain(mss) - .filter((ms:any) => ms) - .sortBy((ms:any) => -ms.sigDate) + return Underscore.chain(Underscore.filter(mss, ms => !!ms) as DBMembership[]) + .sortBy((ms:DBMembership) => -ms.blockNumber) .value() } - async findLeavers(blockMedianTime = 0) { + async findLeavers(blockMedianTime = 0): Promise<DBMembership[]> { const pending = await this.msDAL.getPendingOUT(); - const mss = await Promise.all(pending.map(async (p:any) => { + const mss = await Promise.all<DBMembership|null>(pending.map(async p => { const reduced = await this.mindexDAL.getReducedMS(p.issuer) if (!reduced || !reduced.chainable_on || blockMedianTime >= reduced.chainable_on || blockMedianTime < constants.TIME_TO_TURN_ON_BRG_107) { return p } return null })) - return _.chain(mss) - .filter((ms:any) => ms) - .sortBy((ms:any) => -ms.sigDate) + return Underscore.chain(Underscore.filter(mss, ms => !!ms) as DBMembership[]) + .sortBy(ms => -ms.blockNumber) .value(); } @@ -537,8 +877,12 @@ export class FileDAL { return this.cindexDAL.existsNonReplayableLink(from, to) } - getSource(identifier:string, pos:number) { - return this.sindexDAL.getSource(identifier, pos) + async getSource(identifier:string, pos:number, isDividend: boolean): Promise<SimpleTxInput | null> { + if (isDividend) { + return this.dividendDAL.getUDSource(identifier, pos) + } else { + return this.sindexDAL.getTxSource(identifier, pos) + } } async isMember(pubkey:string):Promise<boolean> { @@ -586,15 +930,17 @@ export class FileDAL { } async setRevoked(pubkey:string) { - const idty = await this.getWrittenIdtyByPubkey(pubkey); - idty.revoked = true; - return await this.idtyDAL.saveIdentity(idty); + return await this.idtyDAL.setRevoked(pubkey) } - setRevocating = (existing:DBIdentity, revocation_sig:string) => { - existing.revocation_sig = revocation_sig; - existing.revoked = false; - return this.idtyDAL.saveIdentity(existing); + setRevocating = (idty:BasicRevocableIdentity, revocation_sig:string) => { + const dbIdentity = IdentityDTO.fromBasicIdentity(idty) + dbIdentity.member = idty.member + dbIdentity.wasMember = idty.wasMember + dbIdentity.expires_on = idty.expires_on + dbIdentity.revocation_sig = revocation_sig + dbIdentity.revoked = false + return this.idtyDAL.saveIdentity(dbIdentity) } async getPeerOrNull(pubkey:string) { @@ -621,17 +967,17 @@ export class FileDAL { async listAllPeersWithStatusNewUP() { const peers = await this.peerDAL.listAll(); - return _.chain(peers) + return Underscore.chain(peers) .filter((p:DBPeer) => ['UP'] .indexOf(p.status) !== -1).value(); } async listAllPeersWithStatusNewUPWithtout(pub:string) { const peers = await this.peerDAL.listAll(); - return _.chain(peers).filter((p:DBPeer) => p.status == 'UP').filter((p:DBPeer) => p.pubkey !== pub).value(); + return Underscore.chain(peers).filter((p:DBPeer) => p.status == 'UP').filter((p:DBPeer) => p.pubkey !== pub).value(); } - async findPeers(pubkey:string) { + async findPeers(pubkey:string): Promise<DBPeer[]> { try { const peer = await this.getPeer(pubkey); return [peer]; @@ -640,9 +986,9 @@ export class FileDAL { } } - async getRandomlyUPsWithout(pubkeys:string[]) { + async getRandomlyUPsWithout(pubkeys:string[]): Promise<DBPeer[]> { const peers = await this.listAllPeersWithStatusNewUP(); - return peers.filter((peer:DBPeer) => pubkeys.indexOf(peer.pubkey) == -1); + return peers.filter(peer => pubkeys.indexOf(peer.pubkey) == -1) } async setPeerUP(pubkey:string) { @@ -677,27 +1023,29 @@ export class FileDAL { } } - async saveBlock(block:BlockDTO) { - const dbb = DBBlock.fromBlockDTO(block) + async saveBlock(dbb:DBBlock) { dbb.wrong = false; await Promise.all([ this.saveBlockInFile(dbb), - this.saveTxsInFiles(block.transactions, block.number, block.medianTime) + this.saveTxsInFiles(dbb.transactions, dbb.number, dbb.medianTime) ]) } - async generateIndexes(block:DBBlock, conf:ConfDTO, index:IndexEntry[], HEAD:DBHead) { + async generateIndexes(block:BlockDTO, conf:ConfDTO, index:IndexEntry[], aHEAD:DBHead|null) { // We need to recompute the indexes for block#0 - if (!index || !HEAD || HEAD.number == 0) { + let HEAD:DBHead + if (!index || !aHEAD || aHEAD.number == 0) { index = indexer.localIndex(block, conf) HEAD = await indexer.completeGlobalScope(block, conf, index, this) + } else { + HEAD = aHEAD } let mindex = indexer.mindex(index); let iindex = indexer.iindex(index); let sindex = indexer.sindex(index); let cindex = indexer.cindex(index); - sindex = sindex.concat(await indexer.ruleIndexGenDividend(HEAD, iindex, this)); - sindex = sindex.concat(await indexer.ruleIndexGarbageSmallAccounts(HEAD, sindex, this)); + const dividends = await indexer.ruleIndexGenDividend(HEAD, iindex, this) // Requires that newcomers are already in DividendDAO + sindex = sindex.concat(await indexer.ruleIndexGarbageSmallAccounts(HEAD, sindex, dividends, this)); cindex = cindex.concat(await indexer.ruleIndexGenCertificationExpiry(HEAD, this)); mindex = mindex.concat(await indexer.ruleIndexGenMembershipExpiry(HEAD, this)); iindex = iindex.concat(await indexer.ruleIndexGenExclusionByMembership(HEAD, mindex, this)); @@ -705,32 +1053,36 @@ export class FileDAL { mindex = mindex.concat(await indexer.ruleIndexGenImplicitRevocation(HEAD, this)); await indexer.ruleIndexCorrectMembershipExpiryDate(HEAD, mindex, this); await indexer.ruleIndexCorrectCertificationExpiryDate(HEAD, cindex, this); - return { HEAD, mindex, iindex, sindex, cindex }; + return { HEAD, mindex, iindex, sindex, cindex, dividends }; } async updateWotbLinks(cindex:CindexEntry[]) { for (const entry of cindex) { - const from = await this.getWrittenIdtyByPubkey(entry.issuer); - const to = await this.getWrittenIdtyByPubkey(entry.receiver); + const from = await this.getWrittenIdtyByPubkeyForWotbID(entry.issuer); + const to = await this.getWrittenIdtyByPubkeyForWotbID(entry.receiver); if (entry.op == CommonConstants.IDX_CREATE) { + // NewLogger().trace('addLink %s -> %s', from.wotb_id, to.wotb_id) this.wotb.addLink(from.wotb_id, to.wotb_id); } else { // Update = removal + NewLogger().trace('removeLink %s -> %s', from.wotb_id, to.wotb_id) this.wotb.removeLink(from.wotb_id, to.wotb_id); } } } async trimIndexes(maxNumber:number) { - await this.bindexDAL.trimBlocks(maxNumber); - await this.iindexDAL.trimRecords(maxNumber); - await this.mindexDAL.trimRecords(maxNumber); - await this.cindexDAL.trimExpiredCerts(maxNumber); - await this.sindexDAL.trimConsumedSource(maxNumber); - return true; + if (!cliprogram.notrim) { + await this.bindexDAL.trimBlocks(maxNumber) + await this.iindexDAL.trimRecords(maxNumber) + await this.mindexDAL.trimRecords(maxNumber) + await this.cindexDAL.trimExpiredCerts(maxNumber) + } + await this.sindexDAL.trimConsumedSource(maxNumber) + await this.dividendDAL.trimConsumedUDs(maxNumber) } - async trimSandboxes(block:DBBlock) { + async trimSandboxes(block:{ medianTime: number }) { await this.certDAL.trimExpiredCerts(block.medianTime); await this.msDAL.trimExpiredMemberships(block.medianTime); await this.idtyDAL.trimExpiredIdentities(block.medianTime); @@ -753,7 +1105,8 @@ export class FileDAL { async saveTxsInFiles(txs:TransactionDTO[], block_number:number, medianTime:number) { return Promise.all(txs.map(async (tx) => { const sp = tx.blockstamp.split('-'); - tx.blockstampTime = (await this.getBlockByNumberAndHash(parseInt(sp[0]), sp[1])).medianTime; + const basedBlock = (await this.getAbsoluteBlockByNumberAndHash(parseInt(sp[0]), sp[1])) as DBBlock + tx.blockstampTime = basedBlock.medianTime; const txEntity = TransactionDTO.fromJSONObject(tx) txEntity.computeAllHashes(); return this.txsDAL.addLinked(TransactionDTO.fromJSONObject(txEntity), block_number, medianTime); @@ -768,14 +1121,6 @@ export class FileDAL { return merkle; } - removeAllSourcesOfBlock(blockstamp:string) { - return this.sindexDAL.removeBlock(blockstamp) - } - - updateTransactions(txs:DBTx[]) { - return this.txsDAL.insertBatchOfTxs(txs) - } - savePendingIdentity(idty:DBIdentity) { return this.idtyDAL.saveIdentity(idty) } @@ -827,13 +1172,19 @@ export class FileDAL { return history; } - async getUDHistory(pubkey:string) { - const sources = await this.sindexDAL.getUDSources(pubkey) + async getUDHistory(pubkey:string): Promise<{ history: HttpUD[] }> { + const sources: UDSource[] = await this.dividendDAL.getUDSources(pubkey) return { - history: sources.map((src:SindexEntry) => _.extend({ - block_number: src.pos, - time: src.written_time - }, src)) + history: (await Promise.all<HttpUD>(sources.map(async (src) => { + const block = await this.getBlockWeHaveItForSure(src.pos) + return { + block_number: src.pos, + time: block.medianTime, + consumed: src.consumed, + amount: src.amount, + base: src.base + } + }))) } } @@ -842,11 +1193,11 @@ export class FileDAL { } async getUniqueIssuersBetween(start:number, end:number) { - const current = await this.blockDAL.getCurrent(); + const current = (await this.blockDAL.getCurrent()) as DBBlock const firstBlock = Math.max(0, start); const lastBlock = Math.max(0, Math.min(current.number, end)); const blocks = await this.blockDAL.getBlocks(firstBlock, lastBlock); - return _.chain(blocks).pluck('issuer').uniq().value(); + return Underscore.uniq(blocks.map(b => b.issuer)) } /** @@ -884,11 +1235,11 @@ export class FileDAL { async loadConf(overrideConf:ConfDTO, defaultConf = false) { let conf = ConfDTO.complete(overrideConf || {}); if (!defaultConf) { - const savedConf = await this.confDAL.loadConf(); - const savedProxyConf = _(savedConf.proxyConf).extend({}); - conf = _(savedConf).extend(overrideConf || {}); + const savedConf = await this.confDAL.loadConf() + const savedProxyConf = Underscore.extend(savedConf.proxyConf, {}) + conf = Underscore.extend(savedConf, overrideConf || {}) if (overrideConf.proxiesConf !== undefined) {} else { - conf.proxyConf = _(savedProxyConf).extend({}); + conf.proxyConf = Underscore.extend(savedProxyConf, {}) } } if (this.loadConfHook) { @@ -938,11 +1289,11 @@ export class FileDAL { } async cleanCaches() { - await _.values(this.newDals).map((dal:any) => dal.cleanCache && dal.cleanCache()) + await Underscore.values(this.newDals).map((dal:Initiable) => dal.cleanCache && dal.cleanCache()) } async close() { - await _.values(this.newDals).map((dal:any) => dal.cleanCache && dal.cleanCache()) + await Underscore.values(this.newDals).map((dal:Initiable) => dal.cleanCache && dal.cleanCache()) return this.sqliteDriver.closeConnection(); } @@ -974,4 +1325,41 @@ export class FileDAL { } }) } + + async findReceiversAbove(minsig: number) { + const receiversAbove:string[] = await this.cindexDAL.getReceiversAbove(minsig) + const members:IdentityForRequirements[] = [] + for (const r of receiversAbove) { + const i = await this.iindexDAL.getFullFromPubkey(r) + members.push({ + hash: i.hash || "", + member: i.member || false, + wasMember: i.wasMember || false, + pubkey: i.pub, + uid: i.uid || "", + buid: i.created_on || "", + sig: i.sig || "", + revocation_sig: "", + revoked: false, + revoked_on: 0 + }) + } + return members + } + + @MonitorFlushedIndex() + async flushIndexes(indexes: IndexBatch) { + await this.mindexDAL.insertBatch(indexes.mindex) + await this.iindexDAL.insertBatch(indexes.iindex) + await this.sindexDAL.insertBatch(indexes.sindex.filter(s => s.srcType === 'T')) // We don't store dividends in SINDEX + await this.cindexDAL.insertBatch(indexes.cindex) + await this.dividendDAL.consume(indexes.sindex.filter(s => s.srcType === 'D')) + } + + async updateDividend(blockNumber: number, dividend: number|null, unitbase: number, local_iindex: IindexEntry[]): Promise<SimpleUdEntryForWallet[]> { + if (dividend) { + return this.dividendDAL.produceDividend(blockNumber, dividend, unitbase, local_iindex) + } + return [] + } } diff --git a/app/lib/dal/fileDALs/AbstractCFS.ts b/app/lib/dal/fileDALs/AbstractCFS.ts index c03ccb0e5e75909760baa9e24cf3273af245bd58..128a441af4ad55135ca8425267ff347a4388e713 100644 --- a/app/lib/dal/fileDALs/AbstractCFS.ts +++ b/app/lib/dal/fileDALs/AbstractCFS.ts @@ -12,13 +12,20 @@ // GNU Affero General Public License for more details. import {CFSCore} from "./CFSCore"; +import {Initiable} from "../sqliteDAL/Initiable" +import {FileSystem} from "../../system/directory" +import {FileDAL} from "../fileDAL" -export class AbstractCFS { +export abstract class AbstractCFS extends Initiable { - protected coreFS:CFSCore - protected dal:any + public coreFS:CFSCore + protected dal:FileDAL - constructor(rootPath:string, qioFS:any) { + constructor(rootPath:string, qioFS:FileSystem) { + super() this.coreFS = new CFSCore(rootPath, qioFS) } + + cleanCache() { + } } diff --git a/app/lib/dal/fileDALs/CFSCore.ts b/app/lib/dal/fileDALs/CFSCore.ts index 0de15b707aeac9ac09001b410647699bd4ffd810..019155abe5149360d5199937a4de0f9a48732919 100644 --- a/app/lib/dal/fileDALs/CFSCore.ts +++ b/app/lib/dal/fileDALs/CFSCore.ts @@ -13,7 +13,9 @@ "use strict"; -const _ = require('underscore'); +import {FileSystem} from "../../system/directory" +import {Underscore} from "../../common-libs/underscore" + const path = require('path'); const DEEP_WRITE = true; @@ -24,7 +26,7 @@ export class CFSCore { private deletionFolderPromise: Promise<any> | null private createDeletionFolder: () => Promise<any> | null - constructor(private rootPath:string, private qfs:any) { + constructor(private rootPath:string, private qfs:FileSystem) { this.deletedFolder = path.join(rootPath, '.deleted') this.deletionFolderPromise = null @@ -42,12 +44,12 @@ export class CFSCore { */ async read(filePath:string): Promise<string | null> { try { - const isDeleted = await this.qfs.exists(path.join(this.deletedFolder, this.toRemoveFileName(filePath))); + const isDeleted = await this.qfs.fsExists(path.join(this.deletedFolder, this.toRemoveFileName(filePath))); if (isDeleted) { // A deleted file must be considered non-existant return null; } - return await this.qfs.read(path.join(this.rootPath, filePath)); + return await this.qfs.fsReadFile(path.join(this.rootPath, filePath)); } catch (e) { return null } @@ -60,12 +62,12 @@ export class CFSCore { */ async exists(filePath:string): Promise<boolean | null> { try { - const isDeleted = await this.qfs.exists(path.join(this.deletedFolder, this.toRemoveFileName(filePath))); + const isDeleted = await this.qfs.fsExists(path.join(this.deletedFolder, this.toRemoveFileName(filePath))); if (isDeleted) { // A deleted file must be considered non-existant return false; } - return await this.qfs.exists(path.join(this.rootPath, filePath)) + return await this.qfs.fsExists(path.join(this.rootPath, filePath)) } catch (e) { return null } @@ -80,19 +82,19 @@ export class CFSCore { async list(ofPath:string): Promise<string[]> { const dirPath = path.normalize(ofPath); let files: string[] = [], folder = path.join(this.rootPath, dirPath); - const hasDir = await this.qfs.exists(folder); + const hasDir = await this.qfs.fsExists(folder); if (hasDir) { - files = files.concat(await this.qfs.list(folder)); + files = files.concat(await this.qfs.fsList(folder)); } - const hasDeletedFiles = await this.qfs.exists(this.deletedFolder); + const hasDeletedFiles = await this.qfs.fsExists(this.deletedFolder); if (hasDeletedFiles) { - const deletedFiles = await this.qfs.list(this.deletedFolder); + const deletedFiles = await this.qfs.fsList(this.deletedFolder); const deletedOfThisPath = deletedFiles.filter((f:string) => f.match(new RegExp('^' + this.toRemoveDirName(dirPath)))); const locallyDeletedFiles = deletedOfThisPath.map((f:string) => f.replace(this.toRemoveDirName(dirPath), '') .replace(/^__/, '')); - files = _.difference(files, locallyDeletedFiles); + files = Underscore.difference(files, locallyDeletedFiles) } - return _.uniq(files); + return Underscore.uniq(files) }; /** @@ -102,7 +104,7 @@ export class CFSCore { * @param deep Wether to make a deep write or not. */ async write(filePath:string, content:string, deep:boolean): Promise<void> { - return this.qfs.write(path.join(this.rootPath, filePath), content); + return this.qfs.fsWrite(path.join(this.rootPath, filePath), content); }; /** @@ -111,10 +113,10 @@ export class CFSCore { * @param deep Wether to remove the file in the root core or not. * @returns {*} Promise of removal. */ - async remove(filePath:string, deep:boolean): Promise<void> { + async remove(filePath:string, deep = false): Promise<void> { // Make a deep physical deletion // Root core: physical deletion - return this.qfs.remove(path.join(this.rootPath, filePath)); + await this.qfs.fsUnlink(path.join(this.rootPath, filePath)); } /** @@ -138,9 +140,9 @@ export class CFSCore { let folder = this.rootPath; for (let i = 0, len = folders.length; i < len; i++) { folder = folder ? path.join(folder, folders[i]) : folders[i]; - let exists = await this.qfs.exists(folder); + let exists = await this.qfs.fsExists(folder); if (!exists) { - await this.qfs.makeDirectory(folder); + await this.qfs.fsMakeDirectory(folder); } } } catch (e) { @@ -225,4 +227,8 @@ export class CFSCore { private toRemoveFileName(filePath:string) { return path.normalize(filePath).replace(/\//g, '__').replace(/\\/g, '__'); } + + fsStreamTo(filename: string, iterator: IterableIterator<string>) { + return this.qfs.fsStreamTo(path.join(this.rootPath, filename), iterator) + } } diff --git a/app/lib/dal/fileDALs/ConfDAL.ts b/app/lib/dal/fileDALs/ConfDAL.ts index 1fa90e1482b5fe29c177c819cea9c0df9dce8a43..f42197b7ef4a9200bf0909f268cee68df822a465 100644 --- a/app/lib/dal/fileDALs/ConfDAL.ts +++ b/app/lib/dal/fileDALs/ConfDAL.ts @@ -14,14 +14,14 @@ import {AbstractCFS} from "./AbstractCFS" import {ConfDTO} from "../../dto/ConfDTO" import {CommonConstants} from "../../common-libs/constants"; - -const _ = require('underscore'); +import {FileSystem} from "../../system/directory" +import {Underscore} from "../../common-libs/underscore" export class ConfDAL extends AbstractCFS { private logger:any - constructor(rootPath:string, qioFS:any) { + constructor(rootPath:string, qioFS:FileSystem) { super(rootPath, qioFS) this.logger = require('../../logger').NewLogger() } @@ -61,7 +61,7 @@ export class ConfDAL extends AbstractCFS { async loadConf() { const data = await this.coreFS.readJSON('conf.json'); if (data) { - return _(ConfDTO.defaultConf()).extend(data); + return Underscore.extend(ConfDTO.defaultConf(), data) } else { // Silent error this.logger.warn('No configuration loaded'); diff --git a/app/lib/dal/fileDALs/PowDAL.ts b/app/lib/dal/fileDALs/PowDAL.ts index eaba6594d4243fc2b7bec5551887c167a54fc527..095bb9f4117f02b8d6b38f95dbf9eece8bfb3eba 100644 --- a/app/lib/dal/fileDALs/PowDAL.ts +++ b/app/lib/dal/fileDALs/PowDAL.ts @@ -12,12 +12,13 @@ // GNU Affero General Public License for more details. import {AbstractCFS} from "./AbstractCFS" +import {FileSystem} from "../../system/directory" export class PowDAL extends AbstractCFS { private static POW_FILE = "pow.txt" - constructor(rootPath:string, qioFS:any) { + constructor(rootPath:string, qioFS:FileSystem) { super(rootPath, qioFS) } diff --git a/app/lib/dal/fileDALs/StatDAL.ts b/app/lib/dal/fileDALs/StatDAL.ts index 1e913c5fe57558c051c3fd611de5d143ed264fc2..96f03e5444b18cc43e31047097606a056e105b79 100644 --- a/app/lib/dal/fileDALs/StatDAL.ts +++ b/app/lib/dal/fileDALs/StatDAL.ts @@ -12,12 +12,12 @@ // GNU Affero General Public License for more details. import {AbstractCFS} from "./AbstractCFS"; -import {CFSCore} from "./CFSCore"; -const _ = require('underscore'); +import {FileSystem} from "../../system/directory" +import {Underscore} from "../../common-libs/underscore" export class StatDAL extends AbstractCFS { - constructor(rootPath:string, qioFS:any) { + constructor(rootPath:string, qioFS:FileSystem) { super(rootPath, qioFS) } @@ -39,7 +39,7 @@ export class StatDAL extends AbstractCFS { async pushStats(statsToPush:any) { const stats = (await this.loadStats()) || {}; - _.keys(statsToPush).forEach(function(statName:string){ + Underscore.keys(statsToPush).forEach(function(statName:string){ if (!stats[statName]) { stats[statName] = { blocks: [] }; } diff --git a/app/lib/dal/indexDAL/CFSBlockchainArchive.ts b/app/lib/dal/indexDAL/CFSBlockchainArchive.ts new file mode 100644 index 0000000000000000000000000000000000000000..361315590e00ef203f419e12c7162ff34484c32a --- /dev/null +++ b/app/lib/dal/indexDAL/CFSBlockchainArchive.ts @@ -0,0 +1,123 @@ +import {BlockchainArchiveDAO, BlockLike} from "./abstract/BlockchainArchiveDAO" +import {CFSCore} from "../fileDALs/CFSCore" + +export class CFSBlockchainArchive<T extends BlockLike> implements BlockchainArchiveDAO<T> { + + constructor(private cfs:CFSCore, private _chunkSize:number) { + } + + async archive(records: T[]): Promise<number> { + if (!this.checkBlocksRepresentChunks(records)) { + return 0 + } + if (!this.checkBlocksAreWellChained(records)) { + return 0 + } + const chunks = this.splitIntoChunks(records) + for (const c of chunks) { + const fileName = this.getFileName(c[0].number) + await this.cfs.writeJSON(fileName, c) + } + return chunks.length + } + + private checkBlocksRepresentChunks(records: BlockLike[]): boolean { + return !(records[0].number % this._chunkSize !== 0 || (records[records.length - 1].number + 1) % this._chunkSize !== 0) + + } + + private checkBlocksAreWellChained(records: T[]): boolean { + let previous:BlockLike = { + number: records[0].number - 1, + hash: records[0].previousHash, + previousHash: '' + } + for (const b of records) { + if (b.previousHash !== previous.hash || b.number !== previous.number + 1) { + return false + } + previous = b + } + return true + } + + private splitIntoChunks(records: T[]): T[][] { + const nbChunks = records.length / this._chunkSize + const chunks: T[][] = [] + for (let i = 0; i < nbChunks; i++) { + chunks.push(records.slice(i * this._chunkSize, (i + 1) * this._chunkSize)) + } + return chunks + } + + async getBlock(number: number, hash: string): Promise<T|null> { + const block = await this.getBlockByNumber(number) + if (!block) { + return null + } + return block.hash === hash ? block : null + } + + async getBlockByNumber(number: number): Promise<T|null> { + if (number < 0) { + return null + } + const content = await this.getChunk(number) + if (!content) { + // The block's chunk is not archived + return null + } + return content[this.getPositionInChunk(number)] + } + + async getChunk(number:number): Promise<(T[])|null> { + const file = this.getFileName(number) + return this.cfs.readJSON(file) + } + + async getLastSavedBlock(): Promise<T | null> { + const list = await this.cfs.list('/') + const max = list + .map(f => f.replace(`chunk_`, '')) + .map(f => f.replace(`-${this._chunkSize}.json`, '')) + .map(f => parseInt(f)) + .reduce((v, max) => { + return Math.max(v, max) + }, 0) + const content = await this.getChunk(max * this._chunkSize) + if (!content) { + return null + } + return this.getBlock(content[content.length - 1].number, content[content.length - 1].hash) + } + + private getFileName(number:number) { + const rest = number % this._chunkSize + const chunk = (number - rest) / this._chunkSize + return CFSBlockchainArchive.getChunkName(chunk, this._chunkSize) + } + + private static getChunkName(chunkNumber:number, chunkSize:number) { + return `chunk_${chunkNumber}-${chunkSize}.json` + } + + private getPositionInChunk(number:number) { + return number % this._chunkSize + } + + async init(): Promise<void> { + return this.cfs.makeTree('/') + } + + triggerInit(): void { + // TODO: remove triggerInit from all the DAOs, it is a wrong implementation + } + + cleanCache(): void { + // TODO: is it really useful? + } + + get chunkSize(): number { + return this._chunkSize + } +} diff --git a/app/lib/dal/indexDAL/abstract/BIndexDAO.ts b/app/lib/dal/indexDAL/abstract/BIndexDAO.ts new file mode 100644 index 0000000000000000000000000000000000000000..8ca8ff55a0f6b0d737a7000b8fcf07ea44ae551b --- /dev/null +++ b/app/lib/dal/indexDAL/abstract/BIndexDAO.ts @@ -0,0 +1,13 @@ +import {GenericDAO} from "./GenericDAO" +import {DBHead} from "../../../db/DBHead" + +export interface BIndexDAO extends GenericDAO<DBHead> { + + head(n:number): Promise<DBHead> // TODO: possibly null? + + tail(): Promise<DBHead> // TODO: possibly null? + + range(n:number, m:number): Promise<DBHead[]> + + trimBlocks(maxnumber:number): Promise<void> +} diff --git a/app/lib/dal/indexDAL/abstract/BlockchainArchiveDAO.ts b/app/lib/dal/indexDAL/abstract/BlockchainArchiveDAO.ts new file mode 100644 index 0000000000000000000000000000000000000000..123c169ea4c9aa6d44d9f33e6e7430ea9d0e27d5 --- /dev/null +++ b/app/lib/dal/indexDAL/abstract/BlockchainArchiveDAO.ts @@ -0,0 +1,48 @@ +import {Initiable} from "../../sqliteDAL/Initiable" +import {DBBlock} from "../../../db/DBBlock" + +export interface BlockLike { + number:number + hash:string + previousHash:string +} + +export interface BlockchainArchiveDAO<T extends BlockLike> extends Initiable { + + /** + * Trigger the initialization of the DAO. Called when the underlying DB is ready. + */ + triggerInit(): void + + /** + * Retrieves a block from the archives. + * @param {number} number Block number. + * @param {string} hash Block hash. + * @returns {Promise<DBBlock>} + */ + getBlock(number:number, hash:string): Promise<T|null> + + /** + * Retrieves a block from the archives, without checking the hash. + * @param {number} number Block number. + * @returns {Promise<DBBlock>} + */ + getBlockByNumber(number:number): Promise<T|null> + + /** + * Archives a suite of blocks. + * + * Throws an exception is blocks does not follow each other, or does not follow previously archived blocks. + * @param {DBBlock[]} records The blocks to archive. + * @returns {Promise<void>} + */ + archive(records:T[]): Promise<number> + + /** + * Retrieve the last block (maximum number) that was archived. + * @returns {Promise<BlockLike | null>} + */ + getLastSavedBlock(): Promise<T|null> + + readonly chunkSize:number +} diff --git a/app/lib/dal/indexDAL/abstract/BlockchainDAO.ts b/app/lib/dal/indexDAL/abstract/BlockchainDAO.ts new file mode 100644 index 0000000000000000000000000000000000000000..47a5927b5c119e7bc9c283e12248e8b37f8e0dfe --- /dev/null +++ b/app/lib/dal/indexDAL/abstract/BlockchainDAO.ts @@ -0,0 +1,43 @@ +import {GenericDAO} from "./GenericDAO" +import {DBBlock} from "../../../db/DBBlock" + +export interface BlockchainDAO extends GenericDAO<DBBlock> { + + getCurrent(): Promise<DBBlock|null> + + getBlock(number:string | number): Promise<DBBlock|null> + + getAbsoluteBlock(number:number, hash:string): Promise<DBBlock|null> + + saveBlock(block:DBBlock): Promise<DBBlock> + + saveSideBlock(block:DBBlock): Promise<DBBlock> + + getPotentialRoots(): Promise<DBBlock[]> + + getBlocks(start:number, end:number): Promise<DBBlock[]> + + getNextForkBlocks(number:number, hash:string): Promise<DBBlock[]> + + getPotentialForkBlocks(numberStart:number, medianTimeStart:number, maxNumber:number): Promise<DBBlock[]> + + lastBlockOfIssuer(issuer:string): Promise<DBBlock|null> + + lastBlockWithDividend(): Promise<DBBlock|null> + + getCountOfBlocksIssuedBy(issuer:string): Promise<number> + + saveBunch(blocks:DBBlock[]): Promise<void> + + dropNonForkBlocksAbove(number: number): Promise<void> + + setSideBlock(number:number, previousBlock:DBBlock|null): Promise<void> + + removeForkBlock(number:number): Promise<void> + + removeForkBlockAboveOrEqual(number:number): Promise<void> + + trimBlocks(number:number): Promise<void> + + getNonForkChunk(start:number, end:number): Promise<DBBlock[]> +} diff --git a/app/lib/dal/indexDAL/abstract/CIndexDAO.ts b/app/lib/dal/indexDAL/abstract/CIndexDAO.ts new file mode 100644 index 0000000000000000000000000000000000000000..c507c2d47412e0a135447f58c9d405453784af1b --- /dev/null +++ b/app/lib/dal/indexDAL/abstract/CIndexDAO.ts @@ -0,0 +1,26 @@ +import {CindexEntry, FullCindexEntry} from "../../../indexer" +import {ReduceableDAO} from "./ReduceableDAO" + +export interface CIndexDAO extends ReduceableDAO<CindexEntry> { + + getValidLinksTo(receiver:string): Promise<CindexEntry[]> + + getValidLinksFrom(issuer:string): Promise<CindexEntry[]> + + findExpired(medianTime:number): Promise<CindexEntry[]> + + findByIssuerAndReceiver(issuer: string, receiver: string): Promise<CindexEntry[]> + + findByIssuerAndChainableOnGt(issuer: string, medianTime: number): Promise<CindexEntry[]> + + findByReceiverAndExpiredOn(pub: string, expired_on: number): Promise<CindexEntry[]> + + existsNonReplayableLink(issuer:string, receiver:string): Promise<boolean> + + getReceiversAbove(minsig: number): Promise<string[]> + + reducablesFrom(from:string): Promise<FullCindexEntry[]> + + trimExpiredCerts(belowNumber:number): Promise<void> + +} diff --git a/app/lib/dal/indexDAL/abstract/DividendDAO.ts b/app/lib/dal/indexDAL/abstract/DividendDAO.ts new file mode 100644 index 0000000000000000000000000000000000000000..69e74c7a7b02bc8483aededd6b54c6feea6631e8 --- /dev/null +++ b/app/lib/dal/indexDAL/abstract/DividendDAO.ts @@ -0,0 +1,54 @@ +import {GenericDAO} from "./GenericDAO" +import {IindexEntry, SimpleTxInput, SimpleUdEntryForWallet, SindexEntry} from "../../../indexer" + +export interface DividendEntry { + pub: string + member: boolean + availables: number[] + consumed: number[] + consumedUDs: { + dividendNumber: number, + txHash: string, + txCreatedOn: string, + txLocktime: number, + dividend: { + amount: number, + base: number + } + }[] + dividends: { amount: number, base: number }[] +} + +export interface UDSource { + consumed: boolean + pos: number + amount: number + base: number +} + +export interface DividendDAO extends GenericDAO<DividendEntry> { + + setMember(member: boolean, pub: string): Promise<void> + + produceDividend(blockNumber: number, dividend: number, unitbase: number, local_iindex: IindexEntry[]): Promise<SimpleUdEntryForWallet[]> + + getUDSources(pub: string): Promise<UDSource[]> + + findUdSourceByIdentifierPosAmountBase(identifier: string, pos: number, amount: number, base: number): Promise<SimpleTxInput[]> + + getUDSource(identifier: string, pos: number): Promise<SimpleTxInput|null> + + createMember(pub: string): Promise<void> + + consume(filter: SindexEntry[]): Promise<void> + + deleteMember(pub: string): Promise<void> + + getWrittenOnUDs(number: number): Promise<SimpleUdEntryForWallet[]> + + revertUDs(number: number): Promise<{ createdUDsDestroyedByRevert: SimpleUdEntryForWallet[], consumedUDsRecoveredByRevert: SimpleUdEntryForWallet[] }> + + findForDump(criterion: any): Promise<SindexEntry[]> + + trimConsumedUDs(belowNumber:number): Promise<void> +} diff --git a/app/lib/dal/indexDAL/abstract/GenericDAO.ts b/app/lib/dal/indexDAL/abstract/GenericDAO.ts new file mode 100644 index 0000000000000000000000000000000000000000..ff2fd53c1792209ca125c8ee5f0bf5d718c9ecc0 --- /dev/null +++ b/app/lib/dal/indexDAL/abstract/GenericDAO.ts @@ -0,0 +1,51 @@ +import {Initiable} from "../../sqliteDAL/Initiable" +import {LokiDAO} from "../loki/LokiDAO" + +export interface GenericDAO<T> extends Initiable, LokiDAO { + + /** + * Trigger the initialization of the DAO. Called when the underlying DB is ready. + */ + triggerInit(): void + + /** + * Make a generic find. + * @param criterion Criterion object, LokiJS's find object format. + * @returns {Promise<any>} A set of records. + */ + findRaw(criterion: any): Promise<any> + + /** + * Make a generic find with some ordering. + * @param criterion Criterion object, LokiJS's find object format. + * @param sort A LokiJS's compunded sort object. + * @returns {Promise<any>} A set of records. + */ + findRawWithOrder(criterion: any, sort:((string|((string|boolean)[]))[])): Promise<T[]> + + /** + * Make a single insert. + * @param record The record to insert. + */ + insert(record:T): Promise<void> + + /** + * Make a batch insert. + * @param records The records to insert as a batch. + */ + insertBatch(records:T[]): Promise<void> + + /** + * Get the set of records written on a particular blockstamp. + * @param {string} blockstamp The blockstamp we want the records written at. + * @returns {Promise<T[]>} The records (array). + */ + getWrittenOn(blockstamp:string): Promise<T[]> + + /** + * Remove all entries written at given `blockstamp`, if these entries are still in the index. + * @param {string} blockstamp Blockstamp of the entries we want to remove. + * @returns {Promise<void>} + */ + removeBlock(blockstamp:string): Promise<void> +} diff --git a/app/lib/dal/indexDAL/abstract/IIndexDAO.ts b/app/lib/dal/indexDAL/abstract/IIndexDAO.ts new file mode 100644 index 0000000000000000000000000000000000000000..698f580c103ad8807c8e20a88bf5050046530d75 --- /dev/null +++ b/app/lib/dal/indexDAL/abstract/IIndexDAO.ts @@ -0,0 +1,32 @@ +import {FullIindexEntry, IindexEntry} from "../../../indexer" +import {ReduceableDAO} from "./ReduceableDAO" +import {OldIindexEntry} from "../../../db/OldIindexEntry" + +export interface IIndexDAO extends ReduceableDAO<IindexEntry> { + + reducable(pub:string): Promise<IindexEntry[]> + + findByPub(pub:string): Promise<IindexEntry[]> + + findByUid(pub:string): Promise<IindexEntry[]> + + getMembers(): Promise<{ pubkey:string, uid:string|null }[]> + + getFromPubkey(pub:string): Promise<FullIindexEntry|null> + + getFromUID(uid:string): Promise<FullIindexEntry|null> + + getFromPubkeyOrUid(search:string): Promise<FullIindexEntry|null> + + searchThoseMatching(search:string): Promise<OldIindexEntry[]> + + getFullFromUID(uid:string): Promise<FullIindexEntry> + + getFullFromPubkey(pub:string): Promise<FullIindexEntry> + + getFullFromHash(hash:string): Promise<FullIindexEntry> + + getToBeKickedPubkeys(): Promise<string[]> + + findAllByWrittenOn(): Promise<IindexEntry[]> +} diff --git a/app/lib/dal/indexDAL/abstract/MIndexDAO.ts b/app/lib/dal/indexDAL/abstract/MIndexDAO.ts new file mode 100644 index 0000000000000000000000000000000000000000..b8fef98bb846c1eea98fa8ca198f7d2221ebebcd --- /dev/null +++ b/app/lib/dal/indexDAL/abstract/MIndexDAO.ts @@ -0,0 +1,17 @@ +import {FullMindexEntry, MindexEntry} from "../../../indexer" +import {ReduceableDAO} from "./ReduceableDAO" + +export interface MIndexDAO extends ReduceableDAO<MindexEntry> { + + reducable(pub:string): Promise<MindexEntry[]> + + getRevokedPubkeys(): Promise<string[]> + + findByPubAndChainableOnGt(pub:string, medianTime:number): Promise<MindexEntry[]> + + findRevokesOnLteAndRevokedOnIsNull(medianTime:number): Promise<MindexEntry[]> + + findExpiresOnLteAndRevokesOnGt(medianTime:number): Promise<MindexEntry[]> + + getReducedMS(pub:string): Promise<FullMindexEntry|null> +} diff --git a/app/lib/dal/indexDAL/abstract/PeerDAO.ts b/app/lib/dal/indexDAL/abstract/PeerDAO.ts new file mode 100644 index 0000000000000000000000000000000000000000..97756b4227a35787eccd1b955568acca7069d98c --- /dev/null +++ b/app/lib/dal/indexDAL/abstract/PeerDAO.ts @@ -0,0 +1,62 @@ +import {Initiable} from "../../sqliteDAL/Initiable" +import {DBPeer} from "../../../db/DBPeer" +import {LokiDAO} from "../loki/LokiDAO" + +export interface PeerDAO extends Initiable, LokiDAO { + + /** + * Trigger the initialization of the DAO. Called when the underlying DB is ready. + */ + triggerInit(): void + + listAll(): Promise<DBPeer[]> + + withUPStatus(): Promise<DBPeer[]> + + /** + * Saves a wallet. + * @param {DBPeer} peer + * @returns {Promise<DBPeer>} + */ + savePeer(peer:DBPeer): Promise<DBPeer> + + /** + * Find a wallet based on conditions. + * @param {string} pubkey + * @returns {Promise<DBPeer>} + */ + getPeer(pubkey:string): Promise<DBPeer> + + /** + * Find all peers with at least one endpoint matching given parameter. + * @param {string} ep + * @returns {Promise<DBPeer[]>} + */ + getPeersWithEndpointsLike(ep:string): Promise<DBPeer[]> + + /** + * Make a batch insert. + * @param records The records to insert as a batch. + */ + insertBatch(records:DBPeer[]): Promise<void> + + /** + * Remove a peer by its pubkey. + * @param {string} pubkey + * @returns {Promise<void>} + */ + removePeerByPubkey(pubkey:string): Promise<void> + + /** + * Remove peers that were set down before a certain datetime. + * @param {number} thresholdTime + * @returns {Promise<void>} + */ + removePeersDownBefore(thresholdTime:number): Promise<void> + + /** + * Remove all the peers. + * @returns {Promise<void>} + */ + removeAll(): Promise<void> +} diff --git a/app/lib/dal/indexDAL/abstract/ReduceableDAO.ts b/app/lib/dal/indexDAL/abstract/ReduceableDAO.ts new file mode 100644 index 0000000000000000000000000000000000000000..5eaf55f7bb066381b9596b739f856c416b3def89 --- /dev/null +++ b/app/lib/dal/indexDAL/abstract/ReduceableDAO.ts @@ -0,0 +1,11 @@ +import {GenericDAO} from "./GenericDAO" + +export interface ReduceableDAO<T> extends GenericDAO<T> { + + /** + * Reduce all records sharing a same reduction key that written before given block number. + * @param {number} belowNumber All records written strictly under `belowNumber` have to be reduced on the reduction key. + * @returns {Promise<void>} + */ + trimRecords(belowNumber:number): Promise<void> +} diff --git a/app/lib/dal/indexDAL/abstract/SIndexDAO.ts b/app/lib/dal/indexDAL/abstract/SIndexDAO.ts new file mode 100644 index 0000000000000000000000000000000000000000..be5aa17033a1a2ef0e7eca2fa2816a181990866e --- /dev/null +++ b/app/lib/dal/indexDAL/abstract/SIndexDAO.ts @@ -0,0 +1,24 @@ +import {FullSindexEntry, SimpleTxEntryForWallet, SimpleTxInput, SindexEntry} from "../../../indexer" +import {ReduceableDAO} from "./ReduceableDAO" + +export interface UDSource { + consumed: boolean + pos: number + amount: number + base: number +} + +export interface SIndexDAO extends ReduceableDAO<SindexEntry> { + + findTxSourceByIdentifierPosAmountBase(identifier: string, pos: number, amount: number, base: number): Promise<SimpleTxInput[]> + + getTxSource(identifier:string, pos:number): Promise<FullSindexEntry|null> + + getAvailableForPubkey(pubkey:string): Promise<{ amount:number, base:number, conditions: string, identifier: string, pos: number }[]> + + getAvailableForConditions(conditionsStr:string): Promise<SindexEntry[]> + + trimConsumedSource(belowNumber:number): Promise<void> + + getWrittenOnTxs(blockstamp: string): Promise<SimpleTxEntryForWallet[]> +} diff --git a/app/lib/dal/indexDAL/abstract/TxsDAO.ts b/app/lib/dal/indexDAL/abstract/TxsDAO.ts new file mode 100644 index 0000000000000000000000000000000000000000..84c53b0086f205eaa5f1d91a66866102da3aab21 --- /dev/null +++ b/app/lib/dal/indexDAL/abstract/TxsDAO.ts @@ -0,0 +1,35 @@ +import {GenericDAO} from "./GenericDAO" +import {TransactionDTO} from "../../../dto/TransactionDTO" +import {SandBox} from "../../sqliteDAL/SandBox" +import {DBTx} from "../../../db/DBTx" + +export interface TxsDAO extends GenericDAO<DBTx> { + + trimExpiredNonWrittenTxs(limitTime:number): Promise<void> + + getAllPending(versionMin:number): Promise<DBTx[]> + + getTX(hash:string): Promise<DBTx> + + addLinked(tx:TransactionDTO, block_number:number, time:number): Promise<DBTx> + + addPending(dbTx:DBTx): Promise<DBTx> + + getLinkedWithIssuer(pubkey:string): Promise<DBTx[]> + + getLinkedWithRecipient(pubkey:string): Promise<DBTx[]> + + getPendingWithIssuer(pubkey:string): Promise<DBTx[]> + + getPendingWithRecipient(pubkey:string): Promise<DBTx[]> + + removeTX(hash:string): Promise<DBTx|null> + + removeAll(): Promise<void> + + sandbox:SandBox<{ issuers: string[], output_base:number, output_amount:number }> + + getSandboxRoom(): Promise<number> + + setSandboxSize(size:number): void +} diff --git a/app/lib/dal/indexDAL/abstract/WalletDAO.ts b/app/lib/dal/indexDAL/abstract/WalletDAO.ts new file mode 100644 index 0000000000000000000000000000000000000000..dfb0d0f50c01bd87a4489e0b28c83a0e04a1e680 --- /dev/null +++ b/app/lib/dal/indexDAL/abstract/WalletDAO.ts @@ -0,0 +1,31 @@ +import {Initiable} from "../../sqliteDAL/Initiable" +import {DBWallet} from "../../../db/DBWallet" +import {LokiDAO} from "../loki/LokiDAO" + +export interface WalletDAO extends Initiable, LokiDAO { + + /** + * Trigger the initialization of the DAO. Called when the underlying DB is ready. + */ + triggerInit(): void + + /** + * Saves a wallet. + * @param {DBWallet} wallet + * @returns {Promise<DBWallet>} + */ + saveWallet(wallet:DBWallet): Promise<DBWallet> + + /** + * Find a wallet based on conditions. + * @param {string} conditions + * @returns {Promise<DBWallet>} + */ + getWallet(conditions:string): Promise<DBWallet> + + /** + * Make a batch insert. + * @param records The records to insert as a batch. + */ + insertBatch(records:DBWallet[]): Promise<void> +} diff --git a/app/lib/dal/indexDAL/loki/LokiBIndex.ts b/app/lib/dal/indexDAL/loki/LokiBIndex.ts new file mode 100644 index 0000000000000000000000000000000000000000..6471996e141c968280c17bd29e66265074d66b8c --- /dev/null +++ b/app/lib/dal/indexDAL/loki/LokiBIndex.ts @@ -0,0 +1,91 @@ +import {DBHead} from "../../../db/DBHead" +import {BIndexDAO} from "../abstract/BIndexDAO" +import {NewLogger} from "../../../logger" +import {MonitorLokiExecutionTime} from "../../../debug/MonitorLokiExecutionTime" +import {LokiProtocolIndex} from "./LokiProtocolIndex" + +const logger = NewLogger() + +export class LokiBIndex extends LokiProtocolIndex<DBHead> implements BIndexDAO { + + private HEAD:DBHead|null = null + + constructor(loki:any) { + super(loki, 'bindex', ['number', 'hash']) + } + + async insert(record: DBHead): Promise<void> { + this.HEAD = record + return super.insert(record); + } + + async removeBlock(blockstamp: string): Promise<void> { + this.HEAD = await this.head(2) + return super.removeBlock(blockstamp); + } + + async head(n: number): Promise<DBHead> { + if (!n) { + throw "Cannot read HEAD~0, which is the incoming block" + } + if (n === 1 && this.HEAD) { + // Cached + return this.HEAD + } else if (this.HEAD) { + // Another than HEAD + return this.collection + .find({ number: this.HEAD.number - n + 1 })[0] + } else { + // Costly method, as a fallback + return this.collection + .chain() + .find({}) + .simplesort('number', true) + .data()[n - 1] + } + } + + async range(n: number, m: number): Promise<DBHead[]> { + if (!n) { + throw "Cannot read HEAD~0, which is the incoming block" + } + const HEAD = await this.head(1) + if (!HEAD) { + return [] + } + return this.collection + .chain() + .find({ + $and: [ + { number: { $lte: HEAD.number - n + 1 } }, + { number: { $gte: HEAD.number - m + 1 } }, + ] + }) + .simplesort('number', true) + .data().slice(n - 1, m) + } + + async tail(): Promise<DBHead> { + const HEAD = await this.head(1) + if (!HEAD) { + return HEAD + } + const nbHEADs = this.collection.length() + return this.collection + .find({ number: HEAD.number - nbHEADs + 1 })[0] + } + + @MonitorLokiExecutionTime(true) + async trimBlocks(maxnumber: number): Promise<void> { + this.collection + .chain() + .find({ number: { $lt: maxnumber }}) + .remove() + } + + @MonitorLokiExecutionTime(true) + async getWrittenOn(blockstamp: string): Promise<DBHead[]> { + const criterion:any = { number: parseInt(blockstamp) } + return this.collection.find(criterion) + } +} diff --git a/app/lib/dal/indexDAL/loki/LokiBlockchain.ts b/app/lib/dal/indexDAL/loki/LokiBlockchain.ts new file mode 100644 index 0000000000000000000000000000000000000000..1c125c194a895508a62c6526a4730492419bb95e --- /dev/null +++ b/app/lib/dal/indexDAL/loki/LokiBlockchain.ts @@ -0,0 +1,247 @@ +import {BlockchainDAO} from "../abstract/BlockchainDAO" +import {DBBlock} from "../../../db/DBBlock" +import {MonitorLokiExecutionTime} from "../../../debug/MonitorLokiExecutionTime" +import {LokiProtocolIndex} from "./LokiProtocolIndex" + +export class LokiBlockchain extends LokiProtocolIndex<DBBlock> implements BlockchainDAO { + + private current:DBBlock|null = null + + constructor(loki:any) { + super(loki, 'blockchain', ['number', 'hash', 'fork']) + } + + cleanCache(): void { + super.cleanCache() + this.current = null + } + + async getCurrent() { + if (this.current) { + // Cached + return this.current + } else { + // Costly method, as a fallback + return this.collection + .chain() + .find({ + fork: false + }) + .simplesort('number', true) + .data()[0] + } + } + + @MonitorLokiExecutionTime(true) + async getBlock(number:string | number) { + return this.collection + .chain() + .find({ + number: parseInt(String(number)), + fork: false + }) + .data()[0] + } + + async getPotentialRoots() { + return this.collection + .chain() + .find({ number: 0, fork: true }) + .data() + } + + async saveBunch(blocks:DBBlock[]) { + return this.insertBatch(blocks) + } + + async insertBatch(records: DBBlock[]): Promise<void> { + const lastInBatch = records[records.length - 1] + if (!this.current || this.current.number < lastInBatch.number) { + this.current = lastInBatch + } + return super.insertBatch(records) + } + + async removeBlock(blockstamp: string): Promise<void> { + // Never remove blocks + } + + async removeForkBlock(number:number): Promise<void> { + await this.collection + .chain() + .find({ + fork: true, + number + }) + .remove() + } + + async removeForkBlockAboveOrEqual(number:number): Promise<void> { + await this.collection + .chain() + .find({ + fork: true, + number: { $gte: number } + }) + .remove() + } + + async trimBlocks(number:number): Promise<void> { + await this.collection + .chain() + .find({ + number: { $lte: number } + }) + .remove() + } + + async getAbsoluteBlock(number: number, hash: string): Promise<DBBlock | null> { + return this.collection + .chain() + .find({ + number, + hash + }) + .data()[0] + } + + async getBlocks(start: number, end: number): Promise<DBBlock[]> { + return this.collection + .chain() + .find({ + number: { $between: [start, end] }, + fork: false + }) + .simplesort('number') + .data() + } + + async getCountOfBlocksIssuedBy(issuer: string): Promise<number> { + return this.collection + .chain() + .find({ + issuer, + fork: false + }) + .data() + .length + } + + async getNextForkBlocks(number: number, hash: string): Promise<DBBlock[]> { + return this.collection + .chain() + .find({ + fork: true, + number: number + 1, + previousHash: hash + }) + .simplesort('number') + .data() + } + + async getPotentialForkBlocks(numberStart: number, medianTimeStart: number, maxNumber: number): Promise<DBBlock[]> { + return this.collection + .chain() + .find({ + fork: true, + number: { $between: [numberStart, maxNumber] }, + medianTime: { $gte: medianTimeStart } + }) + .simplesort('number', true) + .data() + } + + async lastBlockOfIssuer(issuer: string): Promise<DBBlock | null> { + return this.collection + .chain() + .find({ + fork: false, + issuer + }) + .simplesort('number', true) + .data()[0] + } + + async lastBlockWithDividend(): Promise<DBBlock | null> { + return this.collection + .chain() + .find({ + fork: false, + dividend: { $gt: 0 } + }) + .simplesort('number', true) + .data()[0] + } + + async saveBlock(block: DBBlock): Promise<DBBlock> { + if (!this.current || this.current.number < block.number) { + this.current = block; + } + return this.insertOrUpdate(block, false) + } + + async saveSideBlock(block: DBBlock): Promise<DBBlock> { + return this.insertOrUpdate(block, true) + } + + async insertOrUpdate(block: DBBlock, isFork:boolean): Promise<DBBlock> { + block.fork = isFork + const conditions = { number: block.number, hash: block.hash } + const existing = (await this.findRaw(conditions))[0] + if (existing && existing.fork !== isFork) { + // Existing block: we only allow to change the fork flag + this.collection + .chain() + .find(conditions) + .update(b => { + b.fork = isFork + b.monetaryMass = block.monetaryMass + b.dividend = block.dividend + }) + } + else if (!existing) { + await this.insert(block) + } + return block + } + + async dropNonForkBlocksAbove(number: number): Promise<void> { + this.collection + .chain() + .find({ + fork: false, + number: { $gt: number } + }) + .remove() + } + + async setSideBlock(number: number, previousBlock: DBBlock | null): Promise<void> { + this.collection + .chain() + .find({ + number + }) + .update((b:DBBlock) => { + b.fork = true + }) + // Also update the cache if concerned + if (this.current && this.current.number === number) { + if (previousBlock && this.current.previousHash === previousBlock.hash) { + this.current = previousBlock + } else { + this.current = null + } + } + } + + async getNonForkChunk(start: number, end: number): Promise<DBBlock[]> { + return this.collection + .chain() + .find({ + fork: false, + number: { $between: [start, end ]} + }) + .simplesort('number') + .data() + } +} diff --git a/app/lib/dal/indexDAL/loki/LokiCIndex.ts b/app/lib/dal/indexDAL/loki/LokiCIndex.ts new file mode 100644 index 0000000000000000000000000000000000000000..218ef22ee08afc716e70ae13e89e49cfe74bf134 --- /dev/null +++ b/app/lib/dal/indexDAL/loki/LokiCIndex.ts @@ -0,0 +1,161 @@ +import {CIndexDAO} from "../abstract/CIndexDAO" +import {CindexEntry, FullCindexEntry, Indexer} from "../../../indexer" +import {CommonConstants} from "../../../common-libs/constants" +import {MonitorLokiExecutionTime} from "../../../debug/MonitorLokiExecutionTime" +import {LokiProtocolIndex} from "./LokiProtocolIndex" + +export class LokiCIndex extends LokiProtocolIndex<CindexEntry> implements CIndexDAO { + + constructor(loki:any) { + super(loki, 'cindex', ['issuer', 'receiver']) + } + + async existsNonReplayableLink(issuer: string, receiver: string): Promise<boolean> { + return Indexer.DUP_HELPERS.reduce<CindexEntry>( + this.collection + .chain() + .find({ + $and: [ + { issuer }, + { receiver }, + ] + }) + .simplesort('writtenOn') + .data() + ).op === CommonConstants.IDX_CREATE + } + + async findByIssuerAndChainableOnGt(issuer: string, medianTime: number): Promise<CindexEntry[]> { + return this.collection + .chain() + .find({ + $and: [ + { issuer }, + { chainable_on: { $gt: medianTime } }, + ] + }) + .simplesort('writtenOn') + .data() + } + + async findByIssuerAndReceiver(issuer: string, receiver: string): Promise<CindexEntry[]> { + return this.collection + .chain() + .find({ + $and: [ + { issuer }, + { receiver }, + ] + }) + .simplesort('writtenOn') + .data() + } + + async findByReceiverAndExpiredOn(pub: string, expired_on: number): Promise<CindexEntry[]> { + return this.collection + .chain() + .find({ + $and: [ + { receiver: pub }, + { expired_on }, + ] + }) + .simplesort('writtenOn') + .data() + } + + async findExpired(medianTime: number): Promise<CindexEntry[]> { + return this.collection + .chain() + .find({ expires_on: { $lte: medianTime } }) + .simplesort('writtenOn') + .data() + .filter(c => { + return this.collection + .find({ + op: CommonConstants.IDX_UPDATE, + issuer: c.issuer, + receiver: c.receiver, + created_on: c.created_on, + }) + .length === 0 + }) + } + + async reducablesFrom(from: string): Promise<FullCindexEntry[]> { + const reducables = this.collection + .chain() + .find({ issuer: from }) + .simplesort('writtenOn') + .data() + return Indexer.DUP_HELPERS.reduceBy(reducables, ['issuer', 'receiver', 'created_on']) + } + + async getReceiversAbove(minsig: number): Promise<string[]> { + const reduction = this.collection + .find({}) + .reduce((map:any, c) => { + if (!map[c.receiver]) { + map[c.receiver] = 0 + } + map[c.receiver]++ + return map + }, {}) + return Object.keys(reduction) + .map(receiver => ({ receiver, count: reduction[receiver]})) + .filter(o => o.count >= minsig) + .map(o => o.receiver) + } + + async getValidLinksFrom(issuer: string): Promise<CindexEntry[]> { + return this.collection + .find({ issuer }) + .filter(r => this.collection.find({ issuer: r.issuer, receiver: r.receiver, created_on: r.created_on, expired_on: { $gt: 0 } }).length === 0) + } + + async getValidLinksTo(receiver: string): Promise<CindexEntry[]> { + return this.collection + .find({ receiver }) + .filter(r => this.collection.find({ issuer: r.issuer, receiver: r.receiver, created_on: r.created_on, expired_on: { $gt: 0 } }).length === 0) + } + + @MonitorLokiExecutionTime(true) + async trimExpiredCerts(belowNumber: number): Promise<void> { + const expired = this.collection.find({ + $and: [ + { expired_on: { $gt: 0 }}, + { writtenOn: { $lt: belowNumber }}, + ] + }) + for (const e of expired) { + this.collection + .chain() + .find({ + issuer: e.issuer, + receiver: e.receiver, + created_on: e.created_on + }) + .remove() + } + } + + /** + * For CINDEX, trimming records <=> removing the expired certs + * @param {number} belowNumber Number below which an expired certification must be removed. + * @returns {Promise<void>} + */ + async trimRecords(belowNumber: number): Promise<void> { + return this.trimExpiredCerts(belowNumber) + } + + private reduced(issuer:string, receiver:string, created_on:number): FullCindexEntry { + return Indexer.DUP_HELPERS.reduce(this.reducable(issuer, receiver, created_on)) + } + + private reducable(issuer:string, receiver:string, created_on:number): CindexEntry[] { + return this.collection.chain() + .find({ issuer, receiver, created_on }) + .simplesort('writtenOn') + .data() + } +} diff --git a/app/lib/dal/indexDAL/loki/LokiCollection.ts b/app/lib/dal/indexDAL/loki/LokiCollection.ts new file mode 100644 index 0000000000000000000000000000000000000000..e160608b620f75c4858fa95c87990618b1ef985e --- /dev/null +++ b/app/lib/dal/indexDAL/loki/LokiCollection.ts @@ -0,0 +1,59 @@ +import {LokiChainableFind, LokiCollection} from "./LokiTypes" +import {NewLogger} from "../../../logger" +import {getMicrosecondsTime} from "../../../../ProcessCpuProfiler" + +const logger = NewLogger() + +export class LokiProxyCollection<T> implements LokiCollection<T> { + + constructor(public collection:LokiCollection<T>, private collectionName:string) { + } + + get data() { + return this.collection.data + } + + length(): number { + return this.collection.data.length + } + + insert(entity:T) { + this.collection.insert(entity) + } + + remove(entity:T) { + this.collection.remove(entity) + } + + find(criterion:{ [t in keyof T|'$or'|'$and']?: any }) { + const now = getMicrosecondsTime() + const res = this.collection.find(criterion) + // logger.trace('[loki][%s][find] %sµs', this.collectionName, getDurationInMicroSeconds(now), criterion) + return res + } + + chain(): LokiChainableFind<T> { + return this.collection.chain() + } + + setChangesApi(enabled: boolean) { + this.collection.setChangesApi(enabled) + // This is a Loki bug: `disableDeltaChangesApi` should be impacted just like `disableChangesApi`: + ;(this.collection as any).disableDeltaChangesApi = !enabled + } + + // Returns the real Loki property + get disableChangesApi() { + return this.collection.disableChangesApi + } + + // Returns the real Loki property + get disableDeltaChangesApi() { + return this.collection.disableDeltaChangesApi + } + + get changes() { + return this.collection.changes + } + +} diff --git a/app/lib/dal/indexDAL/loki/LokiCollectionManager.ts b/app/lib/dal/indexDAL/loki/LokiCollectionManager.ts new file mode 100755 index 0000000000000000000000000000000000000000..ce6d2ad06d0fb67baf363f1df1b8b977e946a067 --- /dev/null +++ b/app/lib/dal/indexDAL/loki/LokiCollectionManager.ts @@ -0,0 +1,47 @@ +import {LokiCollection} from "./LokiTypes" +import {LokiProxyCollection} from "./LokiCollection" +import {NewLogger} from "../../../logger" +import {LokiDAO} from "./LokiDAO" +import {cliprogram} from "../../../common-libs/programOptions" + +const logger = NewLogger() + +export abstract class LokiCollectionManager<T> implements LokiDAO { + + protected collection:LokiCollection<T> + protected collectionIsInitialized: Promise<void> + protected resolveCollection: () => void + + public constructor( + protected loki:any, + protected collectionName:'iindex'|'mindex'|'cindex'|'sindex'|'bindex'|'blockchain'|'txs'|'wallet'|'peer'|'dividend', + protected indices: (keyof T)[]) { + this.collectionIsInitialized = new Promise<void>(res => this.resolveCollection = res) + } + + get lokiCollection(): LokiCollection<T> { + return this.collection + } + + public triggerInit() { + const coll = this.loki.addCollection(this.collectionName, { + indices: this.indices, + disableChangesApi: cliprogram.isSync + }) + this.collection = new LokiProxyCollection(coll, this.collectionName) + this.resolveCollection() + } + + public enableChangesAPI() { + this.collection.setChangesApi(true) + } + + public disableChangesAPI() { + this.collection.setChangesApi(false) + } + + async init(): Promise<void> { + await this.collectionIsInitialized + logger.info('Collection %s ready', this.collectionName) + } +} \ No newline at end of file diff --git a/app/lib/dal/indexDAL/loki/LokiDAO.ts b/app/lib/dal/indexDAL/loki/LokiDAO.ts new file mode 100644 index 0000000000000000000000000000000000000000..b060804d77b59d7bdc76ed8d2565bd73547471c8 --- /dev/null +++ b/app/lib/dal/indexDAL/loki/LokiDAO.ts @@ -0,0 +1,10 @@ +import {LokiCollection} from "./LokiTypes" + +export interface LokiDAO { + + enableChangesAPI(): void + + disableChangesAPI(): void + + lokiCollection: LokiCollection<any> +} diff --git a/app/lib/dal/indexDAL/loki/LokiDividend.ts b/app/lib/dal/indexDAL/loki/LokiDividend.ts new file mode 100644 index 0000000000000000000000000000000000000000..daa96c8efdc0bc8a1232cf1778fe49464946760e --- /dev/null +++ b/app/lib/dal/indexDAL/loki/LokiDividend.ts @@ -0,0 +1,335 @@ +import {LokiIndex} from "./LokiIndex" +import {DividendDAO, DividendEntry, UDSource} from "../abstract/DividendDAO" +import {IindexEntry, SimpleTxInput, SimpleUdEntryForWallet, SindexEntry} from "../../../indexer" +import {DataErrors} from "../../../common-libs/errors" + +export class LokiDividend extends LokiIndex<DividendEntry> implements DividendDAO { + + constructor(loki:any) { + super(loki, 'dividend', ['pub']) + } + + async createMember(pub: string): Promise<void> { + const existing = this.collection.find({ pub })[0] + if (!existing) { + await this.insert({ pub, member: true, availables: [], dividends: [], consumed: [], consumedUDs: [] }) + } else { + await this.setMember(true, pub) + } + } + + async setMember(member: boolean, pub: string) { + await this.collection + .chain() + .find({ pub }) + .update(r => { + r.member = member + }) + } + + async deleteMember(pub: string): Promise<void> { + this.collection + .chain() + .find({ pub }) + .remove() + } + + async produceDividend(blockNumber: number, dividend: number, unitbase: number, local_iindex: IindexEntry[]): Promise<SimpleUdEntryForWallet[]> { + const dividends: SimpleUdEntryForWallet[] = [] + // Then produce the UD + this.collection + .chain() + .find({ member: true }) + .update(r => { + r.availables.push(blockNumber) + r.dividends.push({ amount: dividend, base: unitbase }) + dividends.push({ + srcType: 'D', + amount: dividend, + base: unitbase, + conditions: 'SIG(' + r.pub + ')', + op: 'CREATE', + identifier: r.pub, + pos: blockNumber + }) + }) + return dividends + } + + async consume(filter: SindexEntry[]): Promise<void> { + for (const dividendToConsume of filter) { + this.collection + .chain() + // We look at the dividends of this member + .find({ + pub: dividendToConsume.identifier + }) + // Then we try to consume the dividend being spent + .update(m => { + const index = m.availables.indexOf(dividendToConsume.pos) + + // We add it to the consumption history + m.consumed.push(dividendToConsume.writtenOn) // `writtenOn` is the date (block#) of consumption + m.consumedUDs.push({ + dividendNumber: dividendToConsume.pos, + dividend: m.dividends[index], + txCreatedOn: dividendToConsume.created_on as string, + txLocktime: dividendToConsume.locktime, + txHash: dividendToConsume.tx as string, + }) + + // We remove it from available dividends + m.availables.splice(index, 1) + m.dividends.splice(index, 1) + }) + } + } + + async getUDSources(pub: string): Promise<UDSource[]> { + const member = this.collection + .chain() + .find({ pub }) + .data()[0] + if (!member) { + return [] + } + return member.availables.map(pos => this.toUDSource(member, pos) as UDSource) + } + + async findUdSourceByIdentifierPosAmountBase(identifier: string, pos: number, amount: number, base: number): Promise<SimpleTxInput[]> { + const member = this.collection.find({ pub: identifier })[0] + let src: UDSource|null = null + if (member) { + const udSrc = this.toUDSource(member, pos) + if (udSrc && udSrc.amount === amount && udSrc.base === base) { + src = udSrc + } + } + return [{ + written_time: 0, + conditions: 'SIG(' + identifier + ')', + consumed: !src, + amount, + base + }] + } + + private toUDSource(entry: DividendEntry, pos: number): UDSource|null { + const index = entry.availables.indexOf(pos) + if (index === -1) { + return null + } + const src = entry.dividends[index] + return { + consumed: false, + pos, + amount: src.amount, + base: src.base, + } + } + + async getUDSource(identifier: string, pos: number): Promise<SimpleTxInput|null> { + const member = this.collection.find({ pub: identifier })[0] + let src: UDSource|null = null + if (member) { + src = this.toUDSource(member, pos) + } + if (!src) { + return null + } + return { + written_time: 0, + conditions: 'SIG(' + identifier + ')', + consumed: !src, + amount: src.amount, + base: src.base + } + } + + async getWrittenOn(blockstamp: string): Promise<DividendEntry[]> { + throw Error(DataErrors[DataErrors.LOKI_DIVIDEND_GET_WRITTEN_ON_SHOULD_NOT_BE_USED]) + } + + async getWrittenOnUDs(number: number): Promise<SimpleUdEntryForWallet[]> { + const res: SimpleUdEntryForWallet[] = [] + this.collection + .chain() + .find({ availables: { $contains: number } }) + .data() + .map(m => { + const s = this.toUDSource(m, number) as UDSource + res.push({ + srcType: 'D', + op: 'CREATE', + conditions: 'SIG(' + m.pub + ')', + amount: s.amount, + base: s.base, + identifier: m.pub, + pos: s.pos + }) + }) + return res + } + + async removeBlock(blockstamp: string): Promise<void> { + throw Error(DataErrors[DataErrors.LOKI_DIVIDEND_REMOVE_BLOCK_SHOULD_NOT_BE_USED]) + } + + /** + * Remove UD data produced in a block, either UD production or UD consumption. + * @param {number} number Block number to revert the created UDs. + * @returns {Promise<{createdUDsDestroyedByRevert: SimpleUdEntryForWallet[]}>} + */ + async revertUDs(number: number): Promise<{ + createdUDsDestroyedByRevert: SimpleUdEntryForWallet[] + consumedUDsRecoveredByRevert: SimpleUdEntryForWallet[] + }> { + const createdUDsDestroyedByRevert: SimpleUdEntryForWallet[] = [] + const consumedUDsRecoveredByRevert: SimpleUdEntryForWallet[] = [] + // Remove produced dividends at this block + this.collection + .chain() + .find({ availables: { $contains: number }}) + .update(m => { + const index = m.availables.indexOf(number) + const src = m.dividends[index] + createdUDsDestroyedByRevert.push({ + conditions: 'SIG(' + m.pub + ')', + pos: number, + identifier: m.pub, + amount: src.amount, + base: src.base, + srcType: 'D', + op: 'CREATE' + }) + m.availables.splice(index, 1) + m.dividends.splice(index, 1) + }) + // Unconsumed dividends consumed at this block + this.collection + .chain() + .find({ consumed: { $contains: number }}) + .update(m => { + const index = m.consumed.indexOf(number) + + const src = m.consumedUDs[index].dividend + consumedUDsRecoveredByRevert.push({ + conditions: 'SIG(' + m.pub + ')', + pos: m.consumedUDs[index].dividendNumber, + identifier: m.pub, + amount: src.amount, + base: src.base, + srcType: 'D', + op: 'CREATE' + }) + + // We put it back as available + m.availables.push(m.consumedUDs[index].dividendNumber) + m.dividends.push(m.consumedUDs[index].dividend) + + // We remove it from consumed + m.consumed.splice(index, 1) + m.consumedUDs.splice(index, 1) + }) + return { + createdUDsDestroyedByRevert, + consumedUDsRecoveredByRevert, + } + } + + async findForDump(criterion: any): Promise<SindexEntry[]> { + const entries: SindexEntry[] = [] + const rows = await this.findRaw(criterion) + for (const m of rows) { + // Generate for unspent UDs + for (let i = 0; i < m.availables.length; i++) { + const writtenOn = m.availables[i] + const ud = m.dividends[i] + entries.push({ + op: 'CREATE', + index: 'SINDEX', + srcType: 'D', + tx: null, + identifier: m.pub, + writtenOn, + pos: writtenOn, + created_on: 'NULL', // TODO + written_on: writtenOn + '', // TODO + written_time: 0, // TODO + amount: ud.amount, + base: ud.base, + locktime: null as any, + consumed: false, + conditions: 'SIG(' + m.pub + ')', + unlock: null, + txObj: null as any, // TODO + age: 0, + }) + } + // Generate for spent UDs + for (let i = 0; i < m.consumed.length; i++) { + const writtenOn = m.consumed[i] + const ud = m.consumedUDs[i] + entries.push({ + op: 'CREATE', + index: 'SINDEX', + srcType: 'D', + tx: null, + identifier: m.pub, + writtenOn: ud.dividendNumber, + pos: ud.dividendNumber, + created_on: 'NULL', // TODO + written_on: writtenOn + '', // TODO + written_time: 0, // TODO + amount: ud.dividend.amount, + base: ud.dividend.base, + locktime: null as any, + consumed: false, + conditions: 'SIG(' + m.pub + ')', + unlock: null, + txObj: null as any, // TODO + age: 0, + }) + entries.push({ + op: 'UPDATE', + index: 'SINDEX', + srcType: 'D', + tx: ud.txHash, + identifier: m.pub, + writtenOn, + pos: ud.dividendNumber, + created_on: ud.txCreatedOn, + written_on: writtenOn + '', // TODO + written_time: 0, // TODO + amount: ud.dividend.amount, + base: ud.dividend.base, + locktime: ud.txLocktime, + consumed: true, + conditions: 'SIG(' + m.pub + ')', + unlock: null, + txObj: null as any, // TODO + age: 0, + }) + } + } + return entries + } + + async trimConsumedUDs(belowNumber: number): Promise<void> { + // Remove dividends consumed before `belowNumber` + this.collection + .chain() + .find({}) + .update(m => { + for (let i = 0; i < m.consumed.length; i++) { + const consumedBlockNumber = m.consumed[i] + if (consumedBlockNumber < belowNumber) { + // We trim this entry as it can't be reverted now + m.consumed.splice(i, 1) + m.consumedUDs.splice(i, 1) + i-- // The array changed, we loop back before i++ + } + } + }) + } +} diff --git a/app/lib/dal/indexDAL/loki/LokiIIndex.ts b/app/lib/dal/indexDAL/loki/LokiIIndex.ts new file mode 100644 index 0000000000000000000000000000000000000000..e0effa5d8c2660be973b3c58d8d116763d7abfee --- /dev/null +++ b/app/lib/dal/indexDAL/loki/LokiIIndex.ts @@ -0,0 +1,180 @@ +import {FullIindexEntry, IindexEntry, Indexer} from "../../../indexer" +import {IIndexDAO} from "../abstract/IIndexDAO" +import {LokiPubkeySharingIndex} from "./LokiPubkeySharingIndex" +import {OldIindexEntry} from "../../../db/OldIindexEntry" + +export class LokiIIndex extends LokiPubkeySharingIndex<IindexEntry> implements IIndexDAO { + + constructor(loki:any) { + super(loki, 'iindex', [ + 'pub', + 'uid', + 'member', + ]) + } + + reducable(pub: string): Promise<IindexEntry[]> { + return this.findByPub(pub) + } + + async findAllByWrittenOn(): Promise<IindexEntry[]> { + return this.collection.chain() + .find({}) + .simplesort('writtenOn') + .data() + } + + async findByPub(pub: string): Promise<IindexEntry[]> { + return this.collection.chain() + .find({ pub }) + .simplesort('writtenOn') + .data() + } + + async findByUid(uid: string): Promise<IindexEntry[]> { + return this.collection.chain() + .find({ uid }) + .simplesort('writtenOn') + .data() + } + + async getMembers(): Promise<{ pubkey: string; uid: string|null }[]> { + return this.collection + // Those who are still marked member somewhere + .find({ member: true }) + // We reduce them + .map(r => { + return Indexer.DUP_HELPERS.reduce( + this.collection + .chain() + .find({ pub: r.pub }) + .simplesort('writtenOn') + .data() + ) + }) + // We keep only the real members (because we could have excluded) + .filter(r => r.member) + // We map + .map(this.toCorrectEntity) + } + + async getFromPubkey(pub: string): Promise<FullIindexEntry | null> { + return this.retrieveIdentityOnPubOrNull( + { pub } + ) as Promise<FullIindexEntry|null> + } + + async getFromUID(uid: string): Promise<FullIindexEntry | null> { + return this.retrieveIdentityOnPubOrNull( + this.collection + .chain() + .find({ uid }) + .data()[0] + ) as Promise<FullIindexEntry|null> + } + + async getFromPubkeyOrUid(search: string): Promise<FullIindexEntry | null> { + const idty = await this.getFromPubkey(search) + if (idty) { + return idty + } + return this.getFromUID(search) as Promise<FullIindexEntry|null> + } + + async searchThoseMatching(search: string): Promise<OldIindexEntry[]> { + const reducables = Indexer.DUP_HELPERS.reduceBy(this.collection + .chain() + .find({ + $or: [ + { pub: { $contains: search } }, + { uid: { $contains: search } }, + ] + }) + .data() + , ['pub']) + // We get the full representation for each member + return await Promise.all(reducables.map(async (entry) => { + return this.toCorrectEntity(Indexer.DUP_HELPERS.reduce(await this.reducable(entry.pub))) + })) + } + + async getFullFromUID(uid: string): Promise<FullIindexEntry> { + return (await this.getFromUID(uid)) as FullIindexEntry + } + + async getFullFromPubkey(pub: string): Promise<FullIindexEntry> { + return (await this.getFromPubkey(pub)) as FullIindexEntry + } + + async getFullFromHash(hash: string): Promise<FullIindexEntry> { + return this.retrieveIdentityOnPubOrNull( + this.collection + .chain() + .find({ hash }) + .data()[0] + ) as Promise<FullIindexEntry> + } + + async retrieveIdentityOnPubOrNull(entry:{ pub:string }|null) { + if (!entry) { + return null + } + return this.entityOrNull( + this.collection + .chain() + .find({ pub: entry.pub }) + .simplesort('writtenOn') + .data() + ) as Promise<FullIindexEntry|null> + } + + async getToBeKickedPubkeys(): Promise<string[]> { + return this.collection + // Those who are still marked member somewhere + .find({ kick: true }) + // We reduce them + .map(r => { + return Indexer.DUP_HELPERS.reduce( + this.collection + .chain() + .find({ pub: r.pub }) + .simplesort('writtenOn') + .data() + ) + }) + // We keep only the real members (because we could have excluded) + .filter(r => r.kick) + // We map + .map(r => r.pub) + } + + private async entityOrNull(reducable:IindexEntry[]) { + if (reducable.length) { + return this.toCorrectEntity(Indexer.DUP_HELPERS.reduce(reducable)) + } + return null + } + + private toCorrectEntity(row:IindexEntry): OldIindexEntry { + // Old field + return { + pubkey: row.pub, + pub: row.pub, + buid: row.created_on, + revocation_sig: null, + uid: row.uid, + hash: row.hash, + sig: row.sig, + created_on: row.created_on, + member: row.member, + wasMember: row.wasMember, + kick: row.kick, + wotb_id: row.wotb_id, + age: row.age, + index: row.index, + op: row.op, + writtenOn: row.writtenOn, + written_on: row.written_on + } + } +} diff --git a/app/lib/dal/indexDAL/loki/LokiIndex.ts b/app/lib/dal/indexDAL/loki/LokiIndex.ts new file mode 100644 index 0000000000000000000000000000000000000000..877cdc511c375456d8358d4057c81aa04d961d1d --- /dev/null +++ b/app/lib/dal/indexDAL/loki/LokiIndex.ts @@ -0,0 +1,35 @@ +import {GenericDAO} from "../abstract/GenericDAO" +import {LokiCollectionManager} from "./LokiCollectionManager" +import {MonitorLokiExecutionTime} from "../../../debug/MonitorLokiExecutionTime" + +export abstract class LokiIndex<T> extends LokiCollectionManager<T> implements GenericDAO<T> { + + cleanCache(): void { + } + + async insert(record: T): Promise<void> { + this.collection.insert(record) + } + + @MonitorLokiExecutionTime(true) + async findRaw(criterion?:any) { + return this.collection.find(criterion) + } + + @MonitorLokiExecutionTime(true) + async findRawWithOrder(criterion:any, sort:((string|((string|boolean)[]))[])) { + const res = this.collection + .chain() + .find(criterion) + .compoundsort(sort) + return res.data() + } + + @MonitorLokiExecutionTime() + async insertBatch(records: T[]): Promise<void> { + records.map(r => this.insert(r)) + } + + abstract getWrittenOn(blockstamp: string): Promise<T[]> + abstract removeBlock(blockstamp: string): Promise<void> +} diff --git a/app/lib/dal/indexDAL/loki/LokiMIndex.ts b/app/lib/dal/indexDAL/loki/LokiMIndex.ts new file mode 100644 index 0000000000000000000000000000000000000000..5ab94e90c3ebf4644056be693d819f7509fb633c --- /dev/null +++ b/app/lib/dal/indexDAL/loki/LokiMIndex.ts @@ -0,0 +1,63 @@ +import {FullMindexEntry, Indexer, MindexEntry} from "../../../indexer" +import {MIndexDAO} from "../abstract/MIndexDAO" +import {LokiPubkeySharingIndex} from "./LokiPubkeySharingIndex" + +export class LokiMIndex extends LokiPubkeySharingIndex<MindexEntry> implements MIndexDAO { + + constructor(loki:any) { + super(loki, 'mindex', ['pub']) + } + + async findByPubAndChainableOnGt(pub: string, medianTime: number): Promise<MindexEntry[]> { + return this.collection + .find({ + $and: [ + { pub }, + { chainable_on: { $gt: medianTime } }, + ] + }) + } + + async findExpiresOnLteAndRevokesOnGt(medianTime: number): Promise<MindexEntry[]> { + return this.collection + .find({ + $and: [ + { expires_on: { $lte: medianTime } }, + { revokes_on: { $gt: medianTime } }, + ] + }) + } + + async findRevokesOnLteAndRevokedOnIsNull(medianTime: number): Promise<MindexEntry[]> { + return this.collection + .find({ + $and: [ + { revokes_on: { $lte: medianTime } }, + { revoked_on: null }, + ] + }) + } + + async getReducedMS(pub: string): Promise<FullMindexEntry | null> { + const reducable = (await this.reducable(pub)) as (FullMindexEntry)[] + if (reducable.length) { + return Indexer.DUP_HELPERS.reduce(reducable) + } + return null + } + + async getRevokedPubkeys(): Promise<string[]> { + return this.collection + .find({ revoked_on: { $gt: 0 } }) + // We map + .map(r => r.pub) + } + + async reducable(pub: string): Promise<MindexEntry[]> { + return this.collection.chain() + .find({ pub }) + .simplesort('writtenOn') + .data() + } + +} diff --git a/app/lib/dal/indexDAL/loki/LokiPeer.ts b/app/lib/dal/indexDAL/loki/LokiPeer.ts new file mode 100644 index 0000000000000000000000000000000000000000..c067aa2976b1379e0b8548677a4c44f0f329b2d8 --- /dev/null +++ b/app/lib/dal/indexDAL/loki/LokiPeer.ts @@ -0,0 +1,107 @@ +import {LokiCollectionManager} from "./LokiCollectionManager" +import {PeerDAO} from "../abstract/PeerDAO" +import {DBPeer} from "../../../db/DBPeer" + +export class LokiPeer extends LokiCollectionManager<DBPeer> implements PeerDAO { + + constructor(loki:any) { + super(loki, 'peer', ['pubkey']) + } + + async init(): Promise<void> { + await super.init(); + this.cleanEmptyPeers() + } + + cleanCache(): void { + } + + async listAll(): Promise<DBPeer[]> { + return this.collection + .find({}) + } + + async withUPStatus(): Promise<DBPeer[]> { + return this.collection + .find({ status: 'UP' }) + } + + async getPeer(pubkey: string): Promise<DBPeer> { + return this.collection + .find({ pubkey })[0] + } + + async insertBatch(peers: DBPeer[]): Promise<void> { + for (const p of peers) { + this.collection.insert(p) + } + } + + async savePeer(peer: DBPeer): Promise<DBPeer> { + let updated = false + this.collection + .chain() + .find({ pubkey: peer.pubkey }) + .update(p => { + p.version = peer.version + p.currency = peer.currency + p.status = peer.status + p.statusTS = peer.statusTS + p.hash = peer.hash + p.first_down = peer.first_down + p.last_try = peer.last_try + p.pubkey = peer.pubkey + p.block = peer.block + p.signature = peer.signature + p.endpoints = peer.endpoints + p.raw = peer.raw + updated = true + }) + if (!updated) { + await this.insertBatch([peer]) + } + return peer + } + + async removePeerByPubkey(pubkey:string): Promise<void> { + this.collection + .chain() + .find({ pubkey }) + .remove() + } + + async removePeersDownBefore(thresholdTime:number): Promise<void> { + this.collection + .chain() + .find({ + $and: [ + { first_down: { $lt: thresholdTime } }, + { first_down: { $gt: 0 } }, + ] + }) + .remove() + } + + async removeAll(): Promise<void> { + this.collection + .chain() + .find({}) + .remove() + } + + async cleanEmptyPeers(): Promise<void> { + this.collection + .chain() + .find({}) + .where(p => !p.endpoints || !p.endpoints.length) + .remove() + } + + async getPeersWithEndpointsLike(ep: string): Promise<DBPeer[]> { + return this.collection + .chain() + .find({}) + .where(p => p.endpoints.filter(ep => ep.indexOf(ep) !== -1).length > 0) + .data() + } +} \ No newline at end of file diff --git a/app/lib/dal/indexDAL/loki/LokiProtocolIndex.ts b/app/lib/dal/indexDAL/loki/LokiProtocolIndex.ts new file mode 100644 index 0000000000000000000000000000000000000000..90848ed997e145725805f3fd010efe8cde653dda --- /dev/null +++ b/app/lib/dal/indexDAL/loki/LokiProtocolIndex.ts @@ -0,0 +1,23 @@ +import {GenericDAO} from "../abstract/GenericDAO" +import {MonitorLokiExecutionTime} from "../../../debug/MonitorLokiExecutionTime" +import {LokiIndex} from "./LokiIndex" + +export interface IndexData { + written_on: string + writtenOn: number +} + +export abstract class LokiProtocolIndex<T extends IndexData> extends LokiIndex<T> implements GenericDAO<T> { + + @MonitorLokiExecutionTime(true) + async getWrittenOn(blockstamp: string): Promise<T[]> { + const criterion:any = { writtenOn: parseInt(blockstamp) } + return this.collection.find(criterion) + } + + @MonitorLokiExecutionTime(true) + async removeBlock(blockstamp: string): Promise<void> { + const data = await this.getWrittenOn(blockstamp) + data.map(d => this.collection.remove(d)) + } +} diff --git a/app/lib/dal/indexDAL/loki/LokiPubkeySharingIndex.ts b/app/lib/dal/indexDAL/loki/LokiPubkeySharingIndex.ts new file mode 100644 index 0000000000000000000000000000000000000000..4fb0a3fd806263aff5d75b29e8d6d0fb721df1ef --- /dev/null +++ b/app/lib/dal/indexDAL/loki/LokiPubkeySharingIndex.ts @@ -0,0 +1,39 @@ +import {Indexer} from "../../../indexer" +import {MonitorLokiExecutionTime} from "../../../debug/MonitorLokiExecutionTime" +import {LokiProtocolIndex} from "./LokiProtocolIndex" + +export class LokiPubkeySharingIndex<T extends { written_on:string, writtenOn:number, pub:string }> extends LokiProtocolIndex<T> { + + @MonitorLokiExecutionTime(true) + async trimRecords(belowNumber: number): Promise<void> { + // TODO: may be optimized by only selecting new offseted records + const criterion:any = { + writtenOn: { + $lt: belowNumber + } + } + const trimmable = await this.collection + .chain() + .find(criterion) + .simplesort('writtenOn') + .data() + const trimmableByPub: { [pub:string]: T[] } = {} + for (const t of trimmable) { + if (!trimmableByPub[t.pub]) { + trimmableByPub[t.pub] = [] + } + trimmableByPub[t.pub].push(t) + } + for (const pub of Object.keys(trimmableByPub)) { + if (trimmableByPub[pub].length > 1) { + // Remove the existing records + for (const t of trimmableByPub[pub]) { + this.collection.remove(t) + } + // Insert a new one that gathers them + const reduced = Indexer.DUP_HELPERS.reduce(trimmableByPub[pub]) + this.collection.insert(reduced) + } + } + } +} diff --git a/app/lib/dal/indexDAL/loki/LokiSIndex.ts b/app/lib/dal/indexDAL/loki/LokiSIndex.ts new file mode 100644 index 0000000000000000000000000000000000000000..8038f3fff7e0a28ec337c09e958fe88877e28ad7 --- /dev/null +++ b/app/lib/dal/indexDAL/loki/LokiSIndex.ts @@ -0,0 +1,113 @@ +import {FullSindexEntry, Indexer, SimpleTxEntryForWallet, SindexEntry} from "../../../indexer" +import {SIndexDAO} from "../abstract/SIndexDAO" +import {Underscore} from "../../../common-libs/underscore" +import {MonitorLokiExecutionTime} from "../../../debug/MonitorLokiExecutionTime" +import {LokiProtocolIndex} from "./LokiProtocolIndex" +import {LokiDividend} from "./LokiDividend" + +export class LokiSIndex extends LokiProtocolIndex<SindexEntry> implements SIndexDAO { + + private lokiDividend: LokiDividend + + constructor(loki:any) { + super(loki, 'sindex', ['identifier', 'conditions', 'writtenOn']) + this.lokiDividend = new LokiDividend(loki) + } + + async findTxSourceByIdentifierPosAmountBase(identifier: string, pos: number, amount: number, base: number): Promise<SindexEntry[]> { + return this.collection + .chain() + .find({ identifier, pos, amount, base }) + .simplesort('writtenOn') + .data() + .map(src => { + src.type = src.tx ? 'T' : 'D' + return src + }) + } + + async getAvailableForConditions(conditionsStr: string): Promise<SindexEntry[]> { + const sources = this.collection + .chain() + .find({ conditions: conditionsStr }) + .simplesort('writtenOn') + .data() + .filter(s => this.collection.find({ identifier: s.identifier, pos: s.pos, consumed: true }).length === 0) + .map(src => { + src.type = src.tx ? 'T' : 'D' + return src + }) + return Underscore.sortBy(sources, (row:SindexEntry) => row.type == 'D' ? 0 : 1) + } + + async getAvailableForPubkey(pubkey: string): Promise<{ amount: number; base: number, conditions: string, identifier: string, pos: number }[]> { + return this.collection + .chain() + .find({ conditions: { $regex: 'SIG\\(' + pubkey + '\\)' } }) + .simplesort('writtenOn') + .data() + .filter(s => this.collection.find({ identifier: s.identifier, pos: s.pos, consumed: true }).length === 0) + .map(src => { + src.type = src.tx ? 'T' : 'D' + return src + }) + } + + async getTxSource(identifier: string, pos: number): Promise<FullSindexEntry | null> { + const reducables = this.collection + .chain() + .find({ identifier, pos }) + .compoundsort([['writtenOn', false], ['op', false]]) + .data() + .map(src => { + src.type = src.tx ? 'T' : 'D' + return src + }) + if (reducables.length === 0) { + return null + } + return Indexer.DUP_HELPERS.reduce(reducables) + } + + @MonitorLokiExecutionTime(true) + async trimConsumedSource(belowNumber: number): Promise<void> { + const consumed = this.collection + .find({ writtenOn: { $lt: belowNumber }}) + .filter(s => s.consumed) + for (const e of consumed) { + this.collection + .chain() + .find({ + identifier: e.identifier, + pos: e.pos + }) + .remove() + } + } + + /** + * For SINDEX, trimming records <=> removing the consumed sources. + * @param {number} belowNumber Number below which a consumed source must be removed. + * @returns {Promise<void>} + */ + async trimRecords(belowNumber: number): Promise<void> { + return this.trimConsumedSource(belowNumber) + } + + async getWrittenOnTxs(blockstamp: string): Promise<SimpleTxEntryForWallet[]> { + const entries = (await this.getWrittenOn(blockstamp)) + const res: SimpleTxEntryForWallet[] = [] + entries.forEach(s => { + res.push({ + srcType: 'T', + op: s.op, + conditions: s.conditions, + amount: s.amount, + base: s.base, + identifier: s.identifier, + pos: s.pos + }) + }) + return res + } +} diff --git a/app/lib/dal/indexDAL/loki/LokiTransactions.ts b/app/lib/dal/indexDAL/loki/LokiTransactions.ts new file mode 100644 index 0000000000000000000000000000000000000000..78ebf7fe54f6a98a4a5364d3095ca76c784b03b5 --- /dev/null +++ b/app/lib/dal/indexDAL/loki/LokiTransactions.ts @@ -0,0 +1,200 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import * as moment from "moment" +import {TxsDAO} from "../abstract/TxsDAO" +import {SandBox} from "../../sqliteDAL/SandBox" +import {TransactionDTO} from "../../../dto/TransactionDTO" +import {DBTx} from "../../../db/DBTx" +import {Underscore} from "../../../common-libs/underscore" +import {LokiProtocolIndex} from "./LokiProtocolIndex" + +const constants = require('../../../constants') + +export class LokiTransactions extends LokiProtocolIndex<DBTx> implements TxsDAO { + + constructor(loki: any) { + super(loki, 'txs', []) + this.sandbox = new SandBox( + constants.SANDBOX_SIZE_TRANSACTIONS, + () => this.getSandboxTxs(), + (compared: { issuers: string[], output_base: number, output_amount: number }, + reference: { issuers: string[], output_base: number, output_amount: number } + ) => { + if (compared.output_base < reference.output_base) { + return -1; + } + else if (compared.output_base > reference.output_base) { + return 1; + } + else if (compared.output_amount > reference.output_amount) { + return -1; + } + else if (compared.output_amount < reference.output_amount) { + return 1; + } + else { + return 0; + } + }) + } + + sandbox: SandBox<{ issuers: string[]; output_base: number; output_amount: number }> + + async addLinked(tx: TransactionDTO, block_number: number, time: number): Promise<DBTx> { + const dbTx = DBTx.fromTransactionDTO(tx) + dbTx.block_number = block_number + dbTx.time = time + dbTx.received = moment().unix() + dbTx.written = true + dbTx.removed = false + dbTx.hash = tx.getHash() + await this.insertOrUpdate(dbTx) + return dbTx + } + + async addPending(dbTx: DBTx): Promise<DBTx> { + dbTx.received = moment().unix() + dbTx.written = false + dbTx.removed = false + await this.insertOrUpdate(dbTx) + return dbTx + } + + async insertOrUpdate(dbTx: DBTx): Promise<DBTx> { + const conditions = { hash: dbTx.hash } + const existing = (await this.findRaw(conditions))[0] + if (existing) { + // Existing block: we only allow to change the fork flag + this.collection + .chain() + .find(conditions) + .update(tx => { + tx.block_number = dbTx.block_number + tx.time = dbTx.time + tx.received = dbTx.received + tx.written = dbTx.written + tx.removed = dbTx.removed + tx.hash = dbTx.hash + }) + } + else if (!existing) { + await this.insert(dbTx) + } + return dbTx + } + + async getAllPending(versionMin: number): Promise<DBTx[]> { + return this.findRaw({ + written: false, + removed: false, + version: {$gte: versionMin} + }) + } + + async getLinkedWithIssuer(pubkey: string): Promise<DBTx[]> { + return this.findRaw({ + issuers: {$contains: pubkey}, + written: true + }) + } + + async getLinkedWithRecipient(pubkey: string): Promise<DBTx[]> { + const rows = await this.findRaw({ + recipients: {$contains: pubkey}, + written: true + }) + // Which does not contains the key as issuer + return Underscore.filter(rows, (row: DBTx) => row.issuers.indexOf(pubkey) === -1); + } + + async getPendingWithIssuer(pubkey: string): Promise<DBTx[]> { + return this.findRaw({ + issuers: {$contains: pubkey}, + written: false, + removed: false + }) + } + + async getPendingWithRecipient(pubkey: string): Promise<DBTx[]> { + return this.findRaw({ + recipients: {$contains: pubkey}, + written: false, + removed: false + }) + } + + async getTX(hash: string): Promise<DBTx> { + return (await this.findRaw({ + hash: hash + }))[0] + } + + async removeTX(hash: string): Promise<DBTx | null> { + let txRemoved = null + await this.collection + .chain() + .find({ + hash: hash + }) + .update(tx => { + tx.removed = true + txRemoved = tx + }) + return txRemoved + } + + async removeAll(): Promise<void> { + await this.collection + .chain() + .find({}) + .remove() + } + + async trimExpiredNonWrittenTxs(limitTime: number): Promise<void> { + await this.collection + .chain() + .find({ + written: false, + blockstampTime: {$lte: limitTime} + }) + .remove() + } + + /************************** + * SANDBOX STUFF + */ + + async getSandboxTxs() { + // SELECT * FROM txs WHERE NOT written AND NOT removed ORDER BY output_base DESC, output_amount DESC + // return this.query('SELECT * FROM sandbox_txs LIMIT ' + (this.sandbox.maxSize), []) + return this.collection + .chain() + .find({ + written: false, + removed: false + }) + .compoundsort(['output_base', ['output_amount', true]]) + .limit(this.sandbox.maxSize) + .data() + } + + getSandboxRoom() { + return this.sandbox.getSandboxRoom() + } + + setSandboxSize(maxSize: number) { + this.sandbox.maxSize = maxSize + } + +} diff --git a/app/lib/dal/indexDAL/loki/LokiTypes.ts b/app/lib/dal/indexDAL/loki/LokiTypes.ts new file mode 100644 index 0000000000000000000000000000000000000000..08b8c21dc1518487c7b53f27361afec0754691b7 --- /dev/null +++ b/app/lib/dal/indexDAL/loki/LokiTypes.ts @@ -0,0 +1,47 @@ + +export interface RealLokiCollection<T> { + + data: T[] + + length(): number + + insert(entity:T): void + + remove(entity:T): void + + find(criterion:{ [t in keyof T|'$or'|'$and']?: any }): T[] + + chain(): LokiChainableFind<T> + + setChangesApi(disable: boolean): void + + disableChangesApi: boolean + + disableDeltaChangesApi: boolean + + changes: any[] +} + +export interface LokiCollection<T> extends RealLokiCollection<T> { + + collection: RealLokiCollection<T> +} + +export interface LokiChainableFind<T> { + + find(criterion:{ [t in keyof T|'$or'|'$and']?: any }): LokiChainableFind<T> + + simplesort(prop:keyof T, desc?:boolean): LokiChainableFind<T> + + limit(l:number): LokiChainableFind<T> + + update(cb:(t:T) => void): LokiChainableFind<T> + + where(filter:(t:T) => boolean): LokiChainableFind<T> + + remove(): LokiChainableFind<T> + + compoundsort(sort:((string|((string|boolean)[]))[])): LokiChainableFind<T> + + data(): T[] +} \ No newline at end of file diff --git a/app/lib/dal/indexDAL/loki/LokiWallet.ts b/app/lib/dal/indexDAL/loki/LokiWallet.ts new file mode 100644 index 0000000000000000000000000000000000000000..caf8d31344051be9dca42cc31c05ac925945f2a2 --- /dev/null +++ b/app/lib/dal/indexDAL/loki/LokiWallet.ts @@ -0,0 +1,39 @@ +import {WalletDAO} from "../abstract/WalletDAO" +import {LokiCollectionManager} from "./LokiCollectionManager" +import {DBWallet} from "../../../db/DBWallet" + +export class LokiWallet extends LokiCollectionManager<DBWallet> implements WalletDAO { + + constructor(loki:any) { + super(loki, 'wallet', ['conditions']) + } + + cleanCache(): void { + } + + async getWallet(conditions: string): Promise<DBWallet> { + return this.collection + .find({ conditions })[0] + } + + async insertBatch(records: DBWallet[]): Promise<void> { + for (const w of records) { + this.collection.insert(w) + } + } + + async saveWallet(wallet: DBWallet): Promise<DBWallet> { + let updated = false + this.collection + .chain() + .find({ conditions: wallet.conditions }) + .update(w => { + w.balance = wallet.balance + updated = true + }) + if (!updated) { + await this.insertBatch([wallet]) + } + return wallet + } +} \ No newline at end of file diff --git a/app/lib/dal/sqliteDAL/AbstractIndex.ts b/app/lib/dal/sqliteDAL/AbstractIndex.ts deleted file mode 100644 index 298295be161751372f971eb068d046f45fe3b531..0000000000000000000000000000000000000000 --- a/app/lib/dal/sqliteDAL/AbstractIndex.ts +++ /dev/null @@ -1,61 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -import {AbstractSQLite, BeforeSaveHook} from "./AbstractSQLite"; -import {SQLiteDriver} from "../drivers/SQLiteDriver"; -import {IndexEntry, Indexer} from "../../indexer"; - -const _ = require('underscore'); - -export class AbstractIndex<T extends IndexEntry> extends AbstractSQLite<T> { - - constructor( - driver:SQLiteDriver, - table: string, - pkFields: string[] = [], - fields: string[] = [], - arrays: string[] = [], - booleans: string[] = [], - bigintegers: string[] = [], - transientFields: string[] = [], - beforeSaveHook: BeforeSaveHook<T> | null = null - ) { - super(driver, table, pkFields, fields, arrays, booleans, bigintegers, transientFields, beforeSaveHook) - } - - public async init() {} - - getWrittenOn(blockstamp:string) { - return this.query('SELECT * FROM ' + this.table + ' WHERE written_on = ?', [blockstamp]) - } - - async trimRecords(belowNumber:number) { - const belowRecords:T[] = await this.query('SELECT COUNT(*) as nbRecords, pub FROM ' + this.table + ' ' + - 'WHERE CAST(written_on as int) < ? ' + - 'GROUP BY pub ' + - 'HAVING nbRecords > 1', [belowNumber]); - const reducedByPub = Indexer.DUP_HELPERS.reduceBy(belowRecords, ['pub']); - for (const record of reducedByPub) { - const recordsOfPub = await this.query('SELECT * FROM ' + this.table + ' WHERE pub = ?', [record.pub]); - const toReduce = _.filter(recordsOfPub, (rec:T) => parseInt(rec.written_on) < belowNumber); - if (toReduce.length) { - // Clean the records in the DB - await this.exec('DELETE FROM ' + this.table + ' WHERE pub = \'' + record.pub + '\''); - const nonReduced = _.filter(recordsOfPub, (rec:T) => parseInt(rec.written_on) >= belowNumber); - const reduced = Indexer.DUP_HELPERS.reduce(toReduce); - // Persist - await this.insertBatch([reduced].concat(nonReduced)); - } - } - } -} diff --git a/app/lib/dal/sqliteDAL/AbstractSQLite.ts b/app/lib/dal/sqliteDAL/AbstractSQLite.ts index 8b9a13926764b7f8619b62dc7e57128a3afe23b8..2649520427b96e64cdde25ae981fb991596c841a 100644 --- a/app/lib/dal/sqliteDAL/AbstractSQLite.ts +++ b/app/lib/dal/sqliteDAL/AbstractSQLite.ts @@ -12,20 +12,18 @@ // GNU Affero General Public License for more details. import {SQLiteDriver} from "../drivers/SQLiteDriver" -/** - * Created by cgeek on 22/08/15. - */ +import {Initiable} from "./Initiable" +import {Underscore} from "../../common-libs/underscore" +import {NewLogger} from "../../logger" +import {MonitorSQLExecutionTime} from "../../debug/MonitorSQLExecutionTime" -const _ = require('underscore'); -const co = require('co'); -const colors = require('colors'); -const logger = require('../../logger').NewLogger('sqlite'); +const logger = NewLogger('sqlite') export interface BeforeSaveHook<T> { (t:T): void } -export abstract class AbstractSQLite<T> { +export abstract class AbstractSQLite<T> extends Initiable { constructor( private driver:SQLiteDriver, @@ -38,28 +36,14 @@ export abstract class AbstractSQLite<T> { private transientFields: string[] = [], private beforeSaveHook: BeforeSaveHook<T> | null = null ) { + super() } + @MonitorSQLExecutionTime() async query(sql:string, params: any[] = []): Promise<T[]> { try { - //logger.trace(sql, JSON.stringify(params || [])); - const start = Date.now() const res = await this.driver.executeAll(sql, params || []); - const duration = Date.now() - start; - const entities = res.map((t:T) => this.toEntity(t)) - // Display result - let msg = sql + ' | %s\t==> %s rows in %s ms'; - if (duration <= 2) { - msg = colors.green(msg); - } else if(duration <= 5) { - msg = colors.yellow(msg); - } else if (duration <= 10) { - msg = colors.magenta(msg); - } else if (duration <= 100) { - msg = colors.red(msg); - } - logger.query(msg, JSON.stringify(params || []), entities.length, duration); - return entities; + return res.map((t:T) => this.toEntity(t)) } catch (e) { logger.error('ERROR >> %s', sql, JSON.stringify(params || []), e.stack || e.message || e); throw e; @@ -81,7 +65,7 @@ export abstract class AbstractSQLite<T> { sqlFind(obj:any, sortObj:any = {}): Promise<T[]> { const conditions = this.toConditionsArray(obj).join(' and '); const values = this.toParams(obj); - const sortKeys: string[] = _.keys(sortObj); + const sortKeys: string[] = Underscore.keys(sortObj) const sort = sortKeys.length ? ' ORDER BY ' + sortKeys.map((k) => "`" + k + "` " + (sortObj[k] ? 'DESC' : 'ASC')).join(',') : ''; return this.query('SELECT * FROM ' + this.table + ' WHERE ' + conditions + sort, values); } @@ -92,12 +76,12 @@ export abstract class AbstractSQLite<T> { } sqlFindLikeAny(obj:any): Promise<T[]> { - const keys:string[] = _.keys(obj); + const keys:string[] = Underscore.keys(obj) return this.query('SELECT * FROM ' + this.table + ' WHERE ' + keys.map((k) => 'UPPER(`' + k + '`) like ?').join(' or '), keys.map((k) => obj[k].toUpperCase())) } async sqlRemoveWhere(obj:any): Promise<void> { - const keys:string[] = _.keys(obj); + const keys:string[] = Underscore.keys(obj) await this.query('DELETE FROM ' + this.table + ' WHERE ' + keys.map((k) => '`' + k + '` = ?').join(' and '), keys.map((k) => obj[k])) } @@ -145,14 +129,9 @@ export abstract class AbstractSQLite<T> { await this.query('DELETE FROM ' + this.table + ' WHERE ' + conditions, condValues) } - exec(sql:string): Promise<void> { - try { - //console.warn(sql); - return this.driver.executeSql(sql); - } catch (e) { - //console.error('ERROR >> %s', sql); - throw e; - } + @MonitorSQLExecutionTime() + async exec(sql:string) { + await this.driver.executeSql(sql) } getInsertQuery(): string { @@ -197,8 +176,14 @@ export abstract class AbstractSQLite<T> { } } + /** + * To redefine if necessary in subclasses. + */ + cleanCache() { + } + private toConditionsArray(obj:any): string[] { - return _.keys(obj).map((k:string) => { + return Underscore.keys(obj).map((k:string) => { if (obj[k].$lte !== undefined) { return '`' + k + '` <= ?'; } else if (obj[k].$gte !== undefined) { @@ -219,7 +204,7 @@ export abstract class AbstractSQLite<T> { private toParams(obj:any, fields:string[] | null = null): any[] { let params:any[] = []; - (fields || _.keys(obj)).forEach((f:string) => { + (fields || Underscore.keys(obj)).forEach((f:string) => { if (obj[f].$null === undefined) { let pValue; if (obj[f].$lte !== undefined) { pValue = obj[f].$lte; } diff --git a/app/lib/dal/sqliteDAL/BlockDAL.ts b/app/lib/dal/sqliteDAL/BlockDAL.ts deleted file mode 100644 index a595055e0ad00fa79937ad40755edfdfaa6e9b40..0000000000000000000000000000000000000000 --- a/app/lib/dal/sqliteDAL/BlockDAL.ts +++ /dev/null @@ -1,182 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -import {AbstractSQLite} from "./AbstractSQLite" -import {SQLiteDriver} from "../drivers/SQLiteDriver" -import {DBBlock} from "../../db/DBBlock" - -const constants = require('../../constants'); - -const IS_FORK = true; -const IS_NOT_FORK = false; - -export class BlockDAL extends AbstractSQLite<DBBlock> { - - private current: any - - constructor(driver:SQLiteDriver) { - super( - driver, - 'block', - // PK fields - ['number','hash'], - // Fields - ['fork', 'hash', 'inner_hash', 'signature', 'currency', 'issuer', 'issuersCount', 'issuersFrame', 'issuersFrameVar', 'parameters', 'previousHash', 'previousIssuer', 'version', 'membersCount', 'monetaryMass', 'UDTime', 'medianTime', 'dividend', 'unitbase', 'time', 'powMin', 'number', 'nonce', 'transactions', 'certifications', 'identities', 'joiners', 'actives', 'leavers', 'revoked', 'excluded', 'len'], - // Arrays - ['identities','certifications','actives','revoked','excluded','leavers','joiners','transactions'], - // Booleans - ['wrong'], - // BigIntegers - ['monetaryMass'], - // Transient - [] - ) - - /** - * Periodically cleans the current block cache. - * It seems the cache is not always correct and may stuck the node, so it is preferable to reset it periodically. - */ - setInterval(this.cleanCache, constants.CURRENT_BLOCK_CACHE_DURATION); - } - - async init() { - await this.exec('BEGIN;' + - 'CREATE TABLE IF NOT EXISTS ' + this.table + ' (' + - 'fork BOOLEAN NOT NULL,' + - 'hash VARCHAR(64) NOT NULL,' + - 'inner_hash VARCHAR(64) NOT NULL,' + - 'signature VARCHAR(100) NOT NULL,' + - 'currency VARCHAR(50) NOT NULL,' + - 'issuer VARCHAR(50) NOT NULL,' + - 'parameters VARCHAR(255),' + - 'previousHash VARCHAR(64),' + - 'previousIssuer VARCHAR(50),' + - 'version INTEGER NOT NULL,' + - 'membersCount INTEGER NOT NULL,' + - 'monetaryMass VARCHAR(100) DEFAULT \'0\',' + - 'UDTime DATETIME,' + - 'medianTime DATETIME NOT NULL,' + - 'dividend INTEGER DEFAULT \'0\',' + - 'unitbase INTEGER NULL,' + - 'time DATETIME NOT NULL,' + - 'powMin INTEGER NOT NULL,' + - 'number INTEGER NOT NULL,' + - 'nonce INTEGER NOT NULL,' + - 'transactions TEXT,' + - 'certifications TEXT,' + - 'identities TEXT,' + - 'joiners TEXT,' + - 'actives TEXT,' + - 'leavers TEXT,' + - 'revoked TEXT,' + - 'excluded TEXT,' + - 'created DATETIME DEFAULT NULL,' + - 'updated DATETIME DEFAULT NULL,' + - 'PRIMARY KEY (number,hash)' + - ');' + - 'CREATE INDEX IF NOT EXISTS idx_block_hash ON block (hash);' + - 'CREATE INDEX IF NOT EXISTS idx_block_fork ON block (fork);' + - 'COMMIT;') - } - - cleanCache() { - this.current = null - } - - async getCurrent() { - if (!this.current) { - this.current = (await this.query('SELECT * FROM block WHERE NOT fork ORDER BY number DESC LIMIT 1'))[0]; - } - return Promise.resolve(this.current) - } - - async getBlock(number:string | number) { - return (await this.query('SELECT * FROM block WHERE number = ? and NOT fork', [parseInt(String(number))]))[0]; - } - - async getAbsoluteBlock(number:number, hash:string) { - return (await this.query('SELECT * FROM block WHERE number = ? and hash = ?', [number, hash]))[0]; - } - - getBlocks(start:number, end:number) { - return this.query('SELECT * FROM block WHERE number BETWEEN ? and ? and NOT fork ORDER BY number ASC', [start, end]); - } - - async lastBlockWithDividend() { - return (await this.query('SELECT * FROM block WHERE dividend > 0 and NOT fork ORDER BY number DESC LIMIT 1'))[0]; - } - - async lastBlockOfIssuer(issuer:string) { - return (await this.query('SELECT * FROM block WHERE issuer = ? and NOT fork ORDER BY number DESC LIMIT 1', [issuer]))[0] - } - - async getCountOfBlocksIssuedBy(issuer:string) { - let res: any = await this.query('SELECT COUNT(*) as quantity FROM block WHERE issuer = ? and NOT fork', [issuer]); - return res[0].quantity; - } - - getForkBlocks() { - return this.query('SELECT * FROM block WHERE fork ORDER BY number'); - } - - getPotentialForkBlocks(numberStart:number, medianTimeStart:number, maxNumber:number) { - return this.query('SELECT * FROM block WHERE fork AND number >= ? AND number <= ? AND medianTime >= ? ORDER BY number DESC', [numberStart, maxNumber, medianTimeStart]); - } - - getPotentialRoots() { - return this.query('SELECT * FROM block WHERE fork AND number = ?', [0]) - } - - getDividendBlocks() { - return this.query('SELECT * FROM block WHERE dividend IS NOT NULL ORDER BY number'); - } - - async saveBunch(blocks:DBBlock[]) { - let queries = "INSERT INTO block (" + this.fields.join(',') + ") VALUES "; - for (let i = 0, len = blocks.length; i < len; i++) { - let block = blocks[i]; - queries += this.toInsertValues(block); - if (i + 1 < len) { - queries += ",\n"; - } - } - await this.exec(queries); - this.cleanCache(); - } - - async saveBlock(block:DBBlock) { - let saved = await this.saveBlockAs(block, IS_NOT_FORK); - if (!this.current || this.current.number < block.number) { - this.current = block; - } - return saved; - } - - saveSideBlock(block:DBBlock) { - return this.saveBlockAs(block, IS_FORK) - } - - private async saveBlockAs(block:DBBlock, fork:boolean) { - block.fork = fork; - return await this.saveEntity(block); - } - - async setSideBlock(number:number, previousBlock:DBBlock) { - await this.query('UPDATE block SET fork = ? WHERE number = ?', [true, number]); - this.current = previousBlock; - } - - getNextForkBlocks(number:number, hash:string) { - return this.query('SELECT * FROM block WHERE fork AND number = ? AND previousHash like ? ORDER BY number', [number + 1, hash]); - } -} diff --git a/app/lib/dal/sqliteDAL/CertDAL.ts b/app/lib/dal/sqliteDAL/CertDAL.ts index 2db48d0aeb067ccaacdfb32fb4df4a240b473d70..7cc11df2ee72e677f0dce703f5c629ae5b4b676f 100644 --- a/app/lib/dal/sqliteDAL/CertDAL.ts +++ b/app/lib/dal/sqliteDAL/CertDAL.ts @@ -13,16 +13,16 @@ import {SQLiteDriver} from "../drivers/SQLiteDriver" import {AbstractSQLite} from "./AbstractSQLite" -import { SandBox } from './SandBox'; -import { DBDocument } from './DocumentDAL'; +import {SandBox} from './SandBox'; +import {DBDocument} from './DocumentDAL'; const constants = require('../../constants'); export interface DBCert extends DBDocument { linked:boolean written:boolean - written_block:null - written_hash:null + written_block:number|null + written_hash:string|null sig:string block_number:number block_hash:string diff --git a/app/lib/dal/sqliteDAL/IdentityDAL.ts b/app/lib/dal/sqliteDAL/IdentityDAL.ts index b87f98e8ddf4560fd717a9591ddadd0cbd422d7b..af22cf43e9588faa108ab29d184cb6eb52e7c56e 100644 --- a/app/lib/dal/sqliteDAL/IdentityDAL.ts +++ b/app/lib/dal/sqliteDAL/IdentityDAL.ts @@ -13,10 +13,11 @@ import {AbstractSQLite} from "./AbstractSQLite" import {SQLiteDriver} from "../drivers/SQLiteDriver" -import { SandBox } from './SandBox'; +import {SandBox} from './SandBox'; import {IdentityDTO} from "../../dto/IdentityDTO" import {Cloneable} from "../../dto/Cloneable"; -import { DBDocument } from './DocumentDAL'; +import {DBDocument} from './DocumentDAL'; + const constants = require('../../constants'); export abstract class DBIdentity implements Cloneable { @@ -26,7 +27,19 @@ export abstract class DBIdentity implements Cloneable { } certs:any[] = [] - signed:any[] = [] + signed: { + idty: { + pubkey: string + uid: string + buid: string + sig: string + member: string + wasMember: string + } + block_number: number + block_hash: string + sig: string + }[] = [] revoked: boolean currentMSN: null @@ -47,7 +60,11 @@ export abstract class DBIdentity implements Cloneable { expires_on: number getTargetHash() { - return IdentityDTO.getTargetHash(this) + return IdentityDTO.getTargetHash({ + pub: this.pubkey, + created_on: this.buid, + uid: this.uid + }) } json() { @@ -71,7 +88,7 @@ export abstract class DBIdentity implements Cloneable { "timestamp": this.buid }, "revoked": this.revoked, - "revoked_on": this.revoked_on, + "revoked_on": parseInt(String(this.revoked_on)), "revocation_sig": this.revocation_sig, "self": this.sig, "others": others @@ -157,7 +174,7 @@ export class ExistingDBIdentity extends DBIdentity { } } -export interface DBSandboxIdentity extends DBIdentity,DBDocument { +export interface DBSandboxIdentity extends DBDocument { certsCount: number ref_block: number } @@ -254,6 +271,10 @@ export class IdentityDAL extends AbstractSQLite<DBIdentity> { }) } + setRevoked(pubkey: string) { + return this.query('UPDATE ' + this.table + ' SET revoked = ? WHERE pubkey = ?', [true, pubkey]) + } + getByHash(hash:string) { return this.sqlFindOne({ hash: hash @@ -278,7 +299,7 @@ export class IdentityDAL extends AbstractSQLite<DBIdentity> { getPendingIdentities() { return this.sqlFind({ - revocation_sig: { $null: false }, + revocation_sig: { $null: true }, revoked: false }) } diff --git a/app/lib/dal/sqliteDAL/Initiable.ts b/app/lib/dal/sqliteDAL/Initiable.ts new file mode 100644 index 0000000000000000000000000000000000000000..c81fd7b7bf6f46ed257381a2241f8564054284b1 --- /dev/null +++ b/app/lib/dal/sqliteDAL/Initiable.ts @@ -0,0 +1,5 @@ + +export abstract class Initiable { + abstract init(): Promise<void> + abstract cleanCache(): void +} diff --git a/app/lib/dal/sqliteDAL/MembershipDAL.ts b/app/lib/dal/sqliteDAL/MembershipDAL.ts index 825559f03ef20efdcf91c002293a1c2950fa0f77..ddadff48d9138f1bc567666eb79405fa918584b2 100644 --- a/app/lib/dal/sqliteDAL/MembershipDAL.ts +++ b/app/lib/dal/sqliteDAL/MembershipDAL.ts @@ -13,9 +13,10 @@ import {SQLiteDriver} from "../drivers/SQLiteDriver"; import {AbstractSQLite} from "./AbstractSQLite"; -import { SandBox } from './SandBox'; -import { DBDocument } from './DocumentDAL'; -const _ = require('underscore'); +import {SandBox} from './SandBox'; +import {DBDocument} from './DocumentDAL'; +import {Underscore} from "../../common-libs/underscore" + const constants = require('../../constants'); export interface DBMembership extends DBDocument { @@ -127,7 +128,7 @@ export class MembershipDAL extends AbstractSQLite<DBMembership> { savePendingMembership(ms:DBMembership) { ms.membership = ms.membership.toUpperCase(); ms.written = false; - return this.saveEntity(_.pick(ms, 'membership', 'issuer', 'number', 'blockNumber', 'blockHash', 'userid', 'certts', 'block', 'fpr', 'idtyHash', 'expires_on', 'written', 'written_number', 'signature')) + return this.saveEntity(Underscore.pick(ms, 'membership', 'issuer', 'number', 'blockNumber', 'blockHash', 'userid', 'certts', 'block', 'fpr', 'idtyHash', 'expires_on', 'written', 'written_number', 'signature')) } async deleteMS(ms:DBMembership) { @@ -146,7 +147,7 @@ export class MembershipDAL extends AbstractSQLite<DBMembership> { return this.query('SELECT * FROM sandbox_memberships LIMIT ' + (this.sandbox.maxSize), []) } - sandbox = new SandBox(constants.SANDBOX_SIZE_MEMBERSHIPS, this.getSandboxMemberships.bind(this), (compared:DBMembership, reference:DBMembership) => { + sandbox = new SandBox(constants.SANDBOX_SIZE_MEMBERSHIPS, this.getSandboxMemberships.bind(this), (compared:{ block_number: number, issuers: string[] }, reference:{ block_number: number, issuers: string[] }) => { if (compared.block_number < reference.block_number) { return -1; } diff --git a/app/lib/dal/sqliteDAL/MetaDAL.ts b/app/lib/dal/sqliteDAL/MetaDAL.ts index fe48511cb584fec8c4a98366dec77593d197b77c..53b7b49086d1d7f1a21ff53d8448f7985ca1be03 100644 --- a/app/lib/dal/sqliteDAL/MetaDAL.ts +++ b/app/lib/dal/sqliteDAL/MetaDAL.ts @@ -14,23 +14,10 @@ import {AbstractSQLite} from "./AbstractSQLite" import {SQLiteDriver} from "../drivers/SQLiteDriver" import {ConfDTO} from "../../dto/ConfDTO" -import {SindexEntry} from "../../indexer" -import {hashf} from "../../common" import {TransactionDTO} from "../../dto/TransactionDTO" -import {BlockDAL} from "./BlockDAL" import {IdentityDAL} from "./IdentityDAL" -import {SIndexDAL} from "./index/SIndexDAL" -import {WalletDAL} from "./WalletDAL" -import {MIndexDAL} from "./index/MIndexDAL" -import {DBBlock} from "../../db/DBBlock" -import {IdentityDTO} from "../../dto/IdentityDTO" -import {rawer} from "../../common-libs/index" -import {CommonConstants} from "../../common-libs/constants" -import {TxsDAL} from "./TxsDAL" - -const _ = require('underscore') + const logger = require('../../logger').NewLogger('metaDAL'); -const constants = require('./../../constants'); export interface DBMeta { id: number, @@ -67,7 +54,71 @@ export class MetaDAL extends AbstractSQLite<DBMeta> { private migrations:any = { // Test - 0: 'BEGIN; COMMIT;', + 0: 'BEGIN;' + + + // This table was initially created by BlockDAL, but now it has been removed so we keep it here + // to keep the unit tests work + 'CREATE TABLE IF NOT EXISTS block (' + + 'fork BOOLEAN NOT NULL,' + + 'hash VARCHAR(64) NOT NULL,' + + 'inner_hash VARCHAR(64) NOT NULL,' + + 'signature VARCHAR(100) NOT NULL,' + + 'currency VARCHAR(50) NOT NULL,' + + 'issuer VARCHAR(50) NOT NULL,' + + 'parameters VARCHAR(255),' + + 'previousHash VARCHAR(64),' + + 'previousIssuer VARCHAR(50),' + + 'version INTEGER NOT NULL,' + + 'membersCount INTEGER NOT NULL,' + + 'monetaryMass VARCHAR(100) DEFAULT \'0\',' + + 'UDTime DATETIME,' + + 'medianTime DATETIME NOT NULL,' + + 'dividend INTEGER DEFAULT \'0\',' + + 'unitbase INTEGER NULL,' + + 'time DATETIME NOT NULL,' + + 'powMin INTEGER NOT NULL,' + + 'number INTEGER NOT NULL,' + + 'nonce INTEGER NOT NULL,' + + 'transactions TEXT,' + + 'certifications TEXT,' + + 'identities TEXT,' + + 'joiners TEXT,' + + 'actives TEXT,' + + 'leavers TEXT,' + + 'revoked TEXT,' + + 'excluded TEXT,' + + 'created DATETIME DEFAULT NULL,' + + 'updated DATETIME DEFAULT NULL,' + + 'PRIMARY KEY (number,hash)' + + ');' + + 'CREATE INDEX IF NOT EXISTS idx_block_hash ON block (hash);' + + 'CREATE INDEX IF NOT EXISTS idx_block_fork ON block (fork);' + + + // Same, but for Transactions + 'CREATE TABLE IF NOT EXISTS txs (' + + 'hash CHAR(64) NOT NULL,' + + 'block_number INTEGER,' + + 'locktime INTEGER NOT NULL,' + + 'version INTEGER NOT NULL,' + + 'currency VARCHAR(50) NOT NULL,' + + 'comment VARCHAR(255) NOT NULL,' + + 'time DATETIME,' + + 'inputs TEXT NOT NULL,' + + 'unlocks TEXT NOT NULL,' + + 'outputs TEXT NOT NULL,' + + 'issuers TEXT NOT NULL,' + + 'signatures TEXT NOT NULL,' + + 'recipients TEXT NOT NULL,' + + 'written BOOLEAN NOT NULL,' + + 'removed BOOLEAN NOT NULL,' + + 'PRIMARY KEY (hash)' + + ');' + + 'CREATE INDEX IF NOT EXISTS idx_txs_issuers ON txs (issuers);' + + 'CREATE INDEX IF NOT EXISTS idx_txs_written ON txs (written);' + + 'CREATE INDEX IF NOT EXISTS idx_txs_removed ON txs (removed);' + + 'CREATE INDEX IF NOT EXISTS idx_txs_hash ON txs (hash);' + + + 'COMMIT;', // Test 1: 'BEGIN;' + @@ -106,7 +157,7 @@ export class MetaDAL extends AbstractSQLite<DBMeta> { 'ALTER TABLE block ADD COLUMN issuersCount INTEGER NULL;' + 'COMMIT;', 12: async () => { - let blockDAL = new BlockDAL(this.driverCopy) + let blockDAL = new MetaDAL(this.driverCopy) await blockDAL.exec('ALTER TABLE block ADD COLUMN len INTEGER NULL;'); await blockDAL.exec('ALTER TABLE txs ADD COLUMN len INTEGER NULL;'); }, @@ -148,161 +199,12 @@ export class MetaDAL extends AbstractSQLite<DBMeta> { 16: async () => {}, 17: async () => { - let blockDAL = new BlockDAL(this.driverCopy) - let sindexDAL = new SIndexDAL(this.driverCopy) - const blocks = await blockDAL.query('SELECT * FROM block WHERE NOT fork'); - type AmountPerKey = { - amounts: { - amount: number - comment:string - }[], - sources: { - amount:number - base:number - identifier:string - pos:number, - conditions:string - block:DBBlock, - tx:string|null - }[] - } - const amountsPerKey:{ [pub:string]: AmountPerKey[] } = {} - const members = []; - for (const b of blocks) { - const amountsInForBlockPerKey: { [pub:string]: AmountPerKey } = {}; - for (const idty of b.identities) { - members.push(IdentityDTO.fromInline(idty).pubkey) - } - if (b.dividend) { - for (const member of members) { - amountsInForBlockPerKey[member] = amountsInForBlockPerKey[member] || { amounts: [], sources: [] }; - amountsInForBlockPerKey[member].amounts.push({ amount: b.dividend * Math.pow(10, b.unitbase), comment: 'Dividend' }); - amountsInForBlockPerKey[member].sources.push({ amount: b.dividend, base: b.unitbase, identifier: member, pos: b.number, block: b, tx: null, conditions: 'SIG(' + member + ')' }); - } - } - const txs = b.transactions - for (let i = 0; i < txs.length; i++) { - const tx = txs[i]; - tx.hash = hashf(rawer.getTransaction(b.transactions[i])) - for (const input of tx.inputsAsObjects()) { - amountsInForBlockPerKey[tx.issuers[0]] = amountsInForBlockPerKey[tx.issuers[0]] || { amounts: [], sources: [] }; - amountsInForBlockPerKey[tx.issuers[0]].amounts.push({ amount: -input.amount * Math.pow(10, input.base), comment: tx.comment || '######' }); - amountsInForBlockPerKey[tx.issuers[0]].sources.push({ - amount: input.amount, - base: input.base, - identifier: input.identifier, - pos: input.pos, - conditions: "", - block: b, - tx: tx.hash - }) - } - const outputObjects = tx.outputsAsObjects() - for (let j = 0; j < outputObjects.length; j++) { - const output = outputObjects[j] - const conditions = output.conditions.match(/^SIG\((.+)\)$/); - if (conditions) { - amountsInForBlockPerKey[conditions[1]] = amountsInForBlockPerKey[conditions[1]] || { amounts: [], sources: [] }; - amountsInForBlockPerKey[conditions[1]].amounts.push({ amount: output.amount * Math.pow(10, output.base), comment: tx.comment || '######' }); - amountsInForBlockPerKey[conditions[1]].sources.push({ - amount: output.amount, - base: output.base, - identifier: tx.hash, - pos: j, - conditions: output.conditions, - block: b, - tx: tx.hash - }) - } - } - } - for (const key of Object.keys(amountsInForBlockPerKey)) { - amountsPerKey[key] = amountsPerKey[key] || []; - amountsPerKey[key].push(amountsInForBlockPerKey[key]); - } - } - const keysToSee = Object.keys(amountsPerKey); - const sourcesMovements: SindexEntry[] = []; - for (const key of keysToSee) { - const allCreates: any = {}; - const allUpdates: any = {}; - const amounts = amountsPerKey[key]; - let balance = 0; - for (let j = 0; j < amounts.length; j++) { - const amountsInBlock = amounts[j].amounts; - for (let i = 0; i < amountsInBlock.length; i++) { - const a = amountsInBlock[i].amount; - const id = [amounts[j].sources[i].identifier, amounts[j].sources[i].pos].join('-'); - if (a < 0) { - allUpdates[id] = amounts[j].sources[i]; - delete allCreates[id]; - } else { - allCreates[id] = amounts[j].sources[i]; - } - balance += a; - } - if (balance > 0 && balance < 100) { - const sourcesToDelete = []; - for (const k of Object.keys(amountsPerKey)) { - for (const packet of amountsPerKey[k]) { - for (const src of packet.sources) { - const id = [src.identifier, src.pos].join('-'); - if (src.conditions == 'SIG(' + key + ')' && allCreates[id]) { - sourcesToDelete.push(src); - } - } - } - } - const amountsToDelete = sourcesToDelete.map((src) => { - return { - amount: -src.amount * Math.pow(10, src.base), - comment: '--DESTRUCTION--' - }; - }); - amounts.splice(j + 1, 0, { amounts: amountsToDelete, sources: sourcesToDelete }); - } - } - let amountMissing = 0; - await Promise.all(_.values(allCreates).map(async (src:any) => { - const exist = await sindexDAL.getSource(src.identifier, src.pos); - if (!exist || exist.consumed) { - amountMissing += src.amount; - const block = src.block; - sourcesMovements.push({ - index: CommonConstants.I_INDEX, - op: CommonConstants.IDX_CREATE, - tx: src.tx, - identifier: src.identifier, - pos: src.pos, - unlock: null, - age: 0, - txObj: TransactionDTO.mock(), - created_on: null, - written_on: [block.number, block.hash].join('-'), - writtenOn: block.number, - written_time: block.medianTime, - locktime: src.locktime, - amount: src.amount, - base: src.base, - conditions: src.conditions, - consumed: false - }); - } - })) - let amountNotDestroyed = 0; - await Promise.all(_.values(allUpdates).map(async (src:any) => { - const exist = await sindexDAL.getSource(src.identifier, src.pos); - if (exist && !exist.consumed) { - amountNotDestroyed += src.amount; - } - })) - } - await sindexDAL.insertBatch(sourcesMovements); + // This migration is now obsolete }, 18: 'BEGIN;' + // Add a `massReeval` column - 'ALTER TABLE b_index ADD COLUMN massReeval VARCHAR(100) NOT NULL DEFAULT \'0\';' + + // 'ALTER TABLE b_index ADD COLUMN massReeval VARCHAR(100) NOT NULL DEFAULT \'0\';' + 'COMMIT;', 19: 'BEGIN;' + @@ -314,33 +216,9 @@ export class MetaDAL extends AbstractSQLite<DBMeta> { * Feeds the table of wallets with balances */ 20: async () => { - let walletDAL = new WalletDAL(this.driverCopy) - let sindexDAL = new SIndexDAL(this.driverCopy) - const conditions = await sindexDAL.query('SELECT DISTINCT(conditions) FROM s_index') - for (const row of conditions) { - const wallet = { - conditions: row.conditions, - balance: 0 - } - const amountsRemaining = await sindexDAL.getAvailableForConditions(row.conditions) - wallet.balance = amountsRemaining.reduce((sum:number, src:SindexEntry) => sum + src.amount * Math.pow(10, src.base), 0) - await walletDAL.saveWallet(wallet) - } }, - /** - * Feeds the m_index.chainable_on - */ 21: async (conf:ConfDTO) => { - let blockDAL = new BlockDAL(this.driverCopy) - let mindexDAL = new MIndexDAL(this.driverCopy) - await mindexDAL.exec('ALTER TABLE m_index ADD COLUMN chainable_on INTEGER NULL;') - const memberships = await mindexDAL.query('SELECT * FROM m_index WHERE op = ?', [CommonConstants.IDX_CREATE]) - for (const ms of memberships) { - const reference = await blockDAL.getBlock(parseInt(ms.written_on.split('-')[0])) - const updateQuery = 'UPDATE m_index SET chainable_on = ' + (reference.medianTime + conf.msPeriod) + ' WHERE pub = \'' + ms.pub + '\' AND op = \'CREATE\'' - await mindexDAL.exec(updateQuery) - } }, // Replay the wallet table feeding, because of a potential bug @@ -349,41 +227,19 @@ export class MetaDAL extends AbstractSQLite<DBMeta> { }, 23: 'BEGIN;' + - // Add a `writtenOn` column for MISC Index - 'ALTER TABLE m_index ADD COLUMN writtenOn INTEGER NOT NULL DEFAULT 0;' + - 'ALTER TABLE i_index ADD COLUMN writtenOn INTEGER NOT NULL DEFAULT 0;' + - 'ALTER TABLE s_index ADD COLUMN writtenOn INTEGER NOT NULL DEFAULT 0;' + - 'ALTER TABLE c_index ADD COLUMN writtenOn INTEGER NOT NULL DEFAULT 0;' + - 'CREATE INDEX IF NOT EXISTS idx_mindex_writtenOn ON m_index (writtenOn);' + - 'CREATE INDEX IF NOT EXISTS idx_iindex_writtenOn ON i_index (writtenOn);' + - 'CREATE INDEX IF NOT EXISTS idx_sindex_writtenOn ON s_index (writtenOn);' + - 'CREATE INDEX IF NOT EXISTS idx_cindex_writtenOn ON c_index (writtenOn);' + - 'UPDATE m_index SET writtenOn = CAST(written_on as integer);' + - 'UPDATE i_index SET writtenOn = CAST(written_on as integer);' + - 'UPDATE s_index SET writtenOn = CAST(written_on as integer);' + - 'UPDATE c_index SET writtenOn = CAST(written_on as integer);' + 'COMMIT;', /** * Feeds the m_index.chainable_on correctly */ 24: async (conf:ConfDTO) => { - let blockDAL = new BlockDAL(this.driverCopy) - let mindexDAL = new MIndexDAL(this.driverCopy) - const memberships = await mindexDAL.query('SELECT * FROM m_index') - for (const ms of memberships) { - const reference = await blockDAL.getBlock(parseInt(ms.written_on.split('-')[0])) - const msPeriod = conf.msWindow // It has the same value, as it was not defined on currency init - const updateQuery = 'UPDATE m_index SET chainable_on = ' + (reference.medianTime + msPeriod) + ' WHERE pub = \'' + ms.pub + '\' AND written_on = \'' + ms.written_on + '\'' - await mindexDAL.exec(updateQuery) - } }, /** * Wrong transaction storage */ 25: async () => { - const txsDAL = new TxsDAL(this.driverCopy) + const txsDAL:any = new MetaDAL(this.driverCopy) const wrongTXS = await txsDAL.query('SELECT * FROM txs WHERE outputs LIKE ? OR inputs LIKE ?', ['%amount%', '%amount%']) let i = 1 for (const tx of wrongTXS) { diff --git a/app/lib/dal/sqliteDAL/PeerDAL.ts b/app/lib/dal/sqliteDAL/PeerDAL.ts deleted file mode 100644 index e5662fa8a7ddf683b6000c944052df7c155490d5..0000000000000000000000000000000000000000 --- a/app/lib/dal/sqliteDAL/PeerDAL.ts +++ /dev/null @@ -1,124 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -import {SQLiteDriver} from "../drivers/SQLiteDriver" -import {AbstractSQLite} from "./AbstractSQLite" - -export class DBPeer { - - version: number - currency: string - status: string - statusTS: number - hash: string - first_down: number | null - last_try: number | null - pubkey: string - block: string - signature: string - endpoints: string[] - raw: string - - json() { - return { - version: this.version, - currency: this.currency, - endpoints: this.endpoints, - status: this.status, - block: this.block, - signature: this.signature, - raw: this.raw, - pubkey: this.pubkey - } - } -} - -export class PeerDAL extends AbstractSQLite<DBPeer> { - - constructor(driver:SQLiteDriver) { - super( - driver, - 'peer', - // PK fields - ['pubkey'], - // Fields - [ - 'version', - 'currency', - 'status', - 'statusTS', - 'hash', - 'first_down', - 'last_try', - 'pubkey', - 'block', - 'signature', - 'endpoints', - 'raw' - ], - // Arrays - ['endpoints'], - // Booleans - [], - // BigIntegers - [], - // Transient - [] - ) - } - - async init() { - await this.exec('BEGIN;' + - 'CREATE TABLE IF NOT EXISTS ' + this.table + ' (' + - 'version INTEGER NOT NULL,' + - 'currency VARCHAR(50) NOT NULL,' + - 'status VARCHAR(10),' + - 'statusTS INTEGER NOT NULL,' + - 'hash CHAR(64),' + - 'first_down INTEGER,' + - 'last_try INTEGER,' + - 'pubkey VARCHAR(50) NOT NULL,' + - 'block VARCHAR(60) NOT NULL,' + - 'signature VARCHAR(100),' + - 'endpoints TEXT NOT NULL,' + - 'raw TEXT,' + - 'PRIMARY KEY (pubkey)' + - ');' + - 'CREATE INDEX IF NOT EXISTS idx_link_source ON peer (pubkey);' + - 'COMMIT;') - } - - listAll() { - return this.sqlListAll() - } - - getPeer(pubkey:string) { - return this.sqlFindOne({ pubkey: pubkey }) - } - - getPeersWithEndpointsLike(str:string) { - return this.query('SELECT * FROM peer WHERE endpoints LIKE ?', ['%' + str + '%']) - } - - savePeer(peer:DBPeer) { - return this.saveEntity(peer) - } - - removePeerByPubkey(pubkey:string) { - return this.exec('DELETE FROM peer WHERE pubkey LIKE \'' + pubkey + '\'') - } - - async removeAll() { - await this.sqlDeleteAll() - } -} diff --git a/app/lib/dal/sqliteDAL/TxsDAL.ts b/app/lib/dal/sqliteDAL/TxsDAL.ts deleted file mode 100644 index 278220cd13823032757bed038aad417e5adf4d1b..0000000000000000000000000000000000000000 --- a/app/lib/dal/sqliteDAL/TxsDAL.ts +++ /dev/null @@ -1,279 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -import {AbstractSQLite} from "./AbstractSQLite" -import {SQLiteDriver} from "../drivers/SQLiteDriver" -import {TransactionDTO} from "../../dto/TransactionDTO" -import {SandBox} from "./SandBox" - -const _ = require('underscore'); -const moment = require('moment'); -const constants = require('../../constants'); - -export class DBTx { - hash: string - block_number: number | null - locktime: number - version: number - currency: string - comment: string - blockstamp: string - blockstampTime: number | null - time: number | null - inputs: string[] - unlocks: string[] - outputs: string[] - issuers: string[] - signatures: string[] - recipients: string[] - written: boolean - removed: boolean - received: number - output_base: number - output_amount: number - - static fromTransactionDTO(tx:TransactionDTO) { - const dbTx = new DBTx() - dbTx.hash = tx.hash - dbTx.locktime = tx.locktime - dbTx.version = tx.version - dbTx.currency = tx.currency - dbTx.blockstamp = tx.blockstamp - dbTx.blockstampTime = tx.blockstampTime - dbTx.comment = tx.comment || "" - dbTx.inputs = tx.inputs - dbTx.unlocks = tx.unlocks - dbTx.outputs = tx.outputs - dbTx.issuers = tx.issuers - dbTx.signatures = tx.signatures - dbTx.recipients = tx.outputsAsRecipients() - dbTx.written = false - dbTx.removed = false - dbTx.output_base = tx.output_base - dbTx.output_amount = tx.output_amount - return dbTx - } - - static setRecipients(txs:DBTx[]) { - // Each transaction must have a good "recipients" field for future searchs - txs.forEach((tx) => tx.recipients = DBTx.outputs2recipients(tx)) - } - - static outputs2recipients(tx:DBTx) { - return tx.outputs.map(function(out) { - const recipent = out.match('SIG\\((.*)\\)') - return (recipent && recipent[1]) || 'UNKNOWN' - }) - } -} - -export class TxsDAL extends AbstractSQLite<DBTx> { - - constructor(driver:SQLiteDriver) { - super( - driver, - 'txs', - // PK fields - ['hash'], - // Fields - [ - 'hash', - 'block_number', - 'version', - 'currency', - 'comment', - 'blockstamp', - 'blockstampTime', - 'locktime', - 'received', - 'time', - 'written', - 'removed', - 'inputs', - 'unlocks', - 'outputs', - 'issuers', - 'signatures', - 'recipients', - 'output_base', - 'output_amount' - ], - // Arrays - ['inputs','unlocks','outputs','issuers','signatures','recipients'], - // Booleans - ['written','removed'], - // BigIntegers - [], - // Transient - [] - ) - } - - async init() { - await this.exec('BEGIN;' + - 'CREATE TABLE IF NOT EXISTS ' + this.table + ' (' + - 'hash CHAR(64) NOT NULL,' + - 'block_number INTEGER,' + - 'locktime INTEGER NOT NULL,' + - 'version INTEGER NOT NULL,' + - 'currency VARCHAR(50) NOT NULL,' + - 'comment VARCHAR(255) NOT NULL,' + - 'time DATETIME,' + - 'inputs TEXT NOT NULL,' + - 'unlocks TEXT NOT NULL,' + - 'outputs TEXT NOT NULL,' + - 'issuers TEXT NOT NULL,' + - 'signatures TEXT NOT NULL,' + - 'recipients TEXT NOT NULL,' + - 'written BOOLEAN NOT NULL,' + - 'removed BOOLEAN NOT NULL,' + - 'PRIMARY KEY (hash)' + - ');' + - 'CREATE INDEX IF NOT EXISTS idx_txs_issuers ON txs (issuers);' + - 'CREATE INDEX IF NOT EXISTS idx_txs_written ON txs (written);' + - 'CREATE INDEX IF NOT EXISTS idx_txs_removed ON txs (removed);' + - 'CREATE INDEX IF NOT EXISTS idx_txs_hash ON txs (hash);' + - 'COMMIT;') - } - - getAllPending(versionMin:number): Promise<DBTx[]> { - return this.sqlFind({ - written: false, - removed: false, - version: { $gte: versionMin } - }) - } - - getTX(hash:string): Promise<DBTx> { - return this.sqlFindOne({ - hash: hash - }) - } - - async removeTX(hash:string) { - const tx = await this.sqlFindOne({ - hash: hash - }); - if (tx) { - tx.removed = true; - return this.saveEntity(tx); - } - return tx - } - - addLinked(tx:TransactionDTO, block_number:number, time:number) { - const dbTx = DBTx.fromTransactionDTO(tx) - dbTx.block_number = block_number - dbTx.time = time - dbTx.received = moment().unix() - dbTx.written = true - dbTx.removed = false - dbTx.hash = tx.getHash() - return this.saveEntity(dbTx) - } - - addPending(dbTx:DBTx) { - dbTx.received = moment().unix() - dbTx.written = false - dbTx.removed = false - return this.saveEntity(dbTx) - } - - getLinkedWithIssuer(pubkey:string): Promise<DBTx[]> { - return this.sqlFind({ - issuers: { $contains: pubkey }, - written: true - }) - } - - async getLinkedWithRecipient(pubkey:string) { - const rows = await this.sqlFind({ - recipients: { $contains: pubkey }, - written: true - }) - // Which does not contains the key as issuer - return _.filter(rows, (row:DBTx) => row.issuers.indexOf(pubkey) === -1); - } - - getPendingWithIssuer(pubkey:string) { - return this.sqlFind({ - issuers: { $contains: pubkey }, - written: false, - removed: false - }) - } - - getPendingWithRecipient(pubkey:string) { - return this.sqlFind({ - recipients: { $contains: pubkey }, - written: false, - removed: false - }) - } - - insertBatchOfTxs(txs:DBTx[]) { - // // Be sure the recipients field are correctly updated - DBTx.setRecipients(txs); - const queries = []; - const insert = this.getInsertHead(); - const values = txs.map((cert) => this.getInsertValue(cert)); - if (txs.length) { - queries.push(insert + '\n' + values.join(',\n') + ';'); - } - if (queries.length) { - this.exec(queries.join('\n')); - } - } - - trimExpiredNonWrittenTxs(limitTime:number) { - return this.exec("DELETE FROM txs WHERE NOT written AND blockstampTime <= " + limitTime) - } - - getTransactionByExtendedHash(hash:string) { - return this.query("SELECT * FROM txs WHERE hash = ? OR v4_hash = ? OR v5_hash = ?", [hash, hash, hash]) - } - - /************************** - * SANDBOX STUFF - */ - - getSandboxTxs() { - return this.query('SELECT * FROM sandbox_txs LIMIT ' + (this.sandbox.maxSize), []) - } - - sandbox = new SandBox(constants.SANDBOX_SIZE_TRANSACTIONS, this.getSandboxTxs.bind(this), (compared:DBTx, reference:DBTx) => { - if (compared.output_base < reference.output_base) { - return -1; - } - else if (compared.output_base > reference.output_base) { - return 1; - } - else if (compared.output_amount > reference.output_amount) { - return -1; - } - else if (compared.output_amount < reference.output_amount) { - return 1; - } - else { - return 0; - } - }) - - getSandboxRoom() { - return this.sandbox.getSandboxRoom() - } - - setSandboxSize(maxSize:number) { - this.sandbox.maxSize = maxSize - } -} diff --git a/app/lib/dal/sqliteDAL/WalletDAL.ts b/app/lib/dal/sqliteDAL/WalletDAL.ts deleted file mode 100644 index 03644eac336e68f87d759b30d371e1285d08630c..0000000000000000000000000000000000000000 --- a/app/lib/dal/sqliteDAL/WalletDAL.ts +++ /dev/null @@ -1,69 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -import {SQLiteDriver} from "../drivers/SQLiteDriver"; -import {AbstractSQLite} from "./AbstractSQLite"; - -export interface DBWallet { - conditions: string - balance: number -} - -/** - * Facility table saving the current state of a wallet. - * @param driver SQL driver for making SQL requests. - * @constructor - */ -export class WalletDAL extends AbstractSQLite<DBWallet> { - - constructor(driver:SQLiteDriver) { - super( - driver, - 'wallet', - // PK fields - ['conditions'], - // Fields - [ - 'conditions', - 'balance' - ], - // Arrays - [], - // Booleans - [], - // BigIntegers - ['monetaryMass'], - // Transient - [] - ) - } - - async init() { - await this.exec('BEGIN;' + - 'CREATE TABLE IF NOT EXISTS ' + this.table + ' (' + - 'conditions TEXT NOT NULL,' + - 'balance INTEGER NOT NULL,' + - 'PRIMARY KEY (conditions)' + - ');' + - 'CREATE INDEX IF NOT EXISTS wallet_balance ON wallet(balance);' + - 'COMMIT;') - } - - getWallet(conditions:string) { - return this.sqlFindOne({ conditions }) - } - - saveWallet(wallet:DBWallet) { - return this.saveEntity(wallet) - } -} diff --git a/app/lib/dal/sqliteDAL/index/BIndexDAL.ts b/app/lib/dal/sqliteDAL/index/BIndexDAL.ts index ee205dd490d68e2d81495238c249b928a7ffd04e..2b8537825305fec4f2bf995754acb0681bb76dc8 100644 --- a/app/lib/dal/sqliteDAL/index/BIndexDAL.ts +++ b/app/lib/dal/sqliteDAL/index/BIndexDAL.ts @@ -94,7 +94,7 @@ export class BIndexDAL extends AbstractSQLite<DBHead> { * Get HEAD~n * @param n Position */ - async head(n:number) { + async head(n:number): Promise<DBHead> { if (!n) { throw "Cannot read HEAD~0, which is the incoming block" } diff --git a/app/lib/dal/sqliteDAL/index/CIndexDAL.ts b/app/lib/dal/sqliteDAL/index/CIndexDAL.ts deleted file mode 100644 index d0c58259a37ae6cc151f61cc5921f40c8e6649fe..0000000000000000000000000000000000000000 --- a/app/lib/dal/sqliteDAL/index/CIndexDAL.ts +++ /dev/null @@ -1,151 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -import {AbstractIndex} from "../AbstractIndex" -import {SQLiteDriver} from "../../drivers/SQLiteDriver" -import {CindexEntry} from "../../../indexer" -import {CommonConstants} from "../../../common-libs/constants" - -const constants = require('./../../../constants'); -const indexer = require('../../../indexer').Indexer - -export class CIndexDAL extends AbstractIndex<CindexEntry> { - - constructor(driver:SQLiteDriver) { - super( - driver, - 'c_index', - // PK fields - ['op', 'issuer', 'receiver', 'written_on'], - // Fields - [ - 'op', - 'issuer', - 'receiver', - 'created_on', - 'written_on', - 'writtenOn', - 'sig', - 'expires_on', - 'expired_on', - 'chainable_on', - 'from_wid', - 'to_wid' - ], - // Arrays - [], - // Booleans - [], - // BigIntegers - [], - // Transient - [] - ) - } - - async init() { - await this.exec('BEGIN;' + - 'CREATE TABLE IF NOT EXISTS ' + this.table + ' (' + - 'op VARCHAR(10) NOT NULL,' + - 'issuer VARCHAR(50) NOT NULL,' + - 'receiver VARCHAR(50) NOT NULL,' + - 'created_on VARCHAR(80) NOT NULL,' + - 'written_on VARCHAR(80) NOT NULL,' + - 'sig VARCHAR(100) NULL,' + - 'expires_on INTEGER NULL,' + - 'expired_on INTEGER NULL,' + - 'chainable_on INTEGER NULL,' + - 'from_wid INTEGER NULL,' + - 'to_wid INTEGER NULL,' + - 'PRIMARY KEY (op,issuer,receiver,written_on)' + - ');' + - 'CREATE INDEX IF NOT EXISTS idx_cindex_issuer ON c_index (issuer);' + - 'CREATE INDEX IF NOT EXISTS idx_cindex_receiver ON c_index (receiver);' + - 'CREATE INDEX IF NOT EXISTS idx_cindex_chainable_on ON c_index (chainable_on);' + - 'COMMIT;') - } - - async reducablesFrom(from:string) { - const reducables = await this.query('SELECT * FROM ' + this.table + ' WHERE issuer = ? ORDER BY CAST(written_on as integer) ASC', [from]); - return indexer.DUP_HELPERS.reduceBy(reducables, ['issuer', 'receiver', 'created_on']); - } - - async trimExpiredCerts(belowNumber:number) { - const toDelete = await this.query('SELECT * FROM ' + this.table + ' WHERE expired_on > ? AND CAST(written_on as int) < ?', [0, belowNumber]) - for (const row of toDelete) { - await this.exec("DELETE FROM " + this.table + " " + - "WHERE issuer like '" + row.issuer + "' " + - "AND receiver = '" + row.receiver + "' " + - "AND created_on like '" + row.created_on + "'"); - } - } - - getWrittenOn(blockstamp:string) { - return this.sqlFind({ written_on: blockstamp }) - } - - findExpired(medianTime:number) { - return this.query('SELECT * FROM ' + this.table + ' c1 WHERE expires_on <= ? ' + - 'AND NOT EXISTS (' + - ' SELECT * FROM c_index c2' + - ' WHERE c1.issuer = c2.issuer' + - ' AND c1.receiver = c2.receiver' + - ' AND c1.created_on = c2.created_on' + - ' AND c2.op = ?' + - ')', [medianTime, CommonConstants.IDX_UPDATE]) - } - - getValidLinksTo(receiver:string) { - return this.query('SELECT * FROM ' + this.table + ' c1 ' + - 'WHERE c1.receiver = ? ' + - 'AND c1.expired_on = 0 ' + - 'AND NOT EXISTS (' + - ' SELECT * FROM c_index c2' + - ' WHERE c1.issuer = c2.issuer' + - ' AND c1.receiver = c2.receiver' + - ' AND c1.created_on = c2.created_on' + - ' AND c2.op = ?' + - ')', [receiver, CommonConstants.IDX_UPDATE]) - } - - getValidLinksFrom(issuer:string) { - return this.query('SELECT * FROM ' + this.table + ' c1 ' + - 'WHERE c1.issuer = ? ' + - 'AND c1.expired_on = 0 ' + - 'AND NOT EXISTS (' + - ' SELECT * FROM c_index c2' + - ' WHERE c1.issuer = c2.issuer' + - ' AND c1.receiver = c2.receiver' + - ' AND c1.created_on = c2.created_on' + - ' AND c2.op = ?' + - ')', [issuer, CommonConstants.IDX_UPDATE]) - } - - async existsNonReplayableLink(issuer:string, receiver:string) { - const results = await this.query('SELECT * FROM ' + this.table + ' c1 ' + - 'WHERE c1.issuer = ? ' + - 'AND c1.receiver = ? ' + - 'AND NOT EXISTS (' + - ' SELECT * FROM c_index c2' + - ' WHERE c1.issuer = c2.issuer' + - ' AND c1.receiver = c2.receiver' + - ' AND c1.created_on = c2.created_on' + - ' AND c2.op = ?' + - ')', [issuer, receiver, CommonConstants.IDX_UPDATE]); - return results.length > 0; - } - - removeBlock(blockstamp:string) { - return this.exec('DELETE FROM ' + this.table + ' WHERE written_on = \'' + blockstamp + '\'') - } -} diff --git a/app/lib/dal/sqliteDAL/index/IIndexDAL.ts b/app/lib/dal/sqliteDAL/index/IIndexDAL.ts deleted file mode 100644 index c945ea994bc11b7c84a497fb51459111d58e79e3..0000000000000000000000000000000000000000 --- a/app/lib/dal/sqliteDAL/index/IIndexDAL.ts +++ /dev/null @@ -1,182 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -import {SQLiteDriver} from "../../drivers/SQLiteDriver"; -import {AbstractIndex} from "../AbstractIndex"; -import {IindexEntry, Indexer} from "../../../indexer"; - -const _ = require('underscore'); - -export interface OldIindexEntry extends IindexEntry { - pubkey: string - buid: string | null - revocation_sig:string | null -} - -export class IIndexDAL extends AbstractIndex<IindexEntry> { - - constructor(driver:SQLiteDriver) { - super( - driver, - 'i_index', - // PK fields - ['op', 'pub', 'created_on', 'written_on'], - // Fields - [ - 'op', - 'uid', - 'pub', - 'hash', - 'sig', - 'created_on', - 'written_on', - 'writtenOn', - 'member', - 'wasMember', - 'kick', - 'wotb_id' - ], - // Arrays - [], - // Booleans - ['member', 'wasMember', 'kick'], - // BigIntegers - [], - // Transient - [] - ) - } - - init() { - return this.exec('BEGIN;' + - 'CREATE TABLE IF NOT EXISTS ' + this.table + ' (' + - 'op VARCHAR(10) NOT NULL,' + - 'uid VARCHAR(100) NULL,' + - 'pub VARCHAR(50) NOT NULL,' + - 'hash VARCHAR(80) NULL,' + - 'sig VARCHAR(80) NULL,' + - 'created_on VARCHAR(80) NULL,' + - 'written_on VARCHAR(80) NOT NULL,' + - 'member BOOLEAN NULL,' + - 'wasMember BOOLEAN NULL,' + - 'kick BOOLEAN NULL,' + - 'wotb_id INTEGER NULL,' + - 'PRIMARY KEY (op,pub,created_on,written_on)' + - ');' + - 'CREATE INDEX IF NOT EXISTS idx_iindex_pub ON i_index (pub);' + - 'COMMIT;') - } - - async getMembers() { - // All those who has been subject to, or who are currently subject to kicking. Make one result per pubkey. - const pubkeys = await this.query('SELECT DISTINCT(pub) FROM ' + this.table); - // We get the full representation for each member - const reduced = await Promise.all(pubkeys.map(async (entry) => { - const reducable = await this.reducable(entry.pub); - return Indexer.DUP_HELPERS.reduce(reducable); - })); - // Filter on those to be kicked, return their pubkey - const filtered = _.filter(reduced, (entry:IindexEntry) => entry.member); - return filtered.map((t:IindexEntry) => this.toCorrectEntity(t)) - } - - getMembersPubkeys() { - return this.query('SELECT i1.pub ' + - 'FROM i_index i1 ' + - 'WHERE i1.member ' + - 'AND CAST(i1.written_on as int) = (' + - ' SELECT MAX(CAST(i2.written_on as int)) ' + - ' FROM i_index i2 ' + - ' WHERE i1.pub = i2.pub ' + - ' AND i2.member IS NOT NULL' + - ')') - } - - async getToBeKickedPubkeys() { - // All those who has been subject to, or who are currently subject to kicking. Make one result per pubkey. - const reducables = Indexer.DUP_HELPERS.reduceBy(await this.sqlFind({ kick: true }), ['pub']); - // We get the full representation for each member - const reduced = await Promise.all(reducables.map(async (entry) => { - const reducable = await this.reducable(entry.pub); - return Indexer.DUP_HELPERS.reduce(reducable); - })) - // Filter on those to be kicked, return their pubkey - return _.filter(reduced, (entry:IindexEntry) => entry.kick).map((entry:IindexEntry) => entry.pub); - } - - async searchThoseMatching(search:string) { - const reducables = Indexer.DUP_HELPERS.reduceBy(await this.sqlFindLikeAny({ - pub: "%" + search + "%", - uid: "%" + search + "%" - }), ['pub']); - // We get the full representation for each member - return await Promise.all(reducables.map(async (entry) => { - return this.toCorrectEntity(Indexer.DUP_HELPERS.reduce(await this.reducable(entry.pub))) - })) - } - - getFromPubkey(pubkey:string) { - return this.entityOrNull('pub', pubkey) - } - - getFromUID(uid:string) { - return this.entityOrNull('uid', uid) - } - - getFromHash(hash:string) { - return this.entityOrNull('hash', hash, true) - } - - reducable(pub:string) { - return this.query('SELECT * FROM ' + this.table + ' WHERE pub = ? ORDER BY CAST(written_on as integer) ASC', [pub]) - } - - removeBlock(blockstamp:string) { - return this.exec('DELETE FROM ' + this.table + ' WHERE written_on = \'' + blockstamp + '\'') - } - - private async entityOrNull(field:string, value:any, retrieveOnField:boolean = false) { - let reducable = await this.query('SELECT * FROM ' + this.table + ' WHERE ' + field + ' = ?', [value]); - if (reducable.length) { - if (retrieveOnField) { - // Force full retrieval on `pub` field - reducable = await this.query('SELECT * FROM ' + this.table + ' WHERE pub = ? ORDER BY CAST(written_on as int) ASC', [reducable[0].pub]); - } - return this.toCorrectEntity(Indexer.DUP_HELPERS.reduce(reducable)); - } - return null; - } - - private toCorrectEntity(row:IindexEntry): OldIindexEntry { - // Old field - return { - pubkey: row.pub, - pub: row.pub, - buid: row.created_on, - revocation_sig: null, - uid: row.uid, - hash: row.hash, - sig: row.sig, - created_on: row.created_on, - member: row.member, - wasMember: row.wasMember, - kick: row.kick, - wotb_id: row.wotb_id, - age: row.age, - index: row.index, - op: row.op, - writtenOn: row.writtenOn, - written_on: row.written_on - } - } -} diff --git a/app/lib/dal/sqliteDAL/index/MIndexDAL.ts b/app/lib/dal/sqliteDAL/index/MIndexDAL.ts deleted file mode 100644 index 2ff64db3b98dccf02f17ba0c788be2a6ca628fe0..0000000000000000000000000000000000000000 --- a/app/lib/dal/sqliteDAL/index/MIndexDAL.ts +++ /dev/null @@ -1,94 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -import {SQLiteDriver} from "../../drivers/SQLiteDriver"; -import {AbstractIndex} from "../AbstractIndex"; -import {Indexer, MindexEntry} from "../../../indexer"; - -export class MIndexDAL extends AbstractIndex<MindexEntry> { - - constructor(driver:SQLiteDriver) { - super( - driver, - 'm_index', - // PK fields - ['op', 'pub', 'created_on', 'written_on'], - // Fields - [ - 'op', - 'pub', - 'created_on', - 'written_on', - 'writtenOn', - 'expires_on', - 'expired_on', - 'revokes_on', - 'revoked_on', - 'chainable_on', - 'leaving', - 'revocation' - ], - // Arrays - [], - // Booleans - ['leaving'], - // BigIntegers - [], - // Transient - [] - ) - } - - async init() { - await this.exec('BEGIN;' + - 'CREATE TABLE IF NOT EXISTS ' + this.table + ' (' + - 'op VARCHAR(10) NOT NULL,' + - 'pub VARCHAR(50) NOT NULL,' + - 'created_on VARCHAR(80) NOT NULL,' + - 'written_on VARCHAR(80) NOT NULL,' + - 'expires_on INTEGER NULL,' + - 'expired_on INTEGER NULL,' + - 'revokes_on INTEGER NULL,' + - 'revoked_on INTEGER NULL,' + - 'leaving BOOLEAN NULL,' + - 'revocation VARCHAR(80) NULL,' + - 'PRIMARY KEY (op,pub,created_on,written_on)' + - ');' + - 'CREATE INDEX IF NOT EXISTS idx_mindex_pub ON m_index (pub);' + - 'COMMIT;') - } - - async getReducedMS(pub:string) { - const reducables = await this.reducable(pub); - if (reducables.length) { - return Indexer.DUP_HELPERS.reduce(reducables); - } - return null; - } - - reducable(pub:string) { - return this.query('SELECT * FROM ' + this.table + ' WHERE pub = ? ORDER BY CAST(written_on as integer) ASC', [pub]) -} - - async removeBlock(blockstamp:string) { - return this.exec('DELETE FROM ' + this.table + ' WHERE written_on = \'' + blockstamp + '\'') - } - - async getRevokedPubkeys() { - // All those who has been revoked. Make one result per pubkey. - const revovedMemberships = await this.sqlFind({ revoked_on: { $null: false} }); - - // Filter on those to be revoked, return their pubkey - return revovedMemberships.map((entry:MindexEntry) => entry.pub); - } -} diff --git a/app/lib/dal/sqliteDAL/index/SIndexDAL.ts b/app/lib/dal/sqliteDAL/index/SIndexDAL.ts deleted file mode 100644 index 13115dcec1233b67a8654b4552c80772b404e726..0000000000000000000000000000000000000000 --- a/app/lib/dal/sqliteDAL/index/SIndexDAL.ts +++ /dev/null @@ -1,142 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -import {Indexer, SindexEntry} from "../../../indexer" -import {SQLiteDriver} from "../../drivers/SQLiteDriver" -import {AbstractIndex} from "../AbstractIndex" -import {CommonConstants} from "../../../common-libs/constants" -const _ = require('underscore'); -const constants = require('../../../constants'); - -export class SIndexDAL extends AbstractIndex<SindexEntry> { - - constructor(driver:SQLiteDriver) { - super( - driver, - 's_index', - // PK fields - ['op', 'identifier', 'pos', 'written_on'], - // Fields - [ - 'op', - 'tx', - 'identifier', - 'pos', - 'created_on', - 'written_on', - 'writtenOn', - 'written_time', - 'amount', - 'base', - 'locktime', - 'consumed', - 'conditions' - ], - // Arrays - [], - // Booleans - ['consumed'], - // BigIntegers - [], - // Transient - [] - ) - } - - async init() { - await this.exec('BEGIN;' + - 'CREATE TABLE IF NOT EXISTS ' + this.table + ' (' + - 'op VARCHAR(10) NOT NULL,' + - 'tx VARCHAR(80) NULL,' + - 'identifier VARCHAR(64) NOT NULL,' + - 'pos INTEGER NOT NULL,' + - 'created_on VARCHAR(80) NULL,' + - 'written_on VARCHAR(80) NOT NULL,' + - 'written_time INTEGER NOT NULL,' + - 'amount INTEGER NULL,' + - 'base INTEGER NULL,' + - 'locktime INTEGER NULL,' + - 'consumed BOOLEAN NOT NULL,' + - 'conditions TEXT,' + - 'PRIMARY KEY (op,identifier,pos,written_on)' + - ');' + - 'CREATE INDEX IF NOT EXISTS idx_sindex_identifier ON s_index (identifier);' + - 'CREATE INDEX IF NOT EXISTS idx_sindex_pos ON s_index (pos);' + - 'COMMIT;') - } - - async removeBlock(blockstamp:string) { - await this.exec('DELETE FROM ' + this.table + ' WHERE written_on = \'' + blockstamp + '\'') - } - - async getSource(identifier:string, pos:number) { - const reducable = await this.query('SELECT * FROM ' + this.table + ' s1 ' + - 'WHERE s1.identifier = ? ' + - 'AND s1.pos = ? ' + - 'ORDER BY op ASC', [identifier, pos]); - if (reducable.length == 0) { - return null; - } else { - const src = Indexer.DUP_HELPERS.reduce(reducable); - src.type = src.tx ? 'T' : 'D'; - return src; - } - } - - async getUDSources(pubkey:string) { - const reducables = await this.query('SELECT * FROM ' + this.table + ' s1 ' + - 'WHERE conditions = ? ' + - 'AND s1.tx IS NULL ' + - 'ORDER BY op ASC', ['SIG(' + pubkey + ')']); - const reduced = Indexer.DUP_HELPERS.reduceBy(reducables, ['identifier', 'pos']).map((src) => { - src.type = src.tx ? 'T' : 'D'; - return src; - }); - return _.sortBy(reduced, (row:SindexEntry) => row.type == 'D' ? 0 : 1); - } - - getAvailableForPubkey(pubkey:string) { - return this.getAvailableForConditions('%SIG(' + pubkey + ')%') - } - - async getAvailableForConditions(conditionsStr:string) { - const potentials = await this.query('SELECT * FROM ' + this.table + ' s1 ' + - 'WHERE s1.op = ? ' + - 'AND conditions LIKE ? ' + - 'AND NOT EXISTS (' + - ' SELECT * ' + - ' FROM s_index s2 ' + - ' WHERE s2.identifier = s1.identifier ' + - ' AND s2.pos = s1.pos ' + - ' AND s2.op = ?' + - ') ' + - 'ORDER BY CAST(SUBSTR(written_on, 0, INSTR(written_on, "-")) as number)', [CommonConstants.IDX_CREATE, conditionsStr, CommonConstants.IDX_UPDATE]); - const sources = potentials.map((src) => { - src.type = src.tx ? 'T' : 'D'; - return src; - }); - return _.sortBy(sources, (row:SindexEntry) => row.type == 'D' ? 0 : 1); - } - - async trimConsumedSource(belowNumber:number) { - const toDelete = await this.query('SELECT * FROM ' + this.table + ' WHERE consumed AND CAST(written_on as int) < ?', [belowNumber]); - const queries = []; - for (const row of toDelete) { - const sql = "DELETE FROM " + this.table + " " + - "WHERE identifier like '" + row.identifier + "' " + - "AND pos = " + row.pos; - queries.push(sql); - } - await this.exec(queries.join(';\n')); - } -} diff --git a/app/lib/db/DBBlock.ts b/app/lib/db/DBBlock.ts index 9e9a7d7c950a2fd60cc18867fb6c8381d678e5d4..05e54ba8fca03f753d2153772d1b9f01666a45b9 100644 --- a/app/lib/db/DBBlock.ts +++ b/app/lib/db/DBBlock.ts @@ -47,6 +47,8 @@ export class DBBlock { monetaryMass: number dividend: number | null UDTime: number + writtenOn: number + written_on: string wrong = false constructor( @@ -66,7 +68,7 @@ export class DBBlock { dbb.previousHash = b.previousHash dbb.issuer = b.issuer dbb.previousIssuer = b.previousIssuer - dbb.dividend = b.dividend + dbb.dividend = (b.dividend === null || b.dividend === undefined ? b.dividend : parseInt(String(b.dividend))) dbb.time = b.time dbb.powMin = b.powMin dbb.unitbase = b.unitbase @@ -90,6 +92,8 @@ export class DBBlock { dbb.nonce = b.nonce dbb.UDTime = b.UDTime dbb.monetaryMass = b.monetaryMass + dbb.writtenOn = b.number + dbb.written_on = [b.number, b.hash].join('-') return dbb } } \ No newline at end of file diff --git a/app/lib/db/DBHead.ts b/app/lib/db/DBHead.ts index ab4e48fcf3d86e4c5721beac1e1adcee57830642..8c97197e4a4a2ffadf81210cfad40c52ddedfbbe 100644 --- a/app/lib/db/DBHead.ts +++ b/app/lib/db/DBHead.ts @@ -45,6 +45,8 @@ export class DBHead { dividend: number new_dividend: number | null issuerIsMember: boolean + written_on: string + writtenOn: number constructor( ) {} diff --git a/app/lib/db/DBPeer.ts b/app/lib/db/DBPeer.ts new file mode 100644 index 0000000000000000000000000000000000000000..c71fb86e76500e80ecad0184c121606bd762e09e --- /dev/null +++ b/app/lib/db/DBPeer.ts @@ -0,0 +1,47 @@ +import {PeerDTO} from "../dto/PeerDTO" + +export class DBPeer { + + version: number + currency: string + status: string + statusTS: number + hash: string + first_down: number | null + last_try: number | null + pubkey: string + block: string + signature: string + endpoints: string[] + raw: string + + static json(peer:DBPeer): JSONDBPeer { + return { + version: peer.version, + currency: peer.currency, + status: peer.status, + first_down: peer.first_down, + last_try: peer.last_try, + pubkey: peer.pubkey, + block: peer.block, + signature: peer.signature, + endpoints: peer.endpoints + } + } + + static fromPeerDTO(peer:PeerDTO): DBPeer { + return peer.toDBPeer() + } +} + +export class JSONDBPeer { + version: number + currency: string + status: string + first_down: number | null + last_try: number | null + pubkey: string + block: string + signature: string + endpoints: string[] +} diff --git a/app/lib/db/DBTransaction.ts b/app/lib/db/DBTransaction.ts deleted file mode 100644 index 4e5bb3ab32addad01f05db020e2d992944e2042d..0000000000000000000000000000000000000000 --- a/app/lib/db/DBTransaction.ts +++ /dev/null @@ -1,72 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -import {TransactionDTO} from "../dto/TransactionDTO" - -export class DBTransaction extends TransactionDTO { - - constructor( - public version: number, - public currency: string, - public locktime: number, - public hash: string, - public blockstamp: string, - public issuers: string[], - public inputs: string[], - public outputs: string[], - public unlocks: string[], - public signatures: string[], - public comment: string, - public blockstampTime: number, - public written: boolean, - public removed: boolean, - public block_number: number, - public time: number, - ) { - super( - version, - currency, - locktime, - hash, - blockstamp, - blockstampTime, - issuers, - inputs, - outputs, - unlocks, - signatures, - comment - ) - } - - static fromTransactionDTO(dto:TransactionDTO, blockstampTime:number, written: boolean, removed: boolean, block_number:number, block_medianTime:number) { - return new DBTransaction( - dto.version, - dto.currency, - dto.locktime, - dto.hash, - dto.blockstamp, - dto.issuers, - dto.inputs, - dto.outputs, - dto.unlocks, - dto.signatures, - dto.comment || "", - blockstampTime, - written, - removed, - block_number, - block_medianTime - ) - } -} \ No newline at end of file diff --git a/app/lib/db/DBTx.ts b/app/lib/db/DBTx.ts new file mode 100644 index 0000000000000000000000000000000000000000..1d47b7bb07530f1733ea58adc1b39f2fa2808b59 --- /dev/null +++ b/app/lib/db/DBTx.ts @@ -0,0 +1,60 @@ +import {TransactionDTO} from "../dto/TransactionDTO" + +export class DBTx { + hash: string + block_number: number | null + locktime: number + version: number + currency: string + comment: string + blockstamp: string + blockstampTime: number | null + time: number | null + inputs: string[] + unlocks: string[] + outputs: string[] + issuers: string[] + signatures: string[] + recipients: string[] + written: boolean + removed: boolean + received: number + output_base: number + output_amount: number + written_on: string + writtenOn: number + + static fromTransactionDTO(tx:TransactionDTO) { + const dbTx = new DBTx() + dbTx.hash = tx.hash + dbTx.locktime = tx.locktime + dbTx.version = tx.version + dbTx.currency = tx.currency + dbTx.blockstamp = tx.blockstamp + dbTx.blockstampTime = tx.blockstampTime + dbTx.comment = tx.comment || "" + dbTx.inputs = tx.inputs + dbTx.unlocks = tx.unlocks + dbTx.outputs = tx.outputs + dbTx.issuers = tx.issuers + dbTx.signatures = tx.signatures + dbTx.recipients = tx.outputsAsRecipients() + dbTx.written = false + dbTx.removed = false + dbTx.output_base = tx.output_base + dbTx.output_amount = tx.output_amount + return dbTx + } + + static setRecipients(txs:DBTx[]) { + // Each transaction must have a good "recipients" field for future searchs + txs.forEach((tx) => tx.recipients = DBTx.outputs2recipients(tx)) + } + + static outputs2recipients(tx:DBTx) { + return tx.outputs.map(function(out) { + const recipent = out.match('SIG\\((.*)\\)') + return (recipent && recipent[1]) || 'UNKNOWN' + }) + } +} diff --git a/app/lib/db/DBWallet.ts b/app/lib/db/DBWallet.ts new file mode 100644 index 0000000000000000000000000000000000000000..d59c617d685ce73a1a4d0d7dc0ba7bf0394b8f23 --- /dev/null +++ b/app/lib/db/DBWallet.ts @@ -0,0 +1,4 @@ +export interface DBWallet { + conditions: string + balance: number +} diff --git a/app/lib/db/OldIindexEntry.ts b/app/lib/db/OldIindexEntry.ts new file mode 100644 index 0000000000000000000000000000000000000000..308969f906b983034a717c6503d54ac9ca1eee61 --- /dev/null +++ b/app/lib/db/OldIindexEntry.ts @@ -0,0 +1,7 @@ +import {IindexEntry} from "../indexer" + +export interface OldIindexEntry extends IindexEntry { + pubkey: string + buid: string | null + revocation_sig:string | null +} diff --git a/app/lib/debug/MonitorFlushedIndex.ts b/app/lib/debug/MonitorFlushedIndex.ts new file mode 100644 index 0000000000000000000000000000000000000000..f2d083ccef8a0488a4f4cce955fe77cd6cf92072 --- /dev/null +++ b/app/lib/debug/MonitorFlushedIndex.ts @@ -0,0 +1,52 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import {cliprogram} from "../common-libs/programOptions" +import {IndexBatch} from "../dal/fileDAL" + +export const MonitorFlushedIndex = function () { + return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { + const original = descriptor.value + if (original.__proto__.constructor.name === "AsyncFunction") { + descriptor.value = async function (...args:any[]) { + const pub = cliprogram.syncTrace + if (pub) { + const batch: IndexBatch = args[0] + batch.iindex.forEach(e => { + if (e.pub === pub) { + console.log(JSON.stringify(e)) + } + }) + batch.mindex.forEach(e => { + if (e.pub === pub) { + console.log(JSON.stringify(e)) + } + }) + batch.cindex.forEach(e => { + if (e.issuer === pub || e.receiver === pub) { + console.log(JSON.stringify(e)) + } + }) + batch.sindex.forEach(e => { + if (e.conditions.indexOf(pub || '') !== -1) { + console.log(JSON.stringify(e)) + } + }) + } + return await original.apply(this, args) + } + } else { + throw Error("Monitoring a synchronous function is not allowed.") + } + } +} \ No newline at end of file diff --git a/app/lib/debug/MonitorLokiExecutionTime.ts b/app/lib/debug/MonitorLokiExecutionTime.ts new file mode 100644 index 0000000000000000000000000000000000000000..0491951af3e646bba96c9cfcc8ef5f55f0cb2297 --- /dev/null +++ b/app/lib/debug/MonitorLokiExecutionTime.ts @@ -0,0 +1,41 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import {NewLogger} from "../logger" +import {getMicrosecondsTime} from "../../ProcessCpuProfiler" +import {OtherConstants} from "../other_constants" + +const theLogger = NewLogger() + +export const MonitorLokiExecutionTime = function (dumpFirstParam = false) { + return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { + if (OtherConstants.ENABLE_LOKI_MONITORING) { + const original = descriptor.value + if (original.__proto__.constructor.name === "AsyncFunction") { + descriptor.value = async function (...args:any[]) { + const that :any = this + const now = getMicrosecondsTime() + const result = await original.apply(this, args) + if (dumpFirstParam) { + theLogger.trace('[loki][%s][%s] => %sµs', that.collectionName, propertyKey, (getMicrosecondsTime() - now), args && args[0]) + } else { + theLogger.trace('[loki][%s][%s] => %sµs', that.collectionName, propertyKey, (getMicrosecondsTime() - now)) + } + return result + } + } else { + throw Error("Monitoring a Loki synchronous function is not allowed.") + } + } + } +} \ No newline at end of file diff --git a/app/lib/debug/MonitorSQLExecutionTime.ts b/app/lib/debug/MonitorSQLExecutionTime.ts new file mode 100644 index 0000000000000000000000000000000000000000..65724c527347f3df256933cbf1c1dcf582edd44d --- /dev/null +++ b/app/lib/debug/MonitorSQLExecutionTime.ts @@ -0,0 +1,39 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import {getDurationInMicroSeconds, getMicrosecondsTime} from "../../ProcessCpuProfiler" +import {NewLogger} from "../logger" +import {OtherConstants} from "../other_constants" + +const theLogger = NewLogger() + +export const MonitorSQLExecutionTime = function () { + return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { + if (OtherConstants.ENABLE_SQL_MONITORING) { + const original = descriptor.value + if (original.__proto__.constructor.name === "AsyncFunction") { + descriptor.value = async function (...args: any[]) { + const start = getMicrosecondsTime() + const sql: string = args[0] + const params: any[] = args[1] + const entities: any[] = await original.apply(this, args) + const duration = getDurationInMicroSeconds(start) + theLogger.trace('[sqlite][query] %s %s %sµs', sql, JSON.stringify(params || []), duration) + return entities + } + } else { + throw Error("Monitoring an SQL synchronous function is not allowed.") + } + } + } +} \ No newline at end of file diff --git a/app/lib/dto/BlockDTO.ts b/app/lib/dto/BlockDTO.ts index dba24f303c178e302669905b1448f080f9744c66..7ee505e4e5b51873b6796d202c1a081df553bad7 100644 --- a/app/lib/dto/BlockDTO.ts +++ b/app/lib/dto/BlockDTO.ts @@ -32,7 +32,7 @@ export class BlockDTO implements Cloneable { previousHash: string issuer: string previousIssuer: string - dividend: number + dividend: number|null time: number powMin: number unitbase: number diff --git a/app/lib/dto/CertificationDTO.ts b/app/lib/dto/CertificationDTO.ts index 45c2d2d403635dfd673e3ea25d1c9a5a66d6d788..66b76c92d9df315a8267dfece93d8cf622e3f7c5 100644 --- a/app/lib/dto/CertificationDTO.ts +++ b/app/lib/dto/CertificationDTO.ts @@ -63,8 +63,8 @@ export class CertificationDTO extends ShortCertificationDTO implements Cloneable getTargetHash() { return IdentityDTO.getTargetHash({ uid: this.idty_uid, - buid: this.idty_buid, - pubkey: this.idty_issuer + created_on: this.idty_buid, + pub: this.idty_issuer }) } diff --git a/app/lib/dto/ConfDTO.ts b/app/lib/dto/ConfDTO.ts index eb9ccef3391badfa479b1ed7dc1fdb013be73f3f..a95feec6ae550a2be1290de75d47bdef7d8cceb5 100644 --- a/app/lib/dto/ConfDTO.ts +++ b/app/lib/dto/ConfDTO.ts @@ -12,8 +12,9 @@ // GNU Affero General Public License for more details. import {CommonConstants} from "../common-libs/constants" -import { ProxiesConf } from '../proxy'; -const _ = require('underscore'); +import {ProxiesConf} from '../proxy'; +import {Underscore} from "../common-libs/underscore" + const constants = require('../constants'); export interface Keypair { @@ -215,6 +216,6 @@ export class ConfDTO implements CurrencyConfDTO, KeypairConfDTO, NetworkConfDTO, } static complete(conf:any) { - return _(ConfDTO.defaultConf()).extend(conf); + return Underscore.extend(ConfDTO.defaultConf(), conf) } } \ No newline at end of file diff --git a/app/lib/dto/IdentityDTO.ts b/app/lib/dto/IdentityDTO.ts index b1ed24d2ee40b3d646ee10bd176c4b4c9591b7d8..5671987b0c2801dd1f820cae2d821a408ec48048 100644 --- a/app/lib/dto/IdentityDTO.ts +++ b/app/lib/dto/IdentityDTO.ts @@ -18,16 +18,26 @@ import {DBIdentity, NewDBIdentity} from "../dal/sqliteDAL/IdentityDAL" const DEFAULT_DOCUMENT_VERSION = 10 export interface HashableIdentity { + created_on: string + uid: string + pub: string +} + +export interface BasicIdentity { buid: string uid: string pubkey: string + sig: string } -export interface BasicIdentity { +export interface BasicRevocableIdentity { buid: string uid: string pubkey: string sig: string + member: boolean + wasMember: boolean + expires_on: number } export class IdentityDTO { @@ -85,7 +95,7 @@ export class IdentityDTO { } static getTargetHash(idty:HashableIdentity) { - return hashf(idty.uid + idty.buid + idty.pubkey) + return hashf(idty.uid + idty.created_on + idty.pub) } static fromJSONObject(obj:any) { @@ -106,8 +116,8 @@ export class IdentityDTO { basic.buid, basic.uid, IdentityDTO.getTargetHash({ - pubkey: basic.pubkey, - buid: basic.buid, + pub: basic.pubkey, + created_on: basic.buid, uid: basic.uid }) ) @@ -120,8 +130,8 @@ export class IdentityDTO { revoc.idty_buid, revoc.idty_uid, IdentityDTO.getTargetHash({ - pubkey: revoc.pubkey, - buid: revoc.idty_buid, + pub: revoc.pubkey, + created_on: revoc.idty_buid, uid: revoc.idty_uid }) ) diff --git a/app/lib/dto/MembershipDTO.ts b/app/lib/dto/MembershipDTO.ts index a3a564c03e34068acaf756d5ec2637a9ff41b94d..267106ab0d634bd17bf9162b771fcfcfda748db6 100644 --- a/app/lib/dto/MembershipDTO.ts +++ b/app/lib/dto/MembershipDTO.ts @@ -11,8 +11,8 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -import {IdentityDTO} from "./IdentityDTO" import * as moment from "moment" +import {IdentityDTO} from "./IdentityDTO" import {Cloneable} from "./Cloneable"; import {hashf} from "../common"; @@ -78,9 +78,9 @@ export class MembershipDTO implements Cloneable { getIdtyHash() { return IdentityDTO.getTargetHash({ - buid: this.certts, + created_on: this.certts, uid: this.userid, - pubkey: this.issuer + pub: this.issuer }) } diff --git a/app/lib/dto/PeerDTO.ts b/app/lib/dto/PeerDTO.ts index 119c8a90c7c3938ed7b658d81af88edd418223c7..9fb10b08f28752e8cbd4c3c5ae6675ae193400f7 100644 --- a/app/lib/dto/PeerDTO.ts +++ b/app/lib/dto/PeerDTO.ts @@ -11,11 +11,10 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -import {DBPeer} from "../dal/sqliteDAL/PeerDAL" import {hashf} from "../common" import {CommonConstants} from "../common-libs/constants" import {Cloneable} from "./Cloneable" -import { WS2PConstants } from '../../modules/ws2p/lib/constants'; +import {DBPeer} from "../db/DBPeer" export interface WS2PEndpoint { version:number diff --git a/app/lib/dto/TransactionDTO.ts b/app/lib/dto/TransactionDTO.ts index b443b1a8024cef6475a1899be10a21eb77729ee0..3aefab836c37676a439d6adb2855a6c7eadc4f43 100644 --- a/app/lib/dto/TransactionDTO.ts +++ b/app/lib/dto/TransactionDTO.ts @@ -23,7 +23,7 @@ export class InputDTO implements BaseDTO { constructor( public amount: number, public base: number, - public type: string, + public type: 'T'|'D', public identifier: string, public pos: number, public raw: string @@ -134,7 +134,7 @@ export class TransactionDTO implements Cloneable { return new InputDTO( parseInt(amount), parseInt(base), - type, + type as 'T'|'D', identifier, parseInt(pos), input diff --git a/app/lib/indexer.ts b/app/lib/indexer.ts index 8b05ab264857ddb9554e034957cdd1ffe0ee20ad..96fd89a6076dcf4af1713c7fb87e6c5a38c0567b 100644 --- a/app/lib/indexer.ts +++ b/app/lib/indexer.ts @@ -23,8 +23,12 @@ import {rawer, txunlock} from "./common-libs/index" import {CommonConstants} from "./common-libs/constants" import {MembershipDTO} from "./dto/MembershipDTO" import {UnlockMetadata} from "./common-libs/txunlock" - -const _ = require('underscore'); +import {FileDAL} from "./dal/fileDAL" +import {DBBlock} from "./db/DBBlock" +import {DBWallet} from "./db/DBWallet" +import {Tristamp} from "./common/Tristamp" +import {Underscore} from "./common-libs/underscore" +import {DataErrors} from "./common-libs/errors" const constants = CommonConstants @@ -59,6 +63,22 @@ export interface MindexEntry extends IndexEntry { revokedIsMember?: boolean, alreadyRevoked?: boolean, revocationSigOK?: boolean, + created_on_ref?: { medianTime: number, number:number, hash:string } +} + +export interface FullMindexEntry { + op: string + pub: string + created_on: string + written_on: string + expires_on: number + expired_on: null|number + revokes_on: number + revoked_on: null|number + leaving: boolean + revocation: null|string + chainable_on: number + writtenOn: number } export interface IindexEntry extends IndexEntry { @@ -67,7 +87,7 @@ export interface IindexEntry extends IndexEntry { hash: string | null, sig: string | null, created_on: string | null, - member: boolean, + member: boolean|null, wasMember: boolean | null, kick: boolean | null, wotb_id: number | null, @@ -79,6 +99,21 @@ export interface IindexEntry extends IndexEntry { hasToBeExcluded?: boolean, } +export interface FullIindexEntry { + op: string + uid: string + pub: string + hash: string + sig: string + created_on: string + written_on: string + writtenOn: number + member: boolean + wasMember: boolean + kick: boolean + wotb_id: number +} + export interface CindexEntry extends IndexEntry { issuer: string, receiver: string, @@ -98,9 +133,21 @@ export interface CindexEntry extends IndexEntry { toLeaver?: boolean, isReplay?: boolean, sigOK?: boolean, + created_on_ref?: { medianTime: number }, +} + +export interface FullCindexEntry { + issuer: string + receiver: string + created_on: number + sig: string + chainable_on: number + expires_on: number + expired_on: number } export interface SindexEntry extends IndexEntry { + srcType: 'T'|'D' tx: string | null, identifier: string, pos: number, @@ -120,25 +167,97 @@ export interface SindexEntry extends IndexEntry { isTimeLocked?: boolean, } +export interface FullSindexEntry { + tx: string | null + identifier: string + pos: number + created_on: string | null + written_time: number + locktime: number + unlock: string | null + amount: number + base: number + conditions: string + consumed: boolean +} + +export interface SimpleTxInput { + conditions: string + consumed: boolean + written_time: number + amount: number + base: number +} + +export interface BasedAmount { + amount: number + base: number +} + +export interface SimpleSindexEntryForWallet { + op: string + srcType: 'T'|'D' + conditions: string + amount: number + base: number + identifier: string + pos: number +} + +export interface SimpleTxEntryForWallet extends SimpleSindexEntryForWallet { + srcType: 'T' +} + +export interface SimpleUdEntryForWallet extends SimpleSindexEntryForWallet { + srcType: 'D' +} + export interface Ranger { - (n:number, m:number, prop?:string): Promise<DBHead[]> + (n:number, m:number): Promise<DBHead[]> } -function pushIindex(index: any[], entry: IindexEntry): void { +export interface ExclusionByCert { + op: 'UPDATE' + pub: string + written_on: string + writtenOn: number + kick: true +} + +function pushIindex(index: IndexEntry[], entry: IindexEntry): void { index.push(entry) } -function pushMindex(index: any[], entry: MindexEntry): void { +function pushMindex(index: IndexEntry[], entry: MindexEntry): void { index.push(entry) } -function pushCindex(index: any[], entry: CindexEntry): void { +function pushCindex(index: IndexEntry[], entry: CindexEntry): void { index.push(entry) } +export interface AccountsGarbagingDAL { + getWallet: (conditions: string) => Promise<DBWallet> + saveWallet: (wallet: DBWallet) => Promise<void> + sindexDAL: { + getAvailableForConditions: (conditions: string) => Promise<SindexEntry[]> + } +} + +export interface BlockchainBlocksDAL { + getBlock(number: number): Promise<BlockDTO> + getBlockByBlockstamp(blockstamp: string): Promise<BlockDTO> +} + export class Indexer { - static localIndex(block:BlockDTO, conf:CurrencyConfDTO): IndexEntry[] { + static localIndex(block:BlockDTO, conf:{ + sigValidity:number, + msValidity:number, + msPeriod:number, + sigPeriod:number, + sigStock:number + }): IndexEntry[] { /******************** * GENERAL BEHAVIOR @@ -188,7 +307,7 @@ export class Indexer { // Joiners (newcomer or join back) for (const inlineMS of block.joiners) { const ms = MembershipDTO.fromInline(inlineMS); - const matchesANewcomer = _.filter(index, (row: IindexEntry) => row.index == constants.I_INDEX && row.pub == ms.issuer).length > 0; + const matchesANewcomer = Underscore.filter(index, (row: IindexEntry) => row.index == constants.I_INDEX && row.pub == ms.issuer).length > 0; if (matchesANewcomer) { // Newcomer pushMindex(index, { @@ -375,6 +494,7 @@ export class Indexer { index.push({ index: constants.S_INDEX, op: constants.IDX_UPDATE, + srcType: input.type, tx: txHash, identifier: input.identifier, pos: input.pos, @@ -399,6 +519,7 @@ export class Indexer { index.push({ index: constants.S_INDEX, op: constants.IDX_CREATE, + srcType: 'T', tx: txHash, identifier: txHash, pos: i++, @@ -420,27 +541,22 @@ export class Indexer { return index; } - static async quickCompleteGlobalScope(block: BlockDTO, conf: CurrencyConfDTO, bindex: DBHead[], iindex: IindexEntry[], mindex: MindexEntry[], cindex: CindexEntry[], dal: any) { + static async quickCompleteGlobalScope(block: BlockDTO, conf: CurrencyConfDTO, bindex: DBHead[], iindex: IindexEntry[], mindex: MindexEntry[], cindex: CindexEntry[], dal:FileDAL) { - function range(start: number, end: number, property = ""): any { - let theRange; + async function range(start: number, end: number) { + let theRange:DBHead[] = [] end = Math.min(end, bindex.length); if (start == 1) { theRange = bindex.slice(-end); } else { theRange = bindex.slice(-end, -start + 1); } - theRange.reverse(); - if (property) { - // Filter on a particular property - return theRange.map((b:any) => b[property]); - } else { - return theRange; - } + theRange.reverse() + return theRange } async function head(n:number) { - return range(n, n)[0]; + return (await range(n, n))[0] } const HEAD = new DBHead() @@ -456,7 +572,7 @@ export class Indexer { HEAD.powMin = block.powMin HEAD.unitBase = block.unitbase HEAD.membersCount = block.membersCount - HEAD.dividend = block.dividend + HEAD.dividend = block.dividend || 0 HEAD.new_dividend = null const HEAD_1 = await head(1); @@ -509,10 +625,14 @@ export class Indexer { // BR_G105 await Indexer.ruleIndexCorrectCertificationExpiryDate(HEAD, cindex, dal); + // Cleaning + cindex.forEach(c => c.created_on_ref = undefined) + mindex.forEach(m => m.created_on_ref = undefined) + return HEAD; } - static async completeGlobalScope(block: BlockDTO, conf: ConfDTO, index: IndexEntry[], dal: any) { + static async completeGlobalScope(block: BlockDTO, conf: ConfDTO, index: IndexEntry[], dal:FileDAL) { const iindex = Indexer.iindex(index); const mindex = Indexer.mindex(index); @@ -562,9 +682,9 @@ export class Indexer { // BR_G03 if (HEAD.number > 0) { - HEAD.issuerIsMember = reduce(await dal.iindexDAL.reducable(HEAD.issuer)).member; + HEAD.issuerIsMember = !!reduce(await dal.iindexDAL.reducable(HEAD.issuer)).member; } else { - HEAD.issuerIsMember = reduce(_.where(iindex, { pub: HEAD.issuer })).member; + HEAD.issuerIsMember = !!reduce(Underscore.where(iindex, { pub: HEAD.issuer })).member; } // BR_G04 @@ -591,11 +711,11 @@ export class Indexer { // BR_G10 if (HEAD.number == 0) { - HEAD.membersCount = count(_.filter(iindex, (entry:IindexEntry) => entry.member === true)); + HEAD.membersCount = count(Underscore.filter(iindex, (entry:IindexEntry) => entry.member === true)); } else { HEAD.membersCount = HEAD_1.membersCount - + count(_.filter(iindex, (entry:IindexEntry) => entry.member === true)) - - count(_.filter(iindex, (entry:IindexEntry) => entry.member === false)); + + count(Underscore.filter(iindex, (entry:IindexEntry) => entry.member === true)) + - count(Underscore.filter(iindex, (entry:IindexEntry) => entry.member === false)); } // BR_G11 @@ -651,7 +771,7 @@ export class Indexer { // BR_G20 await Promise.all(iindex.map(async (ENTRY: IindexEntry) => { if (ENTRY.op == constants.IDX_CREATE) { - ENTRY.uidUnique = count(await dal.iindexDAL.sqlFind({ uid: ENTRY.uid })) == 0; + ENTRY.uidUnique = count(await dal.iindexDAL.findByUid(ENTRY.uid as string)) == 0; } else { ENTRY.uidUnique = true; } @@ -660,7 +780,7 @@ export class Indexer { // BR_G21 await Promise.all(iindex.map(async (ENTRY: IindexEntry) => { if (ENTRY.op == constants.IDX_CREATE) { - ENTRY.pubUnique = count(await dal.iindexDAL.sqlFind({pub: ENTRY.pub})) == 0; + ENTRY.pubUnique = count(await dal.iindexDAL.findByPub(ENTRY.pub)) == 0; } else { ENTRY.pubUnique = true; } @@ -671,7 +791,7 @@ export class Indexer { if (ENTRY.member !== false) { ENTRY.excludedIsMember = true; } else { - ENTRY.excludedIsMember = reduce(await dal.iindexDAL.reducable(ENTRY.pub)).member; + ENTRY.excludedIsMember = !!reduce(await dal.iindexDAL.reducable(ENTRY.pub)).member; } })) @@ -684,7 +804,7 @@ export class Indexer { if (HEAD.number > 0) { await Promise.all(mindex.map(async (ENTRY: MindexEntry) => { if (ENTRY.revocation === null) { - const rows = await dal.mindexDAL.sqlFind({ pub: ENTRY.pub, chainable_on: { $gt: HEAD_1.medianTime }}); + const rows = await dal.mindexDAL.findByPubAndChainableOnGt(ENTRY.pub, HEAD_1.medianTime) // This rule will be enabled on if (HEAD.medianTime >= 1498860000) { ENTRY.unchainables = count(rows); @@ -701,7 +821,7 @@ export class Indexer { // BR_G36 await Promise.all(iindex.map(async (ENTRY: IindexEntry) => { const isMarkedAsToKick = reduce(await dal.iindexDAL.reducable(ENTRY.pub)).kick; - const isBeingRevoked = count(_.filter(mindex, (m:MindexEntry) => m.isBeingRevoked && m.pub == ENTRY.pub)) == 1; + const isBeingRevoked = count(Underscore.filter(mindex, m => !!(m.isBeingRevoked && m.pub == ENTRY.pub))) == 1 ENTRY.hasToBeExcluded = isMarkedAsToKick || isBeingRevoked; })) @@ -725,14 +845,14 @@ export class Indexer { // BR_G24 // Global testing, because of wotb const oneIsOutdistanced = await checkPeopleAreNotOudistanced( - _.filter(mindex, (entry: MindexEntry) => !entry.revoked_on).map((entry: MindexEntry) => entry.pub), - cindex.reduce((newLinks:any, c: CindexEntry) => { + Underscore.filter(mindex, (entry: MindexEntry) => !entry.revoked_on).map((entry: MindexEntry) => entry.pub), + cindex.reduce((newLinks, c: CindexEntry) => { newLinks[c.receiver] = newLinks[c.receiver] || []; newLinks[c.receiver].push(c.issuer); return newLinks; - }, {}), + }, <{ [k:string]: string[] }>{}), // Newcomers - _.where(iindex, { op: constants.IDX_CREATE }).map((entry: IindexEntry) => entry.pub), + Underscore.where(iindex, { op: constants.IDX_CREATE }).map((entry: IindexEntry) => entry.pub), conf, dal ); @@ -750,15 +870,15 @@ export class Indexer { })) // BR_G26 - await Promise.all(_.filter(mindex, (entry: MindexEntry) => entry.op == constants.IDX_UPDATE && entry.expired_on === 0).map(async (ENTRY: MindexEntry) => { + await Promise.all(Underscore.filter(mindex, (entry: MindexEntry) => entry.op == constants.IDX_UPDATE && entry.expired_on === 0).map(async (ENTRY: MindexEntry) => { ENTRY.joinsTwice = reduce(await dal.iindexDAL.reducable(ENTRY.pub)).member == true; })) // BR_G27 await Promise.all(mindex.map(async (ENTRY: MindexEntry) => { if (ENTRY.type == 'JOIN' || ENTRY.type == 'ACTIVE') { - const existing = count(await dal.cindexDAL.sqlFind({ receiver: ENTRY.pub, expired_on: 0 })) - const pending = count(_.filter(cindex, (c:CindexEntry) => c.receiver == ENTRY.pub && c.expired_on == 0)) + const existing = count(await dal.cindexDAL.findByReceiverAndExpiredOn(ENTRY.pub, 0)) + const pending = count(Underscore.filter(cindex, (c:CindexEntry) => c.receiver == ENTRY.pub && c.expired_on == 0)) ENTRY.enoughCerts = (existing + pending) >= conf.sigQty; } else { ENTRY.enoughCerts = true; @@ -768,7 +888,7 @@ export class Indexer { // BR_G28 await Promise.all(mindex.map(async (ENTRY: MindexEntry) => { if (ENTRY.type == 'LEAVE') { - ENTRY.leaverIsMember = reduce(await dal.iindexDAL.reducable(ENTRY.pub)).member + ENTRY.leaverIsMember = !!reduce(await dal.iindexDAL.reducable(ENTRY.pub)).member } else { ENTRY.leaverIsMember = true; } @@ -778,7 +898,7 @@ export class Indexer { await Promise.all(mindex.map(async (ENTRY: MindexEntry) => { if (ENTRY.type == 'ACTIVE') { const reducable = await dal.iindexDAL.reducable(ENTRY.pub) - ENTRY.activeIsMember = reduce(reducable).member; + ENTRY.activeIsMember = !!reduce(reducable).member; } else { ENTRY.activeIsMember = true; } @@ -789,7 +909,7 @@ export class Indexer { if (!ENTRY.revoked_on) { ENTRY.revokedIsMember = true; } else { - ENTRY.revokedIsMember = reduce(await dal.iindexDAL.reducable(ENTRY.pub)).member + ENTRY.revokedIsMember = !!reduce(await dal.iindexDAL.reducable(ENTRY.pub)).member } })) @@ -798,7 +918,7 @@ export class Indexer { if (!ENTRY.revoked_on) { ENTRY.alreadyRevoked = false; } else { - ENTRY.alreadyRevoked = reduce(await dal.mindexDAL.reducable(ENTRY.pub)).revoked_on + ENTRY.alreadyRevoked = !!(reduce(await dal.mindexDAL.reducable(ENTRY.pub)).revoked_on) } })) @@ -817,7 +937,7 @@ export class Indexer { // BR_G38 if (HEAD.number > 0) { await Promise.all(cindex.map(async (ENTRY: CindexEntry) => { - const rows = await dal.cindexDAL.sqlFind({ issuer: ENTRY.issuer, chainable_on: { $gt: HEAD_1.medianTime }}); + const rows = await dal.cindexDAL.findByIssuerAndChainableOnGt(ENTRY.issuer, HEAD_1.medianTime) ENTRY.unchainables = count(rows); })) } @@ -829,47 +949,56 @@ export class Indexer { // BR_G40 await Promise.all(cindex.map(async (ENTRY: CindexEntry) => { - ENTRY.fromMember = reduce(await dal.iindexDAL.reducable(ENTRY.issuer)).member + ENTRY.fromMember = !!reduce(await dal.iindexDAL.reducable(ENTRY.issuer)).member })) // BR_G41 await Promise.all(cindex.map(async (ENTRY: CindexEntry) => { - ENTRY.toMember = reduce(await dal.iindexDAL.reducable(ENTRY.receiver)).member + ENTRY.toMember = !!reduce(await dal.iindexDAL.reducable(ENTRY.receiver)).member })) // BR_G42 await Promise.all(cindex.map(async (ENTRY: CindexEntry) => { - ENTRY.toNewcomer = count(_.where(iindex, { member: true, pub: ENTRY.receiver })) > 0; + ENTRY.toNewcomer = count(Underscore.where(iindex, { member: true, pub: ENTRY.receiver })) > 0; })) // BR_G43 await Promise.all(cindex.map(async (ENTRY: CindexEntry) => { - ENTRY.toLeaver = reduce(await dal.mindexDAL.reducable(ENTRY.receiver)).leaving + ENTRY.toLeaver = !!(reduce(await dal.mindexDAL.reducable(ENTRY.receiver)).leaving) })) // BR_G44 await Promise.all(cindex.map(async (ENTRY: CindexEntry) => { - const reducable = await dal.cindexDAL.sqlFind({ issuer: ENTRY.issuer, receiver: ENTRY.receiver }) + const reducable = await dal.cindexDAL.findByIssuerAndReceiver(ENTRY.issuer, ENTRY.receiver) ENTRY.isReplay = count(reducable) > 0 && reduce(reducable).expired_on === 0 })) // BR_G45 await Promise.all(cindex.map(async (ENTRY: CindexEntry) => { - ENTRY.sigOK = await checkCertificationIsValid(block, ENTRY, async (block:BlockDTO,pub:string,dal:any) => { + ENTRY.sigOK = await checkCertificationIsValid(block, ENTRY, async (block:BlockDTO,pub:string,dal:FileDAL) => { let localInlineIdty = block.getInlineIdentity(pub); if (localInlineIdty) { return IdentityDTO.fromInline(localInlineIdty) } - return dal.getWrittenIdtyByPubkey(pub) + const idty = await dal.getWrittenIdtyByPubkeyForCertificationCheck(pub) + if (!idty) { + return null + } + return { + pubkey: idty.pub, + uid: idty.uid, + sig: idty.sig, + buid: idty.created_on + } }, conf, dal); })) // BR_G102 - await Promise.all(_.where(sindex, { op: constants.IDX_UPDATE }).map(async (ENTRY: SindexEntry) => { + await Promise.all(Underscore.where(sindex, { op: constants.IDX_UPDATE }).map(async (ENTRY: SindexEntry) => { if (HEAD.number == 0 && ENTRY.created_on == '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855') { ENTRY.age = 0; } else { - let ref = await dal.getBlockByBlockstamp(ENTRY.created_on); + let ref = await dal.getAbsoluteValidBlockInForkWindowByBlockstamp(ENTRY.created_on as string); if (ref && blockstamp(ref.number, ref.hash) == ENTRY.created_on) { ENTRY.age = HEAD_1.medianTime - ref.medianTime; } else { @@ -878,40 +1007,41 @@ export class Indexer { } })) - const getInputLocalFirstOrFallbackGlobally = async (sindex:SindexEntry[], ENTRY:SindexEntry) => { - let source = _.filter(sindex, (src:SindexEntry) => + const getInputLocalFirstOrFallbackGlobally = async (sindex:SindexEntry[], ENTRY:SindexEntry): Promise<SimpleTxInput> => { + let source: SimpleTxInput|null = Underscore.filter(sindex, src => src.identifier == ENTRY.identifier && src.pos == ENTRY.pos - && src.conditions + && src.conditions !== '' && src.op === constants.IDX_CREATE)[0]; if (!source) { - const reducable = await dal.sindexDAL.sqlFind({ - identifier: ENTRY.identifier, - pos: ENTRY.pos, - amount: ENTRY.amount, - base: ENTRY.base - }); + const reducable = await dal.findByIdentifierPosAmountBase( + ENTRY.identifier, + ENTRY.pos, + ENTRY.amount, + ENTRY.base, + ENTRY.srcType === 'D' + ); source = reduce(reducable) } return source } // BR_G46 - await Promise.all(_.where(sindex, { op: constants.IDX_UPDATE }).map(async (ENTRY: SindexEntry) => { + await Promise.all(Underscore.where(sindex, { op: constants.IDX_UPDATE }).map(async (ENTRY: SindexEntry) => { const source = await getInputLocalFirstOrFallbackGlobally(sindex, ENTRY) ENTRY.conditions = source.conditions; // We valuate the input conditions, so we can map these records to a same account - ENTRY.available = source.consumed === false; + ENTRY.available = !source.consumed })) // BR_G47 - await Promise.all(_.where(sindex, { op: constants.IDX_UPDATE }).map(async (ENTRY: SindexEntry) => { + await Promise.all(Underscore.where(sindex, { op: constants.IDX_UPDATE }).map(async (ENTRY: SindexEntry) => { const source = await getInputLocalFirstOrFallbackGlobally(sindex, ENTRY) ENTRY.conditions = source.conditions; ENTRY.isLocked = !txSourceUnlock(ENTRY, source, HEAD); })) // BR_G48 - await Promise.all(_.where(sindex, { op: constants.IDX_UPDATE }).map(async (ENTRY: SindexEntry) => { + await Promise.all(Underscore.where(sindex, { op: constants.IDX_UPDATE }).map(async (ENTRY: SindexEntry) => { const source = await getInputLocalFirstOrFallbackGlobally(sindex, ENTRY) ENTRY.isTimeLocked = ENTRY.written_time - source.written_time < ENTRY.locktime; })) @@ -933,7 +1063,7 @@ export class Indexer { if (HEAD.number == 0) { HEAD.issuersCount = 0; } else { - HEAD.issuersCount = count(uniq(await range(1, HEAD_1.issuersFrame, 'issuer'))); // TODO + HEAD.issuersCount = count(uniq(Underscore.pluck(await range(1, HEAD_1.issuersFrame), 'issuer'))) } } @@ -967,8 +1097,8 @@ export class Indexer { } // BR_G07 - static async prepareAvgBlockSize(HEAD: DBHead, range: (n:number,m:number,s:string)=>Promise<number[]>) { - HEAD.avgBlockSize = average(await range(1, HEAD.issuersCount, 'bsize')) + static async prepareAvgBlockSize(HEAD: DBHead, range:Ranger) { + HEAD.avgBlockSize = average(Underscore.pluck(await range(1, HEAD.issuersCount), 'bsize')) } // BR_G09 @@ -1056,7 +1186,7 @@ export class Indexer { } // BR_G16 - static async prepareSpeed(HEAD: DBHead, head: (n:number) => Promise<BlockDTO>, conf: CurrencyConfDTO) { + static async prepareSpeed(HEAD: DBHead, head: (n:number) => Promise<DBHead>, conf: CurrencyConfDTO) { if (HEAD.number == 0) { HEAD.speed = 0; } else { @@ -1071,7 +1201,7 @@ export class Indexer { } // BR_G18 - static async preparePersonalizedPoW(HEAD: DBHead, HEAD_1: DBHead, range: (n:number,m:number)=>Promise<BlockDTO>, conf: ConfDTO) { + static async preparePersonalizedPoW(HEAD: DBHead, HEAD_1: DBHead, range: (n:number,m:number)=>Promise<DBHead[]>, conf: ConfDTO) { let nbPersonalBlocksInFrame, medianOfBlocksInFrame, blocksOfIssuer; let nbPreviousIssuers = 0, nbBlocksSince = 0; if (HEAD.number == 0) { @@ -1079,11 +1209,11 @@ export class Indexer { medianOfBlocksInFrame = 1; } else { const ranged = await range(1, HEAD_1.issuersFrame) - const blocksInFrame = _.filter(ranged, (b:BlockDTO) => b.number <= HEAD_1.number); - const issuersInFrame = blocksInFrame.map((b:BlockDTO) => b.issuer); - blocksOfIssuer = _.filter(blocksInFrame, (entry:BlockDTO) => entry.issuer == HEAD.issuer); + const blocksInFrame = Underscore.filter(ranged, (b:DBHead) => b.number <= HEAD_1.number) + const issuersInFrame = blocksInFrame.map(b => b.issuer) + blocksOfIssuer = Underscore.filter(blocksInFrame, entry => entry.issuer == HEAD.issuer) nbPersonalBlocksInFrame = count(blocksOfIssuer); - const blocksPerIssuerInFrame = uniq(issuersInFrame).map((issuer:string) => count(_.where(blocksInFrame, { issuer }))); + const blocksPerIssuerInFrame = uniq(issuersInFrame).map((issuer:string) => count(Underscore.where(blocksInFrame, { issuer }))); medianOfBlocksInFrame = Math.max(1, median(blocksPerIssuerInFrame)); if (nbPersonalBlocksInFrame == 0) { nbPreviousIssuers = 0; @@ -1108,12 +1238,12 @@ export class Indexer { } // BR_G19 - static async prepareIdentitiesAge(iindex: IindexEntry[], HEAD: DBHead, HEAD_1: DBHead, conf: CurrencyConfDTO, dal: any) { - await Promise.all(_.where(iindex, { op: constants.IDX_CREATE }).map(async (ENTRY: IindexEntry) => { + static async prepareIdentitiesAge(iindex: IindexEntry[], HEAD: DBHead, HEAD_1: DBHead, conf: CurrencyConfDTO, dal:FileDAL) { + await Promise.all(Underscore.where(iindex, { op: constants.IDX_CREATE }).map(async (ENTRY: IindexEntry) => { if (HEAD.number == 0 && ENTRY.created_on == '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855') { ENTRY.age = 0; } else { - let ref = await dal.getBlockByBlockstamp(ENTRY.created_on); + let ref = await dal.getAbsoluteValidBlockInForkWindowByBlockstamp(ENTRY.created_on as string); if (ref && blockstamp(ref.number, ref.hash) == ENTRY.created_on) { ENTRY.age = HEAD_1.medianTime - ref.medianTime; } else { @@ -1124,13 +1254,14 @@ export class Indexer { } // BR_G22 - static async prepareMembershipsAge(mindex: MindexEntry[], HEAD: DBHead, HEAD_1: DBHead, conf: CurrencyConfDTO, dal: any) { - await Promise.all(_.filter(mindex, (entry: MindexEntry) => !entry.revoked_on).map(async (ENTRY:MindexEntry) => { + static async prepareMembershipsAge(mindex: MindexEntry[], HEAD: DBHead, HEAD_1: DBHead, conf: CurrencyConfDTO, dal:FileDAL) { + await Promise.all(Underscore.filter(mindex, (entry: MindexEntry) => !entry.revoked_on).map(async (ENTRY:MindexEntry) => { if (HEAD.number == 0 && ENTRY.created_on == '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855') { ENTRY.age = 0; } else { - let ref = await dal.getBlockByBlockstamp(ENTRY.created_on); + let ref = ENTRY.created_on_ref || await dal.getAbsoluteValidBlockInForkWindowByBlockstamp(ENTRY.created_on) if (ref && blockstamp(ref.number, ref.hash) == ENTRY.created_on) { + ENTRY.created_on_ref = ref ENTRY.age = HEAD_1.medianTime - ref.medianTime; } else { ENTRY.age = conf.msWindow + 1; @@ -1140,13 +1271,16 @@ export class Indexer { } // BR_G37 - static async prepareCertificationsAge(cindex: CindexEntry[], HEAD: DBHead, HEAD_1: DBHead, conf: CurrencyConfDTO, dal: any) { + static async prepareCertificationsAge(cindex: CindexEntry[], HEAD: DBHead, HEAD_1: DBHead, conf: CurrencyConfDTO, dal:FileDAL) { await Promise.all(cindex.map(async (ENTRY) => { if (HEAD.number == 0) { ENTRY.age = 0; } else { - let ref = await dal.getBlock(ENTRY.created_on) + let ref = ENTRY.created_on_ref || await dal.getTristampOf(ENTRY.created_on) if (ref) { + if (!ENTRY.created_on_ref) { + ENTRY.created_on_ref = ref + } ENTRY.age = HEAD_1.medianTime - ref.medianTime; } else { ENTRY.age = conf.sigWindow + 1; @@ -1473,14 +1607,14 @@ export class Indexer { } // BR_G86 - static async ruleToBeKickedArePresent(iindex: IindexEntry[], dal:any) { + static async ruleToBeKickedArePresent(iindex: IindexEntry[], dal:FileDAL) { const toBeKicked = await dal.iindexDAL.getToBeKickedPubkeys(); for (const toKick of toBeKicked) { - if (count(_.where(iindex, { pub: toKick, isBeingKicked: true })) !== 1) { + if (count(Underscore.where(iindex, { pub: toKick, isBeingKicked: true })) !== 1) { return false; } } - const beingKicked = _.filter(iindex, (i:IindexEntry) => i.member === false); + const beingKicked = Underscore.filter(iindex, (i:IindexEntry) => i.member === false); for (const entry of beingKicked) { if (!entry.hasToBeExcluded) { return false; @@ -1499,7 +1633,7 @@ export class Indexer { // BR_G87 static ruleInputIsAvailable(sindex: SindexEntry[]) { - const inputs = _.where(sindex, { op: constants.IDX_UPDATE }); + const inputs = Underscore.where(sindex, { op: constants.IDX_UPDATE }); for (const ENTRY of inputs) { if (!ENTRY.available) { return false; @@ -1510,7 +1644,7 @@ export class Indexer { // BR_G88 static ruleInputIsUnlocked(sindex: SindexEntry[]) { - const inputs = _.where(sindex, { op: constants.IDX_UPDATE }); + const inputs = Underscore.where(sindex, { op: constants.IDX_UPDATE }); for (const ENTRY of inputs) { if (ENTRY.isLocked) { return false; @@ -1521,7 +1655,7 @@ export class Indexer { // BR_G89 static ruleInputIsTimeUnlocked(sindex: SindexEntry[]) { - const inputs = _.where(sindex, { op: constants.IDX_UPDATE }); + const inputs = Underscore.where(sindex, { op: constants.IDX_UPDATE }); for (const ENTRY of inputs) { if (ENTRY.isTimeLocked) { return false; @@ -1532,9 +1666,9 @@ export class Indexer { // BR_G90 static ruleOutputBase(sindex: SindexEntry[], HEAD_1: DBHead) { - const inputs = _.where(sindex, { op: constants.IDX_CREATE }); + const inputs = Underscore.where(sindex, { op: constants.IDX_CREATE }); for (const ENTRY of inputs) { - if (ENTRY.unitBase > HEAD_1.unitBase) { + if (ENTRY.base > HEAD_1.unitBase) { return false; } } @@ -1542,42 +1676,32 @@ export class Indexer { } // BR_G91 - static async ruleIndexGenDividend(HEAD: DBHead, local_iindex: IindexEntry[], dal: any) { - const dividends = []; + static async ruleIndexGenDividend(HEAD: DBHead, local_iindex: IindexEntry[], dal: FileDAL): Promise<SimpleUdEntryForWallet[]> { + // Create the newcomers first, as they will produce a dividend too + for (const newcomer of local_iindex) { + await dal.dividendDAL.createMember(newcomer.pub) + } if (HEAD.new_dividend) { - const members = (await dal.iindexDAL.getMembersPubkeys()).concat(local_iindex.filter(i => i.member)) - for (const MEMBER of members) { - dividends.push({ - op: 'CREATE', - identifier: MEMBER.pub, - pos: HEAD.number, - written_on: [HEAD.number, HEAD.hash].join('-'), - writtenOn: HEAD.number, - written_time: HEAD.medianTime, - amount: HEAD.dividend, - base: HEAD.unitBase, - locktime: null, - conditions: 'SIG(' + MEMBER.pub + ')', - consumed: false - }); - } + return dal.updateDividend(HEAD.number, HEAD.new_dividend, HEAD.unitBase, local_iindex) } - return dividends; + return [] } // BR_G106 - static async ruleIndexGarbageSmallAccounts(HEAD: DBHead, sindex: SindexEntry[], dal: any) { - const garbages = []; + static async ruleIndexGarbageSmallAccounts(HEAD: DBHead, transactions: SindexEntry[], dividends: SimpleUdEntryForWallet[], dal:AccountsGarbagingDAL) { + let sindex: SimpleSindexEntryForWallet[] = transactions + sindex = sindex.concat(dividends) + const garbages: SindexEntry[] = []; const accounts = Object.keys(sindex.reduce((acc: { [k:string]: boolean }, src) => { acc[src.conditions] = true; return acc; }, {})); - const wallets: { [k:string]: Promise<any> } = accounts.reduce((map: { [k:string]: Promise<any> }, acc) => { + const wallets: { [k:string]: Promise<DBWallet> } = accounts.reduce((map: { [k:string]: Promise<DBWallet> }, acc) => { map[acc] = dal.getWallet(acc); return map; }, {}); for (const account of accounts) { - const localAccountEntries = _.filter(sindex, (src:SindexEntry) => src.conditions == account); + const localAccountEntries = Underscore.filter(sindex, src => src.conditions == account) const wallet = await wallets[account]; const balance = wallet.balance const variations = localAccountEntries.reduce((sum:number, src:SindexEntry) => { @@ -1587,15 +1711,18 @@ export class Indexer { return sum - src.amount * Math.pow(10, src.base); } }, 0) - // console.log('Balance of %s = %s (%s)', account, balance, variations > 0 ? '+' + variations : variations) - if (balance + variations < constants.ACCOUNT_MINIMUM_CURRENT_BASED_AMOUNT * Math.pow(10, HEAD.unitBase)) { + if (balance + variations < 0) { + throw Error(DataErrors[DataErrors.NEGATIVE_BALANCE]) + } + else if (balance + variations < constants.ACCOUNT_MINIMUM_CURRENT_BASED_AMOUNT * Math.pow(10, HEAD.unitBase)) { const globalAccountEntries = await dal.sindexDAL.getAvailableForConditions(account) for (const src of localAccountEntries.concat(globalAccountEntries)) { - const sourceBeingConsumed = _.filter(sindex, (entry:SindexEntry) => entry.op === 'UPDATE' && entry.identifier == src.identifier && entry.pos == src.pos).length > 0; + const sourceBeingConsumed = Underscore.filter(sindex, entry => entry.op === 'UPDATE' && entry.identifier == src.identifier && entry.pos == src.pos).length > 0; if (!sourceBeingConsumed) { garbages.push({ + index: 'SINDEX', op: 'UPDATE', - tx: src.tx, + srcType: 'T', identifier: src.identifier, pos: src.pos, amount: src.amount, @@ -1604,7 +1731,17 @@ export class Indexer { writtenOn: HEAD.number, written_time: HEAD.medianTime, conditions: src.conditions, - consumed: true // It is now consumed + consumed: true, // It is now consumed + + // TODO: change types to avoid casting + tx: (src as SindexEntry).tx, + created_on: (src as SindexEntry).created_on, + locktime: null as any, + + // TODO: make these fields being not required using good types + unlock: null, + txObj: {} as TransactionDTO, + age: 0, }); } } @@ -1614,7 +1751,7 @@ export class Indexer { } // BR_G92 - static async ruleIndexGenCertificationExpiry(HEAD: DBHead, dal:any) { + static async ruleIndexGenCertificationExpiry(HEAD: DBHead, dal:FileDAL) { const expiries = []; const certs = await dal.cindexDAL.findExpired(HEAD.medianTime); for (const CERT of certs) { @@ -1632,12 +1769,12 @@ export class Indexer { } // BR_G93 - static async ruleIndexGenMembershipExpiry(HEAD: DBHead, dal:any) { + static async ruleIndexGenMembershipExpiry(HEAD: DBHead, dal:FileDAL) { const expiries = []; - const memberships: MindexEntry[] = reduceBy(await dal.mindexDAL.sqlFind({ expires_on: { $lte: HEAD.medianTime }, revokes_on: { $gt: HEAD.medianTime} }), ['pub']); + const memberships: MindexEntry[] = reduceBy(await dal.mindexDAL.findExpiresOnLteAndRevokesOnGt(HEAD.medianTime), ['pub']); for (const POTENTIAL of memberships) { - const MS = await dal.mindexDAL.getReducedMS(POTENTIAL.pub); + const MS = await dal.mindexDAL.getReducedMS(POTENTIAL.pub) as FullMindexEntry // We are sure because `memberships` already comes from the MINDEX const hasRenewedSince = MS.expires_on > HEAD.medianTime; if (!MS.expired_on && !hasRenewedSince) { expiries.push({ @@ -1654,11 +1791,11 @@ export class Indexer { } // BR_G94 - static async ruleIndexGenExclusionByMembership(HEAD: DBHead, mindex: MindexEntry[], dal:any) { + static async ruleIndexGenExclusionByMembership(HEAD: DBHead, mindex: MindexEntry[], dal:FileDAL) { const exclusions = []; - const memberships = _.filter(mindex, (entry: MindexEntry) => entry.expired_on); + const memberships = Underscore.filter(mindex, entry => !!entry.expired_on) for (const MS of memberships) { - const idty = await dal.iindexDAL.getFromPubkey(MS.pub); + const idty = await dal.iindexDAL.getFullFromPubkey(MS.pub); if (idty.member) { exclusions.push({ op: 'UPDATE', @@ -1673,17 +1810,18 @@ export class Indexer { } // BR_G95 - static async ruleIndexGenExclusionByCertificatons(HEAD: DBHead, cindex: CindexEntry[], iindex: IindexEntry[], conf: ConfDTO, dal: any) { - const exclusions = []; - const expiredCerts = _.filter(cindex, (c: CindexEntry) => c.expired_on > 0); + static async ruleIndexGenExclusionByCertificatons(HEAD: DBHead, cindex: CindexEntry[], iindex: IindexEntry[], conf: ConfDTO, dal:FileDAL) { + const exclusions: ExclusionByCert[] = []; + const expiredCerts = Underscore.filter(cindex, (c: CindexEntry) => c.expired_on > 0); for (const CERT of expiredCerts) { - const just_expired = _.filter(cindex, (c: CindexEntry) => c.receiver == CERT.receiver && c.expired_on > 0); - const just_received = _.filter(cindex, (c: CindexEntry) => c.receiver == CERT.receiver && c.expired_on == 0); + const just_expired = Underscore.filter(cindex, (c: CindexEntry) => c.receiver == CERT.receiver && c.expired_on > 0); + const just_received = Underscore.filter(cindex, (c: CindexEntry) => c.receiver == CERT.receiver && c.expired_on == 0); const non_expired_global = await dal.cindexDAL.getValidLinksTo(CERT.receiver); if ((count(non_expired_global) - count(just_expired) + count(just_received)) < conf.sigQty) { - const isInExcluded = _.filter(iindex, (i: IindexEntry) => i.member === false && i.pub === CERT.receiver)[0]; - const idty = await dal.iindexDAL.getFromPubkey(CERT.receiver); - if (!isInExcluded && idty.member) { + const isInExcluded = Underscore.filter(iindex, (i: IindexEntry) => i.member === false && i.pub === CERT.receiver)[0] + const isInKicked = Underscore.filter(exclusions, e => e.pub === CERT.receiver)[0] + const idty = await dal.iindexDAL.getFullFromPubkey(CERT.receiver) + if (!isInExcluded && !isInKicked && idty.member) { exclusions.push({ op: 'UPDATE', pub: CERT.receiver, @@ -1698,11 +1836,11 @@ export class Indexer { } // BR_G96 - static async ruleIndexGenImplicitRevocation(HEAD: DBHead, dal:any) { + static async ruleIndexGenImplicitRevocation(HEAD: DBHead, dal:FileDAL) { const revocations = []; - const pending = await dal.mindexDAL.sqlFind({ revokes_on: { $lte: HEAD.medianTime}, revoked_on: { $null: true } }) + const pending = await dal.mindexDAL.findRevokesOnLteAndRevokedOnIsNull(HEAD.medianTime) for (const MS of pending) { - const REDUCED = reduce(await dal.mindexDAL.sqlFind({ pub: MS.pub })) + const REDUCED = (await dal.mindexDAL.getReducedMS(MS.pub)) as FullMindexEntry if (REDUCED.revokes_on <= HEAD.medianTime && !REDUCED.revoked_on) { revocations.push({ op: 'UPDATE', @@ -1718,18 +1856,14 @@ export class Indexer { } // BR_G104 - static async ruleIndexCorrectMembershipExpiryDate(HEAD: DBHead, mindex: MindexEntry[], dal:any) { + static async ruleIndexCorrectMembershipExpiryDate(HEAD: DBHead, mindex: MindexEntry[], dal:FileDAL) { for (const MS of mindex) { if (MS.type == 'JOIN' || MS.type == 'ACTIVE') { let basedBlock = { medianTime: 0 }; if (HEAD.number == 0) { basedBlock = HEAD; } else { - if (HEAD.currency === 'gtest') { - basedBlock = await dal.getBlockByBlockstamp(MS.created_on); - } else { - basedBlock = await dal.getBlockByBlockstamp(MS.created_on); - } + basedBlock = MS.created_on_ref || await dal.getAbsoluteValidBlockInForkWindowByBlockstamp(MS.created_on) || basedBlock } if (MS.expires_on === null) { MS.expires_on = 0 @@ -1744,61 +1878,57 @@ export class Indexer { } // BR_G105 - static async ruleIndexCorrectCertificationExpiryDate(HEAD: DBHead, cindex: CindexEntry[], dal:any) { + static async ruleIndexCorrectCertificationExpiryDate(HEAD: DBHead, cindex: CindexEntry[], dal:FileDAL) { for (const CERT of cindex) { let basedBlock = { medianTime: 0 }; if (HEAD.number == 0) { basedBlock = HEAD; } else { - if (HEAD.currency === 'gtest') { - basedBlock = await dal.getBlock(CERT.created_on); - } else { - basedBlock = await dal.getBlock(CERT.created_on); - } + basedBlock = CERT.created_on_ref || ((await dal.getTristampOf(CERT.created_on)) as DBBlock) // We are sure at this point in the rules } CERT.expires_on += basedBlock.medianTime; } } static iindexCreate(index: IndexEntry[]): IindexEntry[] { - return _(index).filter({ index: constants.I_INDEX, op: constants.IDX_CREATE }) + return Underscore.where(index, { index: constants.I_INDEX, op: constants.IDX_CREATE }) as IindexEntry[] } static mindexCreate(index: IndexEntry[]): MindexEntry[] { - return _(index).filter({ index: constants.M_INDEX, op: constants.IDX_CREATE }) + return Underscore.where(index, { index: constants.M_INDEX, op: constants.IDX_CREATE }) as MindexEntry[] } static iindex(index: IndexEntry[]): IindexEntry[] { - return _(index).filter({ index: constants.I_INDEX }) + return Underscore.where(index, { index: constants.I_INDEX }) as IindexEntry[] } static mindex(index: IndexEntry[]): MindexEntry[] { - return _(index).filter({ index: constants.M_INDEX }) + return Underscore.where(index, { index: constants.M_INDEX }) as MindexEntry[] } static cindex(index: IndexEntry[]): CindexEntry[] { - return _(index).filter({ index: constants.C_INDEX }) + return Underscore.where(index, { index: constants.C_INDEX }) as CindexEntry[] } static sindex(index: IndexEntry[]): SindexEntry[] { - return _(index).filter({ index: constants.S_INDEX }) + return Underscore.where(index, { index: constants.S_INDEX }) as SindexEntry[] } static DUP_HELPERS = { - reduce: reduce, + reduce, reduceBy: reduceBy, getMaxBlockSize: (HEAD: DBHead) => Math.max(500, Math.ceil(1.1 * HEAD.avgBlockSize)), checkPeopleAreNotOudistanced } } -function count(range:any[]) { +function count<T>(range:T[]) { return range.length; } -function uniq(range:any[]) { - return _.uniq(range); +function uniq(range:string[]) { + return Underscore.uniq(range) } function average(values:number[]) { @@ -1834,44 +1964,46 @@ function blockstamp(aNumber: number, aHash: string) { return [aNumber, aHash].join('-'); } -function reduce(records: any[]) { - return records.reduce((obj:any, record) => { - const keys = Object.keys(record); +function reduce<T>(records: T[]): T { + return records.reduce((obj:T, record) => { + const keys = Object.keys(record) as (keyof T)[] for (const k of keys) { if (record[k] !== undefined && record[k] !== null) { obj[k] = record[k]; + } else if (record[k] === null && obj[k] === undefined) { + // null overrides undefined + (obj[k] as any) = null } } - return obj; - }, {}); + return obj + }, <T>{}) } -function reduceBy(reducables: IndexEntry[], properties: string[]): any[] { - const reduced = reducables.reduce((map: any, entry: any) => { - const id = properties.map((prop) => entry[prop]).join('-'); - map[id] = map[id] || []; - map[id].push(entry); - return map; - }, {}); - return _.values(reduced).map((value: SindexEntry[]) => Indexer.DUP_HELPERS.reduce(value)); +function reduceBy<T extends IndexEntry>(reducables: T[], properties: (keyof T)[]): T[] { + const reduced: { [k:string]: T[] } = reducables.reduce((map, entry) => { + const id = properties.map((prop) => entry[prop]).join('-') + map[id] = map[id] || [] + map[id].push(entry) + return map + }, <{ [k:string]: T[] }>{}) + return Underscore.values(reduced).map(value => Indexer.DUP_HELPERS.reduce(value)) } -async function checkPeopleAreNotOudistanced (pubkeys: string[], newLinks: any, newcomers: string[], conf: ConfDTO, dal: any) { +async function checkPeopleAreNotOudistanced (pubkeys: string[], newLinks: { [k:string]: string[] }, newcomers: string[], conf: ConfDTO, dal:FileDAL) { // let wotb = dal.wotb; let wotb = dal.wotb.memCopy(); let current = await dal.getCurrentBlockOrNull(); let membersCount = current ? current.membersCount : 0; - // TODO: make a temporary copy of the WoT in RAM // We add temporarily the newcomers to the WoT, to integrate their new links - let nodesCache = newcomers.reduce((map: any, pubkey) => { + let nodesCache = newcomers.reduce((map, pubkey) => { let nodeID = wotb.addNode(); map[pubkey] = nodeID; wotb.setEnabled(false, nodeID); // These are not members yet return map; - }, {}); + }, <{ [k:string]: number }>{}); // Add temporarily the links to the WoT let tempLinks = []; - let toKeys = _.keys(newLinks); + let toKeys = Underscore.keys(newLinks) for (const toKey of toKeys) { let toNode = await getNodeIDfromPubkey(nodesCache, toKey, dal); for (const fromKey of newLinks[toKey]) { @@ -1899,32 +2031,32 @@ async function checkPeopleAreNotOudistanced (pubkeys: string[], newLinks: any, n return error ? true : false; } -async function getNodeIDfromPubkey(nodesCache: any, pubkey: string, dal: any) { +async function getNodeIDfromPubkey(nodesCache: { [k:string]: number }, pubkey: string, dal:FileDAL) { let toNode = nodesCache[pubkey]; // Eventually cache the target nodeID if (toNode === null || toNode === undefined) { - let idty = await dal.getWrittenIdtyByPubkey(pubkey); - toNode = idty.wotb_id; - nodesCache[pubkey] = toNode; + let idty = await dal.getWrittenIdtyByPubkeyForWotbID(pubkey) + toNode = idty.wotb_id + nodesCache[pubkey] = toNode } return toNode; } -async function sigCheckRevoke(entry: MindexEntry, dal: any, currency: string) { +async function sigCheckRevoke(entry: MindexEntry, dal: FileDAL, currency: string) { try { let pubkey = entry.pub, sig = entry.revocation || ""; - let idty = await dal.getWrittenIdtyByPubkey(pubkey); + let idty = await dal.getWrittenIdtyByPubkeyForRevocationCheck(pubkey); if (!idty) { throw Error("A pubkey who was never a member cannot be revoked"); } - if (idty.revoked) { + if (idty.revoked_on) { throw Error("A revoked identity cannot be revoked again"); } let rawRevocation = rawer.getOfficialRevocation({ currency: currency, - issuer: idty.pubkey, + issuer: idty.pub, uid: idty.uid, - buid: idty.buid, + buid: idty.created_on, sig: idty.sig, revocation: '' }); @@ -1940,22 +2072,28 @@ async function sigCheckRevoke(entry: MindexEntry, dal: any, currency: string) { -async function checkCertificationIsValid (block: BlockDTO, cert: CindexEntry, findIdtyFunc: (b:BlockDTO,to:string,dal:any)=>Promise<IdentityDTO>, conf: ConfDTO, dal: any) { +async function checkCertificationIsValid (block: BlockDTO, cert: CindexEntry, findIdtyFunc: (b:BlockDTO,to:string,dal:FileDAL)=>Promise<{ + pubkey:string + uid:string + buid:string + sig:string +}|null>, conf: ConfDTO, dal:FileDAL) { if (block.number == 0 && cert.created_on != 0) { throw Error('Number must be 0 for root block\'s certifications'); } else { try { - let basedBlock = new BlockDTO() - basedBlock.hash = constants.SPECIAL_HASH - + let basedBlock:Tristamp|null = { + number: 0, + hash: constants.SPECIAL_HASH, + medianTime: 0 + } if (block.number != 0) { - try { - basedBlock = await dal.getBlock(cert.created_on); - } catch (e) { - throw Error('Certification based on an unexisting block'); + basedBlock = await dal.getTristampOf(cert.created_on) + if (!basedBlock) { + throw Error('Certification based on an unexisting block') } } - let idty = await findIdtyFunc(block, cert.receiver, dal) + const idty = await findIdtyFunc(block, cert.receiver, dal) let current = block.number == 0 ? null : await dal.getCurrentBlockOrNull(); if (!idty) { throw Error('Identity does not exist for certified'); @@ -1967,8 +2105,8 @@ async function checkCertificationIsValid (block: BlockDTO, cert: CindexEntry, fi throw Error('Rejected certification: certifying its own self-certification has no meaning'); else { const buid = [cert.created_on, basedBlock.hash].join('-'); - idty.currency = conf.currency; - const raw = rawer.getOfficialCertification(_.extend(idty, { + const raw = rawer.getOfficialCertification({ + currency: conf.currency, idty_issuer: idty.pubkey, idty_uid: idty.uid, idty_buid: idty.buid, @@ -1976,7 +2114,7 @@ async function checkCertificationIsValid (block: BlockDTO, cert: CindexEntry, fi issuer: cert.issuer, buid: buid, sig: '' - })); + }) const verified = verify(raw, cert.sig, cert.issuer); if (!verified) { throw constants.ERRORS.WRONG_SIGNATURE_FOR_CERT @@ -1989,7 +2127,7 @@ async function checkCertificationIsValid (block: BlockDTO, cert: CindexEntry, fi } } -function txSourceUnlock(ENTRY:SindexEntry, source:SindexEntry, HEAD: DBHead) { +function txSourceUnlock(ENTRY:SindexEntry, source:{ conditions: string, written_time: number}, HEAD: DBHead) { const tx = ENTRY.txObj; const unlockParams:string[] = TransactionDTO.unlock2params(ENTRY.unlock || '') const unlocksMetadata:UnlockMetadata = {} diff --git a/app/lib/logger.ts b/app/lib/logger.ts index 0fe2865b849f4ea81e889697798118c1ab5f3fd6..f4d4f35babb0ce7e12c6b0454283e256f2cc2eb4 100644 --- a/app/lib/logger.ts +++ b/app/lib/logger.ts @@ -11,11 +11,11 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; -const moment = require('moment'); +import * as moment from "moment" +import {Directory} from "./system/directory" + const path = require('path'); const winston = require('winston'); -const directory = require('../lib/system/directory'); /*************** * CALLBACK LOGGER @@ -151,11 +151,11 @@ logger.unmute = () => { /** * Default logging path */ -logger.addHomeLogs(directory.INSTANCE_HOME) +logger.addHomeLogs(Directory.INSTANCE_HOME) /** * Convenience function to get logger directly */ -export function NewLogger() { +export function NewLogger(name?:string) { return logger } diff --git a/app/lib/other_constants.ts b/app/lib/other_constants.ts index 203afe23d6bcac3053657be600b52b927e58431c..2b0139b508162b43c0d3948f0738687386b8d16b 100644 --- a/app/lib/other_constants.ts +++ b/app/lib/other_constants.ts @@ -14,10 +14,15 @@ export const OtherConstants = { MUTE_LOGS_DURING_UNIT_TESTS: true, + SQL_TRACES: false, BC_EVENT: { SWITCHED: 'switched', HEAD_CHANGED: 'newHEAD', RESOLUTION_DONE: 'resolution_done' - } + }, + + ENABLE_LOKI_MONITORING: false, + ENABLE_SQL_MONITORING: false, + TRACE_BALANCES: false } \ No newline at end of file diff --git a/app/lib/rules/global_rules.ts b/app/lib/rules/global_rules.ts index 3bc3d5417591fea6df8b84d6e1ff7290786e6669..57721cfb1b7bfc6be0f7c18cc6c1da27815769f7 100644 --- a/app/lib/rules/global_rules.ts +++ b/app/lib/rules/global_rules.ts @@ -21,10 +21,9 @@ import {rawer, txunlock} from "../common-libs/index" import {CommonConstants} from "../common-libs/constants" import {IdentityDTO} from "../dto/IdentityDTO" import {hashf} from "../common" -import {Indexer} from "../indexer" -import {DBTx} from "../dal/sqliteDAL/TxsDAL" - -const _ = require('underscore') +import {Indexer, SimpleTxInput} from "../indexer" +import {DBTx} from "../db/DBTx" +import {Tristamp} from "../common/Tristamp" const constants = CommonConstants @@ -76,16 +75,16 @@ export const GLOBAL_RULES_FUNCTIONS = { let current = await dal.getCurrentBlockOrNull(); for (const obj of block.identities) { let idty = IdentityDTO.fromInline(obj); - let found = await dal.getWrittenIdtyByUID(idty.uid); + let found = await dal.getWrittenIdtyByUIDForExistence(idty.uid) if (found) { throw Error('Identity already used'); } // Because the window rule does not apply on initial certifications if (current && idty.buid != constants.SPECIAL_BLOCK) { // From DUP 0.5: we fully check the blockstamp - const basedBlock = await dal.getBlockByBlockstamp(idty.buid); + const basedBlock = await dal.getAbsoluteValidBlockInForkWindowByBlockstamp(idty.buid) || { medianTime: 0 } // Check if writable - let duration = current.medianTime - parseInt(basedBlock.medianTime); + let duration = current.medianTime - basedBlock.medianTime if (duration > conf.idtyWindow) { throw Error('Identity is too old and cannot be written'); } @@ -102,7 +101,7 @@ export const GLOBAL_RULES_FUNCTIONS = { const outputs = tx.outputsAsObjects() let unlocks:any = {}; let sumOfInputs = 0; - let maxOutputBase = current.unitbase; + let maxOutputBase = current && current.unitbase || 0; for (const theUnlock of tx.unlocks) { let sp = theUnlock.split(':'); let index = parseInt(sp[0]); @@ -110,7 +109,7 @@ export const GLOBAL_RULES_FUNCTIONS = { } for (let k = 0, len2 = inputs.length; k < len2; k++) { let src = inputs[k]; - let dbSrc = await dal.getSource(src.identifier, src.pos); + let dbSrc: SimpleTxInput|null = await dal.getSource(src.identifier, src.pos, src.type === 'D'); logger.debug('Source %s:%s:%s:%s = %s', src.amount, src.base, src.identifier, src.pos, dbSrc && dbSrc.consumed); if (!dbSrc) { // For chained transactions which are checked on sandbox submission, we accept them if there is already @@ -182,17 +181,21 @@ export const GLOBAL_RULES_FUNCTIONS = { export const GLOBAL_RULES_HELPERS = { // Functions used in an external context too - checkMembershipBlock: (ms:any, current:DBBlock, conf:ConfDTO, dal:FileDAL) => checkMSTarget(ms, current ? { number: current.number + 1} : { number: 0 }, conf, dal), + checkMembershipBlock: (ms:any, current:DBBlock|null, conf:ConfDTO, dal:FileDAL) => checkMSTarget(ms, current ? { number: current.number + 1} : { number: 0 }, conf, dal), - checkCertificationIsValid: (cert:any, current:BlockDTO, findIdtyFunc:any, conf:ConfDTO, dal:FileDAL) => { - return checkCertificationIsValid(current ? current : { number: 0, currency: '' }, cert, findIdtyFunc, conf, dal) + checkCertificationIsValidInSandbox: (cert:any, current:BlockDTO, findIdtyFunc:any, conf:ConfDTO, dal:FileDAL) => { + return checkCertificationShouldBeValid(current ? current : { number: 0, currency: '' }, cert, findIdtyFunc, conf, dal) }, - checkCertificationIsValidForBlock: (cert:any, block:{ number:number, currency:string }, findIdtyFunc:(b:{ number:number, currency:string }, pubkey:string, dal:FileDAL) => Promise<any>, conf:ConfDTO, dal:FileDAL) => { - return checkCertificationIsValid(block, cert, findIdtyFunc, conf, dal) + checkCertificationIsValidForBlock: (cert:any, block:{ number:number, currency:string }, findIdtyFunc:(b:{ number:number, currency:string }, pubkey:string, dal:FileDAL) => Promise<{ + pubkey:string + uid:string + buid:string + sig:string}|null>, conf:ConfDTO, dal:FileDAL) => { + return checkCertificationShouldBeValid(block, cert, findIdtyFunc, conf, dal) }, - isOver3Hops: async (member:any, newLinks:any, newcomers:string[], current:DBBlock, conf:ConfDTO, dal:FileDAL) => { + isOver3Hops: async (member:any, newLinks:any, newcomers:string[], current:DBBlock|null, conf:ConfDTO, dal:FileDAL) => { if (!current) { return Promise.resolve(false); } @@ -203,9 +206,9 @@ export const GLOBAL_RULES_HELPERS = { } }, - checkExistsUserID: (uid:string, dal:FileDAL) => dal.getWrittenIdtyByUID(uid), + checkExistsUserID: (uid:string, dal:FileDAL) => dal.getWrittenIdtyByUIDForExistence(uid), - checkExistsPubkey: (pub:string, dal:FileDAL) => dal.getWrittenIdtyByPubkey(pub), + checkExistsPubkey: (pub:string, dal:FileDAL) => dal.getWrittenIdtyByPubkeyForExistence(pub), checkSingleTransaction: ( tx:TransactionDTO, @@ -220,7 +223,7 @@ export const GLOBAL_RULES_HELPERS = { checkTxBlockStamp: async (tx:TransactionDTO, dal:FileDAL) => { const number = parseInt(tx.blockstamp.split('-')[0]) const hash = tx.blockstamp.split('-')[1]; - const basedBlock = await dal.getBlockByNumberAndHashOrNull(number, hash); + const basedBlock = await dal.getAbsoluteValidBlockInForkWindow(number, hash) if (!basedBlock) { throw "Wrong blockstamp for transaction"; } @@ -249,11 +252,9 @@ async function checkMSTarget (ms:any, block:any, conf:ConfDTO, dal:FileDAL) { else if (block.number == 0) { return null; // Valid for root block } else { - let basedBlock; - try { - basedBlock = await dal.getBlockByNumberAndHash(ms.number, ms.fpr); - } catch (e) { - throw Error('Membership based on an unexisting block'); + const basedBlock = await dal.getAbsoluteValidBlockInForkWindow(ms.number, ms.fpr) + if (!basedBlock) { + throw Error('Membership based on an unexisting block') } let current = await dal.getCurrentBlockOrNull(); if (current && current.medianTime > basedBlock.medianTime + conf.msValidity) { @@ -263,21 +264,27 @@ async function checkMSTarget (ms:any, block:any, conf:ConfDTO, dal:FileDAL) { } } -async function checkCertificationIsValid (block:{ number:number, currency:string }, cert:any, findIdtyFunc:(b:{ number:number, currency:string }, pubkey:string, dal:FileDAL) => Promise<any>, conf:ConfDTO, dal:FileDAL) { +async function checkCertificationShouldBeValid (block:{ number:number, currency:string }, cert:any, findIdtyFunc:(b:{ number:number, currency:string }, pubkey:string, dal:FileDAL) => Promise<{ + pubkey:string + uid:string + buid:string + sig:string +}|null>, conf:ConfDTO, dal:FileDAL) { if (block.number == 0 && cert.block_number != 0) { throw Error('Number must be 0 for root block\'s certifications'); } else { - let basedBlock:any = { - hash: constants.SPECIAL_HASH - }; + let basedBlock:Tristamp|null = { + number: 0, + hash: constants.SPECIAL_HASH, + medianTime: 0 + } if (block.number != 0) { - try { - basedBlock = await dal.getBlock(cert.block_number); - } catch (e) { + basedBlock = await dal.getTristampOf(cert.block_number) + if (!basedBlock) { throw Error('Certification based on an unexisting block'); } try { - const issuer = await dal.getWrittenIdtyByPubkey(cert.from) + const issuer = await dal.getWrittenIdtyByPubkeyForIsMember(cert.from) if (!issuer || !issuer.member) { throw Error('Issuer is not a member') } @@ -299,8 +306,8 @@ async function checkCertificationIsValid (block:{ number:number, currency:string const buid = [cert.block_number, basedBlock.hash].join('-'); if (cert.block_hash && buid != [cert.block_number, cert.block_hash].join('-')) throw Error('Certification based on an unexisting block buid. from ' + cert.from.substring(0,8) + ' to ' + idty.pubkey.substring(0,8)); - idty.currency = conf.currency; - const raw = rawer.getOfficialCertification(_.extend(idty, { + const raw = rawer.getOfficialCertification({ + currency: conf.currency, idty_issuer: idty.pubkey, idty_uid: idty.uid, idty_buid: idty.buid, @@ -308,7 +315,7 @@ async function checkCertificationIsValid (block:{ number:number, currency:string issuer: cert.from, buid: buid, sig: '' - })); + }) const verified = verify(raw, cert.sig, cert.from); if (!verified) { throw constants.ERRORS.WRONG_SIGNATURE_FOR_CERT diff --git a/app/lib/rules/local_rules.ts b/app/lib/rules/local_rules.ts index bec4b70c7424835917b1d20340aa64c1da997fe9..2522a45fb361d3373dc240a74bf6079fa66996f3 100644 --- a/app/lib/rules/local_rules.ts +++ b/app/lib/rules/local_rules.ts @@ -11,7 +11,6 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; import {BlockDTO} from "../dto/BlockDTO" import {ConfDTO} from "../dto/ConfDTO" import {CindexEntry, IndexEntry, Indexer, MindexEntry, SindexEntry} from "../indexer" @@ -22,8 +21,7 @@ import {hashf} from "../common" import {CommonConstants} from "../common-libs/constants" import {IdentityDTO} from "../dto/IdentityDTO" import {MembershipDTO} from "../dto/MembershipDTO" - -const _ = require('underscore'); +import {Underscore} from "../common-libs/underscore" const constants = CommonConstants const maxAcceleration = require('./helpers').maxAcceleration @@ -120,7 +118,7 @@ export const LOCAL_RULES_FUNCTIONS = { checkIdentitiesUserIDConflict: async (block:BlockDTO, conf:ConfDTO, index:IndexEntry[]) => { const creates = Indexer.iindexCreate(index); - const uids = _.chain(creates).pluck('uid').uniq().value(); + const uids = Underscore.chain(creates).pluck('uid').uniq().value(); if (creates.length !== uids.length) { throw Error('Block must not contain twice same identity uid'); } @@ -129,7 +127,7 @@ export const LOCAL_RULES_FUNCTIONS = { checkIdentitiesPubkeyConflict: async (block:BlockDTO, conf:ConfDTO, index:IndexEntry[]) => { const creates = Indexer.iindexCreate(index); - const pubkeys = _.chain(creates).pluck('pub').uniq().value(); + const pubkeys = Underscore.chain(creates).pluck('pub').uniq().value(); if (creates.length !== pubkeys.length) { throw Error('Block must not contain twice same identity pubkey'); } @@ -140,7 +138,7 @@ export const LOCAL_RULES_FUNCTIONS = { const icreates = Indexer.iindexCreate(index); const mcreates = Indexer.mindexCreate(index); for (const icreate of icreates) { - const matching = _(mcreates).filter({ pub: icreate.pub }); + const matching = Underscore.where(mcreates, { pub: icreate.pub }); if (matching.length == 0) { throw Error('Each identity must match a newcomer line with same userid and certts'); } @@ -151,12 +149,11 @@ export const LOCAL_RULES_FUNCTIONS = { checkRevokedAreExcluded: async (block:BlockDTO, conf:ConfDTO, index:IndexEntry[]) => { const iindex = Indexer.iindex(index); const mindex = Indexer.mindex(index); - const revocations = _.chain(mindex) - .filter((row:MindexEntry) => row.op == constants.IDX_UPDATE && row.revoked_on !== null) - .pluck('pub') - .value(); + const revocations = mindex + .filter((row:MindexEntry) => !!(row.op == constants.IDX_UPDATE && row.revoked_on !== null)) + .map(e => e.pub) for (const pub of revocations) { - const exclusions = _(iindex).where({ op: constants.IDX_UPDATE, member: false, pub }); + const exclusions = Underscore.where(iindex, { op: constants.IDX_UPDATE, member: false, pub }) if (exclusions.length == 0) { throw Error('A revoked member must be excluded'); } @@ -175,7 +172,7 @@ export const LOCAL_RULES_FUNCTIONS = { checkMembershipUnicity: async (block:BlockDTO, conf:ConfDTO, index:IndexEntry[]) => { const mindex = Indexer.mindex(index); - const pubkeys = _.chain(mindex).pluck('pub').uniq().value(); + const pubkeys = Underscore.chain(mindex).pluck('pub').uniq().value(); if (pubkeys.length !== mindex.length) { throw Error('Unicity constraint PUBLIC_KEY on MINDEX is not respected'); } @@ -256,7 +253,7 @@ export const LOCAL_RULES_FUNCTIONS = { checkCertificationOneByIssuer: async (block:BlockDTO, conf:ConfDTO, index:IndexEntry[]) => { if (block.number > 0) { const cindex = Indexer.cindex(index); - const certFromA = _.uniq(cindex.map((row:CindexEntry) => row.issuer)); + const certFromA = Underscore.uniq(cindex.map((row:CindexEntry) => row.issuer)); if (certFromA.length !== cindex.length) { throw Error('Block cannot contain two certifications from same issuer'); } @@ -266,7 +263,7 @@ export const LOCAL_RULES_FUNCTIONS = { checkCertificationUnicity: async (block:BlockDTO, conf:ConfDTO, index:IndexEntry[]) => { const cindex = Indexer.cindex(index); - const certAtoB = _.uniq(cindex.map((row:CindexEntry) => row.issuer + row.receiver)); + const certAtoB = Underscore.uniq(cindex.map((row:CindexEntry) => row.issuer + row.receiver)); if (certAtoB.length !== cindex.length) { throw Error('Block cannot contain identical certifications (A -> B)'); } @@ -279,8 +276,8 @@ export const LOCAL_RULES_FUNCTIONS = { const mindex = Indexer.mindex(index); const certified = cindex.map((row:CindexEntry) => row.receiver); for (const pub of certified) { - const exclusions = _(iindex).where({ op: constants.IDX_UPDATE, member: false, pub: pub }); - const leavers = _(mindex).where({ op: constants.IDX_UPDATE, leaving: true, pub: pub }); + const exclusions = Underscore.where(iindex, { op: constants.IDX_UPDATE, member: false, pub: pub }) + const leavers = Underscore.where(mindex, { op: constants.IDX_UPDATE, leaving: true, pub: pub }) if (exclusions.length > 0 || leavers.length > 0) { throw Error('Block cannot contain certifications concerning leavers or excluded members'); } @@ -347,12 +344,12 @@ export const LOCAL_RULES_FUNCTIONS = { } } const sindex = Indexer.localSIndex(dto); - const inputs = _.filter(sindex, (row:SindexEntry) => row.op == constants.IDX_UPDATE).map((row:SindexEntry) => [row.op, row.identifier, row.pos].join('-')); - if (inputs.length !== _.uniq(inputs).length) { + const inputs = Underscore.filter(sindex, (row:SindexEntry) => row.op == constants.IDX_UPDATE).map((row:SindexEntry) => [row.op, row.identifier, row.pos].join('-')); + if (inputs.length !== Underscore.uniq(inputs).length) { throw Error('It cannot exist 2 identical sources for transactions inside a given block'); } - const outputs = _.filter(sindex, (row:SindexEntry) => row.op == constants.IDX_CREATE).map((row:SindexEntry) => [row.op, row.identifier, row.pos].join('-')); - if (outputs.length !== _.uniq(outputs).length) { + const outputs = Underscore.filter(sindex, (row:SindexEntry) => row.op == constants.IDX_CREATE).map((row:SindexEntry) => [row.op, row.identifier, row.pos].join('-')); + if (outputs.length !== Underscore.uniq(outputs).length) { throw Error('It cannot exist 2 identical sources for transactions inside a given block'); } return true; @@ -414,7 +411,7 @@ export interface SindexShortEntry { } function getMaxTransactionDepth(sindex:SindexShortEntry[]) { - const ids = _.uniq(_.pluck(sindex, 'tx')) + const ids = Underscore.uniq(Underscore.pluck(sindex, 'tx')) as string[] // We are sure because at this moment no UD is in the sources let maxTxChainingDepth = 0 for (let id of ids) { maxTxChainingDepth = Math.max(maxTxChainingDepth, getTransactionDepth(id, sindex, 0)) @@ -423,13 +420,14 @@ function getMaxTransactionDepth(sindex:SindexShortEntry[]) { } function getTransactionDepth(txHash:string, sindex:SindexShortEntry[], localDepth = 0) { - const inputs = _.filter(sindex, (s:SindexShortEntry) => s.op === 'UPDATE' && s.tx === txHash) + const inputs = Underscore.filter(sindex, (s:SindexShortEntry) => s.op === 'UPDATE' && s.tx === txHash) let depth = localDepth for (let input of inputs) { - const consumedOutput = _.findWhere(sindex, { op: 'CREATE', identifier: input.identifier, pos: input.pos }) + const consumedOutput = Underscore.findWhere(sindex, { op: 'CREATE', identifier: input.identifier, pos: input.pos }) if (consumedOutput) { if (localDepth < 5) { - const subTxDepth = getTransactionDepth(consumedOutput.tx, sindex, localDepth + 1) + // Cast: we are sure because at this moment no UD is in the sources + const subTxDepth = getTransactionDepth(consumedOutput.tx as string, sindex, localDepth + 1) depth = Math.max(depth, subTxDepth) } else { depth++ @@ -524,7 +522,7 @@ export const LOCAL_RULES_HELPERS = { } }, - getMaxPossibleVersionNumber: async (current:DBBlock) => { + getMaxPossibleVersionNumber: async (current:DBBlock|null) => { // Looking at current blockchain, find what is the next maximum version we can produce // 1. We follow previous block's version diff --git a/app/lib/streams/multicaster.ts b/app/lib/streams/multicaster.ts index 43d2a3b0621611c38de47941a7723ff4f19b54ea..cf2cdbf3fc9eb7b2c2e13cd108c14b64c2e03290 100644 --- a/app/lib/streams/multicaster.ts +++ b/app/lib/streams/multicaster.ts @@ -13,7 +13,6 @@ import {ConfDTO} from "../dto/ConfDTO" import * as stream from "stream" -import {DBPeer} from "../dal/sqliteDAL/PeerDAL" import {BlockDTO} from "../dto/BlockDTO" import {RevocationDTO} from "../dto/RevocationDTO" import {IdentityDTO} from "../dto/IdentityDTO" @@ -22,6 +21,7 @@ import {MembershipDTO} from "../dto/MembershipDTO" import {TransactionDTO} from "../dto/TransactionDTO" import {PeerDTO} from "../dto/PeerDTO" import {CommonConstants} from "../common-libs/constants" +import {DBPeer} from "../db/DBPeer" const request = require('request'); const constants = require('../../lib/constants'); diff --git a/app/lib/streams/router.ts b/app/lib/streams/router.ts index 30953a8689928e2ee715e58ce9a5140fb014d078..d5a88d46b883d934588cf2a138466ff11c488df6 100644 --- a/app/lib/streams/router.ts +++ b/app/lib/streams/router.ts @@ -14,8 +14,8 @@ import * as stream from "stream" import {PeeringService} from "../../service/PeeringService" import {FileDAL} from "../dal/fileDAL" -import {DBPeer} from "../dal/sqliteDAL/PeerDAL" import {PeerDTO} from "../dto/PeerDTO" +import {DBPeer} from "../db/DBPeer" const constants = require('../constants'); diff --git a/app/lib/system/directory.ts b/app/lib/system/directory.ts index 1dffb23d07035b38568a353abb118d2ce9cf6d05..f30eec2e48e03ccc4fddde860156278c68805275 100644 --- a/app/lib/system/directory.ts +++ b/app/lib/system/directory.ts @@ -11,69 +11,164 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. +import * as path from "path" +import * as fs from 'fs' import {SQLiteDriver} from "../dal/drivers/SQLiteDriver" import {CFSCore} from "../dal/fileDALs/CFSCore" -import {WoTBObject} from "../wot" +import {WoTBInstance, WoTBObject} from "../wot" +import {FileDALParams} from "../dal/fileDAL" +import {LokiJsDriver} from "../dal/drivers/LokiJsDriver" +import {cliprogram} from "../common-libs/programOptions" -const opts = require('optimist').argv; -const path = require('path'); +const opts = cliprogram const qfs = require('q-io/fs'); -const fs = require('fs'); const DEFAULT_DOMAIN = "duniter_default"; const DEFAULT_HOME = (process.platform == 'win32' ? process.env.USERPROFILE : process.env.HOME) + '/.config/duniter/'; -const getLogsPath = (profile:string, directory:string|null = null) => path.join(getHomePath(profile, directory), 'duniter.log'); +const getLogsPath = (profile:string|undefined, directory:string|null = null) => path.join(getHomePath(profile, directory), 'duniter.log'); -const getHomePath = (profile:string|null, directory:string|null = null) => path.normalize(getUserHome(directory) + '/') + getDomain(profile); +const getHomePath = (profile:string|null|undefined, directory:string|null = null) => path.normalize(getUserHome(directory) + '/') + getDomain(profile); const getUserHome = (directory:string|null = null) => (directory || DEFAULT_HOME); const getDomain = (profile:string|null = null) => (profile || DEFAULT_DOMAIN); -const dir = module.exports = { +export interface FileSystem { + fsExists(file:string): Promise<boolean> + fsReadFile(file:string): Promise<string> + fsUnlink(file:string): Promise<boolean> + fsList(dir:string): Promise<string[]> + fsWrite(file:string, content:string): Promise<void> + fsMakeDirectory(dir:string): Promise<void> + fsRemoveTree(dir:string): Promise<void> + fsStreamTo(file: string, iterator: IterableIterator<string>): Promise<void> +} + +class QioFileSystem implements FileSystem { + + constructor(private qio:any, private isMemory:boolean = false) {} + + async fsExists(file:string) { + return this.qio.exists(file) + } + + async fsReadFile(file:string) { + return this.qio.read(file) + } + + async fsUnlink(file:string) { + return this.qio.remove(file) + } + + async fsList(dir: string): Promise<string[]> { + if (!(await this.qio.exists(dir))) { + return [] + } + return this.qio.list(dir) + } + + fsWrite(file: string, content: string): Promise<void> { + return this.qio.write(file, content) + } + + async fsStreamTo(file: string, iterator: IterableIterator<string>): Promise<void> { + if (this.isMemory) { + for (const line of iterator) { + await this.qio.append(file, line) + } + } else { + // Use NodeJS streams for faster writing + let wstream = fs.createWriteStream(file) + await new Promise(async (res, rej) => { + // When done, return + wstream.on('close', (err:any) => { + if (err) return rej(err) + res() + }) + // Write each line + for (const line of iterator) { + wstream.write(line + "\n") + } + // End the writing + wstream.end() + }) + } + } + + fsMakeDirectory(dir: string): Promise<void> { + return this.qio.makeTree(dir) + } + + async fsRemoveTree(dir: string): Promise<void> { + return this.qio.removeTree(dir) + } +} + +export const RealFS = (): FileSystem => { + return new QioFileSystem(qfs) +} + +export const MemFS = (initialTree:{ [folder:string]: { [file:string]: string }} = {}): FileSystem => { + return new QioFileSystem(require('q-io/fs-mock')(initialTree), true) +} + +export const Directory = { INSTANCE_NAME: getDomain(opts.mdb), INSTANCE_HOME: getHomePath(opts.mdb, opts.home), INSTANCE_HOMELOG_FILE: getLogsPath(opts.mdb, opts.home), DUNITER_DB_NAME: 'duniter', + LOKI_DB_DIR: 'loki', WOTB_FILE: 'wotb.bin', getHome: (profile:string|null = null, directory:string|null = null) => getHomePath(profile, directory), getHomeFS: async (isMemory:boolean, theHome:string, makeTree = true) => { - const home = theHome || dir.getHome(); - const params:any = { - home: home - }; - if (isMemory) { - params.fs = require('q-io/fs-mock')({}); - } else { - params.fs = qfs; + const home = theHome || Directory.getHome() + const params = { + home: home, + fs: isMemory ? MemFS() : RealFS() } if (makeTree) { - await params.fs.makeTree(home) + await params.fs.fsMakeDirectory(home) } return params; }, - getHomeParams: async (isMemory:boolean, theHome:string) => { - const params:any = await dir.getHomeFS(isMemory, theHome) + getHomeParams: async (isMemory:boolean, theHome:string): Promise<FileDALParams> => { + const params = await Directory.getHomeFS(isMemory, theHome) const home = params.home; + let dbf: () => SQLiteDriver + let dbf2: () => LokiJsDriver + let wotb: WoTBInstance if (isMemory) { - params.dbf = () => new SQLiteDriver(':memory:'); - params.wotb = WoTBObject.memoryInstance(); + + // Memory DB + dbf = () => new SQLiteDriver(':memory:'); + dbf2 = () => new LokiJsDriver() + wotb = WoTBObject.memoryInstance(); + } else { - const sqlitePath = path.join(home, dir.DUNITER_DB_NAME + '.db'); - params.dbf = () => new SQLiteDriver(sqlitePath); - const wotbFilePath = path.join(home, dir.WOTB_FILE); + + // File DB + const sqlitePath = path.join(home, Directory.DUNITER_DB_NAME + '.db'); + dbf = () => new SQLiteDriver(sqlitePath); + const wotbFilePath = path.join(home, Directory.WOTB_FILE); let existsFile = await qfs.exists(wotbFilePath) if (!existsFile) { fs.closeSync(fs.openSync(wotbFilePath, 'w')); } - params.wotb = WoTBObject.fileInstance(wotbFilePath); + dbf2 = () => new LokiJsDriver(path.join(home, Directory.LOKI_DB_DIR)) + wotb = WoTBObject.fileInstance(wotbFilePath); + } + return { + home: params.home, + fs: params.fs, + dbf, + dbf2, + wotb } - return params; }, createHomeIfNotExists: async (fileSystem:any, theHome:string) => { diff --git a/app/lib/wot.ts b/app/lib/wot.ts index b535ca68e81e533af1f52581e946e27825d92a3f..a3a68b2dd8642ef21fa7e6612d33a2ad52875de2 100644 --- a/app/lib/wot.ts +++ b/app/lib/wot.ts @@ -13,6 +13,169 @@ const wotb = require('wotb'); +export interface WoTBInstance { + + readonly instanceID:number + readonly filePath:string + + /** + * Gets this instance ID. + * @returns {number} The instance technical ID. + */ + getId(): number + + /** + * Makes a memory copy of the WoT instance, and returns this new instance. + * @returns {WoTBInstance} The new memory instance. + */ + memCopy(): WoTBInstance + + /** + * Remove the WoT from the computer's memory. + */ + clear(): void + + /** + * Returns a dump of the WoT as a string. + * @returns {string} The dump. + */ + dumpWoT(): string + + /** + * Makes a dump of the WoT on standard output. + */ + showGraph(): void + + /** + * Removes any node and certification from the WoT. + */ + resetWoT(): void + + /** + * Gets the total number of nodes in the WoT, enabled or not. + * @returns {number} The total of nodes ever added to the WoT. + */ + getWoTSize(): number + + /** + * Add a node and returns its wotb_id. + * @returns {number} The new node identifier. + */ + addNode(): number + + /** + * Remove the lastly added node from the WoT, as well as the certifications it received. + */ + removeNode(): void + + /** + * Sets the maximum number of certifications a node can emit. + * @param {number} max The maximum number of emitted certifications. + */ + setMaxCert(max:number): void + + /** + * Gets the maximum number of certifications a node can emit in the WoT. + * @returns {number} The maximum's value. + */ + getMaxCert(): number + + /** + * Tells wether a node is enabled or not (= member or not). + * @param node Node's ID. + * @returns {boolean} True if enabled, false otherwise. + */ + isEnabled(node:number): boolean + + /** + * Enables or disables a node. + * @param enabled True to enable, False to disable. + * @param node The node to change. + */ + setEnabled(enabled:boolean, node:number): void + + /** + * Tells wether a link exists from a member to another. + * @param from The emitting node. + * @param to The receiving node. + * @returns {boolean} + */ + existsLink(from:number, to:number): boolean + + /** + * Adds a link from a node to another. + * @param from The emitting node. + * @param to The receiving node. + * @returns {boolean} True if the link was added, false otherwise (for example if it from exceeded the maximum quota). + */ + addLink(from:number, to:number): boolean + + /** + * Removes a link from a node to another. Returns the new number of links issued to the destination node. + * @param from Issuer. + * @param to Receiver. + * @returns {number} The new number of links reaching Receiver. + */ + removeLink(from:number, to:number): number + + /** + * Tells wether a node is outdistanced from the WoT. + * @param {number} node The node we want to test. + * @param {number} d_min The minimum number of both issued and received certifications to be considered a sentry. + * @param {number} k_max The maximum distance from the sentries to the node. + * @param {number} x_percent The percentage of sentries to reach to not be considered outdistanced. + * @returns {boolean} True is the node is outdistanced, false otherwise. + */ + isOutdistanced(node:number, d_min:number, k_max:number, x_percent:number): boolean + + /** + * Gives details about the distance of a node from the WoT. + * @param {number} node The node we want to test. + * @param {number} d_min The minimum number of both issued and received certifications to be considered a sentry. + * @param {number} k_max The maximum distance from the sentries to the node. + * @param {number} x_percent The percentage of sentries to reach to not be considered outdistanced. + * @returns {{nbSuccess: number; nbSentries: number; nbReached: number; isOutdistanced: boolean}} The number of reached sentries, the number of sentries, the number of reached members, the distance test. + */ + detailedDistance(node:number, d_min:number, k_max:number, x_percent:number): { + nbSuccess: number + nbSentries: number + nbReached: number + isOutdistanced: boolean + } + + /** + * Returns the sentries of the WoT. + * @param {number} d_min The minimum number of both issued and received certifications to be considered a sentry. + * @returns {number} An array of node ID (= array of integers). + */ + getSentries(d_min:number): number[] + + /** + * Returns the non-sentires of the WoT. + * @param {number} d_min The minimum number of both issued and received certifications to be considered a sentry. + * @returns {number} An array of node ID (= array of integers). + */ + getNonSentries(d_min:number): number[] + + /** + * Returns the non-members of the WoT. + * @returns {number} An array of node ID (= array of integers). + */ + getDisabled(): number[] + + /** + * Returns the list of existing paths from a node to another, using a maximum of k_max steps. + * @param {number} from The departure node. + * @param {number} to The arrival node. + * @param {number} k_max The maximum number of steps allowed for reaching the arrival node from departure node. + * @returns {number[][]} A list of paths. Example of paths from ID 5 to ID 189 using k_max 4 + * [0] = [5, 822, 333, 12, 189] + * [1] = [5, 29, 189] + * [2] = [5, 189] + */ + getPaths(from:number, to:number, k_max:number): number[][] +} + export interface WoTBInterface { fileInstance: (filepath:string) => any memoryInstance: () => any diff --git a/app/modules/bma/index.ts b/app/modules/bma/index.ts index 462ee2c857bfbb0c2f7097931e341b99f1ac23f1..7bfd78702290cfb9590907750ebe22282a3b9420 100644 --- a/app/modules/bma/index.ts +++ b/app/modules/bma/index.ts @@ -11,25 +11,21 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; import {NetworkConfDTO} from "../../lib/dto/ConfDTO" import {Server} from "../../../server" import * as stream from "stream" -import {BmaApi, Network} from "./lib/network" -import {UpnpApi} from "./lib/upnp" +import {BmaApi, Network, NetworkInterface} from "./lib/network" +import {Upnp, UpnpApi} from "./lib/upnp" import {BMAConstants} from "./lib/constants" import {BMALimitation} from "./lib/limiter" import {PeerDTO} from "../../lib/dto/PeerDTO" +import {Underscore} from "../../lib/common-libs/underscore" +import {bma} from "./lib/bma" const Q = require('q'); -const os = require('os'); const rp = require('request-promise'); const async = require('async'); -const _ = require('underscore'); -const upnp = require('./lib/upnp').Upnp -const bma = require('./lib/bma').bma const dtos = require('./lib/dtos') -const http2raw = require('./lib/http2raw'); const inquirer = require('inquirer'); let networkWizardDone = false; @@ -173,7 +169,7 @@ export const BmaDependency = { if (!conf.ipv6) delete conf.ipv6; if (!conf.remoteipv4) delete conf.remoteipv4; if (!conf.remoteipv6) delete conf.remoteipv6; - conf.dos.whitelist = _.uniq(conf.dos.whitelist); + conf.dos.whitelist = Underscore.uniq(conf.dos.whitelist); } }, @@ -189,7 +185,8 @@ export const BmaDependency = { methods: { noLimit: () => BMALimitation.noLimit(), - bma, dtos, + bma: async (server: Server, interfaces: (NetworkInterface[] | null) = null, httpLogs = false, logger?: any) => bma(server, interfaces, httpLogs, logger), + dtos, getMainEndpoint: (conf:NetworkConfDTO) => Promise.resolve(getEndpoint(conf)) } } @@ -243,7 +240,7 @@ export class BMAPI extends stream.Transform { } if (this.server.conf.upnp) { try { - this.upnpAPI = await upnp(this.server.conf.port, this.server.conf.remoteport, this.logger, this.server.conf); + this.upnpAPI = await Upnp(this.server.conf.port, this.server.conf.remoteport, this.logger, this.server.conf); this.upnpAPI.startRegular(); const gateway = await this.upnpAPI.findGateway(); if (gateway) { @@ -306,7 +303,7 @@ function networkReconfiguration(conf:NetworkConfDTO, autoconf:boolean, logger:an const useUPnPOperations = getUseUPnPOperations(conf, logger, autoconf); if (upnpSuccess) { - _.extend(conf, upnpConf); + Underscore.extend(conf, upnpConf) const local = [conf.ipv4, conf.port].join(':'); const remote = [conf.remoteipv4, conf.remoteport].join(':'); if (autoconf) { @@ -408,7 +405,7 @@ function getLocalNetworkOperations(conf:NetworkConfDTO, autoconf:boolean = false const interfaces = [{ name: "None", value: null }]; osInterfaces.forEach(function(netInterface:any){ const addresses = netInterface.addresses; - const filtered = _(addresses).where({family: 'IPv4'}); + const filtered = Underscore.where(addresses, {family: 'IPv4'}); filtered.forEach(function(addr:any){ interfaces.push({ name: [netInterface.name, addr.address].join(' '), @@ -436,7 +433,7 @@ function getLocalNetworkOperations(conf:NetworkConfDTO, autoconf:boolean = false const interfaces:any = [{ name: "None", value: null }]; osInterfaces.forEach(function(netInterface:any){ const addresses = netInterface.addresses; - const filtered = _(addresses).where({ family: 'IPv6' }); + const filtered = Underscore.where(addresses, { family: 'IPv6' }); filtered.forEach(function(addr:any){ let address = addr.address if (addr.scopeid) @@ -495,7 +492,7 @@ function getRemoteNetworkOperations(conf:NetworkConfDTO, remoteipv4:string|null) const osInterfaces = Network.listInterfaces(); osInterfaces.forEach(function(netInterface:any){ const addresses = netInterface.addresses; - const filtered = _(addresses).where({family: 'IPv4'}); + const filtered = Underscore.where(addresses, {family: 'IPv4'}); filtered.forEach(function(addr:any){ choices.push({ name: [netInterface.name, addr.address].join(' '), diff --git a/app/modules/bma/lib/bma.ts b/app/modules/bma/lib/bma.ts index 183125820a98dcc8ed1773bafed2a2273ae0f288..575ab3eb05369d70d38d08a9398394769eacabff 100644 --- a/app/modules/bma/lib/bma.ts +++ b/app/modules/bma/lib/bma.ts @@ -24,12 +24,11 @@ import {UDBinding} from "./controllers/uds" import {PeerDTO} from "../../../lib/dto/PeerDTO" import {BlockDTO} from "../../../lib/dto/BlockDTO" import {OtherConstants} from "../../../lib/other_constants" +import {WebSocketServer} from "../../../lib/common-libs/websocket" -const co = require('co'); const es = require('event-stream'); -const WebSocketServer = require('ws').Server; -export const bma = function(server:Server, interfaces:NetworkInterface[], httpLogs:boolean, logger:any): Promise<BmaApi> { +export const bma = function(server:Server, interfaces:NetworkInterface[]|null, httpLogs:boolean, logger:any): Promise<BmaApi> { if (!interfaces) { interfaces = []; @@ -110,7 +109,7 @@ export const bma = function(server:Server, interfaces:NetworkInterface[], httpLo }, (httpServer:any) => { - let currentBlock = {}; + let currentBlock:any = {}; let wssBlock = new WebSocketServer({ server: httpServer, path: '/ws/block' @@ -129,18 +128,16 @@ export const bma = function(server:Server, interfaces:NetworkInterface[], httpLo logger && logger.error(error); }); - wssBlock.on('connection', function connection(ws:any) { - co(function *() { - try { - currentBlock = yield server.dal.getCurrentBlockOrNull(); - if (currentBlock) { - const blockDTO:BlockDTO = BlockDTO.fromJSONObject(currentBlock) - ws.send(JSON.stringify(block2HttpBlock(blockDTO))) - } - } catch (e) { - logger.error(e); + wssBlock.on('connection', async function connection(ws:any) { + try { + currentBlock = await server.dal.getCurrentBlockOrNull(); + if (currentBlock) { + const blockDTO:BlockDTO = BlockDTO.fromJSONObject(currentBlock) + ws.send(JSON.stringify(block2HttpBlock(blockDTO))) } - }); + } catch (e) { + logger.error(e); + } }); wssHeads.on('connection', async (ws:any) => { @@ -152,16 +149,17 @@ export const bma = function(server:Server, interfaces:NetworkInterface[], httpLo } } }) - wssHeads.broadcast = (data:any) => wssHeads.clients.forEach((client:any) => client.send(data)); + const wssHeadsBroadcast = (data:any) => wssHeads.clients.forEach((client:any) => client.send(data)); - wssBlock.broadcast = (data:any) => wssBlock.clients.forEach((client:any) => { + const wssBlockBroadcast = (data:any) => wssBlock.clients.forEach((client:any) => { try { client.send(data); } catch (e) { logger && logger.error('error on ws: %s', e); } }); - wssPeer.broadcast = (data:any) => wssPeer.clients.forEach((client:any) => client.send(data)); + + const wssPeerBroadcast = (data:any) => wssPeer.clients.forEach((client:any) => client.send(data)); // Forward current HEAD change server @@ -171,7 +169,7 @@ export const bma = function(server:Server, interfaces:NetworkInterface[], httpLo // Broadcast block currentBlock = e.block; const blockDTO:BlockDTO = BlockDTO.fromJSONObject(currentBlock) - wssBlock.broadcast(JSON.stringify(block2HttpBlock(blockDTO))) + wssBlockBroadcast(JSON.stringify(block2HttpBlock(blockDTO))) } catch (e) { logger && logger.error('error on ws mapSync:', e); } @@ -193,11 +191,11 @@ export const bma = function(server:Server, interfaces:NetworkInterface[], httpLo signature: peerDTO.signature, raw: peerDTO.getRaw() } - wssPeer.broadcast(JSON.stringify(peerResult)); + wssPeerBroadcast(JSON.stringify(peerResult)); } // Broadcast heads else if (data.ws2p === 'heads' && data.added.length) { - wssHeads.broadcast(JSON.stringify(data.added)); + wssHeadsBroadcast(JSON.stringify(data.added)); } } catch (e) { logger && logger.error('error on ws mapSync:', e); diff --git a/app/modules/bma/lib/controllers/blockchain.ts b/app/modules/bma/lib/controllers/blockchain.ts index 0a0afd8c50023b85029350426aa9b7d59a8f5122..2fc5fa748dce9a9c453a532cec79b083c30dbf88 100644 --- a/app/modules/bma/lib/controllers/blockchain.ts +++ b/app/modules/bma/lib/controllers/blockchain.ts @@ -11,7 +11,6 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; import {Server} from "../../../../../server" import {AbstractController} from "./AbstractController" import {ParametersService} from "../parameters" @@ -28,8 +27,10 @@ import { HttpParameters, HttpStat } from "../dtos" +import {TransactionDTO} from "../../../../lib/dto/TransactionDTO" +import {DataErrors} from "../../../../lib/common-libs/errors" +import {Underscore} from "../../../../lib/common-libs/underscore" -const _ = require('underscore'); const http2raw = require('../http2raw'); const toJson = require('../tojson'); @@ -118,9 +119,53 @@ export class BlockchainBinding extends AbstractController { const params = ParametersService.getCountAndFrom(req); const count = parseInt(params.count); const from = parseInt(params.from); - let blocks = await this.BlockchainService.blocksBetween(from, count); + let blocks: any[] = await this.BlockchainService.blocksBetween(from, count); blocks = blocks.map((b:any) => toJson.block(b)); - return blocks; + return blocks.map(b => ({ + version: b.version, + currency: b.currency, + number: b.number, + issuer: b.issuer, + issuersFrame: b.issuersFrame, + issuersFrameVar: b.issuersFrameVar, + issuersCount: b.issuersCount, + parameters: b.parameters, + membersCount: b.membersCount, + monetaryMass: b.monetaryMass, + powMin: b.powMin, + time: b.time, + medianTime: b.medianTime, + dividend: b.dividend, + unitbase: b.unitbase, + hash: b.hash, + previousHash: b.previousHash, + previousIssuer: b.previousIssuer, + identities: b.identities, + certifications: b.certifications, + joiners: b.joiners, + actives: b.actives, + leavers: b.leavers, + revoked: b.revoked, + excluded: b.excluded, + transactions: b.transactions.map((t:TransactionDTO) => ({ + version: t.version, + currency: t.currency, + comment: t.comment, + locktime: t.locktime, + signatures: t.signatures, + outputs: t.outputs, + inputs: t.inputs, + unlocks: t.unlocks, + blockstamp: t.blockstamp, + blockstampTime: t.blockstampTime, + issuers: t.issuers, + hash: t.hash, + })), + nonce: b.nonce, + inner_hash: b.inner_hash, + signature: b.signature, + raw: b.raw, + })) } async current(): Promise<HttpBlock> { @@ -132,7 +177,7 @@ export class BlockchainBinding extends AbstractController { async hardship(req:any): Promise<HttpHardship> { let nextBlockNumber = 0; const search = await ParametersService.getSearchP(req); - const idty = await this.IdentityService.findMemberWithoutMemberships(search); + const idty = await this.server.dal.getWrittenIdtyByPubkeyOrUidForIsMemberAndPubkey(search); if (!idty) { throw BMAConstants.ERRORS.NO_MATCHING_IDENTITY; } @@ -143,7 +188,7 @@ export class BlockchainBinding extends AbstractController { if (current) { nextBlockNumber = current ? current.number + 1 : 0; } - const difficulty = await this.server.getBcContext().getIssuerPersonalizedDifficulty(idty.pubkey); + const difficulty = await this.server.getBcContext().getIssuerPersonalizedDifficulty(idty.pub); return { "block": nextBlockNumber, "level": difficulty @@ -151,13 +196,16 @@ export class BlockchainBinding extends AbstractController { } async difficulties(): Promise<HttpDifficulties> { - const current = await this.server.dal.getCurrentBlockOrNull(); + const current = await this.server.dal.getCurrentBlockOrNull() + if (!current) { + throw Error(DataErrors[DataErrors.BLOCKCHAIN_NOT_INITIALIZED_YET]) + } const number = (current && current.number) || 0; const issuers = await this.server.dal.getUniqueIssuersBetween(number - 1 - current.issuersFrame, number - 1); const difficulties = []; for (const issuer of issuers) { - const member = await this.server.dal.getWrittenIdtyByPubkey(issuer); - const difficulty = await this.server.getBcContext().getIssuerPersonalizedDifficulty(member.pubkey); + const member = await this.server.dal.getWrittenIdtyByPubkeyForUidAndPubkey(issuer); + const difficulty = await this.server.getBcContext().getIssuerPersonalizedDifficulty(member.pub); difficulties.push({ uid: member.uid, level: difficulty @@ -165,18 +213,18 @@ export class BlockchainBinding extends AbstractController { } return { "block": number + 1, - "levels": _.sortBy(difficulties, (diff:any) => diff.level) + "levels": Underscore.sortBy(difficulties, (diff:any) => diff.level) }; } async memberships(req:any): Promise<HttpMemberships> { const search = await ParametersService.getSearchP(req); - const idty:any = await this.IdentityService.findMember(search); + const { idty, memberships } = await this.IdentityService.findMember(search); const json = { pubkey: idty.pubkey, uid: idty.uid, sigDate: idty.buid, - memberships: idty.memberships.map((msObj:any) => { + memberships: memberships.map((msObj:any) => { const ms = MembershipDTO.fromJSONObject(msObj); return { version: ms.version, @@ -188,7 +236,7 @@ export class BlockchainBinding extends AbstractController { }; }) } - json.memberships = _.sortBy(json.memberships, 'blockNumber'); + json.memberships = Underscore.sortBy(json.memberships, 'blockNumber') json.memberships.reverse(); return json; } diff --git a/app/modules/bma/lib/controllers/network.ts b/app/modules/bma/lib/controllers/network.ts index 6072103feba92f98cc35fdbe01e59acf590136ba..61bfc34dcfc4f2a1476c20a3f0d170d041ca309e 100644 --- a/app/modules/bma/lib/controllers/network.ts +++ b/app/modules/bma/lib/controllers/network.ts @@ -15,8 +15,8 @@ import {AbstractController} from "./AbstractController" import {BMAConstants} from "../constants" import {HttpMerkleOfPeers, HttpPeer, HttpPeers, HttpWS2PHeads, HttpWS2PInfo} from "../dtos" import {WS2PHead} from "../../../ws2p/lib/WS2PCluster" +import {DBPeer} from "../../../../lib/db/DBPeer" -const _ = require('underscore'); const http2raw = require('../http2raw'); export class NetworkBinding extends AbstractController { @@ -64,19 +64,8 @@ export class NetworkBinding extends AbstractController { async peers(): Promise<HttpPeers> { let peers = await this.server.dal.listAllPeers(); return { - peers: peers.map((p:any) => { - return _.pick(p, - 'version', - 'currency', - 'status', - 'first_down', - 'last_try', - 'pubkey', - 'block', - 'signature', - 'endpoints'); - }) - }; + peers: peers.map(p => DBPeer.json(p)) + } } async ws2pInfo(): Promise<HttpWS2PInfo> { diff --git a/app/modules/bma/lib/controllers/transactions.ts b/app/modules/bma/lib/controllers/transactions.ts index 068993fc12aedcbb21ac75e2a53ea2940eb4e297..3706b8daabdf7dd4fe2f0272906aae57e2e789da 100644 --- a/app/modules/bma/lib/controllers/transactions.ts +++ b/app/modules/bma/lib/controllers/transactions.ts @@ -13,13 +13,12 @@ import {AbstractController} from "./AbstractController"; import {ParametersService} from "../parameters"; -import {Source} from "../entity/source"; import {BMAConstants} from "../constants"; import {TransactionDTO} from "../../../../lib/dto/TransactionDTO"; import {HttpSources, HttpTransaction, HttpTxHistory, HttpTxOfHistory, HttpTxPending} from "../dtos"; -import {DBTx} from "../../../../lib/dal/sqliteDAL/TxsDAL"; +import {DBTx} from "../../../../lib/db/DBTx" +import {Underscore} from "../../../../lib/common-libs/underscore" -const _ = require('underscore'); const http2raw = require('../http2raw'); export class TransactionBinding extends AbstractController { @@ -45,15 +44,11 @@ export class TransactionBinding extends AbstractController { async getSources(req:any): Promise<HttpSources> { const pubkey = await ParametersService.getPubkeyP(req); const sources = await this.server.dal.getAvailableSourcesByPubkey(pubkey); - const result:any = { - "currency": this.conf.currency, - "pubkey": pubkey, - "sources": [] - }; - sources.forEach(function (src:any) { - result.sources.push(new Source(src).json()); - }); - return result; + return { + currency: this.conf.currency, + pubkey, + sources + } } async getByHash(req:any): Promise<HttpTransaction> { @@ -96,9 +91,9 @@ export class TransactionBinding extends AbstractController { const to = await ParametersService.getToP(req); return this.getFilteredHistory(pubkey, (res:any) => { const histo = res.history; - histo.sent = _.filter(histo.sent, function(tx:any){ return tx && tx.block_number >= from && tx.block_number <= to; }); - histo.received = _.filter(histo.received, function(tx:any){ return tx && tx.block_number >= from && tx.block_number <= to; }); - _.extend(histo, { sending: [], receiving: [] }); + histo.sent = Underscore.filter(histo.sent, function(tx:any){ return tx && tx.block_number >= from && tx.block_number <= to; }); + histo.received = Underscore.filter(histo.received, function(tx:any){ return tx && tx.block_number >= from && tx.block_number <= to; }); + Underscore.extend(histo, { sending: [], receiving: [] }); return res; }); } @@ -109,9 +104,9 @@ export class TransactionBinding extends AbstractController { const to = await ParametersService.getToP(req); return this.getFilteredHistory(pubkey, (res:any) => { const histo = res.history; - histo.sent = _.filter(histo.sent, function(tx:any){ return tx && tx.time >= from && tx.time <= to; }); - histo.received = _.filter(histo.received, function(tx:any){ return tx && tx.time >= from && tx.time <= to; }); - _.extend(histo, { sending: [], receiving: [] }); + histo.sent = Underscore.filter(histo.sent, function(tx:any){ return tx && tx.time >= from && tx.time <= to; }); + histo.received = Underscore.filter(histo.received, function(tx:any){ return tx && tx.time >= from && tx.time <= to; }); + Underscore.extend(histo, { sending: [], receiving: [] }); return res; }); } @@ -120,21 +115,32 @@ export class TransactionBinding extends AbstractController { const pubkey = await ParametersService.getPubkeyP(req); return this.getFilteredHistory(pubkey, function(res:any) { const histo = res.history; - _.extend(histo, { sent: [], received: [] }); + Underscore.extend(histo, { sent: [], received: [] }); return res; }); } async getPending(): Promise<HttpTxPending> { const pending = await this.server.dal.getTransactionsPending(); - const res = { - "currency": this.conf.currency, - "pending": pending - }; - pending.map(function(tx:any, index:number) { - pending[index] = _.omit(TransactionDTO.fromJSONObject(tx).json(), 'currency', 'raw'); - }); - return res; + return { + currency: this.conf.currency, + pending: pending.map(t => { + const tx = TransactionDTO.fromJSONObject(t) + return { + version: tx.version, + issuers: tx.issuers, + inputs: tx.inputs, + unlocks: tx.unlocks, + outputs: tx.outputs, + comment: tx.comment, + locktime: tx.locktime, + blockstamp: tx.blockstamp, + blockstampTime: tx.blockstampTime, + signatures: tx.signatures, + hash: tx.hash + } + }) + } } private async getFilteredHistory(pubkey:string, filter:any): Promise<HttpTxHistory> { diff --git a/app/modules/bma/lib/controllers/uds.ts b/app/modules/bma/lib/controllers/uds.ts index 14b5f6f29c72c7b0bbd16c0ac8f61cd79a9db377..f2378438797664ce5f6d1c547503f5f66ef9dfd6 100644 --- a/app/modules/bma/lib/controllers/uds.ts +++ b/app/modules/bma/lib/controllers/uds.ts @@ -15,8 +15,7 @@ import {AbstractController} from "./AbstractController" import {ParametersService} from "../parameters" import {Source} from "../entity/source" import {HttpUDHistory} from "../dtos"; - -const _ = require('underscore'); +import {Underscore} from "../../../../lib/common-libs/underscore" export class UDBinding extends AbstractController { @@ -30,7 +29,7 @@ export class UDBinding extends AbstractController { const from = await ParametersService.getFromP(req); const to = await ParametersService.getToP(req); return this.getUDSources(pubkey, (results:any) => { - results.history.history = _.filter(results.history.history, function(ud:any){ return ud.block_number >= from && ud.block_number <= to; }); + results.history.history = Underscore.filter(results.history.history, function(ud:any){ return ud.block_number >= from && ud.block_number <= to; }); return results; }) } @@ -40,7 +39,7 @@ export class UDBinding extends AbstractController { const from = await ParametersService.getFromP(req); const to = await ParametersService.getToP(req); return this.getUDSources(pubkey, (results:any) => { - results.history.history = _.filter(results.history.history, function(ud:any){ return ud.time >= from && ud.time <= to; }); + results.history.history = Underscore.filter(results.history.history, function(ud:any){ return ud.time >= from && ud.time <= to; }); return results; }); } @@ -52,10 +51,10 @@ export class UDBinding extends AbstractController { "pubkey": pubkey, "history": history }; - _.keys(history).map((key:any) => { + Underscore.keys(history).map((key:any) => { history[key].map((src:any, index:number) => { - history[key][index] = _.omit(new Source(src).UDjson(), 'currency', 'raw'); - _.extend(history[key][index], { block_number: src && src.block_number, time: src && src.time }); + history[key][index] = new Source(src).UDjson() + Underscore.extend(history[key][index], { block_number: src && src.block_number, time: src && src.time }); }); }); return filter(result); diff --git a/app/modules/bma/lib/controllers/wot.ts b/app/modules/bma/lib/controllers/wot.ts index 5e8a8c54431f58b18da88994bd4cf45306486c5c..6a5e8bb23b2d1eea8ad43c1e918f15ec4dcc18b6 100644 --- a/app/modules/bma/lib/controllers/wot.ts +++ b/app/modules/bma/lib/controllers/wot.ts @@ -11,25 +11,32 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -import { IindexEntry } from './../../../../lib/indexer'; import {AbstractController} from "./AbstractController"; import {BMAConstants} from "../constants"; import {DBIdentity} from "../../../../lib/dal/sqliteDAL/IdentityDAL"; -import { IdentityForRequirements } from '../../../../service/BlockchainService'; +import {IdentityForRequirements} from '../../../../service/BlockchainService'; import { HttpCert, - HttpCertIdentity, HttpCertifications, + HttpCertIdentity, + HttpCertification, + HttpCertifications, HttpIdentity, HttpIdentityRequirement, HttpLookup, HttpMembers, HttpMembershipList, HttpRequirements, - HttpResult, HttpSimpleIdentity + HttpResult, + HttpSimpleIdentity } from "../dtos"; +import {IdentityDTO} from "../../../../lib/dto/IdentityDTO" +import {FullIindexEntry} from "../../../../lib/indexer" +import {DBMembership} from "../../../../lib/dal/sqliteDAL/MembershipDAL" +import {Underscore} from "../../../../lib/common-libs/underscore" +import {Map} from "../../../../lib/common-libs/crypto/map" -const _ = require('underscore'); const http2raw = require('../http2raw'); +const constants = require('../../../../lib/constants'); const ParametersService = require('../parameters').ParametersService @@ -39,33 +46,33 @@ export class WOTBinding extends AbstractController { // Get the search parameter from HTTP query const search = await ParametersService.getSearchP(req); // Make the research - const identities:any[] = await this.IdentityService.searchIdentities(search); + const identities = await this.IdentityService.searchIdentities(search); // Entitify each result identities.forEach((idty, index) => identities[index] = DBIdentity.copyFromExisting(idty)); // Prepare some data to avoid displaying expired certifications for (const idty of identities) { - const certs = await this.server.dal.certsToTarget(idty.pubkey, idty.getTargetHash()); + const certs: any[] = await this.server.dal.certsToTarget(idty.pubkey, idty.getTargetHash()); const validCerts = []; for (const cert of certs) { - const member = await this.IdentityService.getWrittenByPubkey(cert.from); + const member = await this.server.dal.getWrittenIdtyByPubkeyForUidAndIsMemberAndWasMember(cert.from); if (member) { cert.uids = [member.uid]; cert.isMember = member.member; cert.wasMember = member.wasMember; } else { const potentials = await this.IdentityService.getPendingFromPubkey(cert.from); - cert.uids = _(potentials).pluck('uid'); + cert.uids = potentials.map(p => p.uid) cert.isMember = false; cert.wasMember = false; } validCerts.push(cert); } idty.certs = validCerts; - const signed = await this.server.dal.certsFrom(idty.pubkey); + const signed:any = await this.server.dal.certsFrom(idty.pubkey); const validSigned = []; for (let j = 0; j < signed.length; j++) { - const cert = _.clone(signed[j]); - cert.idty = await this.server.dal.getIdentityByHashOrNull(cert.target); + const cert = Underscore.clone(signed[j]); + cert.idty = await this.server.dal.getGlobalIdentityByHashForLookup(cert.target) if (cert.idty) { validSigned.push(cert); } else { @@ -77,7 +84,7 @@ export class WOTBinding extends AbstractController { if (identities.length == 0) { throw BMAConstants.ERRORS.NO_MATCHING_IDENTITY; } - const resultsByPubkey:any = {}; + const resultsByPubkey:Map<HttpIdentity> = {}; identities.forEach((identity) => { const copy = DBIdentity.copyFromExisting(identity) const jsoned = copy.json(); @@ -94,7 +101,7 @@ export class WOTBinding extends AbstractController { }); return { partial: false, - results: _.values(resultsByPubkey) + results: Underscore.values(resultsByPubkey) }; } @@ -109,49 +116,38 @@ export class WOTBinding extends AbstractController { async certifiersOf(req:any): Promise<HttpCertifications> { const search = await ParametersService.getSearchP(req); - const idty = await this.IdentityService.findMemberWithoutMemberships(search); - const certs = await this.server.dal.certsToTarget(idty.pubkey, idty.getTargetHash()); - idty.certs = []; + const idty = (await this.server.dal.getWrittenIdtyByPubkeyOrUIdForHashingAndIsMember(search)) as FullIindexEntry + const certs = await this.server.dal.certsToTarget(idty.pub, IdentityDTO.getTargetHash(idty)) + const theCerts:HttpCertification[] = []; for (const cert of certs) { - const certifier = await this.server.dal.getWrittenIdtyByPubkey(cert.from); + const certifier = await this.server.dal.getWrittenIdtyByPubkeyForUidAndMemberAndCreatedOn(cert.from); if (certifier) { - cert.uid = certifier.uid; - cert.isMember = certifier.member; - cert.sigDate = certifier.buid; - cert.wasMember = true; // As we checked if(certified) - if (!cert.cert_time) { - let certBlock = await this.server.dal.getBlock(cert.block_number); - cert.cert_time = { + let certBlock = await this.server.dal.getBlockWeHaveItForSure(cert.block_number) + theCerts.push({ + pubkey: cert.from, + uid: certifier.uid, + isMember: certifier.member, + wasMember: true, // a member is necessarily certified by members + cert_time: { block: certBlock.number, medianTime: certBlock.medianTime - }; - } - idty.certs.push(cert); + }, + sigDate: certifier.created_on, + written: (cert.written_block !== null && cert.written_hash) ? { + number: cert.written_block, + hash: cert.written_hash + } : null, + signature: cert.sig + }) } } - const json:any = { - pubkey: idty.pubkey, + return { + pubkey: idty.pub, uid: idty.uid, - sigDate: idty.buid, + sigDate: idty.created_on, isMember: idty.member, - certifications: [] - }; - idty.certs.forEach(function(cert){ - json.certifications.push({ - pubkey: cert.from, - uid: cert.uid, - isMember: cert.isMember, - wasMember: cert.wasMember, - cert_time: cert.cert_time, - sigDate: cert.sigDate, - written: cert.linked ? { - number: cert.written_block, - hash: cert.written_hash - } : null, - signature: cert.sig - }); - }); - return json; + certifications: theCerts + } } async requirements(req:any): Promise<HttpRequirements> { @@ -168,29 +164,23 @@ export class WOTBinding extends AbstractController { async requirementsOfPending(req:any): Promise<HttpRequirements> { const minsig = ParametersService.getMinSig(req) - let identities:IdentityForRequirements[] = await this.server.dal.idtyDAL.query( + let identities:IdentityForRequirements[] = (await this.server.dal.idtyDAL.query( 'SELECT i.*, count(c.sig) as nbSig ' + 'FROM idty i, cert c ' + 'WHERE c.target = i.hash group by i.hash having nbSig >= ?', - minsig) - const members:IdentityForRequirements[] = (await this.server.dal.idtyDAL.query( - 'SELECT i.*, count(c.sig) as nbSig ' + - 'FROM i_index i, cert c ' + - 'WHERE c.`to` = i.pub group by i.pub having nbSig >= ?', - minsig)).map((i:IindexEntry):IdentityForRequirements => { - return { - hash: i.hash || "", - member: i.member || false, - wasMember: i.wasMember || false, - pubkey: i.pub, - uid: i.uid || "", - buid: i.created_on || "", - sig: i.sig || "", - revocation_sig: "", - revoked: false, - revoked_on: 0 - } - }) + [minsig])).map(i => ({ + hash: i.hash || "", + member: i.member || false, + wasMember: i.wasMember || false, + pubkey: i.pubkey, + uid: i.uid || "", + buid: i.buid || "", + sig: i.sig || "", + revocation_sig: i.revocation_sig, + revoked: i.revoked, + revoked_on: i.revoked_on ? 1 : 0 + })) + const members = await this.server.dal.findReceiversAbove(minsig) identities = identities.concat(members) const all = await this.BlockchainService.requirementsOfIdentities(identities, false); if (!all || !all.length) { @@ -203,63 +193,53 @@ export class WOTBinding extends AbstractController { async certifiedBy(req:any): Promise<HttpCertifications> { const search = await ParametersService.getSearchP(req); - const idty = await this.IdentityService.findMemberWithoutMemberships(search); - const certs = await this.server.dal.certsFrom(idty.pubkey); - idty.certs = []; + const idty = (await this.server.dal.getWrittenIdtyByPubkeyOrUIdForHashingAndIsMember(search)) as FullIindexEntry + const certs = await this.server.dal.certsFrom(idty.pub); + const theCerts:HttpCertification[] = []; for (const cert of certs) { - const certified = await this.server.dal.getWrittenIdtyByPubkey(cert.to); + const certified = await this.server.dal.getWrittenIdtyByPubkeyForUidAndMemberAndCreatedOn(cert.to); if (certified) { - cert.uid = certified.uid; - cert.isMember = certified.member; - cert.sigDate = certified.buid; - cert.wasMember = true; // As we checked if(certified) - if (!cert.cert_time) { - let certBlock = await this.server.dal.getBlock(cert.block_number); - cert.cert_time = { + let certBlock = await this.server.dal.getBlockWeHaveItForSure(cert.block_number) + theCerts.push({ + pubkey: cert.to, + uid: certified.uid, + isMember: certified.member, + wasMember: true, // a member is necessarily certified by members + cert_time: { block: certBlock.number, medianTime: certBlock.medianTime - }; - } - idty.certs.push(cert); + }, + sigDate: certified.created_on, + written: (cert.written_block !== null && cert.written_hash) ? { + number: cert.written_block, + hash: cert.written_hash + } : null, + signature: cert.sig + }) } } - const json:any = { - pubkey: idty.pubkey, + return { + pubkey: idty.pub, uid: idty.uid, - sigDate: idty.buid, + sigDate: idty.created_on, isMember: idty.member, - certifications: [] - }; - idty.certs.forEach((cert) => json.certifications.push({ - pubkey: cert.to, - uid: cert.uid, - isMember: cert.isMember, - wasMember: cert.wasMember, - cert_time: cert.cert_time, - sigDate: cert.sigDate, - written: cert.linked ? { - number: cert.written_block, - hash: cert.written_hash - } : null, - signature: cert.sig - }) - ); - return json; + certifications: theCerts + } } async identityOf(req:any): Promise<HttpSimpleIdentity> { let search = await ParametersService.getSearchP(req); - let idty = await this.IdentityService.findMemberWithoutMemberships(search); + const idty = await this.server.dal.getWrittenIdtyByPubkeyOrUIdForHashingAndIsMember(search) if (!idty) { - throw 'Identity not found'; + throw constants.ERRORS.NO_MEMBER_MATCHING_PUB_OR_UID; } if (!idty.member) { throw 'Not a member'; } return { - pubkey: idty.pubkey, + pubkey: idty.pub, uid: idty.uid, - sigDate: idty.buid + sigDate: idty.created_on }; } @@ -298,20 +278,20 @@ export class WOTBinding extends AbstractController { async pendingMemberships(): Promise<HttpMembershipList> { const memberships = await this.server.dal.findNewcomers(); const json = { - memberships: memberships.map((ms:any) => { + memberships: memberships.map((ms:DBMembership) => { return { pubkey: ms.issuer, uid: ms.userid, - version: ms.version || 0, + version: 10, currency: this.server.conf.currency, membership: ms.membership, - blockNumber: parseInt(ms.blockNumber), + blockNumber: ms.blockNumber, blockHash: ms.blockHash, written: (!ms.written_number && ms.written_number !== 0) ? null : ms.written_number }; }) }; - json.memberships = _.sortBy(json.memberships, 'blockNumber'); + json.memberships = Underscore.sortBy(json.memberships, 'blockNumber'); json.memberships.reverse(); return json; } diff --git a/app/modules/bma/lib/dtos.ts b/app/modules/bma/lib/dtos.ts index f37c5139fd4da215ba1e516f7b48cd11eb75a7e4..d0b7f27d19e7f89c896a36799fc440b2013712cf 100644 --- a/app/modules/bma/lib/dtos.ts +++ b/app/modules/bma/lib/dtos.ts @@ -12,8 +12,8 @@ // GNU Affero General Public License for more details. import {BlockDTO} from "../../../lib/dto/BlockDTO" -import {DBPeer as DBPeer2} from "../../../lib/dal/sqliteDAL/PeerDAL" import {WS2PHead} from "../../ws2p/lib/WS2PCluster" +import {JSONDBPeer} from "../../../lib/db/DBPeer" export const Summary = { duniter: { @@ -125,16 +125,14 @@ export interface HttpMemberships { pubkey: string uid: string sigDate: string - memberships: [ - { - version: number - currency: string - membership: string - blockNumber: number - blockHash: string - written: number - } - ] + memberships: { + version: number + currency: string + membership: string + blockNumber: number + blockHash: string + written: number + }[] } export const MembershipList = { @@ -153,18 +151,16 @@ export const MembershipList = { }; export interface HttpMembershipList { - memberships: [ - { - pubkey: string - uid: string - version: number - currency: string - membership: string - blockNumber: number - blockHash: string - written: number - } - ] + memberships: { + pubkey: string + uid: string + version: number + currency: string + membership: string + blockNumber: number + blockHash: string + written: number|null + }[] } export const TransactionOfBlock = { @@ -186,17 +182,16 @@ export const TransactionOfBlock = { export interface HttpTransactionOfBlock { version: number currency: string - comment: string locktime: number - signatures: string[] - outputs: string[] - inputs: string[] - unlocks: string[] - block_number: number + hash: string blockstamp: string blockstampTime: number - time: number issuers: string[] + inputs: string[] + outputs: string[] + unlocks: string[] + signatures: string[] + comment: string } export const Block = { @@ -246,7 +241,7 @@ export interface HttpBlock { powMin: number time: number medianTime: number - dividend: number + dividend: number|null unitbase: number hash: string previousHash: string @@ -303,10 +298,9 @@ export function block2HttpBlock(blockDTO:BlockDTO): HttpBlock { outputs: tx.outputs, inputs: tx.inputs, unlocks: tx.unlocks, - block_number: tx.blockNumber, + hash: tx.hash, blockstamp: tx.blockstamp, blockstampTime: tx.blockstampTime, - time: tx.blockstampTime } }), nonce: blockDTO.nonce, @@ -406,7 +400,7 @@ export const Peers = { }; export interface HttpPeers { - peers: DBPeer2[] + peers: JSONDBPeer[] } export interface HttpWS2PInfo { @@ -440,7 +434,7 @@ export interface HttpMerkleOfPeers { leaves: string[] leaf: { hash: string - value: DBPeer2 + value: JSONDBPeer } } @@ -486,9 +480,9 @@ export interface HttpUID { timestamp: string }, self: string, - revocation_sig: string, + revocation_sig: string|null, revoked: boolean, - revoked_on: number, + revoked_on: number|null, others: HttpOther[] } @@ -712,7 +706,7 @@ export interface HttpCertification { written: { number: number hash: string - } + } | null signature: string } @@ -774,6 +768,18 @@ export interface HttpTransaction { hash: string } +export interface HttpTransactionPending { + version: number + issuers: string[] + inputs: string[] + unlocks: string[] + outputs: string[] + comment: string + locktime: number + signatures: string[] + hash: string +} + export const Source = { "type": String, "noffset": Number, @@ -869,7 +875,7 @@ export const TxPending = { export interface HttpTxPending { currency: string - pending: HttpTransaction[] + pending: HttpTransactionPending[] } export const UD = { diff --git a/app/modules/bma/lib/entity/source.ts b/app/modules/bma/lib/entity/source.ts index 2543a55206ffc462e0c6c9e3827b7e738df34070..15e678aacb9569b4d1ed78da3cb97efc3f6ee4c9 100644 --- a/app/modules/bma/lib/entity/source.ts +++ b/app/modules/bma/lib/entity/source.ts @@ -11,15 +11,14 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; -const _ = require('underscore'); +import {Underscore} from "../../../../lib/common-libs/underscore" export class Source { [k:string]: any constructor(json:any) { - _(json || {}).keys().forEach((key:string) => { + Underscore.keys(json || {}).forEach((key:string) => { let value = json[key]; if (key == "number") { value = parseInt(value); diff --git a/app/modules/bma/lib/network.ts b/app/modules/bma/lib/network.ts index 831dd61ddcc28714768dfcf6fad8f67db4d3bf33..c75d8d79451a7db6dbc1ca7cccd24861c338a673 100644 --- a/app/modules/bma/lib/network.ts +++ b/app/modules/bma/lib/network.ts @@ -11,15 +11,14 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; import {NetworkConfDTO} from "../../../lib/dto/ConfDTO" import {Server} from "../../../../server" import {BMAConstants} from "./constants" import {BMALimitation} from "./limiter" +import {Underscore} from "../../../lib/common-libs/underscore" const os = require('os'); const Q = require('q'); -const _ = require('underscore'); const ddos = require('ddos'); const http = require('http'); const express = require('express'); @@ -67,7 +66,7 @@ export const Network = { } const ddosConf = server.conf.dos || {}; ddosConf.silentStart = true - ddosConf.whitelist = _.uniq((ddosConf.whitelist || []).concat(whitelist)); + ddosConf.whitelist = Underscore.uniq((ddosConf.whitelist || []).concat(whitelist)); const ddosInstance = new ddos(ddosConf); app.use(ddosInstance.express); @@ -162,7 +161,7 @@ export const Network = { return { http: httpServer, closeSockets: () => { - _.keys(sockets).map((socketId:number) => { + Underscore.keys(sockets).map((socketId:string) => { sockets[socketId].destroy(); }); } @@ -284,8 +283,8 @@ function getBestLocalIPv6() { const osInterfaces = listInterfaces(); for (let netInterface of osInterfaces) { const addresses = netInterface.addresses; - const filtered = _(addresses).where({family: 'IPv6', scopeid: 0, internal: false }); - const filtered2 = _.filter(filtered, (address:any) => !address.address.match(/^fe80/) && !address.address.match(/^::1/)); + const filtered = Underscore.where(addresses, {family: 'IPv6', scopeid: 0, internal: false }) + const filtered2 = Underscore.filter(filtered, (address:any) => !address.address.match(/^fe80/) && !address.address.match(/^::1/)); if (filtered2[0]) { return filtered2[0].address; } @@ -295,7 +294,7 @@ function getBestLocalIPv6() { function getBestLocal(family:string) { let netInterfaces = os.networkInterfaces(); - let keys = _.keys(netInterfaces); + let keys = Underscore.keys(netInterfaces); let res = []; for (const name of keys) { let addresses = netInterfaces[name]; @@ -321,7 +320,7 @@ function getBestLocal(family:string) { /^Loopback/, /^None/ ]; - const best = _.sortBy(res, function(entry:any) { + const best = Underscore.sortBy(res, function(entry:any) { for (let i = 0; i < interfacePriorityRegCatcher.length; i++) { // `i` is the priority (0 is the better, 1 is the second, ...) if (entry.name.match(interfacePriorityRegCatcher[i])) return i; @@ -333,7 +332,7 @@ function getBestLocal(family:string) { function listInterfaces() { const netInterfaces = os.networkInterfaces(); - const keys = _.keys(netInterfaces); + const keys = Underscore.keys(netInterfaces); const res = []; for (const name of keys) { res.push({ diff --git a/app/modules/bma/lib/parameters.ts b/app/modules/bma/lib/parameters.ts index fed7b661e6b9f33fda042444a1ada168a7764a52..3db765af14be26c83ebc4ce899d08953667618ba 100644 --- a/app/modules/bma/lib/parameters.ts +++ b/app/modules/bma/lib/parameters.ts @@ -62,7 +62,7 @@ export class ParametersService { return req.params.hash; }; - static getMinSig(req:any){ + static getMinSig(req:any): number { if(!req.params.minsig){ return 4 // Default value } diff --git a/app/modules/bma/lib/sanitize.ts b/app/modules/bma/lib/sanitize.ts index d2a5ce5e70c74d43b881e7911342631587101271..0a2716741e5d207690ed214b07bd9019fd552553 100644 --- a/app/modules/bma/lib/sanitize.ts +++ b/app/modules/bma/lib/sanitize.ts @@ -11,9 +11,7 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; - -let _ = require('underscore'); +import {Underscore} from "../../../lib/common-libs/underscore" module.exports = function sanitize (json:any, contract:any) { @@ -44,9 +42,9 @@ module.exports = function sanitize (json:any, contract:any) { } } - let contractFields = _(contract).keys(); - let objectFields = _(json).keys(); - let toDeleteFromObj = _.difference(objectFields, contractFields); + let contractFields = Underscore.keys(contract) + let objectFields = Underscore.keys(json) + let toDeleteFromObj = Underscore.difference(objectFields, contractFields) // Remove unwanted fields for (let i = 0, len = toDeleteFromObj.length; i < len; i++) { @@ -74,7 +72,7 @@ module.exports = function sanitize (json:any, contract:any) { } } // Check coherence & alter member if needed - if (!_(json[prop]).isNull() && t.toLowerCase() != tjson.toLowerCase()) { + if (json[prop] !== null && t.toLowerCase() != tjson.toLowerCase()) { try { if (t == "String") { let s = json[prop] == undefined ? '' : json[prop]; diff --git a/app/modules/bma/lib/tojson.ts b/app/modules/bma/lib/tojson.ts index d9528205e898778fde147cf89b8d41ae59731037..75441e665c188820fe2980aa4829718494b35158 100644 --- a/app/modules/bma/lib/tojson.ts +++ b/app/modules/bma/lib/tojson.ts @@ -11,10 +11,8 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; import {BlockDTO} from "../../../lib/dto/BlockDTO" - -const _ = require('underscore') +import {Underscore} from "../../../lib/common-libs/underscore" export const stat = (stat:any) => { return { "blocks": stat.blocks } @@ -52,7 +50,7 @@ export const block = (block:any) => { json.certifications = (block.certifications || []) json.transactions = []; block.transactions.forEach((obj:any) => { - json.transactions.push(_(obj).omit('raw', 'certifiers', 'hash')); + json.transactions.push(Underscore.omit(obj, 'raw', 'certifiers', 'hash')) }); json.transactions = block.transactions.map((tx:any) => { tx.inputs = tx.inputs.map((i:any) => i.raw || i) diff --git a/app/modules/config.ts b/app/modules/config.ts index e37bd9258cac3b72fc67dcc3b47566a3abc827a5..0442cb20a68a968f896705e89cd4957b2de1fed7 100644 --- a/app/modules/config.ts +++ b/app/modules/config.ts @@ -15,6 +15,8 @@ import {ConfDTO} from "../lib/dto/ConfDTO" import {Server} from "../../server" import {CommonConstants} from "../lib/common-libs/constants" +import {Directory} from "../lib/system/directory" +import {Underscore} from "../lib/common-libs/underscore" module.exports = { duniter: { @@ -35,6 +37,98 @@ module.exports = { desc: 'Register configuration in database', // The command does nothing particular, it just stops the process right after configuration phase is over onConfiguredExecute: (server:Server, conf:ConfDTO) => Promise.resolve(conf) + }, { + name: 'parse-logs', + desc: 'Extract data from logs.', + logs: true, + onConfiguredExecute: async (server:Server, conf:ConfDTO) => { + const fs = await Directory.getHomeFS(false, Directory.INSTANCE_HOME, false) + const lines = (await fs.fs.fsReadFile(Directory.INSTANCE_HOMELOG_FILE)).split('\n') + const aggregates = Underscore.uniq( + lines + .map(l => l.match(/: (\[\w+\](\[\w+\])*)/)) + .filter(l => l) + .map((l:string[]) => l[1]) + ) + console.log(aggregates) + const results = aggregates.map((a:string) => { + return { + name: a, + time: lines + .filter(l => l.match(new RegExp(a + .replace(/\[/g, '\\[') + .replace(/\]/g, '\\]') + ))) + .map(l => { + const m = l.match(/ (\d+)(\.\d+)?(ms|µs)( \d+)?$/) + if (!m) { + throw Error('Wrong match') + } + return m + }) + .map(match => { + return { + qty: parseInt(match[1]), + unit: match[3], + } + }) + .reduce((sumMicroSeconds, entry) => { + return sumMicroSeconds + (entry.qty * (entry.unit === 'ms' ? 1000 : 1)) + }, 0) / 1000000 + } + }) + const root:Tree = { + name: 'root', + leaves: {} + } + for (const r of results) { + recursiveReduce(root, r.name, r.time) + } + recursiveDump(root) + } }] } } + +interface Leaf { + name:string + value:number +} + +interface Tree { + name:string + leaves: { [k:string]: Tree|Leaf } +} + +function recursiveReduce(tree:Tree, path:string, duration:number) { + if (path.match(/\]\[/)) { + const m = (path.match(/^(\[\w+\])(\[.+)/) as string[]) + const key = m[1] + if (!tree.leaves[key]) { + tree.leaves[key] = { + name: key, + leaves: {} + } + } + recursiveReduce(tree.leaves[key] as Tree, m[2], duration) + } else { + tree.leaves[path] = { + name: path, + value: duration + } + } +} + +function recursiveDump(tree:Tree, level = -1) { + if (level >= 0) { + console.log(" ".repeat(level), tree.name) + } + for (const k of Object.keys(tree.leaves)) { + const element = tree.leaves[k] + if ((<Tree>element).leaves) { + recursiveDump(<Tree>element, level + 1) + } else { + console.log(" ".repeat(level + 1), (<Leaf>element).name, (<Leaf>element).value + 's') + } + } +} \ No newline at end of file diff --git a/app/modules/crawler/index.ts b/app/modules/crawler/index.ts index 8eff8439a3633275c32dcc25c4b45f99524e6ad7..2d72d7b411e6751aa1d118c79c3178925db994ed 100644 --- a/app/modules/crawler/index.ts +++ b/app/modules/crawler/index.ts @@ -21,6 +21,11 @@ import {rawer} from "../../lib/common-libs/index" import {PeerDTO} from "../../lib/dto/PeerDTO" import {Buid} from "../../lib/common-libs/buid" import {BlockDTO} from "../../lib/dto/BlockDTO" +import {Directory} from "../../lib/system/directory" +import {FileDAL} from "../../lib/dal/fileDAL" +import {RemoteSynchronizer} from "./lib/sync/RemoteSynchronizer" +import {AbstractSynchronizer} from "./lib/sync/AbstractSynchronizer" +import {LocalPathSynchronizer} from "./lib/sync/LocalPathSynchronizer" export const CrawlerDependency = { duniter: { @@ -31,9 +36,9 @@ export const CrawlerDependency = { methods: { - contacter: (host:string, port:number, opts:any) => new Contacter(host, port, opts), + contacter: (host:string, port:number, opts?:any) => new Contacter(host, port, opts), - pullBlocks: async (server:Server, pubkey:string) => { + pullBlocks: async (server:Server, pubkey = "") => { const crawler = new Crawler(server, server.conf, server.logger); return crawler.pullBlocks(server, pubkey); }, @@ -44,17 +49,28 @@ export const CrawlerDependency = { }, synchronize: (server:Server, onHost:string, onPort:number, upTo:number, chunkLength:number) => { - const remote = new Synchroniser(server, onHost, onPort); - const syncPromise = remote.sync(upTo, chunkLength) + const strategy = new RemoteSynchronizer(onHost, onPort, server) + const remote = new Synchroniser(server, strategy) + const syncPromise = (async () => { + await server.dal.disableChangesAPI() + await remote.sync(upTo, chunkLength) + await server.dal.enableChangesAPI() + })() return { flow: remote, - syncPromise: syncPromise + syncPromise }; }, + /** + * Used by duniter-ui + * @param {Server} server + * @param {string} onHost + * @param {number} onPort + * @returns {Promise<any>} + */ testForSync: (server:Server, onHost:string, onPort:number) => { - const remote = new Synchroniser(server, onHost, onPort); - return remote.test(); + return RemoteSynchronizer.test(onHost, onPort) } }, @@ -63,24 +79,25 @@ export const CrawlerDependency = { { value: '--nocautious', desc: 'Do not check blocks validity during sync.'}, { value: '--cautious', desc: 'Check blocks validity during sync (overrides --nocautious option).'}, { value: '--nopeers', desc: 'Do not retrieve peers during sync.'}, + { value: '--nosources', desc: 'Do not parse sources (UD, TX) during sync (debug purposes).'}, + { value: '--nosbx', desc: 'Do not retrieve sandboxes during sync.'}, { value: '--onlypeers', desc: 'Will only try to sync peers.'}, { value: '--slow', desc: 'Download slowly the blokchcain (for low connnections).'}, + { value: '--readfilesystem',desc: 'Also read the filesystem to speed up block downloading.'}, { value: '--minsig <minsig>', desc: 'Minimum pending signatures count for `crawl-lookup`. Default is 5.'} ], cli: [{ - name: 'sync [host] [port] [to]', + name: 'sync [source] [to]', desc: 'Synchronize blockchain from a remote Duniter node', preventIfRunning: true, - onDatabaseExecute: async (server:Server, conf:ConfDTO, program:any, params:any) => { - const host = params[0]; - const port = params[1]; - const to = params[2]; - if (!host) { - throw 'Host is required.'; - } - if (!port) { - throw 'Port is required.'; + onDatabaseExecute: async (server:Server, conf:ConfDTO, program:any, params:any): Promise<any> => { + const source = params[0] + const to = params[1] + const HOST_PATTERN = /^[^:/]+(:[0-9]{1,5})?$/ + const FILE_PATTERN = /^(\/.+)$/ + if (!source || !(source.match(HOST_PATTERN) || source.match(FILE_PATTERN))) { + throw 'Source of sync is required. (either a host:port or a file path)' } let cautious; if (program.nocautious) { @@ -89,19 +106,35 @@ export const CrawlerDependency = { if (program.cautious) { cautious = true; } - const onHost = host; - const onPort = port; const upTo = parseInt(to); const chunkLength = 0; const interactive = !program.nointeractive; const askedCautious = cautious; - const nopeers = program.nopeers; const noShufflePeers = program.noshuffle; - const remote = new Synchroniser(server, onHost, onPort, interactive === true, program.slow === true); + + let otherDAL = undefined + if (program.readfilesystem) { + const dbName = program.mdb; + const dbHome = program.home; + const home = Directory.getHome(dbName, dbHome); + const params = await Directory.getHomeParams(false, home) + otherDAL = new FileDAL(params) + } + + let strategy: AbstractSynchronizer + if (source.match(HOST_PATTERN)) { + const sp = source.split(':') + const onHost = sp[0] + const onPort = parseInt(sp[1] ? sp[1] : '443') // Defaults to 443 + strategy = new RemoteSynchronizer(onHost, onPort, server, noShufflePeers === true, otherDAL) + } else { + strategy = new LocalPathSynchronizer(source, server) + } if (program.onlypeers === true) { - return remote.syncPeers(nopeers, true, onHost, onPort) + return strategy.syncPeers(true) } else { - return remote.sync(upTo, chunkLength, askedCautious, nopeers, noShufflePeers === true) + const remote = new Synchroniser(server, strategy, interactive === true) + return remote.sync(upTo, chunkLength, askedCautious) } } }, { @@ -146,7 +179,7 @@ export const CrawlerDependency = { const toPort = params[4]; const logger = server.logger; try { - const peers = fromHost && fromPort ? [{ endpoints: [['BASIC_MERKLED_API', fromHost, fromPort].join(' ')] }] : await server.dal.peerDAL.query('SELECT * FROM peer WHERE status = ?', ['UP']) + const peers = fromHost && fromPort ? [{ endpoints: [['BASIC_MERKLED_API', fromHost, fromPort].join(' ')] }] : await server.dal.peerDAL.withUPStatus() // Memberships for (const p of peers) { const peer = PeerDTO.fromJSONObject(p) @@ -299,7 +332,7 @@ export const CrawlerDependency = { const fromPort = params[3] const logger = server.logger; try { - const peers = fromHost && fromPort ? [{ endpoints: [['BASIC_MERKLED_API', fromHost, fromPort].join(' ')] }] : await server.dal.peerDAL.query('SELECT * FROM peer WHERE status = ?', ['UP']) + const peers = fromHost && fromPort ? [{ endpoints: [['BASIC_MERKLED_API', fromHost, fromPort].join(' ')] }] : await server.dal.peerDAL.withUPStatus() // Memberships for (const p of peers) { const peer = PeerDTO.fromJSONObject(p) @@ -331,7 +364,7 @@ export const CrawlerDependency = { // Membership let rawMS for (const theMS of pendingMSS) { - console.log('New membership pending for %s', theMS.uid); + console.log('New membership pending for %s', theMS.userid); try { rawMS = rawer.getMembership({ currency: 'g1', diff --git a/app/modules/crawler/lib/contacter.ts b/app/modules/crawler/lib/contacter.ts index c2e93c4e33f568f5147afa128f851723706e46ff..7789fc0f6c0da8f65c7dd2d713f0c2430ffc085e 100644 --- a/app/modules/crawler/lib/contacter.ts +++ b/app/modules/crawler/lib/contacter.ts @@ -62,7 +62,7 @@ export class Contacter { return this.get('/network/peering', dtos.Peer) } - getPeers(obj:any) { + getPeers(obj?:any) { return this.get('/network/peering/peers', dtos.MerkleOfPeers, obj) } diff --git a/app/modules/crawler/lib/crawler.ts b/app/modules/crawler/lib/crawler.ts index 8766c226c9386d33d6342ad73cd24745248b4f71..0a0acbfa89d2bfacae717a12cab6344b59f03fbc 100644 --- a/app/modules/crawler/lib/crawler.ts +++ b/app/modules/crawler/lib/crawler.ts @@ -24,10 +24,9 @@ import {connect} from "./connect" import {CrawlerConstants} from "./constants" import {pullSandboxToLocalServer} from "./sandbox" import {cleanLongDownPeers} from "./garbager" +import {Underscore} from "../../../lib/common-libs/underscore" -const _ = require('underscore'); const async = require('async'); -const querablep = require('querablep'); /** * Service which triggers the server's peering generation (actualization of the Peer document). @@ -52,7 +51,7 @@ export class Crawler extends stream.Transform implements DuniterService { this.sandboxCrawler = new SandboxCrawler(server, conf, logger) } - pullBlocks(server:Server, pubkey:string) { + pullBlocks(server:Server, pubkey = "") { return this.blockCrawler.pullBlocks(server, pubkey) } @@ -119,7 +118,7 @@ export class PeerCrawler implements DuniterService { if (peers.length > CrawlerConstants.COUNT_FOR_ENOUGH_PEERS && dontCrawlIfEnoughPeers == this.DONT_IF_MORE_THAN_FOUR_PEERS) { return; } - let peersToTest = peers.slice().map((p:PeerDTO) => PeerDTO.fromJSONObject(p)); + let peersToTest = peers.slice().map(p => PeerDTO.fromJSONObject(p)) let tested:string[] = []; const found = []; while (peersToTest.length > 0) { @@ -144,7 +143,7 @@ export class PeerCrawler implements DuniterService { } } // Make unique list - peersToTest = _.uniq(peersToTest, false, (p:PeerDTO) => p.pubkey); + peersToTest = Underscore.uniq(peersToTest, false, (p:PeerDTO) => p.pubkey) } this.logger.info('Crawling done.'); for (let i = 0, len = found.length; i < len; i++) { @@ -205,7 +204,7 @@ export class SandboxCrawler implements DuniterService { async sandboxPull(server:Server) { this.logger && this.logger.info('Sandbox pulling started...'); const peers = await server.dal.getRandomlyUPsWithout([this.conf.pair.pub]) - const randoms = chooseXin(peers, CrawlerConstants.SANDBOX_PEERS_COUNT) + const randoms = chooseXin(peers.map(p => PeerDTO.fromDBPeer(p)), CrawlerConstants.SANDBOX_PEERS_COUNT) let peersToTest = randoms.slice().map((p) => PeerDTO.fromJSONObject(p)); for (const peer of peersToTest) { const fromHost = await connect(peer) @@ -241,7 +240,7 @@ export class PeerTester implements DuniterService { private async testPeers(server:Server, conf:ConfDTO, displayDelays:boolean) { let peers = await server.dal.listAllPeers(); let now = (new Date().getTime()); - peers = _.filter(peers, (p:any) => p.pubkey != conf.pair.pub); + peers = Underscore.filter(peers, (p:any) => p.pubkey != conf.pair.pub); await Promise.all(peers.map(async (thePeer:any) => { let p = PeerDTO.fromJSONObject(thePeer); if (thePeer.status == 'DOWN') { @@ -345,7 +344,7 @@ export class BlockCrawler { this.syncBlockFifo.kill(); } - pullBlocks(server:Server, pubkey:string) { + pullBlocks(server:Server, pubkey = "") { return this.syncBlock(server, pubkey) } @@ -364,22 +363,22 @@ export class BlockCrawler { } try { - let current = await server.dal.getCurrentBlockOrNull(); + let current: DBBlock|null = await server.dal.getCurrentBlockOrNull(); if (current) { this.pullingEvent(server, 'start', current.number); this.logger && this.logger.info("Pulling blocks from the network..."); let peers = await server.dal.findAllPeersNEWUPBut([server.conf.pair.pub]); - peers = _.shuffle(peers); + peers = Underscore.shuffle(peers); if (pubkey) { - _(peers).filter((p:any) => p.pubkey == pubkey); + peers = Underscore.filter(peers, (p:any) => p.pubkey == pubkey) } // Shuffle the peers - peers = _.shuffle(peers); + peers = Underscore.shuffle(peers); // Only take at max X of them peers = peers.slice(0, CrawlerConstants.MAX_NUMBER_OF_PEERS_FOR_PULLING); await Promise.all(peers.map(async (thePeer:any, i:number) => { let p = PeerDTO.fromJSONObject(thePeer); - this.pullingEvent(server, 'peer', _.extend({number: i, length: peers.length}, p)); + this.pullingEvent(server, 'peer', Underscore.extend({number: i, length: peers.length}, p)); this.logger && this.logger.trace("Try with %s %s", p.getURL(), p.pubkey.substr(0, 6)); try { let node:any = await connect(p); @@ -406,7 +405,7 @@ export class BlockCrawler { return Promise.resolve([node]) } async getLocalBlock(number: number): Promise<DBBlock> { - return server.dal.getBlock(number) + return server.dal.getBlockWeHaveItForSure(number) } async getRemoteBlock(thePeer: any, number: number): Promise<BlockDTO> { let block = null; @@ -429,7 +428,7 @@ export class BlockCrawler { } this.crawler.pullingEvent(server, 'applying', {number: block.number, last: this.lastDownloaded && this.lastDownloaded.number}); if (addedBlock) { - current = addedBlock; + current = DBBlock.fromBlockDTO(addedBlock); // Emit block events (for sharing with the network) only in forkWindowSize if (nodeCurrent && nodeCurrent.number - addedBlock.number < server.conf.forksize) { server.streamPush(addedBlock); diff --git a/app/modules/crawler/lib/garbager.ts b/app/modules/crawler/lib/garbager.ts index f748a63a66320d97dfb3e6f5729efa73bd56b74d..852d33187eb872fe9b768b9c50c12690dae73f09 100644 --- a/app/modules/crawler/lib/garbager.ts +++ b/app/modules/crawler/lib/garbager.ts @@ -16,5 +16,5 @@ import {Server} from "../../../../server" export const cleanLongDownPeers = async (server:Server, now:number) => { const first_down_limit = now - CrawlerConstants.PEER_LONG_DOWN * 1000; - await server.dal.peerDAL.query('DELETE FROM peer WHERE first_down < ' + first_down_limit) + await server.dal.peerDAL.removePeersDownBefore(first_down_limit) } diff --git a/app/modules/crawler/lib/pulling.ts b/app/modules/crawler/lib/pulling.ts index 7dc23418d1ce38ee117803d74aa8971ba1f9714c..7c785d280b2c2ac671c3990c16168d52c892d3b9 100644 --- a/app/modules/crawler/lib/pulling.ts +++ b/app/modules/crawler/lib/pulling.ts @@ -15,9 +15,8 @@ import {BlockDTO} from "../../../lib/dto/BlockDTO" import {DBBlock} from "../../../lib/db/DBBlock" import {PeerDTO} from "../../../lib/dto/PeerDTO" -import {BranchingDTO, ConfDTO} from "../../../lib/dto/ConfDTO" - -const _ = require('underscore'); +import {BranchingDTO} from "../../../lib/dto/ConfDTO" +import {Underscore} from "../../../lib/common-libs/underscore" export abstract class PullingDao { abstract applyBranch(blocks:BlockDTO[]): Promise<boolean> @@ -211,7 +210,7 @@ export abstract class AbstractDAO extends PullingDao { } return result; }); - memberForks = _.filter(memberForks, (fork:any) => { + memberForks = Underscore.filter(memberForks, (fork:any) => { let blockDistanceInBlocks = (fork.current.number - localCurrent.number) let timeDistanceInBlocks = (fork.current.medianTime - localCurrent.medianTime) / conf.avgGenTime const requiredTimeAdvance = conf.switchOnHeadAdvance diff --git a/app/modules/crawler/lib/sandbox.ts b/app/modules/crawler/lib/sandbox.ts index 58d3d79a7a1f6bde1b4a37ebf106dcdff4d392c5..f4a53218089179c38b3fd8e93268afa260823986 100644 --- a/app/modules/crawler/lib/sandbox.ts +++ b/app/modules/crawler/lib/sandbox.ts @@ -54,29 +54,38 @@ export const pullSandboxToLocalServer = async (currency:string, fromHost:any, to if (res) { const docs = getDocumentsTree(currency, res) + let t = 0 + let T = docs.identities.length + docs.certifications.length + docs.revocations.length + docs.memberships.length + for (let i = 0; i < docs.identities.length; i++) { const idty = docs.identities[i]; watcher && watcher.writeStatus('Identity ' + (i+1) + '/' + docs.identities.length) + watcher && watcher.sbxPercent((t++) / T * 100) await submitIdentityToServer(idty, toServer, notify, logger) } for (let i = 0; i < docs.revocations.length; i++) { const idty = docs.revocations[i]; watcher && watcher.writeStatus('Revocation ' + (i+1) + '/' + docs.revocations.length) + watcher && watcher.sbxPercent((t++) / T * 100) await submitRevocationToServer(idty, toServer, notify, logger) } for (let i = 0; i < docs.certifications.length; i++) { const cert = docs.certifications[i]; watcher && watcher.writeStatus('Certification ' + (i+1) + '/' + docs.certifications.length) + watcher && watcher.sbxPercent((t++) / T * 100) await submitCertificationToServer(cert, toServer, notify, logger) } for (let i = 0; i < docs.memberships.length; i++) { const ms = docs.memberships[i]; watcher && watcher.writeStatus('Membership ' + (i+1) + '/' + docs.memberships.length) + watcher && watcher.sbxPercent((t++) / T * 100) await submitMembershipToServer(ms, toServer, notify, logger) } + + watcher && watcher.sbxPercent(100) } } diff --git a/app/modules/crawler/lib/sync.ts b/app/modules/crawler/lib/sync.ts index efdafcf78ea942b4c77a904359ae576bfb335c52..3bf8cc58d9d40700ae55402dadb2926fa7a2a062 100644 --- a/app/modules/crawler/lib/sync.ts +++ b/app/modules/crawler/lib/sync.ts @@ -11,73 +11,53 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -import {CrawlerConstants} from "./constants" import * as stream from "stream" +import * as moment from "moment" import {Server} from "../../../../server" import {PeerDTO} from "../../../lib/dto/PeerDTO" import {FileDAL} from "../../../lib/dal/fileDAL" import {BlockDTO} from "../../../lib/dto/BlockDTO" -import {connect} from "./connect" -import {Contacter} from "./contacter" -import {pullSandboxToLocalServer} from "./sandbox" import {tx_cleaner} from "./tx_cleaner" import {AbstractDAO} from "./pulling" import {DBBlock} from "../../../lib/db/DBBlock" import {BlockchainService} from "../../../service/BlockchainService" -import {rawer} from "../../../lib/common-libs/index" -import {dos2unix} from "../../../lib/common-libs/dos2unix" -import {hashf} from "../../../lib/common" import {ConfDTO} from "../../../lib/dto/ConfDTO" import {PeeringService} from "../../../service/PeeringService" +import {CommonConstants} from "../../../lib/common-libs/constants" +import {Underscore} from "../../../lib/common-libs/underscore" +import {cliprogram} from "../../../lib/common-libs/programOptions" +import {EventWatcher, LoggerWatcher, MultimeterWatcher, Watcher} from "./sync/Watcher" +import {ChunkGetter} from "./sync/ChunkGetter" +import {AbstractSynchronizer} from "./sync/AbstractSynchronizer" -const util = require('util'); -const _ = require('underscore'); -const moment = require('moment'); -const multimeter = require('multimeter'); -const makeQuerablePromise = require('querablep'); - -const CONST_BLOCKS_CHUNK = 250; const EVAL_REMAINING_INTERVAL = 1000; -const INITIAL_DOWNLOAD_SLOTS = 1; export class Synchroniser extends stream.Duplex { - private watcher:Watcher + private watcher:EventWatcher private speed = 0 private blocksApplied = 0 - private contacterOptions:any constructor( private server:Server, - private host:string, - private port:number, - interactive = false, - private slowOption = false) { + private syncStrategy: AbstractSynchronizer, + interactive = false) { super({ objectMode: true }) // Wrapper to also push event stream - this.watcher = new EventWatcher( - interactive ? new MultimeterWatcher() : new LoggerWatcher(this.logger), - (pct:number, innerWatcher:Watcher) => { - if (pct !== undefined && innerWatcher.downloadPercent() < pct) { - this.push({ download: pct }); - } - }, - (pct:number, innerWatcher:Watcher) => { - if (pct !== undefined && innerWatcher.appliedPercent() < pct) { - this.push({ applied: pct }); - } - } - ) + this.watcher = new EventWatcher(interactive ? new MultimeterWatcher() : new LoggerWatcher(this.logger)) + this.watcher.onEvent('downloadChange', (pct: number) => this.push({ download: pct })) + this.watcher.onEvent('storageChange', (pct: number) => this.push({ saved: pct })) + this.watcher.onEvent('appliedChange', (pct: number) => this.push({ applied: pct })) + this.watcher.onEvent('sbxChange', (pct: number) => this.push({ sandbox: pct })) + this.watcher.onEvent('peersChange', (pct: number) => this.push({ peersSync: pct })) + + this.syncStrategy.setWatcher(this.watcher) if (interactive) { this.logger.mute(); } - - this.contacterOptions = { - timeout: CrawlerConstants.SYNC_LONG_TIMEOUT - } } get conf(): ConfDTO { @@ -117,22 +97,10 @@ export class Synchroniser extends stream.Duplex { } } - async test() { - const peering = await Contacter.fetchPeer(this.host, this.port, this.contacterOptions); - const node = await connect(PeerDTO.fromJSONObject(peering)); - return node.getCurrent(); - } - - async sync(to:number, chunkLen:number, askedCautious = false, nopeers = false, noShufflePeers = false) { + async sync(to:number, chunkLen:number, askedCautious = false) { try { - - const peering = await Contacter.fetchPeer(this.host, this.port, this.contacterOptions); - - let peer = PeerDTO.fromJSONObject(peering); - this.logger.info("Try with %s %s", peer.getURL(), peer.pubkey.substr(0, 6)); - let node:any = await connect(peer); - node.pubkey = peer.pubkey; + await this.syncStrategy.init() this.logger.info('Sync started.'); const fullSync = !to; @@ -141,57 +109,30 @@ export class Synchroniser extends stream.Duplex { // Blockchain headers //============ this.logger.info('Getting remote blockchain info...'); - this.watcher.writeStatus('Connecting to ' + this.host + '...'); - const lCurrent:DBBlock = await this.dal.getCurrentBlockOrNull(); + const lCurrent:DBBlock|null = await this.dal.getCurrentBlockOrNull(); const localNumber = lCurrent ? lCurrent.number : -1; - let rCurrent:BlockDTO + let rCurrent:BlockDTO|null if (isNaN(to)) { - rCurrent = await node.getCurrent(); + rCurrent = await this.syncStrategy.getCurrent(); + if (!rCurrent) { + throw 'Remote does not have a current block. Sync aborted.' + } } else { - rCurrent = await node.getBlock(to); + rCurrent = await this.syncStrategy.getBlock(to) + if (!rCurrent) { + throw 'Remote does not have a target block. Sync aborted.' + } } to = rCurrent.number || 0 - //======= - // Peers (just for P2P download) - //======= - let peers:PeerDTO[] = []; - if (!nopeers && (to - localNumber > 1000)) { // P2P download if more than 1000 blocs - this.watcher.writeStatus('Peers...'); - const merkle = await this.dal.merkleForPeers(); - const getPeers = node.getPeers.bind(node); - const json2 = await getPeers({}); - const rm = new NodesMerkle(json2); - if(rm.root() != merkle.root()){ - const leavesToAdd:string[] = []; - const json = await getPeers({ leaves: true }); - _(json.leaves).forEach((leaf:string) => { - if(merkle.leaves().indexOf(leaf) == -1){ - leavesToAdd.push(leaf); - } - }); - peers = await Promise.all(leavesToAdd.map(async (leaf) => { - try { - const json3 = await getPeers({ "leaf": leaf }); - const jsonEntry = json3.leaf.value; - const endpoint = jsonEntry.endpoints[0]; - this.watcher.writeStatus('Peer ' + endpoint); - return jsonEntry; - } catch (e) { - this.logger.warn("Could not get peer of leaf %s, continue...", leaf); - return null; - } - })) - } - else { - this.watcher.writeStatus('Peers already known'); - } + const rootBlock = await this.syncStrategy.getBlock(0) + if (!rootBlock) { + throw 'Could not get root block. Sync aborted.' } + await this.BlockchainService.saveParametersForRootBlock(rootBlock) + await this.server.reloadConf() - if (!peers.length) { - peers.push(peer); - } - peers = peers.filter((p) => p); + await this.syncStrategy.initWithKnownLocalAndToAndCurrency(to, localNumber, rCurrent.currency) //============ // Blockchain @@ -200,12 +141,20 @@ export class Synchroniser extends stream.Duplex { // We use cautious mode if it is asked, or not particulary asked but blockchain has been started const cautious = (askedCautious === true || localNumber >= 0); - const shuffledPeers = noShufflePeers ? peers : _.shuffle(peers); - const downloader = new P2PDownloader(rCurrent.currency, localNumber, to, rCurrent.hash, shuffledPeers, this.watcher, this.logger, hashf, this.dal, this.slowOption); + const downloader = new ChunkGetter( + localNumber, + to, + rCurrent.hash, + this.syncStrategy, + this.dal, + !cautious, + this.watcher) - downloader.start(); + const startp = downloader.start() let lastPullBlock:BlockDTO|null = null; + let syncStrategy = this.syncStrategy + let node = this.syncStrategy.getPeer() let dao = new (class extends AbstractDAO { @@ -218,7 +167,7 @@ export class Synchroniser extends stream.Duplex { } async applyBranch(blocks:BlockDTO[]) { - blocks = _.filter(blocks, (b:BlockDTO) => b.number <= to); + blocks = Underscore.filter(blocks, (b:BlockDTO) => b.number <= to); if (cautious) { for (const block of blocks) { if (block.number == 0) { @@ -256,19 +205,22 @@ export class Synchroniser extends stream.Duplex { return [node] } async getLocalBlock(number: number): Promise<DBBlock> { - return this.dal.getBlock(number) + return this.dal.getBlockWeHaveItForSure(number) } async getRemoteBlock(thePeer: PeerDTO, number: number): Promise<BlockDTO> { let block = null; try { - block = await node.getBlock(number); + block = await syncStrategy.getBlock(number) + if (!block) { + throw 'Could not get remote block' + } tx_cleaner(block.transactions); } catch (e) { if (e.httpCode != 404) { throw e; } } - return block; + return block as BlockDTO } async applyMainBranch(block: BlockDTO): Promise<boolean> { const addedBlock = await this.BlockchainService.submitBlock(block, true) @@ -283,45 +235,52 @@ export class Synchroniser extends stream.Duplex { } // Tells wether given peer is a member peer async isMemberPeer(thePeer: PeerDTO): Promise<boolean> { - let idty = await this.dal.getWrittenIdtyByPubkey(thePeer.pubkey); + let idty = await this.dal.getWrittenIdtyByPubkeyForIsMember(thePeer.pubkey); return (idty && idty.member) || false; } - downloadBlocks(thePeer: PeerDTO, fromNumber: number, count?: number | undefined): Promise<BlockDTO[]> { + async downloadBlocks(thePeer: PeerDTO, fromNumber: number, count?: number | undefined): Promise<BlockDTO[]> { // Note: we don't care about the particular peer asked by the method. We use the network instead. const numberOffseted = fromNumber - (localNumber + 1); - const targetChunk = Math.floor(numberOffseted / CONST_BLOCKS_CHUNK); + const targetChunk = Math.floor(numberOffseted / CommonConstants.CONST_BLOCKS_CHUNK); // Return the download promise! Simple. - return downloader.getChunk(targetChunk); + return (await downloader.getChunk(targetChunk))() } })(this.server, this.watcher, this.dal, this.BlockchainService) const logInterval = setInterval(() => this.logRemaining(to), EVAL_REMAINING_INTERVAL); - await dao.pull(this.conf, this.logger) + await Promise.all([ + dao.pull(this.conf, this.logger), + await startp // In case of errors, will stop the process + ]) // Finished blocks this.watcher.downloadPercent(100.0); + this.watcher.storagePercent(100.0); this.watcher.appliedPercent(100.0); if (logInterval) { clearInterval(logInterval); } - // Save currency parameters given by root block - const rootBlock = await this.server.dal.getBlock(0); - await this.BlockchainService.saveParametersForRootBlock(rootBlock); this.server.dal.blockDAL.cleanCache(); - //======= - // Sandboxes - //======= - this.watcher.writeStatus('Synchronizing the sandboxes...'); - await pullSandboxToLocalServer(this.conf.currency, node, this.server, this.server.logger, this.watcher, 1, false) + if (!cliprogram.nosbx) { + //======= + // Sandboxes + //======= + await this.syncStrategy.syncSandbox() + } - //======= - // Peers - //======= - await this.syncPeers(nopeers, fullSync, this.host, this.port, to) + if (!cliprogram.nopeers) { + //======= + // Peers + //======= + await this.syncStrategy.syncPeers(fullSync, to) + } + + // Trim the loki data + await this.server.dal.loki.flushAndTrimData() this.watcher.end(); this.push({ sync: true }); @@ -333,668 +292,4 @@ export class Synchroniser extends stream.Duplex { throw err; } } - - async syncPeers(nopeers:boolean, fullSync:boolean, host:string, port:number, to?:number) { - if (!nopeers && fullSync) { - - const peering = await Contacter.fetchPeer(host, port, this.contacterOptions); - - let peer = PeerDTO.fromJSONObject(peering); - this.logger.info("Try with %s %s", peer.getURL(), peer.pubkey.substr(0, 6)); - let node:any = await connect(peer); - node.pubkey = peer.pubkey; - this.logger.info('Sync started.'); - - this.watcher.writeStatus('Peers...'); - await this.syncPeer(node); - const merkle = await this.dal.merkleForPeers(); - const getPeers = node.getPeers.bind(node); - const json2 = await getPeers({}); - const rm = new NodesMerkle(json2); - if(rm.root() != merkle.root()){ - const leavesToAdd:string[] = []; - const json = await getPeers({ leaves: true }); - _(json.leaves).forEach((leaf:string) => { - if(merkle.leaves().indexOf(leaf) == -1){ - leavesToAdd.push(leaf); - } - }); - for (const leaf of leavesToAdd) { - try { - const json3 = await getPeers({ "leaf": leaf }); - const jsonEntry = json3.leaf.value; - const sign = json3.leaf.value.signature; - const entry:any = {}; - ["version", "currency", "pubkey", "endpoints", "block"].forEach((key) => { - entry[key] = jsonEntry[key]; - }); - entry.signature = sign; - this.watcher.writeStatus('Peer ' + entry.pubkey); - await this.PeeringService.submitP(entry, false, to === undefined); - } catch (e) { - this.logger.warn(e); - } - } - } - else { - this.watcher.writeStatus('Peers already known'); - } - } - } - - //============ - // Peer - //============ - private async syncPeer (node:any) { - - // Global sync vars - const remotePeer = PeerDTO.fromJSONObject({}); - let remoteJsonPeer:any = {}; - const json = await node.getPeer(); - remotePeer.version = json.version - remotePeer.currency = json.currency - remotePeer.pubkey = json.pub - remotePeer.endpoints = json.endpoints - remotePeer.blockstamp = json.block - remotePeer.signature = json.signature - const entry = remotePeer.getRawUnsigned(); - const signature = dos2unix(remotePeer.signature); - // Parameters - if(!(entry && signature)){ - throw 'Requires a peering entry + signature'; - } - - remoteJsonPeer = json; - remoteJsonPeer.pubkey = json.pubkey; - let signatureOK = this.PeeringService.checkPeerSignature(remoteJsonPeer); - if (!signatureOK) { - this.watcher.writeStatus('Wrong signature for peer #' + remoteJsonPeer.pubkey); - } - try { - await this.PeeringService.submitP(remoteJsonPeer); - } catch (err) { - if (err.indexOf !== undefined && err.indexOf(CrawlerConstants.ERRORS.NEWER_PEER_DOCUMENT_AVAILABLE.uerr.message) !== -1 && err != CrawlerConstants.ERROR.PEER.UNKNOWN_REFERENCE_BLOCK) { - throw err; - } - } - } -} - -class NodesMerkle { - - private depth:number - private nodesCount:number - private leavesCount:number - private merkleRoot:string - - constructor(json:any) { - this.depth = json.depth - this.nodesCount = json.nodesCount - this.leavesCount = json.leavesCount - this.merkleRoot = json.root; - } - - // var i = 0; - // this.levels = []; - // while(json && json.levels[i]){ - // this.levels.push(json.levels[i]); - // i++; - // } - - root() { - return this.merkleRoot - } -} - -interface Watcher { - writeStatus(str: string): void - downloadPercent(pct?: number): number - appliedPercent(pct?: number): number - end(): void -} - -class EventWatcher implements Watcher { - - constructor( - private innerWatcher:Watcher, - private beforeDownloadPercentHook: (pct:number, innerWatcher:Watcher) => void, - private beforeAppliedPercentHook: (pct:number, innerWatcher:Watcher) => void) { - } - - writeStatus(str: string): void { - this.innerWatcher.writeStatus(str) - } - - downloadPercent(pct?: number): number { - this.beforeDownloadPercentHook(pct || 0, this.innerWatcher) - return this.innerWatcher.downloadPercent(pct) - } - - appliedPercent(pct?: number): number { - this.beforeAppliedPercentHook(pct || 0, this.innerWatcher) - return this.innerWatcher.appliedPercent(pct) - } - - end(): void { - this.innerWatcher.end() - } -} - -class MultimeterWatcher implements Watcher { - - private xPos:number - private yPos:number - private multi:any - private charm:any - private appliedBar:any - private downloadBar:any - private writtens:string[] = [] - - constructor() { - this.multi = multimeter(process); - this.charm = this.multi.charm; - this.charm.on('^C', process.exit); - this.charm.reset(); - - this.multi.write('Progress:\n\n'); - - this.multi.write("Download: \n"); - this.downloadBar = this.multi("Download: \n".length, 3, { - width : 20, - solid : { - text : '|', - foreground : 'white', - background : 'blue' - }, - empty : { text : ' ' } - }); - - this.multi.write("Apply: \n"); - this.appliedBar = this.multi("Apply: \n".length, 4, { - width : 20, - solid : { - text : '|', - foreground : 'white', - background : 'blue' - }, - empty : { text : ' ' } - }); - - this.multi.write('\nStatus: '); - - this.charm.position( (x:number, y:number) => { - this.xPos = x; - this.yPos = y; - }); - - this.writtens = []; - - this.downloadBar.percent(0); - this.appliedBar.percent(0); - } - - writeStatus(str:string) { - this.writtens.push(str); - //require('fs').writeFileSync('writtens.json', JSON.stringify(writtens)); - this.charm - .position(this.xPos, this.yPos) - .erase('end') - .write(str) - ; - }; - - downloadPercent(pct:number) { - return this.downloadBar.percent(pct) - } - - appliedPercent(pct:number) { - return this.appliedBar.percent(pct) - } - - end() { - this.multi.write('\nAll done.\n'); - this.multi.destroy(); - } -} - -class LoggerWatcher implements Watcher { - - private downPct = 0 - private appliedPct = 0 - private lastMsg = "" - - constructor(private logger:any) { - } - - showProgress() { - return this.logger.info('Downloaded %s%, Applied %s%', this.downPct, this.appliedPct) - } - - writeStatus(str:string) { - if (str != this.lastMsg) { - this.lastMsg = str; - this.logger.info(str); - } - } - - downloadPercent(pct:number) { - if (pct !== undefined) { - let changed = pct > this.downPct; - this.downPct = pct; - if (changed) this.showProgress(); - } - return this.downPct; - } - - appliedPercent(pct:number) { - if (pct !== undefined) { - let changed = pct > this.appliedPct; - this.appliedPct = pct; - if (changed) this.showProgress(); - } - return this.appliedPct; - } - - end() { - } - -} - -class P2PDownloader { - - private PARALLEL_PER_CHUNK = 1; - private MAX_DELAY_PER_DOWNLOAD = 15000; - private NO_NODES_AVAILABLE = "No node available for download"; - private TOO_LONG_TIME_DOWNLOAD:string - private nbBlocksToDownload:number - private numberOfChunksToDownload:number - private downloadSlots:number - private chunks:any - private processing:any - private handler:any - private resultsDeferers:any - private resultsData:Promise<BlockDTO[]>[] - private nodes:any = {} - private nbDownloadsTried = 0 - private nbDownloading = 0 - private lastAvgDelay:number - private aSlotWasAdded = false - private slots:number[] = []; - private downloads:any = {}; - private startResolver:any - private downloadStarter:Promise<any> - - constructor( - private currency:string, - private localNumber:number, - private to:number, - private toHash:string, - private peers:PeerDTO[], - private watcher:Watcher, - private logger:any, - private hashf:any, - private dal:FileDAL, - private slowOption:any) { - - this.TOO_LONG_TIME_DOWNLOAD = "No answer after " + this.MAX_DELAY_PER_DOWNLOAD + "ms, will retry download later."; - this.nbBlocksToDownload = Math.max(0, to - localNumber); - this.numberOfChunksToDownload = Math.ceil(this.nbBlocksToDownload / CONST_BLOCKS_CHUNK); - this.chunks = Array.from({ length: this.numberOfChunksToDownload }).map(() => null); - this.processing = Array.from({ length: this.numberOfChunksToDownload }).map(() => false); - this.handler = Array.from({ length: this.numberOfChunksToDownload }).map(() => null); - this.resultsDeferers = Array.from({ length: this.numberOfChunksToDownload }).map(() => null); - this.resultsData = Array.from({ length: this.numberOfChunksToDownload }).map((unused, index) => new Promise((resolve, reject) => { - this.resultsDeferers[index] = { resolve, reject }; - })); - - // Create slots of download, in a ready stage - this.downloadSlots = slowOption ? 1 : Math.min(INITIAL_DOWNLOAD_SLOTS, peers.length); - this.lastAvgDelay = this.MAX_DELAY_PER_DOWNLOAD; - - /** - * Triggers for starting the download. - */ - this.downloadStarter = new Promise((resolve) => this.startResolver = resolve); - - /** - * Download worker - * @type {*|Promise} When finished. - */ - (async () => { - try { - await this.downloadStarter; - let doneCount = 0, resolvedCount = 0; - while (resolvedCount < this.chunks.length) { - doneCount = 0; - resolvedCount = 0; - // Add as much possible downloads as possible, and count the already done ones - for (let i = this.chunks.length - 1; i >= 0; i--) { - if (this.chunks[i] === null && !this.processing[i] && this.slots.indexOf(i) === -1 && this.slots.length < this.downloadSlots) { - this.slots.push(i); - this.processing[i] = true; - this.downloads[i] = makeQuerablePromise(this.downloadChunk(i)); // Starts a new download - } else if (this.downloads[i] && this.downloads[i].isFulfilled() && this.processing[i]) { - doneCount++; - } - // We count the number of perfectly downloaded & validated chunks - if (this.chunks[i]) { - resolvedCount++; - } - } - watcher.downloadPercent(Math.round(doneCount / this.numberOfChunksToDownload * 100)); - let races = this.slots.map((i) => this.downloads[i]); - if (races.length) { - try { - await this.raceOrCancelIfTimeout(this.MAX_DELAY_PER_DOWNLOAD, races); - } catch (e) { - this.logger.warn(e); - } - for (let i = 0; i < this.slots.length; i++) { - // We must know the index of what resolved/rejected to free the slot - const doneIndex = this.slots.reduce((found:any, realIndex:number, index:number) => { - if (found !== null) return found; - if (this.downloads[realIndex].isFulfilled()) return index; - return null; - }, null); - if (doneIndex !== null) { - const realIndex = this.slots[doneIndex]; - if (this.downloads[realIndex].isResolved()) { - // IIFE to be safe about `realIndex` - (async () => { - const blocks = await this.downloads[realIndex]; - if (realIndex < this.chunks.length - 1) { - // We must wait for NEXT blocks to be STRONGLY validated before going any further, otherwise we - // could be on the wrong chain - await this.getChunk(realIndex + 1); - } - const chainsWell = await this.chainsCorrectly(blocks, realIndex); - if (chainsWell) { - // Chunk is COMPLETE - this.logger.warn("Chunk #%s is COMPLETE from %s", realIndex, [this.handler[realIndex].host, this.handler[realIndex].port].join(':')); - this.chunks[realIndex] = blocks; - this.resultsDeferers[realIndex].resolve(this.chunks[realIndex]); - } else { - this.logger.warn("Chunk #%s DOES NOT CHAIN CORRECTLY from %s", realIndex, [this.handler[realIndex].host, this.handler[realIndex].port].join(':')); - // Penality on this node to avoid its usage - if (this.handler[realIndex].resetFunction) { - await this.handler[realIndex].resetFunction(); - } - if (this.handler[realIndex].tta !== undefined) { - this.handler[realIndex].tta += this.MAX_DELAY_PER_DOWNLOAD; - } - // Need a retry - this.processing[realIndex] = false; - } - })() - } else { - this.processing[realIndex] = false; // Need a retry - } - this.slots.splice(doneIndex, 1); - } - } - } - // Wait a bit - await new Promise((resolve, reject) => setTimeout(resolve, 10)); - } - } catch (e) { - this.logger.error('Fatal error in the downloader:'); - this.logger.error(e); - } - })() - } - - /** - * Get a list of P2P nodes to use for download. - * If a node is not yet correctly initialized (we can test a node before considering it good for downloading), then - * this method would not return it. - */ - private async getP2Pcandidates(): Promise<any[]> { - let promises = this.peers.reduce((chosens:any, other:any, index:number) => { - if (!this.nodes[index]) { - // Create the node - let p = PeerDTO.fromJSONObject(this.peers[index]); - this.nodes[index] = makeQuerablePromise((async () => { - // We wait for the download process to be triggered - // await downloadStarter; - // if (nodes[index - 1]) { - // try { await nodes[index - 1]; } catch (e) {} - // } - const node:any = await connect(p) - // We initialize nodes with the near worth possible notation - node.tta = 1; - node.nbSuccess = 0; - return node; - })()) - chosens.push(this.nodes[index]); - } else { - chosens.push(this.nodes[index]); - } - // Continue - return chosens; - }, []); - let candidates:any[] = await Promise.all(promises) - candidates.forEach((c:any) => { - c.tta = c.tta || 0; // By default we say a node is super slow to answer - c.ttas = c.ttas || []; // Memorize the answer delays - }); - if (candidates.length === 0) { - throw this.NO_NODES_AVAILABLE; - } - // We remove the nodes impossible to reach (timeout) - let withGoodDelays = _.filter(candidates, (c:any) => c.tta <= this.MAX_DELAY_PER_DOWNLOAD); - if (withGoodDelays.length === 0) { - // No node can be reached, we can try to lower the number of nodes on which we download - this.downloadSlots = Math.floor(this.downloadSlots / 2); - // We reinitialize the nodes - this.nodes = {}; - // And try it all again - return this.getP2Pcandidates(); - } - const parallelMax = Math.min(this.PARALLEL_PER_CHUNK, withGoodDelays.length); - withGoodDelays = _.sortBy(withGoodDelays, (c:any) => c.tta); - withGoodDelays = withGoodDelays.slice(0, parallelMax); - // We temporarily augment the tta to avoid asking several times to the same node in parallel - withGoodDelays.forEach((c:any) => c.tta = this.MAX_DELAY_PER_DOWNLOAD); - return withGoodDelays; - } - - /** - * Download a chunk of blocks using P2P network through BMA API. - * @param from The starting block to download - * @param count The number of blocks to download. - * @param chunkIndex The # of the chunk in local algorithm (logging purposes only) - */ - private async p2pDownload(from:number, count:number, chunkIndex:number) { - let candidates = await this.getP2Pcandidates(); - // Book the nodes - return await this.raceOrCancelIfTimeout(this.MAX_DELAY_PER_DOWNLOAD, candidates.map(async (node:any) => { - try { - const start = Date.now(); - this.handler[chunkIndex] = node; - node.downloading = true; - this.nbDownloading++; - this.watcher.writeStatus('Getting chunk #' + chunkIndex + '/' + (this.numberOfChunksToDownload - 1) + ' from ' + from + ' to ' + (from + count - 1) + ' on peer ' + [node.host, node.port].join(':')); - let blocks = await node.getBlocks(count, from); - node.ttas.push(Date.now() - start); - // Only keep a flow of 5 ttas for the node - if (node.ttas.length > 5) node.ttas.shift(); - // Average time to answer - node.tta = Math.round(node.ttas.reduce((sum:number, tta:number) => sum + tta, 0) / node.ttas.length); - this.watcher.writeStatus('GOT chunk #' + chunkIndex + '/' + (this.numberOfChunksToDownload - 1) + ' from ' + from + ' to ' + (from + count - 1) + ' on peer ' + [node.host, node.port].join(':')); - node.nbSuccess++; - - // Opening/Closing slots depending on the Interne connection - if (this.slots.length == this.downloadSlots) { - const peers = await Promise.all(_.values(this.nodes)) - const downloading = _.filter(peers, (p:any) => p.downloading && p.ttas.length); - const currentAvgDelay = downloading.reduce((sum:number, c:any) => { - const tta = Math.round(c.ttas.reduce((sum:number, tta:number) => sum + tta, 0) / c.ttas.length); - return sum + tta; - }, 0) / downloading.length; - // Opens or close downloading slots - if (!this.slowOption) { - // Check the impact of an added node (not first time) - if (!this.aSlotWasAdded) { - // We try to add a node - const newValue = Math.min(peers.length, this.downloadSlots + 1); - if (newValue !== this.downloadSlots) { - this.downloadSlots = newValue; - this.aSlotWasAdded = true; - this.logger.info('AUGMENTED DOWNLOAD SLOTS! Now has %s slots', this.downloadSlots); - } - } else { - this.aSlotWasAdded = false; - const decelerationPercent = currentAvgDelay / this.lastAvgDelay - 1; - const addedNodePercent = 1 / this.nbDownloading; - this.logger.info('Deceleration = %s (%s/%s), AddedNodePercent = %s', decelerationPercent, currentAvgDelay, this.lastAvgDelay, addedNodePercent); - if (decelerationPercent > addedNodePercent) { - this.downloadSlots = Math.max(1, this.downloadSlots - 1); // We reduce the number of slots, but we keep at least 1 slot - this.logger.info('REDUCED DOWNLOAD SLOT! Now has %s slots', this.downloadSlots); - } - } - } - this.lastAvgDelay = currentAvgDelay; - } - - this.nbDownloadsTried++; - this.nbDownloading--; - node.downloading = false; - - return blocks; - } catch (e) { - this.nbDownloading--; - node.downloading = false; - this.nbDownloadsTried++; - node.ttas.push(this.MAX_DELAY_PER_DOWNLOAD + 1); // No more ask on this node - // Average time to answer - node.tta = Math.round(node.ttas.reduce((sum:number, tta:number) => sum + tta, 0) / node.ttas.length); - throw e; - } - })) - } - - /** - * Function for downloading a chunk by its number. - * @param index Number of the chunk. - */ - private async downloadChunk(index:number): Promise<BlockDTO[]> { - // The algorithm to download a chunk - const from = this.localNumber + 1 + index * CONST_BLOCKS_CHUNK; - let count = CONST_BLOCKS_CHUNK; - if (index == this.numberOfChunksToDownload - 1) { - count = this.nbBlocksToDownload % CONST_BLOCKS_CHUNK || CONST_BLOCKS_CHUNK; - } - try { - const fileName = this.currency + "/chunk_" + index + "-" + CONST_BLOCKS_CHUNK + ".json"; - if (this.localNumber <= 0 && (await this.dal.confDAL.coreFS.exists(fileName))) { - this.handler[index] = { - host: 'filesystem', - port: 'blockchain', - resetFunction: () => this.dal.confDAL.coreFS.remove(fileName) - }; - return (await this.dal.confDAL.coreFS.readJSON(fileName)).blocks; - } else { - const chunk:any = await this.p2pDownload(from, count, index); - // Store the file to avoid re-downloading - if (this.localNumber <= 0 && chunk.length === CONST_BLOCKS_CHUNK) { - await this.dal.confDAL.coreFS.makeTree(this.currency); - await this.dal.confDAL.coreFS.writeJSON(fileName, { blocks: chunk }); - } - return chunk; - } - } catch (e) { - this.logger.error(e); - return this.downloadChunk(index); - } - } - - /** - * Utility function this starts a race between promises but cancels it if no answer is found before `timeout` - * @param timeout - * @param races - * @returns {Promise} - */ - private raceOrCancelIfTimeout(timeout:number, races:any[]) { - return Promise.race([ - // Process the race, but cancel it if we don't get an anwser quickly enough - new Promise((resolve, reject) => { - setTimeout(() => { - reject(this.TOO_LONG_TIME_DOWNLOAD); - }, timeout) - }) - ].concat(races)); - }; - - private async chainsCorrectly(blocks:BlockDTO[], index:number) { - - if (!blocks.length) { - this.logger.error('No block was downloaded'); - return false; - } - - for (let i = blocks.length - 1; i > 0; i--) { - if (blocks[i].number !== blocks[i - 1].number + 1 || blocks[i].previousHash !== blocks[i - 1].hash) { - this.logger.error("Blocks do not chaing correctly", blocks[i].number); - return false; - } - if (blocks[i].version != blocks[i - 1].version && blocks[i].version != blocks[i - 1].version + 1) { - this.logger.error("Version cannot be downgraded", blocks[i].number); - return false; - } - } - - // Check hashes - for (let i = 0; i < blocks.length; i++) { - // Note: the hash, in Duniter, is made only on the **signing part** of the block: InnerHash + Nonce - if (blocks[i].version >= 6) { - for (const tx of blocks[i].transactions) { - tx.version = CrawlerConstants.TRANSACTION_VERSION; - } - } - if (blocks[i].inner_hash !== hashf(rawer.getBlockInnerPart(blocks[i])).toUpperCase()) { - this.logger.error("Inner hash of block#%s from %s does not match", blocks[i].number); - return false; - } - if (blocks[i].hash !== hashf(rawer.getBlockInnerHashAndNonceWithSignature(blocks[i])).toUpperCase()) { - this.logger.error("Hash of block#%s from %s does not match", blocks[i].number); - return false; - } - } - - const lastBlockOfChunk = blocks[blocks.length - 1]; - if ((lastBlockOfChunk.number == this.to || blocks.length < CONST_BLOCKS_CHUNK) && lastBlockOfChunk.hash != this.toHash) { - // Top chunk - this.logger.error('Top block is not on the right chain'); - return false; - } else { - // Chaining between downloads - const previousChunk = await this.getChunk(index + 1); - const blockN = blocks[blocks.length - 1]; // The block n - const blockNp1 = previousChunk[0]; // The block n + 1 - if (blockN && blockNp1 && (blockN.number + 1 !== blockNp1.number || blockN.hash != blockNp1.previousHash)) { - this.logger.error('Chunk is not referenced by the upper one'); - return false; - } - } - return true; - } - - /** - * PUBLIC API - */ - - /*** - * Triggers the downloading - */ - start() { - return this.startResolver() - } - - /*** - * Promises a chunk to be downloaded and returned - * @param index The number of the chunk to download & return - */ - getChunk(index:number) { - return this.resultsData[index] || Promise.resolve([]) - } } diff --git a/app/modules/crawler/lib/sync/AbstractSynchronizer.ts b/app/modules/crawler/lib/sync/AbstractSynchronizer.ts new file mode 100644 index 0000000000000000000000000000000000000000..4b9215f1307b4f317cc5526a2ade3f2b05a5e04c --- /dev/null +++ b/app/modules/crawler/lib/sync/AbstractSynchronizer.ts @@ -0,0 +1,48 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import {BlockDTO} from "../../../../lib/dto/BlockDTO" +import {ISyncDownloader} from "./ISyncDownloader" +import {CommonConstants} from "../../../../lib/common-libs/constants" +import {PeerDTO} from "../../../../lib/dto/PeerDTO" +import {Watcher} from "./Watcher" +import {FileDAL} from "../../../../lib/dal/fileDAL" +import * as path from 'path' + +export abstract class AbstractSynchronizer { + + constructor() { + } + + abstract init(): Promise<void> + abstract initWithKnownLocalAndToAndCurrency(to: number, localNumber: number, currency: string): Promise<void> + abstract getCurrent(): Promise<BlockDTO|null> + abstract getBlock(number: number): Promise<BlockDTO|null> + abstract p2pDownloader(): ISyncDownloader + abstract fsDownloader(): ISyncDownloader + abstract syncPeers(fullSync:boolean, to?:number): Promise<void> + abstract syncSandbox(): Promise<void> + abstract getPeer(): PeerDTO + abstract setWatcher(watcher: Watcher): void + public abstract getCurrency(): string + public abstract getChunksPath(): string + public abstract get readDAL(): FileDAL + + public getChunkRelativePath(i: number) { + return path.join(this.getCurrency(), this.getChunkName(i)) + } + + public getChunkName(i: number) { + return CommonConstants.CHUNK_PREFIX + i + "-" + CommonConstants.CONST_BLOCKS_CHUNK + ".json" + } +} diff --git a/app/modules/crawler/lib/sync/ChunkGetter.ts b/app/modules/crawler/lib/sync/ChunkGetter.ts new file mode 100644 index 0000000000000000000000000000000000000000..c6a41f8735baedaf490bd5840e3d23f128930b91 --- /dev/null +++ b/app/modules/crawler/lib/sync/ChunkGetter.ts @@ -0,0 +1,299 @@ +import {PromiseOfBlocksReading} from "./PromiseOfBlockReading" +import {BlockDTO} from "../../../../lib/dto/BlockDTO" +import {CrawlerConstants} from "../constants" +import {hashf} from "../../../../lib/common" +import {getBlockInnerHashAndNonceWithSignature, getBlockInnerPart} from "../../../../lib/common-libs/rawer" +import {CommonConstants} from "../../../../lib/common-libs/constants" +import {NewLogger} from "../../../../lib/logger" +import {ISyncDownloader} from "./ISyncDownloader" +import {DBBlock} from "../../../../lib/db/DBBlock" +import {FileDAL} from "../../../../lib/dal/fileDAL" +import {Watcher} from "./Watcher" +import {cliprogram} from "../../../../lib/common-libs/programOptions" +import {Querable, querablep} from "../../../../lib/common-libs/querable" +import {AbstractSynchronizer} from "./AbstractSynchronizer" + +const logger = NewLogger() + +interface DownloadHandler { + downloader: ISyncDownloader +} + +interface WaitingState extends DownloadHandler { + state: 'WAITING', + chunk?: Querable<BlockDTO[]>, +} + +interface DownloadingState extends DownloadHandler { + state: 'DOWNLOADING', + chunk: Querable<BlockDTO[]>, +} + +interface DownloadedState extends DownloadHandler { + state: 'DOWNLOADED', + chunk: Querable<BlockDTO[]>, +} + +interface CompletedState extends DownloadHandler { + state: 'COMPLETED', + readBlocks: PromiseOfBlocksReading, +} + +export class ChunkGetter { + + private resultsDeferers:{ resolve: (data: PromiseOfBlocksReading) => void, reject: () => void }[] + private resultsData:Promise<PromiseOfBlocksReading>[] + private downloadHandlers:(WaitingState|DownloadingState|DownloadedState|CompletedState)[] + private fsDownloader: ISyncDownloader + private p2PDownloader: ISyncDownloader + private downloadedChunks = 0 + private writtenChunks = 0 + private numberOfChunksToDownload:number + private parallelDownloads = cliprogram.slow ? 1 : 5 + private maxDownloadAdvance = 10 // 10 chunks can be downloaded even if 10th chunk above is not completed + private MAX_DOWNLOAD_TIMEOUT = 15000 + private writeDAL: FileDAL + + constructor( + private localNumber:number, + private to:number, + private toHash:string, + private syncStrategy: AbstractSynchronizer, + dal:FileDAL, + private nocautious:boolean, + private watcher:Watcher, + ) { + this.writeDAL = dal + const nbBlocksToDownload = Math.max(0, to - localNumber) + this.numberOfChunksToDownload = Math.ceil(nbBlocksToDownload / CommonConstants.CONST_BLOCKS_CHUNK) + this.p2PDownloader = syncStrategy.p2pDownloader() + this.fsDownloader = syncStrategy.fsDownloader() + + this.resultsDeferers = Array.from({ length: this.numberOfChunksToDownload }).map(() => ({ + resolve: () => { throw Error('resolve should not be called here') }, + reject: () => { throw Error('reject should not be called here') }, + })) + this.resultsData = Array.from({ length: this.numberOfChunksToDownload }).map((unused, index) => new Promise(async (resolve, reject) => { + this.resultsDeferers[index] = { resolve, reject } + })) + } + + /*** + * Triggers the downloading, and parallelize it. + */ + start() { + + // Initializes the downloads queue + this.downloadHandlers = [] + for (let i = 0; i < this.numberOfChunksToDownload; i++) { + this.downloadHandlers.push({ + state: 'WAITING', + downloader: this.fsDownloader, + }) + } + + // Download loop + return (async () => { + let downloadFinished = false + while(!downloadFinished) { + + let usedSlots = 0 + let remainingDownloads = 0 + let firstNonCompleted = 0 + + // Scan loop: + for (let i = this.numberOfChunksToDownload - 1; i >= 0; i--) { + + let isTopChunk = i === this.resultsDeferers.length - 1 + const handler = this.downloadHandlers[i] + if (handler.state !== 'COMPLETED' && firstNonCompleted === 0) { + firstNonCompleted = i + } + if (handler.state === 'WAITING') { + // We reached a new ready slot. + // If there is no more available slot, just stop the scan loop: + if (usedSlots === this.parallelDownloads || i < firstNonCompleted - this.maxDownloadAdvance) { + remainingDownloads++ + break; + } + // Otherwise let's start a download + if (isTopChunk) { + // The top chunk is always downloaded via P2P + handler.downloader = this.p2PDownloader + } + handler.chunk = querablep(handler.downloader.getChunk(i)) + ;(handler as any).state = 'DOWNLOADING' + remainingDownloads++ + usedSlots++ + } + else if (handler.state === 'DOWNLOADING') { + if (handler.chunk.isResolved()) { + (handler as any).state = 'DOWNLOADED' + i++ // We loop back on this handler + } else if (Date.now() - handler.chunk.startedOn > this.MAX_DOWNLOAD_TIMEOUT) { + (handler as any).chunk = []; + (handler as any).state = 'DOWNLOADED' + i++ // We loop back on this handler + } else { + remainingDownloads++ + usedSlots++ + } + } + else if (handler.state === 'DOWNLOADED') { + // Chaining test: we must wait for upper chunk to be completed (= downloaded + chained) + const chunk = await handler.chunk + if (chunk.length === 0 && handler.downloader === this.fsDownloader) { + // Retry with P2P + handler.downloader = this.p2PDownloader + ;(handler as any).state = 'WAITING' + } + if (isTopChunk || this.downloadHandlers[i + 1].state === 'COMPLETED') { + const fileName = this.syncStrategy.getChunkRelativePath(i) + let promiseOfUpperChunk: PromiseOfBlocksReading = async () => [] + if (!isTopChunk && chunk.length) { + // We need to wait for upper chunk to be completed to be able to check blocks' correct chaining + promiseOfUpperChunk = await this.resultsData[i + 1] + } + const chainsWell = await chainsCorrectly(chunk, promiseOfUpperChunk, this.to, this.toHash) + if (!chainsWell) { + if (handler.downloader === this.p2PDownloader) { + if (chunk.length === 0) { + logger.error('No block was downloaded') + } + logger.warn("Chunk #%s is DOES NOT CHAIN CORRECTLY. Retrying.", i) + } + handler.downloader = this.p2PDownloader // If ever the first call does not chains well, we try using P2P + ;(handler as any).state = 'WAITING' + i++ + } else { + logger.warn("Chunk #%s read from filesystem.", i) + let doWrite = handler.downloader !== this.fsDownloader + || !(await this.writeDAL.confDAL.coreFS.exists(fileName)) + if (doWrite) { + // Store the file to avoid re-downloading + if (this.localNumber <= 0 && chunk.length === CommonConstants.CONST_BLOCKS_CHUNK) { + await this.writeDAL.confDAL.coreFS.makeTree(this.syncStrategy.getCurrency()) + const content = { blocks: chunk.map((b:any) => DBBlock.fromBlockDTO(b)) } + await this.writeDAL.confDAL.coreFS.writeJSON(fileName, content) + } + } + } + + if (chainsWell) { + + // Chunk is COMPLETE + logger.warn("Chunk #%s is COMPLETE", i) + ;(handler as any).state = 'COMPLETED' + if (!isTopChunk) { + (handler as any).chunk = undefined + } + this.downloadedChunks++ + this.watcher.downloadPercent(parseInt((this.downloadedChunks / this.numberOfChunksToDownload * 100).toFixed(0))) + // We pre-save blocks only for non-cautious sync + if (this.nocautious) { + await this.writeDAL.blockchainArchiveDAL.archive(chunk.map(b => { + const block = DBBlock.fromBlockDTO(b) + block.fork = false + return block + })) + this.writtenChunks++ + this.watcher.storagePercent(Math.round(this.writtenChunks / this.numberOfChunksToDownload * 100)); + } else { + this.watcher.storagePercent(parseInt((this.downloadedChunks / this.numberOfChunksToDownload * 100).toFixed(0))) + } + + // Returns a promise of file content + this.resultsDeferers[i].resolve(async () => { + if (isTopChunk) { + return await handler.chunk // don't return directly "chunk" as it would prevent the GC to collect it + } + let content: { blocks: BlockDTO[] } = await this.syncStrategy.readDAL.confDAL.coreFS.readJSON(fileName) + if (!content) { + // Reading from classical DAL doesn't work, maybe we are using --readfilesystem option. + content = await this.writeDAL.confDAL.coreFS.readJSON(fileName) + } + return content.blocks + }) + } + } else { + remainingDownloads++ + } + } + } + + downloadFinished = remainingDownloads === 0 + + // Wait for a download to be finished + if (!downloadFinished) { + const downloadsToWait = (this.downloadHandlers.filter(h => h.state === 'DOWNLOADING') as DownloadingState[]) + .map(h => h.chunk) + if (downloadsToWait.length) { + await Promise.race(downloadsToWait) + } + } + } + })() + } + + async getChunk(i: number): Promise<PromiseOfBlocksReading> { + const reading = this.resultsData[i] || Promise.resolve(async (): Promise<BlockDTO[]> => []) + // We don't want blocks above `to` + return async () => { + const blocks = await (await reading)() + return blocks.filter(b => b.number <= this.to) + } + } +} + +export async function chainsCorrectly(blocks:BlockDTO[], readNextChunk: PromiseOfBlocksReading, topNumber: number, topHash: string) { + + if (!blocks.length) { + return false + } + + for (let i = blocks.length - 1; i > 0; i--) { + if (blocks[i].number !== blocks[i - 1].number + 1 || blocks[i].previousHash !== blocks[i - 1].hash) { + logger.error("Blocks do not chaing correctly", blocks[i].number); + return false; + } + if (blocks[i].version != blocks[i - 1].version && blocks[i].version != blocks[i - 1].version + 1) { + logger.error("Version cannot be downgraded", blocks[i].number); + return false; + } + } + + // Check hashes + for (let i = 0; i < blocks.length; i++) { + // Note: the hash, in Duniter, is made only on the **signing part** of the block: InnerHash + Nonce + if (blocks[i].version >= 6) { + for (const tx of blocks[i].transactions) { + tx.version = CrawlerConstants.TRANSACTION_VERSION; + } + } + if (blocks[i].inner_hash !== hashf(getBlockInnerPart(blocks[i])).toUpperCase()) { + logger.error("Inner hash of block#%s from %s does not match", blocks[i].number) + return false + } + if (blocks[i].hash !== hashf(getBlockInnerHashAndNonceWithSignature(blocks[i])).toUpperCase()) { + logger.error("Hash of block#%s from %s does not match", blocks[i].number) + return false + } + } + + const lastBlockOfChunk = blocks[blocks.length - 1]; + if ((lastBlockOfChunk.number === topNumber || blocks.length < CommonConstants.CONST_BLOCKS_CHUNK) && lastBlockOfChunk.hash != topHash) { + // Top chunk + logger.error('Top block is not on the right chain') + return false + } else { + // Chaining between downloads + const previousChunk = await readNextChunk() + const blockN = blocks[blocks.length - 1] // The block n + const blockNp1 = (await previousChunk)[0] // The block n + 1 + if (blockN && blockNp1 && (blockN.number + 1 !== blockNp1.number || blockN.hash != blockNp1.previousHash)) { + logger.error('Chunk is not referenced by the upper one') + return false + } + } + return true +} diff --git a/app/modules/crawler/lib/sync/FsSyncDownloader.ts b/app/modules/crawler/lib/sync/FsSyncDownloader.ts new file mode 100644 index 0000000000000000000000000000000000000000..7078c6f88784be22295f3c31fd6d389736c06dfa --- /dev/null +++ b/app/modules/crawler/lib/sync/FsSyncDownloader.ts @@ -0,0 +1,34 @@ +import {ISyncDownloader} from "./ISyncDownloader" +import {BlockDTO} from "../../../../lib/dto/BlockDTO" +import {FileSystem} from "../../../../lib/system/directory" +import * as path from 'path' + +export class FsSyncDownloader implements ISyncDownloader { + + private ls: Promise<string[]> + + constructor( + private fs: FileSystem, + private basePath: string, + private getChunkName:(i: number) => string, + ) { + this.ls = this.fs.fsList(basePath) + } + + async getChunk(i: number): Promise<BlockDTO[]> { + const files = await this.ls + const filepath = path.join(this.basePath, this.getChunkName(i)) + const basename = path.basename(filepath) + let existsOnDAL = files.filter(f => f === basename).length === 1 + if (!existsOnDAL) { + // We make another try in case the file was created after the initial `ls` test + existsOnDAL = await this.fs.fsExists(filepath) + } + if (existsOnDAL) { + const content: any = JSON.parse(await this.fs.fsReadFile(filepath)) + // Returns a promise of file content + return content.blocks + } + return [] + } +} diff --git a/app/modules/crawler/lib/sync/ISyncDownloader.ts b/app/modules/crawler/lib/sync/ISyncDownloader.ts new file mode 100644 index 0000000000000000000000000000000000000000..e66bdf108f9111d50ba591246a6917a4118f0a0c --- /dev/null +++ b/app/modules/crawler/lib/sync/ISyncDownloader.ts @@ -0,0 +1,5 @@ +import {BlockDTO} from "../../../../lib/dto/BlockDTO" + +export interface ISyncDownloader { + getChunk(i: number): Promise<BlockDTO[]> +} diff --git a/app/modules/crawler/lib/sync/LocalPathSynchronizer.ts b/app/modules/crawler/lib/sync/LocalPathSynchronizer.ts new file mode 100644 index 0000000000000000000000000000000000000000..db76e498e80935e3c76edc0f5c235feb2413f67d --- /dev/null +++ b/app/modules/crawler/lib/sync/LocalPathSynchronizer.ts @@ -0,0 +1,114 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import {ISyncDownloader} from "./ISyncDownloader" +import {BlockDTO} from "../../../../lib/dto/BlockDTO" +import {PeerDTO} from "../../../../lib/dto/PeerDTO" +import {Watcher} from "./Watcher" +import {PeeringService} from "../../../../service/PeeringService" +import {Server} from "../../../../../server" +import {FileDAL} from "../../../../lib/dal/fileDAL" +import {FsSyncDownloader} from "./FsSyncDownloader" +import {AbstractSynchronizer} from "./AbstractSynchronizer" +import {CommonConstants} from "../../../../lib/common-libs/constants" +import {RealFS} from "../../../../lib/system/directory" + +export class LocalPathSynchronizer extends AbstractSynchronizer { + + private theP2pDownloader: ISyncDownloader + private theFsDownloader: ISyncDownloader + private currency: string + private watcher: Watcher + private ls: Promise<string[]> + + constructor( + private path: string, + private server:Server, + ) { + super() + const fs = RealFS() + this.ls = fs.fsList(path) + // We read from the real file system here, directly. + this.theFsDownloader = new FsSyncDownloader(fs, this.path, this.getChunkName.bind(this)) + this.theP2pDownloader = new FsSyncDownloader(fs, this.path, this.getChunkName.bind(this)) + } + + get dal(): FileDAL { + return this.server.dal + } + + get readDAL(): FileDAL { + return this.dal + } + + get PeeringService(): PeeringService { + return this.server.PeeringService + } + + getCurrency(): string { + return this.currency + } + + getPeer(): PeerDTO { + return this as any + } + + getChunksPath(): string { + return this.path + } + + setWatcher(watcher: Watcher): void { + this.watcher = watcher + } + + async init(): Promise<void> { + // TODO: check that path exists and that files seem consistent + } + + async initWithKnownLocalAndToAndCurrency(to: number, localNumber: number, currency: string): Promise<void> { + this.currency = currency + } + + p2pDownloader(): ISyncDownloader { + return this.theP2pDownloader + } + + fsDownloader(): ISyncDownloader { + return this.theFsDownloader + } + + async getCurrent(): Promise<BlockDTO|null> { + const chunkNumbers: number[] = (await this.ls).map(s => parseInt(s.replace(CommonConstants.CHUNK_PREFIX, ''))) + const topChunk = chunkNumbers.reduce((number, max) => Math.max(number, max), -1) + if (topChunk === -1) { + return null + } + const chunk = await this.theFsDownloader.getChunk(topChunk) + return chunk[chunk.length - 1] // This is the top block of the top chunk = the current block + } + + async getBlock(number: number): Promise<BlockDTO|null> { + const chunkNumber = parseInt(String(number / CommonConstants.CONST_BLOCKS_CHUNK)) + const position = number % CommonConstants.CONST_BLOCKS_CHUNK + const chunk = await this.theFsDownloader.getChunk(chunkNumber) + return chunk[position] + } + + async syncPeers(fullSync: boolean, to?: number): Promise<void> { + // Does nothing on LocalPathSynchronizer + } + + async syncSandbox(): Promise<void> { + // Does nothing on LocalPathSynchronizer + } +} diff --git a/app/modules/crawler/lib/sync/P2PSyncDownloader.ts b/app/modules/crawler/lib/sync/P2PSyncDownloader.ts new file mode 100644 index 0000000000000000000000000000000000000000..474973635c43248f72bdf8b3569f286d14250c4c --- /dev/null +++ b/app/modules/crawler/lib/sync/P2PSyncDownloader.ts @@ -0,0 +1,211 @@ +import {JSONDBPeer} from "../../../../lib/db/DBPeer" +import {PeerDTO} from "../../../../lib/dto/PeerDTO" +import {connect} from "../connect" +import {Underscore} from "../../../../lib/common-libs/underscore" +import {BlockDTO} from "../../../../lib/dto/BlockDTO" +import {Watcher} from "./Watcher" +import {CommonConstants} from "../../../../lib/common-libs/constants" +import {ISyncDownloader} from "./ISyncDownloader" +import {cliprogram} from "../../../../lib/common-libs/programOptions" + +const makeQuerablePromise = require('querablep'); + +export class P2PSyncDownloader implements ISyncDownloader { + + private PARALLEL_PER_CHUNK = 1; + private MAX_DELAY_PER_DOWNLOAD = cliprogram.slow ? 15000 : 5000; + private WAIT_DELAY_WHEN_MAX_DOWNLOAD_IS_REACHED = 3000; + private NO_NODES_AVAILABLE = "No node available for download"; + private TOO_LONG_TIME_DOWNLOAD:string + private nbBlocksToDownload:number + private numberOfChunksToDownload:number + private processing:any + private handler:any + private nodes:any = {} + private nbDownloadsTried = 0 + private nbDownloading = 0 + private lastAvgDelay:number + private downloads: { [chunk: number]: any } = {} + + constructor( + private localNumber:number, + private to:number, + private peers:JSONDBPeer[], + private watcher:Watcher, + private logger:any, + ) { + + this.TOO_LONG_TIME_DOWNLOAD = "No answer after " + this.MAX_DELAY_PER_DOWNLOAD + "ms, will retry download later."; + this.nbBlocksToDownload = Math.max(0, to - localNumber); + this.numberOfChunksToDownload = Math.ceil(this.nbBlocksToDownload / CommonConstants.CONST_BLOCKS_CHUNK); + this.processing = Array.from({ length: this.numberOfChunksToDownload }).map(() => false); + this.handler = Array.from({ length: this.numberOfChunksToDownload }).map(() => null); + + // Create slots of download, in a ready stage + this.lastAvgDelay = this.MAX_DELAY_PER_DOWNLOAD; + } + + /** + * Get a list of P2P nodes to use for download. + * If a node is not yet correctly initialized (we can test a node before considering it good for downloading), then + * this method would not return it. + */ + private async getP2Pcandidates(): Promise<any[]> { + let promises = this.peers.reduce((chosens:any, other:any, index:number) => { + if (!this.nodes[index]) { + // Create the node + let p = PeerDTO.fromJSONObject(this.peers[index]); + this.nodes[index] = makeQuerablePromise((async () => { + // We wait for the download process to be triggered + // await downloadStarter; + // if (nodes[index - 1]) { + // try { await nodes[index - 1]; } catch (e) {} + // } + const node:any = await connect(p) + // We initialize nodes with the near worth possible notation + node.tta = 1; + node.nbSuccess = 0; + if (node.host.match(/^(localhost|192|127)/)) { + node.tta = this.MAX_DELAY_PER_DOWNLOAD + } + return node; + })()) + chosens.push(this.nodes[index]); + } else { + chosens.push(this.nodes[index]); + } + // Continue + return chosens; + }, []); + let candidates:any[] = await Promise.all(promises) + candidates.forEach((c:any) => { + c.tta = c.tta || 0; // By default we say a node is super slow to answer + c.ttas = c.ttas || []; // Memorize the answer delays + }); + if (candidates.length === 0) { + throw this.NO_NODES_AVAILABLE; + } + // We remove the nodes impossible to reach (timeout) + let withGoodDelays = Underscore.filter(candidates, (c:any) => c.tta <= this.MAX_DELAY_PER_DOWNLOAD && !c.excluded && !c.downloading); + if (withGoodDelays.length === 0) { + await new Promise(res => setTimeout(res, this.WAIT_DELAY_WHEN_MAX_DOWNLOAD_IS_REACHED)) // We wait a bit before continuing the downloads + // We reinitialize the nodes + this.nodes = {}; + // And try it all again + return this.getP2Pcandidates(); + } + const parallelMax = Math.min(this.PARALLEL_PER_CHUNK, withGoodDelays.length); + withGoodDelays = Underscore.sortBy(withGoodDelays, (c:any) => c.tta); + withGoodDelays = withGoodDelays.slice(0, parallelMax); + // We temporarily augment the tta to avoid asking several times to the same node in parallel + withGoodDelays.forEach((c:any) => c.tta = this.MAX_DELAY_PER_DOWNLOAD); + return withGoodDelays; + } + + /** + * Download a chunk of blocks using P2P network through BMA API. + * @param from The starting block to download + * @param count The number of blocks to download. + * @param chunkIndex The # of the chunk in local algorithm (logging purposes only) + */ + private async p2pDownload(from:number, count:number, chunkIndex:number) { + // if this chunk has already been downloaded before, we exclude its supplier node from the download list as it won't give correct answer now + const lastSupplier = this.downloads[chunkIndex] + if (lastSupplier) { + lastSupplier.excluded = true + this.logger.warn('Excluding node %s as it returns unchainable chunks', [lastSupplier.host, lastSupplier.port].join(':')) + } + let candidates = await this.getP2Pcandidates(); + // Book the nodes + return await this.raceOrCancelIfTimeout(this.MAX_DELAY_PER_DOWNLOAD, candidates.map(async (node:any) => { + try { + const start = Date.now(); + this.handler[chunkIndex] = node; + node.downloading = true; + this.nbDownloading++; + this.watcher.writeStatus('Getting chunck #' + chunkIndex + '/' + (this.numberOfChunksToDownload - 1) + ' from ' + from + ' to ' + (from + count - 1) + ' on peer ' + [node.host, node.port].join(':')); + let blocks = await node.getBlocks(count, from); + node.ttas.push(Date.now() - start); + // Only keep a flow of 5 ttas for the node + if (node.ttas.length > 5) node.ttas.shift(); + // Average time to answer + node.tta = Math.round(node.ttas.reduce((sum:number, tta:number) => sum + tta, 0) / node.ttas.length); + this.watcher.writeStatus('GOT chunck #' + chunkIndex + '/' + (this.numberOfChunksToDownload - 1) + ' from ' + from + ' to ' + (from + count - 1) + ' on peer ' + [node.host, node.port].join(':')); + if (this.PARALLEL_PER_CHUNK === 1) { + // Only works if we have 1 concurrent peer per chunk + this.downloads[chunkIndex] = node + } + node.nbSuccess++; + + const peers = await Promise.all(Underscore.values(this.nodes)) + const downloading = Underscore.filter(peers, (p:any) => p.downloading && p.ttas.length); + this.lastAvgDelay = downloading.reduce((sum:number, c:any) => { + const tta = Math.round(c.ttas.reduce((sum:number, tta:number) => sum + tta, 0) / c.ttas.length) + return sum + tta + }, 0) / downloading.length + + this.nbDownloadsTried++; + this.nbDownloading--; + node.downloading = false; + + return blocks; + } catch (e) { + this.nbDownloading--; + node.downloading = false; + this.nbDownloadsTried++; + node.ttas.push(this.MAX_DELAY_PER_DOWNLOAD + 1); // No more ask on this node + // Average time to answer + node.tta = Math.round(node.ttas.reduce((sum:number, tta:number) => sum + tta, 0) / node.ttas.length); + throw e; + } + })) + } + + /** + * Function for downloading a chunk by its number. + * @param index Number of the chunk. + */ + private async downloadChunk(index:number): Promise<BlockDTO[]> { + // The algorithm to download a chunk + const from = this.localNumber + 1 + index * CommonConstants.CONST_BLOCKS_CHUNK; + let count = CommonConstants.CONST_BLOCKS_CHUNK; + if (index == this.numberOfChunksToDownload - 1) { + count = this.nbBlocksToDownload % CommonConstants.CONST_BLOCKS_CHUNK || CommonConstants.CONST_BLOCKS_CHUNK; + } + try { + return await this.p2pDownload(from, count, index) as BlockDTO[] + } catch (e) { + this.logger.error(e); + return this.downloadChunk(index); + } + } + + /** + * Utility function this starts a race between promises but cancels it if no answer is found before `timeout` + * @param timeout + * @param races + * @returns {Promise} + */ + private raceOrCancelIfTimeout(timeout:number, races:any[]) { + return Promise.race([ + // Process the race, but cancel it if we don't get an anwser quickly enough + new Promise((resolve, reject) => { + setTimeout(() => { + reject(this.TOO_LONG_TIME_DOWNLOAD); + }, timeout) + }) + ].concat(races)); + }; + + /** + * PUBLIC API + */ + + /*** + * Promises a chunk to be downloaded and returned + * @param index The number of the chunk to download & return + */ + getChunk(index:number): Promise<BlockDTO[]> { + return this.downloadChunk(index) + } +} diff --git a/app/modules/crawler/lib/sync/PromiseOfBlockReading.ts b/app/modules/crawler/lib/sync/PromiseOfBlockReading.ts new file mode 100644 index 0000000000000000000000000000000000000000..8b35249ada4daf2fad0b1660bfcb42cb1c463204 --- /dev/null +++ b/app/modules/crawler/lib/sync/PromiseOfBlockReading.ts @@ -0,0 +1,5 @@ +import {BlockDTO} from "../../../../lib/dto/BlockDTO" + +export interface PromiseOfBlocksReading { + (): Promise<BlockDTO[]> +} diff --git a/app/modules/crawler/lib/sync/RemoteSynchronizer.ts b/app/modules/crawler/lib/sync/RemoteSynchronizer.ts new file mode 100644 index 0000000000000000000000000000000000000000..8113ec42d4c6a0fcd9ca2ad123e9f0c22e7aaa68 --- /dev/null +++ b/app/modules/crawler/lib/sync/RemoteSynchronizer.ts @@ -0,0 +1,294 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import {ISyncDownloader} from "./ISyncDownloader" +import {BlockDTO} from "../../../../lib/dto/BlockDTO" +import {PeerDTO} from "../../../../lib/dto/PeerDTO" +import {Contacter} from "../contacter" +import {connect} from "../connect" +import {NewLogger} from "../../../../lib/logger" +import {CrawlerConstants} from "../constants" +import {HttpMerkleOfPeers} from "../../../bma/lib/dtos" +import {cliprogram} from "../../../../lib/common-libs/programOptions" +import {Watcher} from "./Watcher" +import {dos2unix} from "../../../../lib/common-libs/dos2unix" +import {PeeringService} from "../../../../service/PeeringService" +import {Server} from "../../../../../server" +import {DBPeer, JSONDBPeer} from "../../../../lib/db/DBPeer" +import {Underscore} from "../../../../lib/common-libs/underscore" +import {FileDAL} from "../../../../lib/dal/fileDAL" +import {P2PSyncDownloader} from "./P2PSyncDownloader" +import {FsSyncDownloader} from "./FsSyncDownloader" +import {AbstractSynchronizer} from "./AbstractSynchronizer" +import {pullSandboxToLocalServer} from "../sandbox" +import * as path from 'path' + +const logger = NewLogger() + +export class RemoteSynchronizer extends AbstractSynchronizer { + + private node:Contacter + private peer:PeerDTO + private shuffledPeers: JSONDBPeer[] + private theP2pDownloader: ISyncDownloader + private theFsDownloader: ISyncDownloader + private to: number + private localNumber: number + private currency: string + private watcher: Watcher + private static contacterOptions = { + timeout: CrawlerConstants.SYNC_LONG_TIMEOUT + } + + constructor( + private host: string, + private port: number, + private server:Server, + private noShufflePeers = false, + private otherDAL?:FileDAL, + ) { + super() + } + + get dal(): FileDAL { + return this.server.dal + } + + get readDAL(): FileDAL { + return this.otherDAL || this.dal + } + + get PeeringService(): PeeringService { + return this.server.PeeringService + } + + getCurrency(): string { + return this.currency + } + + getPeer(): PeerDTO { + return this.node as any + } + + setWatcher(watcher: Watcher): void { + this.watcher = watcher + } + + getChunksPath(): string { + return this.getCurrency() + } + + async init(): Promise<void> { + const peering = await Contacter.fetchPeer(this.host, this.port, RemoteSynchronizer.contacterOptions) + this.peer = PeerDTO.fromJSONObject(peering) + logger.info("Try with %s %s", this.peer.getURL(), this.peer.pubkey.substr(0, 6)) + this.node = await connect(this.peer) + ;(this.node as any).pubkey = this.peer.pubkey + this.watcher.writeStatus('Connecting to ' + this.host + '...') + } + + async initWithKnownLocalAndToAndCurrency(to: number, localNumber: number, currency: string): Promise<void> { + this.to = to + this.localNumber = localNumber + this.currency = currency + //======= + // Peers (just for P2P download) + //======= + let peers:(JSONDBPeer|null)[] = []; + if (!cliprogram.nopeers && (to - localNumber > 1000)) { // P2P download if more than 1000 blocs + this.watcher.writeStatus('Peers...'); + const merkle = await this.dal.merkleForPeers(); + const getPeers:(params:any) => Promise<HttpMerkleOfPeers> = this.node.getPeers.bind(this.node); + const json2 = await getPeers({}); + const rm = new NodesMerkle(json2); + if(rm.root() != merkle.root()){ + const leavesToAdd:string[] = []; + const json = await getPeers({ leaves: true }); + json.leaves.forEach((leaf:string) => { + if(merkle.leaves().indexOf(leaf) == -1){ + leavesToAdd.push(leaf); + } + }); + peers = await Promise.all(leavesToAdd.map(async (leaf) => { + try { + const json3 = await getPeers({ "leaf": leaf }); + const jsonEntry = json3.leaf.value; + const endpoint = jsonEntry.endpoints[0]; + this.watcher.writeStatus('Peer ' + endpoint); + return jsonEntry; + } catch (e) { + logger.warn("Could not get peer of leaf %s, continue...", leaf); + return null; + } + })) + } + else { + this.watcher.writeStatus('Peers already known'); + } + } + + if (!peers.length) { + peers.push(DBPeer.fromPeerDTO(this.peer)) + } + peers = peers.filter((p) => p); + this.shuffledPeers = (this.noShufflePeers ? peers : Underscore.shuffle(peers)).filter(p => !!(p)) as JSONDBPeer[] + } + + p2pDownloader(): ISyncDownloader { + if (!this.theP2pDownloader) { + this.theP2pDownloader = new P2PSyncDownloader(this.localNumber, this.to, this.shuffledPeers, this.watcher, logger) + } + return this.theP2pDownloader + } + + fsDownloader(): ISyncDownloader { + if (!this.theFsDownloader) { + this.theFsDownloader = new FsSyncDownloader(this.readDAL.fs, path.join(this.readDAL.rootPath, this.getChunksPath()), this.getChunkName.bind(this)) + } + return this.theFsDownloader + } + + getCurrent(): Promise<BlockDTO|null> { + return this.node.getCurrent() + } + + getBlock(number: number): Promise<BlockDTO|null> { + return this.node.getBlock(number) + } + + static async test(host: string, port: number): Promise<BlockDTO> { + const peering = await Contacter.fetchPeer(host, port, this.contacterOptions); + const node = await connect(PeerDTO.fromJSONObject(peering)); + return node.getCurrent() + } + + async syncPeers(fullSync: boolean, to?: number): Promise<void> { + if (!cliprogram.nopeers && fullSync) { + + const peering = await Contacter.fetchPeer(this.host, this.port, RemoteSynchronizer.contacterOptions); + + let peer = PeerDTO.fromJSONObject(peering); + logger.info("Try with %s %s", peer.getURL(), peer.pubkey.substr(0, 6)); + let node:any = await connect(peer); + node.pubkey = peer.pubkey; + logger.info('Sync started.'); + + this.watcher.writeStatus('Peers...'); + await this.syncPeer(node); + const merkle = await this.dal.merkleForPeers(); + const getPeers:(params:any) => Promise<HttpMerkleOfPeers> = node.getPeers.bind(node); + const json2 = await getPeers({}); + const rm = new NodesMerkle(json2); + if(rm.root() != merkle.root()){ + const leavesToAdd:string[] = []; + const json = await getPeers({ leaves: true }); + json.leaves.forEach((leaf:string) => { + if(merkle.leaves().indexOf(leaf) == -1){ + leavesToAdd.push(leaf); + } + }); + for (let i = 0; i < leavesToAdd.length; i++) { + try { + const leaf = leavesToAdd[i] + const json3 = await getPeers({ "leaf": leaf }); + const jsonEntry = json3.leaf.value; + const sign = json3.leaf.value.signature; + const entry:any = {}; + entry.version = jsonEntry.version + entry.currency = jsonEntry.currency + entry.pubkey = jsonEntry.pubkey + entry.endpoints = jsonEntry.endpoints + entry.block = jsonEntry.block + entry.signature = sign; + this.watcher.writeStatus('Peer ' + entry.pubkey); + this.watcher.peersPercent((i + 1) / leavesToAdd.length * 100) + await this.PeeringService.submitP(entry, false, to === undefined); + } catch (e) { + logger.warn(e && e.message || e) + } + } + this.watcher.peersPercent(100) + } + else { + this.watcher.writeStatus('Peers already known'); + } + } + } + + //============ + // Peer + //============ + private async syncPeer (node:any) { + + // Global sync vars + const remotePeer = PeerDTO.fromJSONObject({}); + const json = await node.getPeer(); + remotePeer.version = json.version + remotePeer.currency = json.currency + remotePeer.pubkey = json.pub + remotePeer.endpoints = json.endpoints + remotePeer.blockstamp = json.block + remotePeer.signature = json.signature + const entry = remotePeer.getRawUnsigned(); + const signature = dos2unix(remotePeer.signature); + // Parameters + if(!(entry && signature)){ + throw 'Requires a peering entry + signature'; + } + + let remoteJsonPeer:any = json + remoteJsonPeer.pubkey = json.pubkey; + let signatureOK = this.PeeringService.checkPeerSignature(remoteJsonPeer); + if (!signatureOK) { + this.watcher.writeStatus('Wrong signature for peer #' + remoteJsonPeer.pubkey); + } + try { + await this.PeeringService.submitP(remoteJsonPeer); + } catch (err) { + if (err.indexOf !== undefined && err.indexOf(CrawlerConstants.ERRORS.NEWER_PEER_DOCUMENT_AVAILABLE.uerr.message) !== -1 && err != CrawlerConstants.ERROR.PEER.UNKNOWN_REFERENCE_BLOCK) { + throw err; + } + } + } + + async syncSandbox(): Promise<void> { + this.watcher.writeStatus('Synchronizing the sandboxes...'); + await pullSandboxToLocalServer(this.currency, this.node, this.server, this.server.logger, this.watcher, 1, false) + } +} + +class NodesMerkle { + + private depth:number + private nodesCount:number + private leavesCount:number + private merkleRoot:string + + constructor(json:any) { + this.depth = json.depth + this.nodesCount = json.nodesCount + this.leavesCount = json.leavesCount + this.merkleRoot = json.root; + } + + // var i = 0; + // this.levels = []; + // while(json && json.levels[i]){ + // this.levels.push(json.levels[i]); + // i++; + // } + + root() { + return this.merkleRoot + } +} diff --git a/app/modules/crawler/lib/sync/Watcher.ts b/app/modules/crawler/lib/sync/Watcher.ts new file mode 100644 index 0000000000000000000000000000000000000000..de7a27db35a5d0226e70ac81e24b71b05d051b7a --- /dev/null +++ b/app/modules/crawler/lib/sync/Watcher.ts @@ -0,0 +1,224 @@ +import * as events from "events" +import {cliprogram} from "../../../../lib/common-libs/programOptions" + +const multimeter = require('multimeter') + +export interface Watcher { + writeStatus(str: string): void + downloadPercent(pct?: number): number + storagePercent(pct?: number): number + appliedPercent(pct?: number): number + sbxPercent(pct?: number): number + peersPercent(pct?: number): number + end(): void +} + +export type EventName = 'downloadChange'|'storageChange'|'appliedChange'|'sbxChange'|'peersChange' + +export class EventWatcher extends events.EventEmitter implements Watcher { + + constructor(private innerWatcher:Watcher) { + super() + } + + writeStatus(str: string): void { + this.innerWatcher.writeStatus(str) + } + + downloadPercent(pct?: number): number { + return this.change('downloadChange', (pct) => this.innerWatcher.downloadPercent(pct), pct) + } + + storagePercent(pct?: number): number { + return this.change('storageChange', (pct) => this.innerWatcher.storagePercent(pct), pct) + } + + appliedPercent(pct?: number): number { + return this.change('appliedChange', (pct) => this.innerWatcher.appliedPercent(pct), pct) + } + + sbxPercent(pct?: number): number { + return this.change('sbxChange', (pct) => this.innerWatcher.sbxPercent(pct), pct) + } + + peersPercent(pct?: number): number { + return this.change('peersChange', (pct) => this.innerWatcher.peersPercent(pct), pct) + } + + change(changeName: EventName, method: (pct?: number) => number, pct?: number) { + if (pct !== undefined && method() < pct) { + this.emit(changeName, pct || 0) + } + return method(pct) + } + + end(): void { + this.innerWatcher.end() + } + + onEvent(e: EventName, cb: (pct: number) => void) { + this.on(e, cb) + } +} + +export class MultimeterWatcher implements Watcher { + + private xPos:number + private yPos:number + private multi:any + private charm:any + private appliedBar:any + private savedBar:any + private downloadBar:any + private sbxBar:any + private peersBar:any + private writtens:string[] = [] + + constructor() { + this.multi = multimeter(process); + this.charm = this.multi.charm; + this.charm.on('^C', process.exit); + this.charm.reset(); + + this.multi.write('Progress:\n\n'); + + let line = 3 + this.downloadBar = this.createBar('Download', line++) + this.savedBar = this.createBar('Storage', line++) + this.appliedBar = this.createBar('Apply', line++) + if (!cliprogram.nosbx) { + this.sbxBar = this.createBar('Sandbox', line++) + } + if (!cliprogram.nopeers) { + this.peersBar = this.createBar('Peers', line++) + } + + this.multi.write('\nStatus: '); + + this.charm.position( (x:number, y:number) => { + this.xPos = x; + this.yPos = y; + }); + + this.writtens = []; + + this.downloadBar.percent(0); + this.savedBar.percent(0); + this.appliedBar.percent(0); + if (!cliprogram.nosbx) { + this.sbxBar.percent(0); + } + if (!cliprogram.nopeers) { + this.peersBar.percent(0); + } + } + + writeStatus(str:string) { + this.writtens.push(str); + this.charm + .position(this.xPos, this.yPos) + .erase('end') + .write(str) + ; + }; + + downloadPercent(pct:number) { + return this.downloadBar.percent(pct) + } + + storagePercent(pct:number) { + return this.savedBar.percent(pct) + } + + appliedPercent(pct:number) { + return this.appliedBar.percent(pct) + } + + sbxPercent(pct:number) { + if (!cliprogram.nosbx) { + return this.sbxBar.percent(pct) + } + return 0 + } + + peersPercent(pct:number) { + if (!cliprogram.nopeers) { + return this.peersBar.percent(pct) + } + return 0 + } + + end() { + this.multi.write('\nAll done.\n'); + this.multi.destroy(); + } + + private createBar(title: string, line: number) { + const header = (title + ':').padEnd(14, ' ') + '\n' + this.multi.write(header) + return this.multi(header.length, line, { + width : 20, + solid : { + text : '|', + foreground : 'white', + background : 'blue' + }, + empty : { text : ' ' } + }) + } +} + +export class LoggerWatcher implements Watcher { + + private downPct = 0 + private savedPct = 0 + private appliedPct = 0 + private lastMsg = "" + + constructor(private logger:any) { + } + + showProgress() { + return this.logger.info('Downloaded %s%, Blockchained %s%, Applied %s%', this.downPct, this.savedPct, this.appliedPct) + } + + writeStatus(str:string) { + if (str != this.lastMsg) { + this.lastMsg = str; + this.logger.info(str); + } + } + + downloadPercent(pct:number) { + return this.change('downPct', pct) + } + + storagePercent(pct:number) { + return this.change('savedPct', pct) + } + + appliedPercent(pct:number) { + return this.change('appliedPct', pct) + } + + sbxPercent(pct:number) { + return 0 + } + + peersPercent(pct:number) { + return 0 + } + + change(prop: 'downPct'|'savedPct'|'appliedPct', pct:number) { + if (pct !== undefined) { + let changed = pct > this[prop] + this[prop] = pct + if (changed) this.showProgress() + } + return this[prop] + } + + end() { + } + +} diff --git a/app/modules/daemon.ts b/app/modules/daemon.ts index 628107e6914f112f899d06e579b04861289156f2..9fbff1423b4dec5f7cf07837b41b026a0ea39cc8 100644 --- a/app/modules/daemon.ts +++ b/app/modules/daemon.ts @@ -13,20 +13,17 @@ import {ConfDTO} from "../lib/dto/ConfDTO" import {Server} from "../../server" +import {Directory, RealFS} from "../lib/system/directory" -"use strict"; - -const qfs = require('q-io/fs'); -const directory = require('../lib/system/directory'); const constants = require('../lib/constants'); -const path = require('path'); const Tail = require("tail").Tail module.exports = { duniter: { cliOptions: [ - { value: '--loglevel <level>', desc: 'Logs level, either [error,warning,info,debug,trace]. default to `info`.' } + { value: '--loglevel <level>', desc: 'Logs level, either [error,warning,info,debug,trace]. default to `info`.' }, + { value: '--sql-traces', desc: 'Will log every SQL query that is executed. Requires --loglevel \'trace\'.' } ], service: { @@ -95,7 +92,7 @@ module.exports = { desc: 'Follow duniter logs.', logs: false, onConfiguredExecute: async (server:Server, conf:ConfDTO, program:any, params:any) => { - printTailAndWatchFile(directory.INSTANCE_HOMELOG_FILE, constants.NB_INITIAL_LINES_TO_SHOW) + printTailAndWatchFile(Directory.INSTANCE_HOMELOG_FILE, constants.NB_INITIAL_LINES_TO_SHOW) // Never ending command return new Promise(res => null) } @@ -149,8 +146,9 @@ function stopDaemon(daemon:any) { } async function printTailAndWatchFile(file:any, tailSize:number) { - if (await qfs.exists(file)) { - const content = await qfs.read(file) + const fs = RealFS() + if (await fs.fsExists(file)) { + const content = await fs.fsReadFile(file) const lines = content.split('\n') const from = Math.max(0, lines.length - tailSize) const lastLines = lines.slice(from).join('\n') diff --git a/app/modules/dump.ts b/app/modules/dump.ts new file mode 100644 index 0000000000000000000000000000000000000000..094e35a74651ae6905b1f230148523dd2e070066 --- /dev/null +++ b/app/modules/dump.ts @@ -0,0 +1,203 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import {ConfDTO} from "../lib/dto/ConfDTO" +import {Server} from "../../server" +import {moment} from "../lib/common-libs/moment" +import {DBBlock} from "../lib/db/DBBlock" +import {SindexEntry} from "../lib/indexer" +import {BlockDTO} from "../lib/dto/BlockDTO" + +const Table = require('cli-table') + +module.exports = { + duniter: { + cli: [{ + name: 'dump [what] [name] [cond]', + desc: 'Dumps data of the blockchain.', + logs: false, + preventIfRunning: true, + + onDatabaseExecute: async (server:Server, conf:ConfDTO, program:any, params:any) => { + const what: string = params[0] || '' + const name: string = params[1] || '' + const cond: string = params[2] || '' + switch (what) { + + case 'current': + await dumpCurrent(server) + break + + case 'table': + await dumpTable(server, name, cond) + break + + case 'history': + await dumpHistory(server, name) + break + + default: + console.error(`Unknown dump ${what}`) + break + } + // Save DB + await server.disconnect(); + } + }] + } +} + +async function dumpCurrent(server: Server) { + const current = await server.dal.getCurrentBlockOrNull() + if (!current) { + console.log('') + } + else { + console.log(BlockDTO.fromJSONObject(current).getRawSigned()) + } +} + +async function dumpTable(server: Server, name: string, condition?: string) { + const criterion: any = {} + const filters = condition && condition.split(',') || [] + for (const f of filters) { + const k = f.split('=')[0] + const v = f.split('=')[1] + if (v === 'true' || v === 'false') { + criterion[k] = v === 'true' ? true : 0 + } else if (v === 'NULL') { + criterion[k] = null + } else if (v.match(/^\d+$/)) { + criterion[k] = parseInt(v) + } else { + criterion[k] = v + } + } + let rows: any[] + switch (name) { + case 'i_index': + rows = await server.dal.iindexDAL.findRawWithOrder(criterion, [['writtenOn', false], ['wotb_id', false]]) + dump(rows, ['op','uid','pub','hash','sig','created_on','written_on','member','wasMember','kick','wotb_id']) + break + case 'm_index': + rows = await server.dal.mindexDAL.findRawWithOrder(criterion, [['writtenOn', false], ['pub', false]]) + dump(rows, ['op','pub','created_on','written_on','expires_on','expired_on','revokes_on','revoked_on','leaving','revocation','chainable_on']) + break + case 'c_index': + rows = await server.dal.cindexDAL.findRawWithOrder(criterion, [['writtenOn', false], ['issuer', false], ['receiver', false]]) + dump(rows, ['op','issuer','receiver','created_on','written_on','sig','expires_on','expired_on','chainable_on','from_wid','to_wid']) + break + case 's_index': + const rowsTX = await server.dal.sindexDAL.findRawWithOrder(criterion, [['writtenOn', false], ['identifier', false], ['pos', false]]) + const rowsUD = await server.dal.dividendDAL.findForDump(criterion) + rows = rowsTX.concat(rowsUD) + sortSindex(rows) + dump(rows, ['op','tx','identifier','pos','created_on','amount','base','locktime','consumed','conditions', 'writtenOn']) + break + default: + console.error(`Unknown dump table ${name}`) + break + } +} + +function dump(rows: any[], columns: string[]) { + // Table columns + const t = new Table({ + head: columns + }); + for (const row of rows) { + t.push(columns.map((c) => { + if (row[c] === null) { + return "NULL" + } + else if (row[c] === undefined) { + return 'NULL' + } + else if (typeof row[c] === 'boolean') { + return row[c] ? 1 : 0 + } + return row[c] + })); + } + try { + const dumped = t.toString() + console.log(dumped) + } catch (e) { + console.error(e) + } +} + +async function dumpHistory(server: Server, pub: string) { + const irows = await server.dal.iindexDAL.findRawWithOrder({ pub }, [['writtenOn', false]]) + const mrows = await server.dal.mindexDAL.findRawWithOrder({ pub }, [['writtenOn', false]]) + console.log('----- IDENTITY -----') + for (const e of irows) { + const date = await getDateFor(server, e.written_on) + if (e.uid) { + console.log('%s: new identity %s (created on %s)', date, e.uid, await getDateFor(server, e.created_on as string)) + } else if (e.member) { + console.log('%s: comeback', date) + } else if (e.kick) { + // console.log('%s: being kicked... (either)', date) + } else if (e.member === false) { + console.log('%s: excluded', date) + } else { + console.log('Non displayable IINDEX entry') + } + } + console.log('----- MEMBERSHIP -----') + for (const e of mrows) { + const date = await getDateFor(server, e.written_on) + if (e.chainable_on) { + console.log('%s: join/renew', date) + } else if (e.expired_on) { + console.log('%s: expired', date) + } else if (e.revoked_on) { + console.log('%s: revoked', date) + } else { + console.log('Non displayable MINDEX entry') + } + } +} + +async function getDateFor(server: Server, blockstamp: string) { + const b = (await server.dal.getAbsoluteBlockByBlockstamp(blockstamp)) as DBBlock + const s = " " + b.number + const bnumberPadded = s.substr(s.length - 6) + return formatTimestamp(b.medianTime) + ' (#' + bnumberPadded + ')' +} + +function formatTimestamp(ts: number) { + return moment(ts * 1000).format('YYYY-MM-DD hh:mm:ss') +} + +function sortSindex(rows: SindexEntry[]) { + // We sort by writtenOn, identifier, pos + rows.sort((a, b) => { + if (a.writtenOn === b.writtenOn) { + if (a.identifier === b.identifier) { + if (a.pos === b.pos) { + return a.op === 'CREATE' && b.op === 'UPDATE' ? -1 : (a.op === 'UPDATE' && b.op === 'CREATE' ? 1 : 0) + } else { + return a.pos < b.pos ? -1 : 1 + } + } + else { + return a.identifier < b.identifier ? -1 : 1 + } + } + else { + return a.writtenOn < b.writtenOn ? -1 : 1 + } + }) +} \ No newline at end of file diff --git a/app/modules/export-bc.ts b/app/modules/export-bc.ts index 2deb97a2ad1186a798ddf5bee0d3d44629b079e1..1a0de70cbdefa1b29ac33335d5680eb11eebc1c1 100644 --- a/app/modules/export-bc.ts +++ b/app/modules/export-bc.ts @@ -11,13 +11,10 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; import {ConfDTO} from "../lib/dto/ConfDTO" import {Server} from "../../server" import {BlockDTO} from "../lib/dto/BlockDTO" -const _ = require('underscore'); - module.exports = { duniter: { cli: [{ @@ -48,7 +45,7 @@ module.exports = { for (const chunk of chunks) { let blocks = await server.dal.getBlocksBetween(chunk.start, chunk.to); blocks.forEach(function (block:any) { - jsoned.push(_(BlockDTO.fromJSONObject(block).json()).omit('raw')); + jsoned.push(BlockDTO.fromJSONObject(block).json()) }); } if (!program.nostdout) { diff --git a/app/modules/prover/index.ts b/app/modules/prover/index.ts index 6bf5ceef05f3db500be5391a32b155790374dbce..a33a84d4ecdc48dcdc06f08ea1dee15689b0475d 100644 --- a/app/modules/prover/index.ts +++ b/app/modules/prover/index.ts @@ -21,6 +21,7 @@ import {parsers} from "../../lib/common-libs/parsers/index" import {PeerDTO} from "../../lib/dto/PeerDTO" import {Server} from "../../../server" import {BlockDTO} from "../../lib/dto/BlockDTO" +import {DBIdentity} from "../../lib/dal/sqliteDAL/IdentityDAL" const async = require('async'); @@ -68,7 +69,7 @@ export const ProverDependency = { server.generatorComputeNewCerts = generator.computeNewCerts.bind(generator) server.generatorNewCertsToLinks = generator.newCertsToLinks.bind(generator) }, - prover: (server:Server, conf:ConfDTO, logger:any) => new Prover(server), + prover: (server:Server) => new Prover(server), blockGenerator: (server:Server, prover:any) => new BlockGeneratorWhichProves(server, prover), generateTheNextBlock: async (server:Server, manualValues:any) => { const prover = new BlockProver(server); @@ -109,7 +110,7 @@ export const ProverDependency = { onDatabaseExecute: async (server:Server, conf:ConfDTO, program:any, params:any) => { const difficulty = params[0] const generator = new BlockGeneratorWhichProves(server, null); - let toDelete, catched = true; + let toDelete:DBIdentity[] = [], catched = true; do { try { await generateAndSend(program, difficulty, server, () => () => generator.nextBlock()) diff --git a/app/modules/prover/lib/PowWorker.ts b/app/modules/prover/lib/PowWorker.ts index 5d74ba256bf1a6b74ddf4c9fc4dcd5a58f60bbbc..4c6df8ce83143d23cafb5e64c2b8b822d32e246a 100644 --- a/app/modules/prover/lib/PowWorker.ts +++ b/app/modules/prover/lib/PowWorker.ts @@ -11,7 +11,7 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -import {Querable} from "./permanentProver" +import {Querable} from "../../../lib/common-libs/querable" const querablep = require('querablep') diff --git a/app/modules/prover/lib/blockGenerator.ts b/app/modules/prover/lib/blockGenerator.ts index 0608a1b0291cf4a55bfdd239770aa9d9e374e265..f5520d68f47ee71872c2fd0d811eb78c90edaf71 100644 --- a/app/modules/prover/lib/blockGenerator.ts +++ b/app/modules/prover/lib/blockGenerator.ts @@ -11,15 +11,13 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; -import {ConfDTO} from "../../../lib/dto/ConfDTO" +import * as moment from "moment" import {Server} from "../../../../server" import {BlockchainContext} from "../../../lib/computation/BlockchainContext" import {TransactionDTO} from "../../../lib/dto/TransactionDTO" import {GLOBAL_RULES_HELPERS} from "../../../lib/rules/global_rules" import {LOCAL_RULES_HELPERS} from "../../../lib/rules/local_rules" import {Indexer} from "../../../lib/indexer" -import {FileDAL} from "../../../lib/dal/fileDAL" import {DBBlock} from "../../../lib/db/DBBlock" import {verify} from "../../../lib/common-libs/crypto/keyring" import {rawer} from "../../../lib/common-libs/index" @@ -29,31 +27,64 @@ import {IdentityDTO} from "../../../lib/dto/IdentityDTO" import {CertificationDTO} from "../../../lib/dto/CertificationDTO" import {MembershipDTO} from "../../../lib/dto/MembershipDTO" import {BlockDTO} from "../../../lib/dto/BlockDTO" +import {ConfDTO} from "../../../lib/dto/ConfDTO" +import {FileDAL} from "../../../lib/dal/fileDAL" +import {DataErrors} from "../../../lib/common-libs/errors" +import {Underscore} from "../../../lib/common-libs/underscore" +import {DBCert} from "../../../lib/dal/sqliteDAL/CertDAL" +import {Map} from "../../../lib/common-libs/crypto/map" -const _ = require('underscore'); -const moment = require('moment'); const inquirer = require('inquirer'); const constants = CommonConstants +export interface PreJoin { + identity: { + pubkey: string + uid: string + buid: string + sig: string + member: boolean + wasMember: boolean + revoked: boolean + } + key: null + idHash: string + certs: DBCert[] + ms: any +} + +interface LeaveData { + identity: { + member: boolean + } | null + ms: any + key: any + idHash: string +} + export class BlockGenerator { - conf:ConfDTO - dal:any mainContext:BlockchainContext selfPubkey:string logger:any constructor(private server:Server) { - this.conf = server.conf; - this.dal = server.dal; this.mainContext = server.BlockchainService.getContext(); this.selfPubkey = (this.conf.pair && this.conf.pair.pub) || '' this.logger = server.logger; } + get conf(): ConfDTO { + return this.server.conf + } + + get dal(): FileDAL { + return this.server.dal + } + nextBlock(manualValues:any = {}, simulationValues:any = {}) { - return this.generateNextBlock(new NextBlockGenerator(this.mainContext, this.conf, this.dal, this.logger), manualValues, simulationValues) + return this.generateNextBlock(new NextBlockGenerator(this.mainContext, this.server, this.logger), manualValues, simulationValues) } async manualRoot() { @@ -77,47 +108,31 @@ export class BlockGenerator { const exclusions = await this.dal.getToBeKickedPubkeys(); const wereExcludeds = await this.dal.getRevokedPubkeys(); const newCertsFromWoT = await generator.findNewCertsFromWoT(current); - const newcomersLeavers = await this.findNewcomersAndLeavers(current, (joinersData:any) => generator.filterJoiners(joinersData)); + const newcomers = await this.findNewcomers(current, joinersData => generator.filterJoiners(joinersData)) + const leavers = await this.findLeavers(current) const transactions = await this.findTransactions(current, manualValues); - const joinData = newcomersLeavers[2]; - const leaveData = newcomersLeavers[3]; - const newCertsFromNewcomers = newcomersLeavers[4]; - const certifiersOfNewcomers = _.uniq(_.keys(joinData).reduce((theCertifiers:any, newcomer:string) => { - return theCertifiers.concat(_.pluck(joinData[newcomer].certs, 'from')); - }, [])); - const certifiers:string[] = [].concat(certifiersOfNewcomers); + const certifiersOfNewcomers = Underscore.uniq(Underscore.keys(newcomers).reduce((theCertifiers, newcomer:string) => { + return theCertifiers.concat(Underscore.pluck(newcomers[newcomer].certs, 'from')); + }, <string[]>[])) // Merges updates - _(newCertsFromWoT).keys().forEach(function(certified:string){ + Underscore.keys(newCertsFromWoT).forEach(function(certified:string){ newCertsFromWoT[certified] = newCertsFromWoT[certified].filter((cert:any) => { // Must not certify a newcomer, since it would mean multiple certifications at same time from one member - const isCertifier = certifiers.indexOf(cert.from) != -1; + const isCertifier = certifiersOfNewcomers.indexOf(cert.from) != -1; if (!isCertifier) { - certifiers.push(cert.from); + certifiersOfNewcomers.push(cert.from); } return !isCertifier; }); }); - _(newCertsFromNewcomers).keys().forEach((certified:string) => { - newCertsFromWoT[certified] = (newCertsFromWoT[certified] || []).concat(newCertsFromNewcomers[certified]); - }); - // Revocations // Create the block - return this.createBlock(current, joinData, leaveData, newCertsFromWoT, revocations, exclusions, wereExcludeds, transactions, manualValues); - } - - private async findNewcomersAndLeavers(current:DBBlock, filteringFunc: (joinData: { [pub:string]: any }) => Promise<{ [pub:string]: any }>) { - const newcomers = await this.findNewcomers(current, filteringFunc); - const leavers = await this.findLeavers(current); - - const cur = newcomers.current; - const newWoTMembers = newcomers.newWotMembers; - const finalJoinData = newcomers.finalJoinData; - const updates = newcomers.updates; - - return [cur, newWoTMembers, finalJoinData, leavers, updates]; + return this.createBlock(current, newcomers, leavers, newCertsFromWoT, revocations, exclusions, wereExcludeds, transactions, manualValues); } - private async findTransactions(current:DBBlock, options:{ dontCareAboutChaining?:boolean }) { + private async findTransactions(current:DBBlock|null, options:{ dontCareAboutChaining?:boolean }) { + if (!current) { + return [] + } const versionMin = current ? Math.min(CommonConstants.LAST_VERSION_FOR_TX, current.version) : CommonConstants.DOCUMENTS_VERSION; const txs = await this.dal.getTransactionsPending(versionMin); const transactions = []; @@ -129,7 +144,7 @@ export class BlockGenerator { await LOCAL_RULES_HELPERS.checkBunchOfTransactions(passingTxs.concat(tx), this.conf, options) const nextBlockWithFakeTimeVariation = { medianTime: current.medianTime + 1 }; await GLOBAL_RULES_HELPERS.checkSingleTransaction(tx, nextBlockWithFakeTimeVariation, this.conf, this.dal, async (txHash:string) => { - return _.findWhere(passingTxs, { hash: txHash }) || null + return Underscore.findWhere(passingTxs, { hash: txHash }) || null }); await GLOBAL_RULES_HELPERS.checkTxBlockStamp(tx, this.dal); transactions.push(tx); @@ -149,56 +164,55 @@ export class BlockGenerator { return transactions; } - private async findLeavers(current:DBBlock) { - const leaveData: { [pub:string]: any } = {}; - const memberships = await this.dal.findLeavers(current && current.medianTime); + private async findLeavers(current:DBBlock|null) { + const leaveData: { [pub:string]: { identity: { member:boolean }|null, ms: any, key: any, idHash: string } } = {}; + const memberships = await this.dal.findLeavers((current && current.medianTime) || 0) const leavers:string[] = []; memberships.forEach((ms:any) => leavers.push(ms.issuer)); for (const ms of memberships) { - const leave = { identity: null, ms: ms, key: null, idHash: '' }; + const leave: { identity: { member:boolean }|null, ms: any, key: any, idHash: string } = { identity: null, ms: ms, key: null, idHash: '' }; leave.idHash = (hashf(ms.userid + ms.certts + ms.issuer) + "").toUpperCase(); let block; if (current) { - block = await this.dal.getBlock(ms.number); + block = await this.dal.getAbsoluteValidBlockInForkWindowByBlockstamp(ms.block) } else { block = {}; } - const identity = await this.dal.getIdentityByHashOrNull(leave.idHash); + const identity = await this.dal.getGlobalIdentityByHashForIsMember(leave.idHash) const currentMembership = await this.dal.mindexDAL.getReducedMS(ms.issuer); const currentMSN = currentMembership ? parseInt(currentMembership.created_on) : -1; if (identity && block && currentMSN < leave.ms.number && identity.member) { // MS + matching cert are found leave.identity = identity; - leaveData[identity.pubkey] = leave; + leaveData[identity.pub] = leave; } } return leaveData; } - private async findNewcomers(current:DBBlock, filteringFunc: (joinData: { [pub:string]: any }) => Promise<{ [pub:string]: any }>) { - const updates = {}; + private async findNewcomers(current:DBBlock|null, filteringFunc: (joinData: Map<PreJoin>) => Promise<Map<PreJoin>>) { const preJoinData = await this.getPreJoinData(current); const joinData = await filteringFunc(preJoinData); const members = await this.dal.getMembers(); - const wotMembers = _.pluck(members, 'pubkey'); + const wotMembers = Underscore.pluck(members, 'pubkey'); // Checking step - let newcomers = _(joinData).keys(); - newcomers = _.shuffle(newcomers) + let newcomers = Underscore.keys(joinData) + newcomers = Underscore.shuffle(newcomers) const nextBlockNumber = current ? current.number + 1 : 0; try { const realNewcomers = await this.iteratedChecking(newcomers, async (someNewcomers:string[]) => { const nextBlock = { number: nextBlockNumber, joiners: someNewcomers, - identities: _.filter(newcomers.map((pub:string) => joinData[pub].identity), { wasMember: false }).map((idty:any) => idty.pubkey) + identities: Underscore.where(newcomers.map((pub:string) => joinData[pub].identity), { wasMember: false }).map((idty:any) => idty.pubkey) }; - const theNewLinks = await this.computeNewLinks(nextBlockNumber, someNewcomers, joinData, updates) + const theNewLinks = await this.computeNewLinks(nextBlockNumber, someNewcomers, joinData) await this.checkWoTConstraints(nextBlock, theNewLinks, current); }) - const newLinks = await this.computeNewLinks(nextBlockNumber, realNewcomers, joinData, updates); + const newLinks = await this.computeNewLinks(nextBlockNumber, realNewcomers, joinData) const newWoT = wotMembers.concat(realNewcomers); - const finalJoinData: { [pub:string]: any } = {}; + const finalJoinData: { [pub:string]: PreJoin } = {}; realNewcomers.forEach((newcomer:string) => { // Only keep membership of selected newcomers finalJoinData[newcomer] = joinData[newcomer]; @@ -212,19 +226,14 @@ export class BlockGenerator { }); joinData[newcomer].certs = keptCerts; }); - return { - current: current, - newWotMembers: wotMembers.concat(realNewcomers), - finalJoinData: finalJoinData, - updates: updates - } + return finalJoinData } catch(err) { this.logger.error(err); throw err; } } - private async checkWoTConstraints(block:{ number:number, joiners:string[], identities:string[] }, newLinks:any, current:DBBlock) { + private async checkWoTConstraints(block:{ number:number, joiners:string[], identities:string[] }, newLinks:any, current:DBBlock|null) { if (block.number < 0) { throw 'Cannot compute WoT constraint for negative block number'; } @@ -266,25 +275,28 @@ export class BlockGenerator { } } - private async getPreJoinData(current:DBBlock) { - const preJoinData:any = {}; - const memberships = await this.dal.findNewcomers(current && current.medianTime) + private async getPreJoinData(current:DBBlock|null) { + const preJoinData:{ [k:string]: PreJoin } = {} + const memberships = await this.dal.findNewcomers((current && current.medianTime) || 0) const joiners:string[] = []; memberships.forEach((ms:any) => joiners.push(ms.issuer)); for (const ms of memberships) { try { if (ms.block !== CommonConstants.SPECIAL_BLOCK) { - let msBasedBlock = await this.dal.getBlockByBlockstampOrNull(ms.block); + let msBasedBlock = await this.dal.getAbsoluteValidBlockInForkWindow(ms.blockNumber, ms.blockHash) if (!msBasedBlock) { throw constants.ERRORS.BLOCKSTAMP_DOES_NOT_MATCH_A_BLOCK; } + if (!current) { + throw Error(DataErrors[DataErrors.CANNOT_DETERMINATE_MEMBERSHIP_AGE]) + } let age = current.medianTime - msBasedBlock.medianTime; if (age > this.conf.msWindow) { throw constants.ERRORS.TOO_OLD_MEMBERSHIP; } } const idtyHash = (hashf(ms.userid + ms.certts + ms.issuer) + "").toUpperCase(); - const join:any = await this.getSinglePreJoinData(current, idtyHash, joiners); + const join = await this.getSinglePreJoinData(current, idtyHash, joiners); join.ms = ms; const currentMembership = await this.dal.mindexDAL.getReducedMS(ms.issuer); const currentMSN = currentMembership ? parseInt(currentMembership.created_on) : -1; @@ -302,25 +314,22 @@ export class BlockGenerator { return preJoinData; } - private async computeNewLinks(forBlock:number, theNewcomers:any, joinData:any, updates:any) { + private async computeNewLinks(forBlock:number, theNewcomers:any, joinData:Map<PreJoin>) { let newCerts = await this.computeNewCerts(forBlock, theNewcomers, joinData); - return this.newCertsToLinks(newCerts, updates); + return this.newCertsToLinks(newCerts); } - newCertsToLinks(newCerts:any, updates:any) { - let newLinks:any = {}; - _.mapObject(newCerts, function(certs:any, pubkey:string) { - newLinks[pubkey] = _.pluck(certs, 'from'); - }); - _.mapObject(updates, function(certs:any, pubkey:string) { - newLinks[pubkey] = (newLinks[pubkey] || []).concat(_.pluck(certs, 'pubkey')); - }); - return newLinks; + newCertsToLinks(newCerts:Map<DBCert[]>) { + let newLinks: Map<string[]> = {} + for (const pubkey of Underscore.keys(newCerts)) { + newLinks[pubkey] = Underscore.pluck(newCerts[pubkey], 'from') + } + return newLinks } - async computeNewCerts(forBlock:number, theNewcomers:any, joinData:any) { - const newCerts:any = {}, certifiers = []; - const certsByKey = _.mapObject(joinData, function(val:any){ return val.certs; }); + async computeNewCerts(forBlock:number, theNewcomers:any, joinData:Map<PreJoin>) { + const newCerts:Map<DBCert[]> = {}, certifiers:string[] = [] + const certsByKey = Underscore.mapObjectByProp(joinData, 'certs') for (const newcomer of theNewcomers) { // New array of certifiers newCerts[newcomer] = newCerts[newcomer] || []; @@ -343,11 +352,11 @@ export class BlockGenerator { } } } - return newCerts; + return newCerts } - async getSinglePreJoinData(current:DBBlock, idHash:string, joiners:string[]) { - const identity = await this.dal.getIdentityByHashOrNull(idHash); + async getSinglePreJoinData(current:DBBlock|null, idHash:string, joiners:string[]): Promise<PreJoin> { + const identity = await this.dal.getGlobalIdentityByHashForJoining(idHash) let foundCerts = []; const vHEAD_1 = await this.mainContext.getvHEAD_1(); if (!identity) { @@ -357,7 +366,10 @@ export class BlockGenerator { throw constants.ERRORS.TOO_OLD_IDENTITY; } else if (!identity.wasMember && identity.buid != CommonConstants.SPECIAL_BLOCK) { - const idtyBasedBlock = await this.dal.getBlock(identity.buid); + const idtyBasedBlock = await this.dal.getTristampOf(parseInt(identity.buid.split('-')[0])) + if (!current || !idtyBasedBlock) { + throw Error(DataErrors[DataErrors.CANNOT_DETERMINATE_IDENTITY_AGE]) + } const age = current.medianTime - idtyBasedBlock.medianTime; if (age > this.conf.idtyWindow) { throw constants.ERRORS.TOO_OLD_IDENTITY; @@ -375,9 +387,9 @@ export class BlockGenerator { if (!current) { // Look for certifications from initial joiners const certs = await this.dal.certsNotLinkedToTarget(idHash); - foundCerts = _.filter(certs, function(cert:any){ + foundCerts = Underscore.filter(certs, (cert:any) => { // Add 'joiners && ': special case when block#0 not written ANd not joiner yet (avoid undefined error) - return joiners && ~joiners.indexOf(cert.from); + return !!(joiners && ~joiners.indexOf(cert.from)) }); } else { // Look for certifications from WoT members @@ -385,7 +397,7 @@ export class BlockGenerator { const certifiers = []; for (const cert of certs) { try { - const basedBlock = await this.dal.getBlock(cert.block_number); + const basedBlock = await this.dal.getTristampOf(cert.block_number) if (!basedBlock) { throw 'Unknown timestamp block for identity'; } @@ -408,10 +420,12 @@ export class BlockGenerator { const isMember = await this.dal.isMember(cert.from); const doubleSignature = !!(~certifiers.indexOf(cert.from)) if (isMember && !doubleSignature) { - const isValid = await GLOBAL_RULES_HELPERS.checkCertificationIsValidForBlock(cert, { number: current.number + 1, currency: current.currency }, async () => { - const idty = await this.dal.getIdentityByHashOrNull(idHash) - return idty - }, this.conf, this.dal); + const isValid = await GLOBAL_RULES_HELPERS.checkCertificationIsValidForBlock( + cert, + { number: current.number + 1, currency: current.currency }, + async () => this.dal.getGlobalIdentityByHashForHashingAndSig(idHash), + this.conf, + this.dal) if (isValid) { certifiers.push(cert.from); foundCerts.push(cert); @@ -424,15 +438,26 @@ export class BlockGenerator { } } } + const ms:any = null // TODO: refactor return { identity: identity, key: null, idHash: idHash, - certs: foundCerts + certs: foundCerts, + ms }; } - private async createBlock(current:DBBlock, joinData:any, leaveData:any, updates:any, revocations:any, exclusions:any, wereExcluded:any, transactions:any, manualValues:any) { + private async createBlock( + current:DBBlock|null, + joinData:{ [pub:string]: PreJoin }, + leaveData:{ [pub:string]: LeaveData }, + updates:any, + revocations:any, + exclusions:any, + wereExcluded:any, + transactions:any, + manualValues:any) { if (manualValues && manualValues.excluded) { exclusions = manualValues.excluded; @@ -448,20 +473,20 @@ export class BlockGenerator { // Revocations have an impact on exclusions revocations.forEach((idty:any) => exclusions.push(idty.pubkey)); // Prevent writing joins/updates for members who will be excluded - exclusions = _.uniq(exclusions); + exclusions = Underscore.uniq(exclusions); exclusions.forEach((excluded:any) => { delete updates[excluded]; delete joinData[excluded]; delete leaveData[excluded]; }); // Prevent writing joins/updates for excluded members - wereExcluded = _.uniq(wereExcluded); + wereExcluded = Underscore.uniq(wereExcluded); wereExcluded.forEach((excluded:any) => { delete updates[excluded]; delete joinData[excluded]; delete leaveData[excluded]; }); - _(leaveData).keys().forEach((leaver:any) => { + Underscore.keys(leaveData).forEach((leaver:any) => { delete updates[leaver]; delete joinData[leaver]; }); @@ -503,7 +528,7 @@ export class BlockGenerator { block.issuer = this.selfPubkey } // Members merkle - const joiners = _(joinData).keys(); + const joiners = Underscore.keys(joinData) joiners.sort() const previousCount = current ? current.membersCount : 0; if (joiners.length == 0 && !current) { @@ -517,7 +542,7 @@ export class BlockGenerator { * Priority 1: keep the WoT sane */ // Certifications from the WoT, to the WoT - _(updates).keys().forEach((certifiedMember:any) => { + Underscore.keys(updates).forEach((certifiedMember:any) => { const certs = updates[certifiedMember] || []; certs.forEach((cert:any) => { if (blockLen < maxLenOfBlock) { @@ -538,11 +563,11 @@ export class BlockGenerator { } }); // Leavers - const leavers = _(leaveData).keys(); + const leavers = Underscore.keys(leaveData) leavers.forEach((leaver:any) => { const data = leaveData[leaver]; // Join only for non-members - if (data.identity.member) { + if (data.identity && data.identity.member) { if (blockLen < maxLenOfBlock) { block.leavers.push(MembershipDTO.fromJSONObject(data.ms).inline()); blockLen++; @@ -577,7 +602,7 @@ export class BlockGenerator { block.joiners.push(MembershipDTO.fromJSONObject(data.ms).inline()); } }); - block.identities = _.sortBy(block.identities, (line:string) => { + block.identities = Underscore.sortBy(block.identities, (line:string) => { const sp = line.split(':'); return sp[2] + sp[3]; }); @@ -643,7 +668,7 @@ export class BlockGenerator { block.dividend = vHEAD.dividend; block.unitbase = vHEAD.unitBase; } else { - block.unitbase = block.number == 0 ? 0 : current.unitbase; + block.unitbase = block.number == 0 ? 0 : (current as DBBlock).unitbase; // For sur current is not null, as UD is only on blocks# > 0 } // Rotation block.issuersCount = vHEAD.issuersCount; @@ -651,7 +676,7 @@ export class BlockGenerator { block.issuersFrameVar = vHEAD.issuersFrameVar; // Manual values before hashing if (manualValues) { - _.extend(block, _.omit(manualValues, 'time')); + Underscore.extend(block, Underscore.omit(manualValues, 'time')); } // InnerHash block.time = block.medianTime; @@ -666,7 +691,7 @@ export class BlockGeneratorWhichProves extends BlockGenerator { super(server) } - async makeNextBlock(block:DBBlock|null, trial:number, manualValues:any = null) { + async makeNextBlock(block:DBBlock|null, trial?:number|null, manualValues:any = null) { const unsignedBlock = block || (await this.nextBlock(manualValues)) const trialLevel = trial || (await this.mainContext.getIssuerPersonalizedDifficulty(this.selfPubkey)) return this.prover.prove(unsignedBlock, trialLevel, (manualValues && manualValues.time) || null); @@ -674,7 +699,7 @@ export class BlockGeneratorWhichProves extends BlockGenerator { } interface BlockGeneratorInterface { - findNewCertsFromWoT(current:DBBlock): Promise<any> + findNewCertsFromWoT(current:DBBlock|null): Promise<any> filterJoiners(preJoinData:any): Promise<any> } @@ -686,35 +711,43 @@ class NextBlockGenerator implements BlockGeneratorInterface { constructor( private mainContext:BlockchainContext, - private conf:ConfDTO, - private dal:FileDAL, + private server:Server, private logger:any) { } - async findNewCertsFromWoT(current:DBBlock) { + get conf() { + return this.server.conf + } + + get dal() { + return this.server.dal + } + + async findNewCertsFromWoT(current:DBBlock|null) { const updates:any = {}; const updatesToFrom:any = {}; const certs = await this.dal.certsFindNew(); const vHEAD_1 = await this.mainContext.getvHEAD_1(); for (const cert of certs) { - const targetIdty = await this.dal.getIdentityByHashOrNull(cert.target); + const targetIdty = await this.dal.getGlobalIdentityByHashForHashingAndSig(cert.target) // The identity must be known if (targetIdty) { const certSig = cert.sig; // Do not rely on certification block UID, prefer using the known hash of the block by its given number - const targetBlock = await this.dal.getBlock(cert.block_number); + const targetBlock = await this.dal.getTristampOf(cert.block_number) // Check if writable - let duration = current && targetBlock ? current.medianTime - parseInt(targetBlock.medianTime) : 0; + let duration = current && targetBlock ? current.medianTime - targetBlock.medianTime : 0; if (targetBlock && duration <= this.conf.sigWindow) { - cert.sig = ''; - cert.currency = this.conf.currency; - cert.issuer = cert.from; - cert.idty_issuer = targetIdty.pubkey; - cert.idty_uid = targetIdty.uid; - cert.idty_buid = targetIdty.buid; - cert.idty_sig = targetIdty.sig; - cert.buid = current ? [cert.block_number, targetBlock.hash].join('-') : CommonConstants.SPECIAL_BLOCK; - const rawCert = CertificationDTO.fromJSONObject(cert).getRawUnSigned(); + const rawCert = CertificationDTO.fromJSONObject({ + sig: '', + currency: this.conf.currency, + issuer: cert.from, + idty_issuer: targetIdty.pubkey, + idty_uid: targetIdty.uid, + idty_buid: targetIdty.buid, + idty_sig: targetIdty.sig, + buid: current ? [cert.block_number, targetBlock.hash].join('-') : CommonConstants.SPECIAL_BLOCK, + }).getRawUnSigned(); if (verify(rawCert, certSig, cert.from)) { cert.sig = certSig; let exists = false; @@ -769,7 +802,7 @@ class NextBlockGenerator implements BlockGeneratorInterface { this.logger.warn(err); } } - _.keys(preJoinData).forEach( (joinPubkey:any) => filterings.push(filter(joinPubkey))); + Underscore.keys(preJoinData).forEach( (joinPubkey:any) => filterings.push(filter(joinPubkey))); await Promise.all(filterings) return filtered; } @@ -787,7 +820,7 @@ class ManualRootGenerator implements BlockGeneratorInterface { async filterJoiners(preJoinData:any) { const filtered:any = {}; - const newcomers = _(preJoinData).keys(); + const newcomers = Underscore.keys(preJoinData) const uids:string[] = []; newcomers.forEach((newcomer:string) => uids.push(preJoinData[newcomer].ms.userid)); diff --git a/app/modules/prover/lib/permanentProver.ts b/app/modules/prover/lib/permanentProver.ts index 9bf74f00054a6c36c5f58bf7cd686f15bdc94dea..7347aac00ea85194004af7da636979391f3452f3 100644 --- a/app/modules/prover/lib/permanentProver.ts +++ b/app/modules/prover/lib/permanentProver.ts @@ -19,14 +19,7 @@ import {dos2unix} from "../../../lib/common-libs/dos2unix" import {parsers} from "../../../lib/common-libs/parsers/index" import {Server} from "../../../../server" - -const querablep = require('querablep'); - -export interface Querable<T> extends Promise<T> { - isFulfilled(): boolean - isResolved(): boolean - isRejected(): boolean -} +import {Querable, querablep} from "../../../lib/common-libs/querable" export class PermanentProver { @@ -36,7 +29,7 @@ export class PermanentProver { generator:BlockGeneratorWhichProves loops:number - private permanencePromise:Querable<any>|null = null + private permanencePromise:Querable<void>|null = null private blockchainChangedResolver:any = null private promiseOfWaitingBetween2BlocksOfOurs:any = null @@ -140,7 +133,7 @@ export class PermanentProver { // The pushFIFO is here to get the difficulty level while excluding any new block to be resolved. // Without it, a new block could be added meanwhile and would make the difficulty wrongly computed. await this.server.BlockchainService.pushFIFO('generatingNextBlock', async () => { - const current = await this.server.dal.getCurrentBlockOrNull(); + const current = (await this.server.dal.getCurrentBlockOrNull()) as DBBlock const selfPubkey = this.server.keyPair.publicKey; if (!cancelAlreadyTriggered) { trial2 = await this.server.getBcContext().getIssuerPersonalizedDifficulty(selfPubkey) @@ -209,7 +202,7 @@ export class PermanentProver { permanenceResolve() } - async blockchainChanged(gottenBlock:any) { + async blockchainChanged(gottenBlock?:any) { if (this.server && (!gottenBlock || !this.lastComputedBlock || gottenBlock.hash !== this.lastComputedBlock.hash)) { // Cancel any processing proof await this.prover.cancel() diff --git a/app/modules/prover/lib/powCluster.ts b/app/modules/prover/lib/powCluster.ts index 9bc46362a4fd7ec376dafdc93ab9ed25c505a368..3ee90d488df904aff629220b522c99ce55b7e63c 100644 --- a/app/modules/prover/lib/powCluster.ts +++ b/app/modules/prover/lib/powCluster.ts @@ -16,8 +16,8 @@ import {ProverConstants} from "./constants" import {createPowWorker} from "./proof" import {PowWorker} from "./PowWorker" import {FileDAL} from "../../../lib/dal/fileDAL"; +import {Underscore} from "../../../lib/common-libs/underscore" -const _ = require('underscore') const nuuid = require('node-uuid'); const cluster = require('cluster') const querablep = require('querablep') @@ -50,7 +50,7 @@ export class Master { onInfoCallback:any workersOnline:Promise<any>[] - constructor(private nbCores:number, logger:any, private dal?:FileDAL) { + constructor(private nbCores:number|null|undefined, logger:any, private dal?:FileDAL) { this.clusterId = clusterId++ this.logger = logger || Master.defaultLogger() this.onInfoMessage = (message:any) => { @@ -90,7 +90,8 @@ export class Master { execArgv: [] // Do not try to debug forks }) - this.slaves = Array.from({ length: this.nbCores }).map((value, index) => { + const nbCores = this.nbCores !== undefined && this.nbCores !== null ? this.nbCores : 1 + this.slaves = Array.from({ length: nbCores }).map((value, index) => { const nodejsWorker = cluster.fork() const worker = new PowWorker(nodejsWorker, message => { this.onWorkerMessage(index, message) @@ -139,7 +140,7 @@ export class Master { value: this.conf }) }) - return Promise.resolve(_.clone(conf)) + return Promise.resolve(Underscore.clone(conf)) } private cancelWorkersWork() { @@ -204,7 +205,7 @@ export class Master { nonceBeginning: s.nonceBeginning, zeros: stuff.newPoW.zeros, highMark: stuff.newPoW.highMark, - pair: _.clone(stuff.newPoW.pair), + pair: Underscore.clone(stuff.newPoW.pair), forcedTime: stuff.newPoW.forcedTime, conf: { powNoSecurity: stuff.newPoW.conf.powNoSecurity, diff --git a/app/modules/prover/lib/proof.ts b/app/modules/prover/lib/proof.ts index af1024409ad401bc0f0991215e52de8b3f0bdf82..50329b0a595ed6976b47dfc4390a4d5eb9951cc0 100644 --- a/app/modules/prover/lib/proof.ts +++ b/app/modules/prover/lib/proof.ts @@ -11,6 +11,7 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. +import * as moment from "moment" import {LOCAL_RULES_HELPERS} from "../../../lib/rules/local_rules" import {hashf} from "../../../lib/common" import {DBBlock} from "../../../lib/db/DBBlock" @@ -21,10 +22,9 @@ import {dos2unix} from "../../../lib/common-libs/dos2unix" import {rawer} from "../../../lib/common-libs/index" import {ProcessCpuProfiler} from "../../../ProcessCpuProfiler" import {PowDAL} from "../../../lib/dal/fileDALs/PowDAL"; +import {Directory} from "../../../lib/system/directory" -const moment = require('moment'); const querablep = require('querablep'); -const directory = require('../../../lib/system/directory'); export function createPowWorker() { @@ -64,7 +64,7 @@ export function createPowWorker() { } if (message.value.rootPath) { - const params = await directory.getHomeFS(false, message.value.rootPath, false) + const params = await Directory.getHomeFS(false, message.value.rootPath, false) powDAL = new PowDAL(message.value.rootPath, params.fs) } diff --git a/app/modules/reapply.ts b/app/modules/reapply.ts index 0fcb1b206d56b29ae4c75cd268b73ff161a4c191..ac58ece38c4b6e54c86c399cd96ad1a50681b7f9 100644 --- a/app/modules/reapply.ts +++ b/app/modules/reapply.ts @@ -34,6 +34,18 @@ module.exports = { await server.disconnect(); } } + }, { + name: 'db-dump', + desc: 'Dump some db data', + preventIfRunning: true, + onDatabaseExecute: async (server:Server, conf:ConfDTO, program:any, params:any) => { + const data = await server.dal.iindexDAL.findAllByWrittenOn() + for (const d of data) { + if (d.pub === "9DDn592RMWfka6fPtTGkmAS54CkYxohDGuk41EECxioD") { + console.log("%s %s", d.pub, d.kick) + } + } + } }] } } diff --git a/app/modules/wizard.ts b/app/modules/wizard.ts index a6b6e3b4e3cb9e8470d7f4fa13d17d9ff5d7f7f5..51c3e38c08469b79195de403073523d4448500fe 100644 --- a/app/modules/wizard.ts +++ b/app/modules/wizard.ts @@ -14,8 +14,8 @@ import {ConfDTO} from "../lib/dto/ConfDTO" import {Server} from "../../server" import {Wizard} from "../lib/wizard" +import {Underscore} from "../lib/common-libs/underscore" -const _ = require('underscore') const logger = require('../lib/logger').NewLogger('wizard'); module.exports = { @@ -34,7 +34,7 @@ module.exports = { onConfiguredExecute: async (server:Server, conf:ConfDTO, program:any, params:any, wizardTasks:any) => { const step = params[0]; - const tasks = step ? [wizardTasks[step]] : _.values(wizardTasks); + const tasks = step ? [wizardTasks[step]] : Underscore.values(wizardTasks); for (const task of tasks) { if (!task) { throw 'Unknown task'; diff --git a/app/modules/ws2p/lib/WS2PBlockPuller.ts b/app/modules/ws2p/lib/WS2PBlockPuller.ts index 1a3445bc2ea16c02e58c492a768d1a5c91960464..638db4b9381070f3ca4d55274afcbbe8e1008797 100644 --- a/app/modules/ws2p/lib/WS2PBlockPuller.ts +++ b/app/modules/ws2p/lib/WS2PBlockPuller.ts @@ -84,7 +84,7 @@ class WS2PDao extends AbstractDAO { } async getLocalBlock(number: number): Promise<DBBlock> { - return this.server.dal.getBlock(number) + return this.server.dal.getBlockWeHaveItForSure(number) } async getRemoteBlock(thePeer: any, number: number): Promise<BlockDTO> { diff --git a/app/modules/ws2p/lib/WS2PClient.ts b/app/modules/ws2p/lib/WS2PClient.ts index f4b03b46a03d3c0552ccf3a910f0610d8a6148a1..6f3602cb9a9146a299aeb324954a487720047673 100644 --- a/app/modules/ws2p/lib/WS2PClient.ts +++ b/app/modules/ws2p/lib/WS2PClient.ts @@ -11,7 +11,6 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -import { WS2PCluster } from './WS2PCluster'; import {Server} from "../../../../server" import {WS2PConnection, WS2PPubkeyLocalAuth, WS2PPubkeyRemoteAuth} from "./WS2PConnection" import {Key} from "../../../lib/common-libs/crypto/keyring" @@ -19,12 +18,18 @@ import {WS2PMessageHandler} from "./impl/WS2PMessageHandler" import {WS2PConstants} from "./constants" import {WS2PStreamer} from "./WS2PStreamer" import {WS2PSingleWriteStream} from "./WS2PSingleWriteStream" -import { ProxiesConf } from '../../../lib/proxy'; -import { server } from '../../../../test/integration/tools/toolbox'; +import {ProxiesConf} from '../../../lib/proxy'; export class WS2PClient { - private constructor(public connection:WS2PConnection) {} + private constructor( + public connection:WS2PConnection, + private streamer:WS2PStreamer) { + } + + disableStream() { + this.streamer.disable() + } static async connectTo(server:Server, fullEndpointAddress:string, endpointVersion:number, expectedWS2PUID:string, messageHandler:WS2PMessageHandler, expectedPub:string, allowKey:(pub:string)=>Promise<boolean> ) { const k2 = new Key(server.conf.pair.pub, server.conf.pair.sec) @@ -69,6 +74,6 @@ export class WS2PClient { c.close() throw e } - return new WS2PClient(c) + return new WS2PClient(c, streamer) } } \ No newline at end of file diff --git a/app/modules/ws2p/lib/WS2PCluster.ts b/app/modules/ws2p/lib/WS2PCluster.ts index 5a92233d678f4e571f506dffe588d0940d580048..999c28a6b8f0bda0eb8ec968c69bb79d4b32b4af 100644 --- a/app/modules/ws2p/lib/WS2PCluster.ts +++ b/app/modules/ws2p/lib/WS2PCluster.ts @@ -31,10 +31,10 @@ import {CommonConstants} from '../../../lib/common-libs/constants'; import {Package} from "../../../lib/common/package"; import {ProverConstants} from "../../prover/lib/constants"; import {ProxiesConf} from '../../../lib/proxy'; +import {Underscore} from "../../../lib/common-libs/underscore" const es = require('event-stream') const nuuid = require('node-uuid') -const _ = require('underscore') export interface WS2PHead { message:string @@ -257,7 +257,7 @@ export class WS2PCluster { } if (!exists) { // Do we have this block in the DB? - exists = !!(await this.server.dal.getAbsoluteBlockByBlockstamp(blockstamp)) + exists = !!(await this.server.dal.getAbsoluteBlockInForkWindowByBlockstamp(blockstamp)) } // Update the last time it was checked this.blockstampsCache[blockstamp] = Date.now() @@ -336,7 +336,7 @@ export class WS2PCluster { } } - async connectToRemoteWS(endpointVersion:number, host: string, port: number, path:string, messageHandler:WS2PMessageHandler, expectedPub:string, ws2pEndpointUUID:string = ""): Promise<WS2PConnection> { + async connectToRemoteWS(endpointVersion:number, host: string, port: number, path:string, messageHandler:WS2PMessageHandler, expectedPub:string, ws2pEndpointUUID:string = ""): Promise<WS2PClient> { const uuid = nuuid.v4() let pub = expectedPub.slice(0, 8) const api:string = (host.match(WS2PConstants.HOST_ONION_REGEX) !== null) ? 'WS2PTOR':'WS2P' @@ -368,7 +368,7 @@ export class WS2PCluster { to: { host, port, pubkey: pub } }) await this.server.dal.setPeerUP(pub) - return ws2pc.connection + return ws2pc } catch (e) { this.server.logger.info(api+': Could not connect to peer %s using `'+api+' %s %s: %s`', pub.slice(0, 8), host, port, (e && e.message || e)) throw e @@ -699,7 +699,7 @@ export class WS2PCluster { } // Disconnect Private connexions already present under Public let uuids = Object.keys(this.ws2pClients) - uuids = _.shuffle(uuids) + uuids = Underscore.shuffle(uuids) for (const uuid of uuids) { const client = this.ws2pClients[uuid] const pub = client.connection.pubkey @@ -716,7 +716,7 @@ export class WS2PCluster { // Disconnect Private connexions until the maximum size is respected while (this.clientsCount() > this.maxLevel1Size) { let uuids = Object.keys(this.ws2pClients) - uuids = _.shuffle(uuids) + uuids = Underscore.shuffle(uuids) let lowPriorityConnectionUUID:string = uuids[0] let minPriorityLevel = await this.keyPriorityLevel(this.ws2pClients[lowPriorityConnectionUUID].connection.pubkey, preferedKeys) for (const uuid of uuids) { @@ -889,7 +889,7 @@ export class WS2PCluster { async pullBlocks() { let current:{number:number} = { number: -1 } - let newCurrent:{number:number} = { number: 0 } + let newCurrent:{number:number}|null = { number: 0 } while (current && newCurrent && newCurrent.number > current.number) { current = newCurrent await this.makeApullShot() diff --git a/app/modules/ws2p/lib/WS2PConnection.ts b/app/modules/ws2p/lib/WS2PConnection.ts index dad3bdbeebe643d18d3bbe02db234d483c126ccb..0f8cf34a77e5f6aeafb579f1b7fc5132eb32b93e 100644 --- a/app/modules/ws2p/lib/WS2PConnection.ts +++ b/app/modules/ws2p/lib/WS2PConnection.ts @@ -20,8 +20,8 @@ import {MembershipDTO} from "../../../lib/dto/MembershipDTO" import {TransactionDTO} from "../../../lib/dto/TransactionDTO" import {PeerDTO} from "../../../lib/dto/PeerDTO" import {WS2PConstants} from './constants'; +import {WebSocket} from "../../../lib/common-libs/websocket" -const ws = require('ws') const SocksProxyAgent = require('socks-proxy-agent'); const nuuid = require('node-uuid'); const logger = require('../../../lib/logger').NewLogger('ws2p') @@ -334,7 +334,7 @@ export class WS2PConnection { requestTimeout: WS2PConstants.REQUEST_TOR_TIMEOUT } } - const websocket = (proxySocksAddress !== undefined) ? new ws(address, { agent: SocksProxyAgent("socks://"+proxySocksAddress) }):new ws(address) + const websocket = (proxySocksAddress !== undefined) ? new WebSocket(address, { agent: SocksProxyAgent("socks://"+proxySocksAddress) }):new WebSocket(address) const onWsOpened:Promise<void> = new Promise(res => { websocket.on('open', () => res()) }) diff --git a/app/modules/ws2p/lib/WS2PServer.ts b/app/modules/ws2p/lib/WS2PServer.ts index d0eec6b8fdd663a6a615acb7bef158490ce67c83..a9091d43ebd0b4fb221c755f99682bc2203c19c2 100644 --- a/app/modules/ws2p/lib/WS2PServer.ts +++ b/app/modules/ws2p/lib/WS2PServer.ts @@ -20,8 +20,7 @@ import {WS2PConstants} from "./constants" import {WS2PMessageHandler} from "./impl/WS2PMessageHandler" import {WS2PStreamer} from "./WS2PStreamer" import {WS2PSingleWriteStream} from "./WS2PSingleWriteStream" - -const WebSocketServer = require('ws').Server +import {WebSocketServer} from "../../../lib/common-libs/websocket" export class WS2PServer extends events.EventEmitter { diff --git a/app/modules/ws2p/lib/WS2PStreamer.ts b/app/modules/ws2p/lib/WS2PStreamer.ts index f96b10a026396ff5953e02a9efaa9ff990111c93..045f06ca65bdbbe58e22a8646034ae72afd2bd4b 100644 --- a/app/modules/ws2p/lib/WS2PStreamer.ts +++ b/app/modules/ws2p/lib/WS2PStreamer.ts @@ -12,18 +12,31 @@ // GNU Affero General Public License for more details. import * as stream from "stream" -import { NewLogger } from "../../../lib/logger"; -import { WS2PConnection } from "./WS2PConnection"; +import {NewLogger} from "../../../lib/logger"; +import {WS2PConnection} from "./WS2PConnection"; const logger = NewLogger() export class WS2PStreamer extends stream.Transform { + private enabled = true + constructor(private ws2pc:WS2PConnection) { super({ objectMode: true }) } + enable() { + this.enabled = true + } + + disable() { + this.enabled = false + } + async _write(obj:any, enc:any, done:any) { + if (!this.enabled) { + return done && done() + } try { if (obj.joiners) { await this.ws2pc.pushBlock(obj) diff --git a/app/modules/ws2p/lib/impl/WS2PReqMapperByServer.ts b/app/modules/ws2p/lib/impl/WS2PReqMapperByServer.ts index b5bd1e954ae501f8e0331f49e8d0f9f43d0d507e..ae805fdc2d5a5abe34c3027940db285ea03d9504 100644 --- a/app/modules/ws2p/lib/impl/WS2PReqMapperByServer.ts +++ b/app/modules/ws2p/lib/impl/WS2PReqMapperByServer.ts @@ -11,11 +11,11 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -import { IdentityForRequirements } from './../../../../service/BlockchainService'; +import {IdentityForRequirements} from './../../../../service/BlockchainService'; import {Server} from "../../../../../server" import {WS2PReqMapper} from "../interface/WS2PReqMapper" import {BlockDTO} from "../../../../lib/dto/BlockDTO" -import { IindexEntry } from '../../../../lib/indexer'; +import {DBBlock} from "../../../../lib/db/DBBlock" export class WS2PReqMapperByServer implements WS2PReqMapper { @@ -25,8 +25,8 @@ export class WS2PReqMapperByServer implements WS2PReqMapper { return this.server.BlockchainService.current() } - getBlock(number: number): Promise<BlockDTO[]> { - return this.server.dal.getBlock(number) + async getBlock(number: number): Promise<BlockDTO> { + return Promise.resolve(BlockDTO.fromJSONObject(await this.server.dal.getFullBlockOf(number))) } async getBlocks(count: number, from: number): Promise<BlockDTO[]> { @@ -34,37 +34,34 @@ export class WS2PReqMapperByServer implements WS2PReqMapper { throw 'Count is too high' } const current = await this.server.dal.getCurrentBlockOrNull() + if (!current) { + return [] + } count = Math.min(current.number - from + 1, count) if (!current || current.number < from) { return [] } - return this.server.dal.getBlocksBetween(from, from + count - 1) + return (await this.server.dal.getBlocksBetween(from, from + count - 1)).map((b:DBBlock) => BlockDTO.fromJSONObject(b)) } async getRequirementsOfPending(minsig: number): Promise<any> { - let identities:IdentityForRequirements[] = await this.server.dal.idtyDAL.query( + let identities:IdentityForRequirements[] = (await this.server.dal.idtyDAL.query( 'SELECT i.*, count(c.sig) as nbSig ' + 'FROM idty i, cert c ' + 'WHERE c.target = i.hash group by i.hash having nbSig >= ?', - minsig) - const members:IdentityForRequirements[] = (await this.server.dal.idtyDAL.query( - 'SELECT i.*, count(c.sig) as nbSig ' + - 'FROM i_index i, cert c ' + - 'WHERE c.`to` = i.pub group by i.pub having nbSig >= ?', - minsig)).map((i:IindexEntry):IdentityForRequirements => { - return { - hash: i.hash || "", - member: i.member || false, - wasMember: i.wasMember || false, - pubkey: i.pub, - uid: i.uid || "", - buid: i.created_on || "", - sig: i.sig || "", - revocation_sig: "", - revoked: false, - revoked_on: 0 - } - }) + [minsig])).map(i => ({ + hash: i.hash || "", + member: i.member || false, + wasMember: i.wasMember || false, + pubkey: i.pubkey, + uid: i.uid || "", + buid: i.buid || "", + sig: i.sig || "", + revocation_sig: i.revocation_sig, + revoked: i.revoked, + revoked_on: i.revoked_on ? 1 : 0 + })) + const members = await this.server.dal.findReceiversAbove(minsig) identities = identities.concat(members) const all = await this.server.BlockchainService.requirementsOfIdentities(identities, false) return { diff --git a/app/modules/ws2p/lib/interface/WS2PReqMapper.ts b/app/modules/ws2p/lib/interface/WS2PReqMapper.ts index 651fb22cb59cd04d70c09f2b549df0f5728a55ec..b2575e4a26c5beb51d43b1fbfe67137b37d69822 100644 --- a/app/modules/ws2p/lib/interface/WS2PReqMapper.ts +++ b/app/modules/ws2p/lib/interface/WS2PReqMapper.ts @@ -12,11 +12,12 @@ // GNU Affero General Public License for more details. import {BlockDTO} from "../../../../lib/dto/BlockDTO" +import {DBBlock} from "../../../../lib/db/DBBlock" export interface WS2PReqMapper { - getCurrent(): Promise<BlockDTO> - getBlock(number:number): Promise<BlockDTO[]> + getCurrent(): Promise<DBBlock|null> + getBlock(number:number): Promise<BlockDTO> getBlocks(count:number, fromNumber:number): Promise<BlockDTO[]> getRequirementsOfPending(minCert:number): Promise<any> } \ No newline at end of file diff --git a/app/service/BlockchainService.ts b/app/service/BlockchainService.ts index 1337f3ee83218e721d46d77ae9141e8ac1c619c1..27eb39a7f082786c1e83b66032ac2d8937d01e8b 100644 --- a/app/service/BlockchainService.ts +++ b/app/service/BlockchainService.ts @@ -28,10 +28,9 @@ import {CommonConstants} from "../lib/common-libs/constants" import {LOCAL_RULES_FUNCTIONS} from "../lib/rules/local_rules" import {Switcher, SwitcherDao} from "../lib/blockchain/Switcher" import {OtherConstants} from "../lib/other_constants" +import {DataErrors} from "../lib/common-libs/errors" +import {DuniterBlockchain} from "../lib/blockchain/DuniterBlockchain" -"use strict"; - -const _ = require('underscore'); const constants = require('../lib/constants'); export interface IdentityForRequirements { @@ -42,10 +41,19 @@ export interface IdentityForRequirements { uid:string buid:string sig:string - revocation_sig:string + revocation_sig:string|null revoked:boolean revoked_on:number } + +export interface ValidCert { + from:string + to:string + sig:string + timestamp:number + expiresIn:number +} + export class BlockchainService extends FIFOService { mainContext:BlockchainContext @@ -64,8 +72,12 @@ export class BlockchainService extends FIFOService { constructor(private bcService:BlockchainService) {} - getCurrent(): Promise<BlockDTO> { - return this.bcService.current() + async getCurrent(): Promise<BlockDTO|null> { + const current = await this.bcService.current() + if (!current) { + return null + } + return BlockDTO.fromJSONObject(current) } async getPotentials(numberStart: number, timeStart: number, maxNumber:number): Promise<BlockDTO[]> { @@ -74,16 +86,14 @@ export class BlockchainService extends FIFOService { } async getBlockchainBlock(number: number, hash: string): Promise<BlockDTO | null> { - try { - return BlockDTO.fromJSONObject(await this.bcService.dal.getBlockByNumberAndHash(number, hash)) - } catch (e) { - return null - } + const b = await this.bcService.dal.getAbsoluteValidBlockInForkWindow(number, hash) + if (!b) return null + return BlockDTO.fromJSONObject(b) } - async getSandboxBlock(number: number, hash: string): Promise<BlockDTO | null> { - const block = await this.bcService.dal.getAbsoluteBlockByNumberAndHash(number, hash) - if (block && block.fork) { + async getAbsoluteBlockInForkWindow(number: number, hash: string): Promise<BlockDTO | null> { + const block = await this.bcService.dal.getAbsoluteBlockInForkWindow(number, hash) + if (block) { return BlockDTO.fromJSONObject(block) } else { return null @@ -93,6 +103,9 @@ export class BlockchainService extends FIFOService { async revertTo(number: number): Promise<BlockDTO[]> { const blocks:BlockDTO[] = [] const current = await this.bcService.current(); + if (!current) { + throw Error(DataErrors[DataErrors.CANNOT_REVERT_NO_CURRENT_BLOCK]) + } for (let i = 0, count = current.number - number; i < count; i++) { const reverted = await this.bcService.mainContext.revertCurrentBlock() blocks.push(BlockDTO.fromJSONObject(reverted)) @@ -124,8 +137,8 @@ export class BlockchainService extends FIFOService { this.dal = newDAL; this.conf = newConf; this.logger = require('../lib/logger').NewLogger(this.dal.profile) - this.quickSynchronizer = new QuickSynchronizer(this.server.blockchain, this.conf, this.dal, this.logger) - this.mainContext.setConfDAL(this.conf, this.dal, this.server.blockchain, this.quickSynchronizer) + this.quickSynchronizer = new QuickSynchronizer(this.conf, this.dal, this.logger) + this.mainContext.setConfDAL(this.conf, this.dal, this.quickSynchronizer) this.selfPubkey = newKeyPair.publicKey; } @@ -145,11 +158,18 @@ export class BlockchainService extends FIFOService { return this.mainContext.checkBlock(dto, withPoWAndSignature) } + /** + * Return the potential HEADs we could fork to (necessarily above us, since we don't fork on older branches). + * @returns {Promise<any>} + */ async branches() { const current = await this.current() + if (!current) { + throw Error(DataErrors[DataErrors.CANNOT_REVERT_NO_CURRENT_BLOCK]) + } const switcher = new Switcher(this.switcherDao, this.invalidForks, this.conf.avgGenTime, this.conf.forksize, this.conf.switchOnHeadAdvance, this.logger) const heads = await switcher.findPotentialSuitesHeads(current) - return heads.concat([current]) + return heads.concat([BlockDTO.fromJSONObject(current)]) } submitBlock(blockToAdd:any, noResolution = false): Promise<BlockDTO> { @@ -173,7 +193,7 @@ export class BlockchainService extends FIFOService { throw CommonConstants.ERRORS.OUT_OF_FORK_WINDOW } } - const absolute = await this.dal.getAbsoluteBlockByNumberAndHash(obj.number, obj.hash) + const absolute = await this.dal.existsAbsoluteBlockInForkWindow(parseInt(obj.number), obj.hash) if (!absolute) { // Save the block in the sandbox await this.mainContext.addSideBlock(dto); @@ -202,9 +222,11 @@ export class BlockchainService extends FIFOService { }) } - async blockResolution() { - let added = true - while (added) { + async blockResolution(max = 0): Promise<BlockDTO|null> { + let lastAdded:BlockDTO|null = null + let added:BlockDTO|null + let nbAdded = 0 + do { const current = await this.current() let potentials = [] if (current) { @@ -214,9 +236,9 @@ export class BlockchainService extends FIFOService { potentials = await this.dal.getPotentialRootBlocks() this.logger.info('Block resolution: %s potential blocks for root block...', potentials.length) } - added = false + added = null let i = 0 - while (!added && i < potentials.length) { + while (!added && i < potentials.length && (!max || nbAdded < max)) { const dto = BlockDTO.fromJSONObject(potentials[i]) try { if (dto.issuer === this.conf.pair.pub) { @@ -224,17 +246,17 @@ export class BlockchainService extends FIFOService { await this.dal.removeTxByHash(tx.hash); } } - const addedBlock = await this.mainContext.checkAndAddBlock(dto) - added = true + lastAdded = added = await this.mainContext.checkAndAddBlock(dto) this.push({ bcEvent: OtherConstants.BC_EVENT.HEAD_CHANGED, - block: addedBlock + block: added }) + nbAdded++ // Clear invalid forks' cache this.invalidForks.splice(0, this.invalidForks.length) } catch (e) { this.logger.error(e) - added = false + added = null const theError = e && (e.message || e) this.push({ blockResolutionError: theError @@ -242,7 +264,8 @@ export class BlockchainService extends FIFOService { } i++ } - } + } while (added) + return lastAdded } async forkResolution() { @@ -259,6 +282,10 @@ export class BlockchainService extends FIFOService { revertCurrentBlock() { return this.pushFIFO("revertCurrentBlock", () => this.mainContext.revertCurrentBlock()) } + + revertCurrentHead() { + return this.pushFIFO("revertCurrentHead", () => this.mainContext.revertCurrentHead()) + } applyNextAvailableFork() { @@ -280,7 +307,7 @@ export class BlockchainService extends FIFOService { return all; } - async requirementsOfIdentity(idty:IdentityForRequirements, current:DBBlock, computeDistance = true): Promise<HttpIdentityRequirement> { + async requirementsOfIdentity(idty:IdentityForRequirements, current:DBBlock|null, computeDistance = true): Promise<HttpIdentityRequirement> { // TODO: this is not clear let expired = false; let outdistanced = false; @@ -288,7 +315,7 @@ export class BlockchainService extends FIFOService { let wasMember = false; let expiresMS = 0; let expiresPending = 0; - let certs = []; + let certs:ValidCert[] = []; let certsPending = []; let mssPending = []; try { @@ -315,7 +342,7 @@ export class BlockchainService extends FIFOService { const newCerts = await this.server.generatorComputeNewCerts(nextBlockNumber, [join.identity.pubkey], joinData, updates); const newLinks = await this.server.generatorNewCertsToLinks(newCerts, updates); const currentTime = current ? current.medianTime : 0; - certs = await this.getValidCerts(pubkey, newCerts); + certs = await this.getValidCerts(pubkey, newCerts, currentTime); if (computeDistance) { outdistanced = await GLOBAL_RULES_HELPERS.isOver3Hops(pubkey, newLinks, someNewcomers, current, this.conf, this.dal); } @@ -324,8 +351,8 @@ export class BlockchainService extends FIFOService { const currentMSN = currentMembership ? parseInt(currentMembership.created_on) : -1; if (currentMSN >= 0) { if (join.identity.member) { - const msBlock = await this.dal.getBlock(currentMSN); - if (msBlock && msBlock.medianTime) { // special case for block #0 + const msBlock = await this.dal.getTristampOf(currentMSN) + if (msBlock) { // special case for block #0 expiresMS = Math.max(0, (msBlock.medianTime + this.conf.msValidity - currentTime)); } else { @@ -338,8 +365,8 @@ export class BlockchainService extends FIFOService { // Expiration of pending membership const lastJoin = await this.dal.lastJoinOfIdentity(idty.hash); if (lastJoin) { - const msBlock = await this.dal.getBlock(lastJoin.blockNumber); - if (msBlock && msBlock.medianTime) { // Special case for block#0 + const msBlock = await this.dal.getTristampOf(lastJoin.blockNumber) + if (msBlock) { // Special case for block#0 expiresPending = Math.max(0, (msBlock.medianTime + this.conf.msValidity - currentTime)); } else { @@ -348,10 +375,6 @@ export class BlockchainService extends FIFOService { } wasMember = idty.wasMember; isSentry = idty.member && (await this.dal.isSentry(idty.pubkey, this.conf)); - // Expiration of certifications - for (const cert of certs) { - cert.expiresIn = Math.max(0, cert.timestamp + this.conf.sigValidity - currentTime); - } } catch (e) { // We throw whatever isn't "Too old identity" error if (!(e && e.uerr && e.uerr.ucode == constants.ERRORS.TOO_OLD_IDENTITY.uerr.ucode)) { @@ -382,21 +405,34 @@ export class BlockchainService extends FIFOService { }; } - async getValidCerts(newcomer:string, newCerts:any) { + async getValidCerts(newcomer:string, newCerts:any, currentTime:number): Promise<ValidCert[]> { const links = await this.dal.getValidLinksTo(newcomer); - const certsFromLinks = links.map((lnk:any) => { return { from: lnk.issuer, to: lnk.receiver, timestamp: lnk.expires_on - this.conf.sigValidity }; }); + const certsFromLinks = links.map((lnk:any) => { return { + from: lnk.issuer, + to: lnk.receiver, + sig: lnk.sig, + timestamp: lnk.expires_on - this.conf.sigValidity, + expiresIn: 0 + } + }) const certsFromCerts = []; const certs = newCerts[newcomer] || []; for (const cert of certs) { - const block = await this.dal.getBlock(cert.block_number); - certsFromCerts.push({ - from: cert.from, - to: cert.to, - sig: cert.sig, - timestamp: block.medianTime - }); + const block = await this.dal.getTristampOf(cert.block_number) + if (block) { + certsFromCerts.push({ + from: cert.from, + to: cert.to, + sig: cert.sig, + timestamp: block.medianTime, + expiresIn: 0 + }) + } } - return certsFromLinks.concat(certsFromCerts); + return certsFromLinks.concat(certsFromCerts).map(c => { + c.expiresIn = Math.max(0, c.timestamp + this.conf.sigValidity - currentTime) + return c + }) } isMember() { @@ -410,14 +446,17 @@ export class BlockchainService extends FIFOService { // This method is called by duniter-crawler 1.3.x saveParametersForRootBlock(block:BlockDTO) { - return this.server.blockchain.saveParametersForRoot(block, this.conf, this.dal) + return DuniterBlockchain.saveParametersForRoot(block, this.conf, this.dal) } - async blocksBetween(from:number, count:number) { + async blocksBetween(from:number, count:number): Promise<DBBlock[]> { if (count > 5000) { throw 'Count is too high'; } const current = await this.current() + if (!current) { + return [] + } count = Math.min(current.number - from + 1, count); if (!current || current.number < from) { return []; diff --git a/app/service/IdentityService.ts b/app/service/IdentityService.ts index 4237cdebf83236da80cfb053642142e91038b1ae..dba4089fc4d5d858dd36b3c26f9a743f3926c05a 100644 --- a/app/service/IdentityService.ts +++ b/app/service/IdentityService.ts @@ -14,7 +14,7 @@ import {GlobalFifoPromise} from "./GlobalFifoPromise" import {FileDAL} from "../lib/dal/fileDAL" import {ConfDTO} from "../lib/dto/ConfDTO" -import {DBIdentity, ExistingDBIdentity} from "../lib/dal/sqliteDAL/IdentityDAL" +import {DBIdentity} from "../lib/dal/sqliteDAL/IdentityDAL" import {GLOBAL_RULES_FUNCTIONS, GLOBAL_RULES_HELPERS} from "../lib/rules/global_rules" import {BlockDTO} from "../lib/dto/BlockDTO" import {RevocationDTO} from "../lib/dto/RevocationDTO" @@ -23,6 +23,9 @@ import {CertificationDTO} from "../lib/dto/CertificationDTO" import {DBCert} from "../lib/dal/sqliteDAL/CertDAL" import {verify} from "../lib/common-libs/crypto/keyring" import {FIFOService} from "./FIFOService" +import {MindexEntry} from "../lib/indexer" +import {DataErrors} from "../lib/common-libs/errors" +import {Tristamp} from "../lib/common/Tristamp" "use strict"; const constants = require('../lib/constants'); @@ -49,38 +52,58 @@ export class IdentityService extends FIFOService { return this.dal.searchJustIdentities(search) } - async findMember(search:string): Promise<ExistingDBIdentity> { + async findMember(search:string) { let idty = null; if (search.match(constants.PUBLIC_KEY)) { - idty = await this.dal.getWrittenIdtyByPubkey(search); + idty = await this.dal.getWrittenIdtyByPubkeyForHashing(search); } else { - idty = await this.dal.getWrittenIdtyByUID(search); + idty = await this.dal.getWrittenIdtyByUidForHashing(search); } if (!idty) { throw constants.ERRORS.NO_MEMBER_MATCHING_PUB_OR_UID; } - const obj = DBIdentity.copyFromExisting(idty) - await this.dal.fillInMembershipsOfIdentity(Promise.resolve(obj)); - return obj - } - async findMemberWithoutMemberships(search:string) { - let idty = null; - if (search.match(constants.PUBLIC_KEY)) { - idty = await this.dal.getWrittenIdtyByPubkey(search) - } - else { - idty = await this.dal.getWrittenIdtyByUID(search) - } - if (!idty) { - throw constants.ERRORS.NO_MEMBER_MATCHING_PUB_OR_UID; + let memberships: { + blockstamp:string + membership:string + number:number + fpr:string + written_number:number|null + }[] = [] + + if (idty) { + const mss = await this.dal.msDAL.getMembershipsOfIssuer(idty.pub); + const mssFromMindex = await this.dal.mindexDAL.reducable(idty.pub); + memberships = mss.map(m => { + return { + blockstamp: [m.blockNumber, m.blockHash].join('-'), + membership: m.membership, + number: m.blockNumber, + fpr: m.blockHash, + written_number: m.written_number + } + }) + memberships = memberships.concat(mssFromMindex.map((ms:MindexEntry) => { + const sp = ms.created_on.split('-'); + return { + blockstamp: ms.created_on, + membership: ms.leaving ? 'OUT' : 'IN', + number: parseInt(sp[0]), + fpr: sp[1], + written_number: parseInt(ms.written_on) + } + })) } - return DBIdentity.copyFromExisting(idty) - } - getWrittenByPubkey(pubkey:string) { - return this.dal.getWrittenIdtyByPubkey(pubkey) + return { + idty: { + pubkey: idty.pub, + uid: idty.uid, + buid: idty.created_on + }, + memberships + } } getPendingFromPubkey(pubkey:string) { @@ -101,17 +124,17 @@ export class IdentityService extends FIFOService { if (!verified) { throw constants.ERRORS.SIGNATURE_DOES_NOT_MATCH; } - let existing = await this.dal.getIdentityByHashOrNull(toSave.hash); + let existing = await this.dal.getGlobalIdentityByHashForExistence(toSave.hash); if (existing) { throw constants.ERRORS.ALREADY_UP_TO_DATE; } else { // Create if not already written uid/pubkey - let used = await this.dal.getWrittenIdtyByPubkey(idty.pubkey); + let used = await GLOBAL_RULES_HELPERS.checkExistsPubkey(idty.pubkey, this.dal) if (used) { throw constants.ERRORS.PUBKEY_ALREADY_USED; } - used = await this.dal.getWrittenIdtyByUID(idty.uid); + used = await GLOBAL_RULES_HELPERS.checkExistsUserID(idty.uid, this.dal) if (used) { throw constants.ERRORS.UID_ALREADY_USED; } @@ -119,7 +142,7 @@ export class IdentityService extends FIFOService { if (idty.buid == '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855' && current) { throw constants.ERRORS.BLOCKSTAMP_DOES_NOT_MATCH_A_BLOCK; } else if (current) { - let basedBlock = await this.dal.getBlockByBlockstamp(idty.buid); + let basedBlock = await this.dal.getAbsoluteValidBlockInForkWindowByBlockstamp(idty.buid); if (!basedBlock) { throw constants.ERRORS.BLOCKSTAMP_DOES_NOT_MATCH_A_BLOCK; } @@ -128,6 +151,7 @@ export class IdentityService extends FIFOService { await GLOBAL_RULES_FUNCTIONS.checkIdentitiesAreWritable({ identities: [idtyObj.inline()], version: (current && current.version) || constants.BLOCK_GENERATED_VERSION }, this.conf, this.dal); if (byAbsorption !== BY_ABSORPTION) { if (!(await this.dal.idtyDAL.sandbox.acceptNewSandBoxEntry({ + certsCount: 0, issuers: [idty.pubkey], ref_block: parseInt(idty.buid.split('-')[0]) }, this.conf.pair && this.conf.pair.pub))) { @@ -149,35 +173,44 @@ export class IdentityService extends FIFOService { obj.currency = this.conf.currency || obj.currency; const cert = CertificationDTO.fromJSONObject(obj) const targetHash = cert.getTargetHash(); - let idty = await this.dal.getIdentityByHashOrNull(targetHash); + let possiblyNullIdty = await this.dal.getGlobalIdentityByHashForHashingAndSig(targetHash); let idtyAbsorbed = false - if (!idty) { + const idty:{ + pubkey:string + uid:string + buid:string + sig:string + } = possiblyNullIdty !== null ? possiblyNullIdty : await this.submitIdentity({ + pubkey: cert.idty_issuer, + uid: cert.idty_uid, + buid: cert.idty_buid, + sig: cert.idty_sig + }, BY_ABSORPTION); + if (possiblyNullIdty === null) { idtyAbsorbed = true - idty = await this.submitIdentity({ - pubkey: cert.idty_issuer, - uid: cert.idty_uid, - buid: cert.idty_buid, - sig: cert.idty_sig - }, BY_ABSORPTION); } let anErr:any const hash = cert.getHash() return this.pushFIFO<CertificationDTO>(hash, async () => { this.logger.info('⬇ CERT %s block#%s -> %s', cert.from, cert.block_number, idty.uid); try { - await GLOBAL_RULES_HELPERS.checkCertificationIsValid(cert, potentialNext, () => Promise.resolve(idty), this.conf, this.dal); + await GLOBAL_RULES_HELPERS.checkCertificationIsValidInSandbox(cert, potentialNext, () => Promise.resolve(idty), this.conf, this.dal); } catch (e) { anErr = e; } if (!anErr) { try { - let basedBlock = await this.dal.getBlock(cert.block_number); + let basedBlock: Tristamp|null = await this.dal.getTristampOf(cert.block_number); if (cert.block_number == 0 && !basedBlock) { basedBlock = { number: 0, - hash: 'E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855' + hash: 'E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855', + medianTime: 0 }; } + if (!basedBlock) { + throw Error(DataErrors[DataErrors.CERT_BASED_ON_UNKNOWN_BLOCK]) + } const mCert:DBCert = { issuers: [cert.from], from: cert.from, @@ -234,7 +267,7 @@ export class IdentityService extends FIFOService { if (!verified) { throw 'Wrong signature for revocation'; } - const existing = await this.dal.getIdentityByHashOrNull(obj.hash); + const existing = await this.dal.getGlobalIdentityByHashForRevocation(obj.hash) if (existing) { // Modify if (existing.revoked) { @@ -243,7 +276,15 @@ export class IdentityService extends FIFOService { else if (existing.revocation_sig) { throw 'Revocation already registered'; } else { - await this.dal.setRevocating(existing, revoc.revocation); + await this.dal.setRevocating({ + pubkey: existing.pub, + buid: existing.created_on, + sig: existing.sig, + uid: existing.uid, + expires_on: existing.expires_on, + member: existing.member, + wasMember: existing.wasMember, + }, revoc.revocation); this.logger.info('✔ REVOCATION %s %s', revoc.pubkey, revoc.idty_uid); return revoc } diff --git a/app/service/MembershipService.ts b/app/service/MembershipService.ts index a02eaa76866b79045cbe37e650e17940c1e47274..3dfe33fd6d9d27f261c30e088d34167539077795 100644 --- a/app/service/MembershipService.ts +++ b/app/service/MembershipService.ts @@ -19,6 +19,7 @@ import {LOCAL_RULES_HELPERS} from "../lib/rules/local_rules"; import {GLOBAL_RULES_HELPERS} from "../lib/rules/global_rules"; import {MembershipDTO} from "../lib/dto/MembershipDTO"; import {FIFOService} from "./FIFOService"; +import {DBBlock} from "../lib/db/DBBlock" const constants = require('../lib/constants'); @@ -38,7 +39,7 @@ export class MembershipService extends FIFOService { this.logger = require('../lib/logger').NewLogger(this.dal.profile); } - current() { + current(): Promise<DBBlock | null> { return this.dal.getCurrentBlockOrNull() } @@ -89,7 +90,7 @@ export class MembershipService extends FIFOService { idtyHash: entry.getIdtyHash(), written: false, written_number: null, - expires_on: basedBlock ? basedBlock.medianTime + this.conf.msWindow : null, + expires_on: basedBlock ? basedBlock.medianTime + this.conf.msWindow : 0, signature: entry.signature, expired: false, block_number: entry.number diff --git a/app/service/PeeringService.ts b/app/service/PeeringService.ts old mode 100644 new mode 100755 index e72f6a172fb826d12f382eede5e9351b1a84f7a7..41c6660701257fabc01e3c34db4777d3328845df --- a/app/service/PeeringService.ts +++ b/app/service/PeeringService.ts @@ -13,7 +13,6 @@ import {ConfDTO} from "../lib/dto/ConfDTO" import {FileDAL} from "../lib/dal/fileDAL" -import {DBPeer} from "../lib/dal/sqliteDAL/PeerDAL" import {DBBlock} from "../lib/db/DBBlock" import {Multicaster} from "../lib/streams/multicaster" import {PeerDTO} from "../lib/dto/PeerDTO" @@ -22,9 +21,12 @@ import {dos2unix} from "../lib/common-libs/dos2unix" import {rawer} from "../lib/common-libs/index" import {Server} from "../../server" import {GlobalFifoPromise} from "./GlobalFifoPromise" +import {DBPeer} from "../lib/db/DBPeer" +import {Underscore} from "../lib/common-libs/underscore" +import {CommonConstants} from "../lib/common-libs/constants" +import {DataErrors} from "../lib/common-libs/errors" const util = require('util'); -const _ = require('underscore'); const events = require('events'); const logger = require('../lib/logger').NewLogger('peering'); const constants = require('../lib/constants'); @@ -104,10 +106,11 @@ export class PeeringService { } } if (thePeer.block == constants.PEER.SPECIAL_BLOCK) { + thePeer.block = constants.PEER.SPECIAL_BLOCK; thePeer.statusTS = 0; thePeer.status = 'UP'; } else { - block = await this.dal.getBlockByNumberAndHashOrNull(blockNumber, blockHash); + block = await this.dal.getAbsoluteValidBlockInForkWindow(blockNumber, blockHash) if (!block && makeCheckings) { throw constants.ERROR.PEER.UNKNOWN_REFERENCE_BLOCK; } else if (!block) { @@ -115,6 +118,10 @@ export class PeeringService { thePeer.statusTS = 0; thePeer.status = 'UP'; } + const current = await this.dal.getBlockCurrent() + if ((!block && current.number > CommonConstants.MAX_AGE_OF_PEER_IN_BLOCKS) || (block && current.number - block.number > CommonConstants.MAX_AGE_OF_PEER_IN_BLOCKS)) { + throw Error(DataErrors[DataErrors.TOO_OLD_PEER]) + } } sigTime = block ? block.medianTime : 0; thePeer.statusTS = sigTime; @@ -131,8 +138,8 @@ export class PeeringService { const isOutdatedDocument = blockNumber < previousBlockNumber && !eraseIfAlreadyRecorded; const isAlreadyKnown = blockNumber == previousBlockNumber && !eraseIfAlreadyRecorded; if (isOutdatedDocument){ - const error = _.extend({}, constants.ERRORS.NEWER_PEER_DOCUMENT_AVAILABLE); - _.extend(error.uerr, { peer: found }); + const error = Underscore.extend({}, constants.ERRORS.NEWER_PEER_DOCUMENT_AVAILABLE); + Underscore.extend(error.uerr, { peer: found }); throw error; } else if (isAlreadyKnown) { throw constants.ERRORS.PEER_DOCUMENT_ALREADY_KNOWN; @@ -186,7 +193,7 @@ export class PeeringService { return this.server.writePeer(pretendedNewer) } - async generateSelfPeer(theConf:ConfDTO, signalTimeInterval = 0) { + async generateSelfPeer(theConf:{ currency: string }, signalTimeInterval = 0): Promise<DBPeer|null> { const current = await this.server.dal.getCurrentBlockOrNull(); const currency = theConf.currency || constants.DEFAULT_CURRENCY_NAME; const peers = await this.dal.findPeers(this.selfPubkey); @@ -194,11 +201,13 @@ export class PeeringService { version: constants.DOCUMENTS_VERSION, currency: currency, block: '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855', - endpoints: [] + endpoints: <string[]>[] }; const currentSelfPeer = peers[0] if (peers.length != 0 && currentSelfPeer) { - p1 = _(currentSelfPeer).extend({version: constants.DOCUMENTS_VERSION, currency: currency}); + p1 = currentSelfPeer + p1.version = constants.DOCUMENTS_VERSION + p1.currency = currency } const localEndpoints = await this.server.getEndpoints() const otherPotentialEndpoints = this.getOtherEndpoints(p1.endpoints, localEndpoints) @@ -213,7 +222,7 @@ export class PeeringService { logger.error('It seems there is an issue with your configuration.'); logger.error('Please restart your node with:'); logger.error('$ duniter restart'); - return new Promise(() => null); + return null } const endpointsToDeclare = localEndpoints.concat(toConserve).concat(this.conf.endpoints || []) if (currentSelfPeer && endpointsToDeclare.length === 0 && currentSelfPeer.endpoints.length === 0) { @@ -233,13 +242,13 @@ export class PeeringService { } // The number cannot be superior to current block minBlock = Math.min(minBlock, current ? current.number : minBlock); - let targetBlock = await this.server.dal.getBlock(minBlock); + const targetBlockstamp: string|null = await this.server.dal.getBlockstampOf(minBlock) const p2:any = { version: constants.DOCUMENTS_VERSION, currency: currency, pubkey: this.selfPubkey, - block: targetBlock ? [targetBlock.number, targetBlock.hash].join('-') : constants.PEER.SPECIAL_BLOCK, - endpoints: _.uniq(endpointsToDeclare) + block: targetBlockstamp ? targetBlockstamp : constants.PEER.SPECIAL_BLOCK, + endpoints: Underscore.uniq(endpointsToDeclare) }; const raw2 = dos2unix(PeerDTO.fromJSONObject(p2).getRaw()); const bmaAccess = PeerDTO.fromJSONObject(p2).getURL() diff --git a/app/service/TransactionsService.ts b/app/service/TransactionsService.ts index c8608f6e7b47a63924f0ff69290fdf4a604271d0..489ed660e1aaf794f3324cf60a3dab2045350bc5 100644 --- a/app/service/TransactionsService.ts +++ b/app/service/TransactionsService.ts @@ -11,15 +11,15 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; import {ConfDTO} from "../lib/dto/ConfDTO"; import {FileDAL} from "../lib/dal/fileDAL"; import {TransactionDTO} from "../lib/dto/TransactionDTO"; import {LOCAL_RULES_HELPERS} from "../lib/rules/local_rules"; import {GLOBAL_RULES_HELPERS} from "../lib/rules/global_rules"; -import {DBTx} from "../lib/dal/sqliteDAL/TxsDAL"; import {FIFOService} from "./FIFOService"; import {GlobalFifoPromise} from "./GlobalFifoPromise"; +import {DataErrors} from "../lib/common-libs/errors" +import {DBTx} from "../lib/db/DBTx" const constants = require('../lib/constants'); @@ -47,6 +47,9 @@ export class TransactionService extends FIFOService { this.logger.info('⬇ TX %s:%s from %s', tx.output_amount, tx.output_base, tx.issuers); const existing = await this.dal.getTxByHash(tx.hash); const current = await this.dal.getCurrentBlockOrNull(); + if (!current) { + throw Error(DataErrors[DataErrors.NO_TRANSACTION_POSSIBLE_IF_NOT_CURRENT_BLOCK]) + } if (existing) { throw constants.ERRORS.TX_ALREADY_PROCESSED; } diff --git a/bin/duniter b/bin/duniter index 60946d8e66b60339c941fc107373764248fe9a38..d91cfe530ace9b8cea85db550a72ba92ab9d32be 100755 --- a/bin/duniter +++ b/bin/duniter @@ -1,7 +1,6 @@ #!/usr/bin/env node "use strict"; -const co = require('co'); const logger = require("../app/lib/logger").NewLogger(); // Specific errors handling @@ -13,12 +12,12 @@ process.on('uncaughtException', (err) => { } }); -return co(function*() { +(async () => { try { const duniter = require('../index'); const stack = duniter.statics.autoStack(); - yield stack.executeStack(process.argv); + await stack.executeStack(process.argv); // Everything went well, close Duniter quietly. process.exit(); } catch (e) { @@ -29,4 +28,4 @@ return co(function*() { // If we did not succeed to close before, force close with error. process.exit(100); } -}); +})() diff --git a/doc/Protocol.md b/doc/Protocol.md index 62d8f3ddf9276fa0d797354940e5b899cc81dfca..68a4022c4e3affffb3c9499ad8a568a79035ac19 100644 --- a/doc/Protocol.md +++ b/doc/Protocol.md @@ -1,4 +1,4 @@ -# DUP - Duniter Protocol +DifferentIssuersCount# DUP - Duniter Protocol > This document reflects Duniter in-production protocol. It is updated only for clarifications (2017). diff --git a/index.ts b/index.ts index 646e2bf6d44eda8f96e31c50e96818629fa7f3da..92ff49b31faacd701a135bab1ea812a0498244be 100644 --- a/index.ts +++ b/index.ts @@ -21,12 +21,13 @@ import {CrawlerDependency} from "./app/modules/crawler/index" import {BmaDependency} from "./app/modules/bma/index" import {WS2PDependency} from "./app/modules/ws2p/index" import {ProverConstants} from "./app/modules/prover/lib/constants" -import { ProxiesConf } from './app/lib/proxy'; +import {ProxiesConf} from './app/lib/proxy'; import {RouterDependency} from "./app/modules/router" +import {OtherConstants} from "./app/lib/other_constants" +import {Directory} from "./app/lib/system/directory" +import {Underscore} from "./app/lib/common-libs/underscore" const path = require('path'); -const _ = require('underscore'); -const directory = require('./app/lib/system/directory'); const constants = require('./app/lib/constants'); const logger = require('./app/lib/logger').NewLogger('duniter'); @@ -40,6 +41,7 @@ const revertDependency = require('./app/modules/revert'); const daemonDependency = require('./app/modules/daemon'); const pSignalDependency = require('./app/modules/peersignal'); const pluginDependency = require('./app/modules/plugin'); +const dumpDependency = require('./app/modules/dump'); let sigintListening = false @@ -129,7 +131,8 @@ const DEFAULT_DEPENDENCIES = MINIMAL_DEPENDENCIES.concat([ { name: 'duniter-keypair', required: KeypairDependency }, { name: 'duniter-crawler', required: CrawlerDependency }, { name: 'duniter-bma', required: BmaDependency }, - { name: 'duniter-ws2p', required: WS2PDependency } + { name: 'duniter-ws2p', required: WS2PDependency }, + { name: 'duniter-dump', required: dumpDependency }, ]); const PRODUCTION_DEPENDENCIES = DEFAULT_DEPENDENCIES.concat([ @@ -288,9 +291,10 @@ class Stack { const params = args.slice(2); params.pop(); // Don't need the command argument + OtherConstants.SQL_TRACES = program.sqlTraces === true const dbName = program.mdb; const dbHome = program.home; - const home = directory.getHome(dbName, dbHome); + const home = Directory.getHome(dbName, dbHome); if (command.logs === false) { logger.mute(); @@ -349,7 +353,7 @@ class Stack { // Register the configuration hook for saving phase (overrides the saved data) server.dal.saveConfHook = async (conf:ConfDTO) => { - const clonedConf = _.clone(conf); + const clonedConf = Underscore.clone(conf) for (const callback of this.configBeforeSaveCallbacks) { await callback(clonedConf, program, logger, server.dal.confDAL); } diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json new file mode 100644 index 0000000000000000000000000000000000000000..66a98a3bb7a4bf3c336fdcd8b43d9e0c789a5c78 --- /dev/null +++ b/npm-shrinkwrap.json @@ -0,0 +1,10697 @@ +{ + "name": "duniter", + "version": "1.6.25", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/events": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", + "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==" + }, + "@types/fs-extra": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-5.0.1.tgz", + "integrity": "sha512-h3wnflb+jMTipvbbZnClgA2BexrT4w0GcfoCz5qyxd0IRsbqhLSyesM6mqZTAnhbVmhyTm5tuxfRu9R+8l+lGw==", + "requires": { + "@types/node": "10.5.1" + }, + "dependencies": { + "@types/node": { + "version": "10.5.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.5.1.tgz", + "integrity": "sha512-AFLl1IALIuyt6oK4AYZsgWVJ/5rnyzQWud7IebaZWWV3YmgtPZkQmYio9R5Ze/2pdd7XfqF5bP+hWS11mAKoOQ==" + } + } + }, + "@types/glob": { + "version": "5.0.35", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-5.0.35.tgz", + "integrity": "sha512-wc+VveszMLyMWFvXLkloixT4n0harUIVZjnpzztaZ0nKLuul7Z32iMt2fUFGAaZ4y1XWjFRMtCI5ewvyh4aIeg==", + "requires": { + "@types/events": "1.2.0", + "@types/minimatch": "3.0.3", + "@types/node": "10.5.1" + }, + "dependencies": { + "@types/node": { + "version": "10.5.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.5.1.tgz", + "integrity": "sha512-AFLl1IALIuyt6oK4AYZsgWVJ/5rnyzQWud7IebaZWWV3YmgtPZkQmYio9R5Ze/2pdd7XfqF5bP+hWS11mAKoOQ==" + } + } + }, + "@types/handlebars": { + "version": "4.0.36", + "resolved": "https://registry.npmjs.org/@types/handlebars/-/handlebars-4.0.36.tgz", + "integrity": "sha512-LjNiTX7TY7wtuC6y3QwC93hKMuqYhgV9A1uXBKNvZtVC8ZvyWAjZkJ5BvT0K7RKqORRYRLMrqCxpw5RgS+MdrQ==" + }, + "@types/highlight.js": { + "version": "9.12.2", + "resolved": "https://registry.npmjs.org/@types/highlight.js/-/highlight.js-9.12.2.tgz", + "integrity": "sha512-y5x0XD/WXDaGSyiTaTcKS4FurULJtSiYbGTeQd0m2LYZGBcZZ/7fM6t5H/DzeUF+kv8y6UfmF6yJABQsHcp9VQ==" + }, + "@types/lodash": { + "version": "4.14.104", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.104.tgz", + "integrity": "sha512-ufQcVg4daO8xQ5kopxRHanqFdL4AI7ondQkV+2f+7mz3gvp0LkBx2zBRC6hfs3T87mzQFmf5Fck7Fi145Ul6NQ==" + }, + "@types/lokijs": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@types/lokijs/-/lokijs-1.5.2.tgz", + "integrity": "sha512-ZF14v1P1Bjbw8VJRu+p4WS9V926CAOjWF4yq23QmSBWRPe0/GXlUKzSxjP1fi/xi8nrq6zr9ECo8Z/8KsRqroQ==", + "dev": true + }, + "@types/marked": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-0.3.0.tgz", + "integrity": "sha512-CSf9YWJdX1DkTNu9zcNtdCcn6hkRtB5ILjbhRId4ZOQqx30fXmdecuaXhugQL6eyrhuXtaHJ7PHI+Vm7k9ZJjg==" + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" + }, + "@types/mocha": { + "version": "2.2.48", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.48.tgz", + "integrity": "sha512-nlK/iyETgafGli8Zh9zJVCTicvU3iajSkRwOh3Hhiva598CMqNJ4NcVCGMTGKpGpTYj/9R8RLzS9NAykSSCqGw==", + "dev": true + }, + "@types/node": { + "version": "8.10.20", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.20.tgz", + "integrity": "sha512-M7x8+5D1k/CuA6jhiwuSCmE8sbUWJF0wYsjcig9WrXvwUI5ArEoUBdOXpV4JcEMrLp02/QbDjw+kI+vQeKyQgg==", + "dev": true + }, + "@types/shelljs": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.7.8.tgz", + "integrity": "sha512-M2giRw93PxKS7YjU6GZjtdV9HASdB7TWqizBXe4Ju7AqbKlWvTr0gNO92XH56D/gMxqD/jNHLNfC5hA34yGqrQ==", + "requires": { + "@types/glob": "5.0.35", + "@types/node": "10.5.1" + }, + "dependencies": { + "@types/node": { + "version": "10.5.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.5.1.tgz", + "integrity": "sha512-AFLl1IALIuyt6oK4AYZsgWVJ/5rnyzQWud7IebaZWWV3YmgtPZkQmYio9R5Ze/2pdd7XfqF5bP+hWS11mAKoOQ==" + } + } + }, + "@types/should": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@types/should/-/should-8.3.0.tgz", + "integrity": "sha1-4rRgJDaF2+N3GC857zjTf00Veto=", + "dev": true + }, + "JSONSelect": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/JSONSelect/-/JSONSelect-0.4.0.tgz", + "integrity": "sha1-oI7cxn6z/L6Z7WMIVTRKDPKCu40=" + }, + "JSV": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/JSV/-/JSV-4.0.2.tgz", + "integrity": "sha1-0Hf2glVx+CEy+d/67Vh7QCn+/1c=" + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "accept-encoding": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/accept-encoding/-/accept-encoding-0.1.0.tgz", + "integrity": "sha1-XdiLjfcfHcLlzGuVZezOHjmaMz4=" + }, + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "requires": { + "mime-types": "2.1.18", + "negotiator": "0.6.1" + } + }, + "acorn": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.1.tgz", + "integrity": "sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ==", + "dev": true + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "3.3.0" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, + "agent-base": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.0.tgz", + "integrity": "sha512-c+R/U5X+2zz2+UCrCFv6odQzJdoqI+YecuhnAJLa1zYaMc13zPfwMwZrr91Pd1DYNo/yPRbiM4WVf9whgwFsIg==", + "requires": { + "es6-promisify": "5.0.0" + } + }, + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } + }, + "ajv-keywords": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", + "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", + "dev": true + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "requires": { + "kind-of": "3.2.2", + "longest": "1.0.1", + "repeat-string": "1.6.1" + } + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" + }, + "ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=" + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "archiver": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-1.3.0.tgz", + "integrity": "sha1-TyGU1tj5nfP1MeaIHxTxXVX6ryI=", + "requires": { + "archiver-utils": "1.3.0", + "async": "2.2.0", + "buffer-crc32": "0.2.13", + "glob": "7.1.2", + "lodash": "4.17.10", + "readable-stream": "2.3.6", + "tar-stream": "1.6.1", + "walkdir": "0.0.11", + "zip-stream": "1.2.0" + } + }, + "archiver-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-1.3.0.tgz", + "integrity": "sha1-5QtMCccL89aA4y/xt5lOn52JUXQ=", + "requires": { + "glob": "7.1.2", + "graceful-fs": "4.1.11", + "lazystream": "1.0.0", + "lodash": "4.17.10", + "normalize-path": "2.1.1", + "readable-stream": "2.3.6" + } + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.3.6" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "1.0.3" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "1.0.3" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + }, + "assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" + }, + "async": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.2.0.tgz", + "integrity": "sha1-wyTroBCiN+T71VoS3uhjZ9XA7zI=", + "requires": { + "lodash": "4.17.10" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" + }, + "aws4": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", + "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" + }, + "dependencies": { + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + } + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base-x": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.4.tgz", + "integrity": "sha512-UYOadoSIkEI/VrRGSG6qp93rp2WdokiAiNYDfGW5qURAY8GiAQkvMbwNNSDYiVJopqv4gCna7xqf4rrNGp+5AA==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "basic-auth": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz", + "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "optional": true, + "requires": { + "tweetnacl": "0.14.3" + } + }, + "best-encoding": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/best-encoding/-/best-encoding-0.1.1.tgz", + "integrity": "sha1-GVIT2rysBFgYuAe3ox+Dn63cl04=", + "requires": { + "accept-encoding": "0.1.0" + } + }, + "binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=", + "requires": { + "buffers": "0.1.1", + "chainsaw": "0.1.0" + } + }, + "bindings": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz", + "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE=" + }, + "bl": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", + "requires": { + "readable-stream": "2.3.6", + "safe-buffer": "5.1.2" + } + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "requires": { + "inherits": "2.0.3" + } + }, + "bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" + }, + "body-parser": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.17.1.tgz", + "integrity": "sha1-dbO8mN3W5+DY/+dQ36ylxmmT+kc=", + "requires": { + "bytes": "2.4.0", + "content-type": "1.0.4", + "debug": "2.6.1", + "depd": "1.1.2", + "http-errors": "1.6.3", + "iconv-lite": "0.4.15", + "on-finished": "2.3.0", + "qs": "6.4.0", + "raw-body": "2.2.0", + "type-is": "1.6.16" + } + }, + "boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "requires": { + "hoek": "2.16.3" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", + "requires": { + "base-x": "3.0.4" + } + }, + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "requires": { + "buffer-alloc-unsafe": "1.1.0", + "buffer-fill": "1.0.0" + } + }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" + }, + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=" + }, + "buffer-from": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz", + "integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ==", + "dev": true + }, + "buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=" + }, + "busboy": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", + "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", + "requires": { + "dicer": "0.2.5", + "readable-stream": "1.1.14" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "bytes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", + "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "optional": true + }, + "caseless": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.3.0.tgz", + "integrity": "sha1-U06XkWOH07cGtk/eu6xGQ4RQk08=" + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "optional": true, + "requires": { + "align-text": "0.1.4", + "lazy-cache": "1.0.4" + } + }, + "chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=", + "requires": { + "traverse": "0.3.9" + }, + "dependencies": { + "traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=" + } + } + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=" + }, + "charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" + }, + "charm": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", + "integrity": "sha1-BsIe7RobBq62dVPNxT4jJ0usIpY=" + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "cjson": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/cjson/-/cjson-0.3.0.tgz", + "integrity": "sha1-5kObkHA9MS/24iJAl76pLOPQKhQ=", + "requires": { + "jsonlint": "1.6.0" + } + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "requires": { + "restore-cursor": "2.0.0" + } + }, + "cli-table": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz", + "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=", + "requires": { + "colors": "1.0.3" + }, + "dependencies": { + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" + } + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=" + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "optional": true, + "requires": { + "center-align": "0.1.3", + "right-align": "0.1.3", + "wordwrap": "0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "optional": true + } + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "collections": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/collections/-/collections-0.2.2.tgz", + "integrity": "sha1-HyMCay7zb5J+7MkB6ZxfDUj6M04=", + "requires": { + "weak-map": "1.0.0" + } + }, + "color-convert": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", + "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", + "dev": true, + "requires": { + "color-name": "1.1.1" + } + }, + "color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", + "dev": true + }, + "colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=" + }, + "combined-stream": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "requires": { + "delayed-stream": "1.0.0" + } + }, + "commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "requires": { + "graceful-readlink": "1.0.1" + } + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "compress-commons": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-1.2.2.tgz", + "integrity": "sha1-UkqfEJA/OoEzibAiXSfEi7dRiQ8=", + "requires": { + "buffer-crc32": "0.2.13", + "crc32-stream": "2.0.0", + "normalize-path": "2.1.1", + "readable-stream": "2.3.6" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "1.1.0", + "inherits": "2.0.3", + "readable-stream": "2.3.6", + "typedarray": "0.0.6" + } + }, + "connect-busboy": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/connect-busboy/-/connect-busboy-0.0.2.tgz", + "integrity": "sha1-rFyclmchcYheV2xmsr/ZXTuxEJc=", + "requires": { + "busboy": "0.2.14" + } + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cors": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.2.tgz", + "integrity": "sha1-Omwx9aOYyHOU4xVVtifqNSODkIA=", + "requires": { + "object-assign": "4.1.1", + "vary": "1.1.2" + } + }, + "coveralls": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-2.11.4.tgz", + "integrity": "sha1-tC9OFW9rqUGdJ0NKQokJTyGSZ/c=", + "dev": true, + "requires": { + "js-yaml": "3.0.1", + "lcov-parse": "0.0.6", + "log-driver": "1.2.4", + "request": "2.40.0" + }, + "dependencies": { + "argparse": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz", + "integrity": "sha1-z9AeD7uj1srtBJ+9dY1A9lGW9Xw=", + "dev": true, + "requires": { + "underscore": "1.7.0", + "underscore.string": "2.4.0" + } + }, + "asn1": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz", + "integrity": "sha1-VZvhg3bQik7E2+gId9J4GGObLfc=", + "dev": true, + "optional": true + }, + "assert-plus": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz", + "integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA=", + "dev": true, + "optional": true + }, + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true, + "optional": true + }, + "aws-sign2": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz", + "integrity": "sha1-xXED96F/wDfwLXwuZLYC6iI/fWM=", + "dev": true, + "optional": true + }, + "boom": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/boom/-/boom-0.4.2.tgz", + "integrity": "sha1-emNune1O/O+xnO9JR6PGffrukRs=", + "dev": true, + "requires": { + "hoek": "0.9.1" + } + }, + "combined-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", + "integrity": "sha1-ATfmV7qlp1QcV6w3rF/AfXO03B8=", + "dev": true, + "optional": true, + "requires": { + "delayed-stream": "0.0.5" + } + }, + "cryptiles": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-0.2.2.tgz", + "integrity": "sha1-7ZH/HxetE9N0gohZT4pIoNJvMlw=", + "dev": true, + "optional": true, + "requires": { + "boom": "0.4.2" + } + }, + "delayed-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz", + "integrity": "sha1-1LH0OpPoKW3+AmlPRoC8N6MTxz8=", + "dev": true, + "optional": true + }, + "esprima": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=", + "dev": true + }, + "forever-agent": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.5.2.tgz", + "integrity": "sha1-bQ4JxJIflKJ/Y9O0nF/v8epMUTA=", + "dev": true + }, + "form-data": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.1.4.tgz", + "integrity": "sha1-kavXiKupcCsaq/qLwBAxoqyeOxI=", + "dev": true, + "optional": true, + "requires": { + "async": "0.9.2", + "combined-stream": "0.0.7", + "mime": "1.2.11" + } + }, + "hawk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-1.1.1.tgz", + "integrity": "sha1-h81JH5tG5OKurKM1QWdmiF0tHtk=", + "dev": true, + "optional": true, + "requires": { + "boom": "0.4.2", + "cryptiles": "0.2.2", + "hoek": "0.9.1", + "sntp": "0.2.4" + } + }, + "hoek": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.9.1.tgz", + "integrity": "sha1-PTIkYrrfB3Fup+uFuviAec3c5QU=", + "dev": true + }, + "http-signature": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz", + "integrity": "sha1-T72sEyVZqoMjEh5UB3nAoBKyfmY=", + "dev": true, + "optional": true, + "requires": { + "asn1": "0.1.11", + "assert-plus": "0.1.5", + "ctype": "0.5.3" + } + }, + "js-yaml": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.0.1.tgz", + "integrity": "sha1-dkBf6lvOMPyPQF1Ixtyn8KMsav4=", + "dev": true, + "requires": { + "argparse": "0.1.16", + "esprima": "1.0.4" + } + }, + "mime-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz", + "integrity": "sha1-mVrhOSq4r/y/yyZB3QVOlDwNXc4=", + "dev": true + }, + "oauth-sign": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.3.0.tgz", + "integrity": "sha1-y1QPk7srIqfVlBaRoojWDo6pOG4=", + "dev": true, + "optional": true + }, + "qs": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-1.0.2.tgz", + "integrity": "sha1-UKk+K1r2aRwxvOpdrnjubqGQN2g=", + "dev": true + }, + "request": { + "version": "2.40.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.40.0.tgz", + "integrity": "sha1-TdZw9pbx5uhC5mtLXoOTAaub62c=", + "dev": true, + "requires": { + "aws-sign2": "0.5.0", + "forever-agent": "0.5.2", + "form-data": "0.1.4", + "hawk": "1.1.1", + "http-signature": "0.10.1", + "json-stringify-safe": "5.0.1", + "mime-types": "1.0.2", + "node-uuid": "1.4.8", + "oauth-sign": "0.3.0", + "qs": "1.0.2", + "stringstream": "0.0.6", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.4.3" + } + }, + "sntp": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-0.2.4.tgz", + "integrity": "sha1-+4hfGLDzqtGJ+CSGJTa87ux1CQA=", + "dev": true, + "optional": true, + "requires": { + "hoek": "0.9.1" + } + }, + "tunnel-agent": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", + "dev": true, + "optional": true + }, + "underscore": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", + "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=", + "dev": true + } + } + }, + "crc": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.5.0.tgz", + "integrity": "sha1-mLi6fUiWZbo5efWbITgTdBAaGWQ=" + }, + "crc32-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-2.0.0.tgz", + "integrity": "sha1-483TtN8xaN10494/u8t7KX/pCPQ=", + "requires": { + "crc": "3.5.0", + "readable-stream": "2.3.6" + } + }, + "crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" + }, + "cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "requires": { + "boom": "2.10.1" + } + }, + "ctype": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz", + "integrity": "sha1-gsGMJGH3QRTvFsE1IkrQuRRMoS8=", + "dev": true, + "optional": true + }, + "cycle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", + "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=" + }, + "d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "dev": true, + "requires": { + "es5-ext": "0.10.45" + } + }, + "daemonize2": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/daemonize2/-/daemonize2-0.4.2.tgz", + "integrity": "sha1-xuR0B4vs+n16gUACUUxHEvZP4G8=" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "ddos": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/ddos/-/ddos-0.1.16.tgz", + "integrity": "sha1-RcfMd+vNP3pBpQDSPZbb6yvKNMk=", + "requires": { + "hashish": "0.0.4", + "response": "0.18.0" + } + }, + "debug": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.1.tgz", + "integrity": "sha1-eYVQkLosTjEVzH2HaUkdWPBJE1E=", + "requires": { + "ms": "0.7.2" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "optional": true + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "5.0.0", + "is-path-cwd": "1.0.0", + "is-path-in-cwd": "1.0.1", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "rimraf": "2.6.2" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "dicer": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", + "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", + "requires": { + "readable-stream": "1.1.14", + "streamsearch": "0.1.2" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "diff": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", + "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", + "dev": true + }, + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "2.0.2", + "isarray": "1.0.0" + }, + "dependencies": { + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + } + } + }, + "duplexer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" + }, + "ebnf-parser": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/ebnf-parser/-/ebnf-parser-0.1.10.tgz", + "integrity": "sha1-zR9rpHfFY4xAyX7ZtXLbW6tdgzE=" + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "requires": { + "once": "1.4.0" + } + }, + "errorhandler": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.0.tgz", + "integrity": "sha1-6rpkyl1UKjEayUX1gt78M2Fl2fQ=", + "requires": { + "accepts": "1.3.5", + "escape-html": "1.0.3" + } + }, + "es5-ext": { + "version": "0.10.45", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.45.tgz", + "integrity": "sha512-FkfM6Vxxfmztilbxxz5UKSD4ICMf5tSpRFtDNtkAhOxZ0EKtX6qwmXNyH/sFyIbX2P/nU5AMiA9jilWsUGJzCQ==", + "dev": true, + "requires": { + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1", + "next-tick": "1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.45", + "es6-symbol": "3.1.1" + } + }, + "es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.45", + "es6-iterator": "2.0.3", + "es6-set": "0.1.5", + "es6-symbol": "3.1.1", + "event-emitter": "0.3.5" + } + }, + "es6-promise": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", + "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==" + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "requires": { + "es6-promise": "4.2.4" + } + }, + "es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.45", + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1", + "event-emitter": "0.3.5" + } + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.45" + } + }, + "es6-weak-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", + "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.45", + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "escodegen": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.3.3.tgz", + "integrity": "sha1-8CQBb1qI4Eb9EgBQVek5gC5sXyM=", + "requires": { + "esprima": "1.1.1", + "estraverse": "1.5.1", + "esutils": "1.0.0", + "source-map": "0.1.43" + } + }, + "escope": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "dev": true, + "requires": { + "es6-map": "0.1.5", + "es6-weak-map": "2.0.2", + "esrecurse": "4.2.1", + "estraverse": "4.2.0" + }, + "dependencies": { + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + } + } + }, + "eslint": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.13.1.tgz", + "integrity": "sha1-Vk0mRrXv3thd+WmFMy7dkaI7/yU=", + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "chalk": "1.1.3", + "concat-stream": "1.6.2", + "debug": "2.6.1", + "doctrine": "1.5.0", + "escope": "3.6.0", + "espree": "3.5.4", + "estraverse": "4.2.0", + "esutils": "2.0.2", + "file-entry-cache": "2.0.0", + "glob": "7.1.2", + "globals": "9.18.0", + "ignore": "3.3.10", + "imurmurhash": "0.1.4", + "inquirer": "0.12.0", + "is-my-json-valid": "2.17.2", + "is-resolvable": "1.1.0", + "js-yaml": "3.8.2", + "json-stable-stringify": "1.0.1", + "levn": "0.3.0", + "lodash": "4.17.10", + "mkdirp": "0.5.1", + "natural-compare": "1.4.0", + "optionator": "0.8.2", + "path-is-inside": "1.0.2", + "pluralize": "1.2.1", + "progress": "1.1.8", + "require-uncached": "1.0.3", + "shelljs": "0.7.8", + "strip-bom": "3.0.0", + "strip-json-comments": "2.0.1", + "table": "3.8.3", + "text-table": "0.2.0", + "user-home": "2.0.0" + }, + "dependencies": { + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true, + "requires": { + "restore-cursor": "1.0.1" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5", + "object-assign": "4.1.1" + } + }, + "inquirer": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", + "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", + "dev": true, + "requires": { + "ansi-escapes": "1.4.0", + "ansi-regex": "2.1.1", + "chalk": "1.1.3", + "cli-cursor": "1.0.2", + "cli-width": "2.2.0", + "figures": "1.7.0", + "lodash": "4.17.10", + "readline2": "1.0.1", + "run-async": "0.1.0", + "rx-lite": "3.1.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "through": "2.3.8" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", + "dev": true + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true, + "requires": { + "exit-hook": "1.1.1", + "onetime": "1.1.0" + } + }, + "run-async": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", + "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", + "dev": true, + "requires": { + "once": "1.4.0" + } + }, + "shelljs": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", + "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", + "dev": true, + "requires": { + "glob": "7.1.2", + "interpret": "1.1.0", + "rechoir": "0.6.2" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + } + } + }, + "eslint-plugin-mocha": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-4.8.0.tgz", + "integrity": "sha1-diezWmHlpyBBLaluqwbw4DodzbY=", + "dev": true, + "requires": { + "ramda": "0.22.1" + } + }, + "espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "dev": true, + "requires": { + "acorn": "5.7.1", + "acorn-jsx": "3.0.1" + } + }, + "esprima": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.1.1.tgz", + "integrity": "sha1-W28VR/TRAuZw4UDFCb5ncdautUk=" + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "4.2.0" + }, + "dependencies": { + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + } + } + }, + "estraverse": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.5.1.tgz", + "integrity": "sha1-hno+jlip+EYYr7bC3bzZFrfLr3E=" + }, + "estraverse-fb": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/estraverse-fb/-/estraverse-fb-1.3.2.tgz", + "integrity": "sha1-0yOky15awzHOoDNBOpJT4WQ+B8Q=", + "dev": true + }, + "esutils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.0.0.tgz", + "integrity": "sha1-gVHTWOIMisx/t0XnRywAJf5JZXA=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.45" + } + }, + "event-stream": { + "version": "3.3.4", + "resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "requires": { + "duplexer": "0.1.1", + "from": "0.1.7", + "map-stream": "0.1.0", + "pause-stream": "0.0.11", + "split": "0.3.3", + "stream-combiner": "0.0.4", + "through": "2.3.8" + } + }, + "exit-hook": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", + "dev": true + }, + "express": { + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.15.2.tgz", + "integrity": "sha1-rxB/wUhQRFfy3Kmm8lcdcSm5ezU=", + "requires": { + "accepts": "1.3.5", + "array-flatten": "1.1.1", + "content-disposition": "0.5.2", + "content-type": "1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.1", + "depd": "1.1.2", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "etag": "1.8.1", + "finalhandler": "1.0.6", + "fresh": "0.5.0", + "merge-descriptors": "1.0.1", + "methods": "1.1.2", + "on-finished": "2.3.0", + "parseurl": "1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "1.1.5", + "qs": "6.4.0", + "range-parser": "1.2.0", + "send": "0.15.1", + "serve-static": "1.12.1", + "setprototypeof": "1.0.3", + "statuses": "1.3.1", + "type-is": "1.6.16", + "utils-merge": "1.0.0", + "vary": "1.1.2" + }, + "dependencies": { + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + } + } + }, + "express-fileupload": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-0.0.5.tgz", + "integrity": "sha1-QzpxJSWvqYtMkxYlIui/ecaNguc=", + "requires": { + "connect-busboy": "0.0.2", + "fs-extra": "0.22.1", + "streamifier": "0.1.1" + } + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + }, + "external-editor": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "requires": { + "chardet": "0.4.2", + "iconv-lite": "0.4.23", + "tmp": "0.0.33" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "requires": { + "safer-buffer": "2.1.2" + } + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "requires": { + "os-tmpdir": "1.0.2" + } + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "requires": { + "escape-string-regexp": "1.0.5" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "1.3.0", + "object-assign": "4.1.1" + } + }, + "finalhandler": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.6.tgz", + "integrity": "sha1-AHrqM9Gk0+QgF/YkhIrVjSEvgU8=", + "requires": { + "debug": "2.6.9", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "on-finished": "2.3.0", + "parseurl": "1.3.2", + "statuses": "1.3.1", + "unpipe": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + } + } + }, + "flat-cache": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", + "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "dev": true, + "requires": { + "circular-json": "0.3.3", + "del": "2.2.2", + "graceful-fs": "4.1.11", + "write": "0.2.1" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.6", + "mime-types": "2.1.18" + } + }, + "formidable": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", + "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==" + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz", + "integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44=" + }, + "from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=" + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "fs-extra": { + "version": "0.22.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.22.1.tgz", + "integrity": "sha1-X9b4BJ3JdsoZ6yNV1lgXPKvM4FY=", + "requires": { + "graceful-fs": "4.1.11", + "jsonfile": "2.4.0", + "rimraf": "2.6.2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fstream": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.2" + } + }, + "fstream-ignore": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", + "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=", + "requires": { + "fstream": "1.0.11", + "inherits": "2.0.3", + "minimatch": "3.0.4" + } + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.3" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "1.0.1" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + } + } + }, + "generate-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", + "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", + "dev": true + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "dev": true, + "requires": { + "is-property": "1.0.2" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "1.0.2", + "arrify": "1.0.1", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=" + }, + "growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "dev": true + }, + "handlebars": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", + "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", + "requires": { + "async": "1.5.2", + "optimist": "0.6.1", + "source-map": "0.4.4", + "uglify-js": "2.8.29" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "requires": { + "amdefine": "1.0.1" + } + } + } + }, + "har-schema": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", + "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=" + }, + "har-validator": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", + "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", + "requires": { + "ajv": "4.11.8", + "har-schema": "1.0.5" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "2.1.1" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "hashish": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/hashish/-/hashish-0.0.4.tgz", + "integrity": "sha1-bWC8b/r3Ebav1g5CbQd5iAFOZVQ=", + "requires": { + "traverse": "0.6.6" + } + }, + "hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "highlight.js": { + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.12.0.tgz", + "integrity": "sha1-5tnb5Xy+/mB1HwKvM2GVhwyQwB4=" + }, + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" + }, + "homedir-polyfill": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", + "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", + "dev": true, + "requires": { + "parse-passwd": "1.0.0" + } + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": "1.5.0" + } + }, + "http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.1", + "sshpk": "1.14.2" + } + }, + "iconv-lite": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", + "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=" + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "inquirer": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.0.6.tgz", + "integrity": "sha1-4EqqnQW3o8ubD0B9BDdfBEcZA0c=", + "requires": { + "ansi-escapes": "1.4.0", + "chalk": "1.1.3", + "cli-cursor": "2.1.0", + "cli-width": "2.2.0", + "external-editor": "2.2.0", + "figures": "2.0.0", + "lodash": "4.17.10", + "mute-stream": "0.0.7", + "run-async": "2.3.0", + "rx": "4.1.0", + "string-width": "2.1.1", + "strip-ansi": "3.0.1", + "through": "2.3.8" + } + }, + "interpret": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=" + }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" + }, + "ipaddr.js": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.4.0.tgz", + "integrity": "sha1-KWrKh4qCGBbluF0KKFqZvP9FgvA=" + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "is-my-ip-valid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", + "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", + "dev": true + }, + "is-my-json-valid": { + "version": "2.17.2", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz", + "integrity": "sha512-IBhBslgngMQN8DDSppmgDv7RNrlFotuuDsKcrCP3+HbFaVivIBU7u9oiiErw8sH4ynx3+gOGQ3q2otkgiSi6kg==", + "dev": true, + "requires": { + "generate-function": "2.0.0", + "generate-object-property": "1.2.0", + "is-my-ip-valid": "1.0.0", + "jsonpointer": "4.0.1", + "xtend": "4.0.1" + } + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "requires": { + "is-path-inside": "1.0.1" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "1.0.2" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jison": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/jison/-/jison-0.4.17.tgz", + "integrity": "sha1-vBLUbFhF5v7onM81vSqMxz66F/M=", + "requires": { + "JSONSelect": "0.4.0", + "cjson": "0.3.0", + "ebnf-parser": "0.1.10", + "escodegen": "1.3.3", + "esprima": "1.1.1", + "jison-lex": "0.3.4", + "lex-parser": "0.1.4", + "nomnom": "1.5.2" + } + }, + "jison-lex": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/jison-lex/-/jison-lex-0.3.4.tgz", + "integrity": "sha1-gcoo2E+ESZ36jFlNzePYo/Jux6U=", + "requires": { + "lex-parser": "0.1.4", + "nomnom": "1.5.2" + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.8.2.tgz", + "integrity": "sha1-AtPiwPa+qyAkjUEsNSIDgn14ZyE=", + "requires": { + "argparse": "1.0.10", + "esprima": "3.1.3" + }, + "dependencies": { + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" + } + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "requires": { + "jsonify": "0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "json3": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "dev": true + }, + "jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "requires": { + "graceful-fs": "4.1.11" + } + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" + }, + "jsonlint": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/jsonlint/-/jsonlint-1.6.0.tgz", + "integrity": "sha1-iKpGvCiaesk7tGyuLVihh6m7SUo=", + "requires": { + "JSV": "4.0.2", + "nomnom": "1.5.2" + } + }, + "jsonpointer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "optional": true + }, + "lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "requires": { + "readable-stream": "2.3.6" + } + }, + "lcov-parse": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-0.0.6.tgz", + "integrity": "sha1-gZ5dqL8HkfnT857qXtGGgYfxEXU=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2", + "type-check": "0.3.2" + } + }, + "lex-parser": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/lex-parser/-/lex-parser-0.1.4.tgz", + "integrity": "sha1-ZMTwJfF/1Tv7RXY/rrFvAVp0dVA=" + }, + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + }, + "lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "dev": true, + "requires": { + "lodash._basecopy": "3.0.1", + "lodash.keys": "3.1.2" + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._basecreate": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", + "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash.create": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", + "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", + "dev": true, + "requires": { + "lodash._baseassign": "3.2.0", + "lodash._basecreate": "3.0.3", + "lodash._isiterateecall": "3.0.9" + } + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "3.9.1", + "lodash.isarguments": "3.1.0", + "lodash.isarray": "3.0.4" + } + }, + "log-driver": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.4.tgz", + "integrity": "sha1-LWLX+u9F2KcTQZYaBLB2HsqZz6M=", + "dev": true + }, + "lokijs": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/lokijs/-/lokijs-1.5.5.tgz", + "integrity": "sha1-HCH4KvdXkDf63nueSBNIXCNwi7Y=" + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" + }, + "make-error": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.4.tgz", + "integrity": "sha512-0Dab5btKVPhibSalc9QGXb559ED7G7iLjFXBaj9Wq8O3vorueR5K5jaE3hkG6ZQINyhA/JgG6Qk4qdFQjsYV6g==", + "dev": true + }, + "map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=" + }, + "marked": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", + "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==" + }, + "match-stream": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/match-stream/-/match-stream-0.0.2.tgz", + "integrity": "sha1-mesFAJOzTf+t5CG5rAtBCpz6F88=", + "requires": { + "buffers": "0.1.1", + "readable-stream": "1.0.34" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "merkle": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/merkle/-/merkle-0.5.1.tgz", + "integrity": "sha1-QuI9j28ToDWRJwScGA8rxb2W8Cg=", + "requires": { + "optimist": "0.6.1", + "through": "2.3.6" + }, + "dependencies": { + "through": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.6.tgz", + "integrity": "sha1-JmgcD1JGcQIdTinffDa84tDs8ug=" + } + } + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", + "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=" + }, + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + }, + "mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "requires": { + "mime-db": "1.33.0" + } + }, + "mimeparse": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/mimeparse/-/mimeparse-0.1.4.tgz", + "integrity": "sha1-2vsCdSNw/SJgk64xUsJxrwGsJUo=" + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + } + } + }, + "mocha": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz", + "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==", + "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.9.0", + "debug": "2.6.8", + "diff": "3.2.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.1", + "growl": "1.9.2", + "he": "1.1.1", + "json3": "3.3.2", + "lodash.create": "3.1.1", + "mkdirp": "0.5.1", + "supports-color": "3.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "supports-color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", + "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "dev": true, + "requires": { + "has-flag": "1.0.0" + } + } + } + }, + "mocha-eslint": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/mocha-eslint/-/mocha-eslint-0.1.7.tgz", + "integrity": "sha1-tOm+Vroz9AjYOMqif1wzqfLkCZM=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "eslint": "0.21.2", + "glob": "5.0.5" + }, + "dependencies": { + "ansi-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz", + "integrity": "sha1-QchHGUZGN15qGl0Qw8oFTvn8mA0=", + "dev": true + }, + "cli-width": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-1.1.1.tgz", + "integrity": "sha1-pNKT72frt7iNSk1CwMzwDE0eNm0=", + "dev": true + }, + "doctrine": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-0.6.4.tgz", + "integrity": "sha1-gUKEkalC7xiwSSBW7aOADu5X1h0=", + "dev": true, + "requires": { + "esutils": "1.1.6", + "isarray": "0.0.1" + } + }, + "eslint": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-0.21.2.tgz", + "integrity": "sha1-vt3yR4ANSGf2sQUdIkvbg8LSAcc=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "concat-stream": "1.6.2", + "debug": "2.6.1", + "doctrine": "0.6.4", + "escape-string-regexp": "1.0.5", + "escope": "3.6.0", + "espree": "2.2.5", + "estraverse": "2.0.0", + "estraverse-fb": "1.3.2", + "globals": "6.4.1", + "inquirer": "0.8.5", + "js-yaml": "3.8.2", + "minimatch": "2.0.10", + "mkdirp": "0.5.1", + "object-assign": "2.1.1", + "optionator": "0.5.0", + "path-is-absolute": "1.0.1", + "strip-json-comments": "1.0.4", + "text-table": "0.2.0", + "user-home": "1.1.1", + "xml-escape": "1.0.0" + } + }, + "espree": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/espree/-/espree-2.2.5.tgz", + "integrity": "sha1-32kbkxCIlAKuspzAZnCMVmkLhUs=", + "dev": true + }, + "estraverse": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-2.0.0.tgz", + "integrity": "sha1-WuRpYyQ2ACBmdMyySgnhZnT83KE=", + "dev": true + }, + "esutils": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.1.6.tgz", + "integrity": "sha1-wBzKqa5LiXxtDD4hCuUvPHqEQ3U=", + "dev": true + }, + "fast-levenshtein": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.0.7.tgz", + "integrity": "sha1-AXjc3uAjuSkFGTrwlZ6KdjnP3Lk=", + "dev": true + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5", + "object-assign": "4.1.1" + }, + "dependencies": { + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + } + } + }, + "glob": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.5.tgz", + "integrity": "sha1-eEQx5OKakArg1H+6aqHH8WqOffc=", + "dev": true, + "requires": { + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "2.0.10", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "globals": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/globals/-/globals-6.4.1.tgz", + "integrity": "sha1-hJgDKzttHMge68X3lpDY/in6v08=", + "dev": true + }, + "inquirer": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.8.5.tgz", + "integrity": "sha1-29dAz2yjtzEpamPOb22WGFHzNt8=", + "dev": true, + "requires": { + "ansi-regex": "1.1.1", + "chalk": "1.1.3", + "cli-width": "1.1.1", + "figures": "1.7.0", + "lodash": "3.10.1", + "readline2": "0.1.1", + "rx": "2.5.3", + "through": "2.3.8" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "levn": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.2.5.tgz", + "integrity": "sha1-uo0znQykphDjo/FFucr0iAcVUFQ=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2", + "type-check": "0.3.2" + } + }, + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", + "dev": true + }, + "minimatch": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", + "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", + "dev": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "mute-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.4.tgz", + "integrity": "sha1-qSGZYKbV1dBGWXruUSUsZlX3F34=", + "dev": true + }, + "object-assign": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz", + "integrity": "sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo=", + "dev": true + }, + "optionator": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.5.0.tgz", + "integrity": "sha1-t1qJlaLUF98ltuTjhi9QqohlE2g=", + "dev": true, + "requires": { + "deep-is": "0.1.3", + "fast-levenshtein": "1.0.7", + "levn": "0.2.5", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "0.0.3" + } + }, + "readline2": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/readline2/-/readline2-0.1.1.tgz", + "integrity": "sha1-mUQ7pug7gw7zBRv9fcJBqCco1Wg=", + "dev": true, + "requires": { + "mute-stream": "0.0.4", + "strip-ansi": "2.0.1" + } + }, + "rx": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/rx/-/rx-2.5.3.tgz", + "integrity": "sha1-Ia3H2A8CACr1Da6X/Z2/JIdV9WY=", + "dev": true + }, + "strip-ansi": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-2.0.1.tgz", + "integrity": "sha1-32LBqpTtLxFOHQ8h/R1QSCt5pg4=", + "dev": true, + "requires": { + "ansi-regex": "1.1.1" + } + }, + "strip-json-comments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", + "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", + "dev": true + }, + "user-home": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", + "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=", + "dev": true + } + } + }, + "moment": { + "version": "2.19.3", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.19.3.tgz", + "integrity": "sha1-vbmdJw1tf9p4zA+6zoVeJ/59pp8=" + }, + "morgan": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.8.1.tgz", + "integrity": "sha1-+TAj04h70nt439YCPOp4ku4npLE=", + "requires": { + "basic-auth": "1.1.0", + "debug": "2.6.1", + "depd": "1.1.2", + "on-finished": "2.3.0", + "on-headers": "1.0.1" + } + }, + "ms": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=" + }, + "multimeter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/multimeter/-/multimeter-0.1.1.tgz", + "integrity": "sha1-+FbID8PPDx1K2Os2rWhzXj7Vs+o=", + "requires": { + "charm": "0.1.2" + } + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" + }, + "naclb": { + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/naclb/-/naclb-1.3.10.tgz", + "integrity": "sha1-LE/WzPMYo87yUtzZ2tOJ+rOD2W8=", + "requires": { + "bindings": "1.2.1", + "nan": "2.2.0", + "node-pre-gyp": "0.6.33" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true + }, + "ajv": { + "version": "5.5.2", + "bundled": true, + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.0.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.3.3" + } + }, + "asn1": { + "version": "0.2.3", + "bundled": true + }, + "assert-plus": { + "version": "1.0.0", + "bundled": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true + }, + "aws-sign2": { + "version": "0.7.0", + "bundled": true + }, + "aws4": { + "version": "1.6.0", + "bundled": true + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "bundled": true, + "optional": true, + "requires": { + "tweetnacl": "0.14.3" + } + }, + "block-stream": { + "version": "0.0.9", + "bundled": true, + "requires": { + "inherits": "2.0.3" + } + }, + "boom": { + "version": "4.3.1", + "bundled": true, + "requires": { + "hoek": "4.2.0" + } + }, + "brace-expansion": { + "version": "1.1.8", + "bundled": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-shims": { + "version": "1.0.0", + "bundled": true + }, + "caseless": { + "version": "0.12.0", + "bundled": true + }, + "co": { + "version": "4.6.0", + "bundled": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "combined-stream": { + "version": "1.0.5", + "bundled": true, + "requires": { + "delayed-stream": "1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "cryptiles": { + "version": "3.1.2", + "bundled": true, + "requires": { + "boom": "5.2.0" + }, + "dependencies": { + "boom": { + "version": "5.2.0", + "bundled": true, + "requires": { + "hoek": "4.2.0" + } + } + } + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "requires": { + "assert-plus": "1.0.0" + } + }, + "debug": { + "version": "2.2.0", + "bundled": true, + "requires": { + "ms": "0.7.1" + } + }, + "deep-extend": { + "version": "0.4.2", + "bundled": true + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true + }, + "ecc-jsbn": { + "version": "0.1.1", + "bundled": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "extend": { + "version": "3.0.1", + "bundled": true + }, + "extsprintf": { + "version": "1.3.0", + "bundled": true + }, + "fast-deep-equal": { + "version": "1.0.0", + "bundled": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "bundled": true + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true + }, + "form-data": { + "version": "2.3.1", + "bundled": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.17" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true + }, + "fstream": { + "version": "1.0.11", + "bundled": true, + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.5.4" + } + }, + "fstream-ignore": { + "version": "1.0.5", + "bundled": true, + "requires": { + "fstream": "1.0.11", + "inherits": "2.0.3", + "minimatch": "3.0.4" + } + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "requires": { + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" + } + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "requires": { + "assert-plus": "1.0.0" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true + }, + "har-schema": { + "version": "2.0.0", + "bundled": true + }, + "har-validator": { + "version": "5.0.3", + "bundled": true, + "requires": { + "ajv": "5.5.2", + "har-schema": "2.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true + }, + "hawk": { + "version": "6.0.2", + "bundled": true, + "requires": { + "boom": "4.3.1", + "cryptiles": "3.1.2", + "hoek": "4.2.0", + "sntp": "2.1.0" + } + }, + "hoek": { + "version": "4.2.0", + "bundled": true + }, + "http-signature": { + "version": "1.2.0", + "bundled": true, + "requires": { + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.13.1" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true + }, + "ini": { + "version": "1.3.5", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "bundled": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true + }, + "jsprim": { + "version": "1.4.1", + "bundled": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "mime-db": { + "version": "1.30.0", + "bundled": true + }, + "mime-types": { + "version": "2.1.17", + "bundled": true, + "requires": { + "mime-db": "1.30.0" + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "1.1.8" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "0.7.1", + "bundled": true + }, + "node-pre-gyp": { + "version": "0.6.33", + "bundled": true, + "requires": { + "mkdirp": "0.5.1", + "nopt": "3.0.6", + "npmlog": "4.1.2", + "rc": "1.1.7", + "request": "2.83.0", + "rimraf": "2.5.4", + "semver": "5.3.0", + "tar": "2.2.1", + "tar-pack": "3.3.0" + } + }, + "nopt": { + "version": "3.0.6", + "bundled": true, + "requires": { + "abbrev": "1.1.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "requires": { + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true + }, + "oauth-sign": { + "version": "0.8.2", + "bundled": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true + }, + "performance-now": { + "version": "2.1.0", + "bundled": true + }, + "process-nextick-args": { + "version": "1.0.7", + "bundled": true + }, + "punycode": { + "version": "1.4.1", + "bundled": true + }, + "qs": { + "version": "6.5.1", + "bundled": true + }, + "rc": { + "version": "1.1.7", + "bundled": true, + "requires": { + "deep-extend": "0.4.2", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true + } + } + }, + "readable-stream": { + "version": "2.3.3", + "bundled": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "request": { + "version": "2.83.0", + "bundled": true, + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.3.1", + "har-validator": "5.0.3", + "hawk": "6.0.2", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.17", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.1", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.3", + "tunnel-agent": "0.6.0", + "uuid": "3.2.1" + } + }, + "rimraf": { + "version": "2.5.4", + "bundled": true, + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true + }, + "semver": { + "version": "5.3.0", + "bundled": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true + }, + "sntp": { + "version": "2.1.0", + "bundled": true, + "requires": { + "hoek": "4.2.0" + } + }, + "sshpk": { + "version": "1.13.1", + "bundled": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.3" + } + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.0.3", + "bundled": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "stringstream": { + "version": "0.0.5", + "bundled": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true + }, + "tar": { + "version": "2.2.1", + "bundled": true, + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } + }, + "tar-pack": { + "version": "3.3.0", + "bundled": true, + "requires": { + "debug": "2.2.0", + "fstream": "1.0.11", + "fstream-ignore": "1.0.5", + "once": "1.3.3", + "readable-stream": "2.1.5", + "rimraf": "2.5.4", + "tar": "2.2.1", + "uid-number": "0.0.6" + }, + "dependencies": { + "once": { + "version": "1.3.3", + "bundled": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "readable-stream": { + "version": "2.1.5", + "bundled": true, + "requires": { + "buffer-shims": "1.0.0", + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" + } + }, + "string_decoder": { + "version": "0.10.31", + "bundled": true + } + } + }, + "tough-cookie": { + "version": "2.3.3", + "bundled": true, + "requires": { + "punycode": "1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "uid-number": { + "version": "0.0.6", + "bundled": true + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + }, + "uuid": { + "version": "3.2.1", + "bundled": true + }, + "verror": { + "version": "1.10.0", + "bundled": true, + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + } + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "requires": { + "string-width": "1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + } + } + }, + "nan": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.2.0.tgz", + "integrity": "sha1-d5wHE1YpUDz2p7fmqrMwSbPDhTw=" + }, + "nat-upnp": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nat-upnp/-/nat-upnp-1.1.1.tgz", + "integrity": "sha512-b1Q+sf9fHGCXhlWErNgTTEto8A02MnNysw3vx3kD1657+/Ae23vPEAB6QBh+9RqLL4+xw/LmjVTiLy6A7Cx0xw==", + "requires": { + "async": "2.2.0", + "ip": "1.1.5", + "request": "2.81.0", + "xml2js": "0.1.14" + } + }, + "natives": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.4.tgz", + "integrity": "sha512-Q29yeg9aFKwhLVdkTAejM/HvYG0Y1Am1+HUkFQGn5k2j8GS+v60TVmZh6nujpEAj/qql+wGUrlryO8bF+b1jEg==" + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "node-pre-gyp": { + "version": "0.6.34", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.34.tgz", + "integrity": "sha1-lK0ceYoR1/xnOBtQ1H+MwY2Xmfc=", + "requires": { + "mkdirp": "0.5.1", + "nopt": "4.0.1", + "npmlog": "4.1.2", + "rc": "1.2.8", + "request": "2.81.0", + "rimraf": "2.6.2", + "semver": "5.5.0", + "tar": "2.2.1", + "tar-pack": "3.4.1" + } + }, + "node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" + }, + "nomnom": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.5.2.tgz", + "integrity": "sha1-9DRUSKhTz71cDSYyDyR3qwUm/i8=", + "requires": { + "colors": "0.5.1", + "underscore": "1.1.7" + }, + "dependencies": { + "colors": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz", + "integrity": "sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q=" + }, + "underscore": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.1.7.tgz", + "integrity": "sha1-QLq4S60Z0jAJbo1u9ii/8FXYPbA=" + } + } + }, + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "requires": { + "abbrev": "1.1.1", + "osenv": "0.1.5" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "1.1.0" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "1.1.5", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "nyc": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-11.9.0.tgz", + "integrity": "sha512-w8OdJAhXL5izerzZMdqzYKMj/pgHJyY3qEPYBjLLxrhcVoHEY9pU5ENIiZyCgG9OR7x3VcUMoD40o6PtVpfR4g==", + "dev": true, + "requires": { + "archy": "1.0.0", + "arrify": "1.0.1", + "caching-transform": "1.0.1", + "convert-source-map": "1.5.1", + "debug-log": "1.0.1", + "default-require-extensions": "1.0.0", + "find-cache-dir": "0.1.1", + "find-up": "2.1.0", + "foreground-child": "1.5.6", + "glob": "7.1.2", + "istanbul-lib-coverage": "1.2.0", + "istanbul-lib-hook": "1.1.0", + "istanbul-lib-instrument": "1.10.1", + "istanbul-lib-report": "1.1.3", + "istanbul-lib-source-maps": "1.2.3", + "istanbul-reports": "1.4.0", + "md5-hex": "1.3.0", + "merge-source-map": "1.1.0", + "micromatch": "3.1.10", + "mkdirp": "0.5.1", + "resolve-from": "2.0.0", + "rimraf": "2.6.2", + "signal-exit": "3.0.2", + "spawn-wrap": "1.4.2", + "test-exclude": "4.2.1", + "yargs": "11.1.0", + "yargs-parser": "8.1.0" + }, + "dependencies": { + "align-text": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2", + "longest": "1.0.1", + "repeat-string": "1.6.1" + } + }, + "amdefine": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "bundled": true, + "dev": true + }, + "append-transform": { + "version": "0.4.0", + "bundled": true, + "dev": true, + "requires": { + "default-require-extensions": "1.0.0" + } + }, + "archy": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "arr-diff": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "bundled": true, + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "bundled": true, + "dev": true + }, + "arrify": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "async": { + "version": "1.5.2", + "bundled": true, + "dev": true + }, + "atob": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" + } + }, + "babel-generator": { + "version": "6.26.1", + "bundled": true, + "dev": true, + "requires": { + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "detect-indent": "4.0.0", + "jsesc": "1.3.0", + "lodash": "4.17.10", + "source-map": "0.5.7", + "trim-right": "1.0.1" + } + }, + "babel-messages": { + "version": "6.23.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-runtime": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "core-js": "2.5.6", + "regenerator-runtime": "0.11.1" + } + }, + "babel-template": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "lodash": "4.17.10" + } + }, + "babel-traverse": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "debug": "2.6.9", + "globals": "9.18.0", + "invariant": "2.2.4", + "lodash": "4.17.10" + } + }, + "babel-types": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "esutils": "2.0.2", + "lodash": "4.17.10", + "to-fast-properties": "1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "bundled": true, + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "base": { + "version": "0.11.2", + "bundled": true, + "dev": true, + "requires": { + "cache-base": "1.0.1", + "class-utils": "0.3.6", + "component-emitter": "1.2.1", + "define-property": "1.0.0", + "isobject": "3.0.1", + "mixin-deep": "1.3.1", + "pascalcase": "0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "bundled": true, + "dev": true, + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.2", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "builtin-modules": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "collection-visit": "1.0.0", + "component-emitter": "1.2.1", + "get-value": "2.0.6", + "has-value": "1.0.0", + "isobject": "3.0.1", + "set-value": "2.0.0", + "to-object-path": "0.3.0", + "union-value": "1.0.0", + "unset-value": "1.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "caching-transform": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "md5-hex": "1.3.0", + "mkdirp": "0.5.1", + "write-file-atomic": "1.3.4" + } + }, + "camelcase": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true + }, + "center-align": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "align-text": "0.1.4", + "lazy-cache": "1.0.4" + } + }, + "chalk": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "class-utils": { + "version": "0.3.6", + "bundled": true, + "dev": true, + "requires": { + "arr-union": "3.1.0", + "define-property": "0.2.5", + "isobject": "3.0.1", + "static-extend": "0.1.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "cliui": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "center-align": "0.1.3", + "right-align": "0.1.3", + "wordwrap": "0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.2", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "map-visit": "1.0.0", + "object-visit": "1.0.1" + } + }, + "commondir": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "component-emitter": { + "version": "1.2.1", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "convert-source-map": { + "version": "1.5.1", + "bundled": true, + "dev": true + }, + "copy-descriptor": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "core-js": { + "version": "2.5.6", + "bundled": true, + "dev": true + }, + "cross-spawn": { + "version": "4.0.2", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "4.1.3", + "which": "1.3.0" + } + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "debug-log": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "decamelize": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "bundled": true, + "dev": true + }, + "default-require-extensions": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "strip-bom": "2.0.0" + } + }, + "define-property": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "1.0.2", + "isobject": "3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } + }, + "detect-indent": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "repeating": "2.0.1" + } + }, + "error-ex": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "is-arrayish": "0.2.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "esutils": { + "version": "2.0.2", + "bundled": true, + "dev": true + }, + "execa": { + "version": "0.7.0", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "5.1.0", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "4.1.3", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "bundled": true, + "dev": true, + "requires": { + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "extend-shallow": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "assign-symbols": "1.0.0", + "is-extendable": "1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-plain-object": "2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "bundled": true, + "dev": true, + "requires": { + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } + }, + "fill-range": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "find-cache-dir": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "requires": { + "commondir": "1.0.1", + "mkdirp": "0.5.1", + "pkg-dir": "1.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "locate-path": "2.0.0" + } + }, + "for-in": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "foreground-child": { + "version": "1.5.6", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "4.0.2", + "signal-exit": "3.0.2" + } + }, + "fragment-cache": { + "version": "0.2.1", + "bundled": true, + "dev": true, + "requires": { + "map-cache": "0.2.2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "get-caller-file": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "get-stream": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "get-value": { + "version": "2.0.6", + "bundled": true, + "dev": true + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "globals": { + "version": "9.18.0", + "bundled": true, + "dev": true + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true, + "dev": true + }, + "handlebars": { + "version": "4.0.11", + "bundled": true, + "dev": true, + "requires": { + "async": "1.5.2", + "optimist": "0.6.1", + "source-map": "0.4.4", + "uglify-js": "2.8.29" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "bundled": true, + "dev": true, + "requires": { + "amdefine": "1.0.1" + } + } + } + }, + "has-ansi": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "has-flag": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "has-value": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "get-value": "2.0.6", + "has-values": "1.0.0", + "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "has-values": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-number": "3.0.0", + "kind-of": "4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "hosted-git-info": { + "version": "2.6.0", + "bundled": true, + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "invariant": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "requires": { + "loose-envify": "1.3.1" + } + }, + "invert-kv": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "is-arrayish": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "is-buffer": { + "version": "1.1.6", + "bundled": true, + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "builtin-modules": "1.1.1" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "bundled": true, + "dev": true + } + } + }, + "is-extendable": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "is-number": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "is-odd": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-number": "4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "bundled": true, + "dev": true + } + } + }, + "is-plain-object": { + "version": "2.0.4", + "bundled": true, + "dev": true, + "requires": { + "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "is-stream": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "istanbul-lib-coverage": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "istanbul-lib-hook": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "append-transform": "0.4.0" + } + }, + "istanbul-lib-instrument": { + "version": "1.10.1", + "bundled": true, + "dev": true, + "requires": { + "babel-generator": "6.26.1", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "istanbul-lib-coverage": "1.2.0", + "semver": "5.5.0" + } + }, + "istanbul-lib-report": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "requires": { + "istanbul-lib-coverage": "1.2.0", + "mkdirp": "0.5.1", + "path-parse": "1.0.5", + "supports-color": "3.2.3" + }, + "dependencies": { + "supports-color": { + "version": "3.2.3", + "bundled": true, + "dev": true, + "requires": { + "has-flag": "1.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "1.2.3", + "bundled": true, + "dev": true, + "requires": { + "debug": "3.1.0", + "istanbul-lib-coverage": "1.2.0", + "mkdirp": "0.5.1", + "rimraf": "2.6.2", + "source-map": "0.5.7" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "istanbul-reports": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "handlebars": "4.0.11" + } + }, + "js-tokens": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "jsesc": { + "version": "1.3.0", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + }, + "lazy-cache": { + "version": "1.0.4", + "bundled": true, + "dev": true, + "optional": true + }, + "lcid": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "invert-kv": "1.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "strip-bom": "2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-locate": "2.0.0", + "path-exists": "3.0.0" + }, + "dependencies": { + "path-exists": { + "version": "3.0.0", + "bundled": true, + "dev": true + } + } + }, + "lodash": { + "version": "4.17.10", + "bundled": true, + "dev": true + }, + "longest": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "loose-envify": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "js-tokens": "3.0.2" + } + }, + "lru-cache": { + "version": "4.1.3", + "bundled": true, + "dev": true, + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } + }, + "map-cache": { + "version": "0.2.2", + "bundled": true, + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "object-visit": "1.0.1" + } + }, + "md5-hex": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "md5-o-matic": "0.1.1" + } + }, + "md5-o-matic": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "mem": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "mimic-fn": "1.2.0" + } + }, + "merge-source-map": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "source-map": "0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "bundled": true, + "dev": true + } + } + }, + "micromatch": { + "version": "3.1.10", + "bundled": true, + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.9", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } + }, + "mimic-fn": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "mixin-deep": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "for-in": "1.0.2", + "is-extendable": "1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-plain-object": "2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "nanomatch": { + "version": "1.2.9", + "bundled": true, + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "fragment-cache": "0.2.1", + "is-odd": "2.0.0", + "is-windows": "1.0.2", + "kind-of": "6.0.2", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } + }, + "normalize-package-data": { + "version": "2.4.0", + "bundled": true, + "dev": true, + "requires": { + "hosted-git-info": "2.6.0", + "is-builtin-module": "1.0.0", + "semver": "5.5.0", + "validate-npm-package-license": "3.0.3" + } + }, + "npm-run-path": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "path-key": "2.0.1" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "bundled": true, + "dev": true, + "requires": { + "copy-descriptor": "0.1.1", + "define-property": "0.2.5", + "kind-of": "3.2.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + } + } + }, + "object-visit": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "object.pick": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "optimist": { + "version": "0.6.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8", + "wordwrap": "0.0.3" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "os-locale": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "execa": "0.7.0", + "lcid": "1.0.0", + "mem": "1.1.0" + } + }, + "p-finally": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "p-limit": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "p-try": "1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-limit": "1.2.0" + } + }, + "p-try": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "parse-json": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "requires": { + "error-ex": "1.3.1" + } + }, + "pascalcase": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "path-exists": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "path-key": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "path-parse": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "path-type": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "pify": { + "version": "2.3.0", + "bundled": true, + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "bundled": true, + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + }, + "pkg-dir": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "find-up": "1.1.2" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + } + } + }, + "posix-character-classes": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "read-pkg": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "load-json-file": "1.1.0", + "normalize-package-data": "2.4.0", + "path-type": "1.1.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "find-up": "1.1.2", + "read-pkg": "1.1.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + } + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "bundled": true, + "dev": true + }, + "regex-not": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "3.0.2", + "safe-regex": "1.1.0" + } + }, + "repeat-element": { + "version": "1.1.2", + "bundled": true, + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "bundled": true, + "dev": true + }, + "repeating": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-finite": "1.0.2" + } + }, + "require-directory": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "resolve-from": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "ret": { + "version": "0.1.15", + "bundled": true, + "dev": true + }, + "right-align": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "align-text": "0.1.4" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "requires": { + "glob": "7.1.2" + } + }, + "safe-regex": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "ret": "0.1.15" + } + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "set-value": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "split-string": "3.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "shebang-command": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "shebang-regex": "1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "slide": { + "version": "1.1.6", + "bundled": true, + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "bundled": true, + "dev": true, + "requires": { + "base": "0.11.2", + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "map-cache": "0.2.2", + "source-map": "0.5.7", + "source-map-resolve": "0.5.1", + "use": "3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "define-property": "1.0.0", + "isobject": "3.0.1", + "snapdragon-util": "3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "source-map": { + "version": "0.5.7", + "bundled": true, + "dev": true + }, + "source-map-resolve": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "atob": "2.1.1", + "decode-uri-component": "0.2.0", + "resolve-url": "0.2.1", + "source-map-url": "0.4.0", + "urix": "0.1.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "bundled": true, + "dev": true + }, + "spawn-wrap": { + "version": "1.4.2", + "bundled": true, + "dev": true, + "requires": { + "foreground-child": "1.5.6", + "mkdirp": "0.5.1", + "os-homedir": "1.0.2", + "rimraf": "2.6.2", + "signal-exit": "3.0.2", + "which": "1.3.0" + } + }, + "spdx-correct": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-expression-parse": "3.0.0", + "spdx-license-ids": "3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-exceptions": "2.1.0", + "spdx-license-ids": "3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "split-string": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "3.0.2" + } + }, + "static-extend": { + "version": "0.1.2", + "bundled": true, + "dev": true, + "requires": { + "define-property": "0.2.5", + "object-copy": "0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + } + } + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-bom": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-utf8": "0.2.1" + } + }, + "strip-eof": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "test-exclude": { + "version": "4.2.1", + "bundled": true, + "dev": true, + "requires": { + "arrify": "1.0.1", + "micromatch": "3.1.10", + "object-assign": "4.1.1", + "read-pkg-up": "1.0.1", + "require-main-filename": "1.0.1" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "bundled": true, + "dev": true + }, + "braces": { + "version": "2.3.2", + "bundled": true, + "dev": true, + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.2", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "bundled": true, + "dev": true, + "requires": { + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "bundled": true, + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "bundled": true, + "dev": true, + "requires": { + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "bundled": true, + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.9", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + } + } + } + }, + "to-fast-properties": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "to-regex": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "regex-not": "1.0.2", + "safe-regex": "1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "is-number": "3.0.0", + "repeat-string": "1.6.1" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + } + } + }, + "trim-right": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "uglify-js": { + "version": "2.8.29", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "source-map": "0.5.7", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" + }, + "dependencies": { + "yargs": { + "version": "3.10.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", + "window-size": "0.1.0" + } + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "union-value": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "arr-union": "3.1.0", + "get-value": "2.0.6", + "is-extendable": "0.1.1", + "set-value": "0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "set-value": { + "version": "0.4.3", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "to-object-path": "0.3.0" + } + } + } + }, + "unset-value": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "has-value": "0.3.1", + "isobject": "3.0.1" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "bundled": true, + "dev": true, + "requires": { + "get-value": "2.0.6", + "has-values": "0.1.4", + "isobject": "2.1.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "urix": { + "version": "0.1.0", + "bundled": true, + "dev": true + }, + "use": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "6.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } + }, + "validate-npm-package-license": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "requires": { + "spdx-correct": "3.0.0", + "spdx-expression-parse": "3.0.0" + } + }, + "which": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "isexe": "2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "window-size": { + "version": "0.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "wordwrap": { + "version": "0.0.3", + "bundled": true, + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "write-file-atomic": { + "version": "1.3.4", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "imurmurhash": "0.1.4", + "slide": "1.1.6" + } + }, + "y18n": { + "version": "3.2.1", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "2.1.2", + "bundled": true, + "dev": true + }, + "yargs": { + "version": "11.1.0", + "bundled": true, + "dev": true, + "requires": { + "cliui": "4.1.0", + "decamelize": "1.2.0", + "find-up": "2.1.0", + "get-caller-file": "1.0.2", + "os-locale": "2.1.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "9.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "cliui": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "wrap-ansi": "2.1.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + }, + "yargs-parser": { + "version": "9.0.2", + "bundled": true, + "dev": true, + "requires": { + "camelcase": "4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "8.1.0", + "bundled": true, + "dev": true, + "requires": { + "camelcase": "4.1.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "bundled": true, + "dev": true + } + } + } + } + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", + "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1.0.2" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "requires": { + "mimic-fn": "1.2.0" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "requires": { + "minimist": "0.0.10", + "wordwrap": "0.0.3" + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" + }, + "dependencies": { + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + } + } + }, + "options": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", + "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=" + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "over": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/over/-/over-0.0.5.tgz", + "integrity": "sha1-8phS5w/X4l82DgE6jsRMgq7bVwg=" + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "requires": { + "through": "2.3.8" + } + }, + "performance-now": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + }, + "pluralize": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", + "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "progress": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", + "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=" + }, + "proxy-addr": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.5.tgz", + "integrity": "sha1-ccDuOxAt4/IC87ZPYI0XP8uhqRg=", + "requires": { + "forwarded": "0.1.2", + "ipaddr.js": "1.4.0" + } + }, + "pullstream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/pullstream/-/pullstream-0.4.1.tgz", + "integrity": "sha1-1vs79a7Wl+gxFQ6xACwlo/iuExQ=", + "requires": { + "over": "0.0.5", + "readable-stream": "1.0.34", + "setimmediate": "1.0.5", + "slice-stream": "1.0.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + }, + "q-io": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/q-io/-/q-io-1.13.5.tgz", + "integrity": "sha1-asOd61z+DcaENub4wz0NfD9HG7I=", + "requires": { + "collections": "0.2.2", + "mime": "1.2.11", + "mimeparse": "0.1.4", + "q": "1.5.1", + "qs": "6.4.0", + "url2": "0.0.0" + } + }, + "qs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" + }, + "querablep": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/querablep/-/querablep-0.1.0.tgz", + "integrity": "sha1-ss0rPnX81F1d163kwYEatUeEmoQ=" + }, + "ramda": { + "version": "0.22.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.22.1.tgz", + "integrity": "sha1-Ax2gw99BfFszyWI0dX6zcDPzag4=", + "dev": true + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + }, + "raw-body": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.2.0.tgz", + "integrity": "sha1-mUl2z2pQlqQRYoQEkvC9xdbn+5Y=", + "requires": { + "bytes": "2.4.0", + "iconv-lite": "0.4.15", + "unpipe": "1.0.0" + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "0.6.0", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + } + }, + "readline2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", + "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "mute-stream": "0.0.5" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "mute-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", + "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", + "dev": true + } + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "requires": { + "resolve": "1.8.1" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "request": { + "version": "2.81.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", + "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.7.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "4.2.1", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.18", + "oauth-sign": "0.8.2", + "performance-now": "0.2.0", + "qs": "6.4.0", + "safe-buffer": "5.1.2", + "stringstream": "0.0.6", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.6.0", + "uuid": "3.3.2" + }, + "dependencies": { + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } + } + }, + "request-promise": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.0.tgz", + "integrity": "sha1-aE93dI1rRhe+5qTvRGmQbm0HRyA=", + "requires": { + "bluebird": "3.5.1", + "request-promise-core": "1.1.1", + "stealthy-require": "1.1.1" + } + }, + "request-promise-core": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", + "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", + "requires": { + "lodash": "4.17.10" + } + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "0.1.0", + "resolve-from": "1.0.1" + } + }, + "resolve": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "requires": { + "path-parse": "1.0.5" + } + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "response": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/response/-/response-0.18.0.tgz", + "integrity": "sha1-oQCfJxcz8jNYLeCpX7TLql6osgY=", + "requires": { + "best-encoding": "0.1.1", + "bl": "0.7.0", + "caseless": "0.3.0", + "mime": "1.2.11" + }, + "dependencies": { + "bl": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-0.7.0.tgz", + "integrity": "sha1-P7BnBgKsKHjrdw3CA58YNr5irls=", + "requires": { + "readable-stream": "1.0.34" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "requires": { + "onetime": "2.0.1", + "signal-exit": "3.0.2" + } + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "optional": true, + "requires": { + "align-text": "0.1.4" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "requires": { + "glob": "7.1.2" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "requires": { + "is-promise": "2.1.0" + } + }, + "rx": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", + "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=" + }, + "rx-lite": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", + "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "scryptb": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/scryptb/-/scryptb-6.0.5.tgz", + "integrity": "sha512-KeqKAU89M2C5rnqyk9kzY+i7Mru3yv4TKM7HJuwARUQdPJmQWdkG7x3CQITs4J/gVtyCBzkug0YCuIvsdazRvA==", + "requires": { + "bindings": "1.2.1", + "nan": "2.2.0", + "node-pre-gyp": "0.6.23" + }, + "dependencies": { + "node-pre-gyp": { + "version": "0.6.23", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.23.tgz", + "integrity": "sha1-FVvzaDq8/N4Aiu2rEkiJGgdz25U=", + "requires": { + "mkdirp": "0.5.1", + "nopt": "3.0.6", + "npmlog": "2.0.2", + "rc": "1.1.6", + "request": "2.69.0", + "rimraf": "2.5.2", + "semver": "5.1.0", + "tar": "2.2.1", + "tar-pack": "3.1.3" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "bundled": true + } + } + }, + "nopt": { + "version": "3.0.6", + "bundled": true, + "requires": { + "abbrev": "1.0.7" + }, + "dependencies": { + "abbrev": { + "version": "1.0.7", + "bundled": true + } + } + }, + "npmlog": { + "version": "2.0.2", + "bundled": true, + "requires": { + "ansi": "0.3.1", + "are-we-there-yet": "1.0.6", + "gauge": "1.2.7" + }, + "dependencies": { + "ansi": { + "version": "0.3.1", + "bundled": true + }, + "are-we-there-yet": { + "version": "1.0.6", + "bundled": true, + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.0.5" + }, + "dependencies": { + "delegates": { + "version": "1.0.0", + "bundled": true + }, + "readable-stream": { + "version": "2.0.5", + "bundled": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.1", + "isarray": "0.0.1", + "process-nextick-args": "1.0.6", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "inherits": { + "version": "2.0.1", + "bundled": true + }, + "isarray": { + "version": "0.0.1", + "bundled": true + }, + "process-nextick-args": { + "version": "1.0.6", + "bundled": true + }, + "string_decoder": { + "version": "0.10.31", + "bundled": true + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + } + } + } + } + }, + "gauge": { + "version": "1.2.7", + "bundled": true, + "requires": { + "ansi": "0.3.1", + "has-unicode": "2.0.0", + "lodash.pad": "4.1.0", + "lodash.padend": "4.2.0", + "lodash.padstart": "4.2.0" + }, + "dependencies": { + "has-unicode": { + "version": "2.0.0", + "bundled": true + }, + "lodash.pad": { + "version": "4.1.0", + "bundled": true, + "requires": { + "lodash.repeat": "4.0.0", + "lodash.tostring": "4.1.2" + }, + "dependencies": { + "lodash.repeat": { + "version": "4.0.0", + "bundled": true, + "requires": { + "lodash.tostring": "4.1.2" + } + }, + "lodash.tostring": { + "version": "4.1.2", + "bundled": true + } + } + }, + "lodash.padend": { + "version": "4.2.0", + "bundled": true, + "requires": { + "lodash.repeat": "4.0.0", + "lodash.tostring": "4.1.2" + }, + "dependencies": { + "lodash.repeat": { + "version": "4.0.0", + "bundled": true, + "requires": { + "lodash.tostring": "4.1.2" + } + }, + "lodash.tostring": { + "version": "4.1.2", + "bundled": true + } + } + }, + "lodash.padstart": { + "version": "4.2.0", + "bundled": true, + "requires": { + "lodash.repeat": "4.0.0", + "lodash.tostring": "4.1.2" + }, + "dependencies": { + "lodash.repeat": { + "version": "4.0.0", + "bundled": true, + "requires": { + "lodash.tostring": "4.1.2" + } + }, + "lodash.tostring": { + "version": "4.1.2", + "bundled": true + } + } + } + } + } + } + }, + "rc": { + "version": "1.1.6", + "bundled": true, + "requires": { + "deep-extend": "0.4.1", + "ini": "1.3.4", + "minimist": "1.2.0", + "strip-json-comments": "1.0.4" + }, + "dependencies": { + "deep-extend": { + "version": "0.4.1", + "bundled": true + }, + "ini": { + "version": "1.3.4", + "bundled": true + }, + "minimist": { + "version": "1.2.0", + "bundled": true + }, + "strip-json-comments": { + "version": "1.0.4", + "bundled": true + } + } + }, + "request": { + "version": "2.69.0", + "bundled": true, + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.3.2", + "bl": "1.0.3", + "caseless": "0.11.0", + "combined-stream": "1.0.5", + "extend": "3.0.0", + "forever-agent": "0.6.1", + "form-data": "1.0.0-rc3", + "har-validator": "2.0.6", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.10", + "node-uuid": "1.4.7", + "oauth-sign": "0.8.1", + "qs": "6.0.2", + "stringstream": "0.0.5", + "tough-cookie": "2.2.1", + "tunnel-agent": "0.4.2" + }, + "dependencies": { + "aws-sign2": { + "version": "0.6.0", + "bundled": true + }, + "aws4": { + "version": "1.3.2", + "bundled": true, + "requires": { + "lru-cache": "4.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "4.0.0", + "bundled": true, + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.0.0" + }, + "dependencies": { + "pseudomap": { + "version": "1.0.2", + "bundled": true + }, + "yallist": { + "version": "2.0.0", + "bundled": true + } + } + } + } + }, + "bl": { + "version": "1.0.3", + "bundled": true, + "requires": { + "readable-stream": "2.0.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.0.5", + "bundled": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.1", + "isarray": "0.0.1", + "process-nextick-args": "1.0.6", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "inherits": { + "version": "2.0.1", + "bundled": true + }, + "isarray": { + "version": "0.0.1", + "bundled": true + }, + "process-nextick-args": { + "version": "1.0.6", + "bundled": true + }, + "string_decoder": { + "version": "0.10.31", + "bundled": true + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + } + } + } + } + }, + "caseless": { + "version": "0.11.0", + "bundled": true + }, + "combined-stream": { + "version": "1.0.5", + "bundled": true, + "requires": { + "delayed-stream": "1.0.0" + }, + "dependencies": { + "delayed-stream": { + "version": "1.0.0", + "bundled": true + } + } + }, + "extend": { + "version": "3.0.0", + "bundled": true + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true + }, + "form-data": { + "version": "1.0.0-rc3", + "bundled": true, + "requires": { + "async": "1.5.2", + "combined-stream": "1.0.5", + "mime-types": "2.1.10" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "bundled": true + } + } + }, + "har-validator": { + "version": "2.0.6", + "bundled": true, + "requires": { + "chalk": "1.1.1", + "commander": "2.9.0", + "is-my-json-valid": "2.13.1", + "pinkie-promise": "2.0.0" + }, + "dependencies": { + "chalk": { + "version": "1.1.1", + "bundled": true, + "requires": { + "ansi-styles": "2.2.0", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.0", + "bundled": true, + "requires": { + "color-convert": "1.0.0" + }, + "dependencies": { + "color-convert": { + "version": "1.0.0", + "bundled": true + } + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true + }, + "has-ansi": { + "version": "2.0.0", + "bundled": true, + "requires": { + "ansi-regex": "2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.0.0", + "bundled": true + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.0.0", + "bundled": true + } + } + }, + "supports-color": { + "version": "2.0.0", + "bundled": true + } + } + }, + "commander": { + "version": "2.9.0", + "bundled": true, + "requires": { + "graceful-readlink": "1.0.1" + }, + "dependencies": { + "graceful-readlink": { + "version": "1.0.1", + "bundled": true + } + } + }, + "is-my-json-valid": { + "version": "2.13.1", + "bundled": true, + "requires": { + "generate-function": "2.0.0", + "generate-object-property": "1.2.0", + "jsonpointer": "2.0.0", + "xtend": "4.0.1" + }, + "dependencies": { + "generate-function": { + "version": "2.0.0", + "bundled": true + }, + "generate-object-property": { + "version": "1.2.0", + "bundled": true, + "requires": { + "is-property": "1.0.2" + }, + "dependencies": { + "is-property": { + "version": "1.0.2", + "bundled": true + } + } + }, + "jsonpointer": { + "version": "2.0.0", + "bundled": true + }, + "xtend": { + "version": "4.0.1", + "bundled": true + } + } + }, + "pinkie-promise": { + "version": "2.0.0", + "bundled": true, + "requires": { + "pinkie": "2.0.4" + }, + "dependencies": { + "pinkie": { + "version": "2.0.4", + "bundled": true + } + } + } + } + }, + "hawk": { + "version": "3.1.3", + "bundled": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + }, + "dependencies": { + "boom": { + "version": "2.10.1", + "bundled": true, + "requires": { + "hoek": "2.16.3" + } + }, + "cryptiles": { + "version": "2.0.5", + "bundled": true, + "requires": { + "boom": "2.10.1" + } + }, + "hoek": { + "version": "2.16.3", + "bundled": true + }, + "sntp": { + "version": "1.0.9", + "bundled": true, + "requires": { + "hoek": "2.16.3" + } + } + } + }, + "http-signature": { + "version": "1.1.1", + "bundled": true, + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.2.2", + "sshpk": "1.7.4" + }, + "dependencies": { + "assert-plus": { + "version": "0.2.0", + "bundled": true + }, + "jsprim": { + "version": "1.2.2", + "bundled": true, + "requires": { + "extsprintf": "1.0.2", + "json-schema": "0.2.2", + "verror": "1.3.6" + }, + "dependencies": { + "extsprintf": { + "version": "1.0.2", + "bundled": true + }, + "json-schema": { + "version": "0.2.2", + "bundled": true + }, + "verror": { + "version": "1.3.6", + "bundled": true, + "requires": { + "extsprintf": "1.0.2" + } + } + } + }, + "sshpk": { + "version": "1.7.4", + "bundled": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "0.2.0", + "dashdash": "1.13.0", + "ecc-jsbn": "0.1.1", + "jodid25519": "1.0.2", + "jsbn": "0.1.0", + "tweetnacl": "0.14.1" + }, + "dependencies": { + "asn1": { + "version": "0.2.3", + "bundled": true + }, + "dashdash": { + "version": "1.13.0", + "bundled": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true + } + } + }, + "ecc-jsbn": { + "version": "0.1.1", + "bundled": true, + "optional": true, + "requires": { + "jsbn": "0.1.0" + } + }, + "jodid25519": { + "version": "1.0.2", + "bundled": true, + "optional": true, + "requires": { + "jsbn": "0.1.0" + } + }, + "jsbn": { + "version": "0.1.0", + "bundled": true, + "optional": true + }, + "tweetnacl": { + "version": "0.14.1", + "bundled": true, + "optional": true + } + } + } + } + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true + }, + "mime-types": { + "version": "2.1.10", + "bundled": true, + "requires": { + "mime-db": "1.22.0" + }, + "dependencies": { + "mime-db": { + "version": "1.22.0", + "bundled": true + } + } + }, + "node-uuid": { + "version": "1.4.7", + "bundled": true + }, + "oauth-sign": { + "version": "0.8.1", + "bundled": true + }, + "qs": { + "version": "6.0.2", + "bundled": true + }, + "stringstream": { + "version": "0.0.5", + "bundled": true + }, + "tough-cookie": { + "version": "2.2.1", + "bundled": true + }, + "tunnel-agent": { + "version": "0.4.2", + "bundled": true + } + } + }, + "rimraf": { + "version": "2.5.2", + "bundled": true, + "requires": { + "glob": "7.0.0" + }, + "dependencies": { + "glob": { + "version": "7.0.0", + "bundled": true, + "requires": { + "inflight": "1.0.4", + "inherits": "2.0.1", + "minimatch": "3.0.0", + "once": "1.3.3", + "path-is-absolute": "1.0.0" + }, + "dependencies": { + "inflight": { + "version": "1.0.4", + "bundled": true, + "requires": { + "once": "1.3.3", + "wrappy": "1.0.1" + }, + "dependencies": { + "wrappy": { + "version": "1.0.1", + "bundled": true + } + } + }, + "inherits": { + "version": "2.0.1", + "bundled": true + }, + "minimatch": { + "version": "3.0.0", + "bundled": true, + "requires": { + "brace-expansion": "1.1.3" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.3", + "bundled": true, + "requires": { + "balanced-match": "0.3.0", + "concat-map": "0.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "0.3.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + } + } + } + } + }, + "once": { + "version": "1.3.3", + "bundled": true, + "requires": { + "wrappy": "1.0.1" + }, + "dependencies": { + "wrappy": { + "version": "1.0.1", + "bundled": true + } + } + }, + "path-is-absolute": { + "version": "1.0.0", + "bundled": true + } + } + } + } + }, + "semver": { + "version": "5.1.0", + "bundled": true + }, + "tar": { + "version": "2.2.1", + "bundled": true, + "requires": { + "block-stream": "0.0.8", + "fstream": "1.0.8", + "inherits": "2.0.1" + }, + "dependencies": { + "block-stream": { + "version": "0.0.8", + "bundled": true, + "requires": { + "inherits": "2.0.1" + } + }, + "fstream": { + "version": "1.0.8", + "bundled": true, + "requires": { + "graceful-fs": "4.1.3", + "inherits": "2.0.1", + "mkdirp": "0.5.1", + "rimraf": "2.5.2" + }, + "dependencies": { + "graceful-fs": { + "version": "4.1.3", + "bundled": true + } + } + }, + "inherits": { + "version": "2.0.1", + "bundled": true + } + } + }, + "tar-pack": { + "version": "3.1.3", + "bundled": true, + "requires": { + "debug": "2.2.0", + "fstream": "1.0.8", + "fstream-ignore": "1.0.3", + "once": "1.3.3", + "readable-stream": "2.0.5", + "rimraf": "2.5.2", + "tar": "2.2.1", + "uid-number": "0.0.6" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "bundled": true, + "requires": { + "ms": "0.7.1" + }, + "dependencies": { + "ms": { + "version": "0.7.1", + "bundled": true + } + } + }, + "fstream": { + "version": "1.0.8", + "bundled": true, + "requires": { + "graceful-fs": "4.1.3", + "inherits": "2.0.1", + "mkdirp": "0.5.1", + "rimraf": "2.5.2" + }, + "dependencies": { + "graceful-fs": { + "version": "4.1.3", + "bundled": true + }, + "inherits": { + "version": "2.0.1", + "bundled": true + } + } + }, + "fstream-ignore": { + "version": "1.0.3", + "bundled": true, + "requires": { + "fstream": "1.0.8", + "inherits": "2.0.1", + "minimatch": "3.0.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "bundled": true + }, + "minimatch": { + "version": "3.0.0", + "bundled": true, + "requires": { + "brace-expansion": "1.1.3" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.3", + "bundled": true, + "requires": { + "balanced-match": "0.3.0", + "concat-map": "0.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "0.3.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + } + } + } + } + } + } + }, + "once": { + "version": "1.3.3", + "bundled": true, + "requires": { + "wrappy": "1.0.1" + }, + "dependencies": { + "wrappy": { + "version": "1.0.1", + "bundled": true + } + } + }, + "readable-stream": { + "version": "2.0.5", + "bundled": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.1", + "isarray": "0.0.1", + "process-nextick-args": "1.0.6", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "inherits": { + "version": "2.0.1", + "bundled": true + }, + "isarray": { + "version": "0.0.1", + "bundled": true + }, + "process-nextick-args": { + "version": "1.0.6", + "bundled": true + }, + "string_decoder": { + "version": "0.10.31", + "bundled": true + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + } + } + }, + "uid-number": { + "version": "0.0.6", + "bundled": true + } + } + } + } + } + } + }, + "seedrandom": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.3.tgz", + "integrity": "sha1-JDhQTa0zkXMUv/GKxNeU8W1qrsw=" + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + }, + "send": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.15.1.tgz", + "integrity": "sha1-igI1TCbm9cynAAZfXwzeupDse18=", + "requires": { + "debug": "2.6.1", + "depd": "1.1.2", + "destroy": "1.0.4", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "etag": "1.8.1", + "fresh": "0.5.0", + "http-errors": "1.6.3", + "mime": "1.3.4", + "ms": "0.7.2", + "on-finished": "2.3.0", + "range-parser": "1.2.0", + "statuses": "1.3.1" + }, + "dependencies": { + "mime": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", + "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + } + } + }, + "serve-static": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.1.tgz", + "integrity": "sha1-dEOpZePO1kes61Y5+ga/TRu+ADk=", + "requires": { + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "parseurl": "1.3.2", + "send": "0.15.1" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "sha1": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/sha1/-/sha1-1.1.1.tgz", + "integrity": "sha1-rdqnqTFo85PxnrKxUJFhjicA+Eg=", + "requires": { + "charenc": "0.0.2", + "crypt": "0.0.2" + } + }, + "shelljs": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.2.tgz", + "integrity": "sha512-pRXeNrCA2Wd9itwhvLp5LZQvPJ0wU6bcjaTMywHHGX5XWhVN2nzSu7WV0q+oUY7mGK3mgSkDDzP3MgjqdyIgbQ==", + "requires": { + "glob": "7.1.2", + "interpret": "1.1.0", + "rechoir": "0.6.2" + } + }, + "should": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/should/-/should-13.2.1.tgz", + "integrity": "sha512-l+/NwEMO+DcstsHEwPHRHzC9j4UOE3VQwJGcMWSsD/vqpqHbnQ+1iSHy64Ihmmjx1uiRPD9pFadTSc3MJtXAgw==", + "dev": true, + "requires": { + "should-equal": "2.0.0", + "should-format": "3.0.3", + "should-type": "1.4.0", + "should-type-adaptors": "1.1.0", + "should-util": "1.0.0" + } + }, + "should-equal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", + "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", + "dev": true, + "requires": { + "should-type": "1.4.0" + } + }, + "should-format": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", + "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=", + "dev": true, + "requires": { + "should-type": "1.4.0", + "should-type-adaptors": "1.1.0" + } + }, + "should-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", + "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=", + "dev": true + }, + "should-type-adaptors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", + "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", + "dev": true, + "requires": { + "should-type": "1.4.0", + "should-util": "1.0.0" + } + }, + "should-util": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.0.tgz", + "integrity": "sha1-yYzaN0qmsZDfi6h8mInCtNtiAGM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "slice-ansi": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true + }, + "slice-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-stream/-/slice-stream-1.0.0.tgz", + "integrity": "sha1-WzO9ZvATsaf4ZGCwPUY97DmtPqA=", + "requires": { + "readable-stream": "1.0.34" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "smart-buffer": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-1.1.15.tgz", + "integrity": "sha1-fxFLW2X6s+KjWqd1uxLw0cZJvxY=" + }, + "sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "requires": { + "hoek": "2.16.3" + } + }, + "socks": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/socks/-/socks-1.1.10.tgz", + "integrity": "sha1-W4t/x8jzQcU+0FbpKbe/Tei6e1o=", + "requires": { + "ip": "1.1.5", + "smart-buffer": "1.1.15" + } + }, + "socks-proxy-agent": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-3.0.1.tgz", + "integrity": "sha512-ZwEDymm204mTzvdqyUqOdovVr2YRd2NYskrYrF2LXyZ9qDiMAoFESGK8CRphiO7rtbo2Y757k2Nia3x2hGtalA==", + "requires": { + "agent-base": "4.2.0", + "socks": "1.1.10" + } + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "optional": true, + "requires": { + "amdefine": "1.0.1" + } + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "0.5.7" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "requires": { + "through": "2.3.8" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "sqlite3": { + "version": "3.1.13", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-3.1.13.tgz", + "integrity": "sha512-JxXKPJnkZ6NuHRojq+g2WXWBt3M1G9sjZaYiHEWSTGijDM3cwju/0T2XbWqMXFmPqDgw+iB7zKQvnns4bvzXlw==", + "requires": { + "nan": "2.7.0", + "node-pre-gyp": "0.6.38" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true + }, + "ajv": { + "version": "4.11.8", + "bundled": true, + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.3.3" + } + }, + "asn1": { + "version": "0.2.3", + "bundled": true + }, + "assert-plus": { + "version": "0.2.0", + "bundled": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true + }, + "aws-sign2": { + "version": "0.6.0", + "bundled": true + }, + "aws4": { + "version": "1.6.0", + "bundled": true + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "bundled": true, + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "block-stream": { + "version": "0.0.9", + "bundled": true, + "requires": { + "inherits": "2.0.3" + } + }, + "boom": { + "version": "2.10.1", + "bundled": true, + "requires": { + "hoek": "2.16.3" + } + }, + "brace-expansion": { + "version": "1.1.8", + "bundled": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "caseless": { + "version": "0.12.0", + "bundled": true + }, + "co": { + "version": "4.6.0", + "bundled": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "combined-stream": { + "version": "1.0.5", + "bundled": true, + "requires": { + "delayed-stream": "1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "cryptiles": { + "version": "2.0.5", + "bundled": true, + "requires": { + "boom": "2.10.1" + } + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true + } + } + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.4.2", + "bundled": true + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true + }, + "ecc-jsbn": { + "version": "0.1.1", + "bundled": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "extend": { + "version": "3.0.1", + "bundled": true + }, + "extsprintf": { + "version": "1.3.0", + "bundled": true + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true + }, + "form-data": { + "version": "2.1.4", + "bundled": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.17" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true + }, + "fstream": { + "version": "1.0.11", + "bundled": true, + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.2" + } + }, + "fstream-ignore": { + "version": "1.0.5", + "bundled": true, + "requires": { + "fstream": "1.0.11", + "inherits": "2.0.3", + "minimatch": "3.0.4" + } + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "requires": { + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" + } + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true + } + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true + }, + "har-schema": { + "version": "1.0.5", + "bundled": true + }, + "har-validator": { + "version": "4.2.1", + "bundled": true, + "requires": { + "ajv": "4.11.8", + "har-schema": "1.0.5" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true + }, + "hawk": { + "version": "3.1.3", + "bundled": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "hoek": { + "version": "2.16.3", + "bundled": true + }, + "http-signature": { + "version": "1.1.1", + "bundled": true, + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.1", + "sshpk": "1.13.1" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true + }, + "ini": { + "version": "1.3.4", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "bundled": true, + "requires": { + "jsonify": "0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true + }, + "jsonify": { + "version": "0.0.0", + "bundled": true + }, + "jsprim": { + "version": "1.4.1", + "bundled": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true + } + } + }, + "mime-db": { + "version": "1.30.0", + "bundled": true + }, + "mime-types": { + "version": "2.1.17", + "bundled": true, + "requires": { + "mime-db": "1.30.0" + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "1.1.8" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true + }, + "nan": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz", + "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY=" + }, + "node-pre-gyp": { + "version": "0.6.38", + "bundled": true, + "requires": { + "hawk": "3.1.3", + "mkdirp": "0.5.1", + "nopt": "4.0.1", + "npmlog": "4.1.2", + "rc": "1.2.1", + "request": "2.81.0", + "rimraf": "2.6.2", + "semver": "5.4.1", + "tar": "2.2.1", + "tar-pack": "3.4.0" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "requires": { + "abbrev": "1.1.1", + "osenv": "0.1.4" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "requires": { + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true + }, + "oauth-sign": { + "version": "0.8.2", + "bundled": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true + }, + "osenv": { + "version": "0.1.4", + "bundled": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true + }, + "performance-now": { + "version": "0.2.0", + "bundled": true + }, + "process-nextick-args": { + "version": "1.0.7", + "bundled": true + }, + "punycode": { + "version": "1.4.1", + "bundled": true + }, + "qs": { + "version": "6.4.0", + "bundled": true + }, + "rc": { + "version": "1.2.1", + "bundled": true, + "requires": { + "deep-extend": "0.4.2", + "ini": "1.3.4", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true + } + } + }, + "readable-stream": { + "version": "2.3.3", + "bundled": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "request": { + "version": "2.81.0", + "bundled": true, + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "4.2.1", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.17", + "oauth-sign": "0.8.2", + "performance-now": "0.2.0", + "qs": "6.4.0", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.3", + "tunnel-agent": "0.6.0", + "uuid": "3.1.0" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true + }, + "semver": { + "version": "5.4.1", + "bundled": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true + }, + "sntp": { + "version": "1.0.9", + "bundled": true, + "requires": { + "hoek": "2.16.3" + } + }, + "sshpk": { + "version": "1.13.1", + "bundled": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true + } + } + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.0.3", + "bundled": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "stringstream": { + "version": "0.0.5", + "bundled": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true + }, + "tar": { + "version": "2.2.1", + "bundled": true, + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } + }, + "tar-pack": { + "version": "3.4.0", + "bundled": true, + "requires": { + "debug": "2.6.9", + "fstream": "1.0.11", + "fstream-ignore": "1.0.5", + "once": "1.4.0", + "readable-stream": "2.3.3", + "rimraf": "2.6.2", + "tar": "2.2.1", + "uid-number": "0.0.6" + } + }, + "tough-cookie": { + "version": "2.3.3", + "bundled": true, + "requires": { + "punycode": "1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "optional": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + }, + "uuid": { + "version": "3.1.0", + "bundled": true + }, + "verror": { + "version": "1.10.0", + "bundled": true, + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true + } + } + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "requires": { + "string-width": "1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + } + } + }, + "sshpk": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", + "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.2", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "safer-buffer": "2.1.2", + "tweetnacl": "0.14.3" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" + }, + "stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", + "requires": { + "duplexer": "0.1.1" + } + }, + "streamifier": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/streamifier/-/streamifier-0.1.1.tgz", + "integrity": "sha1-l+mNj6TRBdYqJpHR3AfoINuN/E8=" + }, + "streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "3.0.0" + } + } + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "stringstream": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", + "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==" + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "superagent": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.2.tgz", + "integrity": "sha512-gVH4QfYHcY3P0f/BZzavLreHW3T1v7hG9B+hpMQotGQqurOvhv87GcMCd6LWySmBuf+BDR44TQd0aISjVHLeNQ==", + "requires": { + "component-emitter": "1.2.1", + "cookiejar": "2.1.2", + "debug": "3.1.0", + "extend": "3.0.1", + "form-data": "2.3.2", + "formidable": "1.2.1", + "methods": "1.1.2", + "mime": "1.6.0", + "qs": "6.5.2", + "readable-stream": "2.3.6" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "form-data": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.6", + "mime-types": "2.1.18" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + } + } + }, + "supertest": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-3.1.0.tgz", + "integrity": "sha512-O44AMnmJqx294uJQjfUmEyYOg7d9mylNFsMw/Wkz4evKd1njyPrtCN+U6ZIC7sKtfEVQhfTqFFijlXx8KP/Czw==", + "requires": { + "methods": "1.1.2", + "superagent": "3.8.2" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + }, + "table": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", + "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", + "dev": true, + "requires": { + "ajv": "4.11.8", + "ajv-keywords": "1.5.1", + "chalk": "1.1.3", + "lodash": "4.17.10", + "slice-ansi": "0.0.4", + "string-width": "2.1.1" + } + }, + "tail": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tail/-/tail-1.2.4.tgz", + "integrity": "sha512-IAAm998XX0A1yuQgYzfyfWyDYb1tFI6Q548g71xLwqRn4MvnB+fstbmBoexxRI3i+n04rtetDBqmE0jrYSTvkA==" + }, + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } + }, + "tar-pack": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.1.tgz", + "integrity": "sha512-PPRybI9+jM5tjtCbN2cxmmRU7YmqT3Zv/UDy48tAh2XRkLa9bAORtSWLkVc13+GJF+cdTh1yEnHEk3cpTaL5Kg==", + "requires": { + "debug": "2.6.1", + "fstream": "1.0.11", + "fstream-ignore": "1.0.5", + "once": "1.4.0", + "readable-stream": "2.3.6", + "rimraf": "2.6.2", + "tar": "2.2.1", + "uid-number": "0.0.6" + } + }, + "tar-stream": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.1.tgz", + "integrity": "sha512-IFLM5wp3QrJODQFPm6/to3LJZrONdBY/otxcvDIQzu217zKye6yVR3hhi9lAjrC2Z+m/j5oDxMPb1qcd8cIvpA==", + "requires": { + "bl": "1.2.2", + "buffer-alloc": "1.2.0", + "end-of-stream": "1.4.1", + "fs-constants": "1.0.0", + "readable-stream": "2.3.6", + "to-buffer": "1.1.1", + "xtend": "4.0.1" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "tmp": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", + "integrity": "sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA=", + "dev": true, + "requires": { + "os-tmpdir": "1.0.2" + } + }, + "to-buffer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", + "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==" + }, + "tough-cookie": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "requires": { + "punycode": "1.4.1" + } + }, + "traverse": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", + "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=" + }, + "ts-node": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-3.3.0.tgz", + "integrity": "sha1-wTxqMCTjC+EYDdUwOPwgkonUv2k=", + "dev": true, + "requires": { + "arrify": "1.0.1", + "chalk": "2.4.1", + "diff": "3.2.0", + "make-error": "1.3.4", + "minimist": "1.2.0", + "mkdirp": "0.5.1", + "source-map-support": "0.4.18", + "tsconfig": "6.0.0", + "v8flags": "3.1.1", + "yn": "2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.2" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "tsconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-6.0.0.tgz", + "integrity": "sha1-aw6DdgA9evGGT434+J3QBZ/80DI=", + "dev": true, + "requires": { + "strip-bom": "3.0.0", + "strip-json-comments": "2.0.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "tweetnacl": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.3.tgz", + "integrity": "sha1-PaOC9nDyXe1417PReSEZvKC3Ey0=" + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2" + } + }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "2.1.18" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "typedoc": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.11.1.tgz", + "integrity": "sha512-jdNIoHm5wkZqxQTe/g9AQ3LKnZyrzHXqu6A/c9GUOeJyBWLxNr7/Dm3rwFvLksuxRNwTvY/0HRDU9sJTa9WQSg==", + "requires": { + "@types/fs-extra": "5.0.1", + "@types/handlebars": "4.0.36", + "@types/highlight.js": "9.12.2", + "@types/lodash": "4.14.104", + "@types/marked": "0.3.0", + "@types/minimatch": "3.0.3", + "@types/shelljs": "0.7.8", + "fs-extra": "5.0.0", + "handlebars": "4.0.11", + "highlight.js": "9.12.0", + "lodash": "4.17.10", + "marked": "0.3.19", + "minimatch": "3.0.4", + "progress": "2.0.0", + "shelljs": "0.8.2", + "typedoc-default-themes": "0.5.0", + "typescript": "2.7.2" + }, + "dependencies": { + "fs-extra": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", + "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", + "requires": { + "graceful-fs": "4.1.11", + "jsonfile": "4.0.0", + "universalify": "0.1.2" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "4.1.11" + } + }, + "typescript": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.7.2.tgz", + "integrity": "sha512-p5TCYZDAO0m4G344hD+wx/LATebLWZNkkh2asWUFqSsD2OrDNhbAHuSjobrmsUmdzjJjEeZVU9g1h3O6vpstnw==" + } + } + }, + "typedoc-default-themes": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.5.0.tgz", + "integrity": "sha1-bcJDPnjti+qOiHo6zeLzF4W9Yic=" + }, + "typedoc-plugin-sourcefile-url": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typedoc-plugin-sourcefile-url/-/typedoc-plugin-sourcefile-url-1.0.3.tgz", + "integrity": "sha1-+7zEtxvZLS95TRwWmkgAQilrH+Y=", + "dev": true + }, + "typescript": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.8.4.tgz", + "integrity": "sha512-IIU5cN1mR5J3z9jjdESJbnxikTrEz3lzAw/D0Tf45jHpBp55nY31UkUvmVHoffCfKHTqJs3fCLPDxknQTTFegQ==", + "dev": true + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "optional": true, + "requires": { + "source-map": "0.5.7", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "optional": true + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "optional": true + }, + "uid-number": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", + "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=" + }, + "ultron": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", + "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=" + }, + "underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" + }, + "underscore.string": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz", + "integrity": "sha1-jN2PusTi0uoefi6Al8QvRCKA+Fs=", + "dev": true + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "unzip": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/unzip/-/unzip-0.1.11.tgz", + "integrity": "sha1-iXScY7BY19kNYZ+GuYqhU107l/A=", + "requires": { + "binary": "0.3.0", + "fstream": "0.1.31", + "match-stream": "0.0.2", + "pullstream": "0.4.1", + "readable-stream": "1.0.34", + "setimmediate": "1.0.5" + }, + "dependencies": { + "fstream": { + "version": "0.1.31", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-0.1.31.tgz", + "integrity": "sha1-czfwWPu7vvqMn1YaKMqwhJICyYg=", + "requires": { + "graceful-fs": "3.0.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.2" + } + }, + "graceful-fs": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz", + "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", + "requires": { + "natives": "1.1.4" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "unzip2": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/unzip2/-/unzip2-0.2.5.tgz", + "integrity": "sha1-TveleaeMFcUfVQ9qBT2xlBSciZI=", + "requires": { + "binary": "0.3.0", + "fstream": "0.1.31", + "match-stream": "0.0.2", + "pullstream": "0.4.1", + "readable-stream": "1.0.34", + "setimmediate": "1.0.5" + }, + "dependencies": { + "fstream": { + "version": "0.1.31", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-0.1.31.tgz", + "integrity": "sha1-czfwWPu7vvqMn1YaKMqwhJICyYg=", + "requires": { + "graceful-fs": "3.0.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.2" + } + }, + "graceful-fs": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz", + "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", + "requires": { + "natives": "1.1.4" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "url2": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/url2/-/url2-0.0.0.tgz", + "integrity": "sha1-Tqq9HVw6yQ1iq0SFyZhCKGWgSxo=" + }, + "user-home": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", + "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", + "dev": true, + "requires": { + "os-homedir": "1.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utils-merge": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", + "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" + }, + "v8flags": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.1.tgz", + "integrity": "sha512-iw/1ViSEaff8NJ3HLyEjawk/8hjJib3E7pvG4pddVXfUg1983s3VGsiClDjhK64MQVDGqc1Q8r18S4VKQZS9EQ==", + "dev": true, + "requires": { + "homedir-polyfill": "1.0.1" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "walkdir": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.11.tgz", + "integrity": "sha1-oW0CXrkxvQO1LzCMrtD0D86+lTI=" + }, + "weak-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/weak-map/-/weak-map-1.0.0.tgz", + "integrity": "sha1-tm5Wqd8L0lp2u/G1FNsSkIBhSjc=" + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "2.1.1" + } + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "optional": true + }, + "winston": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.3.1.tgz", + "integrity": "sha1-C0hCDZeMAYBM8CMLZIhhWYIloRk=", + "requires": { + "async": "1.0.0", + "colors": "1.0.3", + "cycle": "1.0.3", + "eyes": "0.1.8", + "isstream": "0.1.2", + "stack-trace": "0.0.10" + }, + "dependencies": { + "async": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", + "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=" + }, + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" + } + } + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + }, + "wotb": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/wotb/-/wotb-0.6.4.tgz", + "integrity": "sha1-L/cCADEZjr6dbhFaiYtVSUCuw+k=", + "requires": { + "bindings": "1.2.1", + "nan": "2.2.0", + "node-pre-gyp": "0.6.23" + }, + "dependencies": { + "node-pre-gyp": { + "version": "0.6.23", + "bundled": true, + "requires": { + "mkdirp": "0.5.1", + "nopt": "3.0.6", + "npmlog": "2.0.2", + "rc": "1.1.6", + "request": "2.69.0", + "rimraf": "2.5.2", + "semver": "5.1.0", + "tar": "2.2.1", + "tar-pack": "3.1.3" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "bundled": true + } + } + }, + "nopt": { + "version": "3.0.6", + "bundled": true, + "requires": { + "abbrev": "1.0.7" + }, + "dependencies": { + "abbrev": { + "version": "1.0.7", + "bundled": true + } + } + }, + "npmlog": { + "version": "2.0.2", + "bundled": true, + "requires": { + "ansi": "0.3.1", + "are-we-there-yet": "1.0.6", + "gauge": "1.2.7" + }, + "dependencies": { + "ansi": { + "version": "0.3.1", + "bundled": true + }, + "are-we-there-yet": { + "version": "1.0.6", + "bundled": true, + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.0.5" + }, + "dependencies": { + "delegates": { + "version": "1.0.0", + "bundled": true + }, + "readable-stream": { + "version": "2.0.5", + "bundled": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.1", + "isarray": "0.0.1", + "process-nextick-args": "1.0.6", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "inherits": { + "version": "2.0.1", + "bundled": true + }, + "isarray": { + "version": "0.0.1", + "bundled": true + }, + "process-nextick-args": { + "version": "1.0.6", + "bundled": true + }, + "string_decoder": { + "version": "0.10.31", + "bundled": true + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + } + } + } + } + }, + "gauge": { + "version": "1.2.7", + "bundled": true, + "requires": { + "ansi": "0.3.1", + "has-unicode": "2.0.0", + "lodash.pad": "4.1.0", + "lodash.padend": "4.2.0", + "lodash.padstart": "4.2.0" + }, + "dependencies": { + "has-unicode": { + "version": "2.0.0", + "bundled": true + }, + "lodash.pad": { + "version": "4.1.0", + "bundled": true, + "requires": { + "lodash.repeat": "4.0.0", + "lodash.tostring": "4.1.2" + }, + "dependencies": { + "lodash.repeat": { + "version": "4.0.0", + "bundled": true, + "requires": { + "lodash.tostring": "4.1.2" + } + }, + "lodash.tostring": { + "version": "4.1.2", + "bundled": true + } + } + }, + "lodash.padend": { + "version": "4.2.0", + "bundled": true, + "requires": { + "lodash.repeat": "4.0.0", + "lodash.tostring": "4.1.2" + }, + "dependencies": { + "lodash.repeat": { + "version": "4.0.0", + "bundled": true, + "requires": { + "lodash.tostring": "4.1.2" + } + }, + "lodash.tostring": { + "version": "4.1.2", + "bundled": true + } + } + }, + "lodash.padstart": { + "version": "4.2.0", + "bundled": true, + "requires": { + "lodash.repeat": "4.0.0", + "lodash.tostring": "4.1.2" + }, + "dependencies": { + "lodash.repeat": { + "version": "4.0.0", + "bundled": true, + "requires": { + "lodash.tostring": "4.1.2" + } + }, + "lodash.tostring": { + "version": "4.1.2", + "bundled": true + } + } + } + } + } + } + }, + "rc": { + "version": "1.1.6", + "bundled": true, + "requires": { + "deep-extend": "0.4.1", + "ini": "1.3.4", + "minimist": "1.2.0", + "strip-json-comments": "1.0.4" + }, + "dependencies": { + "deep-extend": { + "version": "0.4.1", + "bundled": true + }, + "ini": { + "version": "1.3.4", + "bundled": true + }, + "minimist": { + "version": "1.2.0", + "bundled": true + }, + "strip-json-comments": { + "version": "1.0.4", + "bundled": true + } + } + }, + "request": { + "version": "2.69.0", + "bundled": true, + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.3.2", + "bl": "1.0.3", + "caseless": "0.11.0", + "combined-stream": "1.0.5", + "extend": "3.0.0", + "forever-agent": "0.6.1", + "form-data": "1.0.0-rc3", + "har-validator": "2.0.6", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.10", + "node-uuid": "1.4.7", + "oauth-sign": "0.8.1", + "qs": "6.0.2", + "stringstream": "0.0.5", + "tough-cookie": "2.2.1", + "tunnel-agent": "0.4.2" + }, + "dependencies": { + "aws-sign2": { + "version": "0.6.0", + "bundled": true + }, + "aws4": { + "version": "1.3.2", + "bundled": true, + "requires": { + "lru-cache": "4.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "4.0.0", + "bundled": true, + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.0.0" + }, + "dependencies": { + "pseudomap": { + "version": "1.0.2", + "bundled": true + }, + "yallist": { + "version": "2.0.0", + "bundled": true + } + } + } + } + }, + "bl": { + "version": "1.0.3", + "bundled": true, + "requires": { + "readable-stream": "2.0.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.0.5", + "bundled": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.1", + "isarray": "0.0.1", + "process-nextick-args": "1.0.6", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "inherits": { + "version": "2.0.1", + "bundled": true + }, + "isarray": { + "version": "0.0.1", + "bundled": true + }, + "process-nextick-args": { + "version": "1.0.6", + "bundled": true + }, + "string_decoder": { + "version": "0.10.31", + "bundled": true + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + } + } + } + } + }, + "caseless": { + "version": "0.11.0", + "bundled": true + }, + "combined-stream": { + "version": "1.0.5", + "bundled": true, + "requires": { + "delayed-stream": "1.0.0" + }, + "dependencies": { + "delayed-stream": { + "version": "1.0.0", + "bundled": true + } + } + }, + "extend": { + "version": "3.0.0", + "bundled": true + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true + }, + "form-data": { + "version": "1.0.0-rc3", + "bundled": true, + "requires": { + "async": "1.5.2", + "combined-stream": "1.0.5", + "mime-types": "2.1.10" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "bundled": true + } + } + }, + "har-validator": { + "version": "2.0.6", + "bundled": true, + "requires": { + "chalk": "1.1.1", + "commander": "2.9.0", + "is-my-json-valid": "2.13.1", + "pinkie-promise": "2.0.0" + }, + "dependencies": { + "chalk": { + "version": "1.1.1", + "bundled": true, + "requires": { + "ansi-styles": "2.2.0", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.0", + "bundled": true, + "requires": { + "color-convert": "1.0.0" + }, + "dependencies": { + "color-convert": { + "version": "1.0.0", + "bundled": true + } + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true + }, + "has-ansi": { + "version": "2.0.0", + "bundled": true, + "requires": { + "ansi-regex": "2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.0.0", + "bundled": true + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.0.0", + "bundled": true + } + } + }, + "supports-color": { + "version": "2.0.0", + "bundled": true + } + } + }, + "commander": { + "version": "2.9.0", + "bundled": true, + "requires": { + "graceful-readlink": "1.0.1" + }, + "dependencies": { + "graceful-readlink": { + "version": "1.0.1", + "bundled": true + } + } + }, + "is-my-json-valid": { + "version": "2.13.1", + "bundled": true, + "requires": { + "generate-function": "2.0.0", + "generate-object-property": "1.2.0", + "jsonpointer": "2.0.0", + "xtend": "4.0.1" + }, + "dependencies": { + "generate-function": { + "version": "2.0.0", + "bundled": true + }, + "generate-object-property": { + "version": "1.2.0", + "bundled": true, + "requires": { + "is-property": "1.0.2" + }, + "dependencies": { + "is-property": { + "version": "1.0.2", + "bundled": true + } + } + }, + "jsonpointer": { + "version": "2.0.0", + "bundled": true + }, + "xtend": { + "version": "4.0.1", + "bundled": true + } + } + }, + "pinkie-promise": { + "version": "2.0.0", + "bundled": true, + "requires": { + "pinkie": "2.0.4" + }, + "dependencies": { + "pinkie": { + "version": "2.0.4", + "bundled": true + } + } + } + } + }, + "hawk": { + "version": "3.1.3", + "bundled": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + }, + "dependencies": { + "boom": { + "version": "2.10.1", + "bundled": true, + "requires": { + "hoek": "2.16.3" + } + }, + "cryptiles": { + "version": "2.0.5", + "bundled": true, + "requires": { + "boom": "2.10.1" + } + }, + "hoek": { + "version": "2.16.3", + "bundled": true + }, + "sntp": { + "version": "1.0.9", + "bundled": true, + "requires": { + "hoek": "2.16.3" + } + } + } + }, + "http-signature": { + "version": "1.1.1", + "bundled": true, + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.2.2", + "sshpk": "1.7.4" + }, + "dependencies": { + "assert-plus": { + "version": "0.2.0", + "bundled": true + }, + "jsprim": { + "version": "1.2.2", + "bundled": true, + "requires": { + "extsprintf": "1.0.2", + "json-schema": "0.2.2", + "verror": "1.3.6" + }, + "dependencies": { + "extsprintf": { + "version": "1.0.2", + "bundled": true + }, + "json-schema": { + "version": "0.2.2", + "bundled": true + }, + "verror": { + "version": "1.3.6", + "bundled": true, + "requires": { + "extsprintf": "1.0.2" + } + } + } + }, + "sshpk": { + "version": "1.7.4", + "bundled": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "0.2.0", + "dashdash": "1.13.0", + "ecc-jsbn": "0.1.1", + "jodid25519": "1.0.2", + "jsbn": "0.1.0", + "tweetnacl": "0.14.1" + }, + "dependencies": { + "asn1": { + "version": "0.2.3", + "bundled": true + }, + "dashdash": { + "version": "1.13.0", + "bundled": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true + } + } + }, + "ecc-jsbn": { + "version": "0.1.1", + "bundled": true, + "optional": true, + "requires": { + "jsbn": "0.1.0" + } + }, + "jodid25519": { + "version": "1.0.2", + "bundled": true, + "optional": true, + "requires": { + "jsbn": "0.1.0" + } + }, + "jsbn": { + "version": "0.1.0", + "bundled": true, + "optional": true + }, + "tweetnacl": { + "version": "0.14.1", + "bundled": true, + "optional": true + } + } + } + } + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true + }, + "mime-types": { + "version": "2.1.10", + "bundled": true, + "requires": { + "mime-db": "1.22.0" + }, + "dependencies": { + "mime-db": { + "version": "1.22.0", + "bundled": true + } + } + }, + "node-uuid": { + "version": "1.4.7", + "bundled": true + }, + "oauth-sign": { + "version": "0.8.1", + "bundled": true + }, + "qs": { + "version": "6.0.2", + "bundled": true + }, + "stringstream": { + "version": "0.0.5", + "bundled": true + }, + "tough-cookie": { + "version": "2.2.1", + "bundled": true + }, + "tunnel-agent": { + "version": "0.4.2", + "bundled": true + } + } + }, + "rimraf": { + "version": "2.5.2", + "bundled": true, + "requires": { + "glob": "7.0.0" + }, + "dependencies": { + "glob": { + "version": "7.0.0", + "bundled": true, + "requires": { + "inflight": "1.0.4", + "inherits": "2.0.1", + "minimatch": "3.0.0", + "once": "1.3.3", + "path-is-absolute": "1.0.0" + }, + "dependencies": { + "inflight": { + "version": "1.0.4", + "bundled": true, + "requires": { + "once": "1.3.3", + "wrappy": "1.0.1" + }, + "dependencies": { + "wrappy": { + "version": "1.0.1", + "bundled": true + } + } + }, + "inherits": { + "version": "2.0.1", + "bundled": true + }, + "minimatch": { + "version": "3.0.0", + "bundled": true, + "requires": { + "brace-expansion": "1.1.3" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.3", + "bundled": true, + "requires": { + "balanced-match": "0.3.0", + "concat-map": "0.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "0.3.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + } + } + } + } + }, + "once": { + "version": "1.3.3", + "bundled": true, + "requires": { + "wrappy": "1.0.1" + }, + "dependencies": { + "wrappy": { + "version": "1.0.1", + "bundled": true + } + } + }, + "path-is-absolute": { + "version": "1.0.0", + "bundled": true + } + } + } + } + }, + "semver": { + "version": "5.1.0", + "bundled": true + }, + "tar": { + "version": "2.2.1", + "bundled": true, + "requires": { + "block-stream": "0.0.8", + "fstream": "1.0.8", + "inherits": "2.0.1" + }, + "dependencies": { + "block-stream": { + "version": "0.0.8", + "bundled": true, + "requires": { + "inherits": "2.0.1" + } + }, + "fstream": { + "version": "1.0.8", + "bundled": true, + "requires": { + "graceful-fs": "4.1.3", + "inherits": "2.0.1", + "mkdirp": "0.5.1", + "rimraf": "2.5.2" + }, + "dependencies": { + "graceful-fs": { + "version": "4.1.3", + "bundled": true + } + } + }, + "inherits": { + "version": "2.0.1", + "bundled": true + } + } + }, + "tar-pack": { + "version": "3.1.3", + "bundled": true, + "requires": { + "debug": "2.2.0", + "fstream": "1.0.8", + "fstream-ignore": "1.0.3", + "once": "1.3.3", + "readable-stream": "2.0.5", + "rimraf": "2.5.2", + "tar": "2.2.1", + "uid-number": "0.0.6" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "bundled": true, + "requires": { + "ms": "0.7.1" + }, + "dependencies": { + "ms": { + "version": "0.7.1", + "bundled": true + } + } + }, + "fstream": { + "version": "1.0.8", + "bundled": true, + "requires": { + "graceful-fs": "4.1.3", + "inherits": "2.0.1", + "mkdirp": "0.5.1", + "rimraf": "2.5.2" + }, + "dependencies": { + "graceful-fs": { + "version": "4.1.3", + "bundled": true + }, + "inherits": { + "version": "2.0.1", + "bundled": true + } + } + }, + "fstream-ignore": { + "version": "1.0.3", + "bundled": true, + "requires": { + "fstream": "1.0.8", + "inherits": "2.0.1", + "minimatch": "3.0.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "bundled": true + }, + "minimatch": { + "version": "3.0.0", + "bundled": true, + "requires": { + "brace-expansion": "1.1.3" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.3", + "bundled": true, + "requires": { + "balanced-match": "0.3.0", + "concat-map": "0.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "0.3.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + } + } + } + } + } + } + }, + "once": { + "version": "1.3.3", + "bundled": true, + "requires": { + "wrappy": "1.0.1" + }, + "dependencies": { + "wrappy": { + "version": "1.0.1", + "bundled": true + } + } + }, + "readable-stream": { + "version": "2.0.5", + "bundled": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.1", + "isarray": "0.0.1", + "process-nextick-args": "1.0.6", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "inherits": { + "version": "2.0.1", + "bundled": true + }, + "isarray": { + "version": "0.0.1", + "bundled": true + }, + "process-nextick-args": { + "version": "1.0.6", + "bundled": true + }, + "string_decoder": { + "version": "0.10.31", + "bundled": true + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + } + } + }, + "uid-number": { + "version": "0.0.6", + "bundled": true + } + } + } + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "0.5.1" + } + }, + "ws": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", + "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", + "requires": { + "options": "0.0.6", + "ultron": "1.0.2" + } + }, + "xml-escape": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/xml-escape/-/xml-escape-1.0.0.tgz", + "integrity": "sha1-AJY9aXsq3wwYXE4E5zF0upsojrI=", + "dev": true + }, + "xml2js": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.1.14.tgz", + "integrity": "sha1-UnTmf1pkxfkpdM2FE54DMq3GuQw=", + "requires": { + "sax": "1.2.4" + } + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "optional": true, + "requires": { + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", + "window-size": "0.1.0" + } + }, + "yn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", + "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", + "dev": true + }, + "zip-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-1.2.0.tgz", + "integrity": "sha1-qLxF9MG0lpnGuQGYuqyqzbzUugQ=", + "requires": { + "archiver-utils": "1.3.0", + "compress-commons": "1.2.2", + "lodash": "4.17.10", + "readable-stream": "2.3.6" + } + } + } +} diff --git a/package.json b/package.json index a1eea7dc21f280b4ac0ec26127d6a877e626c5fb..b722a32a9d35af7ad12991d1d913e35fbaad55d3 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "prepublish": "tsc", "tsc": "tsc", "tscw": "tsc -w", + "doc": "typedoc --out typedoc/ index.ts app/ --mode file --readme README.md --includeDeclarations --sourcefile-url-prefix \"https://git.duniter.org/nodes/typescript/duniter/blob/loki/\"", "test": "nyc --reporter html mocha", "start": "node bin/duniter start", "build": "tsc && cd \"node_modules/duniter-ui\" && npm install && npm run build", @@ -57,12 +58,13 @@ "url": "https://github.com/duniter/duniter/issues" }, "dependencies": { + "@types/ws": "^5.1.2", "archiver": "1.3.0", "async": "2.2.0", "bindings": "1.2.1", "body-parser": "1.17.1", "bs58": "^4.0.1", - "co": "4.6.0", + "cli-table": "^0.3.1", "colors": "1.1.2", "commander": "2.9.0", "cors": "2.8.2", @@ -75,6 +77,7 @@ "inquirer": "3.0.6", "jison": "0.4.17", "js-yaml": "3.8.2", + "lokijs": "^1.5.3", "merkle": "0.5.1", "moment": "2.19.3", "morgan": "1.8.1", @@ -84,7 +87,7 @@ "node-pre-gyp": "0.6.34", "node-uuid": "1.4.8", "optimist": "0.6.1", - "q-io": "1.13.2", + "q-io": "^1.13.5", "querablep": "^0.1.0", "request": "2.81.0", "request-promise": "4.2.0", @@ -95,6 +98,7 @@ "sqlite3": "3.1.13", "tail": "^1.2.1", "tweetnacl": "0.14.3", + "typedoc": "^0.11.1", "underscore": "1.8.3", "unzip": "0.1.11", "unzip2": "0.2.5", @@ -103,6 +107,7 @@ "ws": "1.1.5" }, "devDependencies": { + "@types/lokijs": "^1.5.2", "@types/mocha": "^2.2.41", "@types/node": "^8.0.9", "@types/should": "^8.3.0", @@ -118,7 +123,8 @@ "supertest": "", "tmp": "0.0.29", "ts-node": "^3.3.0", - "typescript": "^2.4.1" + "typedoc-plugin-sourcefile-url": "^1.0.3", + "typescript": "~2.8.1" }, "peerDependencies": {}, "bin": { diff --git a/release/arch/arm/build-arm.sh b/release/arch/arm/build-arm.sh index ea7c25c0c5b60d5bd1e6329e37d3a53c50434874..9fbc767db8463f56dc0f9d363055fc332c5f325d 100755 --- a/release/arch/arm/build-arm.sh +++ b/release/arch/arm/build-arm.sh @@ -68,7 +68,7 @@ cp -R "$DOWNLOADS/node-${NVER}-linux-${ARCH}" node npm install -npm install duniter-ui@1.6.x --save --production +npm install duniter-ui@1.7.x --save --production SRC=`pwd` echo $SRC diff --git a/release/arch/linux/build-lin.sh b/release/arch/linux/build-lin.sh index 0ca5c2db5fbeb07093a4cacfb808cac5df0a7fe2..6c1f66a7675c56066dcb6a1472ac9c939ce7c619 100644 --- a/release/arch/linux/build-lin.sh +++ b/release/arch/linux/build-lin.sh @@ -123,7 +123,7 @@ NW_VERSION=0.28.0 NW_RELEASE="v${NW_VERSION}" NW="nwjs-${NW_RELEASE}-linux-x64" NW_GZ="${NW}.tar.gz" -DUNITER_UI_VER="1.6.x" +DUNITER_UI_VER="1.7.x" nvm install ${NVER} || exit 1 nvm use ${NVER} || exit 1 @@ -168,11 +168,11 @@ rm -Rf .gitignore .git || exit 1 # Remove git files # Build echo ">> VM: building modules..." -npm install || exit 1 +yarn || exit 1 # Duniter UI -npm install "duniter-ui@${DUNITER_UI_VER}" || exit 1 -npm prune --production || exit 1 +yarn add "duniter-ui@${DUNITER_UI_VER}" || exit 1 +yarn --production || exit 1 rm -rf release coverage test # Non production folders cp -r "${RELEASES}/duniter" "${RELEASES}/desktop_" || exit 1 diff --git a/release/arch/windows/build.bat b/release/arch/windows/build.bat index 4ee27f5a7de6d0af5f4421a92c2fe6bcdfaa6332..57447a19304df74832407478cb24253eed76f321 100644 --- a/release/arch/windows/build.bat +++ b/release/arch/windows/build.bat @@ -66,7 +66,7 @@ call npm cache clean call npm install REM call npm test echo "Ajout du module 1/1 (duniter-ui)..." -call npm install duniter-ui@1.6.x --save --production +call npm install duniter-ui@1.7.x --save --production echo "Retrait des modules 'dev'..." call npm prune --production diff --git a/release/scripts/create-release.js b/release/scripts/create-release.js index badc4b1dfc80d086169e2a11c8ffbad75cc3c083..a64b17f98c1fb7c61866ecee2457291e47985c29 100644 --- a/release/scripts/create-release.js +++ b/release/scripts/create-release.js @@ -13,9 +13,6 @@ "use strict"; -const co = require('co'); -const fs = require('fs'); -const path = require('path'); const rp = require('request-promise'); const GITHUB_TOKEN = process.argv[2] @@ -23,12 +20,12 @@ const tagName = process.argv[3] const command = process.argv[4] const value = process.argv[5] -co(function*() { +(async () => { try { // Get release URL let release try { - release = yield github('/repos/duniter/duniter/releases/tags/' + tagName) + release = await github('/repos/duniter/duniter/releases/tags/' + tagName) } catch (e) { if (!(e && e.statusCode == 404)) { throw e @@ -38,7 +35,7 @@ co(function*() { // Creation if (command === "create") { if (!release) { - release = yield github('/repos/duniter/duniter/releases', 'POST', { + release = await github('/repos/duniter/duniter/releases', 'POST', { tag_name: tagName, draft: false, prerelease: true @@ -59,7 +56,7 @@ co(function*() { if (!release) { console.error('Release ' + tagName + ' does not exist.') } else { - release = yield github('/repos/duniter/duniter/releases/' + release.id, 'PATCH', { + release = await github('/repos/duniter/duniter/releases/' + release.id, 'PATCH', { tag_name: tagName, draft: false, prerelease: isPreRelease @@ -77,21 +74,19 @@ co(function*() { console.error(e); } process.exit(0); -}); +})() -function github(url, method = 'GET', body = undefined) { - return co(function*() { - yield new Promise((resolve) => setTimeout(resolve, 1)); - return yield rp({ - uri: 'https://api.github.com' + url, - method, - body, - json: true, - headers: { - 'User-Agent': 'Request-Promise', - 'Authorization': 'token ' + GITHUB_TOKEN, - 'Accept': 'application/vnd.github.v3+json' - } - }); +async function github(url, method = 'GET', body = undefined) { + await new Promise((resolve) => setTimeout(resolve, 1)); + return await rp({ + uri: 'https://api.github.com' + url, + method, + body, + json: true, + headers: { + 'User-Agent': 'Request-Promise', + 'Authorization': 'token ' + GITHUB_TOKEN, + 'Accept': 'application/vnd.github.v3+json' + } }); } diff --git a/release/scripts/upload-release.js b/release/scripts/upload-release.js index 3d64eafabf4765124590193a31334cff7243e95b..a62e37b1c12436af3a8f30f74647e590557f1981 100644 --- a/release/scripts/upload-release.js +++ b/release/scripts/upload-release.js @@ -13,7 +13,6 @@ "use strict"; -const co = require('co'); const fs = require('fs'); const path = require('path'); const rp = require('request-promise'); @@ -23,51 +22,47 @@ const tagName = process.argv[3] const filePath = process.argv[4] const fileType = getFileType(filePath) -co(function*() { +(async () => { try { // Get release URL - const release = yield github('/repos/duniter/duniter/releases/tags/' + tagName); // May be a draft + const release = await github('/repos/duniter/duniter/releases/tags/' + tagName); // May be a draft console.log('Release: ' + release.tag_name); const filename = path.basename(filePath) console.log('Uploading asset %s...', filename); const upload_url = release.upload_url.replace('{?name,label}', '?' + ['name=' + filename].join('&')); - yield githubUpload(upload_url, filePath, fileType) + await githubUpload(upload_url, filePath, fileType) } catch (e) { console.error(e); } process.exit(0); -}); +})() -function github(url) { - return co(function*() { - yield new Promise((resolve) => setTimeout(resolve, 1)); - return yield rp({ - uri: 'https://api.github.com' + url, - json: true, - headers: { - 'User-Agent': 'Request-Promise', - 'Authorization': 'token ' + GITHUB_TOKEN, - 'Accept': 'application/vnd.github.v3+json' - } - }); +async function github(url) { + await new Promise((resolve) => setTimeout(resolve, 1)); + return await rp({ + uri: 'https://api.github.com' + url, + json: true, + headers: { + 'User-Agent': 'Request-Promise', + 'Authorization': 'token ' + GITHUB_TOKEN, + 'Accept': 'application/vnd.github.v3+json' + } }); } -function githubUpload(upload_url, filePath, type) { - return co(function*() { - const stats = fs.statSync(filePath); - return yield rp({ - method: 'POST', - body: fs.createReadStream(filePath), - uri: upload_url, - headers: { - 'User-Agent': 'Request-Promise', - 'Authorization': 'token ' + GITHUB_TOKEN, - 'Content-type': type, - 'Accept': 'application/json', - 'Content-Length': stats.size - } - }); +async function githubUpload(upload_url, filePath, type) { + const stats = fs.statSync(filePath); + return await rp({ + method: 'POST', + body: fs.createReadStream(filePath), + uri: upload_url, + headers: { + 'User-Agent': 'Request-Promise', + 'Authorization': 'token ' + GITHUB_TOKEN, + 'Content-type': type, + 'Accept': 'application/json', + 'Content-Length': stats.size + } }); } diff --git a/server.ts b/server.ts index 026ddf671f33b9ca98489bb4005e2257c2aad78b..1ddb6257a6f64b456858bc576b2739d241627661 100644 --- a/server.ts +++ b/server.ts @@ -17,9 +17,7 @@ import {PeeringService} from "./app/service/PeeringService" import {BlockchainService} from "./app/service/BlockchainService" import {TransactionService} from "./app/service/TransactionsService" import {ConfDTO} from "./app/lib/dto/ConfDTO" -import {FileDAL} from "./app/lib/dal/fileDAL" -import {DuniterBlockchain} from "./app/lib/blockchain/DuniterBlockchain" -import {SQLBlockchain} from "./app/lib/blockchain/SqlBlockchain" +import {FileDAL, FileDALParams} from "./app/lib/dal/fileDAL" import * as stream from "stream" import {KeyGen, randomKey} from "./app/lib/common-libs/crypto/keyring" import {parsers} from "./app/lib/common-libs/parsers/index" @@ -37,7 +35,11 @@ import {PeerDTO} from "./app/lib/dto/PeerDTO" import {OtherConstants} from "./app/lib/other_constants" import {WS2PCluster} from "./app/modules/ws2p/lib/WS2PCluster" import {DBBlock} from "./app/lib/db/DBBlock" -import { ProxiesConf } from './app/lib/proxy'; +import {ProxiesConf} from './app/lib/proxy'; +import {Directory, FileSystem} from "./app/lib/system/directory" +import {DataErrors} from "./app/lib/common-libs/errors" +import {DBPeer} from "./app/lib/db/DBPeer" +import {Underscore} from "./app/lib/common-libs/underscore" export interface HookableServer { generatorGetJoinData: (...args:any[]) => Promise<any> @@ -56,12 +58,11 @@ const es = require('event-stream'); const daemonize = require("daemonize2") const constants = require('./app/lib/constants'); const jsonpckg = require('./package.json'); -const directory = require('./app/lib/system/directory'); const logger = require('./app/lib/logger').NewLogger('server'); export class Server extends stream.Duplex implements HookableServer { - private paramsP:Promise<any>|null + private paramsP:Promise<FileDALParams> private endpointsDefinitions:(()=>Promise<string>)[] = [] private wrongEndpointsFilters:((endpoints:string[])=>Promise<string[]>)[] = [] startService:()=>Promise<void> @@ -94,7 +95,7 @@ export class Server extends stream.Duplex implements HookableServer { this.version = jsonpckg.version; this.logger = logger; - this.paramsP = directory.getHomeParams(memoryOnly, home) + this.paramsP = Directory.getHomeParams(memoryOnly, home) this.documentFIFO = new GlobalFifoPromise() @@ -158,9 +159,18 @@ export class Server extends stream.Duplex implements HookableServer { await this.dal.close() } - async loadConf(useDefaultConf:any = false) { + async reloadConf() { + await this.loadConf(false, true) + } + + async loadConf(useDefaultConf:any = false, reuseExisting = false) { logger.debug('Loading conf...'); - this.conf = await this.dal.loadConf(this.overrideConf, useDefaultConf) + const loaded = await this.dal.loadConf(this.overrideConf, useDefaultConf) + if (!reuseExisting || !this.conf) { + this.conf = loaded + } else { + Underscore.extend(this.conf, loaded) // Overwrite the current conf + } // Default values this.conf.proxiesConf = this.conf.proxiesConf === undefined ? new ProxiesConf() : this.conf.proxiesConf this.conf.remoteipv6 = this.conf.remoteipv6 === undefined ? this.conf.ipv6 : this.conf.remoteipv6 @@ -196,8 +206,6 @@ export class Server extends stream.Duplex implements HookableServer { // Extract key pair this.keyPair = KeyGen(this.conf.pair.pub, this.conf.pair.sec); this.sign = (msg:string) => this.keyPair.sign(msg) - // Blockchain object - this.blockchain = new DuniterBlockchain(new SQLBlockchain(this.dal), this.dal); // Update services this.IdentityService.setConfDAL(this.conf, this.dal) this.MembershipService.setConfDAL(this.conf, this.dal) @@ -327,21 +335,23 @@ export class Server extends stream.Duplex implements HookableServer { let head_1 = await this.dal.bindexDAL.head(1); if (head_1) { // Case 1: b_index < block - await this.dal.blockDAL.exec('DELETE FROM block WHERE NOT fork AND number > ' + head_1.number); + await this.dal.blockDAL.dropNonForkBlocksAbove(head_1.number) // Case 2: b_index > block const current = await this.dal.blockDAL.getCurrent(); - const nbBlocksToRevert = (head_1.number - current.number); + const nbBlocksToRevert = (head_1.number - (current as DBBlock).number); for (let i = 0; i < nbBlocksToRevert; i++) { - await this.revert(); + await this.revertHead(); } } + // Database trimming + await this.dal.loki.flushAndTrimData() // Eventual block resolution await this.BlockchainService.blockResolution() // Eventual fork resolution await this.BlockchainService.forkResolution() } - recomputeSelfPeer() { + recomputeSelfPeer(): Promise<DBPeer | null> { return this.PeeringService.generateSelfPeer(this.conf) } @@ -364,24 +374,24 @@ export class Server extends stream.Duplex implements HookableServer { const params = await this.paramsP; const myFS = params.fs; const rootPath = params.home; - const existsDir = await myFS.exists(rootPath); + const existsDir = await myFS.fsExists(rootPath); if (existsDir) { - await myFS.removeTree(rootPath); + await myFS.fsRemoveTree(rootPath); } } async resetAll(done:any = null) { await this.resetDataHook() await this.resetConfigHook() - const files = ['stats', 'cores', 'current', directory.DUNITER_DB_NAME, directory.DUNITER_DB_NAME + '.db', directory.DUNITER_DB_NAME + '.log', directory.WOTB_FILE, 'export.zip', 'import.zip', 'conf']; - const dirs = ['blocks', 'blockchain', 'ud_history', 'branches', 'certs', 'txs', 'cores', 'sources', 'links', 'ms', 'identities', 'peers', 'indicators', 'leveldb']; + const files = ['stats', 'cores', 'current', Directory.DUNITER_DB_NAME, Directory.DUNITER_DB_NAME + '.db', Directory.DUNITER_DB_NAME + '.log', Directory.WOTB_FILE, 'export.zip', 'import.zip', 'conf']; + const dirs = ['archives', 'loki', 'blocks', 'blockchain', 'ud_history', 'branches', 'certs', 'txs', 'cores', 'sources', 'links', 'ms', 'identities', 'peers', 'indicators', 'leveldb']; return this.resetFiles(files, dirs, done); } async resetData(done:any = null) { await this.resetDataHook() - const files = ['stats', 'cores', 'current', directory.DUNITER_DB_NAME, directory.DUNITER_DB_NAME + '.db', directory.DUNITER_DB_NAME + '.log', directory.WOTB_FILE]; - const dirs = ['blocks', 'ud_history', 'branches', 'certs', 'txs', 'cores', 'sources', 'links', 'ms', 'identities', 'peers', 'indicators', 'leveldb']; + const files = ['stats', 'cores', 'current', Directory.DUNITER_DB_NAME, Directory.DUNITER_DB_NAME + '.db', Directory.DUNITER_DB_NAME + '.log', Directory.WOTB_FILE]; + const dirs = ['archives', 'loki', 'blocks', 'ud_history', 'branches', 'certs', 'txs', 'cores', 'sources', 'links', 'ms', 'identities', 'peers', 'indicators', 'leveldb']; await this.resetFiles(files, dirs, done); } @@ -407,12 +417,12 @@ export class Server extends stream.Duplex implements HookableServer { const rootPath = params.home; const myFS = params.fs; const archive = archiver('zip'); - if (await myFS.exists(path.join(rootPath, 'indicators'))) { + if (await myFS.fsExists(path.join(rootPath, 'indicators'))) { archive.directory(path.join(rootPath, 'indicators'), '/indicators', undefined, { name: 'indicators'}); } const files = ['duniter.db', 'stats.json', 'wotb.bin']; for (const file of files) { - if (await myFS.exists(path.join(rootPath, file))) { + if (await myFS.fsExists(path.join(rootPath, file))) { archive.file(path.join(rootPath, file), { name: file }); } } @@ -434,42 +444,42 @@ export class Server extends stream.Duplex implements HookableServer { async cleanDBData() { await this.dal.cleanCaches(); this.dal.wotb.resetWoT(); - const files = ['stats', 'cores', 'current', directory.DUNITER_DB_NAME, directory.DUNITER_DB_NAME + '.db', directory.DUNITER_DB_NAME + '.log']; - const dirs = ['blocks', 'ud_history', 'branches', 'certs', 'txs', 'cores', 'sources', 'links', 'ms', 'identities', 'peers', 'indicators', 'leveldb']; + const files = ['stats', 'cores', 'current', Directory.DUNITER_DB_NAME, Directory.DUNITER_DB_NAME + '.db', Directory.DUNITER_DB_NAME + '.log']; + const dirs = ['loki', 'blocks', 'ud_history', 'branches', 'certs', 'txs', 'cores', 'sources', 'links', 'ms', 'identities', 'peers', 'indicators', 'leveldb']; return this.resetFiles(files, dirs); } private async resetFiles(files:string[], dirs:string[], done:any = null) { try { const params = await this.paramsP; - const myFS = params.fs; + const myFS:FileSystem = params.fs; const rootPath = params.home; for (const fName of files) { // JSON file? - const existsJSON = await myFS.exists(rootPath + '/' + fName + '.json'); + const existsJSON = await myFS.fsExists(rootPath + '/' + fName + '.json'); if (existsJSON) { const theFilePath = rootPath + '/' + fName + '.json'; - await myFS.remove(theFilePath); - if (await myFS.exists(theFilePath)) { + await myFS.fsUnlink(theFilePath); + if (await myFS.fsExists(theFilePath)) { throw Error('Failed to delete file "' + theFilePath + '"'); } } else { // Normal file? const normalFile = path.join(rootPath, fName); - const existsFile = await myFS.exists(normalFile); + const existsFile = await myFS.fsExists(normalFile); if (existsFile) { - await myFS.remove(normalFile); - if (await myFS.exists(normalFile)) { + await myFS.fsUnlink(normalFile); + if (await myFS.fsExists(normalFile)) { throw Error('Failed to delete file "' + normalFile + '"'); } } } } for (const dirName of dirs) { - const existsDir = await myFS.exists(rootPath + '/' + dirName); + const existsDir = await myFS.fsExists(rootPath + '/' + dirName); if (existsDir) { - await myFS.removeTree(rootPath + '/' + dirName); - if (await myFS.exists(rootPath + '/' + dirName)) { + await myFS.fsRemoveTree(rootPath + '/' + dirName); + if (await myFS.fsExists(rootPath + '/' + dirName)) { throw Error('Failed to delete folder "' + rootPath + '/' + dirName + '"'); } } @@ -492,8 +502,15 @@ export class Server extends stream.Duplex implements HookableServer { return this.BlockchainService.revertCurrentBlock() } + revertHead() { + return this.BlockchainService.revertCurrentHead() + } + async revertTo(number:number) { const current = await this.BlockchainService.current(); + if (!current) { + throw Error(DataErrors[DataErrors.CANNOT_REVERT_NO_CURRENT_BLOCK]) + } for (let i = 0, count = current.number - number; i < count; i++) { await this.BlockchainService.revertCurrentBlock() } @@ -518,6 +535,9 @@ export class Server extends stream.Duplex implements HookableServer { async reapplyTo(number:number) { const current = await this.BlockchainService.current(); + if (!current) { + throw Error(DataErrors[DataErrors.CANNOT_REAPPLY_NO_CURRENT_BLOCK]) + } if (current.number == number) { logger.warn('Already reached'); } else { @@ -543,8 +563,8 @@ export class Server extends stream.Duplex implements HookableServer { const argv = this.getCommand(overrideCommand, insteadOfCmd) return daemonize.setup({ main: mainModule, - name: directory.INSTANCE_NAME, - pidfile: path.join(directory.INSTANCE_HOME, "app.pid"), + name: Directory.INSTANCE_NAME, + pidfile: path.join(Directory.INSTANCE_HOME, "app.pid"), argv, cwd }); @@ -631,7 +651,7 @@ export class Server extends stream.Duplex implements HookableServer { /** * Default WoT incoming data for new block. To be overriden by a module. */ - generatorGetJoinData(current:DBBlock, idtyHash:string , char:string): Promise<any> { + generatorGetJoinData(current:DBBlock|null, idtyHash:string , char:string): Promise<any> { return Promise.resolve({}) } diff --git a/test/blockchain/basic-blockchain.ts b/test/blockchain/basic-blockchain.ts deleted file mode 100644 index f052c4dace531f85734b0b33ec64ab6ddb0d488d..0000000000000000000000000000000000000000 --- a/test/blockchain/basic-blockchain.ts +++ /dev/null @@ -1,174 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -import {BasicBlockchain} from "../../app/lib/blockchain/BasicBlockchain" -import {ArrayBlockchain} from "./lib/ArrayBlockchain" -import {SQLBlockchain} from "../../app/lib/blockchain/SqlBlockchain" -import {SQLiteDriver} from "../../app/lib/dal/drivers/SQLiteDriver" -import {BIndexDAL} from "../../app/lib/dal/sqliteDAL/index/BIndexDAL"; -import {MetaDAL} from "../../app/lib/dal/sqliteDAL/MetaDAL"; -import {ConfDTO} from "../../app/lib/dto/ConfDTO"; - -const assert = require('assert') - -let blockchain:BasicBlockchain, - emptyBlockchain:BasicBlockchain - -describe('Basic Memory Blockchain', () => { - - before(() => { - blockchain = new BasicBlockchain(new ArrayBlockchain()) - emptyBlockchain = new BasicBlockchain(new ArrayBlockchain()) - }) - - it('should be able to push 3 blocks and read them', async () => { - await blockchain.pushBlock({ name: 'A' }) - await blockchain.pushBlock({ name: 'B' }) - await blockchain.pushBlock({ name: 'C' }) - const HEAD0 = await blockchain.head() - const HEAD1 = await blockchain.head(1) - const HEAD2 = await blockchain.head(2) - const BLOCK0 = await blockchain.getBlock(0) - const BLOCK1 = await blockchain.getBlock(1) - const BLOCK2 = await blockchain.getBlock(2) - assert.equal(HEAD0.name, 'C') - assert.equal(HEAD1.name, 'B') - assert.equal(HEAD2.name, 'A') - assert.deepEqual(HEAD2, BLOCK0) - assert.deepEqual(HEAD1, BLOCK1) - assert.deepEqual(HEAD0, BLOCK2) - }) - - it('should be able to read a range', async () => { - const range1 = await blockchain.headRange(2) - assert.equal(range1.length, 2) - assert.equal(range1[0].name, 'C') - assert.equal(range1[1].name, 'B') - const range2 = await blockchain.headRange(6) - assert.equal(range2.length, 3) - assert.equal(range2[0].name, 'C') - assert.equal(range2[1].name, 'B') - assert.equal(range2[2].name, 'A') - }) - - it('should have a good height', async () => { - const height1 = await blockchain.height() - await blockchain.pushBlock({ name: 'D' }) - const height2 = await blockchain.height() - const height3 = await emptyBlockchain.height() - assert.equal(height1, 3) - assert.equal(height2, 4) - assert.equal(height3, 0) - }) - - it('should be able to revert blocks', async () => { - const reverted = await blockchain.revertHead() - const height2 = await blockchain.height() - assert.equal(height2, 3) - assert.equal(reverted.name, 'D') - }) - -}) - -describe('Basic SQL Blockchain', () => { - - before(async () => { - - { - const db = new SQLiteDriver(':memory:') - - const bindexDAL = new BIndexDAL(db) - const metaDAL = new MetaDAL(db) - - await bindexDAL.init() - await metaDAL.init() - await metaDAL.exec('CREATE TABLE txs (id INTEGER null);') - await metaDAL.exec('CREATE TABLE idty (id INTEGER null);') - await metaDAL.exec('CREATE TABLE cert (id INTEGER null);') - await metaDAL.exec('CREATE TABLE membership (id INTEGER null);') - await metaDAL.exec('CREATE TABLE block (fork INTEGER null);') - await metaDAL.upgradeDatabase(ConfDTO.mock()); - - const dal = { bindexDAL } - - blockchain = new BasicBlockchain(new SQLBlockchain(dal)) - } - { - const db = new SQLiteDriver(':memory:') - - const bindexDAL = new BIndexDAL(db) - const metaDAL = new MetaDAL(db) - - await bindexDAL.init() - await metaDAL.init() - await metaDAL.exec('CREATE TABLE txs (id INTEGER null);') - await metaDAL.exec('CREATE TABLE idty (id INTEGER null);') - await metaDAL.exec('CREATE TABLE cert (id INTEGER null);') - await metaDAL.exec('CREATE TABLE membership (id INTEGER null);') - await metaDAL.exec('CREATE TABLE block (fork INTEGER null);') - await metaDAL.upgradeDatabase(ConfDTO.mock()); - - const dal = { bindexDAL } - - emptyBlockchain = new BasicBlockchain(new SQLBlockchain(dal)) - } - }) - - it('should be able to push 3 blocks and read them', async () => { - await blockchain.pushBlock({ number: 0, version: 1, bsize: 0, hash: 'H', issuer: 'I', time: 1, membersCount: 1, issuersCount: 1, issuersFrame: 1, issuersFrameVar: 1, avgBlockSize: 0, medianTime: 1, dividend: 10, mass: 100, unitBase: 0, powMin: 0, udTime: 0, udReevalTime: 0, diffNumber: 1, speed: 1, massReeval: 1 }) - await blockchain.pushBlock({ number: 1, version: 1, bsize: 0, hash: 'H', issuer: 'I', time: 1, membersCount: 1, issuersCount: 1, issuersFrame: 1, issuersFrameVar: 1, avgBlockSize: 0, medianTime: 1, dividend: 10, mass: 100, unitBase: 0, powMin: 0, udTime: 0, udReevalTime: 0, diffNumber: 1, speed: 1, massReeval: 1 }) - await blockchain.pushBlock({ number: 2, version: 1, bsize: 0, hash: 'H', issuer: 'I', time: 1, membersCount: 1, issuersCount: 1, issuersFrame: 1, issuersFrameVar: 1, avgBlockSize: 0, medianTime: 1, dividend: 10, mass: 100, unitBase: 0, powMin: 0, udTime: 0, udReevalTime: 0, diffNumber: 1, speed: 1, massReeval: 1 }) - const HEAD0 = await blockchain.head() - const HEAD1 = await blockchain.head(1) - const HEAD2 = await blockchain.head(2) - const BLOCK0 = await blockchain.getBlock(0) - const BLOCK1 = await blockchain.getBlock(1) - const BLOCK2 = await blockchain.getBlock(2) - assert.equal(HEAD0.number, 2) - assert.equal(HEAD1.number, 1) - assert.equal(HEAD2.number, 0) - assert.deepEqual(HEAD2, BLOCK0) - assert.deepEqual(HEAD1, BLOCK1) - assert.deepEqual(HEAD0, BLOCK2) - }) - - it('should be able to read a range', async () => { - const range1 = await blockchain.headRange(2) - assert.equal(range1.length, 2) - assert.equal(range1[0].number, 2) - assert.equal(range1[1].number, 1) - const range2 = await blockchain.headRange(6) - assert.equal(range2.length, 3) - assert.equal(range2[0].number, 2) - assert.equal(range2[1].number, 1) - assert.equal(range2[2].number, 0) - }) - - it('should have a good height', async () => { - const height1 = await blockchain.height() - await blockchain.pushBlock({ number: 3, version: 1, bsize: 0, hash: 'H', issuer: 'I', time: 1, membersCount: 1, issuersCount: 1, issuersFrame: 1, issuersFrameVar: 1, avgBlockSize: 0, medianTime: 1, dividend: 10, mass: 100, unitBase: 0, powMin: 0, udTime: 0, udReevalTime: 0, diffNumber: 1, speed: 1, massReeval: 1 }) - const height2 = await blockchain.height() - const height3 = await emptyBlockchain.height() - assert.equal(height1, 3) - assert.equal(height2, 4) - assert.equal(height3, 0) - }) - - it('should be able to revert blocks', async () => { - const reverted = await blockchain.revertHead() - const height2 = await blockchain.height() - assert.equal(height2, 3) - assert.equal(reverted.number, 3) - }) - -}) diff --git a/test/blockchain/indexed-blockchain.ts b/test/blockchain/indexed-blockchain.ts deleted file mode 100644 index 8304e1c32e5c05d653980ac21d52d633b550777b..0000000000000000000000000000000000000000 --- a/test/blockchain/indexed-blockchain.ts +++ /dev/null @@ -1,467 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict"; -import {ArrayBlockchain} from "./lib/ArrayBlockchain" -import {IndexedBlockchain} from "../../app/lib/blockchain/IndexedBlockchain" -import {MemoryIndex} from "./lib/MemoryIndex" -import {SQLIndex} from "../../app/lib/blockchain/SqlIndex" -import {SQLiteDriver} from "../../app/lib/dal/drivers/SQLiteDriver" - -const assert = require('assert') - -describe('Indexed Blockchain', () => { - - describe('MemoryIndex', () => { - - let blockchain:any - - describe('PK on one field', () => { - - before(() => { - blockchain = new IndexedBlockchain(new ArrayBlockchain(), new MemoryIndex(), 'writtenOn', { - iindex: { - pk: 'name', - remove: 'expired' - }, - zindex: { - pk: 'name' - } - }) - }) - - it('should be able to index data', async () => { - await blockchain.recordIndex({ - iindex: [ - { name: 'A', status: 'OK', writtenOn: 23000, events: 0, member: false }, - { name: 'A', status: 'OK', writtenOn: 23000, events: 4 }, - { name: 'A', status: 'OK', writtenOn: 23000, events: 5, member: true }, - { name: 'A', status: 'OK', writtenOn: 23601 }, - { name: 'A', status: 'OK', writtenOn: 23888 }, - { name: 'A', status: 'OK', writtenOn: 23889 }, - { name: 'B', status: 'OK', writtenOn: 23000, events: 1, member: false }, - { name: 'B', status: 'KO', writtenOn: 23000, events: null }, - { name: 'C', status: 'KO', writtenOn: 23500 }, - { name: 'D', status: 'KO', writtenOn: 23500 }, - { name: 'D', status: 'KO', writtenOn: 23521, expired: true } - ] - }) - }) - - it('should be able to reduce data', async () => { - const reducedA = await blockchain.indexReduce('iindex', { name: 'A' }) - const reducedB = await blockchain.indexReduce('iindex', { name: 'B' }) - assert.deepEqual(reducedA, { name: 'A', status: 'OK', writtenOn: 23889, events: 5, member: true }) - assert.deepEqual(reducedB, { name: 'B', status: 'KO', writtenOn: 23000, events: 1, member: false }) - }) - - it('should be able to count data', async () => { - const countAi = await blockchain.indexCount('iindex', { name: 'A' }) - const countBi = await blockchain.indexCount('iindex', { name: 'B' }) - const countCi = await blockchain.indexCount('iindex', { name: 'C' }) - const countDi = await blockchain.indexCount('iindex', { name: 'D' }) - const countBz = await blockchain.indexCount('zindex', { name: 'B' }) - assert.equal(countAi, 6) - assert.equal(countBi, 2) - assert.equal(countCi, 1) - assert.equal(countDi, 2) - assert.equal(countBz, 0) - }) - - it('should be able to reduce grouped data', async () => { - const reducedBy = await blockchain.indexReduceGroupBy('iindex', { writtenOn: 23000 }, ['name']) - assert.deepEqual(reducedBy, [ - { name: 'A', status: 'OK', writtenOn: 23000, events: 5, member: true }, - { name: 'B', status: 'KO', writtenOn: 23000, events: 1, member: false } - ]) - }) - - it('should be able to trim data', async () => { - // The number of records should decrease - await blockchain.indexTrim(23601) - const countAi = await blockchain.indexCount('iindex', { name: 'A' }) - const countBi = await blockchain.indexCount('iindex', { name: 'B' }) - const countCi = await blockchain.indexCount('iindex', { name: 'C' }) - const countDi = await blockchain.indexCount('iindex', { name: 'D' }) - const countBz = await blockchain.indexCount('zindex', { name: 'B' }) - assert.equal(countAi, 4) - assert.equal(countBi, 1) - assert.equal(countCi, 1) - assert.equal(countDi, 0) // Expired = remove rows on trim - assert.equal(countBz, 0) - const reducedAi = await blockchain.indexReduce('iindex', { name: 'A' }) - const reducedBi = await blockchain.indexReduce('iindex', { name: 'B' }) - const reducedCi = await blockchain.indexReduce('iindex', { name: 'C' }) - const reducedDi = await blockchain.indexReduce('iindex', { name: 'D' }) - const reducedBz = await blockchain.indexReduce('zindex', { name: 'B' }) - assert.deepEqual(reducedAi, { name: 'A', status: 'OK', writtenOn: 23889, events: 5, member: true }) - assert.deepEqual(reducedBi, { name: 'B', status: 'KO', writtenOn: 23000, events: 1, member: false }) - assert.deepEqual(reducedCi, { name: 'C', status: 'KO', writtenOn: 23500 }) - assert.deepEqual(reducedDi, {}) - assert.deepEqual(reducedBz, {}) - }) - }) - - describe('PK on two fields', () => { - - before(() => { - blockchain = new IndexedBlockchain(new ArrayBlockchain(), new MemoryIndex(), 'writtenOn', { - iindex: { - pk: ['id', 'pos'], - remove: 'expired' - }, - zindex: { - pk: 'name' - } - }) - }) - - it('should be able to index data', async () => { - await blockchain.recordIndex({ - iindex: [ - { id: 'A', pos: 0, status: 'OK', writtenOn: 23000, events: 0, member: false }, - { id: 'A', pos: 0, status: 'OK', writtenOn: 23000, events: 4 }, - { id: 'A', pos: 0, status: 'OK', writtenOn: 23000, events: 5, member: true }, - { id: 'A', pos: 0, status: 'OK', writtenOn: 23601 }, - { id: 'A', pos: 1, status: 'OK', writtenOn: 23888 }, - { id: 'A', pos: 2, status: 'OK', writtenOn: 23889 }, - { id: 'B', pos: 0, status: 'OK', writtenOn: 23000, events: 1, member: false }, - { id: 'B', pos: 0, status: 'KO', writtenOn: 23000, events: null }, - { id: 'C', pos: 0, status: 'KO', writtenOn: 23500 }, - { id: 'D', pos: 0, status: 'KO', writtenOn: 23500 }, - { id: 'D', pos: 1, status: 'KO', writtenOn: 23521, expired: true } - ] - }) - }) - - it('should be able to reduce data', async () => { - const reducedA = await blockchain.indexReduce('iindex', { id: 'A', pos: 0 }) - const reducedB = await blockchain.indexReduce('iindex', { id: 'B', pos: 0 }) - assert.deepEqual(reducedA, { id: 'A', pos: 0, status: 'OK', writtenOn: 23601, events: 5, member: true }) - assert.deepEqual(reducedB, { id: 'B', pos: 0, status: 'KO', writtenOn: 23000, events: 1, member: false }) - }) - - it('should be able to count data', async () => { - const countAi = await blockchain.indexCount('iindex', { id: 'A', pos: 0 }) - const countBi = await blockchain.indexCount('iindex', { id: 'B', pos: 0 }) - const countCi = await blockchain.indexCount('iindex', { id: 'C', pos: 0 }) - const countDi = await blockchain.indexCount('iindex', { id: 'D', pos: 0 }) - const countBz = await blockchain.indexCount('zindex', { id: 'B', pos: 0 }) - assert.equal(countAi, 4) - assert.equal(countBi, 2) - assert.equal(countCi, 1) - assert.equal(countDi, 1) - assert.equal(countBz, 0) - }) - - it('should be able to reduce grouped data', async () => { - const reducedBy = await blockchain.indexReduceGroupBy('iindex', { writtenOn: 23000 }, ['id', 'pos']) - assert.deepEqual(reducedBy, [ - { id: 'A', pos: 0, status: 'OK', writtenOn: 23000, events: 5, member: true }, - { id: 'B', pos: 0, status: 'KO', writtenOn: 23000, events: 1, member: false } - ]) - }) - - it('should be able to trim data', async () => { - // The number of records should decrease - await blockchain.indexTrim(23601) - const countAi = await blockchain.indexCount('iindex', { id: 'A', pos: 0 }) - const countBi = await blockchain.indexCount('iindex', { id: 'B', pos: 0 }) - const countCi = await blockchain.indexCount('iindex', { id: 'C', pos: 0 }) - const countDi = await blockchain.indexCount('iindex', { id: 'D', pos: 0 }) - const countBz = await blockchain.indexCount('zindex', { id: 'B', pos: 0 }) - assert.equal(countAi, 2) - assert.equal(countBi, 1) - assert.equal(countCi, 1) - assert.equal(countDi, 1) // Not expired! - assert.equal(countBz, 0) - const reducedAi = await blockchain.indexReduce('iindex', { id: 'A', pos: 0 }) - const reducedBi = await blockchain.indexReduce('iindex', { id: 'B', pos: 0 }) - const reducedCi = await blockchain.indexReduce('iindex', { id: 'C', pos: 0 }) - const reducedDi = await blockchain.indexReduce('iindex', { id: 'D', pos: 0 }) - const reducedBz = await blockchain.indexReduce('zindex', { id: 'B', pos: 0 }) - assert.deepEqual(reducedAi, { id: 'A', pos: 0, status: 'OK', writtenOn: 23601, events: 5, member: true }) - assert.deepEqual(reducedBi, { id: 'B', pos: 0, status: 'KO', writtenOn: 23000, events: 1, member: false }) - assert.deepEqual(reducedCi, { id: 'C', pos: 0, status: 'KO', writtenOn: 23500 }) - assert.deepEqual(reducedDi, { id: 'D', pos: 0, status: 'KO', writtenOn: 23500 }) - assert.deepEqual(reducedBz, {}) - }) - }) - }) - - describe('SqlIndex', () => { - - let blockchain:any - - describe('PK on one field', () => { - - before(() => { - const db = new SQLiteDriver(':memory:') - blockchain = new IndexedBlockchain(new ArrayBlockchain(), new SQLIndex(db, { - iindex: { - sqlFields: [ - 'name CHAR(1) NULL', - 'status CHAR(2) NULL', - 'writtenOn INTEGER NULL', - 'events INTEGER NULL', - 'member INTEGER NULL', - 'expired INTEGER NULL' - ], - fields: [ - 'name', - 'status', - 'writtenOn', - 'events', - 'member', - 'expired' - ], - booleans: ['member', 'expired'] - }, - zindex: { - sqlFields: [ - 'name CHAR(1) NULL', - 'status CHAR(2) NULL', - 'writtenOn INTEGER NULL', - 'events INTEGER NULL', - 'member INTEGER NULL', - 'expired INTEGER NULL' - ], - fields: [ - 'name', - 'status', - 'writtenOn', - 'events', - 'member', - 'expired' - ], - booleans: ['member', 'expired'] - } - }), 'writtenOn', { - iindex: { - pk: 'name', - remove: 'expired' - }, - zindex: { - pk: 'name' - } - }) - }) - - it('should be able to index data', async () => { - await blockchain.recordIndex({ - iindex: [ - { name: 'A', status: 'OK', writtenOn: 23000, events: 0, member: false }, - { name: 'A', status: 'OK', writtenOn: 23000, events: 4 }, - { name: 'A', status: 'OK', writtenOn: 23000, events: 5, member: true }, - { name: 'A', status: 'OK', writtenOn: 23601 }, - { name: 'A', status: 'OK', writtenOn: 23888 }, - { name: 'A', status: 'OK', writtenOn: 23889 }, - { name: 'B', status: 'OK', writtenOn: 23000, events: 1, member: false }, - { name: 'B', status: 'KO', writtenOn: 23000, events: null }, - { name: 'C', status: 'KO', writtenOn: 23500 }, - { name: 'D', status: 'KO', writtenOn: 23500 }, - { name: 'D', status: 'KO', writtenOn: 23521, expired: true } - ] - }) - }) - - it('should be able to reduce data', async () => { - const reducedA = await blockchain.indexReduce('iindex', { name: 'A' }) - const reducedB = await blockchain.indexReduce('iindex', { name: 'B' }) - assert.deepEqual(reducedA, { name: 'A', status: 'OK', writtenOn: 23889, events: 5, member: true }) - assert.deepEqual(reducedB, { name: 'B', status: 'KO', writtenOn: 23000, events: 1, member: false }) - }) - - it('should be able to count data', async () => { - const countAi = await blockchain.indexCount('iindex', { name: 'A' }) - const countBi = await blockchain.indexCount('iindex', { name: 'B' }) - const countCi = await blockchain.indexCount('iindex', { name: 'C' }) - const countDi = await blockchain.indexCount('iindex', { name: 'D' }) - const countBz = await blockchain.indexCount('zindex', { name: 'B' }) - assert.equal(countAi, 6) - assert.equal(countBi, 2) - assert.equal(countCi, 1) - assert.equal(countDi, 2) - assert.equal(countBz, 0) - }) - - it('should be able to reduce grouped data', async () => { - const reducedBy = await blockchain.indexReduceGroupBy('iindex', { writtenOn: 23000 }, ['name']) - assert.deepEqual(reducedBy, [ - { name: 'A', status: 'OK', writtenOn: 23000, events: 5, member: true }, - { name: 'B', status: 'KO', writtenOn: 23000, events: 1, member: false } - ]) - }) - - it('should be able to trim data', async () => { - // The number of records should decrease - await blockchain.indexTrim(23601) - const countAi = await blockchain.indexCount('iindex', { name: 'A' }) - const countBi = await blockchain.indexCount('iindex', { name: 'B' }) - const countCi = await blockchain.indexCount('iindex', { name: 'C' }) - const countDi = await blockchain.indexCount('iindex', { name: 'D' }) - const countBz = await blockchain.indexCount('zindex', { name: 'B' }) - assert.equal(countAi, 4) - assert.equal(countBi, 1) - assert.equal(countCi, 1) - assert.equal(countDi, 0) // Expired = remove rows on trim - assert.equal(countBz, 0) - const reducedAi = await blockchain.indexReduce('iindex', { name: 'A' }) - const reducedBi = await blockchain.indexReduce('iindex', { name: 'B' }) - const reducedCi = await blockchain.indexReduce('iindex', { name: 'C' }) - const reducedDi = await blockchain.indexReduce('iindex', { name: 'D' }) - const reducedBz = await blockchain.indexReduce('zindex', { name: 'B' }) - assert.deepEqual(reducedAi, { name: 'A', status: 'OK', writtenOn: 23889, events: 5, member: true }) - assert.deepEqual(reducedBi, { name: 'B', status: 'KO', writtenOn: 23000, events: 1, member: false }) - assert.deepEqual(reducedCi, { name: 'C', status: 'KO', writtenOn: 23500 }) - assert.deepEqual(reducedDi, {}) - assert.deepEqual(reducedBz, {}) - }) - }) - - describe('PK on two fields', () => { - - before(() => { - const db = new SQLiteDriver(':memory:') - blockchain = new IndexedBlockchain(new ArrayBlockchain(), new SQLIndex(db, { - iindex: { - sqlFields: [ - 'id INTEGER NULL', - 'pos INTEGER NULL', - 'name CHAR(1) NULL', - 'status CHAR(2) NULL', - 'writtenOn INTEGER NULL', - 'events INTEGER NULL', - 'member INTEGER NULL', - 'expired INTEGER NULL' - ], - fields: [ - 'id', - 'pos', - 'name', - 'status', - 'writtenOn', - 'events', - 'member', - 'expired' - ], - booleans: ['member', 'expired'] - }, - zindex: { - sqlFields: [ - 'id INTEGER NULL', - 'pos INTEGER NULL', - 'name CHAR(1) NULL', - 'status CHAR(2) NULL', - 'writtenOn INTEGER NULL', - 'events INTEGER NULL', - 'member INTEGER NULL', - 'expired INTEGER NULL' - ], - fields: [ - 'id', - 'pos', - 'name', - 'status', - 'writtenOn', - 'events', - 'member', - 'expired' - ], - booleans: ['member', 'expired'] - } - }), 'writtenOn', { - iindex: { - pk: ['id', 'pos'], - remove: 'expired' - }, - zindex: { - pk: 'name' - } - }) - }) - - it('should be able to index data', async () => { - await blockchain.recordIndex({ - iindex: [ - { id: 'A', pos: 0, status: 'OK', writtenOn: 23000, events: 0, member: false }, - { id: 'A', pos: 0, status: 'OK', writtenOn: 23000, events: 4 }, - { id: 'A', pos: 0, status: 'OK', writtenOn: 23000, events: 5, member: true }, - { id: 'A', pos: 0, status: 'OK', writtenOn: 23601 }, - { id: 'A', pos: 1, status: 'OK', writtenOn: 23888 }, - { id: 'A', pos: 2, status: 'OK', writtenOn: 23889 }, - { id: 'B', pos: 0, status: 'OK', writtenOn: 23000, events: 1, member: false }, - { id: 'B', pos: 0, status: 'KO', writtenOn: 23000, events: null }, - { id: 'C', pos: 0, status: 'KO', writtenOn: 23500 }, - { id: 'D', pos: 0, status: 'KO', writtenOn: 23500 }, - { id: 'D', pos: 1, status: 'KO', writtenOn: 23521, expired: true } - ] - }) - }) - - it('should be able to reduce data', async () => { - const reducedA = await blockchain.indexReduce('iindex', { id: 'A', pos: 0 }) - const reducedB = await blockchain.indexReduce('iindex', { id: 'B', pos: 0 }) - assert.deepEqual(reducedA, { id: 'A', pos: 0, status: 'OK', writtenOn: 23601, events: 5, member: true }) - assert.deepEqual(reducedB, { id: 'B', pos: 0, status: 'KO', writtenOn: 23000, events: 1, member: false }) - }) - - it('should be able to count data', async () => { - const countAi = await blockchain.indexCount('iindex', { id: 'A', pos: 0 }) - const countBi = await blockchain.indexCount('iindex', { id: 'B', pos: 0 }) - const countCi = await blockchain.indexCount('iindex', { id: 'C', pos: 0 }) - const countDi = await blockchain.indexCount('iindex', { id: 'D', pos: 0 }) - const countBz = await blockchain.indexCount('zindex', { id: 'B', pos: 0 }) - assert.equal(countAi, 4) - assert.equal(countBi, 2) - assert.equal(countCi, 1) - assert.equal(countDi, 1) - assert.equal(countBz, 0) - }) - - it('should be able to reduce grouped data', async () => { - const reducedBy = await blockchain.indexReduceGroupBy('iindex', { writtenOn: 23000 }, ['id', 'pos']) - assert.deepEqual(reducedBy, [ - { id: 'A', pos: 0, status: 'OK', writtenOn: 23000, events: 5, member: true }, - { id: 'B', pos: 0, status: 'KO', writtenOn: 23000, events: 1, member: false } - ]) - }) - - it('should be able to trim data', async () => { - // The number of records should decrease - await blockchain.indexTrim(23601) - const countAi = await blockchain.indexCount('iindex', { id: 'A', pos: 0 }) - const countBi = await blockchain.indexCount('iindex', { id: 'B', pos: 0 }) - const countCi = await blockchain.indexCount('iindex', { id: 'C', pos: 0 }) - const countDi = await blockchain.indexCount('iindex', { id: 'D', pos: 0 }) - const countBz = await blockchain.indexCount('zindex', { id: 'B', pos: 0 }) - assert.equal(countAi, 2) - assert.equal(countBi, 1) - assert.equal(countCi, 1) - assert.equal(countDi, 1) // Not expired! - assert.equal(countBz, 0) - const reducedAi = await blockchain.indexReduce('iindex', { id: 'A', pos: 0 }) - const reducedBi = await blockchain.indexReduce('iindex', { id: 'B', pos: 0 }) - const reducedCi = await blockchain.indexReduce('iindex', { id: 'C', pos: 0 }) - const reducedDi = await blockchain.indexReduce('iindex', { id: 'D', pos: 0 }) - const reducedBz = await blockchain.indexReduce('zindex', { id: 'B', pos: 0 }) - assert.deepEqual(reducedAi, { id: 'A', pos: 0, status: 'OK', writtenOn: 23601, events: 5, member: true }) - assert.deepEqual(reducedBi, { id: 'B', pos: 0, status: 'KO', writtenOn: 23000, events: 1, member: false }) - assert.deepEqual(reducedCi, { id: 'C', pos: 0, status: 'KO', writtenOn: 23500 }) - assert.deepEqual(reducedDi, { id: 'D', pos: 0, status: 'KO', writtenOn: 23500 }) - assert.deepEqual(reducedBz, {}) - }) - }) - }) - -}) diff --git a/test/blockchain/lib/ArrayBlockchain.ts b/test/blockchain/lib/ArrayBlockchain.ts deleted file mode 100644 index 25c0fc576e48d3ee7c7e8dde2d64e6bcf895defd..0000000000000000000000000000000000000000 --- a/test/blockchain/lib/ArrayBlockchain.ts +++ /dev/null @@ -1,48 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -import {BlockchainOperator} from "../../../app/lib/blockchain/interfaces/BlockchainOperator" - -export class ArrayBlockchain implements BlockchainOperator { - - // The blockchain storage - private bcArray: any[] = [] - - store(b:any): Promise<any> { - this.bcArray.push(b) - return Promise.resolve(b) - } - - read(i: number): Promise<any> { - return Promise.resolve(this.bcArray[i]) - } - - head(n: number): Promise<any> { - const index = Math.max(0, this.bcArray.length - 1 - (n || 0)) - return Promise.resolve(this.bcArray[index]) - } - - height(): Promise<number> { - return Promise.resolve(this.bcArray.length) - } - - headRange(m: number): Promise<any[]> { - const index = Math.max(0, this.bcArray.length - (m || 0)) - return Promise.resolve(this.bcArray.slice(index, this.bcArray.length).reverse()) - } - - revertHead(): Promise<any> { - const reverted = this.bcArray.pop() - return Promise.resolve(reverted) - } -} diff --git a/test/blockchain/lib/MemoryIndex.ts b/test/blockchain/lib/MemoryIndex.ts deleted file mode 100644 index cfd6471f43c67253554388223971f3c08d1b081f..0000000000000000000000000000000000000000 --- a/test/blockchain/lib/MemoryIndex.ts +++ /dev/null @@ -1,108 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict" -import {IndexOperator} from "../../../app/lib/blockchain/interfaces/IndexOperator" - -const _ = require('underscore') - -export class MemoryIndex implements IndexOperator { - - // The blockchain storage - private indexStorage: { [index: string]: any[] } = { } - - initIndexer(pkFields: any): Promise<void> { - return Promise.resolve() - } - - getSubIndexes(): Promise<string[]> { - return Promise.resolve(_.keys(this.indexStorage)) - } - - findTrimable(subIndex: string, numberField: string, maxNumber: number): Promise<any[]> { - const criterias:any = {} - criterias[numberField] = { $lt: maxNumber } - return this.findWhere(subIndex, criterias) - } - - removeWhere(subIndex: string, criterias: {}): Promise<void> { - let i = 0 - let rows = this.indexStorage[subIndex] - while (i < rows.length) { - if (MemoryIndex.matchComplexCriterias(criterias, rows[i])) { - rows.splice(i, 1) - } else { - i++ - } - } - return Promise.resolve() - } - - recordIndex(index: any): Promise<void> { - const subIndexes = _.keys(index) - // Create subIndexes if they do not exist - for (const subIndex of subIndexes) { - this.indexStorage[subIndex] = this.indexStorage[subIndex] || [] - } - // Feed the subIndexes - for (const subIndex of subIndexes) { - this.indexStorage[subIndex] = this.indexStorage[subIndex].concat(index[subIndex]) - } - return Promise.resolve() - } - - private static matchComplexCriterias(criterias:any, row:any): boolean { - const criteriaKeys = _.keys(criterias) - let matches = true - let i = 0 - while (matches && i < criteriaKeys.length) { - const k = criteriaKeys[i] - if (typeof criterias[k] === 'function') { - matches = criterias[k](row[k]) - } else if (typeof criterias[k] === 'object') { - if (criterias[k].$lt) { - matches = row[k] < criterias[k].$lt - } else if (criterias[k].$gt) { - matches = row[k] > criterias[k].$gt - } else if (criterias[k].$lte) { - matches = row[k] <= criterias[k].$lte - } else if (criterias[k].$gte) { - matches = row[k] >= criterias[k].$gte - } else { - // Unknown predicate - matches = false - } - } else { - matches = row[k] === criterias[k] - } - i++ - } - return matches - } - - findWhere(subIndex: string, criterias: {}): Promise<any[]> { - let res: any[] = [] - const areBasicCriterias = _.values(criterias).reduce((are:boolean, criteria:any) => are && typeof criteria !== 'function' && typeof criteria !== 'object', true) - if (areBasicCriterias) { - res = _.where(this.indexStorage[subIndex], criterias) - } else { - // Slower test, with specific criterias - for (const row of this.indexStorage[subIndex]) { - if (MemoryIndex.matchComplexCriterias(criterias, row)) { - res.push(row) - } - } - } - return Promise.resolve(res) - } -} diff --git a/test/blockchain/misc-sql-blockchain.ts b/test/blockchain/misc-sql-blockchain.ts deleted file mode 100644 index 2caa2d975f41d39acc731161e11dcec72cdd122d..0000000000000000000000000000000000000000 --- a/test/blockchain/misc-sql-blockchain.ts +++ /dev/null @@ -1,233 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict"; -import {MiscIndexedBlockchain} from "../../app/lib/blockchain/MiscIndexedBlockchain" -import {ArrayBlockchain} from "./lib/ArrayBlockchain" -import {SQLiteDriver} from "../../app/lib/dal/drivers/SQLiteDriver" -import {MIndexDAL} from "../../app/lib/dal/sqliteDAL/index/MIndexDAL"; -import {IIndexDAL} from "../../app/lib/dal/sqliteDAL/index/IIndexDAL"; -import {SIndexDAL} from "../../app/lib/dal/sqliteDAL/index/SIndexDAL"; -import {CIndexDAL} from "../../app/lib/dal/sqliteDAL/index/CIndexDAL"; -import {MetaDAL} from "../../app/lib/dal/sqliteDAL/MetaDAL"; -import {ConfDTO} from "../../app/lib/dto/ConfDTO"; - -const assert = require('assert') - -describe('MISC SQL Blockchain', () => { - - let blockchain:any - - before(async () => { - - const db = new SQLiteDriver(':memory:') - - const mindexDAL = new MIndexDAL(db) - const iindexDAL = new IIndexDAL(db) - const sindexDAL = new SIndexDAL(db) - const cindexDAL = new CIndexDAL(db) - const metaDAL = new MetaDAL(db) - - await iindexDAL.init() - await mindexDAL.init() - await sindexDAL.init() - await cindexDAL.init() - await metaDAL.init() - // Ghost tables required for DB upgrade - await metaDAL.exec('CREATE TABLE txs (id INTEGER null);') - await metaDAL.exec('CREATE TABLE idty (id INTEGER null);') - await metaDAL.exec('CREATE TABLE cert (id INTEGER null);') - await metaDAL.exec('CREATE TABLE membership (id INTEGER null);') - await metaDAL.exec('CREATE TABLE block (fork INTEGER null);') - await metaDAL.exec('CREATE TABLE b_index (id INTEGER null);') - await metaDAL.upgradeDatabase(ConfDTO.mock()); - - blockchain = new MiscIndexedBlockchain(new ArrayBlockchain(), mindexDAL, iindexDAL, sindexDAL, cindexDAL) - }) - - describe('MINDEX', () => { - - it('should be able to index data', async () => { - await blockchain.recordIndex({ - m_index: [ - { op: 'CREATE', pub: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', created_on: '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855', written_on: '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855', writtenOn: 0, expires_on: 1520544727, expired_on: null, revokes_on: 1552102327, revoked_on: null, leaving: false, revocation: null, chainable_on: null }, - { op: 'UPDATE', pub: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', created_on: '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855', written_on: '3-0000611A1018A322624853A8AE10D0EBFF3C6AEE37BF4DE5354C720049C22BD1', writtenOn: 3, expires_on: 1520544728, expired_on: null, revokes_on: 1520544728, revoked_on: null, leaving: false, revocation: null, chainable_on: null }, - { op: 'UPDATE', pub: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', created_on: '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855', written_on: '4-0000090B7059E7BF5DD2CCA5E4F0330C1DA42C5DCBD2D1364B99B3FF89DE6744', writtenOn: 4, expires_on: 1520544729, expired_on: null, revokes_on: 1520544729, revoked_on: null, leaving: false, revocation: null, chainable_on: null } - ] - }) - }) - - it('should be able to reduce data', async () => { - const reducedG5 = await blockchain.indexReduce('m_index', { pub: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT' }) - assert.deepEqual(reducedG5, { op: 'UPDATE', pub: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', created_on: '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855', written_on: '4-0000090B7059E7BF5DD2CCA5E4F0330C1DA42C5DCBD2D1364B99B3FF89DE6744', writtenOn: 4, expires_on: 1520544729, revokes_on: 1520544729, leaving: false }) - }) - - it('should be able to count data', async () => { - const countG5 = await blockchain.indexCount('m_index', { pub: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT' }) - assert.equal(countG5, 3) - }) - - it('should be able to reduce grouped data', async () => { - const reducedBy = await blockchain.indexReduceGroupBy('m_index', { created_on: '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855' }, ['op', 'pub']) - assert.deepEqual(reducedBy, [ - { op: 'CREATE', pub: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', created_on: '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855', written_on: '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855', writtenOn: 0, expires_on: 1520544727, revokes_on: 1552102327, leaving: false }, - { op: 'UPDATE', pub: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', created_on: '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855', written_on: '4-0000090B7059E7BF5DD2CCA5E4F0330C1DA42C5DCBD2D1364B99B3FF89DE6744', writtenOn: 4, expires_on: 1520544729, revokes_on: 1520544729, leaving: false } - ]) - }) - - it('should be able to trim data', async () => { - // The number of records should decrease - await blockchain.indexTrim(4) - const countG5 = await blockchain.indexCount('m_index', { pub: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT' }) - assert.equal(countG5, 2) - const reducedG5 = await blockchain.indexReduce('m_index', { pub: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT' }) - assert.deepEqual(reducedG5, { op: 'UPDATE', pub: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', created_on: '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855', written_on: '4-0000090B7059E7BF5DD2CCA5E4F0330C1DA42C5DCBD2D1364B99B3FF89DE6744', writtenOn: 4, expires_on: 1520544729, revokes_on: 1520544729, leaving: false }) - }) - }) - - describe('IINDEX', () => { - - it('should be able to index data', async () => { - await blockchain.recordIndex({ - i_index: [ - { op: 'CREATE', uid: 'pseudo', pub: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', hash: '1505A45A5EEBFC3AFAD1475A4739C8447A79420A83340559CE5A49F9891167BB', sig: '2vfmih7xhW/QLJ85PZH1Tc6j5fooIXca+yr/esnt0yvdk5LhEKrOB32JFqCctAoRNwpRjBdZ2Q8l15+In1rrDg==', created_on: '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855', written_on: '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855', writtenOn: 0, member: true, wasMember: true, kick: false, wotb_id: 164 }, - { op: 'UPDATE', uid: 'pseudo', pub: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', hash: '1505A45A5EEBFC3AFAD1475A4739C8447A79420A83340559CE5A49F9891167BB', sig: '2vfmih7xhW/QLJ85PZH1Tc6j5fooIXca+yr/esnt0yvdk5LhEKrOB32JFqCctAoRNwpRjBdZ2Q8l15+In1rrDg==', created_on: '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855', written_on: '33396-000009C936CD6F7C5672C1E6D36159E0BEA2B394F386CA0EBA7E73662A09BB43', writtenOn: 33396, member: false, wasMember: true, kick: false, wotb_id: 164 }, - { op: 'UPDATE', uid: 'pseudo', pub: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', hash: '1505A45A5EEBFC3AFAD1475A4739C8447A79420A83340559CE5A49F9891167BB', sig: '2vfmih7xhW/QLJ85PZH1Tc6j5fooIXca+yr/esnt0yvdk5LhEKrOB32JFqCctAoRNwpRjBdZ2Q8l15+In1rrDg==', created_on: '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855', written_on: '40000-000006C311D2677D101116287718395A2CBB7B94389004D19B0E4AC6DCE2DE5F', writtenOn: 40000, member: true, wasMember: true, kick: false, wotb_id: 164 } - ] - }) - }) - - it('should be able to reduce data', async () => { - const reducedG5 = await blockchain.indexReduce('i_index', { pub: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT' }) - assert.deepEqual(reducedG5, { op: 'UPDATE', uid: 'pseudo', pub: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', hash: '1505A45A5EEBFC3AFAD1475A4739C8447A79420A83340559CE5A49F9891167BB', sig: '2vfmih7xhW/QLJ85PZH1Tc6j5fooIXca+yr/esnt0yvdk5LhEKrOB32JFqCctAoRNwpRjBdZ2Q8l15+In1rrDg==', created_on: '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855', written_on: '40000-000006C311D2677D101116287718395A2CBB7B94389004D19B0E4AC6DCE2DE5F', writtenOn: 40000, member: true, wasMember: true, kick: false, wotb_id: 164 }) - }) - - it('should be able to count data', async () => { - const countG5 = await blockchain.indexCount('i_index', { pub: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT' }) - assert.equal(countG5, 3) - }) - - it('should be able to reduce grouped data', async () => { - const reducedBy = await blockchain.indexReduceGroupBy('i_index', { created_on: '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855' }, ['op', 'pub']) - assert.deepEqual(reducedBy, [ - { op: 'CREATE', uid: 'pseudo', pub: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', hash: '1505A45A5EEBFC3AFAD1475A4739C8447A79420A83340559CE5A49F9891167BB', sig: '2vfmih7xhW/QLJ85PZH1Tc6j5fooIXca+yr/esnt0yvdk5LhEKrOB32JFqCctAoRNwpRjBdZ2Q8l15+In1rrDg==', created_on: '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855', written_on: '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855', writtenOn: 0, member: true, wasMember: true, kick: false, wotb_id: 164 }, - { op: 'UPDATE', uid: 'pseudo', pub: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', hash: '1505A45A5EEBFC3AFAD1475A4739C8447A79420A83340559CE5A49F9891167BB', sig: '2vfmih7xhW/QLJ85PZH1Tc6j5fooIXca+yr/esnt0yvdk5LhEKrOB32JFqCctAoRNwpRjBdZ2Q8l15+In1rrDg==', created_on: '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855', written_on: '40000-000006C311D2677D101116287718395A2CBB7B94389004D19B0E4AC6DCE2DE5F', writtenOn: 40000, member: true, wasMember: true, kick: false, wotb_id: 164 } - ]) - }) - - it('should be able to trim data', async () => { - // The number of records should decrease - await blockchain.indexTrim(40000) - const countG5 = await blockchain.indexCount('i_index', { pub: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT' }) - assert.equal(countG5, 2) - const reducedG5 = await blockchain.indexReduce('i_index', { pub: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT' }) - assert.deepEqual(reducedG5, { op: 'UPDATE', uid: 'pseudo', pub: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', hash: '1505A45A5EEBFC3AFAD1475A4739C8447A79420A83340559CE5A49F9891167BB', sig: '2vfmih7xhW/QLJ85PZH1Tc6j5fooIXca+yr/esnt0yvdk5LhEKrOB32JFqCctAoRNwpRjBdZ2Q8l15+In1rrDg==', created_on: '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855', written_on: '40000-000006C311D2677D101116287718395A2CBB7B94389004D19B0E4AC6DCE2DE5F', writtenOn: 40000, member: true, wasMember: true, kick: false, wotb_id: 164 }) - }) - }) - - describe('SINDEX', () => { - - it('should be able to index data', async () => { - await blockchain.recordIndex({ - s_index: [ - // Dividend - { op: 'CREATE', tx: null, identifier: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', pos: 35820, created_on: null, written_on: '35820-00000B363BC8F761EB5343660592D50B872FE1140B350C9780EF5BC6F9DD000B', writtenOn: 35820, written_time: 1500000000, amount: 500, base: 0, locktime: 0, consumed: false, conditions: 'SIG(CPEaW4BGNaBdx6FbAxjNQ9Po2apnX2bDvBXJT9yaZUMc)' }, - { op: 'UPDATE', tx: null, identifier: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', pos: 35820, created_on: null, written_on: '35821-0000053C7B54AEFAEC4FCFB2763202ECD8382A635340BD622EDBC0CCC553F763', writtenOn: 35821, written_time: 1500000001, amount: 500, base: 0, locktime: 0, consumed: true, conditions: 'SIG(CPEaW4BGNaBdx6FbAxjNQ9Po2apnX2bDvBXJT9yaZUMc)' }, - // Transaction - { op: 'CREATE', tx: 'D01432C6D7D078CB566C08886FD92CA5D158433D7A8D971124973625BFAB78D9', identifier: 'D01432C6D7D078CB566C08886FD92CA5D158433D7A8D971124973625BFAB78D9', pos: 1, created_on: '33958-0000009CEC38916882EF46C40069EF227F1D7CB4B34EAD5298D84B6658FBB9FB', written_on: '30196-00000A8ABF13284452CD56C9DEC68124D4A31CE1BDD06819EB22E070EBDE1D2D', writtenOn: 30196, written_time: 1499000000, amount: 301, base: 0, locktime: 0, consumed: false, conditions: 'SIG(CPEaW4BGNaBdx6FbAxjNQ9Po2apnX2bDvBXJT9yaZUMc)' }, - { op: 'UPDATE', tx: '3926D234037264654D9C4A2D44CDDC18998DC48262F3677F23DA5BA81BD530EA', identifier: 'D01432C6D7D078CB566C08886FD92CA5D158433D7A8D971124973625BFAB78D9', pos: 1, created_on: '33958-0000009CEC38916882EF46C40069EF227F1D7CB4B34EAD5298D84B6658FBB9FB', written_on: '30197-0000009CEC38916882EF46C40069EF227F1D7CB4B34EAD5298D84B6658FBB9FB', writtenOn: 30197, written_time: 1499000001, amount: 301, base: 0, locktime: 0, consumed: true, conditions: 'SIG(CPEaW4BGNaBdx6FbAxjNQ9Po2apnX2bDvBXJT9yaZUMc)' } - ] - }) - }) - - it('should be able to reduce data', async () => { - const reducedUD = await blockchain.indexReduce('s_index', { identifier: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', pos: 35820 }) - const reducedTX = await blockchain.indexReduce('s_index', { identifier: 'D01432C6D7D078CB566C08886FD92CA5D158433D7A8D971124973625BFAB78D9', pos: 1 }) - assert.deepEqual(reducedUD, { op: 'UPDATE', identifier: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', pos: 35820, written_on: '35821-0000053C7B54AEFAEC4FCFB2763202ECD8382A635340BD622EDBC0CCC553F763', writtenOn: 35821, written_time: 1500000001, amount: 500, base: 0, locktime: 0, consumed: true, conditions: 'SIG(CPEaW4BGNaBdx6FbAxjNQ9Po2apnX2bDvBXJT9yaZUMc)' }) - assert.deepEqual(reducedTX, { op: 'UPDATE', tx: '3926D234037264654D9C4A2D44CDDC18998DC48262F3677F23DA5BA81BD530EA', identifier: 'D01432C6D7D078CB566C08886FD92CA5D158433D7A8D971124973625BFAB78D9', pos: 1, created_on: '33958-0000009CEC38916882EF46C40069EF227F1D7CB4B34EAD5298D84B6658FBB9FB', written_on: '30197-0000009CEC38916882EF46C40069EF227F1D7CB4B34EAD5298D84B6658FBB9FB', writtenOn: 30197, written_time: 1499000001, amount: 301, base: 0, locktime: 0, consumed: true, conditions: 'SIG(CPEaW4BGNaBdx6FbAxjNQ9Po2apnX2bDvBXJT9yaZUMc)' }) - }) - - it('should be able to count data', async () => { - const countUD = await blockchain.indexCount('s_index', { identifier: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', pos: 35820 }) - const countTX = await blockchain.indexCount('s_index', { identifier: 'D01432C6D7D078CB566C08886FD92CA5D158433D7A8D971124973625BFAB78D9', pos: 1 }) - assert.equal(countUD, 2) - assert.equal(countTX, 2) - }) - - it('should be able to reduce grouped data', async () => { - const reducedBy = await blockchain.indexReduceGroupBy('s_index', { pos: { $gt: 0 } }, ['identifier', 'pos']) - assert.deepEqual(reducedBy, [ - { op: 'UPDATE', tx: '3926D234037264654D9C4A2D44CDDC18998DC48262F3677F23DA5BA81BD530EA', identifier: 'D01432C6D7D078CB566C08886FD92CA5D158433D7A8D971124973625BFAB78D9', pos: 1, created_on: '33958-0000009CEC38916882EF46C40069EF227F1D7CB4B34EAD5298D84B6658FBB9FB', written_on: '30197-0000009CEC38916882EF46C40069EF227F1D7CB4B34EAD5298D84B6658FBB9FB', writtenOn: 30197, written_time: 1499000001, amount: 301, base: 0, locktime: 0, consumed: true, conditions: 'SIG(CPEaW4BGNaBdx6FbAxjNQ9Po2apnX2bDvBXJT9yaZUMc)' }, - { op: 'UPDATE', identifier: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', pos: 35820, written_on: '35821-0000053C7B54AEFAEC4FCFB2763202ECD8382A635340BD622EDBC0CCC553F763', writtenOn: 35821, written_time: 1500000001, amount: 500, base: 0, locktime: 0, consumed: true, conditions: 'SIG(CPEaW4BGNaBdx6FbAxjNQ9Po2apnX2bDvBXJT9yaZUMc)' } - ]) - }) - - it('should be able to trim data', async () => { - // The number of records should decrease - await blockchain.indexTrim(35000) - const countUD = await blockchain.indexCount('s_index', { identifier: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', pos: 35820 }) - const countTX = await blockchain.indexCount('s_index', { identifier: 'D01432C6D7D078CB566C08886FD92CA5D158433D7A8D971124973625BFAB78D9', pos: 1 }) - assert.equal(countUD, 2) - assert.equal(countTX, 0) // This index removes the lines marked `consumed` - const reducedUD = await blockchain.indexReduce('s_index', { identifier: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', pos: 35820 }) - assert.deepEqual(reducedUD, { op: 'UPDATE', identifier: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', pos: 35820, written_on: '35821-0000053C7B54AEFAEC4FCFB2763202ECD8382A635340BD622EDBC0CCC553F763', writtenOn: 35821, written_time: 1500000001, amount: 500, base: 0, locktime: 0, consumed: true, conditions: 'SIG(CPEaW4BGNaBdx6FbAxjNQ9Po2apnX2bDvBXJT9yaZUMc)' }) - }) - }) - - describe('CINDEX', () => { - - it('should be able to index data', async () => { - await blockchain.recordIndex({ - c_index: [ - { op: 'CREATE', issuer: 'D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx', receiver: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', created_on: 0, written_on: '0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855', writtenOn: 0, sig: 'MYWlBd2Hw3T/59BDz9HZECBuZ984C23F5lqUGluIUXsvXjsY4xKNqcN2x75s9rn++u4GEzZov6OznLZiHtbAAQ==', expires_on: 1552102327, expired_on: 0, chainable_on: 1489419127 }, - { op: 'UPDATE', issuer: 'D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx', receiver: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', created_on: 0, written_on: '9-0000092C94D0257C61A2504092440600487B2C8BEE73F9C8763C9F351543887D', writtenOn: 9, sig: 'MYWlBd2Hw3T/59BDz9HZECBuZ984C23F5lqUGluIUXsvXjsY4xKNqcN2x75s9rn++u4GEzZov6OznLZiHtbAAQ==', expires_on: 1552102327, expired_on: 1552102400, chainable_on: 1489419127 }, - { op: 'CREATE', issuer: 'EV4yZXAgmDd9rMsRCSH2MK7RHWty7CDB9tmHku3iRnEB', receiver: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', created_on: 11, written_on: '11-000019EC1161FC9FB1848A58A3137B9CD9A919E86B2394B9682BBC3FADB1AF1F', writtenOn: 11, sig: 'plFuA1vgXJh0CQ9MVCmOgfTfFb5u3qICMfgxVJEsyco+lmZTxaKuruSsRdhw3YZgJgfU6YwC+ta/RcgLF6DvDA==', expires_on: 1556866334, expired_on: 0, chainable_on: 1494184082} - ] - }) - }) - - it('should be able to reduce data', async () => { - const reducedC1 = await blockchain.indexReduce('c_index', { issuer: 'D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx', receiver: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', created_on: 0 }) - const reducedC2 = await blockchain.indexReduce('c_index', { issuer: 'EV4yZXAgmDd9rMsRCSH2MK7RHWty7CDB9tmHku3iRnEB', receiver: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', created_on: 11 }) - assert.deepEqual(reducedC1, { op: 'UPDATE', issuer: 'D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx', receiver: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', created_on: 0, written_on: '9-0000092C94D0257C61A2504092440600487B2C8BEE73F9C8763C9F351543887D', writtenOn: 9, sig: 'MYWlBd2Hw3T/59BDz9HZECBuZ984C23F5lqUGluIUXsvXjsY4xKNqcN2x75s9rn++u4GEzZov6OznLZiHtbAAQ==', expires_on: 1552102327, expired_on: 1552102400, chainable_on: 1489419127 }) - assert.deepEqual(reducedC2, { op: 'CREATE', issuer: 'EV4yZXAgmDd9rMsRCSH2MK7RHWty7CDB9tmHku3iRnEB', receiver: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', created_on: 11, written_on: '11-000019EC1161FC9FB1848A58A3137B9CD9A919E86B2394B9682BBC3FADB1AF1F', writtenOn: 11, sig: 'plFuA1vgXJh0CQ9MVCmOgfTfFb5u3qICMfgxVJEsyco+lmZTxaKuruSsRdhw3YZgJgfU6YwC+ta/RcgLF6DvDA==', expires_on: 1556866334, expired_on: 0, chainable_on: 1494184082}) - }) - - it('should be able to count data', async () => { - const countC1 = await blockchain.indexCount('c_index', { issuer: 'D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx', receiver: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', created_on: 0 }) - const countC2 = await blockchain.indexCount('c_index', { issuer: 'EV4yZXAgmDd9rMsRCSH2MK7RHWty7CDB9tmHku3iRnEB', receiver: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', created_on: 11 }) - assert.equal(countC1, 2) - assert.equal(countC2, 1) - }) - - it('should be able to reduce grouped data', async () => { - const reducedBy = await blockchain.indexReduceGroupBy('c_index', { created_on: { $gte: 0 } }, ['issuer', 'receiver', 'created_on']) - assert.deepEqual(reducedBy, [ - { op: 'UPDATE', issuer: 'D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx', receiver: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', created_on: 0, written_on: '9-0000092C94D0257C61A2504092440600487B2C8BEE73F9C8763C9F351543887D', writtenOn: 9, sig: 'MYWlBd2Hw3T/59BDz9HZECBuZ984C23F5lqUGluIUXsvXjsY4xKNqcN2x75s9rn++u4GEzZov6OznLZiHtbAAQ==', expires_on: 1552102327, expired_on: 1552102400, chainable_on: 1489419127 }, - { op: 'CREATE', issuer: 'EV4yZXAgmDd9rMsRCSH2MK7RHWty7CDB9tmHku3iRnEB', receiver: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', created_on: 11, written_on: '11-000019EC1161FC9FB1848A58A3137B9CD9A919E86B2394B9682BBC3FADB1AF1F', writtenOn: 11, sig: 'plFuA1vgXJh0CQ9MVCmOgfTfFb5u3qICMfgxVJEsyco+lmZTxaKuruSsRdhw3YZgJgfU6YwC+ta/RcgLF6DvDA==', expires_on: 1556866334, expired_on: 0, chainable_on: 1494184082} - ]) - }) - - it('should be able to trim data', async () => { - // The number of records should decrease - await blockchain.indexTrim(10) - const countC1 = await blockchain.indexCount('c_index', { issuer: 'D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx', receiver: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', created_on: 0 }) - const countC2 = await blockchain.indexCount('c_index', { issuer: 'EV4yZXAgmDd9rMsRCSH2MK7RHWty7CDB9tmHku3iRnEB', receiver: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', created_on: 11 }) - assert.equal(countC1, 0) // This index removes the lines marked `expired_on` - assert.equal(countC2, 1) - const reducedC2 = await blockchain.indexReduce('c_index', { issuer: 'EV4yZXAgmDd9rMsRCSH2MK7RHWty7CDB9tmHku3iRnEB', receiver: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', created_on: 11 }) - assert.deepEqual(reducedC2, { op: 'CREATE', issuer: 'EV4yZXAgmDd9rMsRCSH2MK7RHWty7CDB9tmHku3iRnEB', receiver: 'G5P7k5t764ybGfFGLnEAwwMDz6y2U4afagAmyJXgKFyT', created_on: 11, written_on: '11-000019EC1161FC9FB1848A58A3137B9CD9A919E86B2394B9682BBC3FADB1AF1F', writtenOn: 11, sig: 'plFuA1vgXJh0CQ9MVCmOgfTfFb5u3qICMfgxVJEsyco+lmZTxaKuruSsRdhw3YZgJgfU6YwC+ta/RcgLF6DvDA==', expires_on: 1556866334, expired_on: 0, chainable_on: 1494184082}) - }) - }) - -}) diff --git a/test/dal/dal.js b/test/dal/basic-dal-tests.ts similarity index 76% rename from test/dal/dal.js rename to test/dal/basic-dal-tests.ts index 44940fa4ca241274bf624a940c2d3db02ba7687e..3a8143c540c00de0bdfd77b5a06df71226d512e2 100644 --- a/test/dal/dal.js +++ b/test/dal/basic-dal-tests.ts @@ -11,15 +11,15 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; -var co = require('co'); -var _ = require('underscore'); +import {FileDAL} from "../../app/lib/dal/fileDAL" +import {PeerDTO} from "../../app/lib/dto/PeerDTO" +import {Directory} from "../../app/lib/system/directory" +import {DBBlock} from "../../app/lib/db/DBBlock" +import {Underscore} from "../../app/lib/common-libs/underscore" + var should = require('should'); var assert = require('assert'); -var dal = require('../../app/lib/dal/fileDAL').FileDAL -var dir = require('../../app/lib/system/directory'); var constants = require('../../app/lib/constants'); -var PeerDTO = require('../../app/lib/dto/PeerDTO').PeerDTO var mocks = { peer1: { @@ -102,22 +102,22 @@ var mocks = { } }; -var fileDAL = null; +let fileDAL:FileDAL describe("DAL", function(){ - before(() => co(function *() { - let params = yield dir.getHomeParams(true, 'db0'); - fileDAL = new dal(params); - yield fileDAL.init(); - return fileDAL.saveConf({ currency: "meta_brouzouf" }); - })); + before(async () => { + let params = await Directory.getHomeParams(true, 'db0'); + fileDAL = new FileDAL(params); + await fileDAL.init({} as any); + return fileDAL.saveConf({ currency: "meta_brouzouf" } as any); + }) - it('should have DB version 21', () => co(function *() { - let version = yield fileDAL.getDBVersion(); + it('should have DB version 21', async () => { + let version = await fileDAL.getDBVersion(); should.exist(version); version.should.equal(constants.CURRENT_DB_VERSION); - })); + }) it('should have no peer in a first time', function(){ return fileDAL.listAllPeers().then(function(peers){ @@ -126,7 +126,7 @@ describe("DAL", function(){ }); it('should have 1 peer if 1 is created', function(){ - return fileDAL.savePeer(PeerDTO.fromJSONObject(mocks.peer1)) + return fileDAL.savePeer(PeerDTO.fromJSONObject(mocks.peer1).toDBPeer()) .then(() => fileDAL.listAllPeers()) .then(function(peers){ peers.should.have.length(1); @@ -150,39 +150,37 @@ describe("DAL", function(){ }); }); - it('should be able to save a Block', function(){ - return co(function *() { - yield fileDAL.saveBlock(_.extend({ fork: false }, mocks.block0)); - let block = yield fileDAL.getBlock(0); - block.should.have.property('hash').equal(mocks.block0.hash); - block.should.have.property('signature').equal(mocks.block0.signature); - block.should.have.property('version').equal(mocks.block0.version); - block.should.have.property('currency').equal(mocks.block0.currency); - block.should.have.property('issuer').equal(mocks.block0.issuer); - block.should.have.property('parameters').equal(mocks.block0.parameters); - block.should.have.property('previousHash').equal(mocks.block0.previousHash); - block.should.have.property('previousIssuer').equal(mocks.block0.previousIssuer); - block.should.have.property('membersCount').equal(mocks.block0.membersCount); - block.should.have.property('monetaryMass').equal(mocks.block0.monetaryMass); - block.should.have.property('UDTime').equal(mocks.block0.UDTime); - block.should.have.property('medianTime').equal(mocks.block0.medianTime); - block.should.have.property('dividend').equal(mocks.block0.dividend); - block.should.have.property('unitbase').equal(mocks.block0.unitbase); - block.should.have.property('time').equal(mocks.block0.time); - block.should.have.property('powMin').equal(mocks.block0.powMin); - block.should.have.property('number').equal(mocks.block0.number); - block.should.have.property('nonce').equal(mocks.block0.nonce); + it('should be able to save a Block', async () => { + await fileDAL.saveBlock(Underscore.extend({ fork: false } as any, mocks.block0)); + let block = (await fileDAL.getFullBlockOf(0)) as DBBlock + block.should.have.property('hash').equal(mocks.block0.hash); + block.should.have.property('signature').equal(mocks.block0.signature); + block.should.have.property('version').equal(mocks.block0.version); + block.should.have.property('currency').equal(mocks.block0.currency); + block.should.have.property('issuer').equal(mocks.block0.issuer); + block.should.have.property('parameters').equal(mocks.block0.parameters); + block.should.have.property('previousHash').equal(mocks.block0.previousHash); + block.should.have.property('previousIssuer').equal(mocks.block0.previousIssuer); + block.should.have.property('membersCount').equal(mocks.block0.membersCount); + block.should.have.property('monetaryMass').equal(mocks.block0.monetaryMass); + block.should.have.property('UDTime').equal(mocks.block0.UDTime); + block.should.have.property('medianTime').equal(mocks.block0.medianTime); + block.should.have.property('dividend').equal(mocks.block0.dividend); + block.should.have.property('unitbase').equal(mocks.block0.unitbase); + block.should.have.property('time').equal(mocks.block0.time); + block.should.have.property('powMin').equal(mocks.block0.powMin); + block.should.have.property('number').equal(mocks.block0.number); + block.should.have.property('nonce').equal(mocks.block0.nonce); - //assert.deepEqual(block, mocks.block0); - assert.deepEqual(block.identities, mocks.block0.identities); - assert.deepEqual(block.certifications, mocks.block0.certifications); - assert.deepEqual(block.actives, mocks.block0.actives); - assert.deepEqual(block.revoked, mocks.block0.revoked); - assert.deepEqual(block.excluded, mocks.block0.excluded); - assert.deepEqual(block.leavers, mocks.block0.leavers); - assert.deepEqual(block.actives, mocks.block0.actives); - assert.deepEqual(block.joiners, mocks.block0.joiners); - assert.deepEqual(block.transactions, mocks.block0.transactions); - }); + //assert.deepEqual(block, mocks.block0); + assert.deepEqual(block.identities, mocks.block0.identities); + assert.deepEqual(block.certifications, mocks.block0.certifications); + assert.deepEqual(block.actives, mocks.block0.actives); + assert.deepEqual(block.revoked, mocks.block0.revoked); + assert.deepEqual(block.excluded, mocks.block0.excluded); + assert.deepEqual(block.leavers, mocks.block0.leavers); + assert.deepEqual(block.actives, mocks.block0.actives); + assert.deepEqual(block.joiners, mocks.block0.joiners); + assert.deepEqual(block.transactions, mocks.block0.transactions); }); }); diff --git a/test/dal/blockchain-archive.ts b/test/dal/blockchain-archive.ts new file mode 100644 index 0000000000000000000000000000000000000000..54d06feb3f04c2869e6a6e5bd8e2c3c158a4cf9c --- /dev/null +++ b/test/dal/blockchain-archive.ts @@ -0,0 +1,88 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import {getNanosecondsTime} from "../../app/ProcessCpuProfiler" +import * as os from "os" +import * as path from "path" +import * as assert from "assert" +import {BlockchainArchiveDAO, BlockLike} from "../../app/lib/dal/indexDAL/abstract/BlockchainArchiveDAO" +import {CFSBlockchainArchive} from "../../app/lib/dal/indexDAL/CFSBlockchainArchive" +import {CFSCore} from "../../app/lib/dal/fileDALs/CFSCore" +import {RealFS} from "../../app/lib/system/directory" + +describe("Blockchain Archive data layer", () => { + + let archives:BlockchainArchiveDAO<BlockLike> + let dbPath = path.join(os.tmpdir(), 'duniter' + getNanosecondsTime()) + + before(async () => { + archives = new CFSBlockchainArchive(new CFSCore(dbPath, RealFS()), 2) + archives.triggerInit() + await archives.init() + }) + + it('should be able to read last saved block when archives are empty', async () => { + assert.equal(null, await archives.getLastSavedBlock()) + }) + + it('should be able to archive 4 blocks', async () => { + const chunksCreated = await archives.archive([ + { number: 0, hash: 'H0', previousHash: '' }, + { number: 1, hash: 'H1', previousHash: 'H0' }, + { number: 2, hash: 'H2', previousHash: 'H1' }, + { number: 3, hash: 'H3', previousHash: 'H2' }, + { number: 4, hash: 'H4', previousHash: 'H3' }, + { number: 5, hash: 'H5', previousHash: 'H4' }, + ]) + assert.equal(chunksCreated, 3) + }) + + it('should be able to read archived blocks', async () => { + assert.notEqual(null, await archives.getBlock(0, 'H0')) + assert.notEqual(null, await archives.getBlock(1, 'H1')) + assert.notEqual(null, await archives.getBlock(2, 'H2')) + assert.notEqual(null, await archives.getBlock(3, 'H3')) + assert.notEqual(null, await archives.getBlock(4, 'H4')) + assert.notEqual(null, await archives.getBlock(5, 'H5')) + }) + + it('should be able to read last saved block when archives are full', async () => { + assert.notEqual(null, await archives.getLastSavedBlock()) + assert.equal(5, ((await archives.getLastSavedBlock()) as BlockLike).number) + }) + + it('should not be able to read non-archived blocks', async () => { + assert.equal(null, await archives.getBlock(0, 'H5')) + assert.equal(null, await archives.getBlock(8, 'H8')) + }) + + it('should refuse to store unchained blocks', async () => { + const chunksCreated1 = await archives.archive([ + { number: 6, hash: 'H6', previousHash: 'H5' }, + { number: 7, hash: 'H7', previousHash: 'H61' }, + ]) + assert.equal(chunksCreated1, 0) + const chunksCreated2 = await archives.archive([ + { number: 6, hash: 'H6', previousHash: 'H5' }, + { number: 8, hash: 'H7', previousHash: 'H6' }, + ]) + assert.equal(chunksCreated2, 0) + }) + + it('should refuse to store blocks that are not chunks', async () => { + const chunksCreated = await archives.archive([ + { number: 6, hash: 'H6', previousHash: 'H5' }, + ]) + assert.equal(chunksCreated, 0) + }) +}) diff --git a/test/dal/file-dal.ts b/test/dal/file-dal.ts new file mode 100644 index 0000000000000000000000000000000000000000..1f243579235f86f85eaffa77f847fc1232d2cb62 --- /dev/null +++ b/test/dal/file-dal.ts @@ -0,0 +1,88 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import {assertEqual, assertFalse, assertTrue, writeBasicTestWith2Users} from "../integration/tools/test-framework" +import {TestingServer} from "../integration/tools/toolbox" +import {CommonConstants} from "../../app/lib/common-libs/constants" + +describe('File Data Access Layer', () => writeBasicTestWith2Users((test) => { + + let initialValue = CommonConstants.BLOCKS_COLLECT_THRESHOLD + + before(() => { + // Let's trim loki data every 3 blocks + CommonConstants.BLOCKS_COLLECT_THRESHOLD = 3 + }) + + after(() => { + // Revert + CommonConstants.BLOCKS_COLLECT_THRESHOLD = initialValue + }) + + test('if we disable the changes API', async (s1: TestingServer) => { + s1.dal.disableChangesAPI() + assertTrue(s1.dal.iindexDAL.lokiCollection.disableChangesApi) + assertTrue(s1.dal.iindexDAL.lokiCollection.disableDeltaChangesApi) + }) + + test('if we enable back the changes API', async (s1: TestingServer) => { + s1.dal.enableChangesAPI() + assertFalse(s1.dal.iindexDAL.lokiCollection.disableChangesApi) + assertFalse(s1.dal.iindexDAL.lokiCollection.disableDeltaChangesApi) + }) + + test('we should have no changes after commit of b#0', async (s1, cat, tac) => { + assertEqual(s1.dal.iindexDAL.lokiCollection.collection.data.length, 0) + assertEqual(s1.dal.iindexDAL.lokiCollection.collection.changes.length, 0) + await cat.createIdentity() + await tac.createIdentity() + await cat.cert(tac) + await tac.cert(cat) + await cat.join() + await tac.join() + await s1.commit() + // No changes after a commit, but new data + assertEqual(s1.dal.iindexDAL.lokiCollection.collection.data.length, 2) + assertEqual(s1.dal.iindexDAL.lokiCollection.collection.changes.length, 0) + // Without changes files (since block#0 triggers the lokijs data commit) + assertEqual((await s1.dal.loki.listChangesFilesPending()).length, 0) + }) + + test('we should have changes files after commit of b#1', async (s1, cat, tac) => { + await tac.revoke() + await s1.commit() + // Some changes, as block#1 does not provoke a lokijs data commit + assertEqual(s1.dal.iindexDAL.lokiCollection.collection.data.length, 3) + assertEqual(s1.dal.iindexDAL.lokiCollection.collection.changes.length, 0) + // With changes files (since block#1 does not trigger the lokijs data commit) + assertEqual((await s1.dal.loki.listChangesFilesPending()).length, 1) + }) + + test('we should have one more changes files after commit of b#2', async (s1) => { + await s1.commit() + // Some changes, as block#1 does not provoke a lokijs data commit + assertEqual(s1.dal.iindexDAL.lokiCollection.collection.data.length, 3) + assertEqual(s1.dal.iindexDAL.lokiCollection.collection.changes.length, 0) + // With changes files (since block#1 does not trigger the lokijs data commit) + assertEqual((await s1.dal.loki.listChangesFilesPending()).length, 2) + }) + + test('we should have no more changes files after commit of b#3', async (s1) => { + await s1.commit() + // Some changes, as block#1 does not provoke a lokijs data commit + assertEqual(s1.dal.iindexDAL.lokiCollection.collection.data.length, 3) + assertEqual(s1.dal.iindexDAL.lokiCollection.collection.changes.length, 0) + // With changes files (since block#1 does not trigger the lokijs data commit) + assertEqual((await s1.dal.loki.listChangesFilesPending()).length, 0) + }) +})) diff --git a/test/dal/loki.ts b/test/dal/loki.ts new file mode 100644 index 0000000000000000000000000000000000000000..ebe6b3ccca71ce2cbacb6431ae92f03bbe441386 --- /dev/null +++ b/test/dal/loki.ts @@ -0,0 +1,115 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import {LokiJsDriver} from "../../app/lib/dal/drivers/LokiJsDriver" +import {getNanosecondsTime} from "../../app/ProcessCpuProfiler" +import * as os from "os" +import * as path from "path" +import * as assert from "assert" +import {RealFS} from "../../app/lib/system/directory" +import {shouldThrow} from "../unit-tools" +import {DBCommit} from "../../app/lib/dal/drivers/LokiFsAdapter" + +describe("Loki data layer", () => { + + let driver:LokiJsDriver + let dbPath = path.join(os.tmpdir(), 'duniter' + getNanosecondsTime()) + + it('should be able to create a new instance', async () => { + driver = new LokiJsDriver(dbPath) + await driver.loadDatabase() + }) + + it('should be able to commit data', async () => { + const coll = driver.getLokiInstance().addCollection('block', { disableChangesApi: false }) + coll.insert({ a: 1 }) + coll.insert({ b: 2 }) + await driver.flushAndTrimData() + }) + + it('should be able restart the DB and read the data', async () => { + const driver2 = new LokiJsDriver(dbPath) + await driver2.loadDatabase() + const coll = driver2.getLokiInstance().getCollection('block') + assert.notEqual(null, coll) + assert.equal(coll.find().length, 2) + }) + + it('should be able to add few changes data', async () => { + const driver2 = new LokiJsDriver(dbPath) + await driver2.loadDatabase() + const coll = driver2.getLokiInstance().getCollection('block') + coll.insert({ c: 3 }) + coll.chain().find({ c: 3 }).update((o:any) => o.c = 4) + coll.chain().find({ a: 1 }).remove() + const changesCount1 = await driver2.commitData() + assert.equal(changesCount1, 3) + const changesCount2 = await driver2.commitData() + assert.equal(changesCount2, 0) + }) + + it('should be able restart the DB and read the commited data', async () => { + const driver2 = new LokiJsDriver(dbPath) + await driver2.loadDatabase() + const coll = driver2.getLokiInstance().getCollection('block') + assert.equal(coll.find().length, 2) + assert.equal(coll.find({ a: 1 }).length, 0) + assert.equal(coll.find({ b: 2 }).length, 1) + assert.equal(coll.find({ c: 4 }).length, 1) + }) + + it('should be able to trim then restart the DB and read the commited data', async () => { + const driverTrim = new LokiJsDriver(dbPath) + await driverTrim.loadDatabase() + await driverTrim.flushAndTrimData() + const driver2 = new LokiJsDriver(dbPath) + await driver2.loadDatabase() + const coll = driver2.getLokiInstance().getCollection('block') + assert.equal(coll.find().length, 2) + assert.equal(coll.find({ a: 1 }).length, 0) + assert.equal(coll.find({ b: 2 }).length, 1) + assert.equal(coll.find({ c: 4 }).length, 1) + }) + + it('should not see any data if commit file is absent', async () => { + const rfs = RealFS() + await rfs.fsUnlink(path.join(dbPath, 'commit.json')) + const driver3 = new LokiJsDriver(dbPath) + await driver3.loadDatabase() + const coll = driver3.getLokiInstance().getCollection('block') + assert.equal(null, coll) + }) + + it('should throw if commit file contains unknown index file', async () => { + const rfs = RealFS() + await rfs.fsWrite(path.join(dbPath, 'commit.json'), JSON.stringify({ + indexFile: 'non-existing.index.json' + })) + const driver4 = new LokiJsDriver(dbPath) + await shouldThrow(driver4.loadDatabase()) + }) + + it('should throw if commit file contains unknown data files', async () => { + const rfs = RealFS() + await rfs.fsRemoveTree(dbPath) + const driver4 = new LokiJsDriver(dbPath) + const coll = driver4.getLokiInstance().addCollection('block') + coll.insert({ a: 1 }) + coll.insert({ b: 2 }) + await driver.flushAndTrimData() + const oldCommit:DBCommit = JSON.parse(await rfs.fsReadFile(path.join(dbPath, 'commit.json'))) + oldCommit.collections['block'] = 'wrong-file.json' + const driver5 = new LokiJsDriver(dbPath) + await shouldThrow(driver5.loadDatabase()) + }) +}) diff --git a/test/dal/source_dal.js b/test/dal/source_dal.js deleted file mode 100644 index 08c243bcdefa51c5507ed5e9631aafc69201e957..0000000000000000000000000000000000000000 --- a/test/dal/source_dal.js +++ /dev/null @@ -1,51 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict"; -const co = require('co'); -const should = require('should'); -const FileDAL = require('../../app/lib/dal/fileDAL').FileDAL -const dir = require('../../app/lib/system/directory'); -const indexer = require('../../app/lib/indexer').Indexer - -let dal; - -describe("Source DAL", function(){ - - before(() => co(function *() { - dal = new FileDAL(yield dir.getHomeParams(true, 'db0')); - yield dal.init(); - })); - - it('should be able to feed the sindex with unordered rows', () => co(function *() { - yield dal.sindexDAL.insertBatch([ - { op: 'UPDATE', identifier: 'SOURCE_1', pos: 4, written_on: '139-H', writtenOn: 139, written_time: 4500, consumed: true, conditions: 'SIG(ABC)' }, - { op: 'CREATE', identifier: 'SOURCE_1', pos: 4, written_on: '126-H', writtenOn: 126, written_time: 2000, consumed: false, conditions: 'SIG(ABC)' }, - { op: 'CREATE', identifier: 'SOURCE_2', pos: 4, written_on: '126-H', writtenOn: 126, written_time: 2000, consumed: false, conditions: 'SIG(ABC)' }, - { op: 'CREATE', identifier: 'SOURCE_3', pos: 4, written_on: '126-H', writtenOn: 126, written_time: 2000, consumed: false, conditions: 'SIG(DEF)' } - ]); - (yield dal.sindexDAL.sqlFind({ identifier: 'SOURCE_1' })).should.have.length(2); - (yield dal.sindexDAL.sqlFind({ pos: 4 })).should.have.length(4); - // Source availability - const sourcesOfDEF = yield dal.sindexDAL.getAvailableForPubkey('DEF'); - sourcesOfDEF.should.have.length(1); - const sourcesOfABC = yield dal.sindexDAL.getAvailableForPubkey('ABC'); - sourcesOfABC.should.have.length(1); - const source1 = yield dal.sindexDAL.getSource('SOURCE_1', 4); - source1.should.have.property('consumed').equal(true); - const udSources = yield dal.sindexDAL.getUDSources('ABC'); - udSources.should.have.length(2); - udSources[0].should.have.property('consumed').equal(true); - udSources[1].should.have.property('consumed').equal(false); - })); -}); diff --git a/test/dal/sources-dal.ts b/test/dal/sources-dal.ts new file mode 100644 index 0000000000000000000000000000000000000000..3288f50319c3af1dce84788329a96033a392f89d --- /dev/null +++ b/test/dal/sources-dal.ts @@ -0,0 +1,47 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import {FileDAL} from "../../app/lib/dal/fileDAL" +import {Directory} from "../../app/lib/system/directory" + +const should = require('should'); + +let dal:FileDAL + +describe("Source DAL", function(){ + + before(async () => { + dal = new FileDAL(await Directory.getHomeParams(true, 'db0')); + await dal.init({} as any) + }) + + it('should be able to feed the sindex with unordered rows', async () => { + await dal.sindexDAL.insertBatch([ + { op: 'UPDATE', tx: null, identifier: 'SOURCE_1', pos: 4, written_on: '139-H', writtenOn: 139, written_time: 4500, consumed: true, conditions: 'SIG(ABC)' }, + { op: 'CREATE', tx: null, identifier: 'SOURCE_1', pos: 4, written_on: '126-H', writtenOn: 126, written_time: 2000, consumed: false, conditions: 'SIG(ABC)' }, + { op: 'CREATE', tx: null, identifier: 'SOURCE_2', pos: 4, written_on: '126-H', writtenOn: 126, written_time: 2000, consumed: false, conditions: 'SIG(ABC)' }, + { op: 'CREATE', tx: null, identifier: 'SOURCE_3', pos: 4, written_on: '126-H', writtenOn: 126, written_time: 2000, consumed: false, conditions: 'SIG(DEF)' } + ] as any); + (await dal.sindexDAL.findRaw({ identifier: 'SOURCE_1' })).should.have.length(2); + (await dal.sindexDAL.findRaw({ pos: 4 })).should.have.length(4); + // Source availability + const sourcesOfDEF = await dal.sindexDAL.getAvailableForPubkey('DEF'); + sourcesOfDEF.should.have.length(1); + const sourcesOfABC = await dal.sindexDAL.getAvailableForPubkey('ABC'); + sourcesOfABC.should.have.length(1); + const source1 = await dal.sindexDAL.getTxSource('SOURCE_1', 4) as any + source1.should.have.property('consumed').equal(true); + const source2 = await dal.sindexDAL.getTxSource('SOURCE_2', 4) as any + source2.should.have.property('consumed').equal(false); + }) +}) diff --git a/test/dal/triming.js b/test/dal/triming-dal.ts similarity index 63% rename from test/dal/triming.js rename to test/dal/triming-dal.ts index a684a0c0e0b66106b2b7bc5a1a2ba26daf9b0385..ada1d304f057d8ca18a9e9b2d535e3a32f40e9df 100644 --- a/test/dal/triming.js +++ b/test/dal/triming-dal.ts @@ -11,132 +11,131 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; -const co = require('co'); +import {FileDAL} from "../../app/lib/dal/fileDAL" +import {Directory} from "../../app/lib/system/directory" +import {Indexer} from "../../app/lib/indexer" +import {simpleNodeWith2Users} from "../integration/tools/toolbox" + const should = require('should'); -const FileDAL = require('../../app/lib/dal/fileDAL').FileDAL -const dir = require('../../app/lib/system/directory'); -const indexer = require('../../app/lib/indexer').Indexer -const toolbox = require('../integration/tools/toolbox'); -let dal; +let dal:FileDAL describe("Triming", function(){ - before(() => co(function *() { - dal = new FileDAL(yield dir.getHomeParams(true, 'db0')); - yield dal.init(); - })); + before(async () => { + dal = new FileDAL(await Directory.getHomeParams(true, 'db0')); + await dal.init({} as any) + }) - it('should be able to feed the bindex', () => co(function *() { - yield dal.bindexDAL.insertBatch([ + it('should be able to feed the bindex', async () => { + await dal.bindexDAL.insertBatch([ { number: 121, version: 6, bsize: 0, hash: "HASH", issuer: "ISSUER", time: 0, membersCount: 3, issuersCount: 2, issuersFrame: 1, issuersFrameVar: 2, avgBlockSize: 0, medianTime: 1482500000, dividend: 100, mass: 300, massReeval: 300, unitBase: 2, powMin: 70, udTime: 0, udReevalTime: 0, diffNumber: 5, speed: 1.0 }, { number: 122, version: 6, bsize: 0, hash: "HASH", issuer: "ISSUER", time: 0, membersCount: 3, issuersCount: 2, issuersFrame: 1, issuersFrameVar: 2, avgBlockSize: 0, medianTime: 1482500000, dividend: 100, mass: 300, massReeval: 300, unitBase: 2, powMin: 70, udTime: 0, udReevalTime: 0, diffNumber: 5, speed: 1.0 }, { number: 123, version: 6, bsize: 0, hash: "HASH", issuer: "ISSUER", time: 0, membersCount: 3, issuersCount: 2, issuersFrame: 1, issuersFrameVar: 2, avgBlockSize: 0, medianTime: 1482500000, dividend: 100, mass: 300, massReeval: 300, unitBase: 2, powMin: 70, udTime: 0, udReevalTime: 0, diffNumber: 5, speed: 1.0 }, { number: 124, version: 6, bsize: 0, hash: "HASH", issuer: "ISSUER", time: 0, membersCount: 3, issuersCount: 2, issuersFrame: 1, issuersFrameVar: 2, avgBlockSize: 0, medianTime: 1482500000, dividend: 100, mass: 300, massReeval: 300, unitBase: 2, powMin: 70, udTime: 0, udReevalTime: 0, diffNumber: 5, speed: 1.0 }, { number: 125, version: 6, bsize: 0, hash: "HASH", issuer: "ISSUER", time: 0, membersCount: 3, issuersCount: 2, issuersFrame: 1, issuersFrameVar: 2, avgBlockSize: 0, medianTime: 1482500000, dividend: 100, mass: 300, massReeval: 300, unitBase: 2, powMin: 70, udTime: 0, udReevalTime: 0, diffNumber: 5, speed: 1.0 } - ]); - })); + ] as any); + }) - it('should have bindex head(1) = 125', () => co(function *() { - const head = yield dal.bindexDAL.head(1); + it('should have bindex head(1) = 125', async () => { + const head = await dal.bindexDAL.head(1); head.should.have.property('number').equal(125); - })); + }) - it('should have bindex range(1, 3) = 125, 124, 123', () => co(function *() { - const range = yield dal.bindexDAL.range(1,3); + it('should have bindex range(1, 3) = 125, 124, 123', async () => { + const range = await dal.bindexDAL.range(1,3); range.should.have.length(3); range[0].should.have.property('number').equal(125); range[1].should.have.property('number').equal(124); range[2].should.have.property('number').equal(123); - })); + }) - it('should be able to feed the iindex', () => co(function *() { - yield dal.iindexDAL.insertBatch([ + it('should be able to feed the iindex', async () => { + await dal.iindexDAL.insertBatch([ { op: 'CREATE', pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', uid: 'cat', created_on: '121-H', written_on: '122-H', writtenOn: 122, member: true, wasMember: true, kick: false }, { op: 'UPDATE', pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', uid: null, created_on: '121-H', written_on: '123-H', writtenOn: 123, member: null, wasMember: null, kick: true }, { op: 'UPDATE', pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', uid: null, created_on: '121-H', written_on: '124-H', writtenOn: 124, member: false, wasMember: null, kick: false } - ]); - let lignes = yield dal.iindexDAL.reducable('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); + ] as any); + let lignes = await dal.iindexDAL.reducable('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); lignes.should.have.length(3); - indexer.DUP_HELPERS.reduce(lignes).should.have.property('member').equal(false); - })); + Indexer.DUP_HELPERS.reduce(lignes).should.have.property('member').equal(false); + }) - it('should be able to trim the iindex', () => co(function *() { + it('should be able to trim the iindex', async () => { // Triming - yield dal.trimIndexes(124); - const lignes = yield dal.iindexDAL.reducable('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); + await dal.trimIndexes(124); + const lignes = await dal.iindexDAL.reducable('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); lignes.should.have.length(2); - indexer.DUP_HELPERS.reduce(lignes).should.have.property('member').equal(false); - })); + Indexer.DUP_HELPERS.reduce(lignes).should.have.property('member').equal(false); + }) - it('triming again the iindex should have no effet', () => co(function *() { + it('triming again the iindex should have no effet', async () => { // Triming - yield dal.trimIndexes(124); - const lignes = yield dal.iindexDAL.reducable('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); + await dal.trimIndexes(124); + const lignes = await dal.iindexDAL.reducable('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); lignes.should.have.length(2); - indexer.DUP_HELPERS.reduce(lignes).should.have.property('member').equal(false); - })); + Indexer.DUP_HELPERS.reduce(lignes).should.have.property('member').equal(false); + }) - it('should be able to feed the mindex', () => co(function *() { - yield dal.mindexDAL.insertBatch([ + it('should be able to feed the mindex', async () => { + await dal.mindexDAL.insertBatch([ { op: 'CREATE', pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', created_on: '121-H', written_on: '122-H', writtenOn: 122, expires_on: 1000, expired_on: null }, { op: 'UPDATE', pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', created_on: '121-H', written_on: '123-H', writtenOn: 123, expires_on: 1200, expired_on: null }, { op: 'UPDATE', pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', created_on: '121-H', written_on: '124-H', writtenOn: 124, expires_on: null, expired_on: null }, { op: 'UPDATE', pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', created_on: '121-H', written_on: '125-H', writtenOn: 125, expires_on: 1400, expired_on: null } - ]); - const lignes = yield dal.mindexDAL.reducable('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); + ] as any); + const lignes = await dal.mindexDAL.reducable('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); lignes.should.have.length(4); - indexer.DUP_HELPERS.reduce(lignes).should.have.property('expires_on').equal(1400); - })); + Indexer.DUP_HELPERS.reduce(lignes).should.have.property('expires_on').equal(1400); + }) - it('should be able to trim the mindex', () => co(function *() { + it('should be able to trim the mindex', async () => { // Triming - yield dal.trimIndexes(124); - const lignes = yield dal.mindexDAL.reducable('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); + await dal.trimIndexes(124); + const lignes = await dal.mindexDAL.reducable('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); lignes.should.have.length(3); - indexer.DUP_HELPERS.reduce(lignes).should.have.property('expires_on').equal(1400); - })); + Indexer.DUP_HELPERS.reduce(lignes).should.have.property('expires_on').equal(1400); + }) - it('should be able to feed the cindex', () => co(function *() { - yield dal.cindexDAL.insertBatch([ + it('should be able to feed the cindex', async () => { + await dal.cindexDAL.insertBatch([ { op: 'CREATE', issuer: 'HgTT', receiver: 'DNan', created_on: '121-H', written_on: '126-H', writtenOn: 126, expires_on: 1000, expired_on: null }, { op: 'UPDATE', issuer: 'HgTT', receiver: 'DNan', created_on: '121-H', written_on: '126-H', writtenOn: 126, expires_on: null, expired_on: 3000 }, { op: 'CREATE', issuer: 'DNan', receiver: 'HgTT', created_on: '125-H', written_on: '126-H', writtenOn: 126, expires_on: null, expired_on: null } - ]); - (yield dal.cindexDAL.sqlFind({ issuer: 'HgTT' })).should.have.length(2); - (yield dal.cindexDAL.sqlFind({ issuer: 'DNan' })).should.have.length(1); - })); + ] as any); + (await dal.cindexDAL.findRaw({ issuer: 'HgTT' })).should.have.length(2); + (await dal.cindexDAL.findRaw({ issuer: 'DNan' })).should.have.length(1); + }) - it('should be able to trim the cindex', () => co(function *() { + it('should be able to trim the cindex', async () => { // Triming - yield dal.trimIndexes(127); - (yield dal.cindexDAL.sqlFind({ issuer: 'HgTT' })).should.have.length(0); + await dal.trimIndexes(127); + (await dal.cindexDAL.findRaw({ issuer: 'HgTT' })).should.have.length(0); // { op: 'UPDATE', issuer: 'DNan', receiver: 'HgTT', created_on: '125-H', written_on: '126-H', writtenOn: 126, expires_on: 3600, expired_on: null },/**/ - (yield dal.cindexDAL.sqlFind({ issuer: 'DNan' })).should.have.length(1); - })); + (await dal.cindexDAL.findRaw({ issuer: 'DNan' })).should.have.length(1); + }) - it('should be able to feed the sindex', () => co(function *() { - yield dal.sindexDAL.insertBatch([ + it('should be able to feed the sindex', async () => { + await dal.sindexDAL.insertBatch([ { op: 'CREATE', identifier: 'SOURCE_1', pos: 4, written_on: '126-H', writtenOn: 126, written_time: 2000, consumed: false }, { op: 'UPDATE', identifier: 'SOURCE_1', pos: 4, written_on: '139-H', writtenOn: 139, written_time: 4500, consumed: true }, { op: 'CREATE', identifier: 'SOURCE_2', pos: 4, written_on: '126-H', writtenOn: 126, written_time: 2000, consumed: false }, { op: 'CREATE', identifier: 'SOURCE_3', pos: 4, written_on: '126-H', writtenOn: 126, written_time: 2000, consumed: false } - ]); - (yield dal.sindexDAL.sqlFind({ identifier: 'SOURCE_1' })).should.have.length(2); - (yield dal.sindexDAL.sqlFind({ pos: 4 })).should.have.length(4); - })); + ] as any); + (await dal.sindexDAL.findRaw({ identifier: 'SOURCE_1' })).should.have.length(2); + (await dal.sindexDAL.findRaw({ pos: 4 })).should.have.length(4); + }) - it('should be able to trim the sindex', () => co(function *() { + it('should be able to trim the sindex', async () => { // Triming - yield dal.trimIndexes(140); - (yield dal.sindexDAL.sqlFind({ identifier: 'SOURCE_1' })).should.have.length(0); - (yield dal.sindexDAL.sqlFind({ pos: 4 })).should.have.length(2); - })); + await dal.trimIndexes(140); + (await dal.sindexDAL.findRaw({ identifier: 'SOURCE_1' })).should.have.length(0); + (await dal.sindexDAL.findRaw({ pos: 4 })).should.have.length(2); + }) - it('should be able to trim the bindex', () => co(function *() { + it('should be able to trim the bindex', async () => { // Triming - const server = (yield toolbox.simpleNodeWith2Users({ + const server = (await simpleNodeWith2Users({ forksize: 9, sigQty: 1, dtDiffEval: 2, @@ -144,13 +143,13 @@ describe("Triming", function(){ })).s1; // const s1 = server.s1; for (let i = 0; i < 13; i++) { - yield server.commit(); + await server.commit(); } - (yield server.dal.bindexDAL.head(1)).should.have.property('number').equal(12); - (yield server.dal.bindexDAL.head(13)).should.have.property('number').equal(0); - yield server.commit(); - should.not.exists(yield server.dal.bindexDAL.head(14)); // Trimed - - yield server.closeCluster() - })); -}); + (await server.dal.bindexDAL.head(1)).should.have.property('number').equal(12); + (await server.dal.bindexDAL.head(13)).should.have.property('number').equal(0); + await server.commit(); + should.not.exists(await server.dal.bindexDAL.head(14)); // Trimed + + await server.closeCluster() + }) +}) diff --git a/test/data/blocks.js b/test/data/blocks-data.ts similarity index 99% rename from test/data/blocks.js rename to test/data/blocks-data.ts index 17094232ad9daf7126b7751d3fd878ad24e7999f..ccaa84559f9f4453701897a8fad0ea8cb9b163c1 100644 --- a/test/data/blocks.js +++ b/test/data/blocks-data.ts @@ -12,7 +12,7 @@ // GNU Affero General Public License for more details. -module.exports = { +export const BLOCK_TEST_DATA = { WRONG_SIGNATURE: "Version: 10\n" + "Type: Block\n" + diff --git a/test/eslint.js b/test/eslint-test.ts similarity index 95% rename from test/eslint.js rename to test/eslint-test.ts index c6714b15f3c9e1b1bdee884774d2201cc1b85386..ae87015a9fb58f03f47eee688ca43d889fad7696 100644 --- a/test/eslint.js +++ b/test/eslint-test.ts @@ -20,12 +20,11 @@ describe('Linting', () => { // matches a glob pattern const paths = [ 'app', - 'bin/duniter', 'test' ]; // Specify style of output - const options = {}; + const options:any = {}; options.formatter = 'stylish'; // Run the tests diff --git a/test/fast/block_global.disabled b/test/fast/block_global.disabled deleted file mode 100644 index 7611166fd585ea3d7d3ea15ffea5d38b2937f23d..0000000000000000000000000000000000000000 --- a/test/fast/block_global.disabled +++ /dev/null @@ -1,678 +0,0 @@ -"use strict"; -const co = require('co'); -const Q = require('q'); -const _ = require('underscore'); -const async = require('async'); -const should = require('should'); -const rules = require('../../app/lib/rules'); -const wotb = require('../../app/lib/wot'); -const parsers = require('duniter-common').parsers; -const blocks = require('../data/blocks'); -const parser = parsers.parseBlock; -const Block = require('../../app/lib/entity/block'); -const Identity = require('../../app/lib/entity/identity'); - -const conf = { - currency: 'bb', - msValidity: 365.25 * 24 * 3600, // 1 year - sigValidity: 365.25 * 24 * 3600, // 1 year - sigQty: 1, - xpercent: 0.9, - powZeroMin: 1, - powPeriod: 18, - incDateMin: 10, - dt: 100, - ud0: 100, - c: 0.1, - medianTimeBlocks: 200, - percentRot: 2 / 3, - blockRot: 300, - dtDiffEval: 500, - stepMax: 1 -}; - -function getDAL(overrides) { - return _.extend({ - wotb: wotb.memoryInstance(), - getCurrentBlockOrNull: () => Q(null), - getWrittenIdtyByUID: () => Q(null), - getWrittenIdtyByPubkey: () => Q(null), - getToBeKicked: () => Q([]), - isLeaving: () => Q(false), - getPreviousLinks: () => Q(null), - getLastValidFrom: () => Q(null), - lastUDBlock: () => Q(null), - getBlock: () => Q(null), - isMember: () => Q(false), - getBlocksBetween: () => Q([]), - lastBlockOfIssuer: () => Q(null) - }, overrides); -} - -/** - * TODO: reimplement tests according to new convention: - * - * - Name: protocol-brg<number>-<title>.js - * - Content: see existing tests - */ - -describe("Block global coherence:", function(){ - - it('a valid block should not have any error', validate(blocks.VALID_ROOT, getDAL(), { - getIssuerPersonalizedDifficulty: () => Q(1), - getvHEAD_1: () => Q({ version : 2 }) - }, function (err) { - should.not.exist(err); - })); - - it('a valid (next) block should not have any error', validate(blocks.VALID_NEXT, getDAL({ - getCurrentBlockOrNull: () => Q({ number: 2, hash: '52DC8A585C5D89571C511BB83F7E7D3382F0041452064B1272E65F0B42B82D57', issuer: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', membersCount: 3, time: 1411776000, medianTime: 1411776000 }), - getBlock: (number) => { - if (number == 1) { - return Q({ time: 1411776000, powMin: 1 }); - } - if (number == 2) { - return Q({ number: 3, powMin: 1 }); - } - return Q(null); - }, - isMember: () => Q(true), - getBlocksBetween: () => Q([{time:1411776000},{time:1411776000},{time:1411776000}]) - }), { - getIssuerPersonalizedDifficulty: () => Q(2), - getvHEAD_1: () => Q({ version : 2 }) - }, function (err) { - should.not.exist(err); - })); - - it('a block with wrong PreviousHash should fail', test(rules.GLOBAL.checkPreviousHash, blocks.WRONG_PREVIOUS_HASH, { - getCurrentBlockOrNull: () => Q({ number: 50, hash: '4C8800825C44A22F230AFC0D140BF1930331A686899D16EBE4C58C9F34C609E8', issuer: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', membersCount: 3 }) - }, function (err) { - should.exist(err); - err.message.should.equal('PreviousHash not matching hash of current block'); - })); - - it('a block with wrong PreviousIssuer should fail', test(rules.GLOBAL.checkPreviousIssuer, blocks.WRONG_PREVIOUS_ISSUER, { - getCurrentBlockOrNull: () => Q({ number: 50, hash: '4C8800825C44A22F230AFC0D140BF1930331A686899D16EBE4C58C9F34C609E8', issuer: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', membersCount: 3 }) - }, function (err) { - should.exist(err); - err.message.should.equal('PreviousIssuer not matching issuer of current block'); - })); - - it('a block with wrong DifferentIssuersCount following V2 should fail', test(rules.GLOBAL.checkDifferentIssuersCount, blocks.WRONG_DIFFERENT_ISSUERS_COUNT_FOLLOWING_V2, { - getCurrentBlockOrNull: () => Q({ version: 2 }), - getBlocksBetween: () => Q([]) - }, function (err) { - should.exist(err); - err.message.should.equal('DifferentIssuersCount is not correct'); - })); - - it('a block with wrong DifferentIssuersCount following V3 should fail', test(rules.GLOBAL.checkDifferentIssuersCount, blocks.WRONG_DIFFERENT_ISSUERS_COUNT_FOLLOWING_V3, { - getCurrentBlockOrNull: () => Q({ version: 3, issuersCount: 4 }), - getBlocksBetween: () => Q([ - // 5 blocks, 4 different issuers - { issuer: 'A' }, - { issuer: 'B' }, - { issuer: 'A' }, - { issuer: 'C' }, - { issuer: 'D' } - ]) - }, function (err) { - should.exist(err); - err.message.should.equal('DifferentIssuersCount is not correct'); - })); - - it('a block with wrong IssuersFrame following V2 should fail', test(rules.GLOBAL.checkIssuersFrame, blocks.WRONG_ISSUERS_FRAME_FOLLOWING_V2, { - getCurrentBlockOrNull: () => Q({ version: 2 }) - }, function (err) { - should.exist(err); - err.message.should.equal('IssuersFrame is not correct'); - })); - - it('a block with wrong IssuersFrame following V3 should fail', test(rules.GLOBAL.checkIssuersFrameVar, blocks.WRONG_ISSUERS_FRAME_FOLLOWING_V3, { - getCurrentBlockOrNull: () => Q({ version: 3, issuersCount: 3, issuersFrame: 56, issuersFrameVar: 6 }) - }, function (err) { - should.exist(err); - err.message.should.equal('IssuersFrameVar is not correct'); - })); - - it('a block with wrong Issuer should fail', test(rules.GLOBAL.checkIssuerIsMember, blocks.WRONG_ISSUER, { - isMember: () => Q(false) - }, function (err) { - should.exist(err); - err.message.should.equal('Issuer is not a member'); - })); - - it('a block with joiner for root block without root number shoud fail', test(rules.GLOBAL.checkJoiners, blocks.WRONG_JOIN_ROOT_NUMBER, { - getCurrentBlockOrNull: () => Q(null) - }, function (err) { - should.exist(err); - err.message.should.equal('Number must be 0 for root block\'s memberships'); - })); - - it('a block with joiner for root block without root hash shoud fail', test(rules.GLOBAL.checkJoiners, blocks.WRONG_JOIN_ROOT_HASH, { - getCurrentBlockOrNull: () => Q(null) - }, function (err) { - should.exist(err); - err.message.should.equal('Hash must be E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 for root block\'s memberships'); - })); - - it('a block with joiner targeting unexisting block fail', test(rules.GLOBAL.checkJoiners, blocks.WRONG_JOIN_BLOCK_TARGET, { - getCurrentBlockOrNull: () => Q(null) - }, function (err) { - should.exist(err); - err.message.should.equal('Membership based on an unexisting block'); - })); - - it('a block with joiner membership number lower or equal than previous should fail', test(rules.GLOBAL.checkJoiners, blocks.WRONG_JOIN_NUMBER_TOO_LOW, { - getCurrentBlockOrNull: () => Q(null), - getWrittenIdtyByPubkey: () => Q({ currentMSN: 2 }), - getBlockByNumberAndHash: () => Q({ number: 3, powMin: 1 }) - }, function (err) { - should.exist(err); - err.message.should.equal('Membership\'s number must be greater than last membership of the pubkey'); - })); - - it('a block with joiner membership of a yet member should fail', test(rules.GLOBAL.checkJoiners, blocks.WRONG_JOIN_ALREADY_MEMBER, { - isMember: () => Q(true), - getWrittenIdtyByPubkey: () => Q({ currentMSN: 2, member: true }), - getBlockByNumberAndHash: () => Q({ number: 3, powMin: 1 }), - getCurrentBlockOrNull: () => Q({ number: 50, hash: '4C8800825C44A22F230AFC0D140BF1930331A686899D16EBE4C58C9F34C609E8', issuer: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', membersCount: 3 }) - }, function (err) { - should.exist(err); - err.message.should.equal('Cannot be in joiners if already a member'); - })); - - it('a block with at least one revoked joiner should fail', test(rules.GLOBAL.checkJoinersAreNotRevoked, blocks.REVOKED_JOINER, { - getWrittenIdtyByPubkey: () => Q({ currentMSN: 2, revoked: true }) - }, function (err) { - should.exist(err); - err.message.should.equal('Revoked pubkeys cannot join'); - })); - - it('a block with at least one joiner without enough certifications should fail', test(rules.GLOBAL.checkJoinersHaveEnoughCertifications, blocks.NOT_ENOUGH_CERTIFICATIONS_JOINER, { - getValidLinksTo: () => Q([]) - }, function (err) { - should.exist(err); - err.message.should.equal('Joiner/Active does not gathers enough certifications'); - })); - - it('a block with at least one joiner without enough certifications should succeed', test(rules.GLOBAL.checkJoinersHaveEnoughCertifications, blocks.NOT_ENOUGH_CERTIFICATIONS_JOINER_BLOCK_0, { - }, function (err) { - should.not.exist(err); - })); - - it('a block with expired membership should fail', test(rules.GLOBAL.checkJoiners, blocks.EXPIRED_MEMBERSHIP, { - getWrittenIdtyByPubkey: () => Q({ currentMSN: 2 }), - getBlockByNumberAndHash: () => Q({ medianTime: 1411775000, powMin: 1 }), - getCurrentBlockOrNull: () => Q({ time: 1443333600, medianTime: 1443333600 }) - }, function (err) { - should.exist(err); - err.message.should.equal('Membership has expired'); - })); - - it('a block with at least one joiner outdistanced from WoT should fail', test(rules.GLOBAL.checkJoinersAreNotOudistanced, blocks.OUTDISTANCED_JOINER, { - wotb: { - addNode: () => 1, - setEnabled: () => 1, - addLink: () => 1, - removeLink: () => 1, - removeNode: () => 1, - isOutdistanced: () => true - }, - getCurrentBlockOrNull: () => Q({ number: 2, hash: '52DC8A585C5D89571C511BB83F7E7D3382F0041452064B1272E65F0B42B82D57', issuer: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', membersCount: 3, time: 1411776000, medianTime: 1411776000 }), - getWrittenIdtyByPubkey: () => Q({ wotb_id: 0 }) - }, function (err) { - should.exist(err); - err.message.should.equal('Joiner/Active is outdistanced from WoT'); - })); - - it('a block with active targeting unexisting block fail', test(rules.GLOBAL.checkActives, blocks.WRONG_ACTIVE_BLOCK_TARGET, { - getCurrentBlockOrNull: () => Q(null) - }, function (err) { - should.exist(err); - err.message.should.equal('Membership based on an unexisting block'); - })); - - it('a block with certification of unknown pubkey should fail', test(rules.GLOBAL.checkCertificationsAreValid, blocks.WRONGLY_SIGNED_CERTIFICATION, { - getCurrentBlockOrNull: () => Q(null), - getBlock: () => Q({}), - getBlockByNumberAndHash: () => Q({}) - }, function (err) { - should.exist(err); - err.message.should.equal('Wrong signature for certification'); - })); - - it('a block with certification to non-zero block for root block should fail', test(rules.GLOBAL.checkCertificationsAreValid, blocks.CERT_BASED_ON_NON_ZERO_FOR_ROOT, { - getCurrentBlockOrNull: () => Q(null) - }, function (err) { - should.exist(err); - err.message.should.equal('Number must be 0 for root block\'s certifications'); - })); - - it('a block with certification to unknown block should fail', test(rules.GLOBAL.checkCertificationsAreValid, blocks.CERT_BASED_ON_NON_EXISTING_BLOCK, { - getCurrentBlockOrNull: () => Q(null) - }, function (err) { - should.exist(err); - err.message.should.equal('Certification based on an unexisting block'); - })); - - it('a block with expired certifications should fail', test(rules.GLOBAL.checkCertificationsAreValid, blocks.EXPIRED_CERTIFICATIONS, { - getCurrentBlockOrNull: () => Q({ time: 1443333600, medianTime: 1443333600 }), - getBlock: () => Q({ medianTime: 1411775000, powMin: 1 }) - }, function (err) { - should.exist(err); - err.message.should.equal('Certification has expired'); - })); - - it('a block with certification from non-member pubkey should fail', test(rules.GLOBAL.checkCertificationsAreMadeByMembers, blocks.UNKNOWN_CERTIFIER, { - getCurrentBlockOrNull: () => Q(null) - }, function (err) { - should.exist(err); - err.message.should.equal('Certification from non-member'); - })); - - it('a block with certification to non-member pubkey should fail', test(rules.GLOBAL.checkCertificationsAreMadeToMembers, blocks.UNKNOWN_CERTIFIED, { - isMember: () => Q(false) - }, function (err) { - should.exist(err); - err.message.should.equal('Certification to non-member'); - })); - - it('a block with already used UserID should fail', test(rules.GLOBAL.checkIdentityUnicity, blocks.EXISTING_UID, { - getWrittenIdtyByUID: () => Q({}) - }, function (err) { - should.exist(err); - err.message.should.equal('Identity already used'); - })); - - it('a block with already used pubkey should fail', test(rules.GLOBAL.checkPubkeyUnicity, blocks.EXISTING_PUBKEY, { - getWrittenIdtyByPubkey: () => Q({}) - }, function (err) { - should.exist(err); - err.message.should.equal('Pubkey already used'); - })); - - it('a block with too early certification replay should fail', test(rules.GLOBAL.checkCertificationsDelayIsRespected, blocks.TOO_EARLY_CERTIFICATION_REPLAY, { - getPreviousLinks: (from, to) => { - if (from == 'G2CBgZBPLe6FSFUgpx2Jf1Aqsgta6iib3vmDRA1yLiqU' - && to == 'CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC') { - // Exactly 1 second remaining - return Q({ timestamp: '1380218401' }); - } - return Q(null); - } - }, function (err) { - should.exist(err); - err.message.should.equal('A similar certification is already active'); - })); - - it('a block with kicked members not written under Excluded field should fail', test(rules.GLOBAL.checkKickedMembersAreExcluded, blocks.KICKED_NOT_EXCLUDED, { - getToBeKicked: () => Q([{}]) - }, function (err) { - should.exist(err); - err.message.should.equal('All kicked members must be present under Excluded members'); - })); - - it('a block with kicked members well written under Excluded field should succeed', test(rules.GLOBAL.checkKickedMembersAreExcluded, blocks.KICKED_EXCLUDED, { - getToBeKicked: () => Q([ - { pubkey: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd' }, - { pubkey: 'G2CBgZBPLe6FSFUgpx2Jf1Aqsgta6iib3vmDRA1yLiqU' } - ]) - }, function (err) { - should.not.exist(err); - })); - it('a block with kicked members not well written under Excluded field should fail', test(rules.GLOBAL.checkKickedMembersAreExcluded, blocks.KICKED_EXCLUDED, { - getToBeKicked: () => Q([ - { pubkey: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd' }, - { pubkey: 'G2CBgZBPLe6FSFUgpx2Jf1Aqsgta6iib3vmDRA1yLiqU' }, - { pubkey: 'D2CBgZBPLe6FSFUgpx2Jf1Aqsgta6iib3vmDRA1yLiqU' } - ]) - }, function (err) { - should.exist(err); - err.message.should.equal('All kicked members must be present under Excluded members'); - })); - - it('a block with wrong members count should fail', test(rules.GLOBAL.checkMembersCountIsGood, blocks.WRONG_MEMBERS_COUNT, { - getCurrentBlockOrNull: () => Q(null) - }, function (err) { - should.exist(err); - err.message.should.equal('Wrong members count'); - })); - - it('a block not starting with a leading zero should fail', test(rules.GLOBAL.checkProofOfWork, blocks.NO_LEADING_ZERO, { - bcContext: { - getIssuerPersonalizedDifficulty: () => Q(8) - } - }, function (err) { - should.exist(err); - err.message.should.equal('Wrong proof-of-work level: given 0 zeros and \'F\', required was 0 zeros and an hexa char between [0-7]'); - })); - - it('a block requiring 2 leading zeros but providing less should fail', test(rules.GLOBAL.checkProofOfWork, blocks.REQUIRES_7_LEADING_ZEROS, { - bcContext: { - getIssuerPersonalizedDifficulty: () => Q(12) - } - }, function (err) { - should.exist(err); - err.message.should.equal('Wrong proof-of-work level: given 0 zeros and \'B\', required was 0 zeros and an hexa char between [0-3]'); - })); - - it('a block requiring 1 leading zeros but providing less should fail', test(rules.GLOBAL.checkProofOfWork, blocks.REQUIRES_6_LEADING_ZEROS, { - bcContext: { - getIssuerPersonalizedDifficulty: () => Q(8) - } - }, function (err) { - should.exist(err); - err.message.should.equal('Wrong proof-of-work level: given 0 zeros and \'8\', required was 0 zeros and an hexa char between [0-7]'); - })); - - it('a block requiring 1 leading zeros as first block of newcomer should succeed', test(rules.GLOBAL.checkProofOfWork, blocks.FIRST_BLOCK_OF_NEWCOMER, { - bcContext: { - getIssuerPersonalizedDifficulty: () => Q(1) - } - }, function (err) { - should.not.exist(err); - })); - - it('a block requiring 40 leading zeros as second block of newcomer should fail', test(rules.GLOBAL.checkProofOfWork, blocks.SECOND_BLOCK_OF_NEWCOMER, { - bcContext: { - getIssuerPersonalizedDifficulty: () => Q(160) - } - }, function (err) { - should.exist(err); - err.message.should.equal('Wrong proof-of-work level: given 0 zeros and \'F\', required was 10 zeros and an hexa char between [0-9A-F]'); - })); - - it('a root block should not fail for time reason', test(rules.GLOBAL.checkTimes, blocks.WRONG_ROOT_DATES, { - getCurrentBlockOrNull: () => Q(null) - }, function (err) { - should.not.exist(err); - })); - - it('a block with wrong median for an odd number of blocks should fail', test(rules.GLOBAL.checkTimes, blocks.WRONG_MEDIAN_TIME_ODD, { - getBlocksBetween: () => Q([{time: 1},{time: 12}]) - }, function (err) { - should.exist(err); - err.message.should.equal('Wrong MedianTime'); - })); - - it('a block with wrong median for an even number of blocks should fail', test(rules.GLOBAL.checkTimes, blocks.WRONG_MEDIAN_TIME_EVEN, { - getBlocksBetween: () => Q([{time: 1},{time: 12}]) - }, function (err) { - should.exist(err); - err.message.should.equal('Wrong MedianTime'); - })); - - it('a block whose median time is correct (odd) should pass', test(rules.GLOBAL.checkTimes, blocks.GOOD_MEDIAN_TIME_ODD, { - getBlocksBetween: () => { - let times = []; - for (let i = 0; i < 103; i++) - times.push({ time: 161 }); - return Q(times); - } - }, function (err) { - should.not.exist(err); - })); - - it('a block whose median time is correct (even) should pass', test(rules.GLOBAL.checkTimes, blocks.GOOD_MEDIAN_TIME_EVEN, { - getBlocksBetween: () => { - let times = []; - for (let i = 0; i < 104; i++) - times.push({ time: 162 }); - return Q(times); - } - }, function (err) { - should.not.exist(err); - })); - - it('a root block with Universal Dividend should fail', test(rules.GLOBAL.checkUD, blocks.ROOT_BLOCK_WITH_UD, { - lastUDBlock: () => Q(null), - getBlock: () => Q(null), - getCurrentBlockOrNull: () => Q(null) - }, function (err) { - should.exist(err); - err.message.should.equal('Root block cannot have UniversalDividend field'); - })); - - it('first block with Universal Dividend should not happen before root time + dt', test(rules.GLOBAL.checkUD, blocks.FIRST_UD_BLOCK_WITH_UD_THAT_SHOULDNT, { - lastUDBlock: () => Q(null), - getBlock: () => Q({ hash: 'E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855', medianTime: 1411773000, powMin: 1 }), - getCurrentBlockOrNull: () => Q({ number: 19, time: 1411773000, medianTime: 1411773000 }) - }, function (err) { - should.exist(err); - err.message.should.equal('This block cannot have UniversalDividend'); - })); - - it('first block with Universal Dividend should happen on root time + dt', test(rules.GLOBAL.checkUD, blocks.FIRST_UD_BLOCK_WITH_UD_THAT_SHOULD, { - lastUDBlock: () => Q(null), - getBlock: () => Q({ hash: 'E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855', medianTime: 1411773000, powMin: 1 }), - getCurrentBlockOrNull: () => Q({ number: 19, time: 1411773000, medianTime: 1411773000 }) - }, function (err) { - should.exist(err); - err.message.should.equal('Block must have a UniversalDividend field'); - })); - - it('a block without Universal Dividend whereas it have to have one should fail', test(rules.GLOBAL.checkUD, blocks.UD_BLOCK_WIHTOUT_UD, { - lastUDBlock: () => Q(null), - getBlock: () => Q({ hash: 'E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855', medianTime: 1411773000, powMin: 1 }), - getCurrentBlockOrNull: () => Q({ number: 19, time: 1411773000, medianTime: 1411773000 }) - }, function (err) { - should.exist(err); - err.message.should.equal('Block must have a UniversalDividend field'); - })); - - it('a block with wrong (version 2) Universal Dividend value should fail', test(rules.GLOBAL.checkUD, blocks.BLOCK_WITH_WRONG_UD, { - lastUDBlock: () => Q({ UDTime: 1411776900, medianTime: 1411776900, monetaryMass: 3620 * 10000, dividend: 110, unitbase: 4 }), - getBlock: () => Q(), - getCurrentBlockOrNull: () => Q({ time: 1411777000, medianTime: 1411777000 }) - }, function (err) { - should.exist(err); - err.message.should.equal('UniversalDividend must be equal to 121'); - })); - - it('a block with wrong (version 3) Universal Dividend value should fail', test(rules.GLOBAL.checkUD, blocks.BLOCK_WITH_WRONG_UD_V3, { - lastUDBlock: () => Q({ UDTime: 1411776900, medianTime: 1411776900, dividend: 110, unitbase: 4 }), - getBlock: () => Q(), - getCurrentBlockOrNull: () => Q({ time: 1411777000, medianTime: 1411777000 }) - }, function (err) { - should.exist(err); - err.message.should.equal('UniversalDividend must be equal to 121'); - })); - - it('a block with wrong UnitBase value should fail', test(rules.GLOBAL.checkUD, blocks.BLOCK_WITH_WRONG_UNIT_BASE, { - lastUDBlock: () => Q({ UDTime: 1411777000, medianTime: 1411777000, monetaryMass: 12345678900, dividend: 100, unitbase: 2 }), - getBlock: () => Q(), - getCurrentBlockOrNull: () => Q({ time: 1411777000, medianTime: 1411777000 }) - }, function (err) { - should.exist(err); - err.message.should.equal('UnitBase must be equal to 3'); - })); - - it('a block without UD with wrong UnitBase value should fail', test(rules.GLOBAL.checkUD, blocks.BLOCK_WITH_WRONG_UNIT_BASE_NO_UD, { - lastUDBlock: () => Q({ UDTime: 1411777000, medianTime: 1411777000, monetaryMass: 12345678900, dividend: 100, unitbase: 8 }), - getBlock: () => Q(), - getCurrentBlockOrNull: () => Q({ time: 1411777000, medianTime: 1411777000, unitbase: 5 }) - }, function (err) { - should.exist(err); - err.message.should.equal('UnitBase must be equal to previous unit base = 5'); - })); - - it('a root block with unlegitimated Universal Dividend presence should fail', test(rules.GLOBAL.checkUD, blocks.BLOCK_UNLEGITIMATE_UD, { - lastUDBlock: () => Q({ UDTime: 1411777000, medianTime: 1411777000, monetaryMass: 3620 * 10000, dividend: 110, unitbase: 4 }), - getBlock: () => Q(), - getCurrentBlockOrNull: () => Q({ time: 1411777000, medianTime: 1411777000 }) - }, function (err) { - should.exist(err); - err.message.should.equal('This block cannot have UniversalDividend'); - })); - - it('a root block with unlegitimated Universal Dividend presence should fail', test(rules.GLOBAL.checkUD, blocks.BLOCK_UNLEGITIMATE_UD_2, { - lastUDBlock: () => Q({ UDTime: 1411777000, medianTime: 1411777000, monetaryMass: 3620 * 10000, dividend: 110, unitbase: 4 }), - getBlock: () => Q(), - getCurrentBlockOrNull: () => Q({ time: 1411777000, medianTime: 1411777000 }) - }, function (err) { - should.exist(err); - err.message.should.equal('This block cannot have UniversalDividend'); - })); - - - it('a block without transactions should pass', test(rules.GLOBAL.checkSourcesAvailability, blocks.BLOCK_WITHOUT_TRANSACTIONS, { - getCurrentBlockOrNull: () => Q(null) - }, function (err) { - should.not.exist(err); - })); - - it('a block with good transactions should pass', test(rules.GLOBAL.checkSourcesAvailability, blocks.BLOCK_WITH_GOOD_TRANSACTIONS, { - getCurrentBlockOrNull: () => Q({ unitbase: 5 }), - getSource: (id, noffset) => { - if (id == '6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3' && noffset == 4) return Q({ amount: 0, base: 4 }); - if (id == '3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435' && noffset == 10) return Q({ amount: 0, base: 3 }); - if (id == 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd' && noffset == 46) return Q({ amount: 0, base: 4 }); - if (id == 'A0D9B4CDC113ECE1145C5525873821398890AE842F4B318BD076095A23E70956' && noffset == 66) return Q({ amount: 235, base: 4 }); - if (id == '67F2045B5318777CC52CD38B424F3E40DDA823FA0364625F124BABE0030E7B5B' && noffset == 176) return Q({ amount: 0, base: 4 }); - return Q(null); - } - }, function (err) { - should.not.exist(err); - })); - - it('a block with wrong transaction sum should fail', test(rules.GLOBAL.checkSourcesAvailability, blocks.BLOCK_WITH_WRONG_TRANSACTION_SUMS, { - getCurrentBlockOrNull: () => Q({ unitbase: 5 }), - getSource: (id, noffset) => { - if (id == '6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3' && noffset == 4) return Q({ amount: 0, base: 4 }); - if (id == '3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435' && noffset == 10) return Q({ amount: 0, base: 3 }); - if (id == 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd' && noffset == 46) return Q({ amount: 0, base: 4 }); - if (id == 'A0D9B4CDC113ECE1145C5525873821398890AE842F4B318BD076095A23E70956' && noffset == 66) return Q({ amount: 235, base: 4 }); - if (id == '67F2045B5318777CC52CD38B424F3E40DDA823FA0364625F124BABE0030E7B5B' && noffset == 176) return Q({ amount: 0, base: 4 }); - return Q(null); - } - }, function (err) { - should.exist(err); - err.uerr.message.should.equal('Sum of inputs must equal sum of outputs'); - })); - - it('a block with wrong transaction unit bases should fail', test(rules.GLOBAL.checkSourcesAvailability, blocks.BLOCK_WITH_WRONG_TRANSACTION_SUMS, { - getCurrentBlockOrNull: () => Q({ unitbase: 5 }), - getSource: (id, noffset) => { - if (id == '6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3' && noffset == 4) return Q({ amount: 0, base: 4 }); - if (id == '3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435' && noffset == 10) return Q({ amount: 0, base: 3 }); - if (id == 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd' && noffset == 46) return Q({ amount: 0, base: 4 }); - if (id == 'A0D9B4CDC113ECE1145C5525873821398890AE842F4B318BD076095A23E70956' && noffset == 66) return Q({ amount: 235, base: 4 }); - if (id == '67F2045B5318777CC52CD38B424F3E40DDA823FA0364625F124BABE0030E7B5B' && noffset == 176) return Q({ amount: 0, base: 4 }); - return Q(null); - } - }, function (err) { - should.exist(err); - err.uerr.message.should.equal('Sum of inputs must equal sum of outputs'); - })); - - it('a block with whose transaction has too high unit bases should fail', test(rules.GLOBAL.checkSourcesAvailability, blocks.BLOCK_WITH_WRONG_TRANSACTION_UNIT_BASES, { - getCurrentBlockOrNull: () => Q({ unitbase: 2 }), - getSource: (id, noffset) => { - if (id == '6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3' && noffset == 4) return Q({ amount: 0, base: 4 }); - if (id == '3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435' && noffset == 10) return Q({ amount: 0, base: 3 }); - if (id == 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd' && noffset == 46) return Q({ amount: 0, base: 4 }); - if (id == 'A0D9B4CDC113ECE1145C5525873821398890AE842F4B318BD076095A23E70956' && noffset == 66) return Q({ amount: 235, base: 4 }); - if (id == '67F2045B5318777CC52CD38B424F3E40DDA823FA0364625F124BABE0030E7B5B' && noffset == 176) return Q({ amount: 0, base: 4 }); - return Q(null); - } - }, function (err) { - should.exist(err); - err.uerr.message.should.equal('Wrong unit base for outputs'); - })); - - it('a block with unavailable UD source should fail', test(rules.GLOBAL.checkSourcesAvailability, blocks.BLOCK_WITH_UNAVAILABLE_UD_SOURCE, { - getCurrentBlockOrNull: () => Q({ unitbase: 5 }), - getSource: (id, noffset) => { - if (id == '6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3' && noffset == 4) return Q({ amount: 0, base: 4 }); - if (id == '3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435' && noffset == 10) return Q({ amount: 0, base: 3 }); - if (id == 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd' && noffset == 46) return Q({ amount: 0, base: 4 }); - if (id == 'A0D9B4CDC113ECE1145C5525873821398890AE842F4B318BD076095A23E70956' && noffset == 66) return Q({ amount: 235, base: 4 }); - if (id == '67F2045B5318777CC52CD38B424F3E40DDA823FA0364625F124BABE0030E7B5B' && noffset == 176) return Q({ amount: 0, base: 4 }); - return Q(null); - } - }, function (err) { - should.exist(err); - err.should.have.property('uerr').property('message').equal('Source already consumed'); - })); - - it('a block with unavailable TX source should fail', test(rules.GLOBAL.checkSourcesAvailability, blocks.BLOCK_WITH_UNAVAILABLE_TX_SOURCE, { - getCurrentBlockOrNull: () => Q({ unitbase: 5 }), - getSource: (id, noffset) => { - if (id == '6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3' && noffset == 4) return Q({ amount: 0, base: 4 }); - if (id == '3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435' && noffset == 10) return Q({ amount: 0, base: 3 }); - if (id == 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd' && noffset == 46) return Q({ amount: 0, base: 4 }); - if (id == 'A0D9B4CDC113ECE1145C5525873821398890AE842F4B318BD076095A23E70956' && noffset == 66) return Q({ amount: 235, base: 4 }); - if (id == '67F2045B5318777CC52CD38B424F3E40DDA823FA0364625F124BABE0030E7B5B' && noffset == 176) return Q({ amount: 0, base: 4 }); - return Q(null); - } - }, function (err) { - should.exist(err); - err.should.have.property('uerr').property('message').equal('Source already consumed'); - })); - - it('a block with a too high unit base should fail', test(rules.GLOBAL.checkSourcesAvailability, blocks.BLOCK_TX_V3_TOO_HIGH_OUTPUT_BASE, { - getCurrentBlockOrNull: () => Q({ unitbase: 3 }), - getSource: () => Q({ base: 1, amount: 10 }) - }, function (err) { - should.exist(err); - err.should.have.property('uerr').property('message').equal('Wrong unit base for outputs'); - })); - - it('a block with an unknown member revoked should fail', test(rules.GLOBAL.checkRevoked, blocks.BLOCK_UNKNOWN_REVOKED, { - getWrittenIdtyByPubkey: () => Q(null) - }, function (err) { - should.exist(err); - err.message.should.equal('A pubkey who was never a member cannot be revoked'); - })); - - it('a block with a yet revoked identity should fail', test(rules.GLOBAL.checkRevoked, blocks.BLOCK_WITH_YET_REVOKED, { - getWrittenIdtyByPubkey: () => Q({ revoked: true }) - }, function (err) { - should.exist(err); - err.message.should.equal('A revoked identity cannot be revoked again'); - })); - - it('a block with a wrong revocation signature should fail', test(rules.GLOBAL.checkRevoked, blocks.BLOCK_WITH_WRONG_REVOCATION_SIG, { - getWrittenIdtyByPubkey: () => Q({}) - }, function (err) { - should.exist(err); - err.message.should.equal('Revocation signature must match'); - })); -}); - -function test (rule, raw, dal, callback) { - return function() { - return co(function *() { - let obj = parser.syncWrite(raw); - let block = new Block(obj); - if (rule == rules.GLOBAL.checkProofOfWork || rule == rules.GLOBAL.checkVersion) { - yield rule(block, dal.bcContext); - } else if (rule.length == 2) { - yield rule(block, dal); - } else { - yield rule(block, conf, dal); - } - }) - .then(callback).catch(callback); - }; -} - -function validate (raw, dal, bcContext, callback) { - var block; - return function() { - return Q.Promise(function(resolve, reject){ - async.waterfall([ - function (next){ - block = new Block(parser.syncWrite(raw)); - rules.CHECK.ASYNC.ALL_GLOBAL(block, conf, dal, bcContext, next); - } - ], function (err) { - err && console.error(err.stack); - err ? reject(err) : resolve(); - }); - }) - .then(callback).catch(callback); - }; -} diff --git a/test/fast/block_local.js b/test/fast/block_local.js deleted file mode 100644 index ed4d289e47dc953ebd2489eb4121719358e65061..0000000000000000000000000000000000000000 --- a/test/fast/block_local.js +++ /dev/null @@ -1,124 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict"; -const co = require('co'); -const should = require('should'); -const parsers = require('../../app/lib/common-libs/parsers').parsers -const indexer = require('../../app/lib/indexer').Indexer -const LOCAL_RULES = require('../../app/lib/rules/local_rules').LOCAL_RULES_FUNCTIONS -const ALIAS = require('../../app/lib/rules').ALIAS -const blocks = require('../data/blocks.js'); -const parser = parsers.parseBlock; -const BlockDTO = require('../../app/lib/dto/BlockDTO').BlockDTO - -const conf = { - - sigQty: 1, - powZeroMin: 1, - powPeriod: 18, - incDateMin: 10, - avgGenTime: 60, - medianTimeBlocks: 20, - dt: 100, - ud0: 100, - c: 0.1 -} - -describe("Block local coherence", function(){ - - it('a valid block should be well formatted', test(ALIAS.ALL_LOCAL_BUT_POW_AND_SIGNATURE, blocks.VALID_ROOT)); - - describe("should be rejected", function(){ - - it('if wrong signature block', test(LOCAL_RULES.checkBlockSignature, blocks.WRONG_SIGNATURE, 'Block\'s signature must match')); - it('if root block does not have Parameters', test(LOCAL_RULES.checkParameters, blocks.ROOT_WITHOUT_PARAMETERS, 'Parameters must be provided for root block')); - it('if proof-of-work does not match PoWMin field', test(LOCAL_RULES.checkProofOfWork, blocks.WRONG_PROOF_OF_WORK, 'Not a proof-of-work')); - it('if non-root has Parameters', test(LOCAL_RULES.checkParameters, blocks.NON_ROOT_WITH_PARAMETERS, 'Parameters must not be provided for non-root block')); - it('if root block has PreviousHash', test(LOCAL_RULES.checkPreviousHash, blocks.ROOT_WITH_PREVIOUS_HASH, 'PreviousHash must not be provided for root block')); - it('if root block has PreviousIssuer', test(LOCAL_RULES.checkPreviousIssuer, blocks.ROOT_WITH_PREVIOUS_ISSUER, 'PreviousIssuer must not be provided for root block')); - it('if non-root block does not have PreviousHash', test(LOCAL_RULES.checkPreviousHash, blocks.NON_ROOT_WITHOUT_PREVIOUS_HASH, 'PreviousHash must be provided for non-root block')); - it('if non-root block does not have PreviousIssuer', test(LOCAL_RULES.checkPreviousIssuer, blocks.NON_ROOT_WITHOUT_PREVIOUS_ISSUER, 'PreviousIssuer must be provided for non-root block')); - it('a V2 block with Dividend must have UnitBase field', test(LOCAL_RULES.checkUnitBase, blocks.UD_BLOCK_WIHTOUT_BASE, 'Document has unkown fields or wrong line ending format')); - it('a V3 root block must have UnitBase field', test(LOCAL_RULES.checkUnitBase, blocks.V3_ROOT_BLOCK_NOBASE, 'Document has unkown fields or wrong line ending format')); - it('a V3 root block must have UnitBase field equal 0', test(LOCAL_RULES.checkUnitBase, blocks.V3_ROOT_BLOCK_POSITIVE_BASE, 'UnitBase must equal 0 for root block')); - it('a block with wrong date (in past)', test(LOCAL_RULES.checkBlockTimes, blocks.WRONG_DATE_LOWER, 'A block must have its Time between MedianTime and MedianTime + 1440')); - it('a block with wrong date (in future, but too far)', test(LOCAL_RULES.checkBlockTimes, blocks.WRONG_DATE_HIGHER_BUT_TOO_HIGH, 'A block must have its Time between MedianTime and MedianTime + 1440')); - it('a root block with different time & medianTime should fail', test(LOCAL_RULES.checkBlockTimes, blocks.WRONG_ROOT_TIMES, 'Root block must have Time equal MedianTime')); - it('a block with good date', test(LOCAL_RULES.checkBlockTimes, blocks.GOOD_DATE_HIGHER)); - it('Block cannot contain wrongly signed identities', test(LOCAL_RULES.checkIdentitiesSignature, blocks.WRONGLY_SIGNED_IDENTITIES, 'Identity\'s signature must match')); - it('block with colliding uids in identities', test(LOCAL_RULES.checkIdentitiesUserIDConflict, blocks.COLLIDING_UIDS, 'Block must not contain twice same identity uid')); - it('a block with colliding pubkeys in identities', test(LOCAL_RULES.checkIdentitiesPubkeyConflict, blocks.COLLIDING_PUBKEYS, 'Block must not contain twice same identity pubkey')); - it('a block with identities not matchin joins', test(LOCAL_RULES.checkIdentitiesMatchJoin, blocks.WRONG_IDTY_MATCH_JOINS, 'Each identity must match a newcomer line with same userid and certts')); - it('Block cannot contain wrongly signed join', test(LOCAL_RULES.checkMembershipsSignature, blocks.WRONGLY_SIGNED_JOIN, 'Membership\'s signature must match')); - it('Block cannot contain wrongly signed active', test(LOCAL_RULES.checkMembershipsSignature, blocks.WRONGLY_SIGNED_ACTIVE, 'Membership\'s signature must match')); - it('Block cannot contain wrongly signed leave', test(LOCAL_RULES.checkMembershipsSignature, blocks.WRONGLY_SIGNED_LEAVE, 'Membership\'s signature must match')); - it('Block cannot contain a same pubkey more than once in joiners', test(LOCAL_RULES.checkPubkeyUnicity, blocks.MULTIPLE_JOINERS, 'Block cannot contain a same pubkey more than once in joiners, actives, leavers and excluded')); - it('Block cannot contain a same pubkey more than once in actives', test(LOCAL_RULES.checkPubkeyUnicity, blocks.MULTIPLE_ACTIVES, 'Block cannot contain a same pubkey more than once in joiners, actives, leavers and excluded')); - it('Block cannot contain a same pubkey more than once in leavers', test(LOCAL_RULES.checkPubkeyUnicity, blocks.MULTIPLE_LEAVES, 'Block cannot contain a same pubkey more than once in joiners, actives, leavers and excluded')); - it('Block cannot contain a same pubkey more than once in excluded', test(LOCAL_RULES.checkPubkeyUnicity, blocks.MULTIPLE_EXCLUDED, 'Block cannot contain a same pubkey more than once in joiners, actives, leavers and excluded')); - it('Block cannot contain a same pubkey more than once in joiners, actives, leavers and excluded', test(LOCAL_RULES.checkPubkeyUnicity, blocks.MULTIPLE_OVER_ALL, 'Block cannot contain a same pubkey more than once in joiners, actives, leavers and excluded')); - it('Block cannot have revoked key in joiners,actives,leavers', test(LOCAL_RULES.checkMembershipUnicity, blocks.REVOKED_WITH_MEMBERSHIPS, 'Unicity constraint PUBLIC_KEY on MINDEX is not respected')); - it('Block cannot have revoked key duplicates', test(LOCAL_RULES.checkRevokedUnicity, blocks.REVOKED_WITH_DUPLICATES, 'A single revocation per member is allowed')); - it('Block revoked keys must be in excluded', test(LOCAL_RULES.checkRevokedAreExcluded, blocks.REVOKED_NOT_IN_EXCLUDED, 'A revoked member must be excluded')); - it('Block cannot contain 2 certifications from same issuer', test(LOCAL_RULES.checkCertificationOneByIssuer, blocks.MULTIPLE_CERTIFICATIONS_FROM_SAME_ISSUER, 'Block cannot contain two certifications from same issuer')); - it('Block cannot contain identical certifications', test(LOCAL_RULES.checkCertificationUnicity, blocks.IDENTICAL_CERTIFICATIONS, 'Block cannot contain identical certifications (A -> B)')); - it('Block cannot contain certifications concerning a leaver', test(LOCAL_RULES.checkCertificationIsntForLeaverOrExcluded, blocks.LEAVER_WITH_CERTIFICATIONS, 'Block cannot contain certifications concerning leavers or excluded members')); - it('Block cannot contain certifications concerning an excluded member', test(LOCAL_RULES.checkCertificationIsntForLeaverOrExcluded, blocks.EXCLUDED_WITH_CERTIFICATIONS, 'Block cannot contain certifications concerning leavers or excluded members')); - it('Block cannot contain transactions without issuers (1)', test(LOCAL_RULES.checkTxIssuers, blocks.TRANSACTION_WITHOUT_ISSUERS, 'A transaction must have at least 1 issuer')); - it('Block cannot contain transactions without issuers (2)', test(LOCAL_RULES.checkTxSources, blocks.TRANSACTION_WITHOUT_SOURCES, 'A transaction must have at least 1 source')); - it('Block cannot contain transactions without issuers (3)', test(LOCAL_RULES.checkTxRecipients, blocks.TRANSACTION_WITHOUT_RECIPIENT, 'A transaction must have at least 1 recipient')); - it('Block cannot contain transactions with identical sources in one transaction', test(LOCAL_RULES.checkTxSources, blocks.TRANSACTION_WITH_DUPLICATED_SOURCE_SINGLE_TX, 'It cannot exist 2 identical sources for transactions inside a given block')); - it('Block cannot contain transactions with identical sources in a pack of transactions', test(LOCAL_RULES.checkTxSources, blocks.TRANSACTION_WITH_DUPLICATED_SOURCE_MULTIPLE_TX, 'It cannot exist 2 identical sources for transactions inside a given block')); - it('Block cannot contain transactions with empty output conditions', test(LOCAL_RULES.checkTxRecipients, blocks.TRANSACTION_WITH_EMPTY_TX_CONDITIONS, 'Empty conditions are forbidden')); - it('Block cannot contain transactions with wrong total', test(LOCAL_RULES.checkTxAmounts, blocks.TRANSACTION_WRONG_TOTAL, 'Transaction inputs sum must equal outputs sum')); - it('Block cannot contain transactions with wrong base transformation', test(LOCAL_RULES.checkTxAmounts, blocks.TRANSACTION_WRONG_TRANSFORM, 'Transaction output base amount does not equal previous base deltas')); - it('Block cannot contain transactions with unexisting lower base in sources', test(LOCAL_RULES.checkTxAmounts, blocks.TRANSACTION_WRONG_TRANSFORM_LOW_BASE, 'Transaction output base amount does not equal previous base deltas')); - it('Block cannot contain transactions with more than 100 lines', test(LOCAL_RULES.checkTxLen, blocks.TRANSACTION_TOO_LONG, 'A transaction has a maximum size of 100 lines')); - it('Block cannot contain transactions with a too large output', test(LOCAL_RULES.checkTxLen, blocks.OUTPUT_TOO_LONG, 'A transaction output has a maximum size of 2000 characters')); - it('Block cannot contain transactions with a too large unlock', test(LOCAL_RULES.checkTxLen, blocks.UNLOCK_TOO_LONG, 'A transaction unlock has a maximum size of 2000 characters')); - it('Block cannot be refused with a good V3 transaction', test(LOCAL_RULES.checkTxAmounts, blocks.TRANSACTION_V3_GOOD_AMOUNTS)); - it('Block cannot contain transactions with wrong signatures', test(LOCAL_RULES.checkTxSignature, blocks.TRANSACTION_WITH_WRONG_SIGNATURES, 'Signature from a transaction must match')); - }); - -}); - - -function test (rule, raw, expectedMessage) { - return () => co(function *() { - try { - let obj = parser.syncWrite(raw); - let block = BlockDTO.fromJSONObject(obj); - let index = indexer.localIndex(block, conf) - yield rule(block, conf, index); // conf parameter is not always used - if (expectedMessage) { - throw 'Test should have thrown an error'; - } - } catch (e) { - if (!expectedMessage) { - console.error(e.stack || e); - } - if (e.uerr) { - // This is a controlled error - e.uerr.message.should.equal(expectedMessage); - } else if (e) { - // This is a controlled error - e.message.should.equal(expectedMessage); - } else { - // throw Error(e) - // Display non wrapped errors (wrapped error is an error in constants.js) - // console.error(e.stack || e); - } - } - }); -} diff --git a/test/fast/cfs.js b/test/fast/cfs.js deleted file mode 100644 index 2067d7b27217e1edca77a9da04c5fb79d28d5302..0000000000000000000000000000000000000000 --- a/test/fast/cfs.js +++ /dev/null @@ -1,155 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict"; - -var assert = require('assert'); -var should = require('should'); -var co = require('co'); -var CFSCore = require('../../app/lib/dal/fileDALs/CFSCore').CFSCore; -var mockFS = require('q-io/fs-mock')({ - 'B5_a': { - "A.json": '{ "text": "Content of A from B5_a" }' - }, - 'B4': { - 'B.json': '{ "text": "Content of B" }' - }, - 'B3': { - 'A.json': '{ "text": "Content of A from B3" }', - 'C.json': '{ "text": "Content of C from B3" }' - }, - 'OTHER': { - 'X.json': '{ "text": "Content of X" }' - } -}); - -describe("CFS", () => { - - var coreB3 = new CFSCore('/B3', mockFS); - var coreB4 = new CFSCore('/B4', mockFS); - var coreB5 = new CFSCore('/B5_a', mockFS); - - var rootCore = new CFSCore('/OTHER', mockFS); - - // ------------ Direct READ ------------ - - it('should have the content of A.json from B5', () => { - return co(function *() { - var content = yield coreB5.readJSON('A.json'); - content.should.have.property('text').equal('Content of A from B5_a'); - }); - }); - - // WRITE of file /C.json - - it('should have the content of C.json modified from B5 (direct read)', () => { - return co(function *() { - yield coreB5.writeJSON('C.json', { text: 'Content of C from B5_a'}); - var content = yield coreB5.readJSON('C.json'); - content.should.have.property('text').equal('Content of C from B5_a'); - }); - }); - - // WRITE of file /D.json - - it('should have the content of D.json modified from B4 (direct read/write)', () => { - return co(function *() { - yield coreB4.writeJSON('D.json', { text: 'Content of D'}); - var content = yield coreB4.readJSON('D.json'); - content.should.have.property('text').equal('Content of D'); - }); - }); - - // REMOVE file /D.json - - it('should have the content of D.json modified from B5 (direct read/write)', () => { - return co(function *() { - var exists = yield coreB5.exists('D.json'); - var content = yield coreB5.read('D.json'); - assert.equal(exists, false); - assert.equal(content, null); - }); - }); - - // ------------ LIST ------------ - - it('should have G,H,I as files from /DIR', () => { - return co(function *() { - yield coreB3.makeTree('/DIR'); - yield coreB4.makeTree('/DIR'); - yield coreB5.makeTree('/DIR'); - yield coreB3.writeJSON('/DIR/G.json', { text: 'Content of DIR/I'}); - yield coreB4.writeJSON('/DIR/H.json', { text: 'Content of DIR/H'}); - yield coreB5.writeJSON('/DIR/I.json', { text: 'Content of DIR/G'}); - (yield coreB3.list('/DIR')).should.deepEqual(['G.json']); - (yield coreB4.list('/DIR')).should.deepEqual(['H.json']); - (yield coreB5.list('/DIR')).should.deepEqual(['I.json']); - }); - }); - - // WRITE of file /DIR2/I.json in B4 - - it('should have I as files from /DIR2', () => { - return co(function *() { - yield coreB3.makeTree('/DIR2'); - yield coreB3.writeJSON('/DIR2/I.json', { text: 'Content of DIR2/I in B4'}); - var files = yield coreB3.list('/DIR2'); - files.should.have.length(1); - files.should.deepEqual(['I.json']); - // Check its contents - var contents = yield coreB3.listJSON('/DIR2'); - contents.should.have.length(1); - contents.should.deepEqual([{ text: 'Content of DIR2/I in B4' }]); - }); - }); - - // REMOVE of file /DIR2/I.json in B5 - - it('should have no files from /DIR2 after file DELETION', () => { - return co(function *() { - yield coreB3.remove('/DIR2/I.json'); - var files = yield coreB3.list('/DIR2'); - files.should.have.length(0); - // Check its contents - var contents = yield coreB3.listJSON('/DIR2'); - contents.should.have.length(0); - }); - }); - - describe("Root core", () => { - - it('should have 1 file in /OTHER folder', () => { - return co(function *() { - var files = yield rootCore.list('/'); - files.should.have.length(1); - // Check its contents - var contents = yield rootCore.listJSON('/'); - contents.should.have.length(1); - contents.should.deepEqual([{ text: 'Content of X' }]); - }); - }); - - // REMOVE of file /OTHER/X.json in rootCore - - it('should have no files from /OTHER after file DELETION', () => { - return co(function *() { - yield rootCore.remove('/X.json'); - var files = yield rootCore.list('/'); - files.should.have.length(0); - // Check its contents - var contents = yield rootCore.listJSON('/'); - contents.should.have.length(0); - }); - }); - }); -}); diff --git a/test/fast/common/crypto.js b/test/fast/crypto/crypto.ts similarity index 68% rename from test/fast/common/crypto.js rename to test/fast/crypto/crypto.ts index 4c2ee4325f6e595acc0bf1a5f4ce7199d51663ae..c494a67f98005093b4236a307ecab6d123c3956c 100644 --- a/test/fast/common/crypto.js +++ b/test/fast/crypto/crypto.ts @@ -12,31 +12,27 @@ // GNU Affero General Public License for more details. "use strict"; -const should = require('should'); -const co = require('co'); -const nacl = require('tweetnacl'); -const base58 = require('../../../app/lib/common-libs/crypto/base58') -const naclUtil = require('../../../app/lib/common-libs/crypto/nacl-util') -const keyring = require('../../../app/lib/common-libs/crypto/keyring') +import {Base58decode, Base58encode} from "../../../app/lib/common-libs/crypto/base58" +import {decodeBase64, encodeBase64} from "../../../app/lib/common-libs/crypto/nacl-util" +import {KeyGen, verify} from "../../../app/lib/common-libs/crypto/keyring" -const Base58decode = base58.Base58decode -const Base58encode = base58.Base58encode +const should = require('should'); -const enc = naclUtil.encodeBase64 -const dec = naclUtil.decodeBase64 +const enc = encodeBase64 +const dec = decodeBase64 -let pub, sec, rawPub, rawSec; +let pub:Uint8Array, sec:Uint8Array, rawPub:string, rawSec:string describe('ed25519 tests:', function(){ - before(() => co(function*() { + before(async () => { // Generate the keypair - const keyPair = keyring.KeyGen('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'); + const keyPair = KeyGen('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'); pub = Base58decode(keyPair.publicKey); sec = Base58decode(keyPair.secretKey); rawPub = Base58encode(pub); rawSec = Base58encode(sec); - })); + }) //it('good signature from existing secret key should be verified', function(done){ // const keys = nacl.sign.scryptKeyPair.fromSecretKey(dec("TM0Imyj/ltqdtsNG7BFOD1uKMZ81q6Yk2oz27U+4pvs9QBfD6EOJWpK3CqdNG368nJgszy7ElozAzVXxKvRmDA==")); @@ -50,17 +46,16 @@ describe('ed25519 tests:', function(){ it('good signature from generated key should be verified', function(done){ const msg = "Some message to be signed"; - const sig = keyring.KeyGen(rawPub, rawSec).signSync(msg); - const verified = keyring.verify(msg, sig, rawPub); + const sig = KeyGen(rawPub, rawSec).signSync(msg); + const verified = verify(msg, sig, rawPub); verified.should.equal(true); done(); }); it('wrong signature from generated key should NOT be verified', function(done){ const msg = "Some message to be signed"; - const cor = dec(enc(msg) + 'delta'); - const sig = keyring.KeyGen(rawPub, rawSec).signSync(msg); - const verified = keyring.verify(cor, sig, rawPub); + const sig = KeyGen(rawPub, rawSec).signSync(msg); + const verified = verify(msg + 'delta', sig, rawPub); verified.should.equal(false); done(); }); @@ -73,7 +68,7 @@ describe('ed25519 tests:', function(){ "Block: 33291-0000088375C232A4DDAE171BB3D3C51347CB6DC8B7AA8BE4CD4DAEEADF26FEB8\n" + "Endpoints:\n" + "BASIC_MERKLED_API g1.duniter.org 10901\n" - const verified = keyring.verify(msg, "u8t1IoWrB/C7T+2rS0rKYJfjPG4FN/HkKGFiUO5tILIzjFDvxxQiVC+0o/Vaz805SMmqJvXqornI71U7//+wCg==", "3AF7bhGQRt6ymcBZgZTBMoDsEtSwruSarjNG8kDnaueX"); + const verified = verify(msg, "u8t1IoWrB/C7T+2rS0rKYJfjPG4FN/HkKGFiUO5tILIzjFDvxxQiVC+0o/Vaz805SMmqJvXqornI71U7//+wCg==", "3AF7bhGQRt6ymcBZgZTBMoDsEtSwruSarjNG8kDnaueX"); verified.should.equal(true); done(); }); @@ -87,7 +82,7 @@ describe('ed25519 tests:', function(){ "Endpoints:\n" + "BASIC_MERKLED_API g1.duniter.tednet.fr 37.187.0.204 8999\n" + "BMAS g1.duniter.tednet.fr 9000\n" - const verified = keyring.verify(msg, "ImvQDdpGv2M6CxSnBuseM/azJhBUGzWVgQhIvb5L2oGLm2GyLk/Sbi5wkb4IjbjbQfdRPdlcx5zxaHhvZCiWAA==", "Com8rJukCozHZyFao6AheSsfDQdPApxQRnz7QYFf64mm"); + const verified = verify(msg, "ImvQDdpGv2M6CxSnBuseM/azJhBUGzWVgQhIvb5L2oGLm2GyLk/Sbi5wkb4IjbjbQfdRPdlcx5zxaHhvZCiWAA==", "Com8rJukCozHZyFao6AheSsfDQdPApxQRnz7QYFf64mm"); verified.should.equal(true); done(); }); diff --git a/test/fast/common/randomKey.js b/test/fast/crypto/randomKey.ts similarity index 63% rename from test/fast/common/randomKey.js rename to test/fast/crypto/randomKey.ts index b2a59e1ed6bbdf1960c7f09499b57c51db0c7946..c3a64cc91ee1bf6be035b63835a337da17bd03f1 100644 --- a/test/fast/common/randomKey.js +++ b/test/fast/crypto/randomKey.ts @@ -11,37 +11,31 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; -const co = require('co') -const should = require('should'); -const keyring = require('../../../app/lib/common-libs/crypto/keyring') -const naclUtil = require('../../../app/lib/common-libs/crypto/nacl-util') +import {Key, KeyGen, randomKey, verify} from "../../../app/lib/common-libs/crypto/keyring" -const enc = naclUtil.encodeBase64 -const dec = naclUtil.decodeBase64 +const should = require('should'); -let key; +let key:Key describe('Random keypair', function(){ - before(() => co(function*() { + before(async () => { // Generate the keypair - key = keyring.randomKey() - })); + key = randomKey() + }) it('good signature from generated key should be verified', function(done){ const msg = "Some message to be signed"; - const sig = keyring.KeyGen(key.publicKey, key.secretKey).signSync(msg); - const verified = keyring.verify(msg, sig, key.publicKey); + const sig = KeyGen(key.publicKey, key.secretKey).signSync(msg); + const verified = verify(msg, sig, key.publicKey); verified.should.equal(true); done(); }); it('wrong signature from generated key should NOT be verified', function(done){ const msg = "Some message to be signed"; - const cor = dec(enc(msg) + 'delta'); - const sig = keyring.KeyGen(key.publicKey, key.secretKey).signSync(msg); - const verified = keyring.verify(cor, sig, key.publicKey); + const sig = KeyGen(key.publicKey, key.secretKey).signSync(msg); + const verified = verify(msg + 'delta', sig, key.publicKey); verified.should.equal(false); done(); }); diff --git a/test/fast/dal/basic-loki.ts b/test/fast/dal/basic-loki.ts new file mode 100644 index 0000000000000000000000000000000000000000..2f26a3b84c81be50d2ca34fc03a7b52359aeea94 --- /dev/null +++ b/test/fast/dal/basic-loki.ts @@ -0,0 +1,50 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import * as assert from "assert" +import {LokiIndex} from "../../../app/lib/dal/indexDAL/loki/LokiIndex" +import {LokiProtocolIndex} from "../../../app/lib/dal/indexDAL/loki/LokiProtocolIndex" + +const loki = require('lokijs') + +interface TestEntity { + name: string + written_on: string + writtenOn: number +} + +let lokiIndex:LokiIndex<TestEntity> + +class TheIndex extends LokiProtocolIndex<TestEntity> { +} + +describe("Basic LokiJS database", () => { + + before(async () => { + lokiIndex = new TheIndex(new loki('index.db'), 'iindex', []) + await lokiIndex.triggerInit() + await lokiIndex.init() + }) + + it('should be able instanciate the index', async () => { + assert.notEqual(null, lokiIndex) + assert.notEqual(undefined, lokiIndex) + }) + + it('should be able add new records', async () => { + assert.equal(0, (await lokiIndex.findRaw()).length) + await lokiIndex.insert({ written_on: '9-ABC', writtenOn: 9, name: 'A' }) + assert.equal(1, (await lokiIndex.findRaw()).length) + }) + +}) diff --git a/test/fast/dal/iindex-loki.ts b/test/fast/dal/iindex-loki.ts new file mode 100644 index 0000000000000000000000000000000000000000..107f321933d5f660e23566d873e6ceaef267f9b3 --- /dev/null +++ b/test/fast/dal/iindex-loki.ts @@ -0,0 +1,77 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import * as assert from "assert" +import {LokiIIndex} from "../../../app/lib/dal/indexDAL/loki/LokiIIndex" + +const loki = require('lokijs') + +let lokiIndex:LokiIIndex + +describe("IIndex LokiJS", () => { + + before(async () => { + lokiIndex = new LokiIIndex(new loki('index.db')) + await lokiIndex.triggerInit() + await lokiIndex.init() + }) + + it('should be able instanciate the index', async () => { + assert.notEqual(null, lokiIndex) + assert.notEqual(undefined, lokiIndex) + }) + + it('should be able to add new records', async () => { + assert.equal(0, (await lokiIndex.findRaw()).length) + await lokiIndex.insert({ + index: 'iindex', + op: 'CREATE', + uid: 'test-uid', + pub: 'test-pub', + hash: 'test-hash', + sig: 'test-sig', + created_on: '1-HASH_1', + written_on: '2-HASH_2', + writtenOn: 2, + age: 0, + member: true, + wasMember: true, + kick: false, + wotb_id: null + }) + await lokiIndex.insert({ + index: 'iindex', + op: 'UPDATE', + uid: null, + pub: 'test-pub', + hash: null, + sig: null, + created_on: '1-HASH_1', + written_on: '3-HASH_3', + writtenOn: 3, + age: 0, + member: false, + wasMember: true, + kick: false, + wotb_id: null + }) + assert.equal(2, (await lokiIndex.findRaw()).length) + }) + + it('should be able to trim records', async () => { + assert.equal(2, (await lokiIndex.findRaw()).length) + await lokiIndex.trimRecords(4) + assert.equal(1, (await lokiIndex.findRaw()).length) + }) + +}) diff --git a/test/fast/fork-resolution-3-3.ts b/test/fast/fork-resolution-3-3.ts index 10e37d1fe9c14e68f2a5221894300e2825e01efd..1b63fa6fb677db9d1c7d2c5b3cf0016ad11d3b3e 100644 --- a/test/fast/fork-resolution-3-3.ts +++ b/test/fast/fork-resolution-3-3.ts @@ -206,7 +206,7 @@ class TestingSwitcherDao implements SwitcherDao<Block> { } - async getSandboxBlock(number: number, hash: string): Promise<Block | any> { + async getAbsoluteBlockInForkWindow(number: number, hash: string): Promise<Block | any> { return this.sbx.getBlock(number, hash) } diff --git a/test/fast/v1.0-local-index.js b/test/fast/index/v1.0-local-index.ts similarity index 78% rename from test/fast/v1.0-local-index.js rename to test/fast/index/v1.0-local-index.ts index 04772abb104223417a4029af2def869b030eed81..acfc7bcc05dba73aca70d214528795e3c42ca9d2 100644 --- a/test/fast/v1.0-local-index.js +++ b/test/fast/index/v1.0-local-index.ts @@ -11,14 +11,13 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {BlockDTO} from "../../../app/lib/dto/BlockDTO" +import {parsers} from "../../../app/lib/common-libs/parsers/index" +import {CommonConstants} from "../../../app/lib/common-libs/constants" +import {Underscore} from "../../../app/lib/common-libs/underscore" +import {IndexEntry, Indexer} from "../../../app/lib/indexer" -const _ = require('underscore'); const should = require('should'); -const parsers = require('../../app/lib/common-libs/parsers').parsers -const indexer = require('../../app/lib/indexer').Indexer -const constants = require('../../app/lib/common-libs/constants').CommonConstants -const BlockDTO = require('../../app/lib/dto/BlockDTO').BlockDTO const raw = "Version: 10\n" + "Type: Block\n" + @@ -94,11 +93,18 @@ const raw = "Version: 10\n" + describe("v1.0 Local Index", function(){ - let block, index; + let block, index:IndexEntry[] before(() => { block = parsers.parseBlock.syncWrite(raw); - index = indexer.localIndex(BlockDTO.fromJSONObject(block), { sigValidity: 100, msValidity: 40 }); + index = Indexer.localIndex(BlockDTO.fromJSONObject(block), { + sigValidity: 100, + msValidity: 40, + // We don't care about these in this test + msPeriod: 0, + sigPeriod: 0, + sigStock: 0 + }); }); it('should have 30 index entries', () => { @@ -110,15 +116,15 @@ describe("v1.0 Local Index", function(){ ********/ it('should have 4 iindex entries', () => { - _(index).where({ index: constants.I_INDEX}).should.have.length(4); + Underscore.where(index, { index: CommonConstants.I_INDEX}).should.have.length(4); }); it('should have 1 iindex CREATE entries', () => { - _(index).where({ index: constants.I_INDEX, op: constants.IDX_CREATE }).should.have.length(1); + Underscore.where(index, { index: CommonConstants.I_INDEX, op: CommonConstants.IDX_CREATE }).should.have.length(1); }); it('should have 3 iindex UPDATE entries', () => { - _(index).where({ index: constants.I_INDEX, op: constants.IDX_UPDATE }).should.have.length(3); + Underscore.where(index, { index: CommonConstants.I_INDEX, op: CommonConstants.IDX_UPDATE }).should.have.length(3); }); /********* @@ -126,15 +132,15 @@ describe("v1.0 Local Index", function(){ ********/ it('should have 5 mindex entries', () => { - _(index).where({ index: constants.M_INDEX}).should.have.length(5); + Underscore.where(index, { index: CommonConstants.M_INDEX}).should.have.length(5); }); it('should have 1 mindex CREATE entries', () => { - _(index).where({ index: constants.M_INDEX, op: constants.IDX_CREATE }).should.have.length(1); + Underscore.where(index, { index: CommonConstants.M_INDEX, op: CommonConstants.IDX_CREATE }).should.have.length(1); }); it('should have 4 mindex UPDATE entries', () => { - _(index).where({ index: constants.M_INDEX, op: constants.IDX_UPDATE }).should.have.length(4); + Underscore.where(index, { index: CommonConstants.M_INDEX, op: CommonConstants.IDX_UPDATE }).should.have.length(4); }); /********* @@ -142,15 +148,15 @@ describe("v1.0 Local Index", function(){ ********/ it('should have 5 cindex entries', () => { - _(index).where({ index: constants.C_INDEX}).should.have.length(5); + Underscore.where(index, { index: CommonConstants.C_INDEX}).should.have.length(5); }); it('should have 5 cindex CREATE entries', () => { - _(index).where({ index: constants.C_INDEX, op: constants.IDX_CREATE }).should.have.length(5); + Underscore.where(index, { index: CommonConstants.C_INDEX, op: CommonConstants.IDX_CREATE }).should.have.length(5); }); it('should have 0 cindex UPDATE entries', () => { - _(index).where({ index: constants.C_INDEX, op: constants.IDX_UPDATE }).should.have.length(0); + Underscore.where(index, { index: CommonConstants.C_INDEX, op: CommonConstants.IDX_UPDATE }).should.have.length(0); }); /********* @@ -158,15 +164,15 @@ describe("v1.0 Local Index", function(){ ********/ it('should have 16 cindex entries', () => { - _(index).where({ index: constants.S_INDEX}).should.have.length(16); + Underscore.where(index, { index: CommonConstants.S_INDEX}).should.have.length(16); }); it('should have 9 cindex CREATE entries', () => { - _(index).where({ index: constants.S_INDEX, op: constants.IDX_CREATE }).should.have.length(9); + Underscore.where(index, { index: CommonConstants.S_INDEX, op: CommonConstants.IDX_CREATE }).should.have.length(9); }); it('should have 7 cindex UPDATE entries', () => { - _(index).where({ index: constants.S_INDEX, op: constants.IDX_UPDATE }).should.have.length(7); + Underscore.where(index, { index: CommonConstants.S_INDEX, op: CommonConstants.IDX_UPDATE }).should.have.length(7); }); }); diff --git a/test/fast/block_format.js b/test/fast/misc/block-format.ts similarity index 92% rename from test/fast/block_format.js rename to test/fast/misc/block-format.ts index 345b6838bc311a9c89fd828f94bc51f40d426bdd..eb9b7356e5fde1fe68eaadcb19c13e170fe09a02 100644 --- a/test/fast/block_format.js +++ b/test/fast/misc/block-format.ts @@ -11,9 +11,9 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {parsers} from "../../../app/lib/common-libs/parsers/index" + const should = require('should'); -const parsers = require('../../app/lib/common-libs/parsers').parsers const raw = "Version: 10\n" + "Type: Block\n" + @@ -63,17 +63,9 @@ const raw = "Version: 10\n" + describe("Block format", function(){ - var parser = parsers.parseBlock; + const parser = parsers.parseBlock; - it('a valid block should be well formatted', () => parser.syncWrite(raw, { - trace: (msg, p1) => { - if (p1) { - console.log(msg, p1) - } else { - console.log(msg) - } - } - })); + it('a valid block should be well formatted', () => parser.syncWrite(raw)) describe("should be rejected", function(){ diff --git a/test/fast/misc/block-local.ts b/test/fast/misc/block-local.ts new file mode 100644 index 0000000000000000000000000000000000000000..413aff68d0eea7d34871817132954d59ac9e1b9e --- /dev/null +++ b/test/fast/misc/block-local.ts @@ -0,0 +1,122 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import {parsers} from "../../../app/lib/common-libs/parsers/index" +import {Indexer} from "../../../app/lib/indexer" +import {LOCAL_RULES_FUNCTIONS} from "../../../app/lib/rules/local_rules" +import {ALIAS} from "../../../app/lib/rules/index" +import {BLOCK_TEST_DATA} from "../../data/blocks-data" +import {BlockDTO} from "../../../app/lib/dto/BlockDTO" + +const should = require('should'); + +const conf = { + + sigQty: 1, + powZeroMin: 1, + powPeriod: 18, + incDateMin: 10, + avgGenTime: 60, + medianTimeBlocks: 20, + dt: 100, + ud0: 100, + c: 0.1 +} + +describe("Block local coherence", function(){ + + it('a valid block should be well formatted', test(ALIAS.ALL_LOCAL_BUT_POW_AND_SIGNATURE, BLOCK_TEST_DATA.VALID_ROOT)); + + describe("should be rejected", function(){ + + it('if wrong signature block', test(LOCAL_RULES_FUNCTIONS.checkBlockSignature, BLOCK_TEST_DATA.WRONG_SIGNATURE, 'Block\'s signature must match')); + it('if root block does not have Parameters', test(LOCAL_RULES_FUNCTIONS.checkParameters, BLOCK_TEST_DATA.ROOT_WITHOUT_PARAMETERS, 'Parameters must be provided for root block')); + it('if proof-of-work does not match PoWMin field', test(LOCAL_RULES_FUNCTIONS.checkProofOfWork, BLOCK_TEST_DATA.WRONG_PROOF_OF_WORK, 'Not a proof-of-work')); + it('if non-root has Parameters', test(LOCAL_RULES_FUNCTIONS.checkParameters, BLOCK_TEST_DATA.NON_ROOT_WITH_PARAMETERS, 'Parameters must not be provided for non-root block')); + it('if root block has PreviousHash', test(LOCAL_RULES_FUNCTIONS.checkPreviousHash, BLOCK_TEST_DATA.ROOT_WITH_PREVIOUS_HASH, 'PreviousHash must not be provided for root block')); + it('if root block has PreviousIssuer', test(LOCAL_RULES_FUNCTIONS.checkPreviousIssuer, BLOCK_TEST_DATA.ROOT_WITH_PREVIOUS_ISSUER, 'PreviousIssuer must not be provided for root block')); + it('if non-root block does not have PreviousHash', test(LOCAL_RULES_FUNCTIONS.checkPreviousHash, BLOCK_TEST_DATA.NON_ROOT_WITHOUT_PREVIOUS_HASH, 'PreviousHash must be provided for non-root block')); + it('if non-root block does not have PreviousIssuer', test(LOCAL_RULES_FUNCTIONS.checkPreviousIssuer, BLOCK_TEST_DATA.NON_ROOT_WITHOUT_PREVIOUS_ISSUER, 'PreviousIssuer must be provided for non-root block')); + it('a V2 block with Dividend must have UnitBase field', test(LOCAL_RULES_FUNCTIONS.checkUnitBase, BLOCK_TEST_DATA.UD_BLOCK_WIHTOUT_BASE, 'Document has unkown fields or wrong line ending format')); + it('a V3 root block must have UnitBase field', test(LOCAL_RULES_FUNCTIONS.checkUnitBase, BLOCK_TEST_DATA.V3_ROOT_BLOCK_NOBASE, 'Document has unkown fields or wrong line ending format')); + it('a V3 root block must have UnitBase field equal 0', test(LOCAL_RULES_FUNCTIONS.checkUnitBase, BLOCK_TEST_DATA.V3_ROOT_BLOCK_POSITIVE_BASE, 'UnitBase must equal 0 for root block')); + it('a block with wrong date (in past)', test(LOCAL_RULES_FUNCTIONS.checkBlockTimes, BLOCK_TEST_DATA.WRONG_DATE_LOWER, 'A block must have its Time between MedianTime and MedianTime + 1440')); + it('a block with wrong date (in future, but too far)', test(LOCAL_RULES_FUNCTIONS.checkBlockTimes, BLOCK_TEST_DATA.WRONG_DATE_HIGHER_BUT_TOO_HIGH, 'A block must have its Time between MedianTime and MedianTime + 1440')); + it('a root block with different time & medianTime should fail', test(LOCAL_RULES_FUNCTIONS.checkBlockTimes, BLOCK_TEST_DATA.WRONG_ROOT_TIMES, 'Root block must have Time equal MedianTime')); + it('a block with good date', test(LOCAL_RULES_FUNCTIONS.checkBlockTimes, BLOCK_TEST_DATA.GOOD_DATE_HIGHER)); + it('Block cannot contain wrongly signed identities', test(LOCAL_RULES_FUNCTIONS.checkIdentitiesSignature, BLOCK_TEST_DATA.WRONGLY_SIGNED_IDENTITIES, 'Identity\'s signature must match')); + it('block with colliding uids in identities', test(LOCAL_RULES_FUNCTIONS.checkIdentitiesUserIDConflict, BLOCK_TEST_DATA.COLLIDING_UIDS, 'Block must not contain twice same identity uid')); + it('a block with colliding pubkeys in identities', test(LOCAL_RULES_FUNCTIONS.checkIdentitiesPubkeyConflict, BLOCK_TEST_DATA.COLLIDING_PUBKEYS, 'Block must not contain twice same identity pubkey')); + it('a block with identities not matchin joins', test(LOCAL_RULES_FUNCTIONS.checkIdentitiesMatchJoin, BLOCK_TEST_DATA.WRONG_IDTY_MATCH_JOINS, 'Each identity must match a newcomer line with same userid and certts')); + it('Block cannot contain wrongly signed join', test(LOCAL_RULES_FUNCTIONS.checkMembershipsSignature, BLOCK_TEST_DATA.WRONGLY_SIGNED_JOIN, 'Membership\'s signature must match')); + it('Block cannot contain wrongly signed active', test(LOCAL_RULES_FUNCTIONS.checkMembershipsSignature, BLOCK_TEST_DATA.WRONGLY_SIGNED_ACTIVE, 'Membership\'s signature must match')); + it('Block cannot contain wrongly signed leave', test(LOCAL_RULES_FUNCTIONS.checkMembershipsSignature, BLOCK_TEST_DATA.WRONGLY_SIGNED_LEAVE, 'Membership\'s signature must match')); + it('Block cannot contain a same pubkey more than once in joiners', test(LOCAL_RULES_FUNCTIONS.checkPubkeyUnicity, BLOCK_TEST_DATA.MULTIPLE_JOINERS, 'Block cannot contain a same pubkey more than once in joiners, actives, leavers and excluded')); + it('Block cannot contain a same pubkey more than once in actives', test(LOCAL_RULES_FUNCTIONS.checkPubkeyUnicity, BLOCK_TEST_DATA.MULTIPLE_ACTIVES, 'Block cannot contain a same pubkey more than once in joiners, actives, leavers and excluded')); + it('Block cannot contain a same pubkey more than once in leavers', test(LOCAL_RULES_FUNCTIONS.checkPubkeyUnicity, BLOCK_TEST_DATA.MULTIPLE_LEAVES, 'Block cannot contain a same pubkey more than once in joiners, actives, leavers and excluded')); + it('Block cannot contain a same pubkey more than once in excluded', test(LOCAL_RULES_FUNCTIONS.checkPubkeyUnicity, BLOCK_TEST_DATA.MULTIPLE_EXCLUDED, 'Block cannot contain a same pubkey more than once in joiners, actives, leavers and excluded')); + it('Block cannot contain a same pubkey more than once in joiners, actives, leavers and excluded', test(LOCAL_RULES_FUNCTIONS.checkPubkeyUnicity, BLOCK_TEST_DATA.MULTIPLE_OVER_ALL, 'Block cannot contain a same pubkey more than once in joiners, actives, leavers and excluded')); + it('Block cannot have revoked key in joiners,actives,leavers', test(LOCAL_RULES_FUNCTIONS.checkMembershipUnicity, BLOCK_TEST_DATA.REVOKED_WITH_MEMBERSHIPS, 'Unicity constraint PUBLIC_KEY on MINDEX is not respected')); + it('Block cannot have revoked key duplicates', test(LOCAL_RULES_FUNCTIONS.checkRevokedUnicity, BLOCK_TEST_DATA.REVOKED_WITH_DUPLICATES, 'A single revocation per member is allowed')); + it('Block revoked keys must be in excluded', test(LOCAL_RULES_FUNCTIONS.checkRevokedAreExcluded, BLOCK_TEST_DATA.REVOKED_NOT_IN_EXCLUDED, 'A revoked member must be excluded')); + it('Block cannot contain 2 certifications from same issuer', test(LOCAL_RULES_FUNCTIONS.checkCertificationOneByIssuer, BLOCK_TEST_DATA.MULTIPLE_CERTIFICATIONS_FROM_SAME_ISSUER, 'Block cannot contain two certifications from same issuer')); + it('Block cannot contain identical certifications', test(LOCAL_RULES_FUNCTIONS.checkCertificationUnicity, BLOCK_TEST_DATA.IDENTICAL_CERTIFICATIONS, 'Block cannot contain identical certifications (A -> B)')); + it('Block cannot contain certifications concerning a leaver', test(LOCAL_RULES_FUNCTIONS.checkCertificationIsntForLeaverOrExcluded, BLOCK_TEST_DATA.LEAVER_WITH_CERTIFICATIONS, 'Block cannot contain certifications concerning leavers or excluded members')); + it('Block cannot contain certifications concerning an excluded member', test(LOCAL_RULES_FUNCTIONS.checkCertificationIsntForLeaverOrExcluded, BLOCK_TEST_DATA.EXCLUDED_WITH_CERTIFICATIONS, 'Block cannot contain certifications concerning leavers or excluded members')); + it('Block cannot contain transactions without issuers (1)', test(LOCAL_RULES_FUNCTIONS.checkTxIssuers, BLOCK_TEST_DATA.TRANSACTION_WITHOUT_ISSUERS, 'A transaction must have at least 1 issuer')); + it('Block cannot contain transactions without issuers (2)', test(LOCAL_RULES_FUNCTIONS.checkTxSources, BLOCK_TEST_DATA.TRANSACTION_WITHOUT_SOURCES, 'A transaction must have at least 1 source')); + it('Block cannot contain transactions without issuers (3)', test(LOCAL_RULES_FUNCTIONS.checkTxRecipients, BLOCK_TEST_DATA.TRANSACTION_WITHOUT_RECIPIENT, 'A transaction must have at least 1 recipient')); + it('Block cannot contain transactions with identical sources in one transaction', test(LOCAL_RULES_FUNCTIONS.checkTxSources, BLOCK_TEST_DATA.TRANSACTION_WITH_DUPLICATED_SOURCE_SINGLE_TX, 'It cannot exist 2 identical sources for transactions inside a given block')); + it('Block cannot contain transactions with identical sources in a pack of transactions', test(LOCAL_RULES_FUNCTIONS.checkTxSources, BLOCK_TEST_DATA.TRANSACTION_WITH_DUPLICATED_SOURCE_MULTIPLE_TX, 'It cannot exist 2 identical sources for transactions inside a given block')); + it('Block cannot contain transactions with empty output conditions', test(LOCAL_RULES_FUNCTIONS.checkTxRecipients, BLOCK_TEST_DATA.TRANSACTION_WITH_EMPTY_TX_CONDITIONS, 'Empty conditions are forbidden')); + it('Block cannot contain transactions with wrong total', test(LOCAL_RULES_FUNCTIONS.checkTxAmounts, BLOCK_TEST_DATA.TRANSACTION_WRONG_TOTAL, 'Transaction inputs sum must equal outputs sum')); + it('Block cannot contain transactions with wrong base transformation', test(LOCAL_RULES_FUNCTIONS.checkTxAmounts, BLOCK_TEST_DATA.TRANSACTION_WRONG_TRANSFORM, 'Transaction output base amount does not equal previous base deltas')); + it('Block cannot contain transactions with unexisting lower base in sources', test(LOCAL_RULES_FUNCTIONS.checkTxAmounts, BLOCK_TEST_DATA.TRANSACTION_WRONG_TRANSFORM_LOW_BASE, 'Transaction output base amount does not equal previous base deltas')); + it('Block cannot contain transactions with more than 100 lines', test(LOCAL_RULES_FUNCTIONS.checkTxLen, BLOCK_TEST_DATA.TRANSACTION_TOO_LONG, 'A transaction has a maximum size of 100 lines')); + it('Block cannot contain transactions with a too large output', test(LOCAL_RULES_FUNCTIONS.checkTxLen, BLOCK_TEST_DATA.OUTPUT_TOO_LONG, 'A transaction output has a maximum size of 2000 characters')); + it('Block cannot contain transactions with a too large unlock', test(LOCAL_RULES_FUNCTIONS.checkTxLen, BLOCK_TEST_DATA.UNLOCK_TOO_LONG, 'A transaction unlock has a maximum size of 2000 characters')); + it('Block cannot be refused with a good V3 transaction', test(LOCAL_RULES_FUNCTIONS.checkTxAmounts, BLOCK_TEST_DATA.TRANSACTION_V3_GOOD_AMOUNTS)); + it('Block cannot contain transactions with wrong signatures', test(LOCAL_RULES_FUNCTIONS.checkTxSignature, BLOCK_TEST_DATA.TRANSACTION_WITH_WRONG_SIGNATURES, 'Signature from a transaction must match')); + }); + +}); + + +function test (rule:any, raw:string, expectedMessage?:string) { + return async () => { + try { + let obj = parsers.parseBlock.syncWrite(raw); + let block = BlockDTO.fromJSONObject(obj); + let index = Indexer.localIndex(block, conf as any) + await rule(block, conf, index); // conf parameter is not always used + if (expectedMessage) { + throw 'Test should have thrown an error'; + } + } catch (e) { + if (!expectedMessage) { + console.error(e.stack || e); + } + if (e.uerr) { + // This is a controlled error + e.uerr.message.should.equal(expectedMessage); + } else if (e) { + // This is a controlled error + e.message.should.equal(expectedMessage); + } else { + // throw Error(e) + // Display non wrapped errors (wrapped error is an error in constants.js) + // console.error(e.stack || e); + } + } + } +} diff --git a/test/fast/misc/cfs.ts b/test/fast/misc/cfs.ts new file mode 100644 index 0000000000000000000000000000000000000000..db937d26e21806534ea77355bc40645009f0e01f --- /dev/null +++ b/test/fast/misc/cfs.ts @@ -0,0 +1,137 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import {CFSCore} from "../../../app/lib/dal/fileDALs/CFSCore" +import {MemFS} from "../../../app/lib/system/directory" + +const assert = require('assert') +const should = require('should') + +const mockFS = MemFS({ + 'B5_a': { + "A.json": '{ "text": "Content of A from B5_a" }' + }, + 'B4': { + 'B.json': '{ "text": "Content of B" }' + }, + 'B3': { + 'A.json': '{ "text": "Content of A from B3" }', + 'C.json': '{ "text": "Content of C from B3" }' + }, + 'OTHER': { + 'X.json': '{ "text": "Content of X" }' + } +}); + +describe("CFS", () => { + + const coreB3 = new CFSCore('/B3', mockFS); + const coreB4 = new CFSCore('/B4', mockFS); + const coreB5 = new CFSCore('/B5_a', mockFS); + + const rootCore = new CFSCore('/OTHER', mockFS); + + // ------------ Direct READ ------------ + + it('should have the content of A.json from B5', async () => { + const content = await coreB5.readJSON('A.json'); + content.should.have.property('text').equal('Content of A from B5_a'); + }); + + // WRITE of file /C.json + + it('should have the content of C.json modified from B5 (direct read)', async () => { + await coreB5.writeJSON('C.json', { text: 'Content of C from B5_a'}); + const content = await coreB5.readJSON('C.json'); + content.should.have.property('text').equal('Content of C from B5_a'); + }); + + // WRITE of file /D.json + + it('should have the content of D.json modified from B4 (direct read/write)', async () => { + await coreB4.writeJSON('D.json', { text: 'Content of D'}); + const content = await coreB4.readJSON('D.json'); + content.should.have.property('text').equal('Content of D'); + }); + + // REMOVE file /D.json + + it('should have the content of D.json modified from B5 (direct read/write)', async () => { + const exists = await coreB5.exists('D.json'); + const content = await coreB5.read('D.json'); + assert.equal(exists, false); + assert.equal(content, null); + }); + + // ------------ LIST ------------ + + it('should have G,H,I as files from /DIR', async () => { + await coreB3.makeTree('/DIR'); + await coreB4.makeTree('/DIR'); + await coreB5.makeTree('/DIR'); + await coreB3.writeJSON('/DIR/G.json', { text: 'Content of DIR/I'}); + await coreB4.writeJSON('/DIR/H.json', { text: 'Content of DIR/H'}); + await coreB5.writeJSON('/DIR/I.json', { text: 'Content of DIR/G'}); + (await coreB3.list('/DIR')).should.deepEqual(['G.json']); + (await coreB4.list('/DIR')).should.deepEqual(['H.json']); + (await coreB5.list('/DIR')).should.deepEqual(['I.json']); + }); + + // WRITE of file /DIR2/I.json in B4 + + it('should have I as files from /DIR2', async () => { + await coreB3.makeTree('/DIR2'); + await coreB3.writeJSON('/DIR2/I.json', { text: 'Content of DIR2/I in B4'}); + const files = await coreB3.list('/DIR2'); + files.should.have.length(1); + files.should.deepEqual(['I.json']); + // Check its contents + const contents = await coreB3.listJSON('/DIR2'); + contents.should.have.length(1); + contents.should.deepEqual([{ text: 'Content of DIR2/I in B4' }]); + }); + + // REMOVE of file /DIR2/I.json in B5 + + it('should have no files from /DIR2 after file DELETION', async () => { + await coreB3.remove('/DIR2/I.json'); + const files = await coreB3.list('/DIR2'); + files.should.have.length(0); + // Check its contents + const contents = await coreB3.listJSON('/DIR2'); + contents.should.have.length(0); + }); + + describe("Root core", () => { + + it('should have 1 file in /OTHER folder', async () => { + const files = await rootCore.list('/'); + files.should.have.length(1); + // Check its contents + const contents = await rootCore.listJSON('/'); + contents.should.have.length(1); + contents.should.deepEqual([{ text: 'Content of X' }]); + }); + + // REMOVE of file /OTHER/X.json in rootCore + + it('should have no files from /OTHER after file DELETION', async () => { + await rootCore.remove('/X.json'); + const files = await rootCore.list('/'); + files.should.have.length(0); + // Check its contents + const contents = await rootCore.listJSON('/'); + contents.should.have.length(0); + }); + }); +}); diff --git a/test/fast/database.js b/test/fast/misc/database.ts similarity index 62% rename from test/fast/database.js rename to test/fast/misc/database.ts index fcf6c5e6de26b0fa3f8667d42cf652e1675dee8c..aeda0ec89d46e47513159b548079108935e9f869 100644 --- a/test/fast/database.js +++ b/test/fast/misc/database.ts @@ -11,12 +11,10 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {SQLiteDriver} from "../../../app/lib/dal/drivers/SQLiteDriver" -const co = require('co'); -const tmp = require('tmp'); -const should = require('should'); -const SQLiteDriver = require('../../app/lib/dal/drivers/SQLiteDriver').SQLiteDriver +const tmp = require('tmp') +const should = require('should') const MEMORY = ':memory:'; const FILE = tmp.fileSync().name + '.db'; // We add an suffix to avoid Windows-locking of the file by the `tmp` module @@ -41,38 +39,38 @@ describe("SQLite driver", function() { let rows; - it('should be openable and closable on will', () => co(function*() { + it('should be openable and closable on will', async () => { const driver = new SQLiteDriver(MEMORY) - yield driver.executeSql(CREATE_TABLE_SQL); - rows = yield driver.executeAll(SELECT_FROM_TABLE, []); + await driver.executeSql(CREATE_TABLE_SQL); + rows = await driver.executeAll(SELECT_FROM_TABLE, []); rows.should.have.length(0); try { // We close the memory database, it should not remember its state - yield driver.closeConnection(); - yield driver.executeAll(SELECT_FROM_TABLE, []); + await driver.closeConnection(); + await driver.executeAll(SELECT_FROM_TABLE, []); throw 'Should have thrown an exception'; } catch (err) { err.should.have.property('message').match(/SQLITE_ERROR: no such table: duniter/) } // But if we populate it again, it will work - yield driver.executeSql(CREATE_TABLE_SQL); - rows = yield driver.executeAll(SELECT_FROM_TABLE, []); + await driver.executeSql(CREATE_TABLE_SQL); + rows = await driver.executeAll(SELECT_FROM_TABLE, []); rows.should.have.length(0); try { // We explicitely ask for destruction - yield driver.destroyDatabase(); - yield driver.executeAll(SELECT_FROM_TABLE, []); + await driver.destroyDatabase(); + await driver.executeAll(SELECT_FROM_TABLE, []); throw 'Should have thrown an exception'; } catch (err) { err.should.have.property('message').match(/SQLITE_ERROR: no such table: duniter/) } // But if we populate it again, it will work - yield driver.executeSql(CREATE_TABLE_SQL); - rows = yield driver.executeAll(SELECT_FROM_TABLE, []); + await driver.executeSql(CREATE_TABLE_SQL); + rows = await driver.executeAll(SELECT_FROM_TABLE, []); rows.should.have.length(0); - })); + }) }); describe("File", function() { @@ -80,35 +78,35 @@ describe("SQLite driver", function() { const driver = new SQLiteDriver(FILE); let rows; - it('should be able to open a new one', () => co(function*() { - yield driver.executeSql(CREATE_TABLE_SQL); - rows = yield driver.executeAll(SELECT_FROM_TABLE, []); + it('should be able to open a new one', async () => { + await driver.executeSql(CREATE_TABLE_SQL); + rows = await driver.executeAll(SELECT_FROM_TABLE, []); rows.should.have.length(0); - yield driver.closeConnection(); - })); + await driver.closeConnection(); + }) - it('should be able to reopen the file', () => co(function*() { + it('should be able to reopen the file', async () => { // Reopens the file - rows = yield driver.executeAll(SELECT_FROM_TABLE, []); + rows = await driver.executeAll(SELECT_FROM_TABLE, []); rows.should.have.length(0); - })); + }) - it('should be able to remove the file', () => co(function*() { + it('should be able to remove the file', async () => { try { // We explicitely ask for destruction - yield driver.destroyDatabase(); - yield driver.executeAll(SELECT_FROM_TABLE, []); + await driver.destroyDatabase(); + await driver.executeAll(SELECT_FROM_TABLE, []); throw 'Should have thrown an exception'; } catch (err) { err.should.have.property('message').match(/SQLITE_ERROR: no such table: duniter/) } - })); + }) - it('should be able to open the file after being removed', () => co(function*() { + it('should be able to open the file after being removed', async () => { // But if we populate it again, it will work - yield driver.executeSql(CREATE_TABLE_SQL); - rows = yield driver.executeAll(SELECT_FROM_TABLE, []); + await driver.executeSql(CREATE_TABLE_SQL); + rows = await driver.executeAll(SELECT_FROM_TABLE, []); rows.should.have.length(0); - })); - }); -}); + }) + }) +}) diff --git a/test/fast/entities.js b/test/fast/misc/entities.ts similarity index 88% rename from test/fast/entities.js rename to test/fast/misc/entities.ts index 4f5656354432f451b788c8d27d5fc0eb4f04f428..542e7d54145f2f820386918b76764076e93036db 100644 --- a/test/fast/entities.js +++ b/test/fast/misc/entities.ts @@ -11,9 +11,9 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; -let should = require('should'); -let BlockDTO = require('../../app/lib/dto/BlockDTO').BlockDTO +import {BlockDTO} from "../../../app/lib/dto/BlockDTO" + +const should = require('should'); describe('Entities', () => { diff --git a/test/fast/merkle.js b/test/fast/misc/merkle.ts similarity index 91% rename from test/fast/merkle.js rename to test/fast/misc/merkle.ts index b93e6823d69ef147d6914ff167216033cea57b87..503ad947ec8dd2d1787e027e40a85ad33b4482bf 100644 --- a/test/fast/merkle.js +++ b/test/fast/misc/merkle.ts @@ -11,15 +11,14 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; -var should = require('should'); -var assert = require('assert'); +import {MerkleDTO} from "../../../app/lib/dto/MerkleDTO" -var MerkleDTO = require('../../app/lib/dto/MerkleDTO').MerkleDTO +const should = require('should'); +const assert = require('assert'); describe("Merkle ['a', 'b', 'c', 'd', 'e']", function(){ - var m = new MerkleDTO(); + const m = new MerkleDTO() as any m.initialize(['a', 'b', 'c', 'd', 'e']); it('should have root 16E6BEB3E080910740A2923D6091618CAA9968AEAD8A52D187D725D199548E2C', function(){ @@ -53,7 +52,7 @@ describe("Merkle ['a', 'b', 'c', 'd', 'e']", function(){ describe("Merkle []", function(){ - var m = new MerkleDTO(); + const m = new MerkleDTO() as any m.initialize([]); it('should have root empty', function(){ diff --git a/test/fast/modules/bma/ddos-test.js b/test/fast/modules/bma/bma-ddos-test.ts similarity index 98% rename from test/fast/modules/bma/ddos-test.js rename to test/fast/modules/bma/bma-ddos-test.ts index 7c1ab98a148a04c54005f6f3f7e916eaaa582e1c..0a3b551f3507964d480c8ef63dca151a47b7a136 100644 --- a/test/fast/modules/bma/ddos-test.js +++ b/test/fast/modules/bma/bma-ddos-test.ts @@ -13,7 +13,6 @@ "use strict"; // const should = require('should'); -// const co = require('co'); // const limiter = require('../../app/lib/system/limiter'); // const toolbox = require('../integration/tools/toolbox'); // const TestUser = require('../integration/tools/TestUser').TestUser diff --git a/test/fast/modules/bma/limiter-test.js b/test/fast/modules/bma/bma-limiter-test.ts similarity index 98% rename from test/fast/modules/bma/limiter-test.js rename to test/fast/modules/bma/bma-limiter-test.ts index f63ae5c4a7253155a11a0970fe7483ec98ffd864..cdc113d90f5d7daeaad3e38fe83e3780ddf51162 100644 --- a/test/fast/modules/bma/limiter-test.js +++ b/test/fast/modules/bma/bma-limiter-test.ts @@ -13,7 +13,6 @@ "use strict"; // const should = require('should'); -// const co = require('co'); // const limiter = require('../lib/limiter'); // const toolbox = require('../integration/tools/toolbox'); // const TestUser = require('../integration/tools/TestUser').TestUser diff --git a/test/fast/modules/bma/module-test.js b/test/fast/modules/bma/bma-module-test.ts similarity index 67% rename from test/fast/modules/bma/module-test.js rename to test/fast/modules/bma/bma-module-test.ts index 08c5757734c6b9c4a4e24cf645a4c192585043ed..4e85f3973022d4adb69fa9abece2d2946d7001ea 100644 --- a/test/fast/modules/bma/module-test.js +++ b/test/fast/modules/bma/bma-module-test.ts @@ -11,141 +11,139 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {BmaDependency} from "../../../../app/modules/bma/index" +import {KeypairDependency} from "../../../../app/modules/keypair/index" +import {Network} from "../../../../app/modules/bma/lib/network" + const assert = require('assert'); const should = require('should'); -const co = require('co'); -const duniterBMA = require('../../../../app/modules/bma/index').BmaDependency -const duniterKeypair = require('../../../../app/modules/keypair').KeypairDependency -const network = require('../../../../app/modules/bma/lib/network').Network const duniter = require('../../../../index') -const logger = require('../../../../app/lib/logger').NewLogger() const rp = require('request-promise'); const stack = duniter.statics.minimalStack(); -stack.registerDependency(duniterKeypair, 'duniter-keypair'); -stack.registerDependency(duniterBMA, 'duniter-bma'); +stack.registerDependency(KeypairDependency, 'duniter-keypair'); +stack.registerDependency(BmaDependency, 'duniter-bma'); describe('Module usage', () => { - it('/node/summary should answer', () => co(function*() { + it('/node/summary should answer', async () => { stack.registerDependency({ duniter: { cli: [{ name: 'test1', desc: 'Unit Test execution', - onDatabaseExecute: (server, conf, program, params, startServices) => co(function*() { - yield startServices(); - }) + onDatabaseExecute: async (server:any, conf:any, program:any, params:any, startServices:any) => { + await startServices(); + } }] } }, 'duniter-automated-test'); - yield stack.executeStack(['node', 'index.js', 'test1', + await stack.executeStack(['node', 'index.js', 'test1', '--memory', '--noupnp', '--ipv4', '127.0.0.1', '--port', '10400' ]); - const json = yield rp.get({ + const json = await rp.get({ url: 'http://127.0.0.1:10400/node/summary', json: true, timeout: 1000 }); should.exist(json); json.should.have.property('duniter').property('software').equal('duniter'); - })); + }) - it('remoteipv4 should NOT be filled if remote Host is declared', () => co(function*() { + it('remoteipv4 should NOT be filled if remote Host is declared', async () => { stack.registerDependency({ duniter: { cli: [{ name: 'test2', desc: 'Unit Test execution', - onConfiguredExecute: (server, conf, program, params, startServices) => co(function*() { + onConfiguredExecute: async (server:any, conf:any, program:any, params:any, startServices:any) => { conf.should.not.have.property('remoteipv4'); conf.should.have.property('remoteipv6').equal(undefined); conf.should.have.property('remotehost').equal('localhost'); - }) + } }] } }, 'duniter-automated-test'); - yield stack.executeStack(['node', 'index.js', 'test2', + await stack.executeStack(['node', 'index.js', 'test2', '--memory', '--ipv4', '127.0.0.1', '--remoteh', 'localhost', '--port', '10400' ]); - })); + }) - it('remoteipv4 should NOT be filled if remote IPv6 is declared', () => co(function*() { + it('remoteipv4 should NOT be filled if remote IPv6 is declared', async () => { stack.registerDependency({ duniter: { cli: [{ name: 'test3', desc: 'Unit Test execution', - onConfiguredExecute: (server, conf, program, params, startServices) => co(function*() { + onConfiguredExecute: async (server:any, conf:any, program:any, params:any, startServices:any) => { conf.should.not.have.property('remoteipv4'); conf.should.not.have.property('remotehost'); conf.should.have.property('remoteipv6').equal('::1'); - }) + } }] } }, 'duniter-automated-test'); - yield stack.executeStack(['node', 'index.js', 'test3', + await stack.executeStack(['node', 'index.js', 'test3', '--memory', '--ipv4', '127.0.0.1', '--ipv6', '::1', '--port', '10400' ]); - })); + }) - it('remoteipv4 should be NOT be auto-filled if manual remoteipv4 is declared', () => co(function*() { + it('remoteipv4 should be NOT be auto-filled if manual remoteipv4 is declared', async () => { stack.registerDependency({ duniter: { cli: [{ name: 'test4', desc: 'Unit Test execution', - onConfiguredExecute: (server, conf, program, params, startServices) => co(function*() { + onConfiguredExecute: async (server:any, conf:any, program:any, params:any, startServices:any) => { conf.should.not.have.property('remotehost'); conf.should.have.property('remoteipv6').equal(undefined); conf.should.have.property('remoteipv4').equal('192.168.0.1'); - }) + } }] } }, 'duniter-automated-test'); - yield stack.executeStack(['node', 'index.js', 'test4', + await stack.executeStack(['node', 'index.js', 'test4', '--memory', '--remote4', '192.168.0.1', '--ipv4', '127.0.0.1', '--port', '10400' ]); - })); + }) - it('remoteipv4 should be filled if no remote is declared, but local IPv4 is', () => co(function*() { + it('remoteipv4 should be filled if no remote is declared, but local IPv4 is', async () => { stack.registerDependency({ duniter: { cli: [{ name: 'test5', desc: 'Unit Test execution', - onConfiguredExecute: (server, conf, program, params, startServices) => co(function*() { + onConfiguredExecute: async (server:any, conf:any, program:any, params:any, startServices:any) => { conf.should.not.have.property('remotehost'); conf.should.have.property('remoteipv6').equal(undefined); conf.should.have.property('remoteipv4').equal('127.0.0.1'); - }) + } }] } }, 'duniter-automated-test'); - yield stack.executeStack(['node', 'index.js', 'test5', + await stack.executeStack(['node', 'index.js', 'test5', '--memory', '--ipv4', '127.0.0.1', '--port', '10400' ]); - })); + }) - it('default IPv6 should not be a local one', () => co(function*() { - const ipv6 = network.getBestLocalIPv6(); + it('default IPv6 should not be a local one', async () => { + const ipv6 = Network.getBestLocalIPv6(); if (ipv6) { ipv6.should.not.match(/fe80/); } - })); -}); + }) +}) diff --git a/test/fast/modules/common/crypto.js b/test/fast/modules/common/common-crypto-test.ts similarity index 61% rename from test/fast/modules/common/crypto.js rename to test/fast/modules/common/common-crypto-test.ts index 3a275f00f4069c388899ea2832cff44ebf9240cc..092e8d1cbe3d2c3267f2952ce961815848176cdc 100644 --- a/test/fast/modules/common/crypto.js +++ b/test/fast/modules/common/common-crypto-test.ts @@ -11,28 +11,23 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; -const should = require('should'); -const co = require('co'); -const nacl = require('tweetnacl'); -const base58 = require('../../../../app/lib/common-libs').base58 -const keyring = require('../../../../app/lib/common-libs/crypto/keyring'); +import {KeyGen, verify} from "../../../../app/lib/common-libs/crypto/keyring" +import {Base58decode, Base58encode} from "../../../../app/lib/common-libs/crypto/base58" -const enc = require('../../../../app/lib/common-libs/crypto/nacl-util').encodeBase64, - dec = require('../../../../app/lib/common-libs/crypto/nacl-util').decodeBase64 +const should = require('should'); -let pub, sec, rawPub, rawSec; +let pub:Uint8Array, sec:Uint8Array, rawPub:string, rawSec:string describe('ed25519 tests:', function(){ - before(() => co(function*() { + before(async () => { // Generate the keypair - const keyPair = keyring.KeyGen('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'); - pub = base58.decode(keyPair.publicKey); - sec = base58.decode(keyPair.secretKey); - rawPub = base58.encode(pub); - rawSec = base58.encode(sec); - })); + const keyPair = KeyGen('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'); + pub = Base58decode(keyPair.publicKey); + sec = Base58decode(keyPair.secretKey); + rawPub = Base58encode(pub); + rawSec = Base58encode(sec); + }) //it('good signature from existing secret key should be verified', function(done){ // const keys = nacl.sign.scryptKeyPair.fromSecretKey(dec("TM0Imyj/ltqdtsNG7BFOD1uKMZ81q6Yk2oz27U+4pvs9QBfD6EOJWpK3CqdNG368nJgszy7ElozAzVXxKvRmDA==")); @@ -46,17 +41,16 @@ describe('ed25519 tests:', function(){ it('good signature from generated key should be verified', function(done){ const msg = "Some message to be signed"; - const sig = keyring.KeyGen(rawPub, rawSec).signSync(msg); - const verified = keyring.verify(msg, sig, rawPub); + const sig = KeyGen(rawPub, rawSec).signSync(msg); + const verified = verify(msg, sig, rawPub); verified.should.equal(true); done(); }); it('wrong signature from generated key should NOT be verified', function(done){ const msg = "Some message to be signed"; - const cor = dec(enc(msg) + 'delta'); - const sig = keyring.KeyGen(rawPub, rawSec).signSync(msg); - const verified = keyring.verify(cor, sig, rawPub); + const sig = KeyGen(rawPub, rawSec).signSync(msg); + const verified = verify(msg + 'delta', sig, rawPub); verified.should.equal(false); done(); }); diff --git a/test/fast/modules/common/peering.js b/test/fast/modules/common/common-peering-test.ts similarity index 93% rename from test/fast/modules/common/peering.js rename to test/fast/modules/common/common-peering-test.ts index 954575e2460edad6fcc9d78826f1030270239719..9f3c4cea679fac09ef580c60f307258c670af24e 100644 --- a/test/fast/modules/common/peering.js +++ b/test/fast/modules/common/common-peering-test.ts @@ -11,11 +11,11 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {PeerDTO} from "../../../../app/lib/dto/PeerDTO" +import {parsers} from "../../../../app/lib/common-libs/parsers/index" + const should = require('should'); const assert = require('assert'); -const parsers = require('../../../../app/lib/common-libs/parsers').parsers -const PeerDTO = require('../../../../app/lib/dto/PeerDTO').PeerDTO const rawPeer = "" + "Version: 10\n" + @@ -32,7 +32,7 @@ describe('Peer', function(){ describe('of some key', function(){ - var pr; + let pr:any before(function(done) { pr = PeerDTO.fromJSONObject(parsers.parsePeer.syncWrite(rawPeer)) diff --git a/test/fast/modules/common/randomKey.js b/test/fast/modules/common/common-random-key.ts similarity index 61% rename from test/fast/modules/common/randomKey.js rename to test/fast/modules/common/common-random-key.ts index 0aabe14a27086d506b0253c9be9338cd94c4bce7..172154b8d23d52953843b058a6c3343289e8e0b1 100644 --- a/test/fast/modules/common/randomKey.js +++ b/test/fast/modules/common/common-random-key.ts @@ -11,38 +11,31 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; -const should = require('should'); -const co = require('co'); -const nacl = require('tweetnacl'); -const keyring = require('../../../../app/lib/common-libs/crypto/keyring'); - +import {Key, KeyGen, randomKey, verify} from "../../../../app/lib/common-libs/crypto/keyring" -const enc = require('../../../../app/lib/common-libs/crypto/nacl-util').encodeBase64, - dec = require('../../../../app/lib/common-libs/crypto/nacl-util').decodeBase64 +const should = require('should'); -let key; +let key:Key describe('Random keypair', function(){ - before(() => co(function*() { + before(async () => { // Generate the keypair - key = keyring.randomKey() - })); + key = randomKey() + }) it('good signature from generated key should be verified', function(done){ const msg = "Some message to be signed"; - const sig = keyring.KeyGen(key.publicKey, key.secretKey).signSync(msg); - const verified = keyring.verify(msg, sig, key.publicKey); + const sig = KeyGen(key.publicKey, key.secretKey).signSync(msg); + const verified = verify(msg, sig, key.publicKey); verified.should.equal(true); done(); }); it('wrong signature from generated key should NOT be verified', function(done){ const msg = "Some message to be signed"; - const cor = dec(enc(msg) + 'delta'); - const sig = keyring.KeyGen(key.publicKey, key.secretKey).signSync(msg); - const verified = keyring.verify(cor, sig, key.publicKey); + const sig = KeyGen(key.publicKey, key.secretKey).signSync(msg); + const verified = verify(msg + 'delta', sig, key.publicKey); verified.should.equal(false); done(); }); diff --git a/test/fast/modules/common/tx_format.js b/test/fast/modules/common/common-tx-format.ts similarity index 88% rename from test/fast/modules/common/tx_format.js rename to test/fast/modules/common/common-tx-format.ts index 5b0df732bcb24ee3babcb48941951a5b00f8f060..6c958119c9a3d9e33af4a6acc578acb3979cebf6 100644 --- a/test/fast/modules/common/tx_format.js +++ b/test/fast/modules/common/common-tx-format.ts @@ -11,11 +11,11 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; -var should = require('should'); -var parsers = require('../../../../app/lib/common-libs/parsers').parsers +import {parsers} from "../../../../app/lib/common-libs/parsers/index" -var raw = "Version: 10\n" + +const should = require('should'); + +const raw = "Version: 10\n" + "Type: Transaction\n" + "Currency: test_net\n" + "Blockstamp: 3-2A27BD040B16B7AF59DDD88890E616987F4DD28AA47B9ABDBBEE46257B88E945\n" + @@ -34,7 +34,7 @@ var raw = "Version: 10\n" + describe("Transaction format", function(){ - var parser = parsers.parseTransaction; + const parser = parsers.parseTransaction; it('a valid block should be well formatted', () => parser.syncWrite(raw)); diff --git a/test/fast/modules/crawler/block_pulling.ts b/test/fast/modules/crawler/block_pulling.ts index 0abac92c7567089dd57e9b17bdd75c285c260a37..fcb3a0b0a4449ae10c55b58f1ae66078dd4fdafc 100644 --- a/test/fast/modules/crawler/block_pulling.ts +++ b/test/fast/modules/crawler/block_pulling.ts @@ -14,9 +14,9 @@ import {AbstractDAO} from "../../../../app/modules/crawler/lib/pulling" import {BlockDTO} from "../../../../app/lib/dto/BlockDTO" import {NewLogger} from "../../../../app/lib/logger" +import {Underscore} from "../../../../app/lib/common-libs/underscore" const should = require('should'); -const _ = require('underscore'); let commonConf = { switchOnHeadAdvance: 1, @@ -240,7 +240,7 @@ class mockDao extends AbstractDAO { let block = bc[number] || null; // Quantum block implementation if (block && block.quantum) { - bc[number] = _.clone(block); + bc[number] = Underscore.clone(block); bc[number].hash = 'Q' + block.hash; } return block; diff --git a/test/fast/modules/crawler/peers_garbaging.js b/test/fast/modules/crawler/crawler-peers-garbaging.ts similarity index 58% rename from test/fast/modules/crawler/peers_garbaging.js rename to test/fast/modules/crawler/crawler-peers-garbaging.ts index 4d44184db5b6171a2dff1dbb7ba8aaddb8cd9489..9bb881fc823aa43c9f632bd2e0ad711b68cac808 100644 --- a/test/fast/modules/crawler/peers_garbaging.js +++ b/test/fast/modules/crawler/crawler-peers-garbaging.ts @@ -11,14 +11,13 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; -const should = require('should'); -const co = require('co'); +import {cleanLongDownPeers} from "../../../../app/modules/crawler/lib/garbager" +import {Server} from "../../../../server" -const garbager = require('../../../../app/modules/crawler/lib/garbager') +const should = require('should'); const duniter = require('../../../../index') -let stack +let stack:any describe('Peers garbaging', () => { @@ -33,23 +32,23 @@ describe('Peers garbaging', () => { name: 'garbage', desc: 'Garbage testing', logs: false, - onDatabaseExecute: (server, conf, program, params) => co(function*() { - yield server.dal.peerDAL.savePeer({ pubkey: 'A', version: 1, currency: 'c', first_down: null, statusTS: 1485000000000, block: '2393-H' }); - yield server.dal.peerDAL.savePeer({ pubkey: 'B', version: 1, currency: 'c', first_down: 1484827199999, statusTS: 1485000000000, block: '2393-H' }); - yield server.dal.peerDAL.savePeer({ pubkey: 'C', version: 1, currency: 'c', first_down: 1484827200000, statusTS: 1485000000000, block: '2393-H' }); - yield server.dal.peerDAL.savePeer({ pubkey: 'D', version: 1, currency: 'c', first_down: 1484820000000, statusTS: 1485000000000, block: '2393-H' }); - (yield server.dal.peerDAL.sqlListAll()).should.have.length(4); + onDatabaseExecute: async (server:Server) => { + await server.dal.peerDAL.savePeer({ pubkey: 'A', version: 1, currency: 'c', first_down: null, statusTS: 1485000000000, block: '2393-H' } as any); + await server.dal.peerDAL.savePeer({ pubkey: 'B', version: 1, currency: 'c', first_down: 1484827199999, statusTS: 1485000000000, block: '2393-H' } as any); + await server.dal.peerDAL.savePeer({ pubkey: 'C', version: 1, currency: 'c', first_down: 1484827200000, statusTS: 1485000000000, block: '2393-H' } as any); + await server.dal.peerDAL.savePeer({ pubkey: 'D', version: 1, currency: 'c', first_down: 1484820000000, statusTS: 1485000000000, block: '2393-H' } as any); + (await server.dal.peerDAL.listAll()).should.have.length(4); const now = 1485000000000; - yield garbager.cleanLongDownPeers(server, now); - (yield server.dal.peerDAL.sqlListAll()).should.have.length(2); - }) + await cleanLongDownPeers(server, now); + (await server.dal.peerDAL.listAll()).should.have.length(2); + } }] } } }]); }) - it('should be able to garbage some peers', () => co(function*() { - yield stack.executeStack(['node', 'b.js', '--memory', 'garbage']); - })); -}); + it('should be able to garbage some peers', async () => { + await stack.executeStack(['node', 'b.js', '--memory', 'garbage']); + }) +}) diff --git a/test/fast/modules/keypair/crypto-test.js b/test/fast/modules/keypair/keypair-crypto-test.ts similarity index 77% rename from test/fast/modules/keypair/crypto-test.js rename to test/fast/modules/keypair/keypair-crypto-test.ts index 3ea1686dae7c8a4c7a55c90fbb393b60cfee1e23..1424c649e671eec4885987c3de6bebad613b8595 100644 --- a/test/fast/modules/keypair/crypto-test.js +++ b/test/fast/modules/keypair/keypair-crypto-test.ts @@ -11,28 +11,27 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {Scrypt} from "../../../../app/modules/keypair/lib/scrypt" + const should = require('should'); -const co = require('co'); -const scrypt = require('../../../../app/modules/keypair/lib/scrypt').Scrypt describe('Scrypt salt // key', () => { - it('abc // abc', () => co(function*() { - const pair = yield scrypt('abc', 'abc'); + it('abc // abc', async () => { + const pair = await Scrypt('abc', 'abc'); pair.should.have.property('pub').equal('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); pair.should.have.property('sec').equal('51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'); - })); + }) - it('abc // def', () => co(function*() { - const pair = yield scrypt('abc', 'def'); + it('abc // def', async () => { + const pair = await Scrypt('abc', 'def'); pair.should.have.property('pub').equal('G2CBgZBPLe6FSFUgpx2Jf1Aqsgta6iib3vmDRA1yLiqU'); pair.should.have.property('sec').equal('58LDg8QLmF5pv6Dn9h7X4yFKfMTdP8fdAiWVcyDoTRJu454fwRihCLULH4MW37zncsg4ruoTGJPZneWk22QmG1w4'); - })); + }) - it('azerty // def', () => co(function*() { - const pair = yield scrypt('azerty', 'def'); + it('azerty // def', async () => { + const pair = await Scrypt('azerty', 'def'); pair.should.have.property('pub').equal('3dbw4NYVEm5mwTH6bFrqBhan1k39qNHubkQWdrw2C5AD'); pair.should.have.property('sec').equal('4kemdi17CPkkBPnjXiPFf6oBhdGiiqhCL3R4Tuafe9THK8mzBs1evHw5r9u3f8xts2zn6VCBJYVrRMzdaEaWn5Ch'); - })); -}); + }) +}) diff --git a/test/fast/modules/keypair/module-test.js b/test/fast/modules/keypair/keypair-module-test.ts similarity index 75% rename from test/fast/modules/keypair/module-test.js rename to test/fast/modules/keypair/keypair-module-test.ts index 7ea191e6ecbad9cbccbce817a7f16dee85f30e55..2b5f5db651266070c6591a4ea30e9590f737f737 100644 --- a/test/fast/modules/keypair/module-test.js +++ b/test/fast/modules/keypair/keypair-module-test.ts @@ -11,34 +11,32 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {KeypairDependency} from "../../../../app/modules/keypair/index" + const should = require('should'); -const co = require('co'); -const keypair = require('../../../../app/modules/keypair/index').KeypairDependency -const assert = require('assert'); const duniter = require('../../../../index') describe('Module usage', () => { - it('wrong options should throw', () => co(function*() { + it('wrong options should throw', async () => { let errMessage; try { const stack = duniter.statics.minimalStack(); - stack.registerDependency(keypair, 'duniter-keypair'); - yield stack.executeStack(['node', 'index.js', 'config', '--memory', '--keyN', '2048']); + stack.registerDependency(KeypairDependency, 'duniter-keypair'); + await stack.executeStack(['node', 'index.js', 'config', '--memory', '--keyN', '2048']); } catch (e) { errMessage = e.message; } should.exist(errMessage); should.equal(errMessage, 'Missing --salt and --passwd options along with --keyN|keyr|keyp option'); - })); + }) - it('no options on brand new node should generate random key', () => co(function*() { + it('no options on brand new node should generate random key', async () => { const stack = duniter.statics.minimalStack(); - stack.registerDependency(keypair, 'duniter-keypair'); - const res = yield stack.executeStack(['node', 'index.js', 'config', '--memory']); + stack.registerDependency(KeypairDependency, 'duniter-keypair'); + const res = await stack.executeStack(['node', 'index.js', 'config', '--memory']); // This is extremely very unlikely to happen res.pair.should.have.property('pub').not.equal('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); res.pair.should.have.property('sec').not.equal('51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'); - })); -}); + }) +}) diff --git a/test/fast/protocol-brg107-udEffectiveTime.js b/test/fast/protocol-brg107-udEffectiveTime.js deleted file mode 100644 index 80a724935d3b52d639ff9a1ef787aeb87e93213d..0000000000000000000000000000000000000000 --- a/test/fast/protocol-brg107-udEffectiveTime.js +++ /dev/null @@ -1,53 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict"; -const co = require('co'); -const should = require('should'); -const indexer = require('../../app/lib/indexer').Indexer - -describe("Protocol BR_G107 - udReevalTime", function(){ - - it('root block good udReevalTime', () => co(function*(){ - const conf = { udReevalTime0: 1500000000 }; - const HEAD_1 = null; - const HEAD = { number: 0 }; - indexer.prepareUDTime(HEAD, HEAD_1, conf); - HEAD.udReevalTime.should.equal(conf.udReevalTime0); - })); - - it('block with medianTime < udReevalTime', () => co(function*(){ - const conf = { dt: 100, dtReeval: 20 }; - const HEAD_1 = { number: 59, udReevalTime: 1500000900 }; - const HEAD = { number: 60, medianTime: 1500000899 }; - indexer.prepareUDTime(HEAD, HEAD_1, conf); - HEAD.udReevalTime.should.equal(HEAD_1.udReevalTime); - })); - - it('block with medianTime == udReevalTime', () => co(function*(){ - const conf = { dt: 100, dtReeval: 20 }; - const HEAD_1 = { number: 59, udReevalTime: 1500000900 }; - const HEAD = { number: 60, medianTime: 1500000900 }; - indexer.prepareUDTime(HEAD, HEAD_1, conf); - HEAD.udReevalTime.should.equal(HEAD_1.udReevalTime + conf.dtReeval); - })); - - it('block with medianTime > udReevalTime', () => co(function*(){ - const conf = { dt: 100, dtReeval: 20 }; - const HEAD_1 = { number: 59, udReevalTime: 1500000900 }; - const HEAD = { number: 60, medianTime: 1500000901 }; - indexer.prepareUDTime(HEAD, HEAD_1, conf); - HEAD.udReevalTime.should.equal(HEAD_1.udReevalTime + conf.dtReeval); - })); - -}); diff --git a/test/fast/protocol-brg11-udTime.js b/test/fast/protocol-brg11-udTime.js deleted file mode 100644 index 9899b5b50fe7e0d2bc3cc771c33052ba33fdf76a..0000000000000000000000000000000000000000 --- a/test/fast/protocol-brg11-udTime.js +++ /dev/null @@ -1,53 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict"; -const co = require('co'); -const should = require('should'); -const indexer = require('../../app/lib/indexer').Indexer - -describe("Protocol BR_G11 - udTime", function(){ - - it('root block good udTime', () => co(function*(){ - const conf = { udTime0: 1500000000 }; - const HEAD_1 = null; - const HEAD = { number: 0 }; - indexer.prepareUDTime(HEAD, HEAD_1, conf); - HEAD.udTime.should.equal(conf.udTime0); - })); - - it('block with medianTime < udTime', () => co(function*(){ - const conf = { dt: 100 }; - const HEAD_1 = { number: 59, udTime: 1500000900 }; - const HEAD = { number: 60, medianTime: 1500000899 }; - indexer.prepareUDTime(HEAD, HEAD_1, conf); - HEAD.udTime.should.equal(HEAD_1.udTime); - })); - - it('block with medianTime == udTime', () => co(function*(){ - const conf = { dt: 100 }; - const HEAD_1 = { number: 59, udTime: 1500000900 }; - const HEAD = { number: 60, medianTime: 1500000900 }; - indexer.prepareUDTime(HEAD, HEAD_1, conf); - HEAD.udTime.should.equal(HEAD_1.udTime + conf.dt); - })); - - it('block with medianTime > udTime', () => co(function*(){ - const conf = { dt: 100 }; - const HEAD_1 = { number: 59, udTime: 1500000900 }; - const HEAD = { number: 60, medianTime: 1500000901 }; - indexer.prepareUDTime(HEAD, HEAD_1, conf); - HEAD.udTime.should.equal(HEAD_1.udTime + conf.dt); - })); - -}); diff --git a/test/fast/protocol-brg49-version.js b/test/fast/protocol-brg49-version.js deleted file mode 100644 index 151134e3a78f57056d268cbf18a102708a30f739..0000000000000000000000000000000000000000 --- a/test/fast/protocol-brg49-version.js +++ /dev/null @@ -1,47 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict"; -const co = require('co'); -const should = require('should'); -const indexer = require('../../app/lib/indexer').Indexer - -const FAIL = false; -const SUCCESS = true; - -describe("Protocol BR_G49 - Version", function(){ - - it('V13 following V12 should fail', () => co(function*(){ - const HEAD_1 = { number: 17, version: 13 }; - const HEAD = { number: 18, version: 12 }; - indexer.ruleVersion(HEAD, HEAD_1).should.equal(FAIL); - })); - - it('V14 following V12 should fail', () => co(function*(){ - const HEAD_1 = { number: 17, version: 14 }; - const HEAD = { number: 18, version: 12 }; - indexer.ruleVersion(HEAD, HEAD_1).should.equal(FAIL); - })); - - it('V13 following V14 should succeed', () => co(function*(){ - const HEAD_1 = { number: 17, version: 13 }; - const HEAD = { number: 18, version: 14 }; - indexer.ruleVersion(HEAD, HEAD_1).should.equal(SUCCESS); - })); - - it('V13 following V15 should fail', () => co(function*(){ - const HEAD_1 = { number: 17, version: 13 }; - const HEAD = { number: 18, version: 15 }; - indexer.ruleVersion(HEAD, HEAD_1).should.equal(FAIL); - })); -}); diff --git a/test/fast/protocol-brg50-blocksize.js b/test/fast/protocol-brg50-blocksize.js deleted file mode 100644 index 8d19f9e3472847526ca5feb9db26ba755d3eb595..0000000000000000000000000000000000000000 --- a/test/fast/protocol-brg50-blocksize.js +++ /dev/null @@ -1,68 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict"; -const co = require('co'); -const should = require('should'); -const indexer = require('../../app/lib/indexer').Indexer - -const FAIL = false; -const SUCCESS = true; - -describe("Protocol BR_G50 - Block size", function(){ - - it('2 for an AVG(10) should succeed', () => co(function*(){ - const HEAD = { number: 24, bsize: 2, avgBlockSize: 10 }; - indexer.ruleBlockSize(HEAD).should.equal(SUCCESS); - })); - - it('400 for an AVG(10) should succeed', () => co(function*(){ - const HEAD = { number: 24, bsize: 400, avgBlockSize: 10 }; - indexer.ruleBlockSize(HEAD).should.equal(SUCCESS); - })); - - it('499 for an AVG(10) should succeed', () => co(function*(){ - const HEAD = { number: 24, bsize: 499, avgBlockSize: 10 }; - indexer.ruleBlockSize(HEAD).should.equal(SUCCESS); - })); - - it('500 for an AVG(10) should fail', () => co(function*(){ - const HEAD = { number: 24, bsize: 500, avgBlockSize: 10 }; - indexer.ruleBlockSize(HEAD).should.equal(FAIL); - })); - - it('500 for an AVG(454) should fail', () => co(function*(){ - const HEAD = { number: 24, bsize: 500, avgBlockSize: 454 }; - indexer.ruleBlockSize(HEAD).should.equal(FAIL); - })); - - it('500 for an AVG(455) should succeed', () => co(function*(){ - const HEAD = { number: 24, bsize: 500, avgBlockSize: 455 }; - indexer.ruleBlockSize(HEAD).should.equal(SUCCESS); - })); - - it('1100 for an AVG(1000) should fail', () => co(function*(){ - const HEAD = { number: 24, bsize: 1100, avgBlockSize: 1000 }; - indexer.ruleBlockSize(HEAD).should.equal(FAIL); - })); - - it('1100 for an AVG(1001) should succeed', () => co(function*(){ - const HEAD = { number: 24, bsize: 1100, avgBlockSize: 1001 }; - indexer.ruleBlockSize(HEAD).should.equal(SUCCESS); - })); - - it('1100 for block#0 should succeed', () => co(function*(){ - const HEAD = { number: 0, bsize: 1100, avgBlockSize: 0 }; - indexer.ruleBlockSize(HEAD).should.equal(SUCCESS); - })); -}); diff --git a/test/fast/protocol-brg51-number.js b/test/fast/protocol-brg51-number.js deleted file mode 100644 index 465238ea46a067a9674f33a9e045716cd394079b..0000000000000000000000000000000000000000 --- a/test/fast/protocol-brg51-number.js +++ /dev/null @@ -1,80 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict"; -const co = require('co'); -const should = require('should'); -const indexer = require('../../app/lib/indexer').Indexer - -const FAIL = false; -const SUCCESS = true; - -describe("Protocol BR_G51 - Number", function(){ - - it('1 following 1 should fail', () => co(function*(){ - const block = { number: 1 }; - const HEAD_1 = { number: 1 }; - const HEAD = {}; - indexer.prepareNumber(HEAD, HEAD_1); - indexer.ruleNumber(block, HEAD).should.equal(FAIL); - })); - - it('1 following 0 should succeed', () => co(function*(){ - const block = { number: 1 }; - const HEAD_1 = { number: 0 }; - const HEAD = {}; - indexer.prepareNumber(HEAD, HEAD_1); - indexer.ruleNumber(block, HEAD).should.equal(SUCCESS); - })); - - it('0 following 0 should fail', () => co(function*(){ - const block = { number: 0 }; - const HEAD_1 = { number: 0 }; - const HEAD = {}; - indexer.prepareNumber(HEAD, HEAD_1); - indexer.ruleNumber(block, HEAD).should.equal(FAIL); - })); - - it('0 following nothing should succeed', () => co(function*(){ - const block = { number: 0 }; - const HEAD_1 = null; - const HEAD = {}; - indexer.prepareNumber(HEAD, HEAD_1); - indexer.ruleNumber(block, HEAD).should.equal(SUCCESS); - })); - - it('4 following nothing should fail', () => co(function*(){ - const block = { number: 4 }; - const HEAD_1 = null; - const HEAD = {}; - indexer.prepareNumber(HEAD, HEAD_1); - indexer.ruleNumber(block, HEAD).should.equal(FAIL); - })); - - it('4 following 2 should fail', () => co(function*(){ - const block = { number: 4 }; - const HEAD_1 = { number: 2 }; - const HEAD = {}; - indexer.prepareNumber(HEAD, HEAD_1); - indexer.ruleNumber(block, HEAD).should.equal(FAIL); - })); - - it('4 following 3 should succeed', () => co(function*(){ - const block = { number: 4 }; - const HEAD_1 = { number: 3 }; - const HEAD = {}; - indexer.prepareNumber(HEAD, HEAD_1); - indexer.ruleNumber(block, HEAD).should.equal(SUCCESS); - })); - -}); diff --git a/test/fast/protocol-local-rule-chained-tx-depth.ts b/test/fast/protocol-local-rule-chained-tx-depth.ts index 398f9a999cb0e0080644653f5496115d3f2d2675..7ae6e642cc8b9913ccc0bce5efd36312e5ec732b 100644 --- a/test/fast/protocol-local-rule-chained-tx-depth.ts +++ b/test/fast/protocol-local-rule-chained-tx-depth.ts @@ -13,7 +13,6 @@ import {LOCAL_RULES_HELPERS} from "../../app/lib/rules/local_rules" -const _ = require('underscore') const assert = require('assert') describe("Protocol BR_G110 - chained tx depth", () => { diff --git a/test/fast/protocol-brg106-number.js b/test/fast/protocol/protocol-brg106-number.ts similarity index 88% rename from test/fast/protocol-brg106-number.js rename to test/fast/protocol/protocol-brg106-number.ts index 334f18ab90fa645a24021737894c9e85389e2b48..8fe0242a26f88d228b1320c55a5d2df492909cc9 100644 --- a/test/fast/protocol-brg106-number.js +++ b/test/fast/protocol/protocol-brg106-number.ts @@ -11,22 +11,22 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; -const co = require('co'); +import {Map} from "../../../app/lib/common-libs/crypto/map" +import {Indexer} from "../../../app/lib/indexer" + const should = require('should'); -const indexer = require('../../app/lib/indexer').Indexer describe("Protocol BR_G106 - Garbaging", function(){ - it('An account with balance < 1,00 should be cleaned up', () => co(function*(){ - const balances = { + it('An account with balance < 1,00 should be cleaned up', async () => { + const balances:Map<{ balance: number }> = { pubkeyA: { balance: 103 }, pubkeyB: { balance: 11 + 22 + 68 }, // 101 pubkeyC: { balance: 100 }, pubkeyD: { balance: 0 }, pubkeyE: { balance: 0 } } - const sources = { + const sources:any = { pubkeyA: [ // 103 { amount: 103, base: 0, tx: 'A1', identifier: 'I1', pos: 0 } ], @@ -42,13 +42,13 @@ describe("Protocol BR_G106 - Garbaging", function(){ pubkeyE: [] } const dal = { - getWallet: (conditions) => Promise.resolve(balances[conditions]), + getWallet: (conditions:string) => Promise.resolve(balances[conditions]), sindexDAL: { - getAvailableForConditions: (conditions) => Promise.resolve(sources[conditions]) + getAvailableForConditions: (conditions:string) => Promise.resolve(sources[conditions]) } }; - const HEAD = { unitBase: 0 }; - const cleaning = yield indexer.ruleIndexGarbageSmallAccounts(HEAD, [ + const HEAD = { unitBase: 0 } as any + const cleaning = await Indexer.ruleIndexGarbageSmallAccounts(HEAD, [ // A sends 3 to D --> A keeps 100 { op: 'UPDATE', conditions: 'pubkeyA', amount: 103, base: 0, identifier: 'I1', pos: 0 }, { op: 'CREATE', conditions: 'pubkeyA', amount: 100, base: 0, identifier: 'I2', pos: 0 }, @@ -62,7 +62,7 @@ describe("Protocol BR_G106 - Garbaging", function(){ // C sends 100 to E --> C keeps 0 { op: 'UPDATE', conditions: 'pubkeyC', amount: 100, base: 0, identifier: 'I8', pos: 0 }, { op: 'CREATE', conditions: 'pubkeyE', amount: 100, base: 0, identifier: 'I9', pos: 0 } - ], dal); + ] as any, [], dal as any); cleaning.should.have.length(5); cleaning[0].should.have.property('identifier').equal('I3'); cleaning[0].should.have.property('amount').equal(3); @@ -98,5 +98,5 @@ describe("Protocol BR_G106 - Garbaging", function(){ cleaning[4].should.have.property('tx').equal(null); cleaning[4].should.have.property('consumed').equal(true); cleaning[4].should.have.property('op').equal('UPDATE'); - })); -}); + }) +}) diff --git a/test/fast/protocol/protocol-brg107-udEffectiveTime.ts b/test/fast/protocol/protocol-brg107-udEffectiveTime.ts new file mode 100644 index 0000000000000000000000000000000000000000..43176bdc124e69ba5f6600589e0f235be9408713 --- /dev/null +++ b/test/fast/protocol/protocol-brg107-udEffectiveTime.ts @@ -0,0 +1,52 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import {Indexer} from "../../../app/lib/indexer" + +const should = require('should'); + +describe("Protocol BR_G107 - udReevalTime", function(){ + + it('root block good udReevalTime', async () => { + const conf = { udReevalTime0: 1500000000 } as any + const HEAD_1 = null as any + const HEAD = { number: 0 } as any + Indexer.prepareUDTime(HEAD, HEAD_1, conf); + HEAD.udReevalTime.should.equal(conf.udReevalTime0); + }) + + it('block with medianTime < udReevalTime', async () => { + const conf = { dt: 100, dtReeval: 20 } as any + const HEAD_1 = { number: 59, udReevalTime: 1500000900 } as any + const HEAD = { number: 60, medianTime: 1500000899 } as any + Indexer.prepareUDTime(HEAD, HEAD_1, conf); + HEAD.udReevalTime.should.equal(HEAD_1.udReevalTime); + }) + + it('block with medianTime == udReevalTime', async () => { + const conf = { dt: 100, dtReeval: 20 } as any + const HEAD_1 = { number: 59, udReevalTime: 1500000900 } as any + const HEAD = { number: 60, medianTime: 1500000900 } as any + Indexer.prepareUDTime(HEAD, HEAD_1, conf); + HEAD.udReevalTime.should.equal(HEAD_1.udReevalTime + conf.dtReeval); + }) + + it('block with medianTime > udReevalTime', async () => { + const conf = { dt: 100, dtReeval: 20 } as any + const HEAD_1 = { number: 59, udReevalTime: 1500000900 } as any + const HEAD = { number: 60, medianTime: 1500000901 } as any + Indexer.prepareUDTime(HEAD, HEAD_1, conf); + HEAD.udReevalTime.should.equal(HEAD_1.udReevalTime + conf.dtReeval); + }) + +}); diff --git a/test/fast/protocol/protocol-brg11-udTime.ts b/test/fast/protocol/protocol-brg11-udTime.ts new file mode 100644 index 0000000000000000000000000000000000000000..0125226defae0105abb29909aeba6267eeb45176 --- /dev/null +++ b/test/fast/protocol/protocol-brg11-udTime.ts @@ -0,0 +1,52 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import {Indexer} from "../../../app/lib/indexer" + +const should = require('should'); + +describe("Protocol BR_G11 - udTime", function(){ + + it('root block good udTime', async () => { + const conf = { udTime0: 1500000000 } as any + const HEAD_1 = null as any + const HEAD = { number: 0 } as any + Indexer.prepareUDTime(HEAD, HEAD_1, conf); + HEAD.udTime.should.equal(conf.udTime0); + }) + + it('block with medianTime < udTime', async () => { + const conf = { dt: 100 } as any + const HEAD_1 = { number: 59, udTime: 1500000900 }as any + const HEAD = { number: 60, medianTime: 1500000899 }as any + Indexer.prepareUDTime(HEAD, HEAD_1, conf); + HEAD.udTime.should.equal(HEAD_1.udTime); + }) + + it('block with medianTime == udTime', async () => { + const conf = { dt: 100 } as any + const HEAD_1 = { number: 59, udTime: 1500000900 }as any + const HEAD = { number: 60, medianTime: 1500000900 }as any + Indexer.prepareUDTime(HEAD, HEAD_1, conf); + HEAD.udTime.should.equal(HEAD_1.udTime + conf.dt); + }) + + it('block with medianTime > udTime', async () => { + const conf = { dt: 100 }as any + const HEAD_1 = { number: 59, udTime: 1500000900 }as any + const HEAD = { number: 60, medianTime: 1500000901 }as any + Indexer.prepareUDTime(HEAD, HEAD_1, conf) + HEAD.udTime.should.equal(HEAD_1.udTime + conf.dt); + }) + +}) diff --git a/test/fast/protocol-brg13-dividend.js b/test/fast/protocol/protocol-brg13-dividend.ts similarity index 54% rename from test/fast/protocol-brg13-dividend.js rename to test/fast/protocol/protocol-brg13-dividend.ts index 66025d6ce6f67e54dc1871a49af7abda7ced5706..e5d31d9178bc81221bbb7c9eea7c6961c93dfce8 100644 --- a/test/fast/protocol-brg13-dividend.js +++ b/test/fast/protocol/protocol-brg13-dividend.ts @@ -12,49 +12,49 @@ // GNU Affero General Public License for more details. "use strict"; -const co = require('co'); +import {Indexer} from "../../../app/lib/indexer" + const should = require('should'); -const indexer = require('../../app/lib/indexer').Indexer describe("Protocol BR_G13 - dividend", function(){ - it('root block has no dividend', () => co(function*(){ - const conf = { udTime0: 1500000000, dt: 100 }; - const HEAD_1 = null; - const HEAD = { number: 0 }; - indexer.prepareUDTime(HEAD, HEAD_1, conf); - indexer.prepareDividend(HEAD, HEAD_1, conf); + it('root block has no dividend', async () => { + const conf = { udTime0: 1500000000, dt: 100 } as any + const HEAD_1 = null as any + const HEAD = { number: 0 } as any + Indexer.prepareUDTime(HEAD, HEAD_1, conf); + Indexer.prepareDividend(HEAD, HEAD_1, conf); should.equal(HEAD.dividend, null); - })); - - it('block with medianTime < udTime has no dividend', () => co(function*(){ - const conf = { dt: 100 }; - const HEAD_1 = { number: 59, udTime: 1500000900 }; - const HEAD = { number: 60, medianTime: 1500000899 }; - indexer.prepareUDTime(HEAD, HEAD_1, conf); - indexer.prepareDividend(HEAD, HEAD_1, conf); + }) + + it('block with medianTime < udTime has no dividend', async () => { + const conf = { dt: 100 } as any + const HEAD_1 = { number: 59, udTime: 1500000900 } as any + const HEAD = { number: 60, medianTime: 1500000899 } as any + Indexer.prepareUDTime(HEAD, HEAD_1, conf); + Indexer.prepareDividend(HEAD, HEAD_1, conf); HEAD.udTime.should.equal(HEAD_1.udTime); should.equal(HEAD.dividend, null); - })); - - it('block with medianTime == udTime', () => co(function*(){ - const conf = { dt: 100, dtReeval: 100, c: 0.0488 }; - const HEAD_1 = { number: 59, udTime: 1500000900, udReevalTime: 1500000900, dividend: 100, mass: 18000, massReeval: 18000, unitBase: 1 }; - const HEAD = { number: 60, medianTime: 1500000900, membersCount: 3 }; - indexer.prepareUDTime(HEAD, HEAD_1, conf); - indexer.prepareDividend(HEAD, HEAD_1, conf); + }) + + it('block with medianTime == udTime', async () => { + const conf = { dt: 100, dtReeval: 100, c: 0.0488 } as any + const HEAD_1 = { number: 59, udTime: 1500000900, udReevalTime: 1500000900, dividend: 100, mass: 18000, massReeval: 18000, unitBase: 1 } as any + const HEAD = { number: 60, medianTime: 1500000900, membersCount: 3 } as any + Indexer.prepareUDTime(HEAD, HEAD_1, conf); + Indexer.prepareDividend(HEAD, HEAD_1, conf); HEAD.udTime.should.equal(HEAD_1.udTime + conf.dt); should.equal(HEAD.dividend, 102); - })); - - it('block with medianTime > udTime', () => co(function*(){ - const conf = { dt: 100, dtReeval: 100, c: 0.0488 }; - const HEAD_1 = { number: 59, udTime: 1500000900, udReevalTime: 1500000900, dividend: 100, mass: 18000, massReeval: 18000, unitBase: 1 }; - const HEAD = { number: 60, medianTime: 1500000901, membersCount: 3 }; - indexer.prepareUDTime(HEAD, HEAD_1, conf); - indexer.prepareDividend(HEAD, HEAD_1, conf); + }) + + it('block with medianTime > udTime', async () => { + const conf = { dt: 100, dtReeval: 100, c: 0.0488 } as any + const HEAD_1 = { number: 59, udTime: 1500000900, udReevalTime: 1500000900, dividend: 100, mass: 18000, massReeval: 18000, unitBase: 1 } as any + const HEAD = { number: 60, medianTime: 1500000901, membersCount: 3 } as any + Indexer.prepareUDTime(HEAD, HEAD_1, conf); + Indexer.prepareDividend(HEAD, HEAD_1, conf); HEAD.udTime.should.equal(HEAD_1.udTime + conf.dt); should.equal(HEAD.dividend, 102); - })); + }) -}); +}) diff --git a/test/fast/protocol/protocol-brg49-version.ts b/test/fast/protocol/protocol-brg49-version.ts new file mode 100644 index 0000000000000000000000000000000000000000..44b0af7be63f33f3c4594f6192d1d197b3f1538b --- /dev/null +++ b/test/fast/protocol/protocol-brg49-version.ts @@ -0,0 +1,46 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import {Indexer} from "../../../app/lib/indexer" + +const should = require('should'); + +const FAIL = false; +const SUCCESS = true; + +describe("Protocol BR_G49 - Version", function(){ + + it('V13 following V12 should fail', async () => { + const HEAD_1 = { number: 17, version: 13 } as any + const HEAD = { number: 18, version: 12 } as any + Indexer.ruleVersion(HEAD, HEAD_1).should.equal(FAIL); + }) + + it('V14 following V12 should fail', async () => { + const HEAD_1 = { number: 17, version: 14 } as any + const HEAD = { number: 18, version: 12 } as any + Indexer.ruleVersion(HEAD, HEAD_1).should.equal(FAIL); + }) + + it('V13 following V14 should succeed', async () => { + const HEAD_1 = { number: 17, version: 13 } as any + const HEAD = { number: 18, version: 14 } as any + Indexer.ruleVersion(HEAD, HEAD_1).should.equal(SUCCESS); + }) + + it('V13 following V15 should fail', async () => { + const HEAD_1 = { number: 17, version: 13 } as any + const HEAD = { number: 18, version: 15 } as any + Indexer.ruleVersion(HEAD, HEAD_1).should.equal(FAIL); + }) +}) diff --git a/test/fast/protocol/protocol-brg50-blocksize.ts b/test/fast/protocol/protocol-brg50-blocksize.ts new file mode 100644 index 0000000000000000000000000000000000000000..3cdc44b5c90cae9817635333758bc34ba0f9ff10 --- /dev/null +++ b/test/fast/protocol/protocol-brg50-blocksize.ts @@ -0,0 +1,67 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import {Indexer} from "../../../app/lib/indexer" + +const should = require('should'); + +const FAIL = false; +const SUCCESS = true; + +describe("Protocol BR_G50 - Block size", function(){ + + it('2 for an AVG(10) should succeed', async () => { + const HEAD = { number: 24, bsize: 2, avgBlockSize: 10 } as any + Indexer.ruleBlockSize(HEAD).should.equal(SUCCESS); + }) + + it('400 for an AVG(10) should succeed', async () => { + const HEAD = { number: 24, bsize: 400, avgBlockSize: 10 } as any + Indexer.ruleBlockSize(HEAD).should.equal(SUCCESS); + }) + + it('499 for an AVG(10) should succeed', async () => { + const HEAD = { number: 24, bsize: 499, avgBlockSize: 10 } as any + Indexer.ruleBlockSize(HEAD).should.equal(SUCCESS); + }) + + it('500 for an AVG(10) should fail', async () => { + const HEAD = { number: 24, bsize: 500, avgBlockSize: 10 } as any + Indexer.ruleBlockSize(HEAD).should.equal(FAIL); + }) + + it('500 for an AVG(454) should fail', async () => { + const HEAD = { number: 24, bsize: 500, avgBlockSize: 454} as any + Indexer.ruleBlockSize(HEAD).should.equal(FAIL); + }) + + it('500 for an AVG(455) should succeed', async () => { + const HEAD = { number: 24, bsize: 500, avgBlockSize: 455} as any + Indexer.ruleBlockSize(HEAD).should.equal(SUCCESS); + }) + + it('1100 for an AVG(1000) should fail', async () => { + const HEAD = { number: 24, bsize: 1100, avgBlockSize: 1000} as any + Indexer.ruleBlockSize(HEAD).should.equal(FAIL); + }) + + it('1100 for an AVG(1001) should succeed', async () => { + const HEAD = { number: 24, bsize: 1100, avgBlockSize: 1001} as any + Indexer.ruleBlockSize(HEAD).should.equal(SUCCESS); + }) + + it('1100 for block#0 should succeed', async () => { + const HEAD = { number: 0, bsize: 1100, avgBlockSize: 0} as any + Indexer.ruleBlockSize(HEAD).should.equal(SUCCESS); + }) +}); diff --git a/test/fast/protocol/protocol-brg51-number.ts b/test/fast/protocol/protocol-brg51-number.ts new file mode 100644 index 0000000000000000000000000000000000000000..5b55b44c6196af949c6aa3338d51e5afd296759f --- /dev/null +++ b/test/fast/protocol/protocol-brg51-number.ts @@ -0,0 +1,79 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import {Indexer} from "../../../app/lib/indexer" + +const should = require('should'); + +const FAIL = false; +const SUCCESS = true; + +describe("Protocol BR_G51 - Number", function(){ + + it('1 following 1 should fail', async () => { + const block = { number: 1 } as any + const HEAD_1 = { number: 1 } as any + const HEAD = {} as any + Indexer.prepareNumber(HEAD, HEAD_1); + Indexer.ruleNumber(block, HEAD).should.equal(FAIL); + }) + + it('1 following 0 should succeed', async () => { + const block = { number: 1 } as any + const HEAD_1 = { number: 0 } as any + const HEAD = {} as any + Indexer.prepareNumber(HEAD, HEAD_1); + Indexer.ruleNumber(block, HEAD).should.equal(SUCCESS); + }) + + it('0 following 0 should fail', async () => { + const block = { number: 0 } as any + const HEAD_1 = { number: 0 } as any + const HEAD = {} as any + Indexer.prepareNumber(HEAD, HEAD_1); + Indexer.ruleNumber(block, HEAD).should.equal(FAIL); + }) + + it('0 following nothing should succeed', async () => { + const block = { number: 0 } as any + const HEAD_1 = null as any + const HEAD = {} as any + Indexer.prepareNumber(HEAD, HEAD_1); + Indexer.ruleNumber(block, HEAD).should.equal(SUCCESS); + }) + + it('4 following nothing should fail', async () => { + const block = { number: 4 } as any + const HEAD_1 = null as any + const HEAD = {} as any + Indexer.prepareNumber(HEAD, HEAD_1); + Indexer.ruleNumber(block, HEAD).should.equal(FAIL); + }) + + it('4 following 2 should fail', async () => { + const block = { number: 4 } as any + const HEAD_1 = { number: 2 } as any + const HEAD = {} as any + Indexer.prepareNumber(HEAD, HEAD_1); + Indexer.ruleNumber(block, HEAD).should.equal(FAIL); + }) + + it('4 following 3 should succeed', async () => { + const block = { number: 4 } as any + const HEAD_1 = { number: 3 } as any + const HEAD = {} as any + Indexer.prepareNumber(HEAD, HEAD_1); + Indexer.ruleNumber(block, HEAD).should.equal(SUCCESS); + }) + +}) diff --git a/test/fast/prover/pow-1-cluster.ts b/test/fast/prover/prover-pow-1-cluster.ts similarity index 92% rename from test/fast/prover/pow-1-cluster.ts rename to test/fast/prover/prover-pow-1-cluster.ts index 928e7243c5bea058905ebd797f71a1974de93a9c..da63563cc5003612d28ba12f729bb10556307752 100644 --- a/test/fast/prover/pow-1-cluster.ts +++ b/test/fast/prover/prover-pow-1-cluster.ts @@ -13,7 +13,6 @@ import {Master} from "../../../app/modules/prover/lib/powCluster" -const co = require('co') require('should') const logger = require('../../../app/lib/logger').NewLogger() @@ -33,9 +32,9 @@ describe('PoW Cluster', () => { master.nbWorkers.should.equal(0) }) - it('should answer for a basic PoW in more than 50ms (cold)', () => co(function*(){ + it('should answer for a basic PoW in more than 50ms (cold)', async () => { const start = Date.now() - yield master.proveByWorkers({ + await master.proveByWorkers({ newPoW: { block: { number: 0 @@ -57,15 +56,15 @@ describe('PoW Cluster', () => { }) const delay = Date.now() - start delay.should.be.above(50) - })) + }) it('should have an non-empty cluster after a PoW was asked', () => { master.nbWorkers.should.above(0) }) - it('should answer within 50ms for a basic PoW (warm)', () => co(function*(){ + it('should answer within 50ms for a basic PoW (warm)', async () => { const start = Date.now() - yield master.proveByWorkers({ + await master.proveByWorkers({ newPoW: { block: { number: 0 @@ -87,7 +86,7 @@ describe('PoW Cluster', () => { }) const delay = Date.now() - start delay.should.be.below(50) - })) + }) it('should be able to stop all the cores on cancel', async () => { master.proveByWorkers({ diff --git a/test/fast/prover/pow-2-engine.js b/test/fast/prover/prover-pow-2-engine.ts similarity index 75% rename from test/fast/prover/pow-2-engine.js rename to test/fast/prover/prover-pow-2-engine.ts index 9f3e0aefe847fcbc5f536282fb70dc46a5d505cc..403f822ef58ba5f3a5064c154f38539ac4074fff 100644 --- a/test/fast/prover/pow-2-engine.js +++ b/test/fast/prover/prover-pow-2-engine.ts @@ -11,23 +11,22 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {PowEngine} from "../../../app/modules/prover/lib/engine" +import {NewLogger} from "../../../app/lib/logger" -const co = require('co'); const should = require('should'); -const PowEngine = require('../../../app/modules/prover/lib/engine').PowEngine -const logger = require('../../../app/lib/logger').NewLogger() +const logger = NewLogger() describe('PoW Engine', () => { - it('should be configurable', () => co(function*(){ - const e1 = new PowEngine({ nbCores: 1 }, logger); - (yield e1.setConf({ cpu: 0.2, prefix: '34' })).should.deepEqual({ cpu: 0.2, prefix: '34' }); - yield e1.shutDown() - })); + it('should be configurable', async () => { + const e1 = new PowEngine({ nbCores: 1 } as any, logger); + (await e1.setConf({ cpu: 0.2, prefix: '34' })).should.deepEqual({ cpu: 0.2, prefix: '34' }); + await e1.shutDown() + }) - it('should be able to make a proof', () => co(function*(){ - const e1 = new PowEngine({ nbCores: 1 }, logger); + it('should be able to make a proof', async () => { + const e1 = new PowEngine({ nbCores: 1 } as any, logger); const block = { number: 35 }; const zeros = 2; const highMark = 'A'; @@ -38,7 +37,7 @@ describe('PoW Engine', () => { const forcedTime = 1; const medianTimeBlocks = 20; const avgGenTime = 5 * 60; - const proof = yield e1.prove({ + const proof = await e1.prove({ newPoW: { block, zeros, @@ -66,12 +65,12 @@ describe('PoW Engine', () => { pow: '009A52E6E2E4EA7DE950A2DA673114FA55B070EBE350D75FF0C62C6AAE9A37E5' } }); - yield e1.shutDown() - })); + await e1.shutDown() + }) - it('should be able to stop a proof', () => co(function*(){ - const e1 = new PowEngine({ nbCores: 1 }, logger); - yield e1.forceInit() + it('should be able to stop a proof', async () => { + const e1 = new PowEngine({ nbCores: 1 } as any, logger); + await e1.forceInit() const block = { number: 26 }; const zeros = 10; // Requires hundreds of thousands of tries probably const highMark = 'A'; @@ -96,10 +95,10 @@ describe('PoW Engine', () => { } } ) - yield new Promise((res) => setTimeout(res, 10)) - yield e1.cancel() - // const proof = yield proofPromise; + await new Promise((res) => setTimeout(res, 10)) + await e1.cancel() + // const proof = await proofPromise; // should.not.exist(proof); - yield e1.shutDown() - })); -}); + await e1.shutDown() + }) +}) diff --git a/test/fast/prover/pow-3-prover.js b/test/fast/prover/prover-pow-3-prover.ts similarity index 77% rename from test/fast/prover/pow-3-prover.js rename to test/fast/prover/prover-pow-3-prover.ts index 9246f8efdef34d4bc1f69b90af88d45754a1ad41..d2da6029e1b45457e9445a9103570fd74644e519 100644 --- a/test/fast/prover/pow-3-prover.js +++ b/test/fast/prover/prover-pow-3-prover.ts @@ -11,20 +11,17 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {BlockProver} from "../../../app/modules/prover/lib/blockProver" -const co = require('co') const should = require('should') -const moment = require('moment') const winston = require('winston') -const BlockProver = require('../../../app/modules/prover/lib/blockProver').BlockProver // Mute logger winston.remove(winston.transports.Console) describe('PoW block prover', () => { - let prover + let prover:BlockProver before(() => { prover = new BlockProver({ @@ -39,23 +36,23 @@ describe('PoW block prover', () => { }, push: () => {}, logger: winston - }) + } as any) }) - it('should be configurable', () => co(function*(){ - const res1 = yield prover.changeCPU(0.2) + it('should be configurable', async () => { + const res1 = await prover.changeCPU(0.2) res1.should.deepEqual({ cpu: 0.2 }) - const res2 = yield prover.changePoWPrefix('34') + const res2 = await prover.changePoWPrefix('34') res2.should.deepEqual({ prefix: '34' }) - })); + }) - it('should be able to make a proof', () => co(function*(){ + it('should be able to make a proof', async () => { const block = { number: 35, issuer: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd' } const forcedTime = 1; - const proof = yield prover.prove(block, 24, forcedTime) + const proof = await prover.prove(block, 24, forcedTime) proof.should.containEql({ version: 10, nonce: 340000000000034, @@ -79,33 +76,33 @@ describe('PoW block prover', () => { certifications: [], transactions: [] }); - })); + }) - it('should be able to use a prefix maxed at 899', () => co(function*(){ + it('should be able to use a prefix maxed at 899', async () => { const block = { number: 1, issuer: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd' } - const params = yield prover.changePoWPrefix('899') + const params = await prover.changePoWPrefix('899') params.should.deepEqual({ prefix: '899' }) const forcedTime = 1; - const proof = yield prover.prove(block, 1, forcedTime) + const proof = await prover.prove(block, 1, forcedTime) proof.nonce.should.equal(8990000000000001) String(proof.nonce).should.have.length(16) - })); + }) - it('should be able to stop a proof', () => co(function*(){ + it('should be able to stop a proof', async () => { const block = { number: 35, issuer: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd' } const forcedTime = 1; const proofPromise = prover.prove(block, 70, forcedTime) - yield new Promise((res) => setTimeout(res, 20)) - yield prover.cancel() + await new Promise((res) => setTimeout(res, 20)) + await prover.cancel() let err = '' try { - yield proofPromise + await proofPromise } catch (e) { err = e } finally { @@ -114,5 +111,5 @@ describe('PoW block prover', () => { } err.should.equal('Proof-of-work computation canceled because block received') } - })); -}); + }) +}) diff --git a/test/integration/collapse.js b/test/integration/block-generation/community-collapse.ts similarity index 62% rename from test/integration/collapse.js rename to test/integration/block-generation/community-collapse.ts index 974790da99cf8170ec0016a0bed76e93721171f5..6c9620538ca68199b9673c9941a1a038861563e3 100644 --- a/test/integration/collapse.js +++ b/test/integration/block-generation/community-collapse.ts @@ -11,16 +11,13 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {TestUser} from "../tools/TestUser" +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {BmaDependency} from "../../../app/modules/bma/index" +import {Underscore} from "../../../app/lib/common-libs/underscore" +import {shutDownEngine} from "../tools/shutdown-engine" +import {expectJSON} from "../tools/http-expect" -const co = require('co'); -const _ = require('underscore'); -const duniter = require('../../index'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const TestUser = require('./tools/TestUser').TestUser -const commit = require('./tools/commit'); -const httpTest = require('./tools/http'); -const shutDownEngine = require('./tools/shutDownEngine'); const rp = require('request-promise'); const MEMORY_MODE = true; @@ -32,18 +29,18 @@ const commonConf = { sigQty: 1 }; -let s1, cat, tac +let s1:TestingServer, cat:TestUser, tac:TestUser describe("Community collapse", function() { const now = Math.round(Date.now() / 1000); - before(function() { + before(async () => { - s1 = duniter( - '/bb11', - MEMORY_MODE, - _.extend({ + s1 = NewTestingServer( + Underscore.extend({ + name: 'bb11', + memory: MEMORY_MODE, port: '9340', pair: { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', @@ -56,19 +53,17 @@ describe("Community collapse", function() { cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); tac = new TestUser('tac', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s1 }); - return co(function *() { - yield s1.initWithDAL().then(bma).then((bmapi) => bmapi.openConnections()); - yield cat.createIdentity(); - yield tac.createIdentity(); - yield cat.join(); - yield tac.join(); - yield cat.cert(tac); - yield tac.cert(cat); - yield commit(s1)({ time: now }); - yield commit(s1)({ time: now + 10 }); - yield commit(s1)({ time: now + 10 }); - yield commit(s1)({ time: now + 10 }); - }); + await s1.initWithDAL().then(BmaDependency.duniter.methods.bma).then((bmapi) => bmapi.openConnections()); + await cat.createIdentity(); + await tac.createIdentity(); + await cat.join(); + await tac.join(); + await cat.cert(tac); + await tac.cert(cat); + await s1.commit({ time: now }); + await s1.commit({ time: now + 10 }); + await s1.commit({ time: now + 10 }); + await s1.commit({ time: now + 10 }); }); after(() => { @@ -78,7 +73,7 @@ describe("Community collapse", function() { }) it('should be handled', function() { - return httpTest.expectJSON(rp('http://127.0.0.1:9340/blockchain/block/2', { json: true }), { + return expectJSON(rp('http://127.0.0.1:9340/blockchain/block/2', { json: true }), { number: 2 }); }); diff --git a/test/integration/newcomers-shuffling.js b/test/integration/block-generation/newcomers-shuffling.ts similarity index 68% rename from test/integration/newcomers-shuffling.js rename to test/integration/block-generation/newcomers-shuffling.ts index 31fb6f1dfcda2d6393712c597e1ec7217f2bdfc4..3a62ef2b55bf83ab219bb87f31b6b42278671e0c 100644 --- a/test/integration/newcomers-shuffling.js +++ b/test/integration/block-generation/newcomers-shuffling.ts @@ -11,22 +11,22 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" -const _ = require('underscore'); -const co = require('co'); -const assert = require('assert'); -const TestUser = require('./tools/TestUser').TestUser -const commit = require('./tools/commit'); -const toolbox = require('./tools/toolbox'); +const assert = require('assert'); -let s1, cat, tac, toc, tic +let s1:TestingServer, cat:TestUser, tac:TestUser, toc:TestUser, tic:TestUser -describe("Newcomers shuffling", function() { +/** + * Thist test is a bit useless as is, because we don't check the fact that the newcomers enter in a random manner. + */ - before(() => co(function*() { +describe("Newcomers shuffling", function() { - s1 = toolbox.server({ + before(async () => { + + s1 = NewTestingServer({ pair: { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' @@ -38,17 +38,17 @@ describe("Newcomers shuffling", function() { toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); tic = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s1 }); - yield s1.prepareForNetwork(); + await s1.prepareForNetwork(); // Publishing identities - yield cat.createIdentity(); - yield tac.createIdentity(); - yield cat.cert(tac); - yield tac.cert(cat); - yield cat.join(); - yield tac.join(); - yield s1.commit() - })); + await cat.createIdentity(); + await tac.createIdentity(); + await cat.cert(tac); + await tac.cert(cat); + await cat.join(); + await tac.join(); + await s1.commit() + }) after(() => { return Promise.all([ @@ -56,16 +56,16 @@ describe("Newcomers shuffling", function() { ]) }) - it('toc and tic could join ', () => co(function*() { - yield toc.createIdentity(); - yield tic.createIdentity(); - yield toc.join(); - yield tic.join(); + it('toc and tic could join ', async () => { + await toc.createIdentity(); + await tic.createIdentity(); + await toc.join(); + await tic.join(); // same certifier for toc and tic - yield cat.cert(toc) - yield cat.cert(tic) + await cat.cert(toc) + await cat.cert(tic) // We do not known which one of toc or tic will be the first in! - yield s1.commit() - yield s1.commit() - })); -}); + await s1.commit() + await s1.commit() + }) +}) diff --git a/test/integration/block-generation/start-generate-blocks.ts b/test/integration/block-generation/start-generate-blocks.ts new file mode 100644 index 0000000000000000000000000000000000000000..04c52d83b6715b21338ec0360b79a71eb1f6b002 --- /dev/null +++ b/test/integration/block-generation/start-generate-blocks.ts @@ -0,0 +1,141 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import {PeerDTO} from "../../../app/lib/dto/PeerDTO" +import {BmaDependency} from "../../../app/modules/bma/index" +import {CrawlerDependency} from "../../../app/modules/crawler/index" +import {Underscore} from "../../../app/lib/common-libs/underscore" +import {NewTestingServer, serverWaitBlock, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {RouterDependency} from "../../../app/modules/router" +import {ProverDependency} from "../../../app/modules/prover/index" +import {until} from "../tools/test-until" +import {sync} from "../tools/test-sync" +import {shutDownEngine} from "../tools/shutdown-engine" +import {expectJSON} from "../tools/http-expect" + +const rp = require('request-promise'); + +const MEMORY_MODE = true; +const commonConf = { + bmaWithCrawler: true, + ipv4: '127.0.0.1', + remoteipv4: '127.0.0.1', + currency: 'bb', + httpLogs: true, + forksize: 0, + sigQty: 1 +}; + +let s1:TestingServer, s2:TestingServer, cat:TestUser, toc:TestUser, tic:TestUser, tuc:TestUser + +let nodeS1; +let nodeS2; + +describe("Generation", function() { + + before(async () => { + + s1 = NewTestingServer( + Underscore.extend({ + name: 'bb7', + memory: MEMORY_MODE, + port: '7790', + pair: { + pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', + sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' + }, + powDelay: 1 + }, commonConf)); + + s2 = NewTestingServer( + Underscore.extend({ + name: 'bb7_2', + memory: MEMORY_MODE, + port: '7791', + pair: { + pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', + sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F' + }, + powDelay: 1 + }, commonConf)); + + cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); + toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); + tic = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s1 }); + tuc = new TestUser('tuc', { pub: '3conGDUXdrTGbQPMQQhEC4Ubu1MCAnFrAYvUaewbUhtk', sec: '5ks7qQ8Fpkin7ycXpxQSxxjVhs8VTzpM3vEBMqM7NfC1ZiFJ93uQryDcoM93Mj77T6hDAABdeHZJDFnkDb35bgiU'}, { server: s1 }); + + let servers = [s1, s2]; + for (const server of servers) { + server._server.addEndpointsDefinitions(() => BmaDependency.duniter.methods.getMainEndpoint(server.conf)) + await server.initWithDAL(); + server.bma = await BmaDependency.duniter.methods.bma(server._server); + await server.bma.openConnections(); + RouterDependency.duniter.methods.routeToNetwork(server._server); + await server.PeeringService.generateSelfPeer(server.conf); + const prover = ProverDependency.duniter.methods.prover(server._server) + server.startBlockComputation = () => prover.startService(); + server.stopBlockComputation = () => prover.stopService(); + } + nodeS1 = CrawlerDependency.duniter.methods.contacter('127.0.0.1', s1.conf.port); + nodeS2 = CrawlerDependency.duniter.methods.contacter('127.0.0.1', s2.conf.port); + // Server 1 + await cat.createIdentity(); + await toc.createIdentity(); + await toc.cert(cat); + await cat.cert(toc); + await cat.join(); + await toc.join(); + await s1.commit(); + // Server 2 syncs block 0 + await sync(0, 0, s1._server, s2._server); + // Let each node know each other + let peer1 = await nodeS1.getPeer(); + await nodeS2.postPeer(PeerDTO.fromJSONObject(peer1).getRawSigned()); + let peer2 = await nodeS2.getPeer(); + await nodeS1.postPeer(PeerDTO.fromJSONObject(peer2).getRawSigned()); + s1.startBlockComputation(); + await until(s2, 'block', 1); + s2.startBlockComputation(); + s1.conf.powDelay = 2000; + s2.conf.powDelay = 2000; + await Promise.all([ + serverWaitBlock(s1._server, 3), + serverWaitBlock(s2._server, 3) + ]) + s1.stopBlockComputation(); + s2.stopBlockComputation(); + }); + + after(() => { + return Promise.all([ + shutDownEngine(s1), + shutDownEngine(s2) + ]) + }) + + describe("Server 1 /blockchain", function() { + + it('/current should exist', function() { + return expectJSON(rp('http://127.0.0.1:7790/blockchain/current', { json: true }), { + number: 3 + }); + }); + + it('/current should exist on other node too', function() { + return expectJSON(rp('http://127.0.0.1:7791/blockchain/current', { json: true }), { + number: 3 + }); + }); + }); +}); diff --git a/test/integration/branches2.ts b/test/integration/branches/branches2.ts similarity index 69% rename from test/integration/branches2.ts rename to test/integration/branches/branches2.ts index 2000dacc69cb11b3b7ea9404af3d27137a1379fb..0654d7e83dfa052364e540b9d9383262ce0b4f90 100644 --- a/test/integration/branches2.ts +++ b/test/integration/branches/branches2.ts @@ -11,25 +11,19 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -import {OtherConstants} from "../../app/lib/other_constants" -import {NewLogger} from "../../app/lib/logger" -import {BmaDependency} from "../../app/modules/bma/index" -import {CrawlerDependency} from "../../app/modules/crawler/index" -import {waitForkResolution, waitToHaveBlock} from "./tools/toolbox" -import {TestUser} from "./tools/TestUser"; - -const co = require('co'); -const _ = require('underscore'); -const duniter = require('../../index'); -const bma = BmaDependency.duniter.methods.bma; -const rp = require('request-promise'); -const httpTest = require('./tools/http'); -const commit = require('./tools/commit'); -const sync = require('./tools/sync'); -const shutDownEngine = require('./tools/shutDownEngine'); -const expectJSON = httpTest.expectJSON; -const expectHttpCode = httpTest.expectHttpCode; +import {BmaDependency} from "../../../app/modules/bma/index" +import {OtherConstants} from "../../../app/lib/other_constants" +import {NewLogger} from "../../../app/lib/logger" +import {Underscore} from "../../../app/lib/common-libs/underscore" +import {NewTestingServer, TestingServer, waitForkResolution, waitToHaveBlock} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {CrawlerDependency} from "../../../app/modules/crawler/index" +import {sync} from "../tools/test-sync" +import {shutDownEngine} from "../tools/shutdown-engine" +import {expectHttpCode, expectJSON} from "../tools/http-expect" + +const rp = require('request-promise'); if (OtherConstants.MUTE_LOGS_DURING_UNIT_TESTS) { NewLogger().mute(); @@ -52,18 +46,18 @@ const commonConf = { sigQty: 1 }; -let s1:any, s2:any, cat, toc +let s1:TestingServer, s2:TestingServer, cat:TestUser, toc:TestUser const now = Math.round(new Date().getTime() / 1000); describe("SelfFork", function() { - before(() => co(function *() { + before(async () => { - s1 = duniter( - '/bb4', - MEMORY_MODE, - _.extend({ + s1 = NewTestingServer( + Underscore.extend({ + name: 'bb4', + memory: MEMORY_MODE, port: '7781', pair: { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', @@ -71,10 +65,10 @@ describe("SelfFork", function() { } }, commonConf)); - s2 = duniter( - '/bb5', - MEMORY_MODE, - _.extend({ + s2 = NewTestingServer( + Underscore.extend({ + name: 'bb5', + memory: MEMORY_MODE, port: '7782', pair: { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', @@ -85,51 +79,46 @@ describe("SelfFork", function() { cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); - const commitS1 = commit(s1); - const commitS2 = commit(s2, { - time: now + 37180 - }); - - yield s1.initWithDAL().then(bma).then((bmapi:any) => bmapi.openConnections()); - yield s2.initWithDAL().then(bma).then((bmapi:any) => bmapi.openConnections()); - s1.addEndpointsDefinitions(() => BmaDependency.duniter.methods.getMainEndpoint(s1.conf)) - s2.addEndpointsDefinitions(() => BmaDependency.duniter.methods.getMainEndpoint(s2.conf)) + await s1.initWithDAL().then(BmaDependency.duniter.methods.bma).then((bmapi:any) => bmapi.openConnections()); + await s2.initWithDAL().then(BmaDependency.duniter.methods.bma).then((bmapi:any) => bmapi.openConnections()); + s1._server.addEndpointsDefinitions(() => BmaDependency.duniter.methods.getMainEndpoint(s1.conf)) + s2._server.addEndpointsDefinitions(() => BmaDependency.duniter.methods.getMainEndpoint(s2.conf)) // Server 1 - yield cat.createIdentity(); - yield toc.createIdentity(); - yield toc.cert(cat); - yield cat.cert(toc); - yield cat.join(); - yield toc.join(); - - yield commitS1({ + await cat.createIdentity(); + await toc.createIdentity(); + await toc.cert(cat); + await cat.cert(toc); + await cat.join(); + await toc.join(); + + await s1.commit({ time: now }); - yield commitS1(); - yield commitS1(); - yield commitS1(); + await s1.commit(); + await s1.commit(); + await s1.commit(); // Server 2 - yield sync(0, 2, s1, s2); - yield waitToHaveBlock(s2, 2) - let s2p = yield s2.PeeringService.peer(); - - yield commitS2(); - yield commitS2(); - yield commitS2(); - yield commitS2(); - yield commitS2(); - yield commitS2(); - yield commitS2(); - - yield s1.writePeer(s2p); + await sync(0, 2, s1._server, s2._server); + await waitToHaveBlock(s2._server, 2) + let s2p = await s2.PeeringService.peer(); + + await s2.commit({ time: now + 37180 }); // <-- block#3 is a fork block, S2 is committing another one than S1 issued + await s2.commit({ time: now + 37180 }); + await s2.commit({ time: now + 37180 }); + await s2.commit({ time: now + 37180 }); + await s2.commit({ time: now + 37180 }); + await s2.commit({ time: now + 37180 }); + await s2.commit({ time: now + 37180 }); + + await s1.writePeer(s2p); // Forking S1 from S2 - yield Promise.all([ - waitForkResolution(s1, 9), - CrawlerDependency.duniter.methods.pullBlocks(s1, s2p.pubkey) + await Promise.all([ + waitForkResolution(s1._server, 9), + CrawlerDependency.duniter.methods.pullBlocks(s1._server, s2p.pubkey) ]) - })); + }) after(() => { return Promise.all([ @@ -223,7 +212,7 @@ describe("SelfFork", function() { }); it('should have 2 branch', async () => { - const branches:any[] = await s1.BlockchainService.branches() + const branches = await s1.BlockchainService.branches() branches.should.have.length(1) }) }); @@ -252,9 +241,9 @@ describe("SelfFork", function() { }); }); - it('should have 1 branch', () => co(function*() { - const branches = yield s2.BlockchainService.branches(); + it('should have 1 branch', async () => { + const branches = await s2.BlockchainService.branches(); branches.should.have.length(1); - })); - }); -}); + }) + }) +}) diff --git a/test/integration/branches_pending_data.js b/test/integration/branches/branches_pending_data.ts similarity index 65% rename from test/integration/branches_pending_data.js rename to test/integration/branches/branches_pending_data.ts index e4fe8f9c3d16065f1d83a04c2a3469dcf43ddcef..ca29812f524ac579b7ee6c6e626d17b351263a1d 100644 --- a/test/integration/branches_pending_data.js +++ b/test/integration/branches/branches_pending_data.ts @@ -11,20 +11,14 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {Underscore} from "../../../app/lib/common-libs/underscore" +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {BmaDependency} from "../../../app/modules/bma/index" +import {shutDownEngine} from "../tools/shutdown-engine" +import {expectAnswer, expectJSON} from "../tools/http-expect" -const co = require('co'); -const _ = require('underscore'); -const duniter = require('../../index'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const TestUser = require('./tools/TestUser').TestUser const rp = require('request-promise'); -const httpTest = require('./tools/http'); -const commit = require('./tools/commit'); -const shutDownEngine = require('./tools/shutDownEngine'); - -const expectJSON = httpTest.expectJSON; -const expectAnswer = httpTest.expectAnswer; const MEMORY_MODE = true; const commonConf = { @@ -35,50 +29,45 @@ const commonConf = { sigQty: 1 }; -let s1, cat, toc, tic, tuc +let s1:TestingServer, cat:TestUser, toc:TestUser, tic:TestUser, tuc:TestUser describe("Pending data", function() { - before(function() { + before(async () => { - s1 = duniter( - '/bb6', - MEMORY_MODE, - _.extend({ - port: '7783', - pair: { - pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', - sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' - } - }, commonConf)); + s1 = NewTestingServer(Underscore.extend({ + memory: MEMORY_MODE, + name: 'bb6', + port: '7783', + pair: { + pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', + sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' + } + }, commonConf)); cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); tic = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s1 }); tuc = new TestUser('tuc', { pub: '3conGDUXdrTGbQPMQQhEC4Ubu1MCAnFrAYvUaewbUhtk', sec: '5ks7qQ8Fpkin7ycXpxQSxxjVhs8VTzpM3vEBMqM7NfC1ZiFJ93uQryDcoM93Mj77T6hDAABdeHZJDFnkDb35bgiU'}, { server: s1 }); - const commitS1 = commit(s1); - - return co(function *() { - yield s1.initWithDAL().then(bma).then((bmapi) => bmapi.openConnections()); - yield cat.createIdentity(); - yield toc.createIdentity(); - yield toc.cert(cat); - yield cat.cert(toc); - yield cat.join(); - yield toc.join(); - yield commitS1(); - yield commitS1(); - yield tic.createIdentity(); - yield cat.cert(tic); - yield toc.cert(tic); - yield tuc.createIdentity(); - yield tuc.join(); - yield commitS1(); - yield commitS1(); - yield commitS1(); - yield commitS1(); - }); + await s1.initWithDAL().then(BmaDependency.duniter.methods.bma).then((bmapi) => bmapi.openConnections()); + await cat.createIdentity(); + await toc.createIdentity(); + await toc.cert(cat); + await cat.cert(toc); + await cat.join(); + await toc.join(); + await s1.commit(); + await s1.commit(); + await tic.createIdentity(); + await cat.cert(tic); + await toc.cert(tic); + await tuc.createIdentity(); + await tuc.join(); + await s1.commit(); + await s1.commit(); + await s1.commit(); + await s1.commit(); }); after(() => { @@ -96,7 +85,7 @@ describe("Pending data", function() { }); it('should have forwarded pending identities + ceritifications of tic', function() { - return expectAnswer(rp('http://127.0.0.1:7783/wot/lookup/tic', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:7783/wot/lookup/tic', { json: true }), function(res:any) { res.should.have.property('results').length(1); res.results[0].should.have.property('uids').length(1); res.results[0].uids[0].should.have.property('others').length(2); @@ -104,7 +93,7 @@ describe("Pending data", function() { }); it('should have forwarded pending identities + ceritifications of tuc', function() { - return expectAnswer(rp('http://127.0.0.1:7783/wot/lookup/tuc', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:7783/wot/lookup/tuc', { json: true }), function(res:any) { res.should.have.property('results').length(1); res.results[0].should.have.property('uids').length(1); res.results[0].uids[0].should.have.property('others').length(0); diff --git a/test/integration/branches/branches_revert.ts b/test/integration/branches/branches_revert.ts new file mode 100644 index 0000000000000000000000000000000000000000..55c284a4358cf44e51d56227564668165321f27b --- /dev/null +++ b/test/integration/branches/branches_revert.ts @@ -0,0 +1,75 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {Underscore} from "../../../app/lib/common-libs/underscore" + +const commonConf = { + ipv4: '127.0.0.1', + currency: 'bb', + httpLogs: true, + forksize: 3, + sigQty: 1 +}; + +let s1:TestingServer, cat, toc + +describe("Revert root", function() { + + before(async () => { + + s1 = NewTestingServer(Underscore.extend({ + pair: { + pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', + sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' + }, + rootoffset: 10, + sigQty: 1, dt: 1, ud0: 120 + }, commonConf)); + + cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); + toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); + + await s1.initDalBmaConnections(); + await cat.createIdentity(); + await toc.createIdentity(); + await toc.cert(cat); + await cat.cert(toc); + await cat.join(); + await toc.join(); + await s1.commit() + }) + + it('/block/0 should exist', () => s1.expectJSON('/blockchain/block/0', { + number: 0 + })); + + it('/wot/cat should exist', () => s1.expectThat('/wot/lookup/cat', (res:any) => { + res.should.have.property('results').length(1); + res.results[0].should.have.property('uids').length(1); + res.results[0].uids[0].should.have.property('uid').equal('cat'); + res.results[0].uids[0].should.have.property('others').length(1); + })) + + it('reverting should erase everything', async () => { + await s1.revert(); + await s1.expectError('/blockchain/current', 404, 'No current block'); + await s1.expectError('/blockchain/block/0', 404, 'Block not found'); + await s1.expectError('/wot/lookup/cat', 404, 'No matching identity'); // Revert completely removes the identity + }) + + after(() => { + return s1.closeCluster() + }) +}) diff --git a/test/integration/branches_revert2.js b/test/integration/branches/branches_revert2.ts similarity index 69% rename from test/integration/branches_revert2.js rename to test/integration/branches/branches_revert2.ts index 93295937b62184b760c658afbaf20e5a555e76af..f00cf81a5f6446d48b0d0668cfcb3a905944c5f2 100644 --- a/test/integration/branches_revert2.js +++ b/test/integration/branches/branches_revert2.ts @@ -11,24 +11,18 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {BmaDependency} from "../../../app/modules/bma/index" +import {Underscore} from "../../../app/lib/common-libs/underscore" +import {ProverConstants} from "../../../app/modules/prover/lib/constants" +import {shutDownEngine} from "../tools/shutdown-engine" +import {expectAnswer, expectHttpCode, expectJSON} from "../tools/http-expect" -const co = require('co'); -const _ = require('underscore'); -const duniter = require('../../index'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const TestUser = require('./tools/TestUser').TestUser const rp = require('request-promise'); -const httpTest = require('./tools/http'); -const commit = require('./tools/commit'); -const shutDownEngine = require('./tools/shutDownEngine'); -require('../../app/modules/prover/lib/constants').ProverConstants.CORES_MAXIMUM_USE_IN_PARALLEL = 1 -require('../../app/modules/bma').BmaDependency.duniter.methods.noLimit(); // Disables the HTTP limiter - -const expectJSON = httpTest.expectJSON; -const expectHttpCode = httpTest.expectHttpCode; -const expectAnswer = httpTest.expectAnswer; +ProverConstants.CORES_MAXIMUM_USE_IN_PARALLEL = 1 +BmaDependency.duniter.methods.noLimit(); // Disables the HTTP limiter const now = 1490000000; @@ -42,16 +36,16 @@ const commonConf = { sigQty: 1 }; -let s1, cat, toc +let s1:TestingServer, cat:TestUser, toc:TestUser describe("Revert two blocks", function() { - before(function() { + before(async () => { - s1 = duniter( - '/bb11', - MEMORY_MODE, - _.extend({ + s1 = NewTestingServer( + Underscore.extend({ + name: 'bb11', + memory: MEMORY_MODE, port: '7712', pair: { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', @@ -66,20 +60,18 @@ describe("Revert two blocks", function() { cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); - return co(function *() { - yield s1.initWithDAL().then(bma).then((bmapi) => bmapi.openConnections()); - yield cat.createIdentity(); - yield toc.createIdentity(); - yield toc.cert(cat); - yield cat.cert(toc); - yield cat.join(); - yield toc.join(); - yield commit(s1)({ time: now }); - yield commit(s1)({ time: now + 1 }); - yield commit(s1)({ time: now + 1 }); - yield cat.sendP(51, toc); - yield commit(s1)({ time: now + 1 }); - }); + await s1.initWithDAL().then(BmaDependency.duniter.methods.bma).then((bmapi) => bmapi.openConnections()); + await cat.createIdentity(); + await toc.createIdentity(); + await toc.cert(cat); + await cat.cert(toc); + await cat.join(); + await toc.join(); + await s1.commit({ time: now }); + await s1.commit({ time: now + 1 }); + await s1.commit({ time: now + 1 }); + await cat.sendMoney(51, toc); + await s1.commit({ time: now + 1 }); }); after(() => { @@ -110,15 +102,15 @@ describe("Revert two blocks", function() { }); }); - it('/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd should have only UD', function() { - return expectAnswer(rp('http://127.0.0.1:7712/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'), (body) => { + it('/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd should have nothing left because of garbaging', function() { + return expectAnswer(rp('http://127.0.0.1:7712/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'), (body:string) => { let res = JSON.parse(body); res.sources.should.have.length(0) }); }); it('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo should have only UD', function() { - return expectAnswer(rp('http://127.0.0.1:7712/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo'), (body) => { + return expectAnswer(rp('http://127.0.0.1:7712/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo'), (body:string) => { let res = JSON.parse(body); res.sources.should.have.length(2); res.sources[0].should.have.property('identifier').equal('DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo'); @@ -133,18 +125,18 @@ describe("Revert two blocks", function() { }); it('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV should have only UD', function() { - return expectAnswer(rp('http://127.0.0.1:7712/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV'), (body) => { + return expectAnswer(rp('http://127.0.0.1:7712/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV'), (body:string) => { let res = JSON.parse(body); res.sources.should.have.length(0); }); }); }) - describe("after revert", () => { + describe("after revert of transaction", () => { - before(() => co(function*() { - yield s1.revert(); - })) + before(async () => { + await s1.revert(); + }) it('/block/0 should exist', function() { return expectJSON(rp('http://127.0.0.1:7712/blockchain/block/0', { json: true }), { @@ -164,7 +156,7 @@ describe("Revert two blocks", function() { }); it('/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd should have only UD', function() { - return expectAnswer(rp('http://127.0.0.1:7712/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'), (body) => { + return expectAnswer(rp('http://127.0.0.1:7712/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'), (body:string) => { let res = JSON.parse(body); res.sources.should.have.length(1); res.sources[0].should.have.property('identifier').equal('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); @@ -175,7 +167,7 @@ describe("Revert two blocks", function() { }); it('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo should have only UD', function() { - return expectAnswer(rp('http://127.0.0.1:7712/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo'), (body) => { + return expectAnswer(rp('http://127.0.0.1:7712/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo'), (body:string) => { let res = JSON.parse(body); res.sources.should.have.length(1); res.sources[0].should.have.property('identifier').equal('DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo'); @@ -186,21 +178,60 @@ describe("Revert two blocks", function() { }); it('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV should have only UD', function() { - return expectAnswer(rp('http://127.0.0.1:7712/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV'), (body) => { + return expectAnswer(rp('http://127.0.0.1:7712/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV'), (body:string) => { let res = JSON.parse(body); res.sources.should.have.length(0); }); }); }) + describe("after revert of UD", () => { + + before(async () => { + await s1.revert(); + }) + + it('/block/0 should exist', function() { + return expectJSON(rp('http://127.0.0.1:7712/blockchain/block/0', { json: true }), { + number: 0 + }); + }); + + it('/block/2 should NOT exist', function() { + return expectHttpCode(404, rp('http://127.0.0.1:7712/blockchain/block/2', { json: true })); + }); + + it('/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd should have nothing', function() { + return expectAnswer(rp('http://127.0.0.1:7712/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'), (body:string) => { + let res = JSON.parse(body); + res.sources.should.have.length(0) + }); + }); + + it('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo should have nothing', function() { + return expectAnswer(rp('http://127.0.0.1:7712/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo'), (body:string) => { + let res = JSON.parse(body); + res.sources.should.have.length(0) + }); + }); + + it('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV should have nothing', function() { + return expectAnswer(rp('http://127.0.0.1:7712/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV'), (body:string) => { + let res = JSON.parse(body); + res.sources.should.have.length(0) + }); + }); + }) + describe("commit again (but send less, to check that the account is not cleaned this time)", () => { - before(() => co(function*() { - yield s1.dal.txsDAL.sqlDeleteAll() - yield cat.sendP(19, toc); - yield s1.dal.blockDAL.exec('DELETE FROM block WHERE fork AND number = 3') - yield commit(s1)({ time: now + 1 }); - })) + before(async () => { + await s1.dal.txsDAL.removeAll() + await s1.resolveExistingBlock(1) // UD block + await cat.sendMoney(19, toc); + await s1.dal.blockDAL.removeBlock('DELETE FROM block WHERE fork AND number = 3') + await s1.commit({ time: now + 1 }); + }) it('/block/0 should exist', function() { return expectJSON(rp('http://127.0.0.1:7712/blockchain/block/0', { json: true }), { @@ -223,7 +254,7 @@ describe("Revert two blocks", function() { }); it('/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd should have only UD', function() { - return expectAnswer(rp('http://127.0.0.1:7712/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'), (body) => { + return expectAnswer(rp('http://127.0.0.1:7712/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'), (body:string) => { let res = JSON.parse(body); res.sources.should.have.length(1); res.sources[0].should.have.property('identifier').equal('7F951D4B73FB65995A1F343366A8CD3B0C76028120FD590170B251EB109926FB'); @@ -234,7 +265,7 @@ describe("Revert two blocks", function() { }); it('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo should have only UD', function() { - return expectAnswer(rp('http://127.0.0.1:7712/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo'), (body) => { + return expectAnswer(rp('http://127.0.0.1:7712/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo'), (body:string) => { let res = JSON.parse(body); res.sources.should.have.length(2); res.sources[0].should.have.property('identifier').equal('DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo'); @@ -249,7 +280,7 @@ describe("Revert two blocks", function() { }); it('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV should have only UD', function() { - return expectAnswer(rp('http://127.0.0.1:7712/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV'), (body) => { + return expectAnswer(rp('http://127.0.0.1:7712/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV'), (body:string) => { let res = JSON.parse(body); res.sources.should.have.length(0); }); diff --git a/test/integration/branches_revert_balance.js b/test/integration/branches/branches_revert_balance.ts similarity index 51% rename from test/integration/branches_revert_balance.js rename to test/integration/branches/branches_revert_balance.ts index b586b053ecb079cd2a5b71adec5227f766e5841e..8839fb3533d72858a7ed38d47451f53765455122 100644 --- a/test/integration/branches_revert_balance.js +++ b/test/integration/branches/branches_revert_balance.ts @@ -11,16 +11,15 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict" +import {simpleNodeWith2Users, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" -const co = require('co') const should = require('should') -const toolbox = require('./tools/toolbox') describe("Revert balance", () => { const now = 1480000000 - let s1, cat, tac + let s1:TestingServer, cat:TestUser, tac:TestUser const conf = { nbCores: 1, @@ -31,65 +30,65 @@ describe("Revert balance", () => { medianTimeBlocks: 1 // The medianTime always equals previous block's medianTime } - before(() => co(function*() { - const res1 = yield toolbox.simpleNodeWith2Users(conf) + before(async () => { + const res1 = await simpleNodeWith2Users(conf) s1 = res1.s1 cat = res1.cat tac = res1.tac - yield s1.commit({ time: now }) - yield s1.commit({ time: now + 1 }) - yield s1.commit({ time: now + 1 }) - })) + await s1.commit({ time: now }) + await s1.commit({ time: now + 1 }) + await s1.commit({ time: now + 1 }) + }) - it('cat and tac should have 200 units', () => co(function*() { - yield s1.expect('/tx/sources/' + cat.pub, (res) => { + it('cat and tac should have 200 units', async () => { + await s1.expect('/tx/sources/' + cat.pub, (res:any) => { res.sources.should.have.length(2) }) - yield s1.expect('/tx/sources/' + tac.pub, (res) => { + await s1.expect('/tx/sources/' + tac.pub, (res:any) => { res.sources.should.have.length(2) }) - })) + }) - it('cat should be able to send 60 units to tac', () => co(function*() { - yield cat.send(60, tac) - yield s1.commit({ time: now + 1 }) - yield s1.expect('/tx/sources/' + cat.pub, (res) => { + it('cat should be able to send 60 units to tac', async () => { + await cat.sendMoney(60, tac) + await s1.commit({ time: now + 1 }) + await s1.expect('/tx/sources/' + cat.pub, (res:any) => { res.sources.should.have.length(2) }) - yield s1.expect('/tx/sources/' + tac.pub, (res) => { + await s1.expect('/tx/sources/' + tac.pub, (res:any) => { res.sources.should.have.length(3) }) - const block = yield s1.dal.blockDAL.getBlock(3) - // yield s1.writeBlock(block) - })) + const block = await s1.dal.blockDAL.getBlock(3) + // await s1.writeBlock(block) + }) - it('revert: cat and tac should have 100 units', () => co(function*() { - yield s1.revert(); - yield s1.expect('/tx/sources/' + cat.pub, (res) => { + it('revert: cat and tac should have 100 units', async () => { + await s1.revert(); + await s1.expect('/tx/sources/' + cat.pub, (res:any) => { res.sources.should.have.length(2) }) - yield s1.expect('/tx/sources/' + tac.pub, (res) => { + await s1.expect('/tx/sources/' + tac.pub, (res:any) => { res.sources.should.have.length(2) }) - })) + }) - it('cat should be able to RE-send 60 units to tac', () => co(function*() { - const txsPending = yield s1.dal.txsDAL.getAllPending(1) - yield s1.dal.blockDAL.exec('DELETE FROM block WHERE fork AND number = 3') + it('cat should be able to RE-send 60 units to tac', async () => { + const txsPending = await s1.dal.txsDAL.getAllPending(1) + await s1.dal.blockDAL.removeForkBlock(3) txsPending.should.have.length(1) - yield s1.commit({ time: now + 1 }) - yield s1.expect('/tx/sources/' + cat.pub, (res) => { + await s1.commit({ time: now + 1 }) + await s1.expect('/tx/sources/' + cat.pub, (res:any) => { // Should have 2 sources: // * the 2nd UD = 100 // * the rest of the 1st UD - the money sent (60) = 40 res.sources.should.have.length(2) }) - yield s1.expect('/tx/sources/' + tac.pub, (res) => { + await s1.expect('/tx/sources/' + tac.pub, (res:any) => { res.sources.should.have.length(3) }) - const block = yield s1.dal.blockDAL.getBlock(3) - // yield s1.writeBlock(block) - })) + const block = await s1.dal.blockDAL.getBlock(3) + // await s1.writeBlock(block) + }) after(() => { return s1.closeCluster() diff --git a/test/integration/branches/branches_revert_memberships.ts b/test/integration/branches/branches_revert_memberships.ts new file mode 100644 index 0000000000000000000000000000000000000000..fdaa44e50261dbf4fd4b9d5ef889d133b6828f77 --- /dev/null +++ b/test/integration/branches/branches_revert_memberships.ts @@ -0,0 +1,254 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {FullIindexEntry} from "../../../app/lib/indexer" + +const should = require('should'); + +let s1:TestingServer, i1:TestUser, i2:TestUser, i3:TestUser + +describe("Revert memberships", function() { + + const now = 1482000000; + + before(async () => { + + s1 = NewTestingServer({ + memory: true, + msValidity: 14, + pair: { + pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', + sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' + } + }); + + i1 = new TestUser('i1', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); + i2 = new TestUser('i2', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s1 }); + i3 = new TestUser('i3', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); + + await s1.initDalBmaConnections(); + + await i1.createIdentity(); + await i2.createIdentity(); + + await i1.cert(i2); + await i2.cert(i1); + + await i1.join(); + await i2.join(); + + await s1.commit({ time: now }); + await s1.expect('/blockchain/current', (res:any) => { res.number.should.equal(0); (res.medianTime - now).should.equal(0); }); + await s1.commit({ time: now + 15 }); + await s1.expect('/blockchain/current', (res:any) => { res.number.should.equal(1); (res.medianTime - now).should.equal(0); }); + + await shouldHavePendingMS(0); + await i3.createIdentity(); + await i1.cert(i3); + await shouldBeFreshlyCreated(); + await i3.join(); + await shouldHavePendingMS(1); + await shouldBeJoining(); + await s1.commit({ time: now + 15 }); + await s1.expect('/blockchain/current', (res:any) => { res.number.should.equal(2); (res.medianTime - now).should.equal(7); }); + await shouldHaveJoined(); + await shouldHavePendingMS(0); + }) + + it('should exist 3 members', async () => s1.expect('/blockchain/current', (res:any) => { + res.should.have.property('membersCount').equal(3); + })) + + it('should exist a renew', async () => { + await i3.join(); // Renew + await shouldHavePendingMS(1); + await s1.commit({ time: now + 15 }); + await s1.expect('/blockchain/current', (res:any) => { res.number.should.equal(3); (res.medianTime - now).should.equal(10); }); + await s1.expect('/blockchain/current', (res:any) => { + res.should.have.property('membersCount').equal(3); + res.should.have.property('actives').length(1); + }); + await shouldBeRenewed(); + await shouldHavePendingMS(0); + }) + + it('should exist 2 other renew', async () => { + await s1.commit({ time: now + 15 }); + // await s1.expect('/blockchain/current', (res) => { res.number.should.equal(4); (res.medianTime - now).should.equal(11); }); + await i1.join(); // Renew + await i2.join(); // Renew + await shouldHavePendingMS(2); + await s1.commit({ time: now + 15 }); + // await s1.expect('/blockchain/current', (res) => { res.number.should.equal(5); (res.medianTime - now).should.equal(21); }); + await s1.expect('/blockchain/current', (res:any) => { + res.should.have.property('membersCount').equal(3); + res.should.have.property('actives').length(2); + }); + await shouldBeRenewed(); + await shouldHavePendingMS(0); + }) + + it('should exist a leaver', async () => { + await i3.leave(); + await s1.commit({ time: now + 80 }); + await s1.expect('/blockchain/current', (res:any) => { + // (res.medianTime - now).should.equal(27); + res.should.have.property('membersCount').equal(3); + res.should.have.property('leavers').length(1); + }); + await shouldBeLeaving(); + await shouldHavePendingMS(0); + }) + + it('should exist a kicked member', async () => { + await s1.commit({ time: now + 25 }); + // await s1.expect('/blockchain/current', (res) => { res.number.should.equal(7); (res.medianTime - now).should.equal(25); }); + // await s1.commit({ time: now + 30 }); + // await s1.expect('/blockchain/current', (res) => { res.number.should.equal(8); (res.medianTime - now).should.equal(18); }); + await shouldBeBeingKicked(); + await s1.commit({ time: now + 30 }); + await s1.expect('/blockchain/current', (res:any) => { + res.should.have.property('membersCount').equal(2); + res.should.have.property('excluded').length(1); + }); + // Should: + // * unset "to be kicked" + // * not be a member anymore + await shouldHaveBeenKicked(); + await shouldHavePendingMS(0); + }) + + it('a kicked member should be able to join back', async () => { + await i3.join(); + await s1.commit({ time: now + 30 }); + await shouldHaveComeBack(); + await shouldHavePendingMS(0); + }) + + it('revert the join back', async () => { + await s1.revert(); + await shouldHaveBeenKicked(); + await shouldHavePendingMS(0); // Undone memberships are lost + }) + + it('revert excluded member', async () => { + await s1.revert(); + await shouldBeBeingKicked(); + await shouldHavePendingMS(0); // Undone memberships are lost + }) + + it('revert being kicked', async () => { + await s1.revert(); + await shouldBeLeaving(); + await shouldHavePendingMS(0); // Undone memberships are lost + }) + + it('revert leaving', async () => { + await s1.revert(); + await shouldBeRenewed(); + await shouldHavePendingMS(0); // Undone memberships are lost + }) + + it('revert 2 neutral blocks for i3', async () => { + await s1.revert(); + await shouldBeRenewed(); + await shouldHavePendingMS(0); // Undone memberships are lost + await s1.revert(); + await shouldBeRenewed(); + await shouldHavePendingMS(0); // Undone memberships are lost + }) + + it('revert renewal block', async () => { + await s1.revert(); + await shouldHaveJoined(); + await shouldHavePendingMS(0); // Undone memberships are lost + }) + + it('revert join block', async () => { + await s1.revert(); + await shouldHavePendingMS(0); // Undone memberships are lost + }) + + after(async () => { + return s1.closeCluster() + }) + + /********* + * + * Identity state testing functions + * + ********/ + + async function shouldHavePendingMS(number:number) { + const pendingIN = await s1.dal.msDAL.getPendingIN(); + const pendingOUT = await s1.dal.msDAL.getPendingOUT(); + pendingIN.concat(pendingOUT).should.have.length(number); + } + + async function shouldBeFreshlyCreated() { + const idty = (await s1.dal.idtyDAL.searchThoseMatching(i3.pub))[0]; + idty.should.have.property('wasMember').equal(false); + idty.should.have.property('written').equal(false); + idty.should.have.property('kick').equal(false); + idty.should.have.property('member').equal(false); + } + + async function shouldBeJoining() { + return shouldBeFreshlyCreated(); + } + + async function shouldHaveJoined() { + const idty = await s1.dal.iindexDAL.getFromPubkey(i3.pub) as FullIindexEntry + idty.should.have.property('wasMember').equal(true); + idty.should.have.property('kick').equal(false); + idty.should.have.property('member').equal(true); + } + + async function shouldBeRenewed() { + const idty = await s1.dal.iindexDAL.getFromPubkey(i3.pub) as FullIindexEntry + idty.should.have.property('wasMember').equal(true); + idty.should.have.property('kick').equal(false); + idty.should.have.property('member').equal(true); + } + + async function shouldBeLeaving() { + const idty = await s1.dal.iindexDAL.getFromPubkey(i3.pub) as FullIindexEntry + idty.should.have.property('wasMember').equal(true); + idty.should.have.property('kick').equal(false); + idty.should.have.property('member').equal(true); + } + + async function shouldBeBeingKicked() { + // Should be set as kicked now + const idty = await s1.dal.iindexDAL.getFromPubkey(i3.pub) as FullIindexEntry + idty.should.have.property('wasMember').equal(true); + idty.should.have.property('kick').equal(true); + idty.should.have.property('member').equal(true); + } + + async function shouldHaveBeenKicked() { + const idty = await s1.dal.iindexDAL.getFromPubkey(i3.pub) as FullIindexEntry + idty.should.have.property('wasMember').equal(true); + idty.should.have.property('kick').equal(false); + idty.should.have.property('member').equal(false); + } + + async function shouldHaveComeBack() { + let idty = await s1.dal.iindexDAL.getFromPubkey(i3.pub) as FullIindexEntry + idty.should.have.property('wasMember').equal(true); + idty.should.have.property('kick').equal(false); + idty.should.have.property('member').equal(true); + } +}); diff --git a/test/integration/branches_switch.ts b/test/integration/branches/branches_switch.ts similarity index 59% rename from test/integration/branches_switch.ts rename to test/integration/branches/branches_switch.ts index 08a13ed914bb8fd97550f455e37e44b34ae96a02..94786b98c734bd41fe80d35c8d5b6c6ad96c1242 100644 --- a/test/integration/branches_switch.ts +++ b/test/integration/branches/branches_switch.ts @@ -11,23 +11,17 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; -import {CrawlerDependency} from "../../app/modules/crawler/index" -import {BmaDependency} from "../../app/modules/bma/index" - -const co = require('co'); -const _ = require('underscore'); -const duniter = require('../../index'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const TestUser = require('./tools/TestUser').TestUser +import {Underscore} from "../../../app/lib/common-libs/underscore" +import {BmaDependency} from "../../../app/modules/bma/index" +import {CrawlerDependency} from "../../../app/modules/crawler/index" +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {sync} from "../tools/test-sync" +import {shutDownEngine} from "../tools/shutdown-engine" +import {expectJSON} from "../tools/http-expect" + const rp = require('request-promise'); -const httpTest = require('./tools/http'); -const commit = require('./tools/commit'); -const sync = require('./tools/sync'); const cluster = require('cluster') -const shutDownEngine = require('./tools/shutDownEngine'); - -const expectJSON = httpTest.expectJSON; const MEMORY_MODE = true; const commonConf = { @@ -39,18 +33,18 @@ const commonConf = { sigQty: 1 }; -let s1:any, s2:any, cat, toc +let s1:TestingServer, s2:TestingServer, cat:TestUser, toc:TestUser describe("Switch", function() { - before(() => co(function *() { + before(async () => { cluster.setMaxListeners(6) - s1 = duniter( - '/bb11', - MEMORY_MODE, - _.extend({ + s1 = NewTestingServer( + Underscore.extend({ + name: 'bb11', + memory: MEMORY_MODE, switchOnHeadAdvance: 0, port: '7788', pair: { @@ -61,10 +55,10 @@ describe("Switch", function() { sigQty: 1, dt: 1, ud0: 120 }, commonConf)); - s2 = duniter( - '/bb12', - MEMORY_MODE, - _.extend({ + s2 = NewTestingServer( + Underscore.extend({ + name: 'bb12', + memory: MEMORY_MODE, switchOnHeadAdvance: 0, port: '7789', pair: { @@ -76,41 +70,41 @@ describe("Switch", function() { cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); - yield s1.initWithDAL().then(bma).then((bmapi:any) => bmapi.openConnections()); - yield s2.initWithDAL().then(bma).then((bmapi:any) => bmapi.openConnections()); - s1.addEndpointsDefinitions(() => BmaDependency.duniter.methods.getMainEndpoint(s1.conf)) - s2.addEndpointsDefinitions(() => BmaDependency.duniter.methods.getMainEndpoint(s2.conf)) - yield cat.createIdentity(); - yield toc.createIdentity(); - yield toc.cert(cat); - yield cat.cert(toc); - yield cat.join(); - yield toc.join(); - yield commit(s1)(); - yield commit(s1)(); - yield commit(s1)(); - yield sync(0, 2, s1, s2); - - let s2p = yield s2.PeeringService.peer(); - - yield commit(s1)(); - yield commit(s1)(); - yield commit(s2)(); - yield commit(s2)(); - yield commit(s2)(); - yield commit(s2)(); - yield commit(s2)(); - yield commit(s2)(); - yield commit(s2)(); + await s1.initWithDAL().then(BmaDependency.duniter.methods.bma).then((bmapi:any) => bmapi.openConnections()); + await s2.initWithDAL().then(BmaDependency.duniter.methods.bma).then((bmapi:any) => bmapi.openConnections()); + s1._server.addEndpointsDefinitions(() => BmaDependency.duniter.methods.getMainEndpoint(s1.conf)) + s2._server.addEndpointsDefinitions(() => BmaDependency.duniter.methods.getMainEndpoint(s2.conf)) + await cat.createIdentity(); + await toc.createIdentity(); + await toc.cert(cat); + await cat.cert(toc); + await cat.join(); + await toc.join(); + await s1.commit(); + await s1.commit(); + await s1.commit(); + await sync(0, 2, s1._server, s2._server); + + let s2p = await s2.PeeringService.peer(); + + await s1.commit(); + await s1.commit(); + await s2.commit(); + await s2.commit(); + await s2.commit(); + await s2.commit(); + await s2.commit(); + await s2.commit(); + await s2.commit(); // So we now have: // S1 01234 // S2 `3456789 - yield s1.writePeer(s2p) + await s1.writePeer(s2p) // Forking S1 from S2 - yield CrawlerDependency.duniter.methods.pullBlocks(s1, s2p.pubkey); + await CrawlerDependency.duniter.methods.pullBlocks(s1._server, s2p.pubkey); // S1 should have switched to the other branch - })); + }) after(() => { cluster.setMaxListeners(3) diff --git a/test/integration/branches_revert.js b/test/integration/branches_revert.js deleted file mode 100644 index d606c049da428dbd3b9b6737d0e286fa72621176..0000000000000000000000000000000000000000 --- a/test/integration/branches_revert.js +++ /dev/null @@ -1,83 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict"; - -const co = require('co'); -const _ = require('underscore'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const TestUser = require('./tools/TestUser').TestUser -const toolbox = require('./tools/toolbox'); -const commit = require('./tools/commit'); - -const commonConf = { - ipv4: '127.0.0.1', - currency: 'bb', - httpLogs: true, - forksize: 3, - sigQty: 1 -}; - -let s1, cat, toc - -describe("Revert root", function() { - - before(function() { - - return co(function *() { - - s1 = toolbox.server(_.extend({ - pair: { - pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', - sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' - }, - rootoffset: 10, - sigQty: 1, dt: 1, ud0: 120 - }, commonConf)); - - cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); - toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); - - yield s1.initDalBmaConnections(); - yield cat.createIdentity(); - yield toc.createIdentity(); - yield toc.cert(cat); - yield cat.cert(toc); - yield cat.join(); - yield toc.join(); - yield commit(s1)(); - }); - }); - - it('/block/0 should exist', () => s1.expectJSON('/blockchain/block/0', { - number: 0 - })); - - it('/wot/cat should exist', () => s1.expectThat('/wot/lookup/cat', (res) => { - res.should.have.property('results').length(1); - res.results[0].should.have.property('uids').length(1); - res.results[0].uids[0].should.have.property('uid').equal('cat'); - res.results[0].uids[0].should.have.property('others').length(1); - })); - - it('reverting should erase everything', () => co(function*() { - yield s1.revert(); - yield s1.expectError('/blockchain/current', 404, 'No current block'); - yield s1.expectError('/blockchain/block/0', 404, 'Block not found'); - yield s1.expectError('/wot/lookup/cat', 404, 'No matching identity'); // Revert completely removes the identity - })); - - after(() => { - return s1.closeCluster() - }) -}); diff --git a/test/integration/branches_revert_memberships.js b/test/integration/branches_revert_memberships.js deleted file mode 100644 index b40919e7d0fd6555bd9121fa0fe1b80cbb0ded14..0000000000000000000000000000000000000000 --- a/test/integration/branches_revert_memberships.js +++ /dev/null @@ -1,273 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict"; - -const co = require('co'); -const should = require('should'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const TestUser = require('./tools/TestUser').TestUser -const commit = require('./tools/commit'); -const toolbox = require('./tools/toolbox'); - -let s1, i1, i2, i3 - -describe("Revert memberships", function() { - - const now = 1482000000; - - before(() => co(function*() { - - s1 = toolbox.server({ - memory: true, - msValidity: 14, - pair: { - pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', - sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' - } - }); - - i1 = new TestUser('i1', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); - i2 = new TestUser('i2', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s1 }); - i3 = new TestUser('i3', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); - - yield s1.initDalBmaConnections(); - - yield i1.createIdentity(); - yield i2.createIdentity(); - - yield i1.cert(i2); - yield i2.cert(i1); - - yield i1.join(); - yield i2.join(); - - yield s1.commit({ time: now }); - yield s1.expect('/blockchain/current', (res) => { res.number.should.equal(0); (res.medianTime - now).should.equal(0); }); - yield s1.commit({ time: now + 15 }); - yield s1.expect('/blockchain/current', (res) => { res.number.should.equal(1); (res.medianTime - now).should.equal(0); }); - - yield shouldHavePendingMS(0); - yield i3.createIdentity(); - yield i1.cert(i3); - yield shouldBeFreshlyCreated(); - yield i3.join(); - yield shouldHavePendingMS(1); - yield shouldBeJoining(); - yield s1.commit({ time: now + 15 }); - yield s1.expect('/blockchain/current', (res) => { res.number.should.equal(2); (res.medianTime - now).should.equal(7); }); - yield shouldHaveJoined(); - yield shouldHavePendingMS(0); - })); - - it('should exist 3 members', () => s1.expect('/blockchain/current', (res) => { - res.should.have.property('membersCount').equal(3); - })); - - it('should exist a renew', () => co(function*() { - yield i3.join(); // Renew - yield shouldHavePendingMS(1); - yield s1.commit({ time: now + 15 }); - yield s1.expect('/blockchain/current', (res) => { res.number.should.equal(3); (res.medianTime - now).should.equal(10); }); - yield s1.expect('/blockchain/current', (res) => { - res.should.have.property('membersCount').equal(3); - res.should.have.property('actives').length(1); - }); - yield shouldBeRenewed(); - yield shouldHavePendingMS(0); - })); - - it('should exist 2 other renew', () => co(function*() { - yield s1.commit({ time: now + 15 }); - // yield s1.expect('/blockchain/current', (res) => { res.number.should.equal(4); (res.medianTime - now).should.equal(11); }); - yield i1.join(); // Renew - yield i2.join(); // Renew - yield shouldHavePendingMS(2); - yield s1.commit({ time: now + 15 }); - // yield s1.expect('/blockchain/current', (res) => { res.number.should.equal(5); (res.medianTime - now).should.equal(21); }); - yield s1.expect('/blockchain/current', (res) => { - res.should.have.property('membersCount').equal(3); - res.should.have.property('actives').length(2); - }); - yield shouldBeRenewed(); - yield shouldHavePendingMS(0); - })); - - it('should exist a leaver', () => co(function*() { - yield i3.leave(); - yield s1.commit({ time: now + 80 }); - yield s1.expect('/blockchain/current', (res) => { - // (res.medianTime - now).should.equal(27); - res.should.have.property('membersCount').equal(3); - res.should.have.property('leavers').length(1); - }); - yield shouldBeLeaving(); - yield shouldHavePendingMS(0); - })); - - it('should exist a kicked member', () => co(function*() { - yield s1.commit({ time: now + 25 }); - // yield s1.expect('/blockchain/current', (res) => { res.number.should.equal(7); (res.medianTime - now).should.equal(25); }); - // yield s1.commit({ time: now + 30 }); - // yield s1.expect('/blockchain/current', (res) => { res.number.should.equal(8); (res.medianTime - now).should.equal(18); }); - yield shouldBeBeingKicked(); - yield s1.commit({ time: now + 30 }); - yield s1.expect('/blockchain/current', (res) => { - res.should.have.property('membersCount').equal(2); - res.should.have.property('excluded').length(1); - }); - // Should: - // * unset "to be kicked" - // * not be a member anymore - yield shouldHaveBeenKicked(); - yield shouldHavePendingMS(0); - })); - - it('a kicked member should be able to join back', () => co(function*() { - yield i3.join(); - yield s1.commit({ time: now + 30 }); - yield shouldHaveComeBack(); - yield shouldHavePendingMS(0); - })); - - it('revert the join back', () => co(function*() { - yield s1.revert(); - yield shouldHaveBeenKicked(); - yield shouldHavePendingMS(0); // Undone memberships are lost - })); - - it('revert excluded member', () => co(function*() { - yield s1.revert(); - yield shouldBeBeingKicked(); - yield shouldHavePendingMS(0); // Undone memberships are lost - })); - - it('revert being kicked', () => co(function*() { - yield s1.revert(); - yield shouldBeLeaving(); - yield shouldHavePendingMS(0); // Undone memberships are lost - })); - - it('revert leaving', () => co(function*() { - yield s1.revert(); - yield shouldBeRenewed(); - yield shouldHavePendingMS(0); // Undone memberships are lost - })); - - it('revert 2 neutral blocks for i3', () => co(function*() { - yield s1.revert(); - yield shouldBeRenewed(); - yield shouldHavePendingMS(0); // Undone memberships are lost - yield s1.revert(); - yield shouldBeRenewed(); - yield shouldHavePendingMS(0); // Undone memberships are lost - })); - - it('revert renewal block', () => co(function*() { - yield s1.revert(); - yield shouldHaveJoined(); - yield shouldHavePendingMS(0); // Undone memberships are lost - })); - - it('revert join block', () => co(function*() { - yield s1.revert(); - yield shouldHavePendingMS(0); // Undone memberships are lost - })); - - after(() => { - return s1.closeCluster() - }) - - /********* - * - * Identity state testing functions - * - ********/ - - function shouldHavePendingMS(number) { - return co(function*() { - const pendingIN = yield s1.dal.msDAL.getPendingIN(); - const pendingOUT = yield s1.dal.msDAL.getPendingOUT(); - pendingIN.concat(pendingOUT).should.have.length(number); - }); - } - - function shouldBeFreshlyCreated() { - return co(function*() { - const idty = (yield s1.dal.idtyDAL.searchThoseMatching(i3.pub))[0]; - idty.should.have.property('wasMember').equal(false); - idty.should.have.property('written').equal(false); - idty.should.have.property('kick').equal(false); - idty.should.have.property('member').equal(false); - }); - } - - function shouldBeJoining() { - return shouldBeFreshlyCreated(); - } - - function shouldHaveJoined() { - return co(function*() { - const idty = yield s1.dal.iindexDAL.getFromPubkey(i3.pub); - idty.should.have.property('wasMember').equal(true); - idty.should.have.property('kick').equal(false); - idty.should.have.property('member').equal(true); - }); - } - - function shouldBeRenewed() { - return co(function*() { - const idty = yield s1.dal.iindexDAL.getFromPubkey(i3.pub); - idty.should.have.property('wasMember').equal(true); - idty.should.have.property('kick').equal(false); - idty.should.have.property('member').equal(true); - }); - } - - function shouldBeLeaving() { - return co(function*() { - const idty = yield s1.dal.iindexDAL.getFromPubkey(i3.pub); - idty.should.have.property('wasMember').equal(true); - idty.should.have.property('kick').equal(false); - idty.should.have.property('member').equal(true); - }); - } - - function shouldBeBeingKicked() { - return co(function*() { - // Should be set as kicked now - const idty = yield s1.dal.iindexDAL.getFromPubkey(i3.pub); - idty.should.have.property('wasMember').equal(true); - idty.should.have.property('kick').equal(true); - idty.should.have.property('member').equal(true); - }); - } - - function shouldHaveBeenKicked() { - return co(function*() { - const idty = yield s1.dal.iindexDAL.getFromPubkey(i3.pub); - idty.should.have.property('wasMember').equal(true); - idty.should.have.property('kick').equal(false); - idty.should.have.property('member').equal(false); - }); - } - - function shouldHaveComeBack() { - return co(function*() { - let idty = yield s1.dal.iindexDAL.getFromPubkey(i3.pub); - idty.should.have.property('wasMember').equal(true); - idty.should.have.property('kick').equal(false); - idty.should.have.property('member').equal(true); - }); - } -}); diff --git a/test/integration/certification_chainability.js b/test/integration/certification/certification_chainability.ts similarity index 66% rename from test/integration/certification_chainability.js rename to test/integration/certification/certification_chainability.ts index 5ca1cf28343a2e0533827e57969cb1cdab9b9615..d232c9d13fb90af88060454383cc11f71620c55d 100644 --- a/test/integration/certification_chainability.js +++ b/test/integration/certification/certification_chainability.ts @@ -11,21 +11,16 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {TestUser} from "../tools/TestUser" +import {BmaDependency} from "../../../app/modules/bma/index" +import {Underscore} from "../../../app/lib/common-libs/underscore" +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {HttpBlock} from "../../../app/modules/bma/lib/dtos" +import {shutDownEngine} from "../tools/shutdown-engine" +import {expectAnswer} from "../tools/http-expect" -const _ = require('underscore'); -const co = require('co'); const should = require('should'); -const duniter = require('../../index'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const TestUser = require('./tools/TestUser').TestUser -const constants = require('../../app/lib/constants'); const rp = require('request-promise'); -const httpTest = require('./tools/http'); -const commit = require('./tools/commit'); -const shutDownEngine = require('./tools/shutDownEngine'); - -const expectAnswer = httpTest.expectAnswer; const MEMORY_MODE = true; const commonConf = { @@ -37,16 +32,16 @@ const commonConf = { sigQty: 1 }; -let s1, cat, tac, tic, toc +let s1:TestingServer, cat:TestUser, tac:TestUser, tic:TestUser, toc:TestUser describe("Certification chainability", function() { - before(function() { + before(async () => { - s1 = duniter( - '/bb11', - MEMORY_MODE, - _.extend({ + s1 = NewTestingServer( + Underscore.extend({ + name: 'bb11', + memory: MEMORY_MODE, port: '9225', pair: { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', @@ -61,41 +56,37 @@ describe("Certification chainability", function() { const now = 1482220000; - const commitS1 = commit(s1); - - return co(function *() { - /** - * tac <===> cat - */ - yield s1.initWithDAL().then(bma).then((bmapi) => bmapi.openConnections()); - yield cat.createIdentity(); - yield tac.createIdentity(); - yield cat.cert(tac); - yield tac.cert(cat); - yield cat.join(); - yield tac.join(); - yield commitS1({ time: now }); - yield commitS1({ - time: now + 399 - }); - - // Should not happen on the first commit due to certPeriod - yield tic.createIdentity(); - yield tic.join(); - yield cat.cert(tic); - yield commitS1({ time: now + 199 }); - yield commitS1({ time: now + 199 }); - // We still are at +199, and the certPeriod must be OVER (or equal to) current time to allow new certs from cat. - // So if we increment +1 - yield commitS1({ - time: now + 300 - }); - yield commitS1({ - time: now + 300 - }); - // Should be integrated now - yield commitS1({ time: now + 300 }); + /** + * tac <===> cat + */ + await s1.initWithDAL().then(BmaDependency.duniter.methods.bma).then((bmapi) => bmapi.openConnections()); + await cat.createIdentity(); + await tac.createIdentity(); + await cat.cert(tac); + await tac.cert(cat); + await cat.join(); + await tac.join(); + await s1.commit({ time: now }); + await s1.commit({ + time: now + 399 + }); + + // Should not happen on the first commit due to certPeriod + await tic.createIdentity(); + await tic.join(); + await cat.cert(tic); + await s1.commit({ time: now + 199 }); + await s1.commit({ time: now + 199 }); + // We still are at +199, and the certPeriod must be OVER (or equal to) current time to allow new certs from cat. + // So if we increment +1 + await s1.commit({ + time: now + 300 + }); + await s1.commit({ + time: now + 300 }); + // Should be integrated now + await s1.commit({ time: now + 300 }); }); after(() => { @@ -105,49 +96,49 @@ describe("Certification chainability", function() { }) it('block 0 should have 2 certs', function() { - return expectAnswer(rp('http://127.0.0.1:9225/blockchain/block/0', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:9225/blockchain/block/0', { json: true }), function(res:HttpBlock) { res.should.have.property('number').equal(0); res.should.have.property('certifications').length(2); }); }); it('block 1 should have 0 certs', function() { - return expectAnswer(rp('http://127.0.0.1:9225/blockchain/block/1', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:9225/blockchain/block/1', { json: true }), function(res:HttpBlock) { res.should.have.property('number').equal(1); res.should.have.property('certifications').length(0); }); }); it('block 2 should have 0 certs', function() { - return expectAnswer(rp('http://127.0.0.1:9225/blockchain/block/2', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:9225/blockchain/block/2', { json: true }), function(res:HttpBlock) { res.should.have.property('number').equal(2); res.should.have.property('certifications').length(0); }); }); it('block 3 should have 0 certs', function() { - return expectAnswer(rp('http://127.0.0.1:9225/blockchain/block/3', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:9225/blockchain/block/3', { json: true }), function(res:HttpBlock) { res.should.have.property('number').equal(3); res.should.have.property('certifications').length(0); }); }); it('block 4 should have 0 certs', function() { - return expectAnswer(rp('http://127.0.0.1:9225/blockchain/block/4', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:9225/blockchain/block/4', { json: true }), function(res:HttpBlock) { res.should.have.property('number').equal(4); res.should.have.property('certifications').length(0); }); }); it('block 5 should have 0 certs', function() { - return expectAnswer(rp('http://127.0.0.1:9225/blockchain/block/5', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:9225/blockchain/block/5', { json: true }), function(res:HttpBlock) { res.should.have.property('number').equal(5); res.should.have.property('certifications').length(0); }); }); it('block 6 should have 1 certs', function() { - return expectAnswer(rp('http://127.0.0.1:9225/blockchain/block/6', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:9225/blockchain/block/6', { json: true }), function(res:HttpBlock) { res.should.have.property('number').equal(6); res.should.have.property('certifications').length(1); }); diff --git a/test/integration/certifier-is-member.js b/test/integration/certification/certifier-is-member.ts similarity index 51% rename from test/integration/certifier-is-member.js rename to test/integration/certification/certifier-is-member.ts index 6c3bfe4568334e19338050e20cf532ab6ff7c936..3a260faa48acabd79888abf7a168465a31057cd3 100644 --- a/test/integration/certifier-is-member.js +++ b/test/integration/certification/certifier-is-member.ts @@ -11,27 +11,22 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {shouldFail} from "../../unit-tools" -const _ = require('underscore'); -const co = require('co'); const assert = require('assert'); const should = require('should'); -const duniter = require('../../index'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const TestUser = require('./tools/TestUser').TestUser -const constants = require('../../app/lib/constants'); -const toolbox = require('./tools/toolbox'); const now = 1480000000; -let s1, cat, tac, tic +let s1:TestingServer, cat:TestUser, tac:TestUser, tic:TestUser describe("Certifier must be a member", function() { - before(() => co(function *() { + before(async () => { - s1 = toolbox.server({ + s1 = NewTestingServer({ pair: { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' @@ -46,62 +41,62 @@ describe("Certifier must be a member", function() { tac = new TestUser('tac', { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'}, { server: s1 }); tic = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s1 }); - yield s1.initDalBmaConnections(); - yield cat.createIdentity(); - yield tac.createIdentity(); - yield cat.cert(tac); - yield tac.cert(cat); - yield cat.join(); - yield tac.join(); - yield s1.commit({ time: now }); - yield s1.commit({ time: now + 8 }); - yield s1.commit({ time: now + 9 }); - })); - - it('tic should not be able to certify yet', () => co(function*() { - yield tic.createIdentity(); - yield tic.join(); - yield cat.cert(tic); - yield toolbox.shouldFail(tic.cert(cat), 'Certifier must be a member') - })); - - it('block#3 should see tic becoming member', () => co(function*() { - yield cat.join(); - yield tac.join(); - yield s1.commit({ time: now + 16 }); - yield s1.expectThat('/blockchain/block/3', (res) => { + await s1.initDalBmaConnections(); + await cat.createIdentity(); + await tac.createIdentity(); + await cat.cert(tac); + await tac.cert(cat); + await cat.join(); + await tac.join(); + await s1.commit({ time: now }); + await s1.commit({ time: now + 8 }); + await s1.commit({ time: now + 9 }); + }) + + it('tic should not be able to certify yet', async () => { + await tic.createIdentity(); + await tic.join(); + await cat.cert(tic); + await shouldFail(tic.cert(cat), 'Certifier must be a member') + }) + + it('block#3 should see tic becoming member', async () => { + await cat.join(); + await tac.join(); + await s1.commit({ time: now + 16 }); + await s1.expectThat('/blockchain/block/3', (res:any) => { res.should.have.property('joiners').length(1); }) - })); - - it('tic is now a member, he should be able to certify', () => co(function*() { - yield tic.cert(cat); - yield s1.commit({ time: now + 16 }); - yield cat.join(); - yield tac.join(); - yield s1.commit({ time: now + 21 }); - })); - - it('tic should be excluded', () => co(function*() { - yield s1.commit({ time: now + 21 }); - yield s1.commit({ time: now + 22 }); - yield s1.expectThat('/blockchain/block/7', (res) => { + }) + + it('tic is now a member, he should be able to certify', async () => { + await tic.cert(cat); + await s1.commit({ time: now + 16 }); + await cat.join(); + await tac.join(); + await s1.commit({ time: now + 21 }); + }) + + it('tic should be excluded', async () => { + await s1.commit({ time: now + 21 }); + await s1.commit({ time: now + 22 }); + await s1.expectThat('/blockchain/block/7', (res:any) => { res.should.have.property('excluded').length(1); res.excluded[0].should.equal('DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV') }) - })); + }) - it('tic should not be able to certify as he is no more a member', () => co(function*() { - yield toolbox.shouldFail(tic.cert(tac), 'Certifier must be a member') - })); + it('tic should not be able to certify as he is no more a member', async () => { + await shouldFail(tic.cert(tac), 'Certifier must be a member') + }) - it('tic should be able to certify when he joins back', () => co(function*() { - yield tic.join(); - yield s1.commit({ time: now + 23 }); - yield tic.cert(tac); - })); + it('tic should be able to certify when he joins back', async () => { + await tic.join(); + await s1.commit({ time: now + 23 }); + await tic.cert(tac); + }) after(() => { return s1.closeCluster() }) -}); +}) diff --git a/test/integration/documents-currency.ts b/test/integration/documents-currency.ts index eb135a09947a8a311dd3797cf0e567698859e6e8..1aeb9edc33c3f6d3a86270a59074136bded3db1f 100644 --- a/test/integration/documents-currency.ts +++ b/test/integration/documents-currency.ts @@ -12,14 +12,10 @@ // GNU Affero General Public License for more details. -import { NewTestingServer, TestingServer } from './tools/toolbox'; -import { unlock } from '../../app/lib/common-libs/txunlock'; -import { ConfDTO, CurrencyConfDTO } from '../../app/lib/dto/ConfDTO'; -import { Server } from '../../server'; +import {NewTestingServer} from './tools/toolbox'; +import {TestUser} from "./tools/TestUser" -const co = require('co'); const should = require('should'); -const TestUser = require('./tools/TestUser').TestUser let s1:any, s2:any, cat1:any, tac1:any, toc2:any, tic2:any; @@ -27,7 +23,7 @@ describe("Document pool currency", function() { const now = 1500000000 - before(() => co(function*() { + before(async () => { s1 = NewTestingServer({ currency: 'currency_one', @@ -52,19 +48,19 @@ describe("Document pool currency", function() { toc2 = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s2 }); tic2 = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s2 }); - yield s1.prepareForNetwork(); - yield s2.prepareForNetwork(); + await s1.prepareForNetwork(); + await s2.prepareForNetwork(); // Publishing identities - yield cat1.createIdentity(); - yield tac1.createIdentity(); - yield cat1.join(); - yield tac1.join(); - yield toc2.createIdentity(); - yield tic2.createIdentity(); - yield toc2.join(); - yield tic2.join(); - })); + await cat1.createIdentity(); + await tac1.createIdentity(); + await cat1.join(); + await tac1.join(); + await toc2.createIdentity(); + await tic2.createIdentity(); + await toc2.join(); + await tic2.join(); + }) after(() => { return Promise.all([ @@ -73,95 +69,95 @@ describe("Document pool currency", function() { ]) }) - it('Identity with wrong currency should be rejected', () => co(function*() { - const idtyCat1 = yield s1.lookup2identity(cat1.pub); + it('Identity with wrong currency should be rejected', async () => { + const idtyCat1 = await s1.lookup2identity(cat1.pub); idtyCat1.getRawSigned() try { - yield s2.postIdentity(idtyCat1); + await s2.postIdentity(idtyCat1); throw "Identity should not have been accepted, since it has an unknown currency name"; } catch (e) { should.exist(e.error); e.should.be.an.Object(); e.error.message.should.match(/Signature does not match/); } - })); + }) - it('Identity absorption with wrong currency should be rejected', () => co(function*() { + it('Identity absorption with wrong currency should be rejected', async () => { try { - const cert = yield toc2.makeCert(cat1, s1); - yield s2.postCert(cert); + const cert = await toc2.makeCert(cat1, s1); + await s2.postCert(cert); throw "Certification should not have been accepted, since it has an unknown currency name"; } catch (e) { should.exist(e.error); e.should.be.an.Object(); e.error.message.should.match(/Signature does not match/); } - })); + }) - it('Certification with wrong currency should be rejected', () => co(function*() { + it('Certification with wrong currency should be rejected', async () => { try { - const cert = yield toc2.makeCert(tic2, null, { + const cert = await toc2.makeCert(tic2, null, { currency: "wrong_currency" }); - yield s2.postCert(cert); + await s2.postCert(cert); throw "Certification should not have been accepted, since it has an unknown currency name"; } catch (e) { should.exist(e.error); e.should.be.an.Object(); e.error.message.should.match(/Wrong signature for certification/); } - })); + }) - it('Membership with wrong currency should be rejected', () => co(function*() { + it('Membership with wrong currency should be rejected', async () => { try { - const join = yield toc2.makeMembership('IN', null, { + const join = await toc2.makeMembership('IN', null, { currency: "wrong_currency" }); - yield s2.postMembership(join); + await s2.postMembership(join); throw "Membership should not have been accepted, since it has an unknown currency name"; } catch (e) { should.exist(e.error); e.should.be.an.Object(); e.error.message.should.match(/wrong signature for membership/); } - })); + }) - it('Revocation with wrong currency should be rejected', () => co(function*() { + it('Revocation with wrong currency should be rejected', async () => { try { - const revocation = yield toc2.makeRevocation(null, { + const revocation = await toc2.makeRevocation(null, { currency: "wrong_currency" }); - yield s2.postRevocation(revocation); + await s2.postRevocation(revocation); throw "Revocation should not have been accepted, since it has an unknown currency name"; } catch (e) { should.exist(e.error); e.should.be.an.Object(); e.error.message.should.match(/Wrong signature for revocation/); } - })); + }) - it('Block with wrong currency should be rejected', () => co(function*() { - yield toc2.cert(tic2); - yield tic2.cert(toc2); - yield s2.commit(); - const b2 = yield s2.makeNext({ currency: "wrong_currency" }); + it('Block with wrong currency should be rejected', async () => { + await toc2.cert(tic2); + await tic2.cert(toc2); + await s2.commit(); + const b2 = await s2.makeNext({ currency: "wrong_currency" }); try { - yield s2.postBlock(b2); + await s2.postBlock(b2); throw "Currency should have been rejected"; } catch (e) { should.exist(e.error); e.should.be.an.Object(); e.error.message.should.match(/Wrong currency/); } - })); + }) - it('Transaction with wrong currency should be rejected', () => co(function*() { + it('Transaction with wrong currency should be rejected', async () => { try { - yield cat1.cert(tac1); - yield tac1.cert(cat1); - yield s1.commit({ time: now }); - yield s1.commit({ time: now }); - const current = yield s1.get('/blockchain/current'); + await cat1.cert(tac1); + await tac1.cert(cat1); + await s1.commit({ time: now }); + await s1.commit({ time: now }); + const current = await s1.get('/blockchain/current'); const tx = cat1.makeTX( [{ src: "1500:0:D:DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo:1", @@ -176,18 +172,18 @@ describe("Document pool currency", function() { currency: "wrong_currency", blockstamp: [current.number, current.hash].join('-') }); - yield s1.postRawTX(tx); + await s1.postRawTX(tx); throw "Transaction should not have been accepted, since it has an unknown currency name"; } catch (e) { should.exist(e.error); e.should.be.an.Object(); e.error.message.should.match(/Signature from a transaction must match/); } - })); + }) - it('Transaction with wrong XHX should be rejected', () => co(function*() { + it('Transaction with wrong XHX should be rejected', async () => { try { - const current = yield s1.get('/blockchain/current'); + const current = await s1.get('/blockchain/current'); const tx = cat1.makeTX( [{ src: "1500:1:D:HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd:1", @@ -201,27 +197,27 @@ describe("Document pool currency", function() { { blockstamp: [current.number, current.hash].join('-') }); - yield s1.postRawTX(tx); + await s1.postRawTX(tx); throw "Transaction should not have been accepted, since it has wrong output format"; } catch (e) { should.exist(e.error); e.should.be.an.Object(); e.error.message.should.match(/Wrong output format/); } - })); + }) - it('Peer with wrong currency should be rejected', () => co(function*() { + it('Peer with wrong currency should be rejected', async () => { try { - const peer = yield toc2.makePeer(['BASIC_MERKLED_API localhost 10901'], { + const peer = await toc2.makePeer(['BASIC_MERKLED_API localhost 10901'], { version: 10, currency: "wrong_currency" }); - yield s2.postPeer(peer); + await s2.postPeer(peer); throw "Peer should not have been accepted, since it has an unknown currency name"; } catch (e) { should.exist(e.error); e.should.be.an.Object(); e.error.message.should.match(/Signature from a peer must match/); } - })); -}); + }) +}) diff --git a/test/integration/register-fork-blocks.js b/test/integration/fork-resolution/register-fork-blocks.ts similarity index 55% rename from test/integration/register-fork-blocks.js rename to test/integration/fork-resolution/register-fork-blocks.ts index b31bbe2d75b2e8f39c60a44700e0af80d1002d9f..d976ad2ccb8052d486c1d3629d12564aa85dbabf 100644 --- a/test/integration/register-fork-blocks.js +++ b/test/integration/fork-resolution/register-fork-blocks.ts @@ -11,26 +11,24 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {CommonConstants} from "../../../app/lib/common-libs/constants" +import {BlockDTO} from "../../../app/lib/dto/BlockDTO" +import {HttpBranches} from "../../../app/modules/bma/lib/dtos" -const _ = require('underscore'); -const co = require('co'); const assert = require('assert'); -const TestUser = require('./tools/TestUser').TestUser -const commit = require('./tools/commit'); -const toolbox = require('./tools/toolbox'); -const CommonConstants = require('../../app/lib/common-libs/constants').CommonConstants const now = 1500000000 const forksize = 10 -let s1, s2, s3, cat1, tac1, toc1 +let s1:TestingServer, s2:TestingServer, s3:TestingServer, cat1:TestUser, tac1:TestUser, toc1:TestUser describe("Fork blocks", function() { - before(() => co(function*() { + before(async () => { - s1 = toolbox.server({ + s1 = NewTestingServer({ // The common conf nbCores:1, @@ -46,7 +44,7 @@ describe("Fork blocks", function() { } }); - s2 = toolbox.server({ + s2 = NewTestingServer({ // Particular conf nbCores:1, @@ -59,7 +57,7 @@ describe("Fork blocks", function() { } }); - s3 = toolbox.server({ + s3 = NewTestingServer({ // Particular conf nbCores:1, @@ -76,21 +74,21 @@ describe("Fork blocks", function() { tac1 = new TestUser('tac', { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'}, { server: s1 }); toc1 = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); - yield s1.prepareForNetwork(); - yield s2.prepareForNetwork(); - yield s3.prepareForNetwork(); + await s1.prepareForNetwork(); + await s2.prepareForNetwork(); + await s3.prepareForNetwork(); // Publishing identities - yield cat1.createIdentity(); - yield tac1.createIdentity(); - yield toc1.createIdentity(); - yield cat1.cert(tac1); - yield tac1.cert(cat1); - yield tac1.cert(toc1); - yield cat1.join(); - yield tac1.join(); - yield toc1.join(); - })); + await cat1.createIdentity(); + await tac1.createIdentity(); + await toc1.createIdentity(); + await cat1.cert(tac1); + await tac1.cert(cat1); + await tac1.cert(toc1); + await cat1.join(); + await tac1.join(); + await toc1.join(); + }) after(() => { return Promise.all([ @@ -100,85 +98,85 @@ describe("Fork blocks", function() { ]) }) - it('should create a common blockchain', () => co(function*() { - const b0 = yield s1.commit({ time: now }) - const b1 = yield s1.commit({ time: now + 11 }) - const b2 = yield s1.commit({ time: now + 22 }) - yield s2.writeBlock(b0) - yield s2.writeBlock(b1) - yield s2.writeBlock(b2) - yield s3.writeBlock(b0) - yield s3.writeBlock(b1) - yield s3.writeBlock(b2) - yield s2.waitToHaveBlock(2) - yield s3.waitToHaveBlock(2) - })) - - it('should exist the same block on each node', () => co(function*() { - yield s1.expectJSON('/blockchain/current', { + it('should create a common blockchain', async () => { + const b0 = await s1.commit({ time: now }) + const b1 = await s1.commit({ time: now + 11 }) + const b2 = await s1.commit({ time: now + 22 }) + await s2.writeBlock(b0) + await s2.writeBlock(b1) + await s2.writeBlock(b2) + await s3.writeBlock(b0) + await s3.writeBlock(b1) + await s3.writeBlock(b2) + await s2.waitToHaveBlock(2) + await s3.waitToHaveBlock(2) + }) + + it('should exist the same block on each node', async () => { + await s1.expectJSON('/blockchain/current', { number: 2 }) - yield s2.expectJSON('/blockchain/current', { + await s2.expectJSON('/blockchain/current', { number: 2 }) - })) - - it('should be able to fork, and notify each node', () => co(function*() { - const b3a = yield s1.commit({ time: now + 33 }) - const b3b = yield s2.commit({ time: now + 33 }) - yield s1.writeBlock(b3b) - yield s2.writeBlock(b3a) - yield s1.waitToHaveBlock(3) - yield s2.waitToHaveBlock(3) - })) - - it('should exist a different third block on each node', () => co(function*() { - yield s1.expectJSON('/blockchain/current', { + }) + + it('should be able to fork, and notify each node', async () => { + const b3a = await s1.commit({ time: now + 33 }) + const b3b = await s2.commit({ time: now + 33 }) + await s1.writeBlock(b3b) + await s2.writeBlock(b3a) + await s1.waitToHaveBlock(3) + await s2.waitToHaveBlock(3) + }) + + it('should exist a different third block on each node', async () => { + await s1.expectJSON('/blockchain/current', { number: 3, hash: "74AB356F0E6CD9AA6F752E58FFCD65D5F8C95CDAA93576A40457CC3598C4E3D1" }) - yield s2.expectJSON('/blockchain/current', { + await s2.expectJSON('/blockchain/current', { number: 3, hash: "2C3555F4009461C81F7209EAAD7DA831D8451708D06BB1173CCB40746CD0641B" }) - })) + }) - it('should exist both branches on each node', () => co(function*() { - yield s1.expect('/blockchain/branches', (res) => { + it('should exist both branches on each node', async () => { + await s1.expect('/blockchain/branches', (res:HttpBranches) => { assert.equal(res.blocks.length, 2) assert.equal(res.blocks[0].number, 3) assert.equal(res.blocks[0].hash, '2C3555F4009461C81F7209EAAD7DA831D8451708D06BB1173CCB40746CD0641B') assert.equal(res.blocks[1].number, 3) assert.equal(res.blocks[1].hash, '74AB356F0E6CD9AA6F752E58FFCD65D5F8C95CDAA93576A40457CC3598C4E3D1') }) - yield s2.expect('/blockchain/branches', (res) => { + await s2.expect('/blockchain/branches', (res:HttpBranches) => { assert.equal(res.blocks.length, 2) assert.equal(res.blocks[0].number, 3) assert.equal(res.blocks[0].hash, '74AB356F0E6CD9AA6F752E58FFCD65D5F8C95CDAA93576A40457CC3598C4E3D1') assert.equal(res.blocks[1].number, 3) assert.equal(res.blocks[1].hash, '2C3555F4009461C81F7209EAAD7DA831D8451708D06BB1173CCB40746CD0641B') }) - })) - - let b4a, b5a, b6a, b7a, b8a - - it('should be able to grow S1\'s blockchain', () => co(function*() { - b4a = yield s1.commit({time: now + 44}) - b5a = yield s1.commit({time: now + 55}) - b6a = yield s1.commit({time: now + 66}) - b7a = yield s1.commit({time: now + 77}) - b8a = yield s1.commit({time: now + 88}) - yield s1.waitToHaveBlock(8) - })) - - it('should refuse known fork blocks', () => co(function*() { - yield s1.sharePeeringWith(s2) - yield s2.sharePeeringWith(s1) - yield s2.writeBlock(b4a) - const b3c = yield s3.commit({ time: now + 33 }) - yield new Promise((res, rej) => { + }) + + let b4a:BlockDTO, b5a:BlockDTO, b6a:BlockDTO, b7a:BlockDTO, b8a:BlockDTO + + it('should be able to grow S1\'s blockchain', async () => { + b4a = (await s1.commit({time: now + 44})) as BlockDTO + b5a = (await s1.commit({time: now + 55})) as BlockDTO + b6a = (await s1.commit({time: now + 66})) as BlockDTO + b7a = (await s1.commit({time: now + 77})) as BlockDTO + b8a = (await s1.commit({time: now + 88})) as BlockDTO + await s1.waitToHaveBlock(8) + }) + + it('should refuse known fork blocks', async () => { + await s1.sharePeeringWith(s2) + await s2.sharePeeringWith(s1) + await s2.writeBlock(b4a) + const b3c = await s3.commit({ time: now + 33 }) + await new Promise((res, rej) => { const event = CommonConstants.DocumentError - s2.on(event, (e) => { + s2.on(event, (e:any) => { try { assert.equal(e, 'Block already known') res() @@ -189,46 +187,48 @@ describe("Fork blocks", function() { // Trigger the third-party fork block writing s2.writeBlock(b3c) }) - })) - - it('should be able to make one fork grow enough to make one node switch', () => co(function*() { - yield s2.writeBlock(b5a) - yield s2.writeBlock(b6a) - yield s2.writeBlock(b7a) - yield s2.writeBlock(b8a) - yield s2.waitToHaveBlock(8) - yield s2.waitForkResolution(8) - })) - - it('should exist a same current block on each node', () => co(function*() { - yield s1.expectJSON('/blockchain/current', { + }) + + it('should be able to make one fork grow enough to make one node switch', async () => { + await s2.writeBlock(b5a) + await s2.writeBlock(b6a) + await s2.writeBlock(b7a) + await s2.writeBlock(b8a) + await Promise.all([ + s2.waitToHaveBlock(8), + s2.waitForkResolution(8) + ]) + }) + + it('should exist a same current block on each node', async () => { + await s1.expectJSON('/blockchain/current', { number: 8, hash: "B8D2AA2A5556F7A2837FB4B881FCF50595F855D0BF8F71C0B432E27216BBA40B" }) - yield s2.expectJSON('/blockchain/current', { + await s2.expectJSON('/blockchain/current', { number: 8, hash: "B8D2AA2A5556F7A2837FB4B881FCF50595F855D0BF8F71C0B432E27216BBA40B" }) - })) + }) - it('should exist 2 branches on each node', () => co(function*() { - yield s1.expect('/blockchain/branches', (res) => { + it('should exist 2 branches on each node', async () => { + await s1.expect('/blockchain/branches', (res:HttpBranches) => { assert.equal(res.blocks.length, 3) assert.equal(res.blocks[0].number, 3) - assert.equal(res.blocks[0].hash, '9A0FA1F0899124444ADC5B2C0AB66AC5B4303A0D851BED2E7382BB57E10AA2C5') + assert.equal(res.blocks[0].hash, '2C3555F4009461C81F7209EAAD7DA831D8451708D06BB1173CCB40746CD0641B') // This is s2 fork! assert.equal(res.blocks[1].number, 3) - assert.equal(res.blocks[1].hash, '2C3555F4009461C81F7209EAAD7DA831D8451708D06BB1173CCB40746CD0641B') // This is s2 fork! + assert.equal(res.blocks[1].hash, '9A0FA1F0899124444ADC5B2C0AB66AC5B4303A0D851BED2E7382BB57E10AA2C5') assert.equal(res.blocks[2].number, 8) assert.equal(res.blocks[2].hash, 'B8D2AA2A5556F7A2837FB4B881FCF50595F855D0BF8F71C0B432E27216BBA40B') }) - yield s2.expect('/blockchain/branches', (res) => { + await s2.expect('/blockchain/branches', (res:HttpBranches) => { assert.equal(res.blocks.length, 3) assert.equal(res.blocks[0].number, 3) - assert.equal(res.blocks[0].hash, '9A0FA1F0899124444ADC5B2C0AB66AC5B4303A0D851BED2E7382BB57E10AA2C5') + assert.equal(res.blocks[0].hash, '2C3555F4009461C81F7209EAAD7DA831D8451708D06BB1173CCB40746CD0641B') // This is s2 fork! assert.equal(res.blocks[1].number, 3) - assert.equal(res.blocks[1].hash, '2C3555F4009461C81F7209EAAD7DA831D8451708D06BB1173CCB40746CD0641B') // This is s2 fork! + assert.equal(res.blocks[1].hash, '9A0FA1F0899124444ADC5B2C0AB66AC5B4303A0D851BED2E7382BB57E10AA2C5') assert.equal(res.blocks[2].number, 8) assert.equal(res.blocks[2].hash, 'B8D2AA2A5556F7A2837FB4B881FCF50595F855D0BF8F71C0B432E27216BBA40B') }) - })) -}); + }) +}) diff --git a/test/integration/identity-expiry.js b/test/integration/identity-expiry.js deleted file mode 100644 index 597752905a9cbbe63e5c571f0d417991a3d5ed3e..0000000000000000000000000000000000000000 --- a/test/integration/identity-expiry.js +++ /dev/null @@ -1,116 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict"; - -const _ = require('underscore'); -const co = require('co'); -const should = require('should'); -const duniter = require('../../index'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const prover = require('../../app/modules/prover').ProverDependency.duniter.methods; -const TestUser = require('./tools/TestUser').TestUser -const constants = require('../../app/lib/constants'); -const rp = require('request-promise'); -const httpTest = require('./tools/http'); -const commit = require('./tools/commit'); -const shutDownEngine = require('./tools/shutDownEngine'); - -const expectAnswer = httpTest.expectAnswer; -const expectError = httpTest.expectError; - -const MEMORY_MODE = true; -const commonConf = { - ipv4: '127.0.0.1', - currency: 'bb', - httpLogs: true, - forksize: 3, - xpercent: 0.9, - msValidity: 10000, - idtyWindow: 1, // 1 second of duration - sigQty: 1 -}; - -let s1, cat, tac, tic, toc - -const now = 1482300000; -const commitS1 = (opts) => commit(s1)(opts) - -describe("Identities expiry", function() { - - before(function() { - - return co(function *() { - - s1 = duniter( - '/bb11', - MEMORY_MODE, - _.extend({ - port: '8560', - pair: { - pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', - sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' - } - }, commonConf)); - - cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); - tac = new TestUser('tac', { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'}, { server: s1 }); - tic = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s1 }); - toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); - - yield s1.initWithDAL().then(bma).then((bmapi) => bmapi.openConnections()); - prover.hookServer(s1) - yield cat.createIdentity(); - yield tac.createIdentity(); - yield tic.createIdentity(); - yield cat.cert(tac); - yield tac.cert(cat); - yield cat.join(); - yield tac.join(); - yield commitS1({ - time: now - }); - yield toc.createIdentity(); - yield toc.join(); - yield commitS1({ - time: now + 5 - }); - }); - }); - - after(() => { - return shutDownEngine(s1) - }) - - it('should have requirements failing for tic', function() { - // tic has been cleaned up, since its identity has expired after the root block - return expectError(404, 'No identity matching this pubkey or uid', rp('http://127.0.0.1:8560/wot/requirements/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', { json: true })); - }); - - it('should have requirements failing for toc', function() { - return expectAnswer(rp('http://127.0.0.1:8560/wot/requirements/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', { json: true }), (res) => { - res.should.have.property('identities').length(1); - res.identities[0].should.have.property('pubkey').equal('DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo'); - res.identities[0].should.have.property('uid').equal('toc'); - res.identities[0].should.have.property('expired').equal(false); - }); - }); - - it('should have requirements failing for toc', () => co(function*() { - // tic has been cleaned up after the block#2 - yield commitS1({ - time: now + 5 - }); - return expectError(404, 'No identity matching this pubkey or uid', rp('http://127.0.0.1:8560/wot/requirements/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', { json: true })); - })); -}); diff --git a/test/integration/identity-absorption.js b/test/integration/identity/identity-absorption.ts similarity index 62% rename from test/integration/identity-absorption.js rename to test/integration/identity/identity-absorption.ts index 3335529e5d17b8334e588e120002094becdfd659..4ee1051e018870ed5beecdeadabf905a1427465e 100644 --- a/test/integration/identity-absorption.js +++ b/test/integration/identity/identity-absorption.ts @@ -11,19 +11,16 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {BmaDependency} from "../../../app/modules/bma/index" +import {shouldFail} from "../../unit-tools" +import {Underscore} from "../../../app/lib/common-libs/underscore" +import {shutDownEngine} from "../tools/shutdown-engine" +import {expectAnswer} from "../tools/http-expect" -const _ = require('underscore'); -const co = require('co'); -const duniter = require('../../index'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const TestUser = require('./tools/TestUser').TestUser +const duniter = require('../../../index'); const rp = require('request-promise'); -const httpTest = require('./tools/http'); -const toolbox = require('./tools/toolbox'); -const shutDownEngine = require('./tools/shutDownEngine'); - -const expectAnswer = httpTest.expectAnswer; const MEMORY_MODE = true; const commonConf = { @@ -36,16 +33,16 @@ const commonConf = { sigQty: 1 }; -let s1, s2, cat, tic +let s1:TestingServer, s2:TestingServer, cat:TestUser, tic:TestUser -describe("Identity absorption", function() { +describe("Identity absorption", () => { - before(function() { + before(async () => { s1 = duniter( '/bb12', MEMORY_MODE, - _.extend({ + Underscore.extend({ port: '4450', pair: { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', @@ -56,7 +53,7 @@ describe("Identity absorption", function() { s2 = duniter( '/bb12', MEMORY_MODE, - _.extend({ + Underscore.extend({ port: '4451', pair: { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', @@ -67,12 +64,10 @@ describe("Identity absorption", function() { cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); tic = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s2 }); - return co(function *() { - yield s1.initWithDAL().then(bma).then((bmapi) => bmapi.openConnections()); - yield s2.initWithDAL().then(bma).then((bmapi) => bmapi.openConnections()); - yield cat.createIdentity(); - yield tic.cert(cat, s1); - }); + await s1.initWithDAL().then(BmaDependency.duniter.methods.bma).then((bmapi) => bmapi.openConnections()); + await s2.initWithDAL().then(BmaDependency.duniter.methods.bma).then((bmapi) => bmapi.openConnections()); + await cat.createIdentity(); + await tic.cert(cat, s1); }); after(() => { @@ -82,29 +77,29 @@ describe("Identity absorption", function() { ]) }) - it('cat should exist on server 1', function() { - return expectAnswer(rp('http://127.0.0.1:4450/wot/lookup/cat', { json: true }), function(res) { + it('cat should exist on server 1', () => { + return expectAnswer(rp('http://127.0.0.1:4450/wot/lookup/cat', { json: true }), function(res:any) { res.should.have.property('results').length(1); res.results[0].should.have.property('uids').length(1); res.results[0].uids[0].should.have.property('uid').equal('cat'); }); }); - it('cat should exist on server 2', function() { - return expectAnswer(rp('http://127.0.0.1:4451/wot/lookup/cat', { json: true }), function(res) { + it('cat should exist on server 2', () => { + return expectAnswer(rp('http://127.0.0.1:4451/wot/lookup/cat', { json: true }), function(res:any) { res.should.have.property('results').length(1); res.results[0].should.have.property('uids').length(1); res.results[0].uids[0].should.have.property('uid').equal('cat'); }); }); - it('should test idty absorption refusal', () => co(function*() { - (yield s2.dal.idtyDAL.query('SELECT * FROM idty')).should.have.length(1); - yield s2.dal.idtyDAL.exec('DELETE FROM idty'); - (yield s2.dal.idtyDAL.query('SELECT * FROM idty')).should.have.length(0); - yield toolbox.shouldFail(tic.cert(cat, s1), 'Already up-to-date'); - (yield s2.dal.idtyDAL.query('SELECT * FROM idty')).should.have.length(1); - (yield s2.dal.idtyDAL.query('SELECT * FROM idty WHERE removed')).should.have.length(1); - (yield s2.dal.idtyDAL.query('SELECT * FROM idty WHERE NOT removed')).should.have.length(0); - })); -}); + it('should test idty absorption refusal', async () => { + (await s2.dal.idtyDAL.query('SELECT * FROM idty')).should.have.length(1); + await s2.dal.idtyDAL.exec('DELETE FROM idty'); + (await s2.dal.idtyDAL.query('SELECT * FROM idty')).should.have.length(0); + await shouldFail(tic.cert(cat, s1), 'Already up-to-date'); + (await s2.dal.idtyDAL.query('SELECT * FROM idty')).should.have.length(1); + (await s2.dal.idtyDAL.query('SELECT * FROM idty WHERE removed')).should.have.length(1); + (await s2.dal.idtyDAL.query('SELECT * FROM idty WHERE NOT removed')).should.have.length(0); + }) +}) diff --git a/test/integration/identity-clean-test.js b/test/integration/identity/identity-clean-test.ts similarity index 63% rename from test/integration/identity-clean-test.js rename to test/integration/identity/identity-clean-test.ts index a042d79d8dbe8f2a7d87cdef5e936da0fa4f31ba..2a40b596cd4837ea2c7fb7c3169cc6caca83e798 100644 --- a/test/integration/identity-clean-test.js +++ b/test/integration/identity/identity-clean-test.ts @@ -11,19 +11,15 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {BmaDependency} from "../../../app/modules/bma/index" +import {Underscore} from "../../../app/lib/common-libs/underscore" +import {HttpMembers} from "../../../app/modules/bma/lib/dtos" +import {shutDownEngine} from "../tools/shutdown-engine" +import {expectAnswer} from "../tools/http-expect" -const _ = require('underscore'); -const co = require('co'); -const duniter = require('../../index'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const TestUser = require('./tools/TestUser').TestUser const rp = require('request-promise'); -const httpTest = require('./tools/http'); -const commit = require('./tools/commit'); -const shutDownEngine = require('./tools/shutDownEngine'); - -const expectAnswer = httpTest.expectAnswer; const MEMORY_MODE = true; const commonConf = { @@ -36,16 +32,16 @@ const commonConf = { sigQty: 1 }; -let s1, cat, tac, tic, toc +let s1:TestingServer, cat:TestUser, tac:TestUser, tic:TestUser, toc:TestUser describe("Identities cleaned", function() { - before(function() { + before(async () => { - s1 = duniter( - '/bb12', - MEMORY_MODE, - _.extend({ + s1 = NewTestingServer( + Underscore.extend({ + name: 'bb12', + memory: MEMORY_MODE, port: '7733', pair: { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', @@ -58,33 +54,29 @@ describe("Identities cleaned", function() { toc = new TestUser('cat', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); tac = new TestUser('tac', { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'}, { server: s1 }); - const commitS1 = commit(s1); - - return co(function *() { - yield s1.initWithDAL().then(bma).then((bmapi) => bmapi.openConnections()); - yield cat.createIdentity(); - yield tic.createIdentity(); - yield toc.createIdentity(); + await s1.initWithDAL().then(BmaDependency.duniter.methods.bma).then((bmapi) => bmapi.openConnections()); + await cat.createIdentity(); + await tic.createIdentity(); + await toc.createIdentity(); - yield expectAnswer(rp('http://127.0.0.1:7733/wot/lookup/cat', { json: true }), function(res) { - res.should.have.property('results').length(2); - res.results[0].should.have.property('uids').length(1); - res.results[0].uids[0].should.have.property('uid').equal('cat'); // This is cat - res.results[1].uids[0].should.have.property('uid').equal('cat'); // This is toc - }); + await expectAnswer(rp('http://127.0.0.1:7733/wot/lookup/cat', { json: true }), function(res:any) { + res.should.have.property('results').length(2); + res.results[0].should.have.property('uids').length(1); + res.results[0].uids[0].should.have.property('uid').equal('cat'); // This is cat + res.results[1].uids[0].should.have.property('uid').equal('cat'); // This is toc + }); - yield cat.cert(tic); - yield tic.cert(cat); - yield cat.join(); - yield tic.join(); - yield commitS1(); + await cat.cert(tic); + await tic.cert(cat); + await cat.join(); + await tic.join(); + await s1.commit() - // We have the following WoT (diameter 1): + // We have the following WoT (diameter 1): - /** - * cat <-> tic - */ - }); + /** + * cat <-> tic + */ }); after(() => { @@ -94,14 +86,14 @@ describe("Identities cleaned", function() { }) it('should have 2 members', function() { - return expectAnswer(rp('http://127.0.0.1:7733/wot/members', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:7733/wot/members', { json: true }), function(res:HttpMembers) { res.should.have.property('results').length(2); - _.pluck(res.results, 'uid').sort().should.deepEqual(['cat', 'tic']); + Underscore.pluck(res.results, 'uid').sort().should.deepEqual(['cat', 'tic']); }); }); it('lookup should give only 1 cat', function() { - return expectAnswer(rp('http://127.0.0.1:7733/wot/lookup/cat', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:7733/wot/lookup/cat', { json: true }), function(res:any) { res.should.have.property('results').length(1); res.results[0].should.have.property('uids').length(1); res.results[0].uids[0].should.have.property('uid').equal('cat'); @@ -109,7 +101,7 @@ describe("Identities cleaned", function() { }); it('lookup should give only 1 tic', function() { - return expectAnswer(rp('http://127.0.0.1:7733/wot/lookup/tic', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:7733/wot/lookup/tic', { json: true }), function(res:any) { res.should.have.property('results').length(1); res.results[0].should.have.property('uids').length(1); res.results[0].uids[0].should.have.property('uid').equal('tic'); diff --git a/test/integration/identity/identity-expiry.ts b/test/integration/identity/identity-expiry.ts new file mode 100644 index 0000000000000000000000000000000000000000..9085e381afc640a6bba6fb368242ebe844761931 --- /dev/null +++ b/test/integration/identity/identity-expiry.ts @@ -0,0 +1,106 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import {BmaDependency} from "../../../app/modules/bma/index" +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {Underscore} from "../../../app/lib/common-libs/underscore" +import {HttpRequirements} from "../../../app/modules/bma/lib/dtos" +import {ProverDependency} from "../../../app/modules/prover/index" +import {shutDownEngine} from "../tools/shutdown-engine" +import {expectAnswer, expectError} from "../tools/http-expect" + +const should = require('should'); +const rp = require('request-promise'); + +const MEMORY_MODE = true; +const commonConf = { + ipv4: '127.0.0.1', + currency: 'bb', + httpLogs: true, + forksize: 3, + xpercent: 0.9, + msValidity: 10000, + idtyWindow: 1, // 1 second of duration + sigQty: 1 +}; + +let s1:TestingServer, cat:TestUser, tac:TestUser, tic:TestUser, toc:TestUser + +const now = 1482300000; + +describe("Identities expiry", function() { + + before(async () => { + + s1 = NewTestingServer( + Underscore.extend({ + name: 'bb11', + memory: MEMORY_MODE, + port: '8560', + pair: { + pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', + sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' + } + }, commonConf)); + + cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); + tac = new TestUser('tac', { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'}, { server: s1 }); + tic = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s1 }); + toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); + + await s1.initWithDAL().then(BmaDependency.duniter.methods.bma).then((bmapi) => bmapi.openConnections()); + ProverDependency.duniter.methods.hookServer(s1._server) + await cat.createIdentity(); + await tac.createIdentity(); + await tic.createIdentity(); + await cat.cert(tac); + await tac.cert(cat); + await cat.join(); + await tac.join(); + await s1.commit({ + time: now + }); + await toc.createIdentity(); + await toc.join(); + await s1.commit({ + time: now + 5 + }) + }); + + after(() => { + return shutDownEngine(s1) + }) + + it('should have requirements failing for tic', function() { + // tic has been cleaned up, since its identity has expired after the root block + return expectError(404, 'No identity matching this pubkey or uid', rp('http://127.0.0.1:8560/wot/requirements/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', { json: true })); + }); + + it('should have requirements failing for toc', function() { + return expectAnswer(rp('http://127.0.0.1:8560/wot/requirements/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', { json: true }), (res:HttpRequirements) => { + res.should.have.property('identities').length(1); + res.identities[0].should.have.property('pubkey').equal('DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo'); + res.identities[0].should.have.property('uid').equal('toc'); + res.identities[0].should.have.property('expired').equal(false); + }); + }); + + it('should have requirements failing for toc', async () => { + // tic has been cleaned up after the block#2 + await s1.commit({ + time: now + 5 + }); + return expectError(404, 'No identity matching this pubkey or uid', rp('http://127.0.0.1:8560/wot/requirements/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', { json: true })); + }) +}); diff --git a/test/integration/identity-implicit-revocation.js b/test/integration/identity/identity-implicit-revocation.ts similarity index 63% rename from test/integration/identity-implicit-revocation.js rename to test/integration/identity/identity-implicit-revocation.ts index 1037ebad4e5c5b8ef4eafc4229948b94b34a8956..728b31c89229c5158c9b86d6d3c9153d3617a65e 100644 --- a/test/integration/identity-implicit-revocation.js +++ b/test/integration/identity/identity-implicit-revocation.ts @@ -11,27 +11,23 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {FullMindexEntry} from "../../../app/lib/indexer" +import {HttpBlock, HttpLookup} from "../../../app/modules/bma/lib/dtos" -const _ = require('underscore'); -const co = require('co'); const assert = require('assert'); const should = require('should'); -const duniter = require('../../index'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const TestUser = require('./tools/TestUser').TestUser -const constants = require('../../app/lib/constants'); -const toolbox = require('./tools/toolbox'); const now = 1480000000; -let s1, cat, tac, tic +let s1:TestingServer, cat:TestUser, tac:TestUser, tic:TestUser describe("Implicit revocation", function() { - before(() => co(function *() { + before(async () => { - s1 = toolbox.server({ + s1 = NewTestingServer({ pair: { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' @@ -46,31 +42,31 @@ describe("Implicit revocation", function() { tac = new TestUser('tac', { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'}, { server: s1 }); tic = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s1 }); - yield s1.initDalBmaConnections(); - yield cat.createIdentity(); - yield tac.createIdentity(); - yield tic.createIdentity(); - yield cat.cert(tac); - yield tac.cert(tic); - yield tic.cert(cat); - yield cat.join(); - yield tac.join(); - yield tic.join(); - yield s1.commit({ time: now }); - yield s1.commit({ time: now + 8 }); - yield s1.commit({ time: now + 9 }); - yield cat.join(); - yield tac.join(); - yield s1.commit({ time: now + 10 }); - yield s1.commit({ time: now + 10 }); - yield s1.commit({ time: now + 11 }); - yield s1.commit({ time: now + 15 }); - yield s1.commit({ time: now + 15 }); - yield cat.join(); - yield tac.join(); - yield s1.commit({ time: now + 20 }); - yield s1.commit({ time: now + 20 }); - })); + await s1.initDalBmaConnections(); + await cat.createIdentity(); + await tac.createIdentity(); + await tic.createIdentity(); + await cat.cert(tac); + await tac.cert(tic); + await tic.cert(cat); + await cat.join(); + await tac.join(); + await tic.join(); + await s1.commit({ time: now }); + await s1.commit({ time: now + 8 }); + await s1.commit({ time: now + 9 }); + await cat.join(); + await tac.join(); + await s1.commit({ time: now + 10 }); + await s1.commit({ time: now + 10 }); + await s1.commit({ time: now + 11 }); + await s1.commit({ time: now + 15 }); + await s1.commit({ time: now + 15 }); + await cat.join(); + await tac.join(); + await s1.commit({ time: now + 20 }); + await s1.commit({ time: now + 20 }); + }) after(() => { return Promise.all([ @@ -78,18 +74,18 @@ describe("Implicit revocation", function() { ]) }) - it('block#4 should have kicked tic', () => s1.expectThat('/blockchain/block/5', (res) => { + it('block#4 should have kicked tic', () => s1.expectThat('/blockchain/block/5', (res:HttpBlock) => { assert.deepEqual(res.excluded, [ 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV' ]); })); - it('should exist implicit revocation traces', () => co(function*() { - const ms = yield s1.dal.mindexDAL.getReducedMS('DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV') + it('should exist implicit revocation traces', async () => { + const ms = (await s1.dal.mindexDAL.getReducedMS('DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV')) as FullMindexEntry ms.should.have.property('revoked_on').equal(1480000020) - })); + }) - it('should answer that tic is revoked on API', () => s1.expectThat('/wot/lookup/tic', (res) => { + it('should answer that tic is revoked on API', () => s1.expectThat('/wot/lookup/tic', (res:HttpLookup) => { res.should.have.property('results').length(1); res.results[0].should.have.property('uids').length(1); res.results[0].uids[0].should.have.property('uid').equal('tic'); diff --git a/test/integration/identity-kicking-by-certs.js b/test/integration/identity/identity-kicking-by-certs.ts similarity index 64% rename from test/integration/identity-kicking-by-certs.js rename to test/integration/identity/identity-kicking-by-certs.ts index c9e2274c2cc0f46d5fca835579bda372350fed04..ecf3f46202bf44c05a2984ecd30a732a2cbf9f6b 100644 --- a/test/integration/identity-kicking-by-certs.js +++ b/test/integration/identity/identity-kicking-by-certs.ts @@ -11,22 +11,21 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {HttpBlock} from "../../../app/modules/bma/lib/dtos" -const co = require('co'); const assert = require('assert'); -const TestUser = require('./tools/TestUser').TestUser -const toolbox = require('./tools/toolbox'); const now = 1480000000; -let s1, cat, tac, tic, toc, tuc +let s1:TestingServer, cat:TestUser, tac:TestUser, tic:TestUser, toc:TestUser, tuc:TestUser describe("Identities kicking by certs", function() { - before(() => co(function *() { + before(async () => { - s1 = toolbox.server({ + s1 = NewTestingServer({ pair: { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' @@ -44,41 +43,41 @@ describe("Identities kicking by certs", function() { toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); tuc = new TestUser('tuc', { pub: '3conGDUXdrTGbQPMQQhEC4Ubu1MCAnFrAYvUaewbUhtk', sec: '5ks7qQ8Fpkin7ycXpxQSxxjVhs8VTzpM3vEBMqM7NfC1ZiFJ93uQryDcoM93Mj77T6hDAABdeHZJDFnkDb35bgiU'}, { server: s1 }); - yield s1.initDalBmaConnections(); - yield cat.createIdentity(); - yield tac.createIdentity(); - yield toc.createIdentity(); - yield cat.cert(tac); - yield cat.cert(toc); - yield tac.cert(cat); - yield tac.cert(toc); - yield toc.cert(cat); - yield toc.cert(tac); - yield cat.join(); - yield tac.join(); - yield toc.join(); - yield s1.commit({ time: now }); - yield s1.commit({ time: now + 3 }); - yield s1.commit({ time: now + 5 }); - yield tic.createIdentity(); - yield cat.cert(tic); - yield tac.cert(tic); - yield tic.join(); - yield tuc.createIdentity(); - yield s1.commit({ time: now + 8 }); - yield tic.cert(cat); - yield cat.cert(tuc); - yield tac.cert(tuc); - yield tuc.join(); - yield s1.commit({ time: now + 8 }); - yield tuc.cert(cat); - yield s1.commit({ time: now + 8 }); - yield s1.commit({ time: now + 8 }); - yield s1.commit({ time: now + 8 }); - yield cat.revoke(); - yield s1.commitWaitError({ time: now + 8, excluded: ['3conGDUXdrTGbQPMQQhEC4Ubu1MCAnFrAYvUaewbUhtk'] }, "ruleToBeKickedArePresent") - yield s1.commit({ time: now + 8 }); - })); + await s1.initDalBmaConnections(); + await cat.createIdentity(); + await tac.createIdentity(); + await toc.createIdentity(); + await cat.cert(tac); + await cat.cert(toc); + await tac.cert(cat); + await tac.cert(toc); + await toc.cert(cat); + await toc.cert(tac); + await cat.join(); + await tac.join(); + await toc.join(); + await s1.commit({ time: now }); + await s1.commit({ time: now + 3 }); + await s1.commit({ time: now + 5 }); + await tic.createIdentity(); + await cat.cert(tic); + await tac.cert(tic); + await tic.join(); + await tuc.createIdentity(); + await s1.commit({ time: now + 8 }); + await tic.cert(cat); + await cat.cert(tuc); + await tac.cert(tuc); + await tuc.join(); + await s1.commit({ time: now + 8 }); + await tuc.cert(cat); + await s1.commit({ time: now + 8 }); + await s1.commit({ time: now + 8 }); + await s1.commit({ time: now + 8 }); + await cat.revoke(); + await s1.commitWaitError({ time: now + 8, excluded: ['3conGDUXdrTGbQPMQQhEC4Ubu1MCAnFrAYvUaewbUhtk'] }, "ruleToBeKickedArePresent") + await s1.commit({ time: now + 8 }); + }) after(() => { return Promise.all([ @@ -86,14 +85,14 @@ describe("Identities kicking by certs", function() { ]) }) - it('block#7 should have kicked 2 member', () => s1.expectJSON('/blockchain/block/7', (res) => { + it('block#7 should have kicked 2 member', () => s1.expectJSON('/blockchain/block/7', (res:HttpBlock) => { assert.deepEqual(res.excluded, [ '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo' ]); })); - it('block#8 should have kicked 1 member', () => s1.expectJSON('/blockchain/block/8', (res) => { + it('block#8 should have kicked 1 member', () => s1.expectJSON('/blockchain/block/8', (res:HttpBlock) => { assert.deepEqual(res.excluded, [ 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd' ]); diff --git a/test/integration/identity-kicking.js b/test/integration/identity/identity-kicking.ts similarity index 53% rename from test/integration/identity-kicking.js rename to test/integration/identity/identity-kicking.ts index 7d1b024b8ce8ffdc149197477f8c4d5b66a09051..dfccca03d6f85b5e7187909582a59be3cdc5ddf5 100644 --- a/test/integration/identity-kicking.js +++ b/test/integration/identity/identity-kicking.ts @@ -11,21 +11,17 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {HttpRequirements} from "../../../app/modules/bma/lib/dtos" +import {BmaDependency} from "../../../app/modules/bma/index" +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {Underscore} from "../../../app/lib/common-libs/underscore" +import {ProverDependency} from "../../../app/modules/prover/index" +import {shutDownEngine} from "../tools/shutdown-engine" +import {expectAnswer} from "../tools/http-expect" -const _ = require('underscore'); -const co = require('co'); const should = require('should'); -const duniter = require('../../index'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const TestUser = require('./tools/TestUser').TestUser -const constants = require('../../app/lib/constants'); const rp = require('request-promise'); -const httpTest = require('./tools/http'); -const commit = require('./tools/commit'); -const shutDownEngine = require('./tools/shutDownEngine'); - -const expectAnswer = httpTest.expectAnswer; const MEMORY_MODE = true; const commonConf = { @@ -39,70 +35,65 @@ const commonConf = { sigQty: 1 }; -let s1, cat, tac, toc - -const commitS1 = (opts) => commit(s1)(opts) +let s1:TestingServer, cat:TestUser, tac:TestUser, toc:TestUser describe("Identities kicking", function() { - before(function() { - - return co(function *() { - - s1 = duniter( - '/bb11', - MEMORY_MODE, - _.extend({ - port: '8561', - pair: { - pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', - sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' - } - }, commonConf)); - - cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); - tac = new TestUser('tac', { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'}, { server: s1 }); - toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); - - const now = 1400000000 - yield s1.initWithDAL().then(bma).then((bmapi) => bmapi.openConnections()); - require('../../app/modules/prover').ProverDependency.duniter.methods.hookServer(s1); - yield cat.createIdentity(); - yield tac.createIdentity(); - yield cat.cert(tac); - yield tac.cert(cat); - yield cat.join(); - yield tac.join(); - yield commitS1({ - time: now - }); - yield commitS1({ - time: now + 2000 - }); - yield commitS1({ - time: now + 2000 - }); - // Update their membership - yield cat.join(); - yield tac.join(); - // toc joins thereafter - yield toc.createIdentity(); - yield toc.join(); - yield cat.cert(toc); - yield tac.cert(toc); - yield commitS1({ - time: now + 2000 - }); - yield toc.cert(cat); - yield commitS1({ - time: now + 5000 - }); - yield commitS1({ - time: now + 5000 - }); - yield commitS1({ - time: now + 5000 - }); + before(async () => { + + s1 = NewTestingServer( + Underscore.extend({ + name: 'bb11', + memory: MEMORY_MODE, + port: '8561', + pair: { + pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', + sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' + } + }, commonConf)); + + cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); + tac = new TestUser('tac', { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'}, { server: s1 }); + toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); + + const now = 1400000000 + await s1.initWithDAL().then(BmaDependency.duniter.methods.bma).then((bmapi) => bmapi.openConnections()); + ProverDependency.duniter.methods.hookServer(s1._server) + await cat.createIdentity(); + await tac.createIdentity(); + await cat.cert(tac); + await tac.cert(cat); + await cat.join(); + await tac.join(); + await s1.commit({ + time: now + }); + await s1.commit({ + time: now + 2000 + }); + await s1.commit({ + time: now + 2000 + }); + // Update their membership + await cat.join(); + await tac.join(); + // toc joins thereafter + await toc.createIdentity(); + await toc.join(); + await cat.cert(toc); + await tac.cert(toc); + await s1.commit({ + time: now + 2000 + }); + await toc.cert(cat); + await s1.commit({ + time: now + 5000 + }); + await s1.commit({ + time: now + 5000 + }); + await s1.commit({ + time: now + 5000 }); }); @@ -117,7 +108,7 @@ describe("Identities kicking", function() { */ it('membershipExpiresIn should be positive for cat (actualized member)', function() { - return expectAnswer(rp('http://127.0.0.1:8561/wot/requirements/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', { json: true }), (res) => { + return expectAnswer(rp('http://127.0.0.1:8561/wot/requirements/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', { json: true }), (res:HttpRequirements) => { res.should.have.property('identities').length(1); res.identities[0].should.have.property('pubkey').equal('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); res.identities[0].should.have.property('uid').equal('cat'); @@ -127,7 +118,7 @@ describe("Identities kicking", function() { }); it('membershipExpiresIn should be positive for toc (member)', function() { - return expectAnswer(rp('http://127.0.0.1:8561/wot/requirements/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', { json: true }), (res) => { + return expectAnswer(rp('http://127.0.0.1:8561/wot/requirements/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', { json: true }), (res:HttpRequirements) => { res.should.have.property('identities').length(1); res.identities[0].should.have.property('pubkey').equal('DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo'); res.identities[0].should.have.property('uid').equal('toc'); @@ -137,7 +128,7 @@ describe("Identities kicking", function() { }); it('membershipExpiresIn should equal 0 for a kicked member', function() { - return expectAnswer(rp('http://127.0.0.1:8561/wot/requirements/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', { json: true }), (res) => { + return expectAnswer(rp('http://127.0.0.1:8561/wot/requirements/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', { json: true }), (res:HttpRequirements) => { res.should.have.property('identities').length(1); res.identities[0].should.have.property('pubkey').equal('2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc'); res.identities[0].should.have.property('uid').equal('tac'); diff --git a/test/integration/lookup.js b/test/integration/identity/identity-lookup.ts similarity index 72% rename from test/integration/lookup.js rename to test/integration/identity/identity-lookup.ts index 3ce8d834f1093fdd4cdae8a57747cf52db26ef61..574005094ddaa2372103c09844b52c49df265156 100644 --- a/test/integration/lookup.js +++ b/test/integration/identity/identity-lookup.ts @@ -11,16 +11,15 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {BmaDependency} from "../../../app/modules/bma/index" +import {Underscore} from "../../../app/lib/common-libs/underscore" +import {HttpLookup, HttpMemberships} from "../../../app/modules/bma/lib/dtos" +import {shutDownEngine} from "../tools/shutdown-engine" +import {expectAnswer} from "../tools/http-expect" -const _ = require('underscore'); -const co = require('co'); -const duniter = require('../../index'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const TestUser = require('./tools/TestUser').TestUser const rp = require('request-promise'); -const httpTest = require('./tools/http'); -const shutDownEngine = require('./tools/shutDownEngine'); const MEMORY_MODE = true; const commonConf = { @@ -28,16 +27,16 @@ const commonConf = { currency: 'bb' }; -let s1, cat, tic1, tic2 +let s1:TestingServer, cat:TestUser, tic1:TestUser, tic2:TestUser describe("Lookup identity grouping", () => { - before(() => co(function *() { + before(async () => { - s1 = duniter( - 'bb12', - MEMORY_MODE, - _.extend({ + s1 = NewTestingServer( + Underscore.extend({ + name: 'bb12', + memory: MEMORY_MODE, port: '4452', pair: { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', @@ -50,22 +49,22 @@ describe("Lookup identity grouping", () => { tic2 = new TestUser('tic2', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s1 }); // Server initialization - yield s1.initWithDAL().then(bma).then((bmapi) => bmapi.openConnections()); + await s1.initWithDAL().then(BmaDependency.duniter.methods.bma).then((bmapi) => bmapi.openConnections()); // cat is publishing its identity, no problem - yield cat.createIdentity(); + await cat.createIdentity(); // tic1 is publishing its identity - yield tic1.createIdentity(); + await tic1.createIdentity(); // tic2 is publishing its identity, but he has **the same pubkey as tic1**. // This is OK on the protocol side, but the lookup should group the 2 identities // under the same pubkey - yield tic2.createIdentity(); + await tic2.createIdentity(); - yield cat.join(); - yield tic1.join(); - })); + await cat.join(); + await tic1.join(); + }) after(() => { return Promise.all([ @@ -73,7 +72,7 @@ describe("Lookup identity grouping", () => { ]) }) - it('cat should have only 1 identity in 1 pubkey', () => httpTest.expectAnswer(rp('http://127.0.0.1:4452/wot/lookup/cat', { json: true }), (res) => { + it('cat should have only 1 identity in 1 pubkey', () => expectAnswer(rp('http://127.0.0.1:4452/wot/lookup/cat', { json: true }), (res:HttpLookup) => { res.should.have.property('results').length(1); // cat pubkey res.results[0].should.have.property('pubkey').equal('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); @@ -83,7 +82,7 @@ describe("Lookup identity grouping", () => { res.results[0].uids[0].should.have.property('uid').equal('cat'); })); - it('tic should have only 2 identities in 1 pubkey', () => httpTest.expectAnswer(rp('http://127.0.0.1:4452/wot/lookup/tic', { json: true }), (res) => { + it('tic should have only 2 identities in 1 pubkey', () => expectAnswer(rp('http://127.0.0.1:4452/wot/lookup/tic', { json: true }), (res:HttpLookup) => { // We want to have only 1 result for the 2 identities res.should.have.property('results').length(1); // because they share the same pubkey @@ -96,11 +95,11 @@ describe("Lookup identity grouping", () => { res.results[0].uids[1].should.have.property('uid').equal('tic2'); })); - it('should exist 2 pending memberships', () => httpTest.expectAnswer(rp('http://127.0.0.1:4452/wot/pending', { json: true }), (res) => { + it('should exist 2 pending memberships', () => expectAnswer(rp('http://127.0.0.1:4452/wot/pending', { json: true }), (res:HttpMemberships) => { res.should.have.property('memberships').length(2); res.memberships[0].should.have.property('pubkey').equal('DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV'); res.memberships[0].should.have.property('uid').equal('tic1'); - res.memberships[0].should.have.property('version').equal(0); + res.memberships[0].should.have.property('version').equal(10); res.memberships[0].should.have.property('currency').equal('bb'); res.memberships[0].should.have.property('membership').equal('IN'); res.memberships[0].should.have.property('blockNumber').equal(0); diff --git a/test/integration/identity-pulling.js b/test/integration/identity/identity-pulling.ts similarity index 69% rename from test/integration/identity-pulling.js rename to test/integration/identity/identity-pulling.ts index 8d3058b125856cfadedd0e9eb15eb775f15355bb..6e3640348321b9a034509c8f911ebf6fe44a478d 100644 --- a/test/integration/identity-pulling.js +++ b/test/integration/identity/identity-pulling.ts @@ -11,28 +11,27 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {Underscore} from "../../../app/lib/common-libs/underscore" +import {HttpRequirements} from "../../../app/modules/bma/lib/dtos" +import {CrawlerDependency} from "../../../app/modules/crawler/index" -const _ = require('underscore'); -const co = require('co'); const assert = require('assert'); -const TestUser = require('./tools/TestUser').TestUser -const commit = require('./tools/commit'); -const toolbox = require('./tools/toolbox'); -let s1, s2, cat1, tac1, toc2, tic2, tuc2 +let s1:TestingServer, s2:TestingServer, cat1:TestUser, tac1:TestUser, toc2:TestUser, tic2:TestUser, tuc2:TestUser describe("Identity pulling", function() { - before(() => co(function*() { + before(async () => { - s1 = toolbox.server({ + s1 = NewTestingServer({ pair: { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' } }); - s2 = toolbox.server({ + s2 = NewTestingServer({ pair: { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE' @@ -45,17 +44,17 @@ describe("Identity pulling", function() { tic2 = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s2 }); tuc2 = new TestUser('tuc', { pub: '3conGDUXdrTGbQPMQQhEC4Ubu1MCAnFrAYvUaewbUhtk', sec: '5ks7qQ8Fpkin7ycXpxQSxxjVhs8VTzpM3vEBMqM7NfC1ZiFJ93uQryDcoM93Mj77T6hDAABdeHZJDFnkDb35bgiU'}, { server: s2 }); - yield s1.prepareForNetwork(); - yield s2.prepareForNetwork(); + await s1.prepareForNetwork(); + await s2.prepareForNetwork(); // Publishing identities - yield cat1.createIdentity(); - yield tac1.createIdentity(); - yield cat1.cert(tac1); - yield tac1.cert(cat1); - yield cat1.join(); - yield tac1.join(); - })); + await cat1.createIdentity(); + await tac1.createIdentity(); + await cat1.cert(tac1); + await tac1.cert(cat1); + await cat1.join(); + await tac1.join(); + }) after(() => { return Promise.all([ @@ -64,48 +63,48 @@ describe("Identity pulling", function() { ]) }) - it('toc, tic and tuc can create their account on s2', () => co(function*() { - yield toc2.createIdentity(); - yield tic2.createIdentity(); - yield tuc2.createIdentity(); - yield toc2.join(); - yield tic2.join(); - yield tuc2.join(); + it('toc, tic and tuc can create their account on s2', async () => { + await toc2.createIdentity(); + await tic2.createIdentity(); + await tuc2.createIdentity(); + await toc2.join(); + await tic2.join(); + await tuc2.join(); // 2 certs for toc - yield cat1.cert(toc2, s2, s2); - yield tac1.cert(toc2, s2, s2); + await cat1.cert(toc2, s2, s2); + await tac1.cert(toc2, s2, s2); // 1 certs for tic - yield cat1.cert(tic2, s2, s2); + await cat1.cert(tic2, s2, s2); // 0 certs for tuc // tic2 also revokes its pending identity - yield tic2.revoke() - })); + await tic2.revoke() + }) - it('toc should not be known of s1', () => co(function*() { - yield s1.expectError('/wot/lookup/toc', 404) - })); + it('toc should not be known of s1', async () => { + await s1.expectError('/wot/lookup/toc', 404) + }) - it('tic should not be known of s1', () => co(function*() { - yield s1.expectError('/wot/lookup/tic', 404) - })); + it('tic should not be known of s1', async () => { + await s1.expectError('/wot/lookup/tic', 404) + }) - it('tuc should not be known of s1', () => co(function*() { - yield s1.expectError('/wot/lookup/tuc', 404) - })); + it('tuc should not be known of s1', async () => { + await s1.expectError('/wot/lookup/tuc', 404) + }) - it('toc should have 2 certs on server2', () => co(function*() { - yield s2.expectThat('/wot/requirements-of-pending/2', (json) => { + it('toc should have 2 certs on server2', async () => { + await s2.expectThat('/wot/requirements-of-pending/2', (json:HttpRequirements) => { assert.equal(json.identities.length, 1) assert.equal(json.identities[0].pubkey, 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo') assert.equal(json.identities[0].uid, 'toc') assert.equal(json.identities[0].pendingCerts.length, 2) assert.equal(json.identities[0].pendingMemberships.length, 1) }) - })); + }) - it('tic should have 1 certs on server2', () => co(function*() { - yield s2.expectThat('/wot/requirements-of-pending/1', (json) => { + it('tic should have 1 certs on server2', async () => { + await s2.expectThat('/wot/requirements-of-pending/1', (json:HttpRequirements) => { assert.equal(json.identities.length, 2) assert.equal(json.identities[1].pubkey, 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV') @@ -118,18 +117,18 @@ describe("Identity pulling", function() { assert.equal(json.identities[0].pendingCerts.length, 2) assert.equal(json.identities[0].pendingMemberships.length, 1) }) - })); + }) - it('s1 should be able to pull sandbox data from s2', () => co(function*() { + it('s1 should be able to pull sandbox data from s2', async () => { - yield s2.sharePeeringWith(s1) - const pullSandbox = require('../../app/modules/crawler').CrawlerDependency.duniter.methods.pullSandbox - yield pullSandbox(s1) - yield pullSandbox(s1) + await s2.sharePeeringWith(s1) + const pullSandbox = CrawlerDependency.duniter.methods.pullSandbox + await pullSandbox(s1._server) + await pullSandbox(s1._server) - yield s1.expectThat('/wot/requirements-of-pending/1', (json) => { + await s1.expectThat('/wot/requirements-of-pending/1', (json:HttpRequirements) => { - json.identities = _.sortBy(json.identities, 'pubkey') + json.identities = Underscore.sortBy(json.identities, 'pubkey') assert.equal(json.identities.length, 4) assert.equal(json.identities[3].pubkey, 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd') @@ -156,6 +155,6 @@ describe("Identity pulling", function() { assert.equal(json.identities[1].pendingCerts.length, 2) assert.equal(json.identities[1].pendingMemberships.length, 1) }) - })); + }) -}); +}) diff --git a/test/integration/revocation-test.js b/test/integration/identity/identity-revocation-test.ts similarity index 55% rename from test/integration/revocation-test.js rename to test/integration/identity/identity-revocation-test.ts index 0dad545100d4a80187b62c296c5a17d21910b143..a624b121767e2519886899257f8175201dc23246 100644 --- a/test/integration/revocation-test.js +++ b/test/integration/identity/identity-revocation-test.ts @@ -13,20 +13,18 @@ "use strict"; -const _ = require('underscore'); -const co = require('co'); +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {BmaDependency} from "../../../app/modules/bma/index" +import {Underscore} from "../../../app/lib/common-libs/underscore" +import {HttpLookup, HttpMembers} from "../../../app/modules/bma/lib/dtos" +import {shutDownEngine} from "../tools/shutdown-engine" +import {expectAnswer} from "../tools/http-expect" + const should = require('should'); -const duniter = require('../../index'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const TestUser = require('./tools/TestUser').TestUser const rp = require('request-promise'); -const httpTest = require('./tools/http'); -const commit = require('./tools/commit'); -const shutDownEngine = require('./tools/shutDownEngine'); - -const expectAnswer = httpTest.expectAnswer; -require('../../app/modules/bma').BmaDependency.duniter.methods.noLimit(); // Disables the HTTP limiter +BmaDependency.duniter.methods.noLimit(); // Disables the HTTP limiter const MEMORY_MODE = true; const commonConf = { @@ -40,64 +38,59 @@ const commonConf = { avgGenTime: 300 }; -let s1, s2, cat, tic, toc, tacOnS1, tacOnS2 - -const commitS1 = (opts) => commit(s1)(opts) +let s1:TestingServer, s2:TestingServer, cat:TestUser, tic:TestUser, toc:TestUser, tacOnS1:TestUser, tacOnS2:TestUser describe("Revocation", function() { - before(function() { - - return co(function *() { - - s1 = duniter( - '/bb12', - MEMORY_MODE, - _.extend({ - port: '9964', - pair: { - pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', - sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F' - } - }, commonConf)); - - s2 = duniter( - '/bb13', - MEMORY_MODE, - _.extend({ - port: '9965', - pair: { - pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', - sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' - } - }, commonConf)); - - cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); - tic = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s1 }); - toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); - tacOnS1 = new TestUser('tac', { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'}, { server: s1 }); - tacOnS2 = new TestUser('tac', { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'}, { server: s2 }); - - const now = 1400000000 - yield s1.initWithDAL().then(bma).then((bmapi) => bmapi.openConnections()); - yield s2.initWithDAL().then(bma).then((bmapi) => bmapi.openConnections()); - yield cat.createIdentity(); - yield tic.createIdentity(); - yield toc.createIdentity(); - yield cat.cert(tic); - yield tic.cert(cat); - yield tic.cert(toc); - yield toc.cert(tic); - yield cat.join(); - yield tic.join(); - yield toc.join(); - yield commitS1({ time: now }); - - // We have the following WoT: - /** - * cat <-> tic <-> toc - */ - }); + before(async () => { + + s1 = NewTestingServer( + Underscore.extend({ + name: 'bb12', + memory: MEMORY_MODE, + port: '9964', + pair: { + pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', + sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F' + } + }, commonConf)); + + s2 = NewTestingServer( + Underscore.extend({ + name: 'bb13', + memory: MEMORY_MODE, + port: '9965', + pair: { + pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', + sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' + } + }, commonConf)); + + cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); + tic = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s1 }); + toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); + tacOnS1 = new TestUser('tac', { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'}, { server: s1 }); + tacOnS2 = new TestUser('tac', { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'}, { server: s2 }); + + const now = 1400000000 + await s1.initWithDAL().then(BmaDependency.duniter.methods.bma).then((bmapi) => bmapi.openConnections()); + await s2.initWithDAL().then(BmaDependency.duniter.methods.bma).then((bmapi) => bmapi.openConnections()); + await cat.createIdentity(); + await tic.createIdentity(); + await toc.createIdentity(); + await cat.cert(tic); + await tic.cert(cat); + await tic.cert(toc); + await toc.cert(tic); + await cat.join(); + await tic.join(); + await toc.join(); + await s1.commit({ time: now }); + + // We have the following WoT: + /** + * cat <-> tic <-> toc + */ }); after(() => { @@ -108,13 +101,13 @@ describe("Revocation", function() { }) it('should have 3 members', function() { - return expectAnswer(rp('http://127.0.0.1:9964/wot/members', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:9964/wot/members', { json: true }), function(res:HttpMembers) { res.should.have.property('results').length(3); - _.pluck(res.results, 'uid').sort().should.deepEqual(['cat', 'tic', 'toc']); + Underscore.pluck(res.results, 'uid').sort().should.deepEqual(['cat', 'tic', 'toc']); }); }); - it('cat should not be revoked yet', () => expectAnswer(rp('http://127.0.0.1:9964/wot/lookup/cat', { json: true }), function(res) { + it('cat should not be revoked yet', () => expectAnswer(rp('http://127.0.0.1:9964/wot/lookup/cat', { json: true }), function(res:HttpLookup) { res.should.have.property('results').length(1); res.results[0].should.have.property('uids').length(1); res.results[0].uids[0].should.have.property('uid').equal('cat'); @@ -123,9 +116,9 @@ describe("Revocation", function() { res.results[0].uids[0].should.have.property('revocation_sig').equal(null); })); - it('sending a revocation for cat should be displayed', () => co(function *() { - yield cat.revoke(); - return expectAnswer(rp('http://127.0.0.1:9964/wot/lookup/cat', { json: true }), function(res) { + it('sending a revocation for cat should be displayed', async () => { + await cat.revoke(); + return expectAnswer(rp('http://127.0.0.1:9964/wot/lookup/cat', { json: true }), function(res:HttpLookup) { res.should.have.property('results').length(1); res.results[0].should.have.property('uids').length(1); res.results[0].uids[0].should.have.property('uid').equal('cat'); @@ -134,14 +127,14 @@ describe("Revocation", function() { res.results[0].uids[0].should.have.property('revocation_sig').not.equal(null); res.results[0].uids[0].should.have.property('revocation_sig').not.equal(''); }); - })); + }) - it('sending a revocation for tac should add an identity', () => co(function *() { - yield tacOnS1.createIdentity(); - const idty = yield tacOnS1.lookup(tacOnS1.pub); - yield tacOnS2.revoke(idty); + it('sending a revocation for tac should add an identity', async () => { + await tacOnS1.createIdentity(); + const idty = await tacOnS1.lookup(tacOnS1.pub); + await tacOnS2.revoke(idty); // On S1 server, tac is known as normal identity - yield expectAnswer(rp('http://127.0.0.1:9964/wot/lookup/tac', { json: true }), function(res) { + await expectAnswer(rp('http://127.0.0.1:9964/wot/lookup/tac', { json: true }), function(res:HttpLookup) { res.should.have.property('results').length(1); res.results[0].should.have.property('uids').length(1); res.results[0].uids[0].should.have.property('uid').equal('tac'); @@ -150,7 +143,7 @@ describe("Revocation", function() { res.results[0].uids[0].should.have.property('revocation_sig').equal(null); }); // On S2 server, tac is known as identity with revocation pending (not written! so `revoked` field is false) - yield expectAnswer(rp('http://127.0.0.1:9965/wot/lookup/tac', { json: true }), function(res) { + await expectAnswer(rp('http://127.0.0.1:9965/wot/lookup/tac', { json: true }), function(res:HttpLookup) { res.should.have.property('results').length(1); res.results[0].should.have.property('uids').length(1); res.results[0].uids[0].should.have.property('uid').equal('tac'); @@ -159,12 +152,12 @@ describe("Revocation", function() { res.results[0].uids[0].should.have.property('revocation_sig').not.equal(null); res.results[0].uids[0].should.have.property('revocation_sig').not.equal(''); }); - })); + }) - it('if we commit a revocation, cat should be revoked', () => co(function *() { - yield commitS1({ revoked: [], excluded: [] }); - yield commitS1(); - return expectAnswer(rp('http://127.0.0.1:9964/wot/lookup/cat', { json: true }), function(res) { + it('if we commit a revocation, cat should be revoked', async () => { + await s1.commit({ revoked: [], excluded: [] }); + await s1.commit(); + return expectAnswer(rp('http://127.0.0.1:9964/wot/lookup/cat', { json: true }), function(res:HttpLookup) { res.should.have.property('results').length(1); res.results[0].should.have.property('uids').length(1); res.results[0].uids[0].should.have.property('uid').equal('cat'); @@ -173,33 +166,33 @@ describe("Revocation", function() { res.results[0].uids[0].should.have.property('revocation_sig').not.equal(null); res.results[0].uids[0].should.have.property('revocation_sig').not.equal(''); }); - })); + }) it('should have 2 members', function() { - return expectAnswer(rp('http://127.0.0.1:9964/wot/members', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:9964/wot/members', { json: true }), function(res:HttpMembers) { res.should.have.property('results').length(2); - _.pluck(res.results, 'uid').sort().should.deepEqual(['tic','toc']); + Underscore.pluck(res.results, 'uid').sort().should.deepEqual(['tic','toc']); }); }); - it('cat should not be able to join back', () => co(function *() { + it('cat should not be able to join back', async () => { try { - yield cat.join(); + await cat.join(); } catch (e) { should.exists(e); } - yield commitS1(); - return expectAnswer(rp('http://127.0.0.1:9964/wot/members', { json: true }), function(res) { + await s1.commit(); + return expectAnswer(rp('http://127.0.0.1:9964/wot/members', { json: true }), function(res:HttpMembers) { res.should.have.property('results').length(2); - _.pluck(res.results, 'uid').sort().should.deepEqual(['tic','toc']); + Underscore.pluck(res.results, 'uid').sort().should.deepEqual(['tic','toc']); }); - })); + }) - it('if we revert the commit, cat should not be revoked', () => co(function *() { - yield s1.revert(); - yield s1.revert(); - yield s1.dal.blockDAL.exec('DELETE FROM block WHERE fork AND number >= 2') - return expectAnswer(rp('http://127.0.0.1:9964/wot/lookup/cat', { json: true }), function(res) { + it('if we revert the commit, cat should not be revoked', async () => { + await s1.revert(); + await s1.revert(); + await s1.dal.blockDAL.removeForkBlockAboveOrEqual(2) + return expectAnswer(rp('http://127.0.0.1:9964/wot/lookup/cat', { json: true }), function(res:HttpLookup) { res.should.have.property('results').length(1); res.results[0].should.have.property('uids').length(1); res.results[0].uids[0].should.have.property('uid').equal('cat'); @@ -208,11 +201,11 @@ describe("Revocation", function() { res.results[0].uids[0].should.have.property('revocation_sig').equal(null); // We loose the revocation res.results[0].uids[0].should.have.property('revocation_sig').equal(null); }); - })); + }) - it('if we commit again, cat should NOT be revoked (we have lost the revocation)', () => co(function *() { - yield commitS1(); - return expectAnswer(rp('http://127.0.0.1:9964/wot/lookup/cat', { json: true }), function(res) { + it('if we commit again, cat should NOT be revoked (we have lost the revocation)', async () => { + await s1.commit(); + return expectAnswer(rp('http://127.0.0.1:9964/wot/lookup/cat', { json: true }), function(res:HttpLookup) { res.should.have.property('results').length(1); res.results[0].should.have.property('uids').length(1); res.results[0].uids[0].should.have.property('uid').equal('cat'); @@ -221,6 +214,6 @@ describe("Revocation", function() { res.results[0].uids[0].should.have.property('revocation_sig').equal(null); // We loose the revocation res.results[0].uids[0].should.have.property('revocation_sig').equal(null); }); - })); + }) -}); +}) diff --git a/test/integration/identity-same-pubkey.js b/test/integration/identity/identity-same-pubkey.ts similarity index 83% rename from test/integration/identity-same-pubkey.js rename to test/integration/identity/identity-same-pubkey.ts index 1e1949422b208c4bafd7e4a1cc604f1b96192798..e3e0fc2cc63477fb63fa6ba3348241d8eaa45ca0 100644 --- a/test/integration/identity-same-pubkey.js +++ b/test/integration/identity/identity-same-pubkey.ts @@ -11,22 +11,19 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {TestUser} from "../tools/TestUser" +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {HttpLookup} from "../../../app/modules/bma/lib/dtos" -const co = require('co'); const should = require('should'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const TestUser = require('./tools/TestUser').TestUser -const commit = require('./tools/commit'); -const toolbox = require('./tools/toolbox'); -let s1, cat1, cat2, catb +let s1:TestingServer, cat1:TestUser, cat2:TestUser, catb:TestUser describe("Identities with shared pubkey", function() { - before(() => co(function*() { + before(async () => { - s1 = toolbox.server({ + s1 = NewTestingServer({ pair: { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' @@ -37,18 +34,18 @@ describe("Identities with shared pubkey", function() { cat2 = new TestUser('cat2', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); catb = new TestUser('cat1', { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'}, { server: s1 }); - yield s1.initDalBmaConnections(); + await s1.initDalBmaConnections(); - yield cat2.createIdentity(); + await cat2.createIdentity(); // Early certification, to have only one matching 'HgTT' key at this moment - yield catb.cert(cat2); + await catb.cert(cat2); // catb gets certified by 'HgTT' - yield cat1.createIdentity(); - yield catb.createIdentity(); - yield cat1.cert(catb); - })); + await cat1.createIdentity(); + await catb.createIdentity(); + await cat1.cert(catb); + }) after(() => { return Promise.all([ @@ -56,13 +53,13 @@ describe("Identities with shared pubkey", function() { ]) }) - it('should exit 2 pubkey result', () => s1.expect('/wot/lookup/cat', (res) => { + it('should exit 2 pubkey result', () => s1.expect('/wot/lookup/cat', (res:HttpLookup) => { res.results.should.have.length(2); res.results[0].should.have.property('pubkey').equal('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); res.results[1].should.have.property('pubkey').equal('2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc'); })); - it('pubkey HgTT should have signed 1 key', () => s1.expect('/wot/lookup/cat', (res) => { + it('pubkey HgTT should have signed 1 key', () => s1.expect('/wot/lookup/cat', (res:HttpLookup) => { res.results.should.have.length(2); res.results[0].should.have.property('signed').length(1); const pubkey_hgtt = res.results[0]; diff --git a/test/integration/identity/identity-several-tests.ts b/test/integration/identity/identity-several-tests.ts new file mode 100644 index 0000000000000000000000000000000000000000..67621a0f15c33cba61d79a0986a34d7f3960419d --- /dev/null +++ b/test/integration/identity/identity-several-tests.ts @@ -0,0 +1,304 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import {TestUser} from "../tools/TestUser" +import {BmaDependency} from "../../../app/modules/bma/index" +import {HttpBlock, HttpLookup, HttpSigned, HttpSummary} from "../../../app/modules/bma/lib/dtos" +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {Underscore} from "../../../app/lib/common-libs/underscore" +import {shutDownEngine} from "../tools/shutdown-engine" +import {shouldFail} from "../../unit-tools" +import {expectAnswer} from "../tools/http-expect" + +const should = require('should'); +const assert = require('assert'); +const constants = require('../../../app/lib/constants'); +const jspckg = require('../../../package'); +const rp = require('request-promise'); + +const MEMORY_MODE = true; + +BmaDependency.duniter.methods.noLimit(); // Disables the HTTP limiter + +describe("Integration", function() { + + let node1:TestingServer, cat:TestUser, tac:TestUser, tic:TestUser, toc:TestUser + + before(async () => { + node1 = NewTestingServer({ + name: 'db1', + memory: MEMORY_MODE, + currency: 'bb', ipv4: 'localhost', port: 9999, remoteipv4: 'localhost', remoteport: 9999, httplogs: false, + rootoffset: 0, + sigQty: 1, sigPeriod: 0, + pair: { + pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', + sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' + } + }) + + cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: node1 }) + tac = new TestUser('tac', { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'}, { server: node1 }) + tic = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: node1 }) + toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: node1 }) + await node1.initWithDAL().then(BmaDependency.duniter.methods.bma).then((bmapi) => bmapi.openConnections()); + }) + + describe("Node 1", function() { + + describe("Testing technical API", function(){ + + it('/node/summary should give package.json version', () => node1.expectJSON('/node/summary', (summary:HttpSummary) => { + should.exists(summary); + should.exists(summary.duniter); + should.exists(summary.duniter.software); + should.exists(summary.duniter.version); + assert.equal(summary.duniter.software, "duniter"); + assert.equal(summary.duniter.version, jspckg.version); + })) + }) + + describe("Testing malformed documents", () => { + + it('should not have crashed because of wrong tx', async () => { + const malformedTransaction = "Version: 2\n" + + "Type: Transaction\n" + + "Currency: null\n" + + "Issuers:\n" + + "G2CBgZBPLe6FSFUgpx2Jf1Aqsgta6iib3vmDRA1yLiqU\n" + + "Inputs:\n" + + "0:T:1536:539CB0E60CD5F55CF1BE96F067E73BF55C052112:1.0\n" + + "Outputs:Comment: mon comments\n"; + + await shouldFail(node1.post('/tx/process', { + json: { + transaction: malformedTransaction + } + }), '400 - {"ucode":1106,"message":"Requires a transaction"}') + }) + }) + + describe("Lookup on", function(){ + + before(async () => { + + // Self certifications + await cat.createIdentity(); + await tac.createIdentity(); + await tic.createIdentity(); + await toc.createIdentity(); + // Certifications + await cat.cert(tac); + }); + + describe("identities collisions", () => { + + it("sending same identity should fail", async () => { + + // We send again the same + try { + await tic.createIdentity(); + throw 'Should have thrown an error'; + } catch (e) { + JSON.parse(e).ucode.should.equal(constants.ERRORS.ALREADY_UP_TO_DATE.uerr.ucode); + } + }) + + it("sending same identity (again) should fail", async () => { + + // We send again the same + try { + await tic.createIdentity(); + throw 'Should have thrown an error'; + } catch (e) { + JSON.parse(e).ucode.should.equal(constants.ERRORS.ALREADY_UP_TO_DATE.uerr.ucode); + } + }) + }); + + describe("user cat", function(){ + + it('should give only 1 result', () => node1.expectJSON('/wot/lookup/cat', (res:HttpLookup) => { + should.exists(res); + assert.equal(res.results.length, 1); + })); + + it('should have sent 1 signature', () => node1.expectJSON('/wot/lookup/cat', (res:HttpLookup) => { + should.exists(res); + assert.equal(res.results[0].signed.length, 1); + should.exists(res.results[0].signed[0].isMember); + should.exists(res.results[0].signed[0].wasMember); + assert.equal(res.results[0].signed[0].isMember, false); + assert.equal(res.results[0].signed[0].wasMember, false); + })); + }); + + describe("user tac", function(){ + + it('should give only 1 result', () => node1.expectJSON('/wot/lookup/tac', (res:HttpLookup) => { + should.exists(res); + assert.equal(res.results.length, 1); + })); + + it('should have 1 signature', () => node1.expectJSON('/wot/lookup/tac', (res:HttpLookup) => { + should.exists(res); + assert.equal(res.results[0].uids[0].others.length, 1); + })); + + it('should have sent 0 signature', () => node1.expectJSON('/wot/lookup/tac', (res:HttpLookup) => { + should.exists(res); + assert.equal(res.results[0].signed.length, 0); + })); + }); + + it('toc should give only 1 result', () => node1.expectJSON('/wot/lookup/toc', (res:HttpLookup) => { + should.exists(res); + assert.equal(res.results.length, 1); + })); + + it('tic should give only 1 result', () => node1.expectJSON('/wot/lookup/tic', (res:HttpLookup) => { + should.exists(res); + assert.equal(res.results.length, 1); + })); + }); + }); + + describe("Testing leavers", function(){ + + let node3:TestingServer, cat:TestUser, tac:TestUser, tic:TestUser, toc:TestUser + + before(async () => { + + node3 = NewTestingServer({ + name: 'db3', + memory: MEMORY_MODE, + currency: 'dd', ipv4: 'localhost', port: 9997, remoteipv4: 'localhost', remoteport: 9997, httplogs: false, + rootoffset: 0, + sigQty: 1, sigPeriod: 0, + pair: { + pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', + sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' + } + }); + + cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: node3 }); + tac = new TestUser('tac', { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'}, { server: node3 }); + tic = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: node3 }); + toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: node3 }); + + await node3.initWithDAL().then(BmaDependency.duniter.methods.bma).then((bmapi) => bmapi.openConnections()); + const now = 1482220000; + + // Self certifications + await cat.createIdentity(); + await tac.createIdentity(); + await tic.createIdentity(); + await toc.createIdentity(); + await cat.cert(tac); + await cat.cert(tic); + await cat.cert(toc); + await tac.cert(cat); + await tac.cert(tic); + await tic.cert(cat); + await tic.cert(tac); + await toc.cert(cat); + await cat.join(); + await tac.join(); + await tic.join(); + await toc.join(); + await node3.commit({ + time: now + }); + await node3.commit({ + time: now + }); + await toc.leave(); + await node3.commit({ + time: now + }); + await tac.cert(toc); + await tic.cert(toc); + await toc.cert(tic); // Should be taken in 1 block + await toc.cert(tac); // Should be taken in 1 other block + await node3.commit({ + time: now + 200 + }); + await node3.commit({ + time: now + 200 + }); + await node3.commit({ + time: now + 200 + }); + }); + + after(() => { + return Promise.all([ + shutDownEngine(node3) + ]) + }) + + it('toc should give only 1 result with 3 certification by others', () => expectAnswer(rp('http://127.0.0.1:9997/wot/lookup/toc', { json: true }), function(res:HttpLookup) { + should.exists(res); + assert.equal(res.results.length, 1); + assert.equal(res.results[0].uids[0].others.length, 3); + })); + + it('tic should give only 1 results', () => expectAnswer(rp('http://127.0.0.1:9997/wot/lookup/tic', { json: true }), function(res:HttpLookup) { + should.exists(res); + const uids = Underscore.pluck(res.results[0].signed, 'uid'); + const uidsShould = ["cat", "tac", "toc"]; + uids.sort(); + uidsShould.sort(); + assert.deepEqual(uids, uidsShould); + assert.equal(res.results.length, 1); + assert.equal(res.results[0].signed.length, 3); + const cat_signed = Underscore.findWhere(res.results[0].signed, { uid: 'cat'}) as HttpSigned + const tac_signed = Underscore.findWhere(res.results[0].signed, { uid: 'tac'}) as HttpSigned + const toc_signed = Underscore.findWhere(res.results[0].signed, { uid: 'toc'}) as HttpSigned + assert.equal(cat_signed.uid, "cat"); + assert.equal(cat_signed.isMember, true); + assert.equal(cat_signed.wasMember, true); + assert.equal(tac_signed.uid, "tac"); + assert.equal(tac_signed.isMember, true); + assert.equal(tac_signed.wasMember, true); + assert.equal(toc_signed.uid, "toc"); + assert.equal(toc_signed.isMember, true); + assert.equal(toc_signed.wasMember, true); + assert.equal(res.results[0].uids[0].others.length, 3); + assert.equal(res.results[0].uids[0].others[0].uids.length, 1); + assert.equal(res.results[0].uids[0].others[0].isMember, true); + assert.equal(res.results[0].uids[0].others[0].wasMember, true); + })); + + it('it should exist block#2 with 4 members', () => expectAnswer(rp('http://127.0.0.1:9997/blockchain/block/2', { json: true }), function(block:HttpBlock) { + should.exists(block); + assert.equal(block.number, 2); + assert.equal(block.membersCount, 4); + })); + + blockShouldHaveCerts(0, 8); + blockShouldHaveCerts(1, 0); + blockShouldHaveCerts(2, 0); + blockShouldHaveCerts(3, 1); + blockShouldHaveCerts(4, 1); + blockShouldHaveCerts(5, 0); + + function blockShouldHaveCerts(number:number, certificationsCount:number) { + it('it should exist block#' + number + ' with ' + certificationsCount + ' certification', () => expectAnswer(rp('http://127.0.0.1:9997/blockchain/block/' + number, { json: true }), function(block:HttpBlock) { + should.exists(block); + assert.equal(block.number, number); + assert.equal(block.certifications.length, certificationsCount); + })); + } + }); +}); diff --git a/test/integration/identity-test.js b/test/integration/identity/identity-test.ts similarity index 82% rename from test/integration/identity-test.js rename to test/integration/identity/identity-test.ts index ec434a0d0bccd9e385921f1feaeb918ac42519a4..c1e0150bb41f021af17b9e2432aa78758f038192 100644 --- a/test/integration/identity-test.js +++ b/test/integration/identity/identity-test.ts @@ -11,23 +11,26 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {BmaDependency} from "../../../app/modules/bma/index" +import { + HttpCertifications, + HttpIdentity, + HttpMembers, + HttpMemberships, + HttpRequirements +} from "../../../app/modules/bma/lib/dtos" +import {Underscore} from "../../../app/lib/common-libs/underscore" +import {ProverDependency} from "../../../app/modules/prover/index" +import {shutDownEngine} from "../tools/shutdown-engine" +import {expectAnswer, expectError} from "../tools/http-expect" -const _ = require('underscore'); -const co = require('co'); const should = require('should'); -const duniter = require('../../index'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const TestUser = require('./tools/TestUser').TestUser -const constants = require('../../app/lib/constants'); +const constants = require('../../../app/lib/constants'); const rp = require('request-promise'); -const httpTest = require('./tools/http'); -const commit = require('./tools/commit'); -const shutDownEngine = require('./tools/shutDownEngine'); -require('../../app/modules/bma').BmaDependency.duniter.methods.noLimit(); // Disables the HTTP limiter - -const expectAnswer = httpTest.expectAnswer; +BmaDependency.duniter.methods.noLimit(); // Disables the HTTP limiter const MEMORY_MODE = true; const commonConf = { @@ -40,16 +43,16 @@ const commonConf = { sigQty: 1 }; -let s1, cat, tac, tic, toc, tic2, man1, man2, man3 +let s1:TestingServer, cat:TestUser, tac:TestUser, tic:TestUser, toc:TestUser, tic2:TestUser, man1:TestUser, man2:TestUser, man3:TestUser describe("Identities collision", function() { - before(function() { + before(async () => { - s1 = duniter( - '/bb11', - MEMORY_MODE, - _.extend({ + s1 = NewTestingServer( + Underscore.extend({ + name: 'bb11', + memory: MEMORY_MODE, port: '7799', pair: { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', @@ -66,67 +69,63 @@ describe("Identities collision", function() { man2 = new TestUser('man2', { pub: 'E44RxG9jKZQsaPLFSw2ZTJgW7AVRqo1NGy6KGLbKgtNm', sec: 'pJRwpaCWshKZNWsbDxAHFQbVjk6X8gz9eBy9jaLnVY9gUZRqotrZLZPZe68ag4vEX1Y8mX77NhPXV2hj9F1UkX3'}, { server: s1 }); man3 = new TestUser('man3', { pub: '5bfpAfZJ4xYspUBYseASJrofhRm6e6JMombt43HBaRzW', sec: '2VFQtEcYZRwjoc8Lxwfzcejtw9VP8VAi47WjwDDjCJCXu7g1tXUAbVZN3QmvG6NJqaSuLCuYP7WDHWkFmTrUEMaE'}, { server: s1 }); - const commitS1 = commit(s1); - - return co(function *() { - yield s1.initWithDAL().then(bma).then((bmapi) => bmapi.openConnections()); - require('../../app/modules/prover').ProverDependency.duniter.methods.hookServer(s1); - yield cat.createIdentity(); - yield tac.createIdentity(); - yield toc.createIdentity(); - yield tic.createIdentity(); - yield toc.cert(cat); - yield cat.cert(toc); - yield cat.cert(tic); - yield tic.cert(tac); - yield tic.cert(cat); - yield cat.join(); - yield toc.join(); - yield tic.join(); - yield tac.join(); - yield commitS1(); - yield commitS1(); - - // We have the following WoT (diameter 3): - - /** - * toc <=> cat <=> tic -> tac - */ - - // cat is the sentry - - // Man1 is someone who just needs a commit to join - yield man1.createIdentity(); - yield man1.join(); - yield tac.cert(man1); - - /** - * toc <=> cat -> tic -> tac -> man1 - */ - - // Man2 is someone who has no certifications yet has sent a JOIN - yield man2.createIdentity(); - yield man2.join(); - - // Man3 is someone who has only published its identity - yield man3.createIdentity(); - - // tic RENEW, but not written - yield tic.join(); - - try { - yield tic.createIdentity(); - throw 'Should have thrown an error for already used pubkey'; - } catch (e) { - JSON.parse(e).message.should.equal('Pubkey already used in the blockchain'); - } - try { - yield tic2.createIdentity(); - throw 'Should have thrown an error for already used uid'; - } catch (e) { - JSON.parse(e).message.should.equal('UID already used in the blockchain'); - } - }); + await s1.initWithDAL().then(BmaDependency.duniter.methods.bma).then((bmapi) => bmapi.openConnections()); + ProverDependency.duniter.methods.hookServer(s1._server) + await cat.createIdentity(); + await tac.createIdentity(); + await toc.createIdentity(); + await tic.createIdentity(); + await toc.cert(cat); + await cat.cert(toc); + await cat.cert(tic); + await tic.cert(tac); + await tic.cert(cat); + await cat.join(); + await toc.join(); + await tic.join(); + await tac.join(); + await s1.commit(); + await s1.commit(); + + // We have the following WoT (diameter 3): + + /** + * toc <=> cat <=> tic -> tac + */ + + // cat is the sentry + + // Man1 is someone who just needs a commit to join + await man1.createIdentity(); + await man1.join(); + await tac.cert(man1); + + /** + * toc <=> cat -> tic -> tac -> man1 + */ + + // Man2 is someone who has no certifications yet has sent a JOIN + await man2.createIdentity(); + await man2.join(); + + // Man3 is someone who has only published its identity + await man3.createIdentity(); + + // tic RENEW, but not written + await tic.join(); + + try { + await tic.createIdentity(); + throw 'Should have thrown an error for already used pubkey'; + } catch (e) { + JSON.parse(e).message.should.equal('Pubkey already used in the blockchain'); + } + try { + await tic2.createIdentity(); + throw 'Should have thrown an error for already used uid'; + } catch (e) { + JSON.parse(e).message.should.equal('UID already used in the blockchain'); + } }); after(() => { @@ -136,14 +135,14 @@ describe("Identities collision", function() { }) it('should have 4 members', function() { - return expectAnswer(rp('http://127.0.0.1:7799/wot/members', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:7799/wot/members', { json: true }), function(res:HttpMembers) { res.should.have.property('results').length(4); - _.pluck(res.results, 'uid').sort().should.deepEqual(['cat', 'tac', 'tic', 'toc']); + Underscore.pluck(res.results, 'uid').sort().should.deepEqual(['cat', 'tac', 'tic', 'toc']); }); }); it('should have identity-of/cat', function() { - return expectAnswer(rp('http://127.0.0.1:7799/wot/identity-of/cat', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:7799/wot/identity-of/cat', { json: true }), function(res:HttpIdentity) { res.should.have.property('pubkey').equal('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); res.should.have.property('uid').equal('cat'); res.should.have.property('sigDate').be.a.Number; @@ -151,7 +150,7 @@ describe("Identities collision", function() { }); it('should have identity-of/toc', function() { - return expectAnswer(rp('http://127.0.0.1:7799/wot/identity-of/toc', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:7799/wot/identity-of/toc', { json: true }), function(res:HttpIdentity) { res.should.have.property('pubkey').equal('DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo'); res.should.have.property('uid').equal('toc'); res.should.have.property('sigDate').be.a.Number; @@ -159,7 +158,7 @@ describe("Identities collision", function() { }); it('should have identity-of/tic', function() { - return expectAnswer(rp('http://127.0.0.1:7799/wot/identity-of/tic', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:7799/wot/identity-of/tic', { json: true }), function(res:HttpIdentity) { res.should.have.property('pubkey').equal('DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV'); res.should.have.property('uid').equal('tic'); res.should.have.property('sigDate').be.a.Number; @@ -167,11 +166,11 @@ describe("Identities collision", function() { }); it('should have identity-of/aaa', function() { - return httpTest.expectError(404, "No member matching this pubkey or uid", rp('http://127.0.0.1:7799/wot/identity-of/aaa')); + return expectError(404, "No member matching this pubkey or uid", rp('http://127.0.0.1:7799/wot/identity-of/aaa')); }); it('should have certifiers-of/cat giving results', function() { - return expectAnswer(rp('http://127.0.0.1:7799/wot/certifiers-of/cat', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:7799/wot/certifiers-of/cat', { json: true }), function(res:HttpCertifications) { res.should.have.property('pubkey').equal('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); res.should.have.property('uid').equal('cat'); res.should.have.property('isMember').equal(true); @@ -194,7 +193,7 @@ describe("Identities collision", function() { }); it('should have certifiers-of/tic giving results', function() { - return expectAnswer(rp('http://127.0.0.1:7799/wot/certifiers-of/tic', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:7799/wot/certifiers-of/tic', { json: true }), function(res:HttpCertifications) { res.should.have.property('pubkey').equal('DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV'); res.should.have.property('uid').equal('tic'); res.should.have.property('isMember').equal(true); @@ -215,7 +214,7 @@ describe("Identities collision", function() { }); it('should have certifiers-of/toc giving results', function() { - return expectAnswer(rp('http://127.0.0.1:7799/wot/certifiers-of/toc', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:7799/wot/certifiers-of/toc', { json: true }), function(res:HttpCertifications) { res.should.have.property('pubkey').equal('DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo'); res.should.have.property('uid').equal('toc'); res.should.have.property('isMember').equal(true); @@ -236,7 +235,7 @@ describe("Identities collision", function() { }); it('requirements of cat', function() { - return expectAnswer(rp('http://127.0.0.1:7799/wot/requirements/cat', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:7799/wot/requirements/cat', { json: true }), function(res:HttpRequirements) { res.should.have.property('identities').be.an.Array; res.should.have.property('identities').have.length(1); res.identities[0].should.have.property('pubkey').equal('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); @@ -253,7 +252,7 @@ describe("Identities collision", function() { }); it('requirements of man1', function() { - return expectAnswer(rp('http://127.0.0.1:7799/wot/requirements/man1', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:7799/wot/requirements/man1', { json: true }), function(res:HttpRequirements) { res.should.have.property('identities').be.an.Array; res.should.have.property('identities').have.length(1); res.identities[0].should.have.property('pubkey').equal('12AbjvYY5hxV4v2KrN9pnGzgFxogwrzgYyncYHHsyFDK'); @@ -272,7 +271,7 @@ describe("Identities collision", function() { }); it('should have certified-by/tic giving results', function() { - return expectAnswer(rp('http://127.0.0.1:7799/wot/certified-by/tic', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:7799/wot/certified-by/tic', { json: true }), function(res:HttpCertifications) { res.should.have.property('pubkey').equal('DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV'); res.should.have.property('uid').equal('tic'); res.should.have.property('isMember').equal(true); @@ -300,7 +299,7 @@ describe("Identities collision", function() { }); it('should have certified-by/tac giving results', function() { - return expectAnswer(rp('http://127.0.0.1:7799/wot/certified-by/tac', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:7799/wot/certified-by/tac', { json: true }), function(res:HttpCertifications) { res.should.have.property('pubkey').equal('2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc'); res.should.have.property('uid').equal('tac'); res.should.have.property('isMember').equal(true); @@ -310,7 +309,7 @@ describe("Identities collision", function() { }); it('should have certified-by/cat giving results', function() { - return expectAnswer(rp('http://127.0.0.1:7799/wot/certified-by/cat', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:7799/wot/certified-by/cat', { json: true }), function(res:HttpCertifications) { res.should.have.property('pubkey').equal('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); res.should.have.property('uid').equal('cat'); res.should.have.property('isMember').equal(true); @@ -345,7 +344,7 @@ describe("Identities collision", function() { }); it('requirements of man2', function() { - return expectAnswer(rp('http://127.0.0.1:7799/wot/requirements/man2', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:7799/wot/requirements/man2', { json: true }), function(res:HttpRequirements) { res.should.have.property('identities').be.an.Array; res.should.have.property('identities').have.length(1); res.identities[0].should.have.property('pubkey').equal('E44RxG9jKZQsaPLFSw2ZTJgW7AVRqo1NGy6KGLbKgtNm'); @@ -361,7 +360,7 @@ describe("Identities collision", function() { }); it('requirements of man3', function() { - return expectAnswer(rp('http://127.0.0.1:7799/wot/requirements/man3', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:7799/wot/requirements/man3', { json: true }), function(res:HttpRequirements) { res.should.have.property('identities').be.an.Array; res.should.have.property('identities').have.length(1); res.identities[0].should.have.property('pubkey').equal('5bfpAfZJ4xYspUBYseASJrofhRm6e6JMombt43HBaRzW'); @@ -376,9 +375,9 @@ describe("Identities collision", function() { }); }); - it('requirements of man3 after revocation', () => co(function*() { - yield man3.revoke(); - return expectAnswer(rp('http://127.0.0.1:7799/wot/requirements/man3', { json: true }), function(res) { + it('requirements of man3 after revocation', async () => { + await man3.revoke(); + return expectAnswer(rp('http://127.0.0.1:7799/wot/requirements/man3', { json: true }), function(res:HttpRequirements) { res.should.have.property('identities').be.an.Array; res.should.have.property('identities').have.length(1); res.identities[0].should.have.property('pubkey').equal('5bfpAfZJ4xYspUBYseASJrofhRm6e6JMombt43HBaRzW'); @@ -394,10 +393,10 @@ describe("Identities collision", function() { res.identities[0].should.have.property('revoked_on').equal(null); res.identities[0].should.have.property('revocation_sig').not.equal(null); }); - })); + }) it('memberships of tic', function() { - return expectAnswer(rp('http://127.0.0.1:7799/blockchain/memberships/tic', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:7799/blockchain/memberships/tic', { json: true }), function(res:HttpMemberships) { res.should.have.property('pubkey').equal('DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV'); res.should.have.property('uid').equal('tic'); res.should.have.property('sigDate').be.a.Number; @@ -410,10 +409,10 @@ describe("Identities collision", function() { res.memberships[0].should.have.property('blockHash').not.equal('E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855'); res.memberships[0].should.have.property('written').equal(null); }); - }); + }) // it('memberships of man3', function() { - // return httpTest.expectHttpCode(404, rp('http://127.0.0.1:7799/blockchain/memberships/man3')); + // return expectHttpCode(404, rp('http://127.0.0.1:7799/blockchain/memberships/man3')); // }); // // it('difficulties', function() { @@ -424,4 +423,4 @@ describe("Identities collision", function() { // res.levels[0].should.have.property('level').equal(4); // }); // }); -}); +}) diff --git a/test/integration/membership_chainability.ts b/test/integration/membership_chainability.ts index af5da41eff3d084a17e9d6a4cfbd92698d0990b4..95ffc5f94ba1976ac82634dc624e6af18493dcb5 100644 --- a/test/integration/membership_chainability.ts +++ b/test/integration/membership_chainability.ts @@ -11,7 +11,7 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -const toolbox = require('./tools/toolbox') +import {simpleNodeWith2Users} from "./tools/toolbox" describe("Membership chainability", function() { @@ -31,7 +31,7 @@ describe("Membership chainability", function() { } before(async () => { - const res1 = await toolbox.simpleNodeWith2Users(conf) + const res1 = await simpleNodeWith2Users(conf) s1 = res1.s1 cat = res1.cat await s1.commit({ time: now }) @@ -67,7 +67,7 @@ describe("Membership chainability", function() { } before(async () => { - const res1 = await toolbox.simpleNodeWith2Users(conf) + const res1 = await simpleNodeWith2Users(conf) s1 = res1.s1 cat = res1.cat await s1.commit({ time: now }) diff --git a/test/integration/cli.js b/test/integration/misc/cli.ts similarity index 51% rename from test/integration/cli.js rename to test/integration/misc/cli.ts index 5700267beaaaf3c83b04abaf5dde435e044bc888..a4540493bfc591fa046194e28e995b6327cccb0f 100644 --- a/test/integration/cli.js +++ b/test/integration/misc/cli.ts @@ -11,68 +11,60 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {MerkleDTO} from "../../../app/lib/dto/MerkleDTO" +import {hashf} from "../../../app/lib/common" +import {processForURL} from "../../../app/lib/helpers/merkle" +import {fakeSyncServer} from "../tools/toolbox" +import {Underscore} from "../../../app/lib/common-libs/underscore" const spawn = require('child_process').spawn; const path = require('path'); -const co = require('co'); const should = require('should'); -const _ = require('underscore'); -const toolbox = require('./tools/toolbox'); -const duniter = require('../../index'); -const merkleh = require('../../app/lib/helpers/merkle'); -const hashf = require('../../app/lib/common-libs').hashf -const constants = require('../../app/lib/constants'); -const MerkleDTO = require('../../app/lib/dto/MerkleDTO').MerkleDTO +const duniter = require('../../../index'); const DB_NAME = "unit_tests"; describe("CLI", function() { - let farmOfServers = [], fakeServer; + let farmOfServers:{ host:string, port:number}[] = [], fakeServer:{ host:string, port:number} - before(() => co(function*() { + before(async () => { - const blockchain = require('../data/blockchain.json'); - const peers = []; - const peersMap = {}; - const leaves = []; + const blockchain = require('../../data/blockchain.json'); + const peersMap:any = {}; + const leaves:string[] = []; /******** * HTTP METHODS */ - const onReadBlockchainChunk = (count, from) => Promise.resolve(blockchain.blocks.slice(from, from + count)); - const onReadParticularBlock = (number) => Promise.resolve(blockchain.blocks[number]); - const onPeersRequested = (req) => co(function*() { + const onReadBlockchainChunk = (count:number, from:number) => Promise.resolve(blockchain.blocks.slice(from, from + count)); + const onReadParticularBlock = (number:number) => Promise.resolve(blockchain.blocks[number]); + const onPeersRequested = async (req:any) => { const merkle = new MerkleDTO(); merkle.initialize(leaves); - merkle.leaf = { - "hash": req.params.leaf, - "value": peersMap[req.params.leaf] || "" - }; - return merkleh.processForURL(req, merkle, () => co(function*() { + return processForURL(req, merkle, async () => { return peersMap; - })); - }); + }) + } /** * The fake hash in the blockchain */ const fakeHash = hashf("A wrong content").toUpperCase(); - farmOfServers = yield Array.from({ length: 5 }).map((unused, index) => { + farmOfServers = await Promise.all(Array.from({ length: 5 }).map(async (unused, index) => { if (index < 2) { /*************** * Normal nodes */ - return toolbox.fakeSyncServer(onReadBlockchainChunk, onReadParticularBlock, onPeersRequested); + return fakeSyncServer(onReadBlockchainChunk, onReadParticularBlock, onPeersRequested); } else if (index == 2) { /*************** * Node with wrong chaining between 2 chunks of blocks */ - return toolbox.fakeSyncServer((count, from) => { + return fakeSyncServer((count:number, from:number) => { // We just need to send the wrong chunk from = from - count; return Promise.resolve(blockchain.blocks.slice(from, from + count)); @@ -82,30 +74,30 @@ describe("CLI", function() { /*************** * Node with wrong chaining between 2 blocks */ - return toolbox.fakeSyncServer((count, from) => { + return fakeSyncServer((count:number, from:number) => { // We just need to send the wrong chunk - const chunk = blockchain.blocks.slice(from, from + count).map((block, index2) => { + const chunk = blockchain.blocks.slice(from, from + count).map((block:any, index2:number) => { if (index2 === 10) { - const clone = _.clone(block); + const clone = Underscore.clone(block); clone.hash = fakeHash; } return block; }); return Promise.resolve(chunk); }, onReadParticularBlock, onPeersRequested); - } else if (index == 4) { + } else { /*************** * Node with apparent good chaining, but one of the hashs is WRONG */ - return toolbox.fakeSyncServer((count, from) => { + return fakeSyncServer((count:number, from:number) => { // We just need to send the wrong chunk - const chunk = blockchain.blocks.slice(from, from + count).map((block, index2) => { + const chunk = blockchain.blocks.slice(from, from + count).map((block:any, index2:number) => { if (index2 === 10) { - const clone = _.clone(block); + const clone = Underscore.clone(block); clone.hash = fakeHash; } else if (index2 === 11) { - const clone = _.clone(block); + const clone = Underscore.clone(block); clone.previousHash = fakeHash; return clone; } @@ -114,56 +106,55 @@ describe("CLI", function() { return Promise.resolve(chunk); }, onReadParticularBlock, onPeersRequested); } - }); + })) farmOfServers.map((server, index) => { const peer = { endpoints: [['BASIC_MERKLED_API', server.host, server.port].join(' ')], pubkey: hashf(index + ""), hash: hashf(index + "").toUpperCase() }; - peers.push(peer); leaves.push(peer.hash); peersMap[peer.hash] = peer; }); fakeServer = farmOfServers[0]; - })); + }) - it('config --autoconf', () => co(function*() { - let res = yield execute(['config', '--autoconf', '--noupnp']); + it('config --autoconf', async () => { + let res = await execute(['config', '--autoconf', '--noupnp']); res.should.have.property("pair").property('pub').not.equal(""); res.should.have.property("pair").property('sec').not.equal(""); - })); + }) - it('reset data', () => co(function*() { - yield execute(['reset', 'data']); - // const res = yield execute(['export-bc', '--nostdout']); + it('reset data', async () => { + await execute(['reset', 'data']); + // const res = await execute(['export-bc', '--nostdout']); // res.slice(0, 1).should.have.length(0); - })); + }) - it('sync 7 blocks (fast)', () => co(function*() { - yield execute(['reset', 'data']); - yield execute(['sync', fakeServer.host, fakeServer.port, '7', '--nocautious', '--nointeractive', '--noshuffle']); - const res = yield execute(['export-bc', '--nostdout']); + it('sync 7 blocks (fast)', async () => { + await execute(['reset', 'data']); + await execute(['sync', fakeServer.host + ':' + String(fakeServer.port), '7', '--nocautious', '--nointeractive', '--noshuffle']); + const res = await execute(['export-bc', '--nostdout']); res[res.length - 1].should.have.property('number').equal(7); res.should.have.length(7 + 1); // blocks #0..#7 - })); + }) - it('sync 4 blocks (cautious)', () => co(function*() { - yield execute(['sync', fakeServer.host, fakeServer.port, '11', '--nointeractive']); - const res = yield execute(['export-bc', '--nostdout']); + it('sync 4 blocks (cautious)', async () => { + await execute(['sync', fakeServer.host + ':' + String(fakeServer.port), '11', '--nointeractive']); + const res = await execute(['export-bc', '--nostdout']); res[res.length - 1].should.have.property('number').equal(11); res.should.have.length(11 + 1); - })); + }) - it('[spawn] reset data', () => co(function*() { - yield executeSpawn(['reset', 'data']); - const res = yield executeSpawn(['export-bc']); + it('[spawn] reset data', async () => { + await executeSpawn(['reset', 'data']); + const res = await executeSpawn(['export-bc']); JSON.parse(res).should.have.length(0); - })); + }) - it('[spawn] sync 10 first blocks --memory', () => co(function*() { - yield execute(['sync', fakeServer.host, fakeServer.port, '10', '--memory', '--cautious', '--nointeractive']); - })); + it('[spawn] sync 10 first blocks --memory', async () => { + await execute(['sync', fakeServer.host + ':' + String(fakeServer.port), '10', '--memory', '--cautious', '--nointeractive']); + }) }); /** @@ -171,14 +162,11 @@ describe("CLI", function() { * @param args Array of arguments. * @returns {*|Promise} Returns the command output. */ -function execute(args) { +async function execute(args:(string)[]) { const finalArgs = [process.argv[0], __filename].concat(args).concat(['--mdb', DB_NAME]); - return co(function*() { - - const stack = duniter.statics.autoStack(); - // Executes the command - return stack.executeStack(finalArgs); - }); + const stack = duniter.statics.autoStack(); + // Executes the command + return stack.executeStack(finalArgs); } /** @@ -186,19 +174,17 @@ function execute(args) { * @param command Array of arguments. * @returns {*|Promise} Returns the command output. */ -function executeSpawn(command) { - return co(function*() { - const finalArgs = [path.join(__dirname, '../../bin/duniter')].concat(command).concat(['--mdb', DB_NAME]); - const duniterCmd = spawn(process.argv[0], finalArgs); - return new Promise((resolve, reject) => { - let res = ""; - duniterCmd.stdout.on('data', (data) => { - res += data.toString('utf8').replace(/\n/, ''); - }); - duniterCmd.stderr.on('data', (err) => { - console.log(err.toString('utf8').replace(/\n/, '')); - }); - duniterCmd.on('close', (code) => code ? reject(code) : resolve(res) ); +async function executeSpawn(command:string[]): Promise<string> { + const finalArgs = [path.join(__dirname, '../../../bin/duniter')].concat(command).concat(['--mdb', DB_NAME]); + const duniterCmd = spawn(process.argv[0], finalArgs); + return new Promise<string>((resolve, reject) => { + let res = ""; + duniterCmd.stdout.on('data', (data:any) => { + res += data.toString('utf8').replace(/\n/, ''); + }); + duniterCmd.stderr.on('data', (err:any) => { + console.log(err.toString('utf8').replace(/\n/, '')); }); + duniterCmd.on('close', (code:any) => code ? reject(code) : resolve(res) ); }); } diff --git a/test/integration/http_api.js b/test/integration/misc/http-api.ts similarity index 61% rename from test/integration/http_api.js rename to test/integration/misc/http-api.ts index 22ee16aca9f47af5fa7d5bd7ad19ecbe9293a84a..349ebabc3f4eefb6c19780da6a1f1999edac56e7 100644 --- a/test/integration/http_api.js +++ b/test/integration/misc/http-api.ts @@ -11,36 +11,37 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {ProverConstants} from "../../../app/modules/prover/lib/constants" +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {BmaDependency} from "../../../app/modules/bma/index" +import {PeerDTO} from "../../../app/lib/dto/PeerDTO" +import {ProverDependency} from "../../../app/modules/prover/index" +import {HttpBlock, HttpDifficulties} from "../../../app/modules/bma/lib/dtos" +import {Underscore} from "../../../app/lib/common-libs/underscore" +import {BlockDTO} from "../../../app/lib/dto/BlockDTO" +import {shutDownEngine} from "../tools/shutdown-engine" +import {expectAnswer, expectError} from "../tools/http-expect" +import {WebSocket} from "../../../app/lib/common-libs/websocket" -const co = require('co'); -const _ = require('underscore'); const should = require('should'); const assert = require('assert'); -const duniter = require('../../index'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const PeerDTO = require('../../app/lib/dto/PeerDTO').PeerDTO -const TestUser = require('./tools/TestUser').TestUser -const http = require('./tools/http'); -const shutDownEngine = require('./tools/shutDownEngine'); const rp = require('request-promise'); -const ws = require('ws'); -require('../../app/modules/prover/lib/constants').ProverConstants.CORES_MAXIMUM_USE_IN_PARALLEL = 1 +ProverConstants.CORES_MAXIMUM_USE_IN_PARALLEL = 1 -let server, server2, cat, toc +let server:TestingServer, server2:TestingServer, cat:TestUser, toc:TestUser describe("HTTP API", function() { const now = 1500000000 - let commit + let commit:(options:any) => Promise<BlockDTO> - before(() => co(function*(){ + before(async () => { - server = duniter( - '/bb11', - true, + server = NewTestingServer( { + name: 'bb11', ipv4: '127.0.0.1', port: '7777', currency: 'bb', @@ -57,10 +58,9 @@ describe("HTTP API", function() { } }); - server2 = duniter( - '/bb12', - true, + server2 = NewTestingServer( { + name: 'bb12', ipv4: '127.0.0.1', port: '30410', currency: 'bb', @@ -82,33 +82,33 @@ describe("HTTP API", function() { commit = makeBlockAndPost(server); - let s = yield server.initWithDAL(); - let bmapi = yield bma(s); - yield bmapi.openConnections(); - - let s2 = yield server2.initWithDAL(); - let bmapi2 = yield bma(s2); - yield bmapi2.openConnections(); - - yield cat.createIdentity(); - yield toc.createIdentity(); - yield toc.cert(cat); - yield cat.cert(toc); - yield cat.join(); - yield toc.join(); - const b0 = yield commit({ time: now }); - const b1 = yield commit({ time: now + 120 }); - yield server2.writeBlock(b0) - yield server2.writeBlock(b1) - server.addEndpointsDefinitions(() => Promise.resolve('SOME_FAKE_ENDPOINT_P1')) - server2.addEndpointsDefinitions(() => Promise.resolve('SOME_FAKE_ENDPOINT_P2')) - const p1 = yield server.PeeringService.generateSelfPeer(server.conf) - yield server2.PeeringService.generateSelfPeer(server2.conf) - yield server2.writePeer(p1) - server2.writeBlock(yield commit({ time: now + 120 * 2 })) - server2.writeBlock(yield commit({ time: now + 120 * 3 })) - server2.writeBlock(yield commit({ time: now + 120 * 4 })) - })); + let s = await server.initWithDAL(); + let bmapi = await BmaDependency.duniter.methods.bma(s); + await bmapi.openConnections(); + + let s2 = await server2.initWithDAL(); + let bmapi2 = await BmaDependency.duniter.methods.bma(s2); + await bmapi2.openConnections(); + + await cat.createIdentity(); + await toc.createIdentity(); + await toc.cert(cat); + await cat.cert(toc); + await cat.join(); + await toc.join(); + const b0 = await commit({ time: now }); + const b1 = await commit({ time: now + 120 }); + await server2.writeBlock(b0) + await server2.writeBlock(b1) + server._server.addEndpointsDefinitions(() => Promise.resolve('SOME_FAKE_ENDPOINT_P1')) + server2._server.addEndpointsDefinitions(() => Promise.resolve('SOME_FAKE_ENDPOINT_P2')) + const p1 = await server.PeeringService.generateSelfPeer(server.conf) + await server2.PeeringService.generateSelfPeer(server2.conf) + await server2.writePeer(p1) + server2.writeBlock(await commit({ time: now + 120 * 2 })) + server2.writeBlock(await commit({ time: now + 120 * 3 })) + server2.writeBlock(await commit({ time: now + 120 * 4 })) + }) after(() => { return Promise.all([ @@ -116,14 +116,12 @@ describe("HTTP API", function() { ]) }) - function makeBlockAndPost(theServer) { - return function(options) { - return co(function*() { - const block = yield require('../../app/modules/prover').ProverDependency.duniter.methods.generateAndProveTheNext(theServer, null, null, options) - const res = yield postBlock(theServer)(block) - return JSON.parse(res) - }) - }; + function makeBlockAndPost(theServer:TestingServer) { + return async function(options:any) { + const block = await ProverDependency.duniter.methods.generateAndProveTheNext(theServer._server, null, null, options) + const res = await postBlock(theServer)(block) + return JSON.parse(res) + } } describe("/blockchain", function() { @@ -170,7 +168,7 @@ describe("HTTP API", function() { }) it('/block/88 should not exist', function() { - return http.expectError(404, rp('http://127.0.0.1:7777/blockchain/block/88')); + return expectError(404, rp('http://127.0.0.1:7777/blockchain/block/88')); }); it('/current should exist', function() { @@ -180,7 +178,7 @@ describe("HTTP API", function() { }); it('/membership should not accept wrong signature', function() { - return http.expectError(400, 'wrong signature for membership', rp.post('http://127.0.0.1:7777/blockchain/membership', { + return expectError(400, 'wrong signature for membership', rp.post('http://127.0.0.1:7777/blockchain/membership', { json: { membership: 'Version: 10\n' + 'Type: Membership\n' + @@ -196,7 +194,7 @@ describe("HTTP API", function() { }); it('/membership should not accept wrong signature 2', function() { - return http.expectError(400, 'Document has unkown fields or wrong line ending format', rp.post('http://127.0.0.1:7777/blockchain/membership', { + return expectError(400, 'Document has unkown fields or wrong line ending format', rp.post('http://127.0.0.1:7777/blockchain/membership', { json: { membership: 'Version: 2\n' + 'Type: Membership\n' + @@ -211,7 +209,7 @@ describe("HTTP API", function() { }); it('/membership should not accept wrong signature 3', function() { - return http.expectError(400, 'Document has unkown fields or wrong line ending format', rp.post('http://127.0.0.1:7777/blockchain/membership', { + return expectError(400, 'Document has unkown fields or wrong line ending format', rp.post('http://127.0.0.1:7777/blockchain/membership', { json: { membership: 'Version: 2\n' + 'Type: Membership\n' + @@ -227,7 +225,7 @@ describe("HTTP API", function() { }); it('/difficulties should have current block number + 1', function() { - return http.expectAnswer(rp('http://127.0.0.1:7777/blockchain/difficulties', { json: true }), function(res) { + return expectAnswer(rp('http://127.0.0.1:7777/blockchain/difficulties', { json: true }), function(res:HttpDifficulties) { res.should.have.property('block').equal(5); res.should.have.property('levels').have.length(1); }); @@ -237,7 +235,7 @@ describe("HTTP API", function() { describe("/ws", function() { it('/block should exist', function(done) { - const client = new ws('ws://127.0.0.1:7777/ws/block'); + const client = new WebSocket('ws://127.0.0.1:7777/ws/block'); client.on('open', function open() { client.terminate(); done(); @@ -246,8 +244,8 @@ describe("HTTP API", function() { it('/block should send a block', function(done) { let completed = false - const client = new ws('ws://127.0.0.1:7777/ws/block'); - client.once('message', function message(data) { + const client = new WebSocket('ws://127.0.0.1:7777/ws/block'); + client.once('message', function message(data:any) { const block = JSON.parse(data); should(block).have.property('number', 4); should(block).have.property('dividend').equal(null) @@ -260,41 +258,41 @@ describe("HTTP API", function() { }); }); - it('/block (number 5,6,7) should send a block', () => co(function*() { - server2.writeBlock(yield commit({ time: now + 120 * 5 })) - const client = new ws('ws://127.0.0.1:7777/ws/block'); - let resolve5, resolve6, resolve7 + it('/block (number 5,6,7) should send a block', async () => { + server2.writeBlock(await commit({ time: now + 120 * 5 })) + const client = new WebSocket('ws://127.0.0.1:7777/ws/block'); + let resolve5:any, resolve6:any, resolve7:any const p5 = new Promise(res => resolve5 = res) const p6 = new Promise(res => resolve6 = res) const p7 = new Promise(res => resolve7 = res) - client.on('message', function message(data) { + client.on('message', function message(data:string) { const block = JSON.parse(data); if (block.number === 5) resolve5(block) if (block.number === 6) resolve6(block) if (block.number === 7) resolve7(block) }) - server2.writeBlock(yield commit({ time: now + 120 * 6 })) - server2.writeBlock(yield commit({ time: now + 120 * 7 })) - const b5 = yield p5 + server2.writeBlock(await commit({ time: now + 120 * 6 })) + server2.writeBlock(await commit({ time: now + 120 * 7 })) + const b5 = await p5 should(b5).have.property('number', 5); should(b5).have.property('dividend').equal(100) should(b5).have.property('monetaryMass').equal(600) should(b5).have.property('monetaryMass').not.equal("600") - const b6 = yield p6 + const b6 = await p6 should(b6).have.property('number', 6); should(b6).have.property('dividend').equal(null) should(b6).have.property('monetaryMass').equal(600) should(b6).have.property('monetaryMass').not.equal("600") - const b7 = yield p7 + const b7 = await p7 should(b7).have.property('number', 7); should(b7).have.property('dividend').equal(100) should(b7).have.property('monetaryMass').equal(800) should(b7).have.property('monetaryMass').not.equal("800") - })) + }) it('/block should answer to pings', function(done) { - const client = new ws('ws://127.0.0.1:7777/ws/block'); - client.on('pong', function message(data, flags) { + const client = new WebSocket('ws://127.0.0.1:7777/ws/block'); + client.on('pong', function message() { client.terminate(); done(); }); @@ -303,73 +301,71 @@ describe("HTTP API", function() { }); }); - it('/peer (number 5,6,7) should send a peer document', () => co(function*() { - const client = new ws('ws://127.0.0.1:30410/ws/peer'); - let resolve5, resolve6, resolve7 + it('/peer (number 5,6,7) should send a peer document', async () => { + const client = new WebSocket('ws://127.0.0.1:30410/ws/peer'); + let resolve5:any, resolve6:any const p5 = new Promise(res => resolve5 = res) const p6 = new Promise(res => resolve6 = res) - server.addEndpointsDefinitions(() => Promise.resolve("BASIC_MERKLED_API localhost 7777")) - const p1 = yield server.PeeringService.generateSelfPeer({ - currency: server.conf.currency - }, 0) - client.on('message', function message(data) { + server._server.addEndpointsDefinitions(() => Promise.resolve("BASIC_MERKLED_API localhost 7777")) + client.on('message', function message(data:any) { const peer = JSON.parse(data); if (peer.block.match(/2-/)) { server2.PeeringService.generateSelfPeer(server.conf) return resolve5(peer) } - if (peer.block.match(/1-/) && peer.pubkey === 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo') { + if ((peer.block.match(/1-/) || peer.block.match(/3-/)) && peer.pubkey === 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo') { return resolve6(peer) } }) - yield server2.writeRawPeer(PeerDTO.fromJSONObject(p1).getRawSigned()) - const b5 = yield p5 + const p1 = await server.PeeringService.generateSelfPeer({ + currency: server.conf.currency + }, 0) + await server2._server.writeRawPeer(PeerDTO.fromJSONObject(p1).getRawSigned()) + const b5 = await p5 should(b5).have.property('version', 10) - const b6 = yield p6 + const b6 = await p6 should(b6).have.property('version', 10) - })) - }); -}); - -function expectJSON(promise, json) { - return co(function*(){ - try { - const resJson = yield promise; - _.keys(json).forEach(function(key){ - resJson.should.have.property(key).equal(json[key]); - }); - } catch (err) { - if (err.response) { - assert.equal(err.response.statusCode, 200); - } - else throw err; + }) + }) +}) + +async function expectJSON<T>(promise:Promise<T>, json:any) { + try { + const resJson = await promise; + Underscore.keys(json).forEach(function(key){ + resJson.should.have.property(key).equal(json[key]); + }); + } catch (err) { + if (err.response) { + assert.equal(err.response.statusCode, 200); } - }); + else throw err; + } } -function postBlock(server2) { - return function(block) { +function postBlock(server2:TestingServer) { + return function(block:any) { return post(server2, '/blockchain/block')({ block: typeof block == 'string' ? block : block.getRawSigned() }) - .then((result) => co(function*() { + .then(async (result:HttpBlock) => { const numberToReach = block.number - yield new Promise((res) => { - const interval = setInterval(() => co(function*() { - const current = yield server2.dal.getCurrentBlockOrNull() + await new Promise((res) => { + const interval = setInterval(async () => { + const current = await server2.dal.getCurrentBlockOrNull() if (current && current.number == numberToReach) { res() clearInterval(interval) } - }), 1) + }, 1) }) return result - })) + }) }; } -function post(server2, uri) { - return function(data) { +function post(server2:TestingServer, uri:string) { + return function(data:any) { return rp.post('http://' + [server2.conf.ipv4, server2.conf.port].join(':') + uri, { form: data }); diff --git a/test/integration/server-import-export.js b/test/integration/misc/server-import-export.ts similarity index 60% rename from test/integration/server-import-export.js rename to test/integration/misc/server-import-export.ts index daab7a580d0a78aaa44cda10aec5c2283574a04a..2f2e7bd5487336cf1fa3bde24bc46cdbd2fc9ec5 100644 --- a/test/integration/server-import-export.js +++ b/test/integration/misc/server-import-export.ts @@ -11,15 +11,11 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; -const _ = require('underscore'); +import {TestUser} from "../tools/TestUser" +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {Underscore} from "../../../app/lib/common-libs/underscore" + const should = require('should'); -const fs = require('fs'); -const co = require('co'); -const unzip = require('unzip'); -const toolbox = require('../integration/tools/toolbox'); -const TestUser = require('../integration/tools/TestUser').TestUser -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; const serverConfig = { memory: false, @@ -29,28 +25,28 @@ const serverConfig = { } }; -let s0, s1; +let s0:TestingServer, s1:TestingServer describe('Import/Export', () => { - before(() => co(function *() { - s0 = toolbox.server(_.extend({ homename: 'dev_unit_tests1', powNoSecurity: true }, serverConfig)); - yield s0.resetHome(); + before(async () => { + s0 = NewTestingServer(Underscore.extend({ homename: 'dev_unit_tests1', powNoSecurity: true }, serverConfig)); + await s0.resetHome(); - s1 = toolbox.server(_.extend({ homename: 'dev_unit_tests1', powNoSecurity: true }, serverConfig)); + s1 = NewTestingServer(Underscore.extend({ homename: 'dev_unit_tests1', powNoSecurity: true }, serverConfig)); const cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); const tac = new TestUser('tac', { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'}, { server: s1 }); - yield s1.initDalBmaConnections(); - yield cat.createIdentity(); - yield tac.createIdentity(); - yield cat.cert(tac); - yield tac.cert(cat); - yield cat.join(); - yield tac.join(); - yield s1.commit(); - })); + await s1.initDalBmaConnections(); + await cat.createIdentity(); + await tac.createIdentity(); + await cat.cert(tac); + await tac.cert(cat); + await cat.join(); + await tac.join(); + await s1.commit(); + }) after(() => { return Promise.all([ @@ -59,8 +55,8 @@ describe('Import/Export', () => { ]) }) - it('should be able to export data', () => co(function *() { - const archive = yield s1.exportAllDataAsZIP(); + it('should be able to export data', async () => { + const archive = await s1.exportAllDataAsZIP(); const output = require('fs').createWriteStream(s1.home + '/export.zip'); archive.pipe(output); return new Promise((resolve, reject) => { @@ -69,10 +65,10 @@ describe('Import/Export', () => { resolve(); }); }); - })); + }) - it('should be able to import data', () => co(function *() { - yield s1.unplugFileSystem(); - yield s1.importAllDataFromZIP(s1.home + '/export.zip'); - })); -}); + it('should be able to import data', async () => { + await s1.unplugFileSystem(); + await s1.importAllDataFromZIP(s1.home + '/export.zip'); + }) +}) diff --git a/test/integration/single-document-treatment.js b/test/integration/misc/single-document-treatment.ts similarity index 69% rename from test/integration/single-document-treatment.js rename to test/integration/misc/single-document-treatment.ts index 58aa5229afb2810676af006837910cda605708b1..1f73502196cc56e87d1bdae365cb10ac5a508b10 100644 --- a/test/integration/single-document-treatment.js +++ b/test/integration/misc/single-document-treatment.ts @@ -11,25 +11,20 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {NewTestingServer, serverWaitBlock, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" -const _ = require('underscore'); -const co = require('co'); const assert = require('assert'); -const TestUser = require('./tools/TestUser').TestUser -const commit = require('./tools/commit'); -const toolbox = require('./tools/toolbox'); -const CommonConstants = require('../../app/lib/common-libs/constants').CommonConstants const now = 1500000000 -let s1, s2, cat, tac +let s1:TestingServer, s2:TestingServer, cat:TestUser, tac:TestUser describe("Single document treatment", function() { - before(() => co(function*() { + before(async () => { - s1 = toolbox.server({ + s1 = NewTestingServer({ // The common conf medianTimeBlocks: 1, avgGenTime: 11, @@ -41,7 +36,7 @@ describe("Single document treatment", function() { } }); - s2 = toolbox.server({ + s2 = NewTestingServer({ pair: { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE' @@ -51,17 +46,17 @@ describe("Single document treatment", function() { cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); tac = new TestUser('tac', { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'}, { server: s1 }); - yield s1.prepareForNetwork(); - yield s2.prepareForNetwork(); + await s1.prepareForNetwork(); + await s2.prepareForNetwork(); // Publishing identities - yield cat.createIdentity(); - yield tac.createIdentity(); - yield cat.cert(tac); - yield tac.cert(cat); - yield cat.join(); - yield tac.join(); - })); + await cat.createIdentity(); + await tac.createIdentity(); + await cat.cert(tac); + await tac.cert(cat); + await cat.join(); + await tac.join(); + }) after(() => { return Promise.all([ @@ -70,29 +65,29 @@ describe("Single document treatment", function() { ]) }) - it('should create a common blockchain', () => co(function*() { - const b0 = yield s1.commit({ time: now }) - const b1 = yield s1.commit({ time: now + 11 }) - const b2 = yield s1.commit({ time: now + 22 }) - yield s2.writeBlock(b0) - yield s2.writeBlock(b1) - yield s2.writeBlock(b2) - yield toolbox.serverWaitBlock(s2, 2) - })) - - it('should exist the same block on each node', () => co(function*() { - yield s1.expectJSON('/blockchain/current', { + it('should create a common blockchain', async () => { + const b0 = await s1.commit({ time: now }) + const b1 = await s1.commit({ time: now + 11 }) + const b2 = await s1.commit({ time: now + 22 }) + await s2.writeBlock(b0) + await s2.writeBlock(b1) + await s2.writeBlock(b2) + await serverWaitBlock(s2._server, 2) + }) + + it('should exist the same block on each node', async () => { + await s1.expectJSON('/blockchain/current', { number: 2 }) - yield s2.expectJSON('/blockchain/current', { + await s2.expectJSON('/blockchain/current', { number: 2 }) - })) + }) - it('should refuse known fork blocks', () => co(function*() { - const p1 = yield s1.getPeer() + it('should refuse known fork blocks', async () => { + const p1 = await s1.getPeer() // Trigger the multiple writings in parallel - const res = yield Promise.all([ + const res = await Promise.all([ s2.writePeer(p1).then(p => p).catch(e => { assert.equal(e.uerr.message, "Document already under treatment"); return null }), s2.writePeer(p1).then(p => p).catch(e => { assert.equal(e.uerr.message, "Document already under treatment"); return null }), s2.writePeer(p1).then(p => p).catch(e => { assert.equal(e.uerr.message, "Document already under treatment"); return null }), @@ -108,6 +103,6 @@ describe("Single document treatment", function() { assert.equal(res[4], null) assert.equal(res[5], null) - })) + }) }) diff --git a/test/integration/network-update.js b/test/integration/network-update.js deleted file mode 100644 index baae2b7a3ef1527d13cbd8df2d2254dc8959d774..0000000000000000000000000000000000000000 --- a/test/integration/network-update.js +++ /dev/null @@ -1,129 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict"; - -const co = require('co'); -const _ = require('underscore'); -const rp = require('request-promise'); -const httpTest = require('./tools/http'); -const node = require('./tools/node'); -const TestUser = require('./tools/TestUser').TestUser -const commit = require('./tools/commit'); -const sync = require('./tools/sync'); -const until = require('./tools/until'); -const toolbox = require('./tools/toolbox'); -const BlockDTO = require("../../app/lib/dto/BlockDTO"); - -const expectHttpCode = httpTest.expectHttpCode; -const expectAnswer = httpTest.expectAnswer; - -const MEMORY_MODE = true; -const commonConf = { - ipv4: '127.0.0.1', - remoteipv4: '127.0.0.1', - currency: 'bb', - httpLogs: true, - forksize: 3, - sigQty: 1 -}; - -const catKeyPair = { - pair: { - pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', - sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' - } -}; - -const tocKeyPair = { - pair: { - pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', - sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F' - } -}; - -let s1, s2, cat, toc - - -describe("Network updating", function() { - - before(function() { - - return co(function *() { - - s1 = toolbox.server(_.clone(catKeyPair)); - s2 = toolbox.server(_.clone(tocKeyPair)); - - cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); - toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); - - const commitS1 = commit(s1); - const commitS2 = commit(s2); - - yield [s1, s2].reduce((p, server) => co(function*() { - yield p; - yield server.initDalBmaConnections() - require('../../app/modules/router').RouterDependency.duniter.methods.routeToNetwork(server); - }), Promise.resolve()); - - // Server 1 - yield cat.createIdentity(); - yield toc.createIdentity(); - yield toc.cert(cat); - yield cat.cert(toc); - yield cat.join(); - yield toc.join(); - for (const i in _.range(32)) { - yield commitS1(); // block#0 - } - // // s2 syncs from s1 - yield sync(0, 31, s1, s2); - - const b2 = yield s1.makeNext({}); - yield s1.postBlock(b2); - yield s2.postBlock(b2); - yield s1.recomputeSelfPeer(); // peer#1 - yield s1.sharePeeringWith(s2); - const b3 = yield s1.makeNext({}); - yield s1.postBlock(b3); - yield s2.postBlock(b3); - yield s2.waitToHaveBlock(b3.number); - yield s1.recomputeSelfPeer(); // peer#1 - yield s1.sharePeeringWith(s2); - }); - }); - - describe("Server 1 /network/peering", function() { - - it('/peers?leaf=LEAFDATA', () => co(function*() { - const data = yield s1.get('/network/peering/peers?leaves=true'); - const leaf = data.leaves[0]; - const res = yield s1.get('/network/peering/peers?leaf=' + leaf); - res.leaf.value.should.have.property("pubkey").equal('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); - res.leaf.value.should.have.property("block").match(new RegExp('^3-')); - res.leaf.value.should.have.property("raw").match(new RegExp('.*Block: 3-.*')); - })); - }); - - describe("Server 2 /network/peering", function() { - - it('/peers?leaf=LEAFDATA', () => co(function*() { - const data = yield s2.get('/network/peering/peers?leaves=true'); - const leaf = data.leaves[0]; - const res = yield s2.get('/network/peering/peers?leaf=' + leaf); - res.leaf.value.should.have.property("pubkey").equal('DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo'); - res.leaf.value.should.have.property("block").match(new RegExp('^0-')); - res.leaf.value.should.have.property("raw").match(new RegExp('.*Block: 0-.*')); - })); - }); - }); diff --git a/test/integration/network.js b/test/integration/network/network-merkle.ts similarity index 87% rename from test/integration/network.js rename to test/integration/network/network-merkle.ts index 9f75b690445779c9511a5629e529cf5a63dc2970..d1ea5d084537f2be595610d24fb14aad9ece9638 100644 --- a/test/integration/network.js +++ b/test/integration/network/network-merkle.ts @@ -11,18 +11,13 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {Underscore} from "../../../app/lib/common-libs/underscore" +import {HttpMerkleOfPeers} from "../../../app/modules/bma/lib/dtos" +import {NewTestingServer} from "../tools/toolbox" +import {expectAnswer, expectHttpCode} from "../tools/http-expect" -const co = require('co'); -const _ = require('underscore'); const rp = require('request-promise'); -const httpTest = require('./tools/http'); -const node = require('./tools/node'); -const expectHttpCode = httpTest.expectHttpCode; -const expectAnswer = httpTest.expectAnswer; - -const MEMORY_MODE = true; const commonConf = { bmaWithCrawler: true, ipv4: '127.0.0.1', @@ -33,7 +28,8 @@ const commonConf = { sigQty: 1 }; -const s1 = node('bb33', _.extend({ +const s1 = NewTestingServer(Underscore.extend({ + name: 'bb33', ipv4: '127.0.0.1', port: '20501', remoteport: '20501', @@ -46,7 +42,8 @@ const s1 = node('bb33', _.extend({ sigQty: 1, dt: 0, ud0: 120 }, commonConf)); -const s2 = node('bb12', _.extend({ +const s2 = NewTestingServer(Underscore.extend({ + name: 'bb12', port: '20502', remoteport: '20502', ws2p: { upnp: false }, @@ -58,20 +55,18 @@ const s2 = node('bb12', _.extend({ describe("Network Merkle", function() { - before(function() { - - return co(function *() { - yield s1.startTesting(); - yield s2.startTesting(); - let peer1 = yield s1.peeringP(); - yield s2.submitPeerP(peer1); - }); - }); + before(async () => { + await s1.initDalBmaConnections() + await s2.initDalBmaConnections() + await s1._server.PeeringService.generateSelfPeer(s1._server.conf, 0) + await s2._server.PeeringService.generateSelfPeer(s1._server.conf, 0) + await s1.sharePeeringWith(s2) + }) describe("Server 1 /network/peering", function() { it('/peers?leaves=true', function() { - return expectAnswer(rp('http://127.0.0.1:20501/network/peering/peers?leaves=true', { json: true }), (res) => { + return expectAnswer(rp('http://127.0.0.1:20501/network/peering/peers?leaves=true', { json: true }), (res:HttpMerkleOfPeers) => { res.should.have.property('depth').equal(0); res.should.have.property('nodesCount').equal(0); res.should.have.property('leavesCount').equal(1); @@ -82,7 +77,7 @@ describe("Network Merkle", function() { }); it('/peers?leaf=C3EAB939F0BEF711461A140A1BA2649C75905107FACA3BE9C5F76F7FD1C7BC5E', function() { - return expectAnswer(rp('http://127.0.0.1:20501/network/peering/peers?leaf=C3EAB939F0BEF711461A140A1BA2649C75905107FACA3BE9C5F76F7FD1C7BC5E', { json: true }), (res) => { + return expectAnswer(rp('http://127.0.0.1:20501/network/peering/peers?leaf=C3EAB939F0BEF711461A140A1BA2649C75905107FACA3BE9C5F76F7FD1C7BC5E', { json: true }), (res:HttpMerkleOfPeers) => { res.should.have.property('depth').equal(0); res.should.have.property('nodesCount').equal(0); res.should.have.property('leavesCount').equal(1); @@ -104,7 +99,7 @@ describe("Network Merkle", function() { describe("Server 2 /network/peering", function() { it('/peers?leaves=true', function() { - return expectAnswer(rp('http://127.0.0.1:20502/network/peering/peers?leaves=true', { json: true }), (res) => { + return expectAnswer(rp('http://127.0.0.1:20502/network/peering/peers?leaves=true', { json: true }), (res:HttpMerkleOfPeers) => { res.should.have.property('depth').equal(1); res.should.have.property('nodesCount').equal(1); res.should.have.property('leavesCount').equal(2); @@ -116,7 +111,7 @@ describe("Network Merkle", function() { }); it('/peers?leaf=BDD850441E3CDEB9005345B425CDBDA83E7BC7E5D83E9130C6012084F93CD220', function() { - return expectAnswer(rp('http://127.0.0.1:20502/network/peering/peers?leaf=BDD850441E3CDEB9005345B425CDBDA83E7BC7E5D83E9130C6012084F93CD220', { json: true }), (res) => { + return expectAnswer(rp('http://127.0.0.1:20502/network/peering/peers?leaf=BDD850441E3CDEB9005345B425CDBDA83E7BC7E5D83E9130C6012084F93CD220', { json: true }), (res:HttpMerkleOfPeers) => { res.should.have.property('depth').equal(1); res.should.have.property('nodesCount').equal(1); res.should.have.property('leavesCount').equal(2); diff --git a/test/integration/peerings.js b/test/integration/network/network-peerings.ts similarity index 53% rename from test/integration/peerings.js rename to test/integration/network/network-peerings.ts index f9c42296e841edfd09aacd026c013116a6aed438..a1a58f729d1f1357a96274ee1ec8c61a4f5b941d 100644 --- a/test/integration/peerings.js +++ b/test/integration/network/network-peerings.ts @@ -11,27 +11,20 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {NewTestingServer, serverWaitBlock, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {CrawlerDependency} from "../../../app/modules/crawler/index" +import {Contacter} from "../../../app/modules/crawler/lib/contacter" +import {PeerDTO} from "../../../app/lib/dto/PeerDTO" +import {BmaDependency} from "../../../app/modules/bma/index" +import {RouterDependency} from "../../../app/modules/router" +import {Underscore} from "../../../app/lib/common-libs/underscore" +import {sync} from "../tools/test-sync" +import {shutDownEngine} from "../tools/shutdown-engine" +import {expectJSON} from "../tools/http-expect" -const co = require('co'); -const _ = require('underscore'); const should = require('should'); -const duniter = require('../../index'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const TestUser = require('./tools/TestUser').TestUser -const constants = require('../../app/lib/constants'); const rp = require('request-promise'); -const httpTest = require('./tools/http'); -const commit = require('./tools/commit'); -const sync = require('./tools/sync'); -const toolbox = require('./tools/toolbox'); -const contacter = require('../../app/modules/crawler').CrawlerDependency.duniter.methods.contacter; -const until = require('./tools/until'); -const shutDownEngine = require('./tools/shutDownEngine'); -const multicaster = require('../../app/lib/streams/multicaster'); -const PeerDTO = require('../../app/lib/dto/PeerDTO').PeerDTO - -const expectJSON = httpTest.expectJSON; const MEMORY_MODE = true; const commonConf = { @@ -44,20 +37,20 @@ const commonConf = { sigQty: 1 }; -let s1, s2, s3, cat, toc, tic +let s1:TestingServer, s2:TestingServer, s3:TestingServer, cat:TestUser, toc:TestUser, tic:TestUser -let nodeS1; -let nodeS2; -let nodeS3; +let nodeS1:Contacter +let nodeS2:Contacter +let nodeS3:Contacter -describe("Network", function() { +describe("Network peering", function() { before(function() { - s1 = duniter( - 'bb_net1', - MEMORY_MODE, - _.extend({ + s1 = NewTestingServer( + Underscore.extend({ + name: 'bb_net1', + memory: MEMORY_MODE, port: '7784', pair: { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', @@ -65,10 +58,10 @@ describe("Network", function() { } }, commonConf)); - s2 = duniter( - 'bb_net2', - MEMORY_MODE, - _.extend({ + s2 = NewTestingServer( + Underscore.extend({ + name: 'bb_net2', + memory: MEMORY_MODE, port: '7785', pair: { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', @@ -76,10 +69,10 @@ describe("Network", function() { } }, commonConf)); - s3 = duniter( - 'bb_net3', - MEMORY_MODE, - _.extend({ + s3 = NewTestingServer( + Underscore.extend({ + name: 'bb_net3', + memory: MEMORY_MODE, port: '7786', pair: { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', @@ -91,86 +84,77 @@ describe("Network", function() { toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); tic = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s1 }); - const commitS1 = commit(s1); - const commitS2 = commit(s2); - const commitS3 = commit(s3); - return [s1, s2, s3].reduce(function(p, server) { - server.addEndpointsDefinitions(() => require('../../app/modules/bma').BmaDependency.duniter.methods.getMainEndpoint(server.conf)) + server._server.addEndpointsDefinitions(() => BmaDependency.duniter.methods.getMainEndpoint(server.conf)) return p .then(function(){ return server .initWithDAL() - .then(bma) + .then(BmaDependency.duniter.methods.bma) .then(function(bmaAPI){ return bmaAPI.openConnections() .then(() => { server.bma = bmaAPI; - require('../../app/modules/router').RouterDependency.duniter.methods.routeToNetwork(server); + RouterDependency.duniter.methods.routeToNetwork(server._server); }); }); }); }, Promise.resolve()) - .then(function(){ - return co(function *() { - nodeS1 = contacter('127.0.0.1', s1.conf.port); - nodeS2 = contacter('127.0.0.1', s2.conf.port); - nodeS3 = contacter('127.0.0.1', s3.conf.port); - // Server 1 - yield cat.createIdentity(); - yield toc.createIdentity(); - yield tic.createIdentity(); - yield toc.cert(cat); - yield cat.cert(toc); - yield cat.cert(tic); - yield cat.join(); - yield toc.join(); - yield tic.join(); - yield commitS1(); - // Server 2 syncs block 0 - yield sync(0, 0, s1, s2); - yield toolbox.serverWaitBlock(s1, 0) - // Server 3 syncs block 0 - yield sync(0, 0, s1, s3); - yield toolbox.serverWaitBlock(s3, 0) - yield nodeS1.getPeer().then((peer) => nodeS2.postPeer(PeerDTO.fromJSONObject(peer).getRawSigned())).catch(e => console.error(e)) - yield nodeS2.getPeer().then((peer) => nodeS1.postPeer(PeerDTO.fromJSONObject(peer).getRawSigned())).catch(e => console.error(e)) - yield nodeS3.getPeer().then((peer) => nodeS1.postPeer(PeerDTO.fromJSONObject(peer).getRawSigned())).catch(e => console.error(e)) - yield commitS1(); - yield [ - toolbox.serverWaitBlock(s2, 1), - toolbox.serverWaitBlock(s3, 1) - ]; - // A block was successfully spread accross the network - yield s2.bma.closeConnections(); - yield commitS1(); - yield [ - toolbox.serverWaitBlock(s3, 2) - ]; - // Server 2 syncs block number 2 (it did not have it) - yield sync(2, 2, s1, s2); - yield toolbox.serverWaitBlock(s2, 2) - yield s2.recomputeSelfPeer(); - yield s2.bma.openConnections(); - yield new Promise((resolve) => setTimeout(resolve, 1000)); - yield [ - toolbox.serverWaitBlock(s2, 4), - toolbox.serverWaitBlock(s3, 4), - commitS1() - .then(commitS1) - ]; - yield [ - toolbox.serverWaitBlock(s1, 5), - toolbox.serverWaitBlock(s2, 5), - commitS3() - ]; - yield [ - toolbox.serverWaitBlock(s1, 6), - toolbox.serverWaitBlock(s3, 6), - commitS2() - ]; - }); + .then(async () => { + nodeS1 = CrawlerDependency.duniter.methods.contacter('127.0.0.1', s1.conf.port); + nodeS2 = CrawlerDependency.duniter.methods.contacter('127.0.0.1', s2.conf.port); + nodeS3 = CrawlerDependency.duniter.methods.contacter('127.0.0.1', s3.conf.port); + // Server 1 + await cat.createIdentity(); + await toc.createIdentity(); + await tic.createIdentity(); + await toc.cert(cat); + await cat.cert(toc); + await cat.cert(tic); + await cat.join(); + await toc.join(); + await tic.join(); + await s1.commit(); + // Server 2 syncs block 0 + await sync(0, 0, s1._server, s2._server); + await serverWaitBlock(s1._server, 0) + // Server 3 syncs block 0 + await sync(0, 0, s1._server, s3._server); + await serverWaitBlock(s3._server, 0) + await nodeS1.getPeer().then((peer) => nodeS2.postPeer(PeerDTO.fromJSONObject(peer).getRawSigned())).catch(e => console.error(e)) + await nodeS2.getPeer().then((peer) => nodeS1.postPeer(PeerDTO.fromJSONObject(peer).getRawSigned())).catch(e => console.error(e)) + await nodeS3.getPeer().then((peer) => nodeS1.postPeer(PeerDTO.fromJSONObject(peer).getRawSigned())).catch(e => console.error(e)) + await s1.commit(); + await Promise.all([ + serverWaitBlock(s2._server, 1), + serverWaitBlock(s3._server, 1) + ]) + // A block was successfully spread accross the network + await s2.bma.closeConnections(); + await s1.commit(); + await serverWaitBlock(s3._server, 2) + // Server 2 syncs block number 2 (it did not have it) + await sync(2, 2, s1._server, s2._server); + await serverWaitBlock(s2._server, 2) + await s2.recomputeSelfPeer(); + await s2.bma.openConnections(); + await new Promise((resolve) => setTimeout(resolve, 1000)); + await Promise.all([ + serverWaitBlock(s2._server, 4), + serverWaitBlock(s3._server, 4), + s1.commit().then(() => s1.commit()) + ]) + await Promise.all([ + serverWaitBlock(s1._server, 5), + serverWaitBlock(s2._server, 5), + s3.commit() + ]) + await Promise.all([ + serverWaitBlock(s1._server, 6), + serverWaitBlock(s3._server, 6), + s2.commit() + ]) }) ; }); diff --git a/test/integration/peers-same-pubkey.js b/test/integration/network/network-peers-same-pubkey.ts similarity index 63% rename from test/integration/peers-same-pubkey.js rename to test/integration/network/network-peers-same-pubkey.ts index 3e85615f607b8cf5e553d7802834d6a10bcca791..f6a35980af983fa98e8a67df37ce7b209011d6a4 100644 --- a/test/integration/peers-same-pubkey.js +++ b/test/integration/network/network-peers-same-pubkey.ts @@ -11,17 +11,16 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {TestUser} from "../tools/TestUser" +import {NewTestingServer, serverWaitBlock, TestingServer} from "../tools/toolbox" +import {PeerDTO} from "../../../app/lib/dto/PeerDTO" +import {HttpPeer} from "../../../app/modules/bma/lib/dtos" +import {RouterDependency} from "../../../app/modules/router" +import {Underscore} from "../../../app/lib/common-libs/underscore" +import {until} from "../tools/test-until" +import {sync} from "../tools/test-sync" -const co = require('co'); -const _ = require('underscore'); const should = require('should'); -const TestUser = require('./tools/TestUser').TestUser -const commit = require('./tools/commit'); -const sync = require('./tools/sync'); -const until = require('./tools/until'); -const toolbox = require('./tools/toolbox'); -const PeerDTO = require('../../app/lib/dto/PeerDTO').PeerDTO const catKeyPair = { pair: { @@ -30,76 +29,73 @@ const catKeyPair = { } }; -let s1, s2, s3, cat, toc +let s1:TestingServer, s2:TestingServer, s3:TestingServer, cat:TestUser, toc:TestUser describe("Peer document", function() { - before(() => co(function*() { + before(async () => { - s1 = toolbox.server(_.clone(catKeyPair)); - s2 = toolbox.server(_.clone(catKeyPair)); - s3 = toolbox.server(_.clone(catKeyPair)); + s1 = NewTestingServer(Underscore.clone(catKeyPair)); + s2 = NewTestingServer(Underscore.clone(catKeyPair)); + s3 = NewTestingServer(Underscore.clone(catKeyPair)); cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); - const commitS1 = commit(s1); - const commitS2 = commit(s2); - - yield [s1, s2, s3].reduce((p, server) => co(function*() { - yield p; - yield server.initDalBmaConnections() - require('../../app/modules/router').RouterDependency.duniter.methods.routeToNetwork(server); - }), Promise.resolve()); + await [s1, s2, s3].reduce(async (p, server) => { + await p; + await server.initDalBmaConnections() + RouterDependency.duniter.methods.routeToNetwork(server._server) + }, Promise.resolve()) // Server 1 - yield cat.createIdentity(); - yield toc.createIdentity(); - yield toc.cert(cat); - yield cat.cert(toc); - yield cat.join(); - yield toc.join(); - yield commitS1(); // block#0 - yield commitS1(); // block#1 - yield s1.recomputeSelfPeer(); // peer#1 - yield commitS1(); // block#2 + await cat.createIdentity(); + await toc.createIdentity(); + await toc.cert(cat); + await cat.cert(toc); + await cat.join(); + await toc.join(); + await s1.commit(); // block#0 + await s1.commit(); // block#1 + await s1.recomputeSelfPeer(); // peer#1 + await s1.commit(); // block#2 // // s2 syncs from s1 - yield sync(0, 2, s1, s2); - yield toolbox.serverWaitBlock(s1, 2) - yield [ - s1.get('/network/peering').then((peer) => s2.post('/network/peering/peers', { peer: PeerDTO.fromJSONObject(peer).getRawSigned() })), // peer#2 + await sync(0, 2, s1._server, s2._server); + await serverWaitBlock(s1._server, 2) + await Promise.all([ + s1.get('/network/peering').then((peer:HttpPeer) => s2.post('/network/peering/peers', { peer: PeerDTO.fromJSONObject(peer).getRawSigned() })), // peer#2 until(s2, 'peer', 1) - ]; + ]) - yield [ - commitS2(), // block#3 - toolbox.serverWaitBlock(s1, 3) - ]; + await Promise.all([ + s2.commit(), // block#3 + serverWaitBlock(s1._server, 3) + ]) - yield sync(0, 3, s1, s3); - yield toolbox.serverWaitBlock(s3, 3) + await sync(0, 3, s1._server, s3._server); + await serverWaitBlock(s3._server, 3) - const peer1 = yield s1.get('/network/peering'); + const peer1 = await s1.get('/network/peering'); peer1.should.have.property("block").match(/^2-/); - yield [ + await Promise.all([ s3.post('/network/peering/peers', { peer: PeerDTO.fromJSONObject(peer1).getRawSigned() }), // peer#3 until(s3, 'peer', 2) - ]; - const peer3 = yield s3.get('/network/peering'); + ]) + const peer3 = await s3.get('/network/peering'); peer3.should.have.property("block").match(/^3-/); - yield [ - commitS2(), // block#4 - toolbox.serverWaitBlock(s1, 4), - toolbox.serverWaitBlock(s3, 4) - ]; + await Promise.all([ + s2.commit(), // block#4 + serverWaitBlock(s1._server, 4), + serverWaitBlock(s3._server, 4) + ]) - yield [ - commitS1(), // block#5 - toolbox.serverWaitBlock(s2, 5), - toolbox.serverWaitBlock(s3, 5) - ]; - })); + await Promise.all([ + s1.commit(), // block#5 + serverWaitBlock(s2._server, 5), + serverWaitBlock(s3._server, 5) + ]) + }) after(() => { return Promise.all([ @@ -115,25 +111,25 @@ describe("Peer document", function() { leavesCount: 1 })); - it('leaf data', () => co(function*() { - const data = yield s1.get('/network/peering/peers?leaves=true'); + it('leaf data', async () => { + const data = await s1.get('/network/peering/peers?leaves=true'); const leaf = data.leaves[0]; - const res = yield s1.get('/network/peering/peers?leaf=' + leaf); + const res = await s1.get('/network/peering/peers?leaf=' + leaf); res.leaf.value.should.have.property("pubkey").equal('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); res.leaf.value.should.have.property("block").match(new RegExp('^3-')); res.leaf.value.should.have.property("raw").match(new RegExp('.*Block: 3-.*')); res.leaf.value.should.have.property("endpoints").length(3); - })); + }) - it('peers', () => s1.expectThat('/network/peering', (res) => { + it('peers', () => s1.expectThat('/network/peering', (res:HttpPeer) => { res.should.have.property("pubkey").equal('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); res.should.have.property("block").match(new RegExp('^3-')); res.should.have.property("endpoints").length(3); })); - it('peering should have been updated by node 1', () => s1.expectThat('/network/peering', (res) => { + it('peering should have been updated by node 1', () => s1.expectThat('/network/peering', (res:HttpPeer) => { res.should.have.property("pubkey").equal('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); res.should.have.property("block").match(new RegExp('^3-')); res.should.have.property("endpoints").length(3); @@ -152,18 +148,18 @@ describe("Peer document", function() { })); - it('leaf data', () => co(function*() { - const data = yield s2.get('/network/peering/peers?leaves=true'); + it('leaf data', async () => { + const data = await s2.get('/network/peering/peers?leaves=true'); const leaf = data.leaves[0]; - const res = yield s2.get('/network/peering/peers?leaf=' + leaf); + const res = await s2.get('/network/peering/peers?leaf=' + leaf); res.leaf.value.should.have.property("pubkey").equal('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); res.leaf.value.should.have.property("block").match(new RegExp('^3-')); res.leaf.value.should.have.property("raw").match(new RegExp('.*Block: 3-.*')); res.leaf.value.should.have.property("endpoints").length(3); - })); + }) - it('peering should have been updated by node 1', () => s2.expectThat('/network/peering', (res) => { + it('peering should have been updated by node 1', () => s2.expectThat('/network/peering', (res:HttpPeer) => { res.should.have.property("pubkey").equal('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); res.should.have.property("block").match(new RegExp('^3-')); res.should.have.property("endpoints").length(3); @@ -181,17 +177,17 @@ describe("Peer document", function() { leavesCount: 1 })); - it('leaf data', () => co(function*() { - const data = yield s3.get('/network/peering/peers?leaves=true'); + it('leaf data', async () => { + const data = await s3.get('/network/peering/peers?leaves=true'); const leaf = data.leaves[0]; - const res = yield s3.get('/network/peering/peers?leaf=' + leaf); + const res = await s3.get('/network/peering/peers?leaf=' + leaf); res.leaf.value.should.have.property("pubkey").equal('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); res.leaf.value.should.have.property("block").match(new RegExp('^3-')); res.leaf.value.should.have.property("raw").match(new RegExp('.*Block: 3-.*')); res.leaf.value.should.have.property("endpoints").length(3); - })); + }) - it('peering should have been updated by node 1', () => s3.expectThat('/network/peering', (res) => { + it('peering should have been updated by node 1', () => s3.expectThat('/network/peering', (res:HttpPeer) => { res.should.have.property("pubkey").equal('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); res.should.have.property("block").match(new RegExp('^3-')); res.should.have.property("endpoints").length(3); @@ -200,6 +196,6 @@ describe("Peer document", function() { it('current block', () => s3.expectJSON('/blockchain/current', { number: 5, issuer: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd' - })); - }); -}); + })) + }) +}) diff --git a/test/integration/network/network-update.ts b/test/integration/network/network-update.ts new file mode 100644 index 0000000000000000000000000000000000000000..4c0a523d88a388af5b0fd4a4156bf82082e814e4 --- /dev/null +++ b/test/integration/network/network-update.ts @@ -0,0 +1,104 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +"use strict"; + +import {TestUser} from "../tools/TestUser" +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {Underscore} from "../../../app/lib/common-libs/underscore" +import {RouterDependency} from "../../../app/modules/router" +import {sync} from "../tools/test-sync" + +const catKeyPair = { + pair: { + pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', + sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' + } +}; + +const tocKeyPair = { + pair: { + pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', + sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F' + } +}; + +let s1:TestingServer, s2:TestingServer, cat:TestUser, toc:TestUser + + +describe("Network updating", function() { + + before(async () => { + + s1 = NewTestingServer(Underscore.clone(catKeyPair)); + s2 = NewTestingServer(Underscore.clone(tocKeyPair)); + + cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); + toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); + + await [s1, s2].reduce(async (p, server) => { + await p; + await server.initDalBmaConnections() + RouterDependency.duniter.methods.routeToNetwork(server._server); + }, Promise.resolve()) + + // Server 1 + await cat.createIdentity(); + await toc.createIdentity(); + await toc.cert(cat); + await cat.cert(toc); + await cat.join(); + await toc.join(); + for (const i in Underscore.range(32)) { + await s1.commit(); // block#0 + } + // // s2 syncs from s1 + await sync(0, 31, s1._server, s2._server); + + const b2 = await s1.makeNext({}); + await s1.postBlock(b2); + await s2.postBlock(b2); + await s1.recomputeSelfPeer(); // peer#1 + await s1.sharePeeringWith(s2); + const b3 = await s1.makeNext({}); + await s1.postBlock(b3); + await s2.postBlock(b3); + await s2.waitToHaveBlock(b3.number); + await s1.recomputeSelfPeer(); // peer#1 + await s1.sharePeeringWith(s2); + }); + + describe("Server 1 /network/peering", function() { + + it('/peers?leaf=LEAFDATA', async () => { + const data = await s1.get('/network/peering/peers?leaves=true'); + const leaf = data.leaves[0]; + const res = await s1.get('/network/peering/peers?leaf=' + leaf); + res.leaf.value.should.have.property("pubkey").equal('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); + res.leaf.value.should.have.property("block").match(new RegExp('^3-')); + res.leaf.value.should.have.property("raw").match(new RegExp('.*Block: 3-.*')); + }) + }); + + describe("Server 2 /network/peering", function() { + + it('/peers?leaf=LEAFDATA', async () => { + const data = await s2.get('/network/peering/peers?leaves=true'); + const leaf = data.leaves[0]; + const res = await s2.get('/network/peering/peers?leaf=' + leaf); + res.leaf.value.should.have.property("pubkey").equal('DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo'); + res.leaf.value.should.have.property("block").match(new RegExp('^0-')); + res.leaf.value.should.have.property("raw").match(new RegExp('.*Block: 0-.*')); + }) + }) + }) diff --git a/test/integration/peer-outdated.js b/test/integration/network/peer-outdated.ts similarity index 65% rename from test/integration/peer-outdated.js rename to test/integration/network/peer-outdated.ts index 79a61acffb5b52c5b85c8528b2ec54791d543ead..a83d9540e72b060a8579d28be0b77d0778c9249f 100644 --- a/test/integration/peer-outdated.js +++ b/test/integration/network/peer-outdated.ts @@ -11,36 +11,33 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {HttpPeer, HttpPeers} from "../../../app/modules/bma/lib/dtos" +import {PeerDTO} from "../../../app/lib/dto/PeerDTO" +import {RouterDependency} from "../../../app/modules/router" +import {Multicaster} from "../../../app/lib/streams/multicaster" +import {until} from "../tools/test-until" -const co = require('co'); const should = require('should'); const es = require('event-stream'); -const _ = require('underscore'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const TestUser = require('./tools/TestUser').TestUser -const commit = require('./tools/commit'); -const until = require('./tools/until'); -const toolbox = require('./tools/toolbox'); -const Multicaster = require('../../app/lib/streams/multicaster').Multicaster -const PeerDTO = require('../../app/lib/dto/PeerDTO').PeerDTO -let s1, s2, cat, toc +let s1:TestingServer, s2:TestingServer, cat:TestUser, toc:TestUser describe("Peer document expiry", function() { - let peer1V1; + let peer1V1:HttpPeer - before(() => co(function*() { + before(async () => { - s1 = toolbox.server({ + s1 = NewTestingServer({ pair: { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' } }); - s2 = toolbox.server({ + s2 = NewTestingServer({ pair: { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F' @@ -50,30 +47,28 @@ describe("Peer document expiry", function() { cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); - const commitS1 = commit(s1); - - yield [s1, s2].reduce((p, server) => co(function*() { - yield p; - yield server.initDalBmaConnections() - require('../../app/modules/router').RouterDependency.duniter.methods.routeToNetwork(server); - }), Promise.resolve()); + await [s1, s2].reduce(async (p:Promise<any>, server:TestingServer) => { + await p; + await server.initDalBmaConnections() + RouterDependency.duniter.methods.routeToNetwork(server._server) + }, Promise.resolve()) // Server 1 - yield cat.createIdentity(); - yield toc.createIdentity(); - yield toc.cert(cat); - yield cat.cert(toc); - yield cat.join(); - yield toc.join(); - yield commitS1(); // block#0 - yield commitS1(); // block#1 - yield s1.recomputeSelfPeer(); // peer#1 - peer1V1 = yield s1.get('/network/peering'); - yield commitS1(); // block#2 - yield s1.recomputeSelfPeer(); // peer#2 - yield s2.syncFrom(s1, 0, 2); - yield s2.waitToHaveBlock(2) - })); + await cat.createIdentity(); + await toc.createIdentity(); + await toc.cert(cat); + await cat.cert(toc); + await cat.join(); + await toc.join(); + await s1.commit(); // block#0 + await s1.commit(); // block#1 + await s1.recomputeSelfPeer(); // peer#1 + peer1V1 = await s1.get('/network/peering'); + await s1.commit(); // block#2 + await s1.recomputeSelfPeer(); // peer#2 + await s2.syncFrom(s1._server, 0, 2); + await s2.waitToHaveBlock(2) + }) after(() => { return Promise.all([ @@ -82,50 +77,50 @@ describe("Peer document expiry", function() { ]) }) - it('sending back V1 peer document should return the latest known one', () => co(function*() { + it('sending back V1 peer document should return the latest known one', async () => { let res; try { - yield s1.post('/network/peering/peers', { peer: PeerDTO.fromJSONObject(peer1V1).getRawSigned() }); + await s1.post('/network/peering/peers', { peer: PeerDTO.fromJSONObject(peer1V1).getRawSigned() }); } catch (e) { res = e; } should.exist(res); res.should.have.property("error").property("peer").property("block").match(/^2-/); - })); + }) - it('routing V1 peer document should raise an "outdated" event', () => co(function*() { + it('routing V1 peer document should raise an "outdated" event', async () => { const caster = new Multicaster(); return new Promise((resolve) => { caster - .pipe(es.mapSync((obj) => { + .pipe(es.mapSync((obj:any) => { obj.should.have.property("outdated").equal(true); resolve(); })); caster.sendPeering(PeerDTO.fromJSONObject(peer1V1), PeerDTO.fromJSONObject(peer1V1)); }); - })); + }) it('mirror should have 3 known blocks', () => s2.expectJSON('/blockchain/current', { number: 2 })); - it('mirror should have 1 known peers', () => s2.expect('/network/peers', (res) => { + it('mirror should have 1 known peers', () => s2.expect('/network/peers', (res:HttpPeers) => { res.should.have.property("peers").length(1); res.peers[0].should.have.property("pubkey").equal('DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo'); })); - it('routing V1 peer document should inject newer peer', () => co(function*() { - yield [ + it('routing V1 peer document should inject newer peer', async () => { + await [ s2.writePeer(peer1V1), until(s2, 'peer', 2) ]; - })); + }) - it('mirror should now have 2 known peers', () => s2.expect('/network/peers', (res) => { + it('mirror should now have 2 known peers', () => s2.expect('/network/peers', (res:HttpPeers) => { res.should.have.property("peers").length(2); res.peers[0].should.have.property("pubkey").equal('DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo'); res.peers[0].should.have.property("block").match(/^0-/); res.peers[1].should.have.property("pubkey").equal('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); res.peers[1].should.have.property("block").match(/^2-/); - })); -}); + })) +}) diff --git a/test/integration/continuous-proof.js b/test/integration/proof-of-work/continuous-proof.ts similarity index 63% rename from test/integration/continuous-proof.js rename to test/integration/proof-of-work/continuous-proof.ts index ffe8615b33bd37db02e9ffaf2564cfddd3d53c1c..452f7c2d0a454122446c7e7853470f0c3b38d1c8 100644 --- a/test/integration/continuous-proof.js +++ b/test/integration/proof-of-work/continuous-proof.ts @@ -11,24 +11,22 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {TestUser} from "../tools/TestUser" +import {NewTestingServer, simpleNetworkOf2NodesAnd2Users, TestingServer} from "../tools/toolbox" +import {CrawlerDependency} from "../../../app/modules/crawler/index" -const co = require('co'); const es = require('event-stream'); const should = require('should'); -const TestUser = require('./tools/TestUser').TestUser -const toolbox = require('./tools/toolbox'); -const constants = require('../../app/lib/constants'); const NB_CORES_FOR_COMPUTATION = 1 // For simple tests. Can be changed to test multiple cores. -let s1, s2, s3, i1, i2 +let s1:TestingServer, s2:TestingServer, s3:TestingServer, i1:TestUser, i2:TestUser describe("Continous proof-of-work", function() { - before(() => co(function*() { + before(async () => { - s1 = toolbox.server({ + s1 = NewTestingServer({ cpu: 1, nbCores: NB_CORES_FOR_COMPUTATION, powDelay: 100, @@ -42,37 +40,37 @@ describe("Continous proof-of-work", function() { i1 = new TestUser('i1', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); i2 = new TestUser('i2', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s1 }); - yield s1.prepareForNetwork(); - yield i1.createIdentity(); - yield i2.createIdentity(); - yield i1.cert(i2); - yield i2.cert(i1); - yield i1.join(); - yield i2.join(); - yield s1.commit(); - yield s1.closeCluster(); - })); - - it('should automatically stop waiting if nothing happens', () => co(function*() { + await s1.prepareForNetwork(); + await i1.createIdentity(); + await i2.createIdentity(); + await i1.cert(i2); + await i2.cert(i1); + await i1.join(); + await i2.join(); + await s1.commit(); + await s1.closeCluster(); + }) + + it('should automatically stop waiting if nothing happens', async () => { s1.conf.powSecurityRetryDelay = 10; let start = Date.now(); s1.startBlockComputation(); // s1.permaProver.should.have.property('loops').equal(0); - yield s1.until('block', 1); + await s1.until('block', 1); // s1.permaProver.should.have.property('loops').equal(1); (start - Date.now()).should.be.belowOrEqual(1000); - yield s1.stopBlockComputation(); - yield new Promise((resolve) => setTimeout(resolve, 100)); + await s1.stopBlockComputation(); + await new Promise((resolve) => setTimeout(resolve, 100)); // s1.permaProver.should.have.property('loops').equal(2); s1.conf.powSecurityRetryDelay = 10 * 60 * 1000; - yield s1.revert(); + await s1.revert(); s1.permaProver.loops = 0; - yield s1.stopBlockComputation(); - })); + await s1.stopBlockComputation(); + }) - it('should be able to start generation and find a block', () => co(function*() { + it('should be able to start generation and find a block', async () => { s1.permaProver.should.have.property('loops').equal(0); - yield [ + await [ s1.startBlockComputation(), s1.until('block', 2) ]; @@ -81,48 +79,48 @@ describe("Continous proof-of-work", function() { // * 1 loop for making b#1 // * 1 loop by waiting between b#1 and b#2 // * 1 loop for making b#2 - yield new Promise((resolve) => setTimeout(resolve, 100)); + await new Promise((resolve) => setTimeout(resolve, 100)); // s1.permaProver.should.have.property('loops').equal(4); - yield s1.stopBlockComputation(); + await s1.stopBlockComputation(); // If we wait a bit, the loop should be ended - yield new Promise((resolve) => setTimeout(resolve, 100)); + await new Promise((resolve) => setTimeout(resolve, 100)); // s1.permaProver.should.have.property('loops').equal(5); - yield s1.stopBlockComputation(); - })); + await s1.stopBlockComputation(); + }) - it('should be able to cancel generation because of a blockchain switch', () => co(function*() { + it('should be able to cancel generation because of a blockchain switch', async () => { // s1.permaProver.should.have.property('loops').equal(5); s1.startBlockComputation(); - yield s1.until('block', 1); + await s1.until('block', 1); // * 1 loop for making b#3 - yield new Promise((resolve) => setTimeout(resolve, 100)); + await new Promise((resolve) => setTimeout(resolve, 100)); // s1.permaProver.should.have.property('loops').equal(6); - yield s1.permaProver.blockchainChanged(); - yield new Promise((resolve) => setTimeout(resolve, 100)); + await s1.permaProver.blockchainChanged(); + await new Promise((resolve) => setTimeout(resolve, 100)); // * 1 loop for waiting for b#4 but being interrupted - s1.permaProver.should.have.property('loops').greaterThanOrEqual(5); - yield s1.stopBlockComputation(); + s1.permaProver.should.have.property('loops').greaterThanOrEqual(4); + await s1.stopBlockComputation(); // If we wait a bit, the loop should be ended - yield new Promise((resolve) => setTimeout(resolve, 100)); - s1.permaProver.should.have.property('loops').greaterThanOrEqual(6); - })); + await new Promise((resolve) => setTimeout(resolve, 100)); + s1.permaProver.should.have.property('loops').greaterThanOrEqual(5); + }) - it('testing proof-of-work during a block pulling', () => co(function*() { - const res = yield toolbox.simpleNetworkOf2NodesAnd2Users({ + it('testing proof-of-work during a block pulling', async () => { + const res = await simpleNetworkOf2NodesAnd2Users({ nbCores: NB_CORES_FOR_COMPUTATION, powMin: 0 }), s2 = res.s1, s3 = res.s2; - yield s2.commit(); + await s2.commit(); s2.conf.cpu = 1.0; s2.startBlockComputation(); - yield s2.until('block', 15); - yield s2.stopBlockComputation(); - yield [ - require('../../app/modules/crawler').CrawlerDependency.duniter.methods.pullBlocks(s3), + await s2.until('block', 15); + await s2.stopBlockComputation(); + await [ + CrawlerDependency.duniter.methods.pullBlocks(s3._server), new Promise(res => { - s3.pipe(es.mapSync((e) => { + s3.pipe(es.mapSync((e:any) => { if (e.number === 15) { res() } @@ -132,9 +130,9 @@ describe("Continous proof-of-work", function() { }), s3.startBlockComputation() ]; - const current = yield s3.get('/blockchain/current') - yield s3.stopBlockComputation(); + const current = await s3.get('/blockchain/current') + await s3.stopBlockComputation(); current.number.should.be.aboveOrEqual(14) - yield s1.closeCluster() - })); + await s1.closeCluster() + }) }); diff --git a/test/integration/proof-of-work.js b/test/integration/proof-of-work/proof-of-work.ts similarity index 68% rename from test/integration/proof-of-work.js rename to test/integration/proof-of-work/proof-of-work.ts index dd43486b2af277c2e10c37d989d08958810ab8e6..29b3a7306b386687d1f003ab8a2a3f5ac42be0df 100644 --- a/test/integration/proof-of-work.js +++ b/test/integration/proof-of-work/proof-of-work.ts @@ -11,14 +11,11 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {NewLogger} from "../../../app/lib/logger" +import {BlockProver} from "../../../app/modules/prover/lib/blockProver" -const co = require('co'); const should = require('should'); -const toolbox = require('./tools/toolbox'); -const constants = require('../../app/lib/constants'); -const logger = require('../../app/lib/logger').NewLogger(); -const BlockProver = require('../../app/modules/prover/lib/blockProver').BlockProver +const logger = NewLogger(); /*** conf.medianTimeBlocks @@ -28,11 +25,11 @@ conf.cpu keyring from Key ***/ -const intermediateProofs = []; +const intermediateProofs:any[] = []; const NB_CORES_FOR_COMPUTATION = 1 // For simple tests. Can be changed to test multiple cores. const prover = new BlockProver({ - push: (data) => intermediateProofs.push(data), + push: (data:any) => intermediateProofs.push(data), conf: { nbCores: NB_CORES_FOR_COMPUTATION, cpu: 1.0, // 80%, @@ -42,7 +39,7 @@ const prover = new BlockProver({ } }, logger -}); +} as any); const now = 1474382274 * 1000; const MUST_START_WITH_A_ZERO = 16; @@ -50,8 +47,8 @@ const MUST_START_WITH_TWO_ZEROS = 32; describe("Proof-of-work", function() { - it('should be able to find an easy PoW', () => co(function*() { - let block = yield prover.prove({ + it('should be able to find an easy PoW', async () => { + let block = await prover.prove({ issuer: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', number: 2 }, MUST_START_WITH_TWO_ZEROS, now); @@ -59,29 +56,29 @@ describe("Proof-of-work", function() { intermediateProofs.length.should.be.greaterThan(0); intermediateProofs[intermediateProofs.length - 1].pow.should.have.property('found').equal(true); intermediateProofs[intermediateProofs.length - 1].pow.should.have.property('hash').equal(block.hash); - })); + }) // Too randomly successing test // it('should be able to cancel a proof-of-work on other PoW receival', () => co(function*() { // const now = 1474464489; - // const res = yield toolbox.simpleNetworkOf2NodesAnd2Users({ + // const res = await toolbox.simpleNetworkOf2NodesAnd2Users({ // powMin: 46 // }), s1 = res.s1, s2 = res.s2; - // yield s1.commit({ + // await s1.commit({ // time: now // 38 hits to find the proof (known by test) // }); - // yield s2.until('block', 1); - // yield s1.expectJSON('/blockchain/current', { number: 0 }); - // yield s2.expectJSON('/blockchain/current', { number: 0 }); - // yield s1.commit({ + // await s2.until('block', 1); + // await s1.expectJSON('/blockchain/current', { number: 0 }); + // await s2.expectJSON('/blockchain/current', { number: 0 }); + // await s1.commit({ // time: now + 13 // 521 hits to find the proof // }); - // yield s2.until('block', 1); - // yield s1.expectJSON('/blockchain/current', { number: 1 }); - // yield s2.expectJSON('/blockchain/current', { number: 1 }); + // await s2.until('block', 1); + // await s1.expectJSON('/blockchain/current', { number: 1 }); + // await s2.expectJSON('/blockchain/current', { number: 1 }); // s1.conf.cpu = 1.0; // s2.conf.cpu = 0.02; - // yield Promise.all([ + // await Promise.all([ // // // Make a concurrent trial // Promise.all([ @@ -90,7 +87,7 @@ describe("Proof-of-work", function() { // let s2commit = s2.commit({ time: now + 14 }); // 7320 hits to be found: very high, that's good because we need time for s1 to find the proof *before* s2 // // A little handicap for s1 which will find the proof almost immediately // setTimeout(() => s1.commit({ time: now + 10 }), 100); - // yield s2commit; + // await s2commit; // throw 's2 server should not have found the proof before s1'; // } catch (e) { // should.exist(e); @@ -103,62 +100,62 @@ describe("Proof-of-work", function() { // s1.until('block', 1), // s2.until('block', 1) // ]); - // yield s1.expectJSON('/blockchain/current', { number: 2 }); - // yield s2.expectJSON('/blockchain/current', { number: 2 }); + // await s1.expectJSON('/blockchain/current', { number: 2 }); + // await s2.expectJSON('/blockchain/current', { number: 2 }); // // Both nodes should receive the same last block from s2 // s2.conf.cpu = 1.0; - // yield [ + // await [ // s1.until('block', 1), // s2.until('block', 1), // s2.commit({ time: now + 10 }) // ]; - // yield s1.expectJSON('/blockchain/current', { number: 3 }); - // yield s2.expectJSON('/blockchain/current', { number: 3 }); + // await s1.expectJSON('/blockchain/current', { number: 3 }); + // await s2.expectJSON('/blockchain/current', { number: 3 }); // })); // TODO: re-enable when algorithm is better // it('should be able to cancel a waiting on other PoW receival', () => co(function*() { // const now = 1474464481; - // const res = yield toolbox.simpleNetworkOf2NodesAnd2Users({ + // const res = await toolbox.simpleNetworkOf2NodesAnd2Users({ // powSecurityRetryDelay: 10 * 60 * 1000, // powMaxHandicap: 8, // percentRot: 1, // powMin: 35 // }), s1 = res.s1, s2 = res.s2; - // yield Promise.all([ + // await Promise.all([ // s1.commit({ time: now }), // // We wait until both nodes received the new block // s1.until('block', 1), // s2.until('block', 1) // ]); - // yield s1.expectJSON('/blockchain/current', { number: 0 }); - // yield s2.expectJSON('/blockchain/current', { number: 0 }); - // yield Promise.all([ + // await s1.expectJSON('/blockchain/current', { number: 0 }); + // await s2.expectJSON('/blockchain/current', { number: 0 }); + // await Promise.all([ // s2.commit({ time: now }), // // We wait until both nodes received the new block // s1.until('block', 1), // s2.until('block', 1) // ]); - // yield s1.expectJSON('/blockchain/current', { number: 1 }); - // yield s2.expectJSON('/blockchain/current', { number: 1 }); - // yield Promise.all([ + // await s1.expectJSON('/blockchain/current', { number: 1 }); + // await s2.expectJSON('/blockchain/current', { number: 1 }); + // await Promise.all([ // s1.commit({ time: now }), // // We wait until both nodes received the new block // s1.until('block', 1), // s2.until('block', 1) // ]); - // yield s1.expectJSON('/blockchain/current', { number: 2, issuersCount: 1 }); - // yield s2.expectJSON('/blockchain/current', { number: 2, issuersCount: 1 }); - // yield Promise.all([ + // await s1.expectJSON('/blockchain/current', { number: 2, issuersCount: 1 }); + // await s2.expectJSON('/blockchain/current', { number: 2, issuersCount: 1 }); + // await Promise.all([ // s2.commit({ time: now }), // // We wait until both nodes received the new block // s1.until('block', 1), // s2.until('block', 1) // ]); - // yield s1.expectJSON('/blockchain/current', { number: 3, issuersCount: 2 }); - // yield s2.expectJSON('/blockchain/current', { number: 3, issuersCount: 2 }); - // // yield s2.expectJSON('/blockchain/difficulties', { number: 3, issuersCount: 2 }); - // yield Promise.all([ + // await s1.expectJSON('/blockchain/current', { number: 3, issuersCount: 2 }); + // await s2.expectJSON('/blockchain/current', { number: 3, issuersCount: 2 }); + // // await s2.expectJSON('/blockchain/difficulties', { number: 3, issuersCount: 2 }); + // await Promise.all([ // // new Promise((resolve) => { // s1.startBlockComputation(); @@ -170,7 +167,7 @@ describe("Proof-of-work", function() { // s1.until('block', 2), // s2.until('block', 2) // ]); - // yield s1.expectJSON('/blockchain/current', { number: 5 }); - // yield s2.expectJSON('/blockchain/current', { number: 5 }); + // await s1.expectJSON('/blockchain/current', { number: 5 }); + // await s2.expectJSON('/blockchain/current', { number: 5 }); // })); }); diff --git a/test/integration/v0.4-times.js b/test/integration/protocol/v0.4-times.ts similarity index 69% rename from test/integration/v0.4-times.js rename to test/integration/protocol/v0.4-times.ts index e11ee84fe326da66da098feeb3c8550266cdc8c4..723367d00f5a7208ca033135fbb05f4d5e336885 100644 --- a/test/integration/v0.4-times.js +++ b/test/integration/protocol/v0.4-times.ts @@ -13,11 +13,9 @@ "use strict"; -const co = require('co'); +import {simpleNodeWith2Users, TestingServer} from "../tools/toolbox" + const should = require('should'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const commit = require('./tools/commit'); -const toolbox = require('./tools/toolbox'); const conf = { avgGenTime: 5000, @@ -25,15 +23,15 @@ const conf = { }; const now = 1475069096; -let s1; +let s1:TestingServer describe("Protocol 0.4 Times", function() { - before(() => co(function*() { - const res = yield toolbox.simpleNodeWith2Users(conf); + before(async () => { + const res = await simpleNodeWith2Users(conf); s1 = res.s1; - yield s1.commit({ time: now }); // We must issue a normal root block, because always medianTime(0) == time(0) - })); + await s1.commit({ time: now }); // We must issue a normal root block, because always medianTime(0) == time(0) + }) after(() => { return Promise.all([ @@ -41,16 +39,16 @@ describe("Protocol 0.4 Times", function() { ]) }) - it('a V4 block should not accept a time = medianTime + avgGenTime * 1.189', () => co(function*() { - yield s1.commit({ medianTime: now, time: Math.ceil(now + conf.avgGenTime * 1.189) }); - yield s1.revert(); - })); + it('a V4 block should not accept a time = medianTime + avgGenTime * 1.189', async () => { + await s1.commit({ medianTime: now, time: Math.ceil(now + conf.avgGenTime * 1.189) }); + await s1.revert(); + }) - it('a V4 block should not accept a time > medianTime + avgGenTime * 1.189', () => co(function*() { + it('a V4 block should not accept a time > medianTime + avgGenTime * 1.189', async () => { try { - yield s1.commitExpectError({ medianTime: now, time: Math.ceil(now + conf.avgGenTime * 1.189) + 1 }); + await s1.commitExpectError({ medianTime: now, time: Math.ceil(now + conf.avgGenTime * 1.189) + 1 }); } catch (e) { e.should.have.property('message').equal('A block must have its Time between MedianTime and MedianTime + 5945'); } - })); + }) }); diff --git a/test/integration/v0.5-identity-blockstamp.js b/test/integration/protocol/v0.5-identity-blockstamp.ts similarity index 58% rename from test/integration/v0.5-identity-blockstamp.js rename to test/integration/protocol/v0.5-identity-blockstamp.ts index 9016e87ab79e8fecffecd13a44716d083c6cb858..28d3adde892ab850fc69482edc4f88ff174908af 100644 --- a/test/integration/v0.5-identity-blockstamp.js +++ b/test/integration/protocol/v0.5-identity-blockstamp.ts @@ -11,13 +11,10 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {createUser, simpleNodeWith2otherUsers, simpleNodeWith2Users, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" -const co = require('co'); const should = require('should'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const constants = require('../../app/lib/constants'); -const toolbox = require('./tools/toolbox'); const conf = { avgGenTime: 5000, @@ -26,19 +23,19 @@ const conf = { const now = 1578540000; -let s1, s2, tuc; +let s1:TestingServer, s2:TestingServer, tuc:TestUser describe("Protocol 0.5 Identity blockstamp", function() { - before(() => co(function*() { + before(async () => { - const res1 = yield toolbox.simpleNodeWith2Users(conf); - const res2 = yield toolbox.simpleNodeWith2otherUsers(conf); + const res1 = await simpleNodeWith2Users(conf); + const res2 = await simpleNodeWith2otherUsers(conf); s1 = res1.s1; s2 = res2.s1; - tuc = yield toolbox.createUser('tuc', '3conGDUXdrTGbQPMQQhEC4Ubu1MCAnFrAYvUaewbUhtk', '5ks7qQ8Fpkin7ycXpxQSxxjVhs8VTzpM3vEBMqM7NfC1ZiFJ93uQryDcoM93Mj77T6hDAABdeHZJDFnkDb35bgiU', s1); - })); + tuc = await createUser('tuc', '3conGDUXdrTGbQPMQQhEC4Ubu1MCAnFrAYvUaewbUhtk', '5ks7qQ8Fpkin7ycXpxQSxxjVhs8VTzpM3vEBMqM7NfC1ZiFJ93uQryDcoM93Mj77T6hDAABdeHZJDFnkDb35bgiU', s1._server); + }) after(() => { return Promise.all([ @@ -47,23 +44,23 @@ describe("Protocol 0.5 Identity blockstamp", function() { ]) }) - it('should be able to create tuc on s1', () => co(function*() { - yield s1.commit({ time: now }); - yield s1.commit({ time: now }); - yield s2.commit({ time: now }); - yield s2.commit({ time: now }); - yield tuc.createIdentity(); - })); + it('should be able to create tuc on s1', async () => { + await s1.commit({ time: now }); + await s1.commit({ time: now }); + await s2.commit({ time: now }); + await s2.commit({ time: now }); + await tuc.createIdentity(); + }) - it('should not be able to create tuc on s2, using identity generated on s1', () => co(function*() { + it('should not be able to create tuc on s2, using identity generated on s1', async () => { try { - yield tuc.submitIdentity(tuc.getIdentityRaw(), s2); + await tuc.submitIdentity(tuc.getIdentityRaw(), s2); throw { message: 'Submitting wrong identity should have thrown an error' }; } catch (e) { if (!(typeof e == "string") || e.match(/Submitting wrong identity should have thrown an error/)) { throw e; } } - })); -}); + }) +}) diff --git a/test/integration/v0.5-transactions.js b/test/integration/protocol/v0.5-transactions.ts similarity index 65% rename from test/integration/v0.5-transactions.js rename to test/integration/protocol/v0.5-transactions.ts index c7e546b0db62d57a2b1bb707dc6d7aec4882d4cf..63863a77f3fd8892ffa1bede279647a2cef5ee94 100644 --- a/test/integration/v0.5-transactions.js +++ b/test/integration/protocol/v0.5-transactions.ts @@ -11,13 +11,11 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {simpleNodeWith2Users, TestingServer} from "../tools/toolbox" +import {BlockDTO} from "../../../app/lib/dto/BlockDTO" -const co = require('co'); const should = require('should'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const constants = require('../../app/lib/constants'); -const toolbox = require('./tools/toolbox'); +const constants = require('../../../app/lib/constants'); const conf = { dt: 30, @@ -27,21 +25,21 @@ const conf = { const now = 1578540000; -let s1; +let s1:TestingServer describe("Protocol 0.5 Transaction version", function() { - before(() => co(function*() { + before(async () => { - const res1 = yield toolbox.simpleNodeWith2Users(conf); + const res1 = await simpleNodeWith2Users(conf); s1 = res1.s1; const cat = res1.cat; const tac = res1.tac; - yield s1.commit({ time: now }); - yield s1.commit({ time: now + 100 }); - yield s1.commit({ time: now + 100 }); - yield cat.sendP(51, tac); - })); + await s1.commit({ time: now }); + await s1.commit({ time: now + 100 }); + await s1.commit({ time: now + 100 }); + await cat.sendMoney(51, tac); + }) after(() => { return Promise.all([ @@ -49,9 +47,9 @@ describe("Protocol 0.5 Transaction version", function() { ]) }) - it('should not have a block with v5 transaction, but v3', () => co(function*() { - const block = yield s1.commit({ time: now + 100 }); + it('should not have a block with v5 transaction, but v3', async () => { + const block = (await s1.commit({ time: now + 100 })) as BlockDTO should.exists(block.transactions[0]); block.transactions[0].version.should.equal(constants.TRANSACTION_VERSION); - })); -}); + }) +}) diff --git a/test/integration/v0.6-difficulties.js b/test/integration/protocol/v0.6-difficulties.ts similarity index 60% rename from test/integration/v0.6-difficulties.js rename to test/integration/protocol/v0.6-difficulties.ts index 983a1224685449c8a649caa7684e65eb930a90e2..7b834dfaeb140e321a2644b7d947642cc83ab0aa 100644 --- a/test/integration/v0.6-difficulties.js +++ b/test/integration/protocol/v0.6-difficulties.ts @@ -11,11 +11,9 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {serverWaitBlock, simpleNetworkOf2NodesAnd2Users, TestingServer} from "../tools/toolbox" -const co = require('co'); const should = require('should'); -const toolbox = require('./tools/toolbox'); const conf = { avgGenTime: 5000, @@ -24,20 +22,20 @@ const conf = { const now = 1480937906; -let s1, s2; +let s1:TestingServer, s2:TestingServer describe("Protocol 0.6 Difficulties", function() { - before(() => co(function*() { + before(async () => { - const res = yield toolbox.simpleNetworkOf2NodesAnd2Users(conf); + const res = await simpleNetworkOf2NodesAnd2Users(conf); s1 = res.s1; s2 = res.s2; - yield [ + await Promise.all([ s1.commit({ time: now }), s2.until('block', 1) - ]; - })); + ]) + }) after(() => { return Promise.all([ @@ -46,69 +44,69 @@ describe("Protocol 0.6 Difficulties", function() { ]) }) - it('should be able to emit a block#1 by a different user', () => co(function*() { - yield [ + it('should be able to emit a block#1 by a different user', async () => { + await Promise.all([ s1.commit({ time: now }), // medianOfBlocksInFrame = MEDIAN([1]) = 1 - toolbox.serverWaitBlock(s1, 1), - toolbox.serverWaitBlock(s2, 1) - ]; - yield [ + serverWaitBlock(s1._server, 1), + serverWaitBlock(s2._server, 1) + ]) + await Promise.all([ s2.commit({ time: now }), // medianOfBlocksInFrame = MEDIAN([1]) = 1 - toolbox.serverWaitBlock(s1, 2), - toolbox.serverWaitBlock(s2, 2) - ]; - yield s1.expectJSON('/blockchain/current', { + serverWaitBlock(s1._server, 2), + serverWaitBlock(s2._server, 2) + ]) + await s1.expectJSON('/blockchain/current', { number: 2, issuer: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc' }); - yield s1.expectJSON('/blockchain/block/0', { + await s1.expectJSON('/blockchain/block/0', { number: 0, issuer: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd' }); - yield s1.expectJSON('/blockchain/hardship/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', { level: 4 }); // medianOfBlocksInFrame = MEDIAN([1, 1]) = 1, personal_excess = 100%, level = 4 - yield s1.expectJSON('/blockchain/hardship/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', { level: 4 }); // medianOfBlocksInFrame = MEDIAN([1, 1]) = 1, personal_excess = 100%, level = 4 - yield s1.commit({ time: now }); - yield s1.expectJSON('/blockchain/hardship/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', { level: 4 }); // medianOfBlocksInFrame = MEDIAN([1, 2]) = 1.5, personal_excess = 3/1.5 = 100%, level = 4 - yield s1.expectJSON('/blockchain/hardship/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', { level: 1 }); // medianOfBlocksInFrame = MEDIAN([1, 2]) = 1.5, personal_excess = 2/1.5 = 33%, level = 1 - yield s1.commit({ time: now }); - yield s1.expectJSON('/blockchain/hardship/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', { level: 4 }); // medianOfBlocksInFrame = MEDIAN([1, 3]) = 2, personal_excess = 4/2 = 100%, level = 4 - yield s1.expectJSON('/blockchain/hardship/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', { level: 0 }); // medianOfBlocksInFrame = MEDIAN([1, 3]) = 2, personal_excess = 2/2 = 0%, level = 0 - yield s1.commit({ time: now }); - yield s1.expectJSON('/blockchain/hardship/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', { level: 4 }); // medianOfBlocksInFrame = MEDIAN([1, 4]) = 2.5, personal_excess = 5/2.5 = 100%, level = 4 - yield s1.expectJSON('/blockchain/hardship/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', { level: 0 }); // medianOfBlocksInFrame = MEDIAN([1, 4]) = 2.5, personal_excess = 2/2.5 = 0%, level = 0 - yield s1.commit({ time: now }); - yield s1.expectJSON('/blockchain/hardship/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', { level: 4 }); // ... [1, 5] ... = 4 - yield s1.expectJSON('/blockchain/hardship/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', { level: 0 }); // ... [1, 5] ... = 0 - yield s1.commit({ time: now }); - yield s1.expectJSON('/blockchain/hardship/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', { level: 4 }); // ... [1, 6] ... = 4 - yield s1.expectJSON('/blockchain/hardship/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', { level: 0 }); // ... [1, 6] ... = 0 - yield s1.commit({ time: now }); - yield s1.expectJSON('/blockchain/hardship/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', { level: 4 }); // ... [1, 7] ... = 4 - yield s1.expectJSON('/blockchain/hardship/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', { level: 0 }); // ... [1, 7] ... = 0 - yield s1.commit({ time: now }); + await s1.expectJSON('/blockchain/hardship/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', { level: 4 }); // medianOfBlocksInFrame = MEDIAN([1, 1]) = 1, personal_excess = 100%, level = 4 + await s1.expectJSON('/blockchain/hardship/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', { level: 4 }); // medianOfBlocksInFrame = MEDIAN([1, 1]) = 1, personal_excess = 100%, level = 4 + await s1.commit({ time: now }); + await s1.expectJSON('/blockchain/hardship/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', { level: 4 }); // medianOfBlocksInFrame = MEDIAN([1, 2]) = 1.5, personal_excess = 3/1.5 = 100%, level = 4 + await s1.expectJSON('/blockchain/hardship/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', { level: 1 }); // medianOfBlocksInFrame = MEDIAN([1, 2]) = 1.5, personal_excess = 2/1.5 = 33%, level = 1 + await s1.commit({ time: now }); + await s1.expectJSON('/blockchain/hardship/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', { level: 4 }); // medianOfBlocksInFrame = MEDIAN([1, 3]) = 2, personal_excess = 4/2 = 100%, level = 4 + await s1.expectJSON('/blockchain/hardship/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', { level: 0 }); // medianOfBlocksInFrame = MEDIAN([1, 3]) = 2, personal_excess = 2/2 = 0%, level = 0 + await s1.commit({ time: now }); + await s1.expectJSON('/blockchain/hardship/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', { level: 4 }); // medianOfBlocksInFrame = MEDIAN([1, 4]) = 2.5, personal_excess = 5/2.5 = 100%, level = 4 + await s1.expectJSON('/blockchain/hardship/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', { level: 0 }); // medianOfBlocksInFrame = MEDIAN([1, 4]) = 2.5, personal_excess = 2/2.5 = 0%, level = 0 + await s1.commit({ time: now }); + await s1.expectJSON('/blockchain/hardship/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', { level: 4 }); // ... [1, 5] ... = 4 + await s1.expectJSON('/blockchain/hardship/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', { level: 0 }); // ... [1, 5] ... = 0 + await s1.commit({ time: now }); + await s1.expectJSON('/blockchain/hardship/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', { level: 4 }); // ... [1, 6] ... = 4 + await s1.expectJSON('/blockchain/hardship/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', { level: 0 }); // ... [1, 6] ... = 0 + await s1.commit({ time: now }); + await s1.expectJSON('/blockchain/hardship/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', { level: 4 }); // ... [1, 7] ... = 4 + await s1.expectJSON('/blockchain/hardship/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', { level: 0 }); // ... [1, 7] ... = 0 + await s1.commit({ time: now }); /********************* * PowMin incremented ********************/ - yield s1.expectJSON('/blockchain/hardship/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', { level: 5 }); // medianOfBlocksInFrame = MEDIAN([1, 8]) = 4.5, personal_excess = 9/4.5 = 100%, level = 1 + 4 - yield s1.expectJSON('/blockchain/hardship/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', { level: 1 }); // medianOfBlocksInFrame = MEDIAN([1, 8]) = 4.5, personal_excess = 1/4.5 = 0%, level = 1 + 0 - yield s1.commit({ time: now }); - yield s1.expectJSON('/blockchain/hardship/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', { level: 5 }); // ... [1, 9] ... = 5 - yield s1.expectJSON('/blockchain/hardship/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', { level: 1 }); // ... [1, 9] ... = 1 - yield s1.commit({ time: now }); - yield s1.expectJSON('/blockchain/hardship/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', { level: 5 }); // ... [1, 10] ... = 5 - yield s1.expectJSON('/blockchain/hardship/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', { level: 1 }); // ... [1, 10] ... = 1 - yield s1.commit({ time: now }); - yield s1.expectJSON('/blockchain/hardship/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', { level: 5 }); // ... [1, 11] ... = 5 - yield s1.expectJSON('/blockchain/hardship/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', { level: 1 }); // ... [1, 11] ... = 1 - yield s1.commit({ time: now }); + await s1.expectJSON('/blockchain/hardship/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', { level: 5 }); // medianOfBlocksInFrame = MEDIAN([1, 8]) = 4.5, personal_excess = 9/4.5 = 100%, level = 1 + 4 + await s1.expectJSON('/blockchain/hardship/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', { level: 1 }); // medianOfBlocksInFrame = MEDIAN([1, 8]) = 4.5, personal_excess = 1/4.5 = 0%, level = 1 + 0 + await s1.commit({ time: now }); + await s1.expectJSON('/blockchain/hardship/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', { level: 5 }); // ... [1, 9] ... = 5 + await s1.expectJSON('/blockchain/hardship/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', { level: 1 }); // ... [1, 9] ... = 1 + await s1.commit({ time: now }); + await s1.expectJSON('/blockchain/hardship/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', { level: 5 }); // ... [1, 10] ... = 5 + await s1.expectJSON('/blockchain/hardship/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', { level: 1 }); // ... [1, 10] ... = 1 + await s1.commit({ time: now }); + await s1.expectJSON('/blockchain/hardship/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', { level: 5 }); // ... [1, 11] ... = 5 + await s1.expectJSON('/blockchain/hardship/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', { level: 1 }); // ... [1, 11] ... = 1 + await s1.commit({ time: now }); /********************* * Frame excluded `2LvDg21` ********************/ - yield s1.expectJSON('/blockchain/hardship/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', { level: 1 }); // medianOfBlocksInFrame = MEDIAN([11]) = 11, personal_excess = 12/11 = 9%, level = 1 - yield s1.expectJSON('/blockchain/hardship/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', { level: 1 }); // medianOfBlocksInFrame = MEDIAN([11]) = 11, personal_excess = 0/11 = 0%, level = 1 - })); -}); + await s1.expectJSON('/blockchain/hardship/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', { level: 1 }); // medianOfBlocksInFrame = MEDIAN([11]) = 11, personal_excess = 12/11 = 9%, level = 1 + await s1.expectJSON('/blockchain/hardship/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', { level: 1 }); // medianOfBlocksInFrame = MEDIAN([11]) = 11, personal_excess = 0/11 = 0%, level = 1 + }) +}) diff --git a/test/integration/v1.0-double-dividend.js b/test/integration/protocol/v1.0-double-dividend.ts similarity index 63% rename from test/integration/v1.0-double-dividend.js rename to test/integration/protocol/v1.0-double-dividend.ts index da14d906630411f931fa9b2b8d1d792f174e1475..20c830c86205bbfa1744ddba2490c1384540c88b 100644 --- a/test/integration/v1.0-double-dividend.js +++ b/test/integration/protocol/v1.0-double-dividend.ts @@ -11,13 +11,11 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {simpleNodeWith2Users, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {HttpBlock} from "../../../app/modules/bma/lib/dtos" -const co = require('co'); const should = require('should'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const constants = require('../../app/lib/constants'); -const toolbox = require('./tools/toolbox'); const now = 1480000000; @@ -31,7 +29,7 @@ const conf = { medianTimeBlocks: 1 // The medianTime always equals previous block's medianTime }; -let s1, cat, tac; +let s1:TestingServer, cat:TestUser, tac:TestUser describe("Protocol 1.0 Dividend Update", function() { @@ -45,23 +43,23 @@ describe("Protocol 1.0 Dividend Update", function() { * * effective dividend: this is the real dividend, which is a share of the theoretical one */ - before(() => co(function*() { + before(async () => { - const res1 = yield toolbox.simpleNodeWith2Users(conf); + const res1 = await simpleNodeWith2Users(conf); s1 = res1.s1; cat = res1.cat; // HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd tac = res1.tac; // 2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc - yield s1.commit({ time: now }); - yield s1.commit({ time: now + 3 }); - yield s1.commit({ time: now + 4 }); - yield s1.commit({ time: now + 5 }); - yield s1.commit({ time: now + 6 }); - yield s1.commit({ time: now + 8 }); - yield s1.commit({ time: now + 10 }); - yield s1.commit({ time: now + 12 }); - yield s1.commit({ time: now + 14 }); - yield s1.commit({ time: now + 16 }); - })); + await s1.commit({ time: now }); + await s1.commit({ time: now + 3 }); + await s1.commit({ time: now + 4 }); + await s1.commit({ time: now + 5 }); + await s1.commit({ time: now + 6 }); + await s1.commit({ time: now + 8 }); + await s1.commit({ time: now + 10 }); + await s1.commit({ time: now + 12 }); + await s1.commit({ time: now + 14 }); + await s1.commit({ time: now + 16 }); + }) after(() => { return Promise.all([ @@ -69,35 +67,35 @@ describe("Protocol 1.0 Dividend Update", function() { ]) }) - it('should have block#2 with no UD', () => s1.expectThat('/blockchain/block/2', (json) => { + it('should have block#2 with no UD', () => s1.expectThat('/blockchain/block/2', (json:HttpBlock) => { should.not.exist(json.dividend); })); - it('should have block#3 with UD 1000', () => s1.expectThat('/blockchain/block/3', (json) => { - json.dividend.should.equal(1000); + it('should have block#3 with UD 1000', () => s1.expectThat('/blockchain/block/3', (json:HttpBlock) => { + (json.dividend as any).should.equal(1000); })); - it('should have block#4 with no UD', () => s1.expectThat('/blockchain/block/4', (json) => { + it('should have block#4 with no UD', () => s1.expectThat('/blockchain/block/4', (json:HttpBlock) => { should.not.exist(json.dividend); })); - it('should have block#5 with UD 1000', () => s1.expectThat('/blockchain/block/5', (json) => { - json.dividend.should.equal(1000); + it('should have block#5 with UD 1000', () => s1.expectThat('/blockchain/block/5', (json:HttpBlock) => { + (json.dividend as any).should.equal(1000); })); - it('should have block#6 with UD 1000', () => s1.expectThat('/blockchain/block/6', (json) => { - json.dividend.should.equal(1000); + it('should have block#6 with UD 1000', () => s1.expectThat('/blockchain/block/6', (json:HttpBlock) => { + (json.dividend as any).should.equal(1000); })); - it('should have block#7 with UD 1000', () => s1.expectThat('/blockchain/block/7', (json) => { - json.dividend.should.equal(1000); + it('should have block#7 with UD 1000', () => s1.expectThat('/blockchain/block/7', (json:HttpBlock) => { + (json.dividend as any).should.equal(1000); })); - it('should have block#8 with UD 1000', () => s1.expectThat('/blockchain/block/8', (json) => { - json.dividend.should.equal(1000); + it('should have block#8 with UD 1000', () => s1.expectThat('/blockchain/block/8', (json:HttpBlock) => { + (json.dividend as any).should.equal(1000); })); - it('should have block#9 with UD 1000', () => s1.expectThat('/blockchain/block/9', (json) => { - json.dividend.should.equal(1000); + it('should have block#9 with UD 1000', () => s1.expectThat('/blockchain/block/9', (json:HttpBlock) => { + (json.dividend as any).should.equal(1000); })); }); diff --git a/test/integration/v1.0-g1-dividend-long-run.js b/test/integration/protocol/v1.0-g1-dividend-long-run.ts similarity index 74% rename from test/integration/v1.0-g1-dividend-long-run.js rename to test/integration/protocol/v1.0-g1-dividend-long-run.ts index 512ae506dbbb083319c4222fbddd1ebdf477532c..752f656b1216714ac6130e800567ea160a13d3e7 100644 --- a/test/integration/v1.0-g1-dividend-long-run.js +++ b/test/integration/protocol/v1.0-g1-dividend-long-run.ts @@ -11,13 +11,11 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {simpleNodeWith2Users, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {HttpBlock} from "../../../app/modules/bma/lib/dtos" -const co = require('co'); const should = require('should'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const constants = require('../../app/lib/constants'); -const toolbox = require('./tools/toolbox'); const start = 1488985390; // 2016-03-08 16:03:10 UTC+0 const delayToUD = 1489057200 - start; // Delay to 2016-03-09 12:00:00 UTC+0 @@ -39,7 +37,7 @@ const conf = { avgGenTime: 3600 * 24 // 1 bloc a day }; -let s1, cat, tac; +let s1:TestingServer, cat:TestUser, tac:TestUser describe("Protocol 1.0 Ğ1 Dividend - long run", function() { @@ -50,19 +48,19 @@ describe("Protocol 1.0 Ğ1 Dividend - long run", function() { * Simulates the real dividends that would occur in the currency (simulating N) */ - before(() => co(function*() { + before(async () => { - const res1 = yield toolbox.simpleNodeWith2Users(conf); + const res1 = await simpleNodeWith2Users(conf); s1 = res1.s1; cat = res1.cat; // HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd tac = res1.tac; // 2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc - yield s1.commit({ time: start }); - yield s1.commit({ time: start + 1 }); - yield s1.commit({ time: start + delayToUD }); + await s1.commit({ time: start }); + await s1.commit({ time: start + 1 }); + await s1.commit({ time: start + delayToUD }); for (let i = 1; i < 20; i++) { - yield s1.commit({ time: (start + delayToUD) + aDay * i }); + await s1.commit({ time: (start + delayToUD) + aDay * i }); } - })); + }) after(() => { return Promise.all([ @@ -70,38 +68,38 @@ describe("Protocol 1.0 Ğ1 Dividend - long run", function() { ]) }) - it('should have block#0 has no UD', () => s1.expectThat('/blockchain/block/0', (json) => { + it('should have block#0 has no UD', () => s1.expectThat('/blockchain/block/0', (json:HttpBlock) => { should.not.exist(json.dividend); json.should.have.property('medianTime').equal(start); // 2016-03-08 16:03:10 UTC+0 })); - it('should have block#1 has no UD', () => s1.expectThat('/blockchain/block/1', (json) => { - json.dividend.should.equal(1000); + it('should have block#1 has no UD', () => s1.expectThat('/blockchain/block/1', (json:HttpBlock) => { + (json.dividend as any).should.equal(1000); json.should.have.property('medianTime').equal(start); // 2016-03-08 16:03:10 UTC+0 })); - it('should have block#2 with UD 1000', () => s1.expectThat('/blockchain/block/2', (json) => { + it('should have block#2 with UD 1000', () => s1.expectThat('/blockchain/block/2', (json:HttpBlock) => { should.not.exist(json.dividend); json.should.have.property('medianTime').equal(start + 1); // 2016-03-08 16:03:11 UTC+0 })); - it('should have block#3 with UD 1000', () => s1.expectThat('/blockchain/block/3', (json) => { - json.dividend.should.equal(1000); + it('should have block#3 with UD 1000', () => s1.expectThat('/blockchain/block/3', (json:HttpBlock) => { + (json.dividend as any).should.equal(1000); json.should.have.property('medianTime').equal(start + delayToUD); // 2016-03-09 12:00:00 UTC+0 })); - it('should have block#4 with UD 1000', () => s1.expectThat('/blockchain/block/4', (json) => { - json.dividend.should.equal(1000); + it('should have block#4 with UD 1000', () => s1.expectThat('/blockchain/block/4', (json:HttpBlock) => { + (json.dividend as any).should.equal(1000); json.should.have.property('medianTime').equal((start + delayToUD) + aDay); // 2016-03-10 12:00:00 UTC+0 })); - it('should have block#14 with UD 1000, even if dtReeval has been reached', () => s1.expectThat('/blockchain/block/15', (json) => { - json.dividend.should.equal(1000); + it('should have block#14 with UD 1000, even if dtReeval has been reached', () => s1.expectThat('/blockchain/block/15', (json:HttpBlock) => { + (json.dividend as any).should.equal(1000); json.should.have.property('medianTime').equal((start + delayToUD) + aDay * 12); // 2016-03-21 12:00:00 UTC+0 })); - it('should have block#14 with UD 1000, even if dtReeval has been reached', () => s1.expectThat('/blockchain/block/16', (json) => { - json.dividend.should.equal(1000); + it('should have block#14 with UD 1000, even if dtReeval has been reached', () => s1.expectThat('/blockchain/block/16', (json:HttpBlock) => { + (json.dividend as any).should.equal(1000); json.should.have.property('medianTime').equal((start + delayToUD) + aDay * 13); // 2016-03-22 12:00:00 UTC+0 })); }); diff --git a/test/integration/v1.0-g1-dividend.js b/test/integration/protocol/v1.0-g1-dividend.ts similarity index 71% rename from test/integration/v1.0-g1-dividend.js rename to test/integration/protocol/v1.0-g1-dividend.ts index 55991a1c71edc41e9c05ccd26194a4dd605cecfd..3a15bfb8e41dfb668fd96a4d7e579fcb754b2bec 100644 --- a/test/integration/v1.0-g1-dividend.js +++ b/test/integration/protocol/v1.0-g1-dividend.ts @@ -11,13 +11,11 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {simpleNodeWith2Users, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {HttpBlock} from "../../../app/modules/bma/lib/dtos" -const co = require('co'); const should = require('should'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const constants = require('../../app/lib/constants'); -const toolbox = require('./tools/toolbox'); const start = 1488985390; // 2016-03-08 16:03:10 UTC+0 const delayToUD = 1489057200 - start; // Delay to 2016-03-09 12:00:00 UTC+0 @@ -36,7 +34,7 @@ const conf = { avgGenTime: 3600 * 24 // 1 bloc a day }; -let s1, cat, tac; +let s1:TestingServer, cat:TestUser, tac:TestUser describe("Protocol 1.0 Ğ1 Dividend", function() { @@ -47,19 +45,19 @@ describe("Protocol 1.0 Ğ1 Dividend", function() { * Simulates the real dividends that would occur in the currency (simulating N) */ - before(() => co(function*() { + before(async () => { - const res1 = yield toolbox.simpleNodeWith2Users(conf); + const res1 = await simpleNodeWith2Users(conf); s1 = res1.s1; cat = res1.cat; // HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd tac = res1.tac; // 2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc - yield s1.commit({ time: start }); - yield s1.commit({ time: start + 1 }); - yield s1.commit({ time: start + delayToUD }); + await s1.commit({ time: start }); + await s1.commit({ time: start + 1 }); + await s1.commit({ time: start + delayToUD }); for (let i = 1; i < 15; i++) { - yield s1.commit({ time: (start + delayToUD) + aDay * i }); + await s1.commit({ time: (start + delayToUD) + aDay * i }); } - })); + }) after(() => { return Promise.all([ @@ -67,70 +65,70 @@ describe("Protocol 1.0 Ğ1 Dividend", function() { ]) }) - it('should have block#0 has no UD', () => s1.expectThat('/blockchain/block/0', (json) => { + it('should have block#0 has no UD', () => s1.expectThat('/blockchain/block/0', (json:HttpBlock) => { should.not.exist(json.dividend); json.should.have.property('medianTime').equal(start); // 2016-03-08 16:03:10 UTC+0 })); - it('should have block#1 has no UD', () => s1.expectThat('/blockchain/block/1', (json) => { - json.dividend.should.equal(1000); + it('should have block#1 has no UD', () => s1.expectThat('/blockchain/block/1', (json:HttpBlock) => { + (json.dividend as any).should.equal(1000); json.should.have.property('medianTime').equal(start); // 2016-03-08 16:03:10 UTC+0 })); - it('should have block#2 with UD 1000', () => s1.expectThat('/blockchain/block/2', (json) => { + it('should have block#2 with UD 1000', () => s1.expectThat('/blockchain/block/2', (json:HttpBlock) => { should.not.exist(json.dividend); json.should.have.property('medianTime').equal(start + 1); // 2016-03-08 16:03:11 UTC+0 })); - it('should have block#3 with UD 1000', () => s1.expectThat('/blockchain/block/3', (json) => { - json.dividend.should.equal(1000); + it('should have block#3 with UD 1000', () => s1.expectThat('/blockchain/block/3', (json:HttpBlock) => { + (json.dividend as any).should.equal(1000); json.should.have.property('medianTime').equal(start + delayToUD); // 2016-03-09 12:00:00 UTC+0 })); - it('should have block#4 with UD 1000', () => s1.expectThat('/blockchain/block/4', (json) => { - json.dividend.should.equal(1000); + it('should have block#4 with UD 1000', () => s1.expectThat('/blockchain/block/4', (json:HttpBlock) => { + (json.dividend as any).should.equal(1000); json.should.have.property('medianTime').equal((start + delayToUD) + aDay); // 2016-03-10 12:00:00 UTC+0 })); - it('should have block#4 with UD 1000', () => s1.expectThat('/blockchain/block/5', (json) => { - json.dividend.should.equal(1000); + it('should have block#4 with UD 1000', () => s1.expectThat('/blockchain/block/5', (json:HttpBlock) => { + (json.dividend as any).should.equal(1000); json.should.have.property('medianTime').equal((start + delayToUD) + aDay * 2); // 2016-03-11 12:00:00 UTC+0 })); - it('should have block#4 with UD 1000', () => s1.expectThat('/blockchain/block/6', (json) => { - json.dividend.should.equal(1000); + it('should have block#4 with UD 1000', () => s1.expectThat('/blockchain/block/6', (json:HttpBlock) => { + (json.dividend as any).should.equal(1000); json.should.have.property('medianTime').equal((start + delayToUD) + aDay * 3); // 2016-03-12 12:00:00 UTC+0 })); // ... skip some blocks ... - it('should have block#11 with UD 1000', () => s1.expectThat('/blockchain/block/11', (json) => { - json.dividend.should.equal(1000); + it('should have block#11 with UD 1000', () => s1.expectThat('/blockchain/block/11', (json:HttpBlock) => { + (json.dividend as any).should.equal(1000); json.should.have.property('medianTime').equal((start + delayToUD) + aDay * 8); // 2016-03-17 12:00:00 UTC+0 })); - it('should have block#12 with UD 1000', () => s1.expectThat('/blockchain/block/12', (json) => { - json.dividend.should.equal(1000); + it('should have block#12 with UD 1000', () => s1.expectThat('/blockchain/block/12', (json:HttpBlock) => { + (json.dividend as any).should.equal(1000); json.should.have.property('medianTime').equal((start + delayToUD) + aDay * 9); // 2016-03-18 12:00:00 UTC+0 })); - it('should have block#13 with UD 1000', () => s1.expectThat('/blockchain/block/13', (json) => { - json.dividend.should.equal(1000); + it('should have block#13 with UD 1000', () => s1.expectThat('/blockchain/block/13', (json:HttpBlock) => { + (json.dividend as any).should.equal(1000); json.should.have.property('medianTime').equal((start + delayToUD) + aDay * 10); // 2016-03-19 12:00:00 UTC+0 })); - it('should have block#14 with UD 1000', () => s1.expectThat('/blockchain/block/14', (json) => { - json.dividend.should.equal(1000); + it('should have block#14 with UD 1000', () => s1.expectThat('/blockchain/block/14', (json:HttpBlock) => { + (json.dividend as any).should.equal(1000); json.should.have.property('medianTime').equal((start + delayToUD) + aDay * 11); // 2016-03-20 12:00:00 UTC+0 })); - it('should have block#14 with UD 1000, even if dtReeval has been reached', () => s1.expectThat('/blockchain/block/15', (json) => { - json.dividend.should.equal(1000); + it('should have block#14 with UD 1000, even if dtReeval has been reached', () => s1.expectThat('/blockchain/block/15', (json:HttpBlock) => { + (json.dividend as any).should.equal(1000); json.should.have.property('medianTime').equal((start + delayToUD) + aDay * 12); // 2016-03-21 12:00:00 UTC+0 })); - it('should have block#14 with UD 1000, even if dtReeval has been reached', () => s1.expectThat('/blockchain/block/16', (json) => { - json.dividend.should.equal(1000); + it('should have block#14 with UD 1000, even if dtReeval has been reached', () => s1.expectThat('/blockchain/block/16', (json:HttpBlock) => { + (json.dividend as any).should.equal(1000); json.should.have.property('medianTime').equal((start + delayToUD) + aDay * 13); // 2016-03-22 12:00:00 UTC+0 })); }); diff --git a/test/integration/v1.0-modules-api.js b/test/integration/protocol/v1.0-modules-api.ts similarity index 70% rename from test/integration/v1.0-modules-api.js rename to test/integration/protocol/v1.0-modules-api.ts index 9c6b3b76bbde6431aef9c29f4bb3129d680273b8..28c2798a45768c4d91af68d462c4650ad996fe5a 100644 --- a/test/integration/v1.0-modules-api.js +++ b/test/integration/protocol/v1.0-modules-api.ts @@ -11,29 +11,30 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import * as stream from "stream" +import {parsers} from "../../../app/lib/common-libs/parsers/index" +import {Server} from "../../../server" +import {ConfDTO} from "../../../app/lib/dto/ConfDTO" +import {KeypairDependency} from "../../../app/modules/keypair/index" +import {BmaDependency} from "../../../app/modules/bma/index" -const co = require('co'); -const _ = require('underscore'); const should = require('should'); const util = require('util'); const path = require('path'); -const stream = require('stream'); -const duniter = require('../../index'); -const parsers = require('../../app/lib/common-libs/parsers').parsers +const duniter = require('../../../index'); const querablep = require('querablep'); describe("v1.0 Module API", () => { - it('should be able to execute `hello` command with quickRun', () => co(function*() { + it('should be able to execute `hello` command with quickRun', async () => { duniter.statics.setOnRunDone(() => { /* Do not exit the process */ }) - const absolutePath = path.join(__dirname, './scenarios/hello-plugin.js') - process.argv = ['', absolutePath, 'hello-world'] - const res = yield duniter.statics.quickRun(absolutePath) + const absolutePath = path.join(__dirname, '../scenarios/hello-plugin.js') + process.argv = ['', absolutePath, 'hello-world', '--memory'] + const res = await duniter.statics.quickRun(absolutePath) res.should.equal('Hello world! from within Duniter.') - })) + }) - it('should be able to execute `hello` command', () => co(function*() { + it('should be able to execute `hello` command', async () => { const sStack = duniter.statics.simpleStack(); const aStack = duniter.statics.autoStack(); @@ -47,21 +48,21 @@ describe("v1.0 Module API", () => { cli: [{ name: 'hello', desc: 'Returns an "Hello, world" string after configuration phase.', - onConfiguredExecute: (server, conf, program, params) => co(function*(){ + onConfiguredExecute: async (server:Server, conf:ConfDTO, program:any, params:any) => { return "Hello, " + params[0] + ". You successfully sent arg '" + params[1] + "' along with opt1 = " + program.opt1 + " and option2 = " + program.option2 + "."; - }) + } }] } }; sStack.registerDependency(helloDependency, 'duniter-hello'); sStack.registerDependency(helloDependency, 'duniter-hello'); // Try to load it 2 times, should not throw an error - sStack.registerDependency(require('../../app/modules/keypair').KeypairDependency, 'duniter-keypair'); + sStack.registerDependency(KeypairDependency, 'duniter-keypair'); aStack.registerDependency(helloDependency, 'duniter-hello'); - (yield sStack.executeStack(['node', 'index.js', '--memory', 'hello', 'World', 'TEST', '--opt1', '--option2', '5'])).should.equal('Hello, World. You successfully sent arg \'TEST\' along with opt1 = true and option2 = 5.'); - (yield aStack.executeStack(['node', 'index.js', '--memory', 'hello', 'Zorld', 'ESSE', '--option2', 'd'])).should.equal('Hello, Zorld. You successfully sent arg \'ESSE\' along with opt1 = undefined and option2 = NaN.'); - })); + (await sStack.executeStack(['node', 'index.js', '--memory', 'hello', 'World', 'TEST', '--opt1', '--option2', '5'])).should.equal('Hello, World. You successfully sent arg \'TEST\' along with opt1 = true and option2 = 5.'); + (await aStack.executeStack(['node', 'index.js', '--memory', 'hello', 'Zorld', 'ESSE', '--option2', 'd'])).should.equal('Hello, Zorld. You successfully sent arg \'ESSE\' along with opt1 = undefined and option2 = NaN.'); + }) /*********************** * CONFIGURATION HOOKS @@ -69,13 +70,12 @@ describe("v1.0 Module API", () => { describe("Configuration hooks", () => { - let stack; - function run() { - const args = Array.from(arguments); + let stack:any + function run(...args:string[]) { return stack.executeStack(['node', 'index.js', '--mdb', 'modules_api_tests'].concat(args)); } - before(() => co(function*() { + before(async () => { stack = duniter.statics.simpleStack(); const configurationDependency = { @@ -85,7 +85,7 @@ describe("v1.0 Module API", () => { { value: '--superpasswd <passwd>', desc: 'A crypto password.' } ], config: { - onLoading: (conf, program) => co(function*(){ + onLoading: async (conf:any, program:any) => { // Always adds a parameter named "superkey" conf.superkey = { pub: 'publicPart', sec: 'secretPart' }; @@ -97,12 +97,11 @@ describe("v1.0 Module API", () => { if (program.superpasswd) { conf.superpasswd = program.superpasswd; } - }), - beforeSave: (conf, program) => co(function*(){ - + }, + beforeSave: async (conf:any) => { // We never want to store "superpasswd" delete conf.superpasswd; - }) + } } } }; @@ -111,52 +110,52 @@ describe("v1.0 Module API", () => { cli: [{ name: 'gimme-conf', desc: 'Returns the configuration object.', - onDatabaseExecute: (server, conf, program, params, startServices, stopServices) => co(function*() { + onConfiguredExecute: async (server:Server, conf:any) => { // Gimme the conf! return conf; - }) + } }] } }; - stack.registerDependency(require('../../app/modules/keypair').KeypairDependency, 'duniter-keypair'); + stack.registerDependency(KeypairDependency, 'duniter-keypair'); stack.registerDependency(configurationDependency, 'duniter-configuration'); stack.registerDependency(returnConfDependency, 'duniter-gimme-conf'); - })); + }) - it('verify that we get the CLI options', () => co(function*() { - const conf = yield run('gimme-conf', '--supersalt', 'NaCl'); + it('verify that we get the CLI options', async () => { + const conf = await run('gimme-conf', '--supersalt', 'NaCl'); conf.should.have.property('supersalt').equal('NaCl'); - })); + }) - it('verify that we get the saved options', () => co(function*() { + it('verify that we get the saved options', async () => { let conf; // We make an initial reset - yield run('reset', 'config'); - conf = yield run('gimme-conf'); + await run('reset', 'config'); + conf = await run('gimme-conf'); conf.should.have.property('superkey'); // Always loaded conf.should.not.have.property('supersalt'); // Nothing should have changed - conf = yield run('gimme-conf'); + conf = await run('gimme-conf'); conf.should.have.property('superkey'); // Always loaded conf.should.not.have.property('supersalt'); // Now we try to save the parameters - yield run('config', '--supersalt', 'NaCl2', '--superpasswd', 'megapasswd'); - conf = yield run('gimme-conf'); + await run('config', '--supersalt', 'NaCl2', '--superpasswd', 'megapasswd'); + conf = await run('gimme-conf'); conf.should.have.property('superkey'); // Always loaded conf.should.have.property('supersalt').equal('NaCl2'); conf.should.not.have.property('superpasswd'); // Yet we can have all options by giving them explicitely using options - conf = yield run('gimme-conf', '--superpasswd', 'megapasswd2'); + conf = await run('gimme-conf', '--superpasswd', 'megapasswd2'); conf.should.have.property('superkey'); conf.should.have.property('supersalt').equal('NaCl2'); conf.should.have.property('superpasswd').equal('megapasswd2'); - })); - }); + }) + }) /*********************** * SERVICE START/STOP @@ -164,32 +163,31 @@ describe("v1.0 Module API", () => { describe("Service triggers", () => { - let stack; - let fakeI; - let fakeP; - let fakeO; + let stack:any + let fakeI:FakeStream + let fakeP:FakeStream + let fakeO:FakeStream - function run() { - const args = Array.from(arguments); + function run(...args:string[]) { return stack.executeStack(['node', 'index.js', '--memory', '--ws2p-noupnp'].concat(args)); } - before(() => co(function*() { + before(async () => { stack = duniter.statics.simpleStack(); - fakeI = new FakeStream((that, data) => { + fakeI = new FakeStream((that:any, data:any) => { // Note: we never pass here if (typeof data == "string") { that.push(data); } }); - fakeP = new FakeStream((that, data) => { + fakeP = new FakeStream((that:any, data:any) => { if (typeof data == "object" && data.type == "transaction") { const tx = parsers.parseTransaction.syncWrite(data.doc); that.push(tx); } }); - fakeO = new FakeStream((that, data, enc, done) => { + fakeO = new FakeStream((that:any, data:any, enc:any, done:any) => { if (data.issuers) { that.resolveData(); } @@ -202,8 +200,8 @@ describe("v1.0 Module API", () => { cli: [{ name: 'hello-service', desc: 'Says hello to the world, at service phase. And feed INPUT with a transaction.', - onDatabaseExecute: (duniterServer, conf, program, programArgs, startServices, stopServices) => co(function*(){ - yield startServices(); + onDatabaseExecute: async (duniterServer:Server, conf:any, program:any, programArgs:any, startServices:any) => { + await startServices(); fakeI.push("Version: 10\n" + "Type: Transaction\n" + "Currency: test_net\n" + @@ -220,9 +218,9 @@ describe("v1.0 Module API", () => { "99000:0:SIG(HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk)\n" + "Comment: reessai\n" + "P6MxJ/2SdkvNDyIyWuOkTz3MUwsgsfo70j+rpWeQWcm6GdvKQsbplB8482Ar1HMz2q0h5V3tfMqjCuAeWVQ+Ag==\n"); - yield fakeO.outputed; + await fakeO.outputed; return fakeO.outputed; - }) + } }], service: { input: () => fakeI, @@ -236,10 +234,10 @@ describe("v1.0 Module API", () => { cli: [{ name: 'bye-service', desc: 'Says goodbye to the world, at service phase.', - onDatabaseExecute: (duniterServer, conf, program, programArgs, startServices, stopServices) => co(function*(){ - yield stopServices(); + onDatabaseExecute: async (duniterServer:any, conf:any, program:any, programArgs:any, startServices:any, stopServices:any) => { + await stopServices(); return Promise.resolve(); - }) + } }], service: { input: () => fakeI, @@ -249,20 +247,20 @@ describe("v1.0 Module API", () => { } }; - stack.registerDependency(require('../../app/modules/keypair').KeypairDependency, 'duniter-keypair'); - stack.registerDependency(require('../../app/modules/bma').BmaDependency, 'duniter-bma'); + stack.registerDependency(KeypairDependency, 'duniter-keypair'); + stack.registerDependency(BmaDependency, 'duniter-bma'); stack.registerDependency(dummyStartServiceDependency, 'duniter-dummy-start'); stack.registerDependency(dummyStopServiceDependency, 'duniter-dummy-stop'); - })); + }) - it('verify that services are started', () => co(function*() { + it('verify that services are started', async () => { fakeI.started.isResolved().should.equal(false); fakeP.started.isResolved().should.equal(false); fakeO.started.isResolved().should.equal(false); fakeI.stopped.isResolved().should.equal(false); fakeP.stopped.isResolved().should.equal(false); fakeO.stopped.isResolved().should.equal(false); - yield run('hello-service'); + await run('hello-service'); fakeO.outputed.isResolved().should.equal(true); // The transaction has successfully gone through the whole stream fakeI.started.isResolved().should.equal(true); fakeP.started.isResolved().should.equal(true); @@ -270,50 +268,57 @@ describe("v1.0 Module API", () => { fakeI.stopped.isResolved().should.equal(false); fakeP.stopped.isResolved().should.equal(false); fakeO.stopped.isResolved().should.equal(false); - })); + }) - it('verify that services are stopped', () => co(function*() { + it('verify that services are stopped', async () => { fakeI.stopped.isResolved().should.equal(false); fakeP.stopped.isResolved().should.equal(false); fakeO.stopped.isResolved().should.equal(false); fakeI.started.isResolved().should.equal(true); fakeP.started.isResolved().should.equal(true); fakeO.started.isResolved().should.equal(true); - yield run('bye-service'); + await run('bye-service'); fakeI.started.isResolved().should.equal(false); fakeP.started.isResolved().should.equal(false); fakeO.started.isResolved().should.equal(false); fakeI.stopped.isResolved().should.equal(true); fakeP.stopped.isResolved().should.equal(true); fakeO.stopped.isResolved().should.equal(true); - })); - }); + }) + }) -}); +}) -function FakeStream(onWrite) { +class FakeStream extends stream.Transform { - const that = this; - stream.Transform.call(this, { objectMode: true }); + private resolveStart:any = () => null; + private resolveStop:any = () => null; + public resolveData:any + public started:any + public stopped:any + public outputed:any - let resolveStart = () => null; - let resolveStop = () => null; + constructor(private onWrite:any) { + super({ objectMode: true }) - this._write = onWrite.bind(this, that); + this.started = querablep(new Promise(res => this.resolveStart = res)); + this.stopped = querablep(new Promise(res => this.resolveStop = res)); + } - this.started = querablep(new Promise(res => resolveStart = res)); - this.stopped = querablep(new Promise(res => resolveStop = res)); + _write(obj:any, enc:any, done:any) { + this.onWrite(this, obj, enc, done) + } - this.startService = () => co(function*() { - resolveStart(); - that.stopped = querablep(new Promise(res => resolveStop = res)); - }); + async startService() { + this.resolveStart(); + this.stopped = querablep(new Promise(res => this.resolveStop = res)); + } - this.stopService = () => co(function*() { - resolveStop(); - that.started = querablep(new Promise(res => resolveStart = res)); - }); + async stopService() { + this.resolveStop(); + this.started = querablep(new Promise(res => this.resolveStart = res)); + } } util.inherits(FakeStream, stream.Transform); diff --git a/test/integration/v1.1-dividend.js b/test/integration/protocol/v1.1-dividend.ts similarity index 76% rename from test/integration/v1.1-dividend.js rename to test/integration/protocol/v1.1-dividend.ts index 475593909345ea6809697947e79c3a14b66ba977..c4a6add28bb050488afd933d8f6d4db804e8afef 100644 --- a/test/integration/v1.1-dividend.js +++ b/test/integration/protocol/v1.1-dividend.ts @@ -11,25 +11,22 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {HttpSources, HttpTxHistory} from "../../../app/modules/bma/lib/dtos" -const co = require('co'); const should = require('should'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const TestUser = require('./tools/TestUser').TestUser -const commit = require('./tools/commit'); -const toolbox = require('./tools/toolbox'); const now = 1484000000; -let s1, cat, tac, tic +let s1:TestingServer, cat:TestUser, tac:TestUser, tic:TestUser describe("Protocol 1.1 Dividend", function() { - before(() => co(function*() { + before(async () => { - s1 = toolbox.server({ + s1 = NewTestingServer({ c: 0.1, dt: 10, dtReeval: 10, @@ -47,26 +44,26 @@ describe("Protocol 1.1 Dividend", function() { tac = new TestUser('tac', { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'}, { server: s1 }); tic = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s1 }); - yield s1.initDalBmaConnections(); + await s1.initDalBmaConnections(); - yield cat.createIdentity(); - yield tac.createIdentity(); - yield cat.cert(tac); - yield tac.cert(cat); - yield cat.join(); - yield tac.join(); - yield s1.commit({ time: now }); - yield s1.commit({ time: now + 10 }); - yield s1.commit({ time: now + 10 * 2 }); - yield s1.commit({ time: now + 10 * 3 }); + await cat.createIdentity(); + await tac.createIdentity(); + await cat.cert(tac); + await tac.cert(cat); + await cat.join(); + await tac.join(); + await s1.commit({ time: now }); + await s1.commit({ time: now + 10 }); + await s1.commit({ time: now + 10 * 2 }); + await s1.commit({ time: now + 10 * 3 }); // tic joins - yield tic.createIdentity(); - yield cat.cert(tic); - yield tic.join(); - yield s1.commit({ time: now + 10 + 10 * 4 }); - yield s1.commit({ time: now + 10 + 10 * 5 }); - })); + await tic.createIdentity(); + await cat.cert(tic); + await tic.join(); + await s1.commit({ time: now + 10 + 10 * 4 }); + await s1.commit({ time: now + 10 + 10 * 5 }); + }) after(() => { return Promise.all([ @@ -74,7 +71,7 @@ describe("Protocol 1.1 Dividend", function() { ]) }) - it('should exit 2 dividends for cat', () => s1.expect('/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', (res) => { + it('should exit 2 dividends for cat', () => s1.expect('/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', (res:HttpSources) => { res.should.have.property('pubkey').equal('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); res.should.have.property('sources').length(4); res.sources[0].should.have.property('amount').equal(100); // UD(0) = ud0 => M(0) = 0 @@ -83,12 +80,12 @@ describe("Protocol 1.1 Dividend", function() { res.sources[3].should.have.property('amount').equal(103); // t = 3, M(t-1) = 402, N(t) = 3, UD(t) = UD(t-1) + c²*M(t-1)/N(t) = 101 + 0.01*400/3 = 103 (ceiled) => M(3) = M(2)+N(t-1)*DU(t-1) = 400+3*101 = 703 res.sources[0].should.have.property('base').equal(0); res.sources[1].should.have.property('base').equal(0); - })); + })) - it('should be able to send 300 units', () => co(function *() { - yield cat.send(105, tac); - yield s1.commit(); - yield s1.expect('/tx/sources/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', (res) => { + it('should be able to send 300 units', async () => { + await cat.sendMoney(105, tac); + await s1.commit(); + await s1.expect('/tx/sources/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', (res:HttpSources) => { res.should.have.property('pubkey').equal('2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc'); res.should.have.property('sources').length(6); res.sources[0].should.have.property('amount').equal(100); @@ -104,10 +101,10 @@ describe("Protocol 1.1 Dividend", function() { res.sources[4].should.have.property('type').equal('D'); res.sources[5].should.have.property('type').equal('T'); }) - })); + }) - it('should have a correct history', () => s1.expect('/tx/history/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', (res) => { + it('should have a correct history', () => s1.expect('/tx/history/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', (res:HttpTxHistory) => { res.history.received[0].should.have.property('blockstamp').not.equal(null).not.equal(''); res.history.received[0].should.have.property('blockstampTime').not.equal(null).greaterThan(0); - })); -}); + })) +}) diff --git a/test/integration/revoked_pubkey_replay.ts b/test/integration/revoked_pubkey_replay.ts index e0a32f2ede9aa57a05c157262f1762e894cb39a2..cb94affdafcd173587d27cd53743df6b30589359 100644 --- a/test/integration/revoked_pubkey_replay.ts +++ b/test/integration/revoked_pubkey_replay.ts @@ -12,14 +12,13 @@ // GNU Affero General Public License for more details. import {simpleNodeWith2Users, TestingServer} from "./tools/toolbox" +import {Underscore} from "../../app/lib/common-libs/underscore" -const _ = require('underscore') const TestUser = require('./tools/TestUser').TestUser describe("Revoked pubkey replay", function() { const now = 1500000000 - const DONT_WAIT_FOR_BLOCKCHAIN_CHANGE = true let s1:TestingServer, cat:any, tic:any const conf = { nbCores: 1, sigQty: 1 } @@ -47,7 +46,7 @@ describe("Revoked pubkey replay", function() { await s1.commit() await s1.expect('/wot/members', (res:any) => { res.should.have.property('results').length(3) - const ticEntries = _.filter(res.results, (entry:any) => entry.uid === 'tic') + const ticEntries = Underscore.filter(res.results, (entry:any) => entry.uid === 'tic') ticEntries.should.have.length(1) }) }) @@ -63,7 +62,7 @@ describe("Revoked pubkey replay", function() { await s1.commit() await s1.expect('/wot/members', (res:any) => { res.should.have.property('results').length(2) - const ticEntries = _.filter(res.results, (entry:any) => entry.uid === 'tic') + const ticEntries = Underscore.filter(res.results, (entry:any) => entry.uid === 'tic') ticEntries.should.have.length(0) }) }) @@ -71,7 +70,7 @@ describe("Revoked pubkey replay", function() { it('should not try to include tic2 in a new block', async () => { await s1.commit() await tic.join() - const block = await s1.commit(null, DONT_WAIT_FOR_BLOCKCHAIN_CHANGE) + const block = await s1.justCommit() block.should.have.property('joiners').length(0) }) diff --git a/test/integration/server-sandbox.js b/test/integration/sandbox/server-sandbox.ts similarity index 53% rename from test/integration/server-sandbox.js rename to test/integration/sandbox/server-sandbox.ts index 732087dca6336194a40da5ccbf2670753d999256..1bef58893d32dd9e8a0e55a2d2af2e5231c02302 100644 --- a/test/integration/server-sandbox.js +++ b/test/integration/sandbox/server-sandbox.ts @@ -11,26 +11,22 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {CommonConstants} from "../../../app/lib/common-libs/constants" +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" -const co = require('co'); const should = require('should'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const TestUser = require('./tools/TestUser').TestUser -const commit = require('./tools/commit'); -const toolbox = require('./tools/toolbox'); -const constants = require('../../app/lib/constants'); -const CommonConstants = require('../../app/lib/common-libs/constants').CommonConstants +const constants = require('../../../app/lib/constants'); const now = 1482300000; -let s1, s2, s3, i1, i2, i3, i4, i5, i6, i7, i7onS2, i8, i9, i10, i11, i12, i13, i14, i15, i16 +let s1:TestingServer, s2:TestingServer, s3:TestingServer, i1:TestUser, i2:TestUser, i3:TestUser, i4:TestUser, i5:TestUser, i6:TestUser, i7:TestUser, i7onS2:TestUser, i8:TestUser, i9:TestUser, i10:TestUser, i11:TestUser, i12:TestUser, i13:TestUser, i14:TestUser describe("Sandboxes", function() { + + before(async () => { - before(() => co(function*() { - - s1 = toolbox.server({ + s1 = NewTestingServer({ idtyWindow: 10, sigWindow: 10, msWindow: 10, @@ -42,14 +38,14 @@ describe("Sandboxes", function() { } }); - s2 = toolbox.server({ + s2 = NewTestingServer({ pair: { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7' } }); - s3 = toolbox.server({ + s3 = NewTestingServer({ pair: { pub: 'H9dtBFmJohAwMNXSbfoL6xfRtmrqMw8WZnjXMHr4vEHX', sec: '2ANWb1qjjYRtT2TPFv1rBWA4EVfY7pqE4WqFUuzEgWG4vzcuvyUxMtyeBSf93M4V3g4MeEkELaj6NjA72jxnb4yF' @@ -74,15 +70,15 @@ describe("Sandboxes", function() { // i15 = new TestUser('i15', { pub: '8cHWEmVrdT249w8vJdiBms9mbu6CguQgXx2gRVE8gfnT', sec: '5Fy9GXiLMyhvRLCpoFf35XXNj24WXX29wM6xeCQiy5Uk7ggNhRcZjjp8GcpjRyE94oNR2jRNK4eAGiYUFnvbEnGB' }, { server: s1 }); // i16 = new TestUser('i16', { pub: 'vi8hUTxss825cFCQE4SzmqBaAwLS236NmtrTQZBAAhG', sec: '5dVvAdWKcndQSaR9pzjEriRhGkCjef74HzecqKnydBVHdxXDewpAu3mcSU72PRKcCkTYTJPpgWmwuCyZubDKmoy4' }, { server: s1 }); - yield s1.initDalBmaConnections(); - yield s2.initDalBmaConnections(); - yield s3.initDalBmaConnections(); + await s1.initDalBmaConnections(); + await s2.initDalBmaConnections(); + await s3.initDalBmaConnections(); s1.dal.idtyDAL.setSandboxSize(3); s1.dal.msDAL.setSandboxSize(2); s1.dal.txsDAL.setSandboxSize(2); s2.dal.idtyDAL.setSandboxSize(10); s3.dal.idtyDAL.setSandboxSize(3); - })); + }) after(() => { return Promise.all([ @@ -95,108 +91,108 @@ describe("Sandboxes", function() { describe('Identities', () => { - it('should i1, i2, i3', () => co(function *() { - (yield s1.dal.idtyDAL.getSandboxRoom()).should.equal(3); - yield i1.createIdentity(); - yield i2.createIdentity(); - yield i3.createIdentity(); - (yield s1.dal.idtyDAL.getSandboxRoom()).should.equal(0); - })); - - it('should reject i4', () => shouldThrow(co(function *() { - (yield s1.dal.idtyDAL.getSandboxRoom()).should.equal(0); - yield i4.createIdentity(); - }))); - - it('should create i4 by i1->i4', () => co(function *() { - yield i4.createIdentity(null, s2); - yield i1.cert(i4, s2); - })); - - it('should accept i1 (already here) by i4->i1', () => co(function *() { - yield i4.cert(i1); - })); - - it('should commit & make room for sandbox, and commit again', () => co(function *() { - yield i1.join(); - yield i4.join(); - (yield s1.dal.idtyDAL.getSandboxRoom()).should.equal(0); - yield s1.commit({ time: now }); - (yield s1.dal.idtyDAL.getSandboxRoom()).should.equal(3); // i2, i3 were removed for too old identities (based on virtual root block) - yield i2.createIdentity(); - yield i3.createIdentity(); - (yield s1.dal.idtyDAL.getSandboxRoom()).should.equal(1); - yield s1.commit({ time: now }); - yield s2.syncFrom(s1, 0, 1); - yield s3.syncFrom(s1, 0, 1); - })); - - it('should create i5(1)', () => co(function *() { - yield i5.createIdentity(); - (yield s1.dal.idtyDAL.getSandboxRoom()).should.equal(0); - })); + it('should i1, i2, i3', async () => { + (await s1.dal.idtyDAL.getSandboxRoom()).should.equal(3); + await i1.createIdentity(); + await i2.createIdentity(); + await i3.createIdentity(); + (await s1.dal.idtyDAL.getSandboxRoom()).should.equal(0); + }) + + it('should reject i4', () => shouldThrow((async () => { + (await s1.dal.idtyDAL.getSandboxRoom()).should.equal(0); + await i4.createIdentity(); + })())) + + it('should create i4 by i1->i4', async () => { + await i4.createIdentity(null, s2); + await i1.cert(i4, s2); + }) + + it('should accept i1 (already here) by i4->i1', async () => { + await i4.cert(i1); + }) + + it('should commit & make room for sandbox, and commit again', async () => { + await i1.join(); + await i4.join(); + (await s1.dal.idtyDAL.getSandboxRoom()).should.equal(0); + await s1.commit({ time: now }); + (await s1.dal.idtyDAL.getSandboxRoom()).should.equal(3); // i2, i3 were removed for too old identities (based on virtual root block) + await i2.createIdentity(); + await i3.createIdentity(); + (await s1.dal.idtyDAL.getSandboxRoom()).should.equal(1); + await s1.commit({ time: now }); + await s2.syncFrom(s1._server, 0, 1); + await s3.syncFrom(s1._server, 0, 1); + }) + + it('should create i5(1)', async () => { + await i5.createIdentity(); + (await s1.dal.idtyDAL.getSandboxRoom()).should.equal(0); + }) it('should reject i7(1)', () => shouldThrow(i7.createIdentity(true))); - it('should reject i7(1) by revocation', () => shouldThrow(co(function *() { - yield i7onS2.createIdentity(true); - const idty = yield i7onS2.lookup(i7onS2.pub); - yield i7.revoke(idty); - }))); - - it('should reject i1 -> i7 by revocation', () => shouldThrow(co(function *() { - yield i1.cert(i7, s2); - }))); - - it('should accept i7(1), i8(1), i9(1) by i1->i7(1), i1->i8(1), i1->i9(1)', () => co(function *() { - (yield s1.dal.idtyDAL.getSandboxRoom()).should.equal(0); - yield i8.createIdentity(null, s2); - yield i1.cert(i8, s2); - (yield s1.dal.idtyDAL.getSandboxRoom()).should.equal(0); - yield i9.createIdentity(null, s2); - yield i1.cert(i9, s2); - (yield s1.dal.idtyDAL.getSandboxRoom()).should.equal(0); - })); - - it('should reject i10(1) by i1->i10(1)', () => shouldThrow(co(function *() { - (yield s1.dal.idtyDAL.getSandboxRoom()).should.equal(0); - yield i10.createIdentity(true, s2); - yield i1.cert(i10, s2); - }))); - - it('should accept i10(0) by i1->i10(0) because of an superior date compared to others in sandbox', () => co(function *() { - yield i10.createIdentity(null, s3); - yield i1.cert(i10, s3); - })); - - it('should accept i11(0) and i12(0) for the same reason', () => co(function *() { - yield i11.createIdentity(null, s3); - yield i12.createIdentity(null, s3); - })); - - it('should reject i13(0) because absolutely no more room is available', () => shouldThrow(co(function *() { - yield i13.createIdentity(true, s3); - yield i1.cert(i13, s3); - }))); - - it('should accept an identity with the same key as server, always', () => co(function *() { - (yield s3.dal.idtyDAL.getSandboxRoom()).should.equal(0); - yield i14.createIdentity(null, s3); - })); - - it('should make room as identities get expired', () => co(function *() { - yield s1.commit({ + it('should reject i7(1) by revocation', () => shouldThrow((async () => { + await i7onS2.createIdentity(true); + const idty = await i7onS2.lookup(i7onS2.pub); + await i7.revoke(idty); + })())) + + it('should reject i1 -> i7 by revocation', () => shouldThrow((async () => { + await i1.cert(i7, s2); + })())) + + it('should accept i7(1), i8(1), i9(1) by i1->i7(1), i1->i8(1), i1->i9(1)', async () => { + (await s1.dal.idtyDAL.getSandboxRoom()).should.equal(0); + await i8.createIdentity(null, s2); + await i1.cert(i8, s2); + (await s1.dal.idtyDAL.getSandboxRoom()).should.equal(0); + await i9.createIdentity(null, s2); + await i1.cert(i9, s2); + (await s1.dal.idtyDAL.getSandboxRoom()).should.equal(0); + }) + + it('should reject i10(1) by i1->i10(1)', () => shouldThrow((async () => { + (await s1.dal.idtyDAL.getSandboxRoom()).should.equal(0); + await i10.createIdentity(true, s2); + await i1.cert(i10, s2); + })())) + + it('should accept i10(0) by i1->i10(0) because of an superior date compared to others in sandbox', async () => { + await i10.createIdentity(null, s3); + await i1.cert(i10, s3); + }) + + it('should accept i11(0) and i12(0) for the same reason', async () => { + await i11.createIdentity(null, s3); + await i12.createIdentity(null, s3); + }) + + it('should reject i13(0) because absolutely no more room is available', () => shouldThrow((async () => { + await i13.createIdentity(true, s3); + await i1.cert(i13, s3); + })())) + + it('should accept an identity with the same key as server, always', async () => { + (await s3.dal.idtyDAL.getSandboxRoom()).should.equal(0); + await i14.createIdentity(null, s3); + }) + + it('should make room as identities get expired', async () => { + await s1.commit({ time: now + 1000 }); - yield s1.commit({ + await s1.commit({ time: now + 1000 }); - yield s1.commit({ + await s1.commit({ time: now + 1000 }); - (yield s1.dal.idtyDAL.getSandboxRoom()).should.equal(3); - })); - }); + (await s1.dal.idtyDAL.getSandboxRoom()).should.equal(3); + }) + }) describe('Certifications', () => { @@ -207,34 +203,34 @@ describe("Sandboxes", function() { constants.SANDBOX_SIZE_CERTIFICATIONS = NEW_VALUE }) - it('should accept i4->i7(0),i4->i8(0),i4->i9(0)', () => co(function *() { - yield i7.createIdentity(); - yield i8.createIdentity(); - yield i9.createIdentity(); - (yield s1.dal.certDAL.getSandboxForKey('2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc').getSandboxRoom()).should.equal(3); - yield i4.cert(i7); - yield i4.cert(i8); - yield i4.cert(i9); - (yield s1.dal.certDAL.getSandboxForKey('2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc').getSandboxRoom()).should.equal(0); - (yield s1.dal.certDAL.getSandboxForKey('91dWdiyf7KaC4GAiKrwU7nGuue1vvmHqjCXbPziJFYtE').getSandboxRoom()).should.equal(3); - })); - - it('should reject i4->i10(0)', () => shouldThrow(co(function *() { - (yield s1.dal.certDAL.getSandboxForKey('2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc').getSandboxRoom()).should.equal(0); - yield i4.cert(i10); - }))); - - it('should accept a certification from the same key as server, always', () => co(function *() { - (yield s1.dal.certDAL.getSandboxForKey('2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc').getSandboxRoom()).should.equal(0); - yield i1.cert(i8); - })); - - it('should make room as certs get expired', () => co(function *() { - yield s1.commit({ + it('should accept i4->i7(0),i4->i8(0),i4->i9(0)', async () => { + await i7.createIdentity(); + await i8.createIdentity(); + await i9.createIdentity(); + (await s1.dal.certDAL.getSandboxForKey('2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc').getSandboxRoom()).should.equal(3); + await i4.cert(i7); + await i4.cert(i8); + await i4.cert(i9); + (await s1.dal.certDAL.getSandboxForKey('2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc').getSandboxRoom()).should.equal(0); + (await s1.dal.certDAL.getSandboxForKey('91dWdiyf7KaC4GAiKrwU7nGuue1vvmHqjCXbPziJFYtE').getSandboxRoom()).should.equal(3); + }) + + it('should reject i4->i10(0)', () => shouldThrow((async () => { + (await s1.dal.certDAL.getSandboxForKey('2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc').getSandboxRoom()).should.equal(0); + await i4.cert(i10); + })())) + + it('should accept a certification from the same key as server, always', async () => { + (await s1.dal.certDAL.getSandboxForKey('2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc').getSandboxRoom()).should.equal(0); + await i1.cert(i8); + }) + + it('should make room as certs get expired', async () => { + await s1.commit({ time: now + 1000 }); - (yield s1.dal.certDAL.getSandboxForKey('2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc').getSandboxRoom()).should.equal(3); - })); + (await s1.dal.certDAL.getSandboxForKey('2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc').getSandboxRoom()).should.equal(3); + }) after(() => { constants.SANDBOX_SIZE_CERTIFICATIONS = OLD_VALUE @@ -243,31 +239,31 @@ describe("Sandboxes", function() { describe('Memberships', () => { - it('should accept i8,i9', () => co(function *() { - yield i8.createIdentity(); // Identities have changed - yield i9.createIdentity(); - (yield s1.dal.msDAL.getSandboxRoom()).should.equal(2); - yield i8.join(); - yield i9.join(); - (yield s1.dal.msDAL.getSandboxRoom()).should.equal(0); - })); - - it('should reject i7', () => shouldThrow(co(function *() { - (yield s1.dal.msDAL.getSandboxRoom()).should.equal(0); - yield i7.join(); - }))); - - it('should accept a membership from the same key as server, always', () => co(function *() { - (yield s1.dal.msDAL.getSandboxRoom()).should.equal(0); - yield i1.join(); - })); - - it('should make room as membership get expired', () => co(function *() { - yield s1.commit({ + it('should accept i8,i9', async () => { + await i8.createIdentity(); // Identities have changed + await i9.createIdentity(); + (await s1.dal.msDAL.getSandboxRoom()).should.equal(2); + await i8.join(); + await i9.join(); + (await s1.dal.msDAL.getSandboxRoom()).should.equal(0); + }) + + it('should reject i7', () => shouldThrow((async () => { + (await s1.dal.msDAL.getSandboxRoom()).should.equal(0); + await i7.join(); + })())) + + it('should accept a membership from the same key as server, always', async () => { + (await s1.dal.msDAL.getSandboxRoom()).should.equal(0); + await i1.join(); + }) + + it('should make room as membership get expired', async () => { + await s1.commit({ time: now + 1000 }); - (yield s1.dal.msDAL.getSandboxRoom()).should.equal(2); - })); + (await s1.dal.msDAL.getSandboxRoom()).should.equal(2); + }) }); describe('Transaction', () => { @@ -278,29 +274,29 @@ describe("Sandboxes", function() { CommonConstants.TRANSACTION_MAX_TRIES = 2; }) - it('should accept 2 transactions of 20, 30 units', () => co(function *() { - yield i4.send(20, i1); - yield i4.send(30, i1); - (yield s1.dal.txsDAL.getSandboxRoom()).should.equal(0); - })); + it('should accept 2 transactions of 20, 30 units', async () => { + await i4.sendMoney(20, i1); + await i4.sendMoney(30, i1); + (await s1.dal.txsDAL.getSandboxRoom()).should.equal(0); + }) - it('should reject amount of 10', () => shouldThrow(co(function *() { - yield i4.send(10, i1); - }))); + it('should reject amount of 10', () => shouldThrow((async () => { + await i4.sendMoney(10, i1); + })())) - it('should accept a transaction from the same key as server, always', () => co(function *() { - yield i1.send(10, i4); - })); + it('should accept a transaction from the same key as server, always', async () => { + await i1.sendMoney(10, i4); + }) - it('should make room as transactions get commited', () => co(function *() { - yield s1.commit(); - yield s1.commit(); - (yield s1.dal.txsDAL.getSandboxRoom()).should.equal(2); + it('should make room as transactions get commited', async () => { + await s1.commit(); + await s1.commit(); + (await s1.dal.txsDAL.getSandboxRoom()).should.equal(2); CommonConstants.TRANSACTION_MAX_TRIES = tmp; - })); - }); -}); + }) + }) +}) -function shouldThrow(promise) { +function shouldThrow<T>(promise:Promise<T>) { return promise.should.be.rejected(); } diff --git a/test/integration/scenarios/hello-plugin.js b/test/integration/scenarios/hello-plugin.js index 2c0dcf2571397d3b890151130ac1963ea6a68f7c..c5b98105235fd1dbd6b2e106b8ce9467b49f6448 100644 --- a/test/integration/scenarios/hello-plugin.js +++ b/test/integration/scenarios/hello-plugin.js @@ -13,19 +13,17 @@ "use strict" -const co = require('co') - module.exports = { duniter: { cli: [{ name: 'hello-world', desc: 'Says hello from \`duniter\` command.', logs: false, - onDatabaseExecute: (server, conf, program, params) => co(function*() { + onDatabaseExecute: async (server, conf, program, params) => { const msg = 'Hello world! from within Duniter.' console.log(msg) return msg - }) + } }] } } diff --git a/test/integration/scenarios/malformed-documents.js b/test/integration/scenarios/malformed-documents.js deleted file mode 100644 index 8ec1d222447895df14c2cb46e31da14ebd63f5d2..0000000000000000000000000000000000000000 --- a/test/integration/scenarios/malformed-documents.js +++ /dev/null @@ -1,50 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict"; - -const request = require('request'); - -module.exports = function(node1) { - - const malformedTransaction = "Version: 2\n" + - "Type: Transaction\n" + - "Currency: null\n" + - "Issuers:\n" + - "G2CBgZBPLe6FSFUgpx2Jf1Aqsgta6iib3vmDRA1yLiqU\n" + - "Inputs:\n" + - "0:T:1536:539CB0E60CD5F55CF1BE96F067E73BF55C052112:1.0\n" + - "Outputs:Comment: mon comments\n"; - - - function sendRaw (raw) { - return function(done) { - post('/tx/process', { - "transaction": raw - }, done); - } - } - - function post(uri, data, done) { - const postReq = request.post({ - "uri": 'http://' + [node1.server.conf.remoteipv4, node1.server.conf.remoteport].join(':') + uri, - "timeout": 1000 * 10 - }, function (err, res, body) { - done(err, res, body); - }); - postReq.form(data); - } - return [ - sendRaw(malformedTransaction) - ]; -}; diff --git a/test/integration/sources_property.js b/test/integration/sources/sources-property.ts similarity index 70% rename from test/integration/sources_property.js rename to test/integration/sources/sources-property.ts index 89467dc86cb71f98c81b8b9b88288fad89bafddc..eab71ca03a0ff8611ebadc10c664d03c0cb8e206 100644 --- a/test/integration/sources_property.js +++ b/test/integration/sources/sources-property.ts @@ -11,18 +11,12 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {simpleNodeWith2Users, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {HttpBlock, HttpSources} from "../../../app/modules/bma/lib/dtos" -const co = require('co'); -const _ = require('underscore'); const should = require('should'); const assert = require('assert'); -const constants = require('../../app/lib/constants'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const toolbox = require('./tools/toolbox'); -const node = require('./tools/node'); -const unit = require('./tools/unit'); -const http = require('./tools/http'); const now = 1480000000; @@ -33,18 +27,18 @@ const conf = { medianTimeBlocks: 1 // Easy: medianTime(b) = time(b-1) }; -let s1, cat, tac +let s1:TestingServer, cat:TestUser, tac:TestUser describe("Sources property", function() { - before(() => co(function*() { - const res = yield toolbox.simpleNodeWith2Users(conf); + before(async () => { + const res = await simpleNodeWith2Users(conf); s1 = res.s1; cat = res.cat; tac = res.tac; - yield s1.commit({ time: now }); - yield s1.commit({ time: now + 1 }); - })); + await s1.commit({ time: now }); + await s1.commit({ time: now + 1 }); + }) after(() => { return Promise.all([ @@ -52,17 +46,17 @@ describe("Sources property", function() { ]) }) - it('it should exist block#1 with UD of 200', () => s1.expect('/blockchain/block/1', (block) => { + it('it should exist block#1 with UD of 200', () => s1.expect('/blockchain/block/1', (block:HttpBlock) => { should.exists(block); assert.equal(block.number, 1); assert.equal(block.dividend, 200); })); - it('it should exist sources for HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', () => s1.expect('/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', (res) => { + it('it should exist sources for HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', () => s1.expect('/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', (res:HttpSources) => { assert.equal(res.sources.length, 1) })); - it('it should NOT exist sources if we change one letter to uppercased version', () => s1.expect('/tx/sources/HGTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', (res) => { + it('it should NOT exist sources if we change one letter to uppercased version', () => s1.expect('/tx/sources/HGTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', (res:HttpSources) => { assert.equal(res.sources.length, 0) })); }); diff --git a/test/integration/start_generate_blocks.js b/test/integration/start_generate_blocks.js deleted file mode 100644 index 6cef8f45240145f9e5158d3bcec9d406cf108ceb..0000000000000000000000000000000000000000 --- a/test/integration/start_generate_blocks.js +++ /dev/null @@ -1,150 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict"; - -const co = require('co'); -const _ = require('underscore'); -const duniter = require('../../index'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const TestUser = require('./tools/TestUser').TestUser -const rp = require('request-promise'); -const httpTest = require('./tools/http'); -const commit = require('./tools/commit'); -const until = require('./tools/until'); -const toolbox = require('./tools/toolbox'); -const PeerDTO = require('../../app/lib/dto/PeerDTO').PeerDTO -const contacter = require('../../app/modules/crawler').CrawlerDependency.duniter.methods.contacter; -const sync = require('./tools/sync'); -const shutDownEngine = require('./tools/shutDownEngine'); - -const expectJSON = httpTest.expectJSON; - -const MEMORY_MODE = true; -const commonConf = { - bmaWithCrawler: true, - ipv4: '127.0.0.1', - remoteipv4: '127.0.0.1', - currency: 'bb', - httpLogs: true, - forksize: 0, - sigQty: 1 -}; - -let s1, s2, cat, toc, tic, tuc - -let nodeS1; -let nodeS2; - -describe("Generation", function() { - - before(function() { - - return co(function *() { - - s1 = duniter( - '/bb7', - MEMORY_MODE, - _.extend({ - port: '7790', - pair: { - pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', - sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' - }, - powDelay: 1 - }, commonConf)); - - s2 = duniter( - '/bb7_2', - MEMORY_MODE, - _.extend({ - port: '7791', - pair: { - pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', - sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F' - }, - powDelay: 1 - }, commonConf)); - - const commitS1 = commit(s1); - - cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); - toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); - tic = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s1 }); - tuc = new TestUser('tuc', { pub: '3conGDUXdrTGbQPMQQhEC4Ubu1MCAnFrAYvUaewbUhtk', sec: '5ks7qQ8Fpkin7ycXpxQSxxjVhs8VTzpM3vEBMqM7NfC1ZiFJ93uQryDcoM93Mj77T6hDAABdeHZJDFnkDb35bgiU'}, { server: s1 }); - - let servers = [s1, s2]; - for (const server of servers) { - server.addEndpointsDefinitions(() => require('../../app/modules/bma').BmaDependency.duniter.methods.getMainEndpoint(server.conf)) - yield server.initWithDAL(); - server.bma = yield bma(server); - yield server.bma.openConnections(); - require('../../app/modules/router').RouterDependency.duniter.methods.routeToNetwork(server); - yield server.PeeringService.generateSelfPeer(server.conf); - const prover = require('../../app/modules/prover').ProverDependency.duniter.methods.prover(server); - server.startBlockComputation = () => prover.startService(); - server.stopBlockComputation = () => prover.stopService(); - } - nodeS1 = contacter('127.0.0.1', s1.conf.port); - nodeS2 = contacter('127.0.0.1', s2.conf.port); - // Server 1 - yield cat.createIdentity(); - yield toc.createIdentity(); - yield toc.cert(cat); - yield cat.cert(toc); - yield cat.join(); - yield toc.join(); - yield commitS1(); - // Server 2 syncs block 0 - yield sync(0, 0, s1, s2); - // Let each node know each other - let peer1 = yield nodeS1.getPeer(); - yield nodeS2.postPeer(PeerDTO.fromJSONObject(peer1).getRawSigned()); - let peer2 = yield nodeS2.getPeer(); - yield nodeS1.postPeer(PeerDTO.fromJSONObject(peer2).getRawSigned()); - s1.startBlockComputation(); - yield until(s2, 'block', 1); - s2.startBlockComputation(); - s1.conf.powDelay = 2000; - s2.conf.powDelay = 2000; - yield [ - toolbox.serverWaitBlock(s1, 3), - toolbox.serverWaitBlock(s2, 3) - ]; - s1.stopBlockComputation(); - s2.stopBlockComputation(); - }); - }); - - after(() => { - return Promise.all([ - shutDownEngine(s1), - shutDownEngine(s2) - ]) - }) - - describe("Server 1 /blockchain", function() { - - it('/current should exist', function() { - return expectJSON(rp('http://127.0.0.1:7790/blockchain/current', { json: true }), { - number: 3 - }); - }); - - it('/current should exist on other node too', function() { - return expectJSON(rp('http://127.0.0.1:7791/blockchain/current', { json: true }), { - number: 3 - }); - }); - }); -}); diff --git a/test/integration/tests.js b/test/integration/tests.js deleted file mode 100644 index 3632bfefb5776f5ecb6c96de65427e7c3c80e5d7..0000000000000000000000000000000000000000 --- a/test/integration/tests.js +++ /dev/null @@ -1,314 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict"; - -const co = require('co'); -const _ = require('underscore'); -const should = require('should'); -const assert = require('assert'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const constants = require('../../app/lib/constants'); -const node = require('./tools/node'); -const duniter = require('../../index'); -const TestUser = require('./tools/TestUser').TestUser -const jspckg = require('../../package'); -const commit = require('./tools/commit'); -const httpTest = require('./tools/http'); -const shutDownEngine = require('./tools/shutDownEngine'); -const rp = require('request-promise'); - -const expectAnswer = httpTest.expectAnswer; -const MEMORY_MODE = true; - -require('../../app/modules/bma').BmaDependency.duniter.methods.noLimit(); // Disables the HTTP limiter - -describe("Integration", function() { - - describe("Node 1", function() { - - const node1 = node('db1', { upnp: false, currency: 'bb', ipv4: 'localhost', port: 9999, remoteipv4: 'localhost', remoteport: 9999, httplogs: false, - rootoffset: 0, - sigQty: 1, - pair: { - pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', - sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' - } - }); - - const cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, node1); - const tac = new TestUser('tac', { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'}, node1); - const tic = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, node1); - const toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, node1); - - before(function(done) { - node1.startTesting() - .then(function(){ - node1.before([])(done); - }); - }); - - describe("Testing technical API", function(){ - - before(function(done) { - node1.before([])(done); - }); - after(node1.after()); - - it('/node/summary should give package.json version', node1.summary(function(summary, done){ - should.exists(summary); - should.exists(summary.duniter); - should.exists(summary.duniter.software); - should.exists(summary.duniter.version); - assert.equal(summary.duniter.software, "duniter"); - assert.equal(summary.duniter.version, jspckg.version); - done(); - })); - }); - - describe("Testing malformed documents", function(){ - - before(function(done) { - node1.before(require('./scenarios/malformed-documents')(node1))(done); - }); - after(node1.after()); - - it('should not have crashed because of wrong tx', function(){ - assert.equal(true, true); - }); - }); - - describe("Lookup on", function(){ - - before(function() { - return co(function *() { - - // Self certifications - yield cat.createIdentity(); - yield tac.createIdentity(); - yield tic.createIdentity(); - yield toc.createIdentity(); - // Certifications - yield cat.cert(tac); - }); - }); - after(node1.after()); - - describe("identities collisions", () => { - - it("sending same identity should fail", () => co(function *() { - - // We send again the same - try { - yield tic.createIdentity(); - throw 'Should have thrown an error'; - } catch (e) { - JSON.parse(e).ucode.should.equal(constants.ERRORS.ALREADY_UP_TO_DATE.uerr.ucode); - } - })); - - it("sending same identity (again) should fail", () => co(function *() { - - // We send again the same - try { - yield tic.createIdentity(); - throw 'Should have thrown an error'; - } catch (e) { - JSON.parse(e).ucode.should.equal(constants.ERRORS.ALREADY_UP_TO_DATE.uerr.ucode); - } - })); - }); - - describe("user cat", function(){ - - it('should give only 1 result', node1.lookup('cat', function(res, done){ - should.exists(res); - assert.equal(res.results.length, 1); - done(); - })); - - it('should have sent 1 signature', node1.lookup('cat', function(res, done){ - should.exists(res); - assert.equal(res.results[0].signed.length, 1); - should.exists(res.results[0].signed[0].isMember); - should.exists(res.results[0].signed[0].wasMember); - assert.equal(res.results[0].signed[0].isMember, false); - assert.equal(res.results[0].signed[0].wasMember, false); - done(); - })); - }); - - describe("user tac", function(){ - - it('should give only 1 result', node1.lookup('tac', function(res, done){ - should.exists(res); - assert.equal(res.results.length, 1); - done(); - })); - - it('should have 1 signature', node1.lookup('tac', function(res, done){ - should.exists(res); - assert.equal(res.results[0].uids[0].others.length, 1); - done(); - })); - - it('should have sent 0 signature', node1.lookup('tac', function(res, done){ - should.exists(res); - assert.equal(res.results[0].signed.length, 0); - done(); - })); - }); - - it('toc should give only 1 result', node1.lookup('toc', function(res, done){ - should.exists(res); - assert.equal(res.results.length, 1); - done(); - })); - - it('tic should give only 1 result', node1.lookup('tic', function(res, done){ - should.exists(res); - assert.equal(res.results.length, 1); - done(); - })); - }); - }); - - describe("Testing leavers", function(){ - - let node3, cat, tac, tic, toc - - before(function() { - return co(function *() { - - node3 = duniter('/db3', MEMORY_MODE, { - currency: 'dd', ipv4: 'localhost', port: 9997, remoteipv4: 'localhost', remoteport: 9997, httplogs: false, - rootoffset: 0, - sigQty: 1, sigPeriod: 0, - pair: { - pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', - sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' - } - }); - - cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: node3 }); - tac = new TestUser('tac', { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'}, { server: node3 }); - tic = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: node3 }); - toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: node3 }); - - yield node3.initWithDAL().then(bma).then((bmapi) => bmapi.openConnections()); - const now = 1482220000; - - // Self certifications - yield cat.createIdentity(); - yield tac.createIdentity(); - yield tic.createIdentity(); - yield toc.createIdentity(); - yield cat.cert(tac); - yield cat.cert(tic); - yield cat.cert(toc); - yield tac.cert(cat); - yield tac.cert(tic); - yield tic.cert(cat); - yield tic.cert(tac); - yield toc.cert(cat); - yield cat.join(); - yield tac.join(); - yield tic.join(); - yield toc.join(); - yield commit(node3)({ - time: now - }); - yield commit(node3)({ - time: now - }); - yield toc.leave(); - yield commit(node3)({ - time: now - }); - yield tac.cert(toc); - yield tic.cert(toc); - yield toc.cert(tic); // Should be taken in 1 block - yield toc.cert(tac); // Should be taken in 1 other block - yield commit(node3)({ - time: now + 200 - }); - yield commit(node3)({ - time: now + 200 - }); - yield commit(node3)({ - time: now + 200 - }); - }); - }); - - after(() => { - return Promise.all([ - shutDownEngine(node3) - ]) - }) - - it('toc should give only 1 result with 3 certification by others', () => expectAnswer(rp('http://127.0.0.1:9997/wot/lookup/toc', { json: true }), function(res) { - should.exists(res); - assert.equal(res.results.length, 1); - assert.equal(res.results[0].uids[0].others.length, 3); - })); - - it('tic should give only 1 results', () => expectAnswer(rp('http://127.0.0.1:9997/wot/lookup/tic', { json: true }), function(res) { - should.exists(res); - const uids = _.pluck(res.results[0].signed, 'uid'); - const uidsShould = ["cat", "tac", "toc"]; - uids.sort(); - uidsShould.sort(); - assert.deepEqual(uids, uidsShould); - assert.equal(res.results.length, 1); - assert.equal(res.results[0].signed.length, 3); - const cat_signed = _.findWhere(res.results[0].signed, { uid: 'cat'}); - const tac_signed = _.findWhere(res.results[0].signed, { uid: 'tac'}); - const toc_signed = _.findWhere(res.results[0].signed, { uid: 'toc'}); - assert.equal(cat_signed.uid, "cat"); - assert.equal(cat_signed.isMember, true); - assert.equal(cat_signed.wasMember, true); - assert.equal(tac_signed.uid, "tac"); - assert.equal(tac_signed.isMember, true); - assert.equal(tac_signed.wasMember, true); - assert.equal(toc_signed.uid, "toc"); - assert.equal(toc_signed.isMember, true); - assert.equal(toc_signed.wasMember, true); - assert.equal(res.results[0].uids[0].others.length, 3); - assert.equal(res.results[0].uids[0].others[0].uids.length, 1); - assert.equal(res.results[0].uids[0].others[0].isMember, true); - assert.equal(res.results[0].uids[0].others[0].wasMember, true); - })); - - it('it should exist block#2 with 4 members', () => expectAnswer(rp('http://127.0.0.1:9997/blockchain/block/2', { json: true }), function(block) { - should.exists(block); - assert.equal(block.number, 2); - assert.equal(block.membersCount, 4); - })); - - blockShouldHaveCerts(0, 8); - blockShouldHaveCerts(1, 0); - blockShouldHaveCerts(2, 0); - blockShouldHaveCerts(3, 1); - blockShouldHaveCerts(4, 1); - blockShouldHaveCerts(5, 0); - - function blockShouldHaveCerts(number, certificationsCount) { - it('it should exist block#' + number + ' with ' + certificationsCount + ' certification', () => expectAnswer(rp('http://127.0.0.1:9997/blockchain/block/' + number, { json: true }), function(block) { - should.exists(block); - assert.equal(block.number, number); - assert.equal(block.certifications.length, certificationsCount); - })); - } - }); -}); diff --git a/test/integration/tools/TestUser.ts b/test/integration/tools/TestUser.ts index edf7b8764d5dd0cd1b7538557e6bf1f0bdc4b126..ddd6e515a24eb91ac9807fe9c4584c7abbecc9d4 100644 --- a/test/integration/tools/TestUser.ts +++ b/test/integration/tools/TestUser.ts @@ -24,9 +24,10 @@ import {parsers} from "../../../app/lib/common-libs/parsers/index" import {TransactionDTO} from "../../../app/lib/dto/TransactionDTO" import {PeerDTO} from "../../../app/lib/dto/PeerDTO" import {Contacter} from "../../../app/modules/crawler/lib/contacter" +import {Underscore} from "../../../app/lib/common-libs/underscore" +import {HttpLookup} from "../../../app/modules/bma/lib/dtos" const request = require('request') -const _ = require('underscore') export interface TestInput { src:string @@ -63,7 +64,7 @@ export class TestUser { } } - public async createIdentity(useRoot?:boolean, fromServer?:any) { + public async createIdentity(useRoot?:boolean|null, fromServer?:any) { if (!this.pub) { this.init(() => {}) } @@ -92,7 +93,7 @@ export class TestUser { public async makeCert(user:TestUser, fromServer?:TestingServer, overrideProps?:any) { const lookup = await this.lookup(user.pub, fromServer) const current = await this.node.server.BlockchainService.current() - const idty = _.filter(lookup.results[0].uids, (uidEntry:{ uid: string }) => uidEntry.uid === user.uid)[0] + const idty = Underscore.filter(lookup.results[0].uids, uidEntry => uidEntry.uid === user.uid)[0] let buid = current ? Buid.format.buid(current.number, current.hash) : CommonConstants.SPECIAL_BLOCK const cert = { "version": CommonConstants.DOCUMENTS_VERSION, @@ -105,7 +106,7 @@ export class TestUser { "buid": buid, "sig": "" } - _.extend(cert, overrideProps || {}); + Underscore.extend(cert, overrideProps || {}); const rawCert = CertificationDTO.fromJSONObject(cert).getRawUnSigned() cert.sig = KeyGen(this.pub, this.sec).signSync(rawCert) return CertificationDTO.fromJSONObject(cert) @@ -126,9 +127,9 @@ export class TestUser { return await this.sendMembership("OUT") } - public async makeRevocation(givenLookupIdty?:any, overrideProps?:any) { + public async makeRevocation(givenLookupIdty?:HttpLookup, overrideProps?:any) { const res = givenLookupIdty || (await this.lookup(this.pub)); - const matchingResult = _.filter(res.results[0].uids, (uidEntry: { uid:string }) => uidEntry.uid === this.uid)[0] + const matchingResult = Underscore.filter(res.results[0].uids, uidEntry => uidEntry.uid === this.uid)[0] const idty = { uid: matchingResult.uid, buid: matchingResult.meta.timestamp, @@ -142,7 +143,7 @@ export class TestUser { "buid": idty.buid, "revocation": '' }; - _.extend(revocation, overrideProps || {}); + Underscore.extend(revocation, overrideProps || {}); const rawRevocation = RevocationDTO.fromJSONObject(revocation).getRawUnsigned() revocation.revocation = KeyGen(this.pub, this.sec).signSync(rawRevocation); return RevocationDTO.fromJSONObject(revocation) @@ -170,7 +171,7 @@ export class TestUser { "certts": idty.meta.timestamp, "signature": "" }; - _.extend(join, overrideProps || {}); + Underscore.extend(join, overrideProps || {}); const rawJoin = MembershipDTO.fromJSONObject(join).getRaw() join.signature = KeyGen(this.pub, this.sec).signSync(rawJoin) return MembershipDTO.fromJSONObject(join) @@ -183,19 +184,6 @@ export class TestUser { }) } - public send(amount:number, recipient:string, comment?:string) { - const that = this - return async function(done:(e?:any)=>void) { - try { - let raw = await that.prepareITX(amount, recipient, comment) - await that.sendTX(raw) - done() - } catch (e) { - done(e) - } - }; - }; - public async sendMoney(amount:number, recipient:TestUser, comment?:string) { const raw = await this.prepareITX(amount, recipient, comment) await this.sendTX(raw) @@ -351,7 +339,7 @@ export class TestUser { block: '2-00008DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB', endpoints: endpoints }); - _.extend(peer, overrideProps || {}); + Underscore.extend(peer, overrideProps || {}); const rawPeer = PeerDTO.fromJSONObject(peer).getRawUnsigned() peer.signature = KeyGen(this.pub, this.sec).signSync(rawPeer) return PeerDTO.fromJSONObject(peer) @@ -400,17 +388,8 @@ export class TestUser { }); } - public async lookup(pubkey:string, fromServer?:TestingServer) { + public async lookup(pubkey:string, fromServer?:TestingServer): Promise<HttpLookup> { const node2 = await this.getContacter(fromServer) return node2.getLookup(pubkey); } - - public async sendP(amount:number, userid:string, comment?:string) { - return new Promise((res, rej) => { - this.send(amount, userid, comment)((err) => { - if (err) return rej(err) - res() - }) - }) - } } diff --git a/test/integration/tools/commit.js b/test/integration/tools/commit.js deleted file mode 100644 index bb5804a8ed22526cd12426261b7eb3cd94fe39a7..0000000000000000000000000000000000000000 --- a/test/integration/tools/commit.js +++ /dev/null @@ -1,69 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict"; - -var _ = require('underscore'); -var co = require('co'); -var rp = require('request-promise'); -var logger = require('../../../app/lib/logger').NewLogger('test'); -const until = require('./until') -const BlockProver = require('../../../app/modules/prover/lib/blockProver').BlockProver - -module.exports = function makeBlockAndPost(theServer, extraProps, noWait) { - return function(manualValues) { - if (extraProps) { - manualValues = manualValues || {}; - manualValues = _.extend(manualValues, extraProps); - } - return co(function *() { - if (!theServer._utProver) { - theServer._utProver = new BlockProver(theServer) - theServer._utGenerator = require('../../../app/modules/prover').ProverDependency.duniter.methods.blockGenerator(theServer, theServer._utProver) - } - let proven = yield theServer._utGenerator.makeNextBlock(null, null, manualValues) - const numberToReach = proven.number - const block = yield postBlock(theServer)(proven) - yield new Promise((res) => { - if (noWait) { - return res(block) - } - const interval = setInterval(() => co(function*() { - const current = yield theServer.dal.getCurrentBlockOrNull() - if (current && current.number == numberToReach) { - res() - clearInterval(interval) - } - }), 1) - }) - return block - }); - }; -}; - -function postBlock(server) { - return function(block) { - logger.trace(block.getRawSigned()); - return post(server, '/blockchain/block')({ - block: typeof block == 'string' ? block : block.getRawSigned() - }); - }; -} - -function post(server, uri) { - return function(data) { - return rp.post('http://' + [server.conf.ipv4, server.conf.port].join(':') + uri, { - form: data - }); - }; -} diff --git a/test/integration/tools/http-expect.ts b/test/integration/tools/http-expect.ts new file mode 100644 index 0000000000000000000000000000000000000000..19053cfbfea5f752d6f236e12bfbae503ab43681 --- /dev/null +++ b/test/integration/tools/http-expect.ts @@ -0,0 +1,83 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import {Underscore} from "../../../app/lib/common-libs/underscore" + +const should = require('should'); +const assert = require('assert'); + +export async function expectHttpCode(code:number, message:any, promise?:any) { + if (arguments.length == 2) { + promise = arguments[1]; + message = undefined; + } + try { + const res = await promise; + assert.equal(res.statusCode, code); + } catch (err) { + if (err.response) { + assert.equal(err.response.statusCode, code); + if (message) { + assert.equal(err.error || err.cause, message); + } + } + else throw err; + } +} + +export async function expectError(code:number, message:any, promise?:any) { + if (arguments.length == 2) { + promise = arguments[1]; + message = undefined; + } + try { + const res = await promise; + assert.equal(res.statusCode, code); + } catch (err) { + if (err.response) { + assert.equal(err.response.statusCode, code); + if (message) { + let errorObj = typeof err.error == "string" ? JSON.parse(err.error) : err.error; + assert.equal(errorObj.message, message); + } + } + else throw err; + } +} + +export async function expectJSON<T>(promise:Promise<T>, json:any) { + try { + const resJson = await promise; + Underscore.keys(json).forEach(function(key){ + resJson.should.have.property(key).equal(json[key]); + }); + } catch (err) { + if (err.response) { + assert.equal(err.response.statusCode, 200); + } + else throw err; + } +} + +export async function expectAnswer<T>(promise:Promise<T>, testFunc:any) { + try { + const res = await promise; + return testFunc(res); + } catch (err) { + if (err.response) { + console.error(err.error); + assert.equal(err.response.statusCode, 200); + } + else throw err; + } +} diff --git a/test/integration/tools/http.js b/test/integration/tools/http.js deleted file mode 100644 index fcc25dada6ab8e319eefd483d52ef0e2e1885b34..0000000000000000000000000000000000000000 --- a/test/integration/tools/http.js +++ /dev/null @@ -1,96 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict"; - -const co = require('co'); -const should = require('should'); -const assert = require('assert'); -const _ = require('underscore'); - -module.exports = { - - expectHttpCode: function expectHttpCode(code, message, promise) { - if (arguments.length == 2) { - promise = arguments[1]; - message = undefined; - } - return co(function*(){ - try { - const res = yield promise; - assert.equal(res.statusCode, code); - } catch (err) { - if (err.response) { - assert.equal(err.response.statusCode, code); - if (message) { - assert.equal(err.error || err.cause, message); - } - } - else throw err; - } - }); - }, - - expectError: function expectHttpCode(code, message, promise) { - if (arguments.length == 2) { - promise = arguments[1]; - message = undefined; - } - return co(function*(){ - try { - const res = yield promise; - assert.equal(res.statusCode, code); - } catch (err) { - if (err.response) { - assert.equal(err.response.statusCode, code); - if (message) { - let errorObj = typeof err.error == "string" ? JSON.parse(err.error) : err.error; - assert.equal(errorObj.message, message); - } - } - else throw err; - } - }); - }, - - expectJSON: function expectJSON(promise, json) { - return co(function*(){ - try { - const resJson = yield promise; - _.keys(json).forEach(function(key){ - resJson.should.have.property(key).equal(json[key]); - }); - } catch (err) { - if (err.response) { - assert.equal(err.response.statusCode, 200); - } - else throw err; - } - }); - }, - - expectAnswer: function expectJSON(promise, testFunc) { - return co(function*(){ - try { - const res = yield promise; - return testFunc(res); - } catch (err) { - if (err.response) { - console.error(err.error); - assert.equal(err.response.statusCode, 200); - } - else throw err; - } - }); - } -}; diff --git a/test/integration/tools/node.js b/test/integration/tools/node.js deleted file mode 100644 index 57f696cd56ca7cc981e65d6eaf90ace83005d14c..0000000000000000000000000000000000000000 --- a/test/integration/tools/node.js +++ /dev/null @@ -1,335 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict"; -var co = require('co'); -var _ = require('underscore'); -var async = require('async'); -var request = require('request'); -var contacter = require('../../../app/modules/crawler').CrawlerDependency.duniter.methods.contacter; -var duniter = require('../../../index'); -var multicaster = require('../../../app/lib/streams/multicaster'); -var ConfDTO = require('../../../app/lib/dto/ConfDTO').ConfDTO -var PeerDTO = require('../../../app/lib/dto/PeerDTO').PeerDTO -var http = require('./http'); -const bma = require('../../../app/modules/bma').BmaDependency.duniter.methods.bma; - -module.exports = function (dbName, options) { - return new Node(dbName, options); -}; - -module.exports.statics = { -}; - -var UNTIL_TIMEOUT = 20000; - -function Node (dbName, options) { - - var logger = require('../../../app/lib/logger').NewLogger(dbName); - var that = this; - var started = false; - that.server = null; - that.http = null; - - /** - * To be executed before tests - * @param scenarios Scenarios to execute: a suite of operations over a node (identities, certs, tx, blocks, ...). - * @returns {Function} Callback executed by unit test framework. - */ - this.before = function (scenarios) { - return function(done) { - async.waterfall([ - function (next) { - that.http = contacter(options.remoteipv4, options.remoteport); - that.executes(scenarios, next); - } - ], done); - }; - }; - - this.executes = function (scenarios, done) { - async.waterfall([ - function(next) { - async.forEachSeries(scenarios, function(useCase, callback) { - useCase(callback); - }, next); - } - ], done); - }; - - /** - * To be exectued after unit tests. Here: clean the database (removal) - * @returns {Function} Callback executed by unit test framework. - */ - this.after = function () { - return function (done) { - done(); - }; - }; - - /** - * Generates next block and submit it to local node. - * @returns {Function} - */ - this.commit = function(params) { - return function(done) { - async.waterfall([ - function(next) { - async.parallel({ - block: function(callback){ - co(function *() { - try { - const block2 = yield require('../../../app/modules/prover').ProverDependency.duniter.methods.generateTheNextBlock(that.server, params); - const trial2 = yield that.server.getBcContext().getIssuerPersonalizedDifficulty(that.server.keyPair.publicKey); - const block = yield require('../../../app/modules/prover').ProverDependency.duniter.methods.generateAndProveTheNext(that.server, block2, trial2, params); - callback(null, block); - } catch (e) { - callback(e); - } - }); - } - }, next); - }, - function(res, next) { - var block = res.block; - logger.debug(block.getRawSigned()); - post('/blockchain/block', { - "block": block.getRawSigned() - }, next); - } - ], function(err, res) { - done(err, res.body); - }); - }; - }; - - function post(uri, data, done) { - return new Promise((resolve, reject) => { - var postReq = request.post({ - "uri": 'http://' + [that.server.conf.remoteipv4, that.server.conf.remoteport].join(':') + uri, - "timeout": 1000 * 10, - "json": true - }, function (err, res, body) { - if (err) { - reject(err) - done && done(err) - } else { - resolve(res, body) - done && done(err, res, body) - } - }); - postReq.form(data); - }) - } - - this.startTesting = function(done) { - return new Promise(function(resolve, reject){ - if (started) return done(); - async.waterfall([ - function(next) { - service(next)(); - }, - function (server, next){ - // Launching server - that.server = server; - started = true; - server.PeeringService.generateSelfPeer(server.conf, 0) - .then(() => next()) - .catch(next) - }, - function (next) { - that.http = contacter(options.remoteipv4, options.remoteport); - next(); - } - ], function(err) { - err ? reject(err) : resolve(); - done && done(err); - }); - }); - }; - - function service(callback) { - return function () { - const stack = duniter.statics.simpleStack(); - stack.registerDependency({ - duniter: { - config: { - onLoading: (conf, program) => co(function*() { - const overConf = ConfDTO.complete(options); - _.extend(conf, overConf); - }) - }, - service: { - process: (server) => _.extend(server, { - startService: () => { - logger.debug('Server Servie Started!'); - } - }) - }, - cli: [{ - name: 'execute', - desc: 'Unit Test execution', - onDatabaseExecute: (server, conf, program, params, startServices) => co(function*() { - yield startServices(); - callback(null, server); - yield Promise.resolve((res) => null); // Never ending - }) - }] - } - }, 'duniter-automated-test'); - options.port = options.port || 10901; - options.ipv4 = options.ipv4 || "127.0.0.1"; - options.ipv6 = options.ipv6 || null; - options.remotehost = options.remotehost || null; - options.remoteipv4 = options.remoteipv4 || null; - options.remoteipv6 = options.remoteipv6 || null; - options.remoteport = options.remoteport || 10901; - const cliOptions = ['--ws2p-noupnp'] - if (options.port) { - cliOptions.push('--port') - cliOptions.push(options.port) - } - if (options.ipv4) { - cliOptions.push('--ipv4') - cliOptions.push(options.ipv4) - } - if (options.ipv6) { - cliOptions.push('--ipv6') - cliOptions.push(options.ipv6) - } - if (options.remotehost) { - cliOptions.push('--remoteh') - cliOptions.push(options.remotehost) - } - if (options.remoteipv4) { - cliOptions.push('--remote4') - cliOptions.push(options.remoteipv4) - } - if (options.remoteipv6) { - cliOptions.push('--remote6') - cliOptions.push(options.remoteipv6) - } - if (options.remoteport) { - cliOptions.push('--remotep') - cliOptions.push(options.remoteport) - } - - stack.registerDependency(require('../../../app/modules/keypair').KeypairDependency, 'duniter-keypair') - stack.registerDependency(require('../../../app/modules/bma').BmaDependency, 'duniter-bma') - stack.executeStack(['', '', '--mdb', dbName, '--memory', 'execute'].concat(cliOptions)); - }; - } - - /************************ - * TEST UTILITIES - ************************/ - - this.lookup = function(search, callback) { - return function(done) { - co(function*(){ - try { - const res = yield that.http.getLookup(search); - callback(res, done); - } catch (err) { - logger.error(err); - callback(null, done); - } - }); - }; - }; - - this.until = function (eventName, count) { - var counted = 0; - var max = count == undefined ? 1 : count; - return new Promise(function (resolve, reject) { - var finished = false; - that.server.on(eventName, function () { - counted++; - if (counted == max) { - if (!finished) { - finished = true; - resolve(); - } - } - }); - setTimeout(function() { - if (!finished) { - finished = true; - reject('Received ' + counted + '/' + count + ' ' + eventName + ' after ' + UNTIL_TIMEOUT + ' ms'); - } - }, UNTIL_TIMEOUT); - }); - }; - - this.block = function(number, callback) { - return function(done) { - co(function*(){ - try { - const res = yield that.http.getBlock(number); - callback(res, done); - } catch (err) { - logger.error(err); - callback(null, done); - } - }); - }; - }; - - this.summary = function(callback) { - return function(done) { - co(function*(){ - try { - const res = yield that.http.getSummary(); - callback(res, done); - } catch (err) { - logger.error(err); - callback(null, done); - } - }); - }; - }; - - this.peering = function(done) { - co(function*(){ - try { - const res = yield that.http.getPeer(); - done(null, res); - } catch (err) { - logger.error(err); - done(err); - } - }); - }; - - this.peeringP = () => that.http.getPeer(); - - this.submitPeer = function(peer, done) { - return post('/network/peering/peers', { - "peer": PeerDTO.fromJSONObject(peer).getRawSigned() - }, done); - }; - - this.submitPeerP = (peer) => new Promise((res, rej) => { - that.submitPeer(peer, (err, data) => { - if (err) return rej(err) - res(data) - }) - }) - - this.commitP = (params) => new Promise((res, rej) => { - this.commit(params)((err, data) => { - if (err) return rej(err) - res(data) - }) - }) -} diff --git a/app/lib/blockchain/interfaces/IndexOperator.ts b/test/integration/tools/shutdown-engine.ts similarity index 63% rename from app/lib/blockchain/interfaces/IndexOperator.ts rename to test/integration/tools/shutdown-engine.ts index 57b475d630fa753a04713bd904b7d29b89bc8951..b58c37666761b1ab8f2cad94360f5c02f6224585 100644 --- a/app/lib/blockchain/interfaces/IndexOperator.ts +++ b/test/integration/tools/shutdown-engine.ts @@ -11,19 +11,11 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict" +import {TestingServer} from "./toolbox" -export interface IndexOperator { - - initIndexer(pkFields: any): Promise<void>, - - getSubIndexes(): Promise<string[]>, - - findWhere(subIndex: string, criterias: {}): Promise<any[]>, - - findTrimable(subIndex: string, numberField: string, maxNumber: number): Promise<any[]>, - - removeWhere(subIndex: string, criterias: {}): Promise<void>, - - recordIndex(index: any): Promise<void> +export async function shutDownEngine(server:TestingServer) { + if ((server as any)._utProver) { + const farm = await (server as any)._utProver.getWorker() + return farm.shutDownEngine() + } } diff --git a/test/integration/tools/test-framework.ts b/test/integration/tools/test-framework.ts new file mode 100644 index 0000000000000000000000000000000000000000..0f691805f034bb3e849c865330862ed081e9b665 --- /dev/null +++ b/test/integration/tools/test-framework.ts @@ -0,0 +1,39 @@ +import {catUser, NewTestingServer, tacUser, TestingServer} from "./toolbox" +import {TestUser} from "./TestUser" +import * as assert from 'assert' + +export function writeBasicTestWith2Users(writeTests: (test: (testTitle: string, fn: (server: TestingServer, cat: TestUser, tac: TestUser) => Promise<void>) => void) => void) { + + let s1:TestingServer, cat:TestUser, tac:TestUser + + before(async () => { + s1 = NewTestingServer({ + medianTimeBlocks: 1, + pair: { + pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', + sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' + } + }) + cat = catUser(s1) + tac = tacUser(s1) + await s1.prepareForNetwork() + }) + + writeTests((title, cb: (server: TestingServer, cat: TestUser, tac: TestUser) => Promise<void>) => { + it(title, async () => { + await cb(s1, cat, tac) + }) + }) +} + +export function assertEqual(value: number, expected: number) { + assert.equal(value, expected) +} + +export function assertTrue(expected: boolean) { + assert.equal(true, expected) +} + +export function assertFalse(expected: boolean) { + assert.equal(false, expected) +} \ No newline at end of file diff --git a/test/integration/tools/sync.js b/test/integration/tools/test-sync.ts similarity index 63% rename from test/integration/tools/sync.js rename to test/integration/tools/test-sync.ts index b453100665f011266382cd45bf2b33c1bd3192ed..5a3d13b744e800ffec99224ed0d85f7b5a08e372 100644 --- a/test/integration/tools/sync.js +++ b/test/integration/tools/test-sync.ts @@ -11,17 +11,16 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {Server} from "../../../server" -const co = require('co'); -const _ = require('underscore'); +const Underscore = require('../../../app/lib/common-libs/underscore').Underscore; const rp = require('request-promise'); -module.exports = function makeBlockAndPost(fromBlock, toBlock, fromServer, toServer) { +export function sync(fromBlock:number, toBlock:number, fromServer:Server, toServer:Server) { // Sync blocks - return _.range(fromBlock, toBlock + 1).reduce((p, number) => co(function*(){ - yield p; - const json = yield rp('http://' + fromServer.conf.ipv4 + ':' + fromServer.conf.port + '/blockchain/block/' + number, { json: true }); - yield toServer.writeBlock(json) - }), Promise.resolve()); -}; + return Underscore.range(fromBlock, toBlock + 1).reduce(async (p:Promise<any>, number:number) => { + await p; + const json = await rp('http://' + fromServer.conf.ipv4 + ':' + fromServer.conf.port + '/blockchain/block/' + number, { json: true }); + await toServer.writeBlock(json) + }, Promise.resolve()) +} diff --git a/test/integration/tools/until.js b/test/integration/tools/test-until.ts similarity index 79% rename from test/integration/tools/until.js rename to test/integration/tools/test-until.ts index 3d45be8f4540d6990279c009938c6b9f05084215..4c05899eab0e5f7599930e6489edf6d440059642 100644 --- a/test/integration/tools/until.js +++ b/test/integration/tools/test-until.ts @@ -11,16 +11,16 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {TestingServer} from "./toolbox" -var UNTIL_TIMEOUT = 115000; +const UNTIL_TIMEOUT = 115000; -module.exports = function (server, eventName, count) { - var counted = 0; - var max = count == undefined ? 1 : count; +export function until(server:TestingServer, eventName:string, count:number) { + let counted = 0; + const max = count == undefined ? 1 : count; return new Promise(function (resolve, reject) { - var finished = false; - server.on(eventName, function () { + let finished = false; + server._server.on(eventName, function () { counted++; if (counted == max) { if (!finished) { @@ -36,4 +36,4 @@ module.exports = function (server, eventName, count) { } }, UNTIL_TIMEOUT); }); -}; +} diff --git a/test/integration/tools/toolbox.ts b/test/integration/tools/toolbox.ts index d9261a036fc2195f5dce2a73b890efe56dfdca71..677c3b2a86ee976c3ca8406f5613c3d73be95f74 100644 --- a/test/integration/tools/toolbox.ts +++ b/test/integration/tools/toolbox.ts @@ -19,7 +19,7 @@ import * as stream from "stream" import {RevocationDTO} from "../../../app/lib/dto/RevocationDTO" import {IdentityDTO} from "../../../app/lib/dto/IdentityDTO" import {PeerDTO} from "../../../app/lib/dto/PeerDTO" -import {Network} from "../../../app/modules/bma/lib/network" +import {BmaApi, Network} from "../../../app/modules/bma/lib/network" import {DBIdentity} from "../../../app/lib/dal/sqliteDAL/IdentityDAL" import {CertificationDTO} from "../../../app/lib/dto/CertificationDTO" import {BlockchainService} from "../../../app/service/BlockchainService" @@ -37,18 +37,24 @@ import {WS2PServer} from "../../../app/modules/ws2p/lib/WS2PServer" import {WS2PServerMessageHandler} from "../../../app/modules/ws2p/lib/interface/WS2PServerMessageHandler" import {TestUser} from "./TestUser" import {RouterDependency} from "../../../app/modules/router" +import {ProverDependency} from "../../../app/modules/prover/index" +import {WS2PClient} from "../../../app/modules/ws2p/lib/WS2PClient" +import {DBBlock} from "../../../app/lib/db/DBBlock" +import {DBPeer} from "../../../app/lib/db/DBPeer" +import {Underscore} from "../../../app/lib/common-libs/underscore" +import {BmaDependency} from "../../../app/modules/bma/index" +import {NewLogger} from "../../../app/lib/logger" +import {BlockProver} from "../../../app/modules/prover/lib/blockProver" +import {DataErrors} from "../../../app/lib/common-libs/errors" +import {until} from "./test-until" +import {sync} from "./test-sync" +import {expectAnswer, expectError, expectJSON} from "./http-expect" +import {WebSocketServer} from "../../../app/lib/common-libs/websocket" const assert = require('assert'); -const _ = require('underscore'); const rp = require('request-promise'); const es = require('event-stream'); -const WebSocketServer = require('ws').Server -const httpTest = require('../tools/http'); -const sync = require('../tools/sync'); -const commit = require('../tools/commit'); -const until = require('../tools/until'); -const bma = require('../../../app/modules/bma').BmaDependency.duniter.methods.bma; -const logger = require('../../../app/lib/logger').NewLogger('toolbox'); +const logger = NewLogger(); require('../../../app/modules/bma').BmaDependency.duniter.methods.noLimit(); // Disables the HTTP limiter @@ -61,30 +67,6 @@ export const getNewTestingPort = () => { return PORT++ } -export const shouldFail = async (promise:Promise<any>, message:string|null = null) => { - try { - await promise; - throw '{ "message": "Should have thrown an error" }' - } catch(e) { - let err = e - if (typeof e === "string") { - err = JSON.parse(e) - } - err.should.have.property('message').equal(message); - } -} -export const assertThrows = async (promise:Promise<any>, message:string|null = null) => { - try { - await promise; - throw "Should have thrown" - } catch(e) { - if (e === "Should have thrown") { - throw e - } - assert.equal(e, message) - } -} - export const simpleUser = (uid:string, keyring:{ pub:string, sec:string }, server:TestingServer) => { return new TestUser(uid, keyring, { server }); } @@ -93,8 +75,8 @@ export const simpleNetworkOf2NodesAnd2Users = async (options:any) => { const catKeyring = { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}; const tacKeyring = { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'}; - const s1 = NewTestingServer(_.extend({ pair: catKeyring }, options || {})); - const s2 = NewTestingServer(_.extend({ pair: tacKeyring }, options || {})); + const s1 = NewTestingServer(Underscore.extend({ pair: catKeyring }, options || {})); + const s2 = NewTestingServer(Underscore.extend({ pair: tacKeyring }, options || {})); const cat = new TestUser('cat', catKeyring, { server: s1 }); const tac = new TestUser('tac', tacKeyring, { server: s1 }); @@ -125,7 +107,7 @@ export const simpleNodeWith2Users = async (options:any) => { const catKeyring = { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}; const tacKeyring = { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'}; - const s1 = NewTestingServer(_.extend({ pair: catKeyring }, options || {})); + const s1 = NewTestingServer(Underscore.extend({ pair: catKeyring }, options || {})); const cat = new TestUser('cat', catKeyring, { server: s1 }); const tac = new TestUser('tac', tacKeyring, { server: s1 }); @@ -147,7 +129,7 @@ export const simpleNodeWith2otherUsers = async (options:any) => { const ticKeyring = { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}; const tocKeyring = { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}; - const s1 = NewTestingServer(_.extend({ pair: ticKeyring }, options || {})); + const s1 = NewTestingServer(Underscore.extend({ pair: ticKeyring }, options || {})); const tic = new TestUser('cat', ticKeyring, { server: s1 }); const toc = new TestUser('tac', tocKeyring, { server: s1 }); @@ -258,7 +240,7 @@ export const NewTestingServer = (conf:any) => { const server = new Server( '~/.config/duniter/' + (conf.homename || 'dev_unit_tests'), conf.memory !== undefined ? conf.memory : MEMORY_MODE, - _.extend(conf, commonConf)); + Underscore.extend(conf, commonConf)); return new TestingServer(port, server) } @@ -331,13 +313,15 @@ export const waitForHeads = async (server:Server, nbHeads:number) => { export class TestingServer { private prover:Prover - private permaProver:PermanentProver - private bma:any + public permaProver:PermanentProver + public bma:BmaApi constructor( private port:number, private server:Server) { + ProverDependency.duniter.methods.hookServer(server) + server.addEndpointsDefinitions(async () => { return require('../../../app/modules/bma').BmaDependency.duniter.methods.getMainEndpoint(server.conf) }) @@ -371,7 +355,11 @@ export class TestingServer { return this.server.home } - revert() { + initWithDAL() { + return this.server.initWithDAL() + } + + revert(): Promise<DBBlock> { return this.server.revert() } @@ -383,7 +371,7 @@ export class TestingServer { return this.server.on(event, f) } - recomputeSelfPeer() { + recomputeSelfPeer(): Promise<DBPeer | null> { return this.server.recomputeSelfPeer() } @@ -445,7 +433,7 @@ export class TestingServer { async initDalBmaConnections() { await this.server.initWithDAL() - const bmapi = await bma(this.server) + const bmapi = await BmaDependency.duniter.methods.bma(this.server) this.bma = bmapi const res = await bmapi.openConnections() return res @@ -465,34 +453,61 @@ export class TestingServer { expect(uri:string, expectations:any) { - return typeof expectations == 'function' ? httpTest.expectAnswer(rp(this.url(uri), { json: true }), expectations) : httpTest.expectJSON(rp(this.url(uri), { json: true }), expectations); + return typeof expectations == 'function' ? expectAnswer(rp(this.url(uri), { json: true }), expectations) : expectJSON(rp(this.url(uri), { json: true }), expectations); } expectThat(uri:string, expectations:any) { - return httpTest.expectAnswer(rp(this.url(uri), { json: true }), expectations); + return expectAnswer(rp(this.url(uri), { json: true }), expectations); } expectJSON(uri:string, expectations:any) { - return httpTest.expectJSON(rp(this.url(uri), { json: true }), expectations); + return expectJSON(rp(this.url(uri), { json: true }), expectations); } - expectError(uri:string, code:number, message:string) { - return httpTest.expectError(code, message, rp(this.url(uri), { json: true })); + expectError(uri:string, code:number, message = '') { + return expectError(code, message, rp(this.url(uri), { json: true })); } - syncFrom(otherServer:Server, fromIncuded:number, toIncluded:number) { return sync(fromIncuded, toIncluded, otherServer, this.server); } - until(type:string, count:number) { - return until(this.server, type, count); + return until(this, type, count); } - async commit(options:any = null, noWait = false) { - const raw = await commit(this.server, null, noWait)(options); - return JSON.parse(raw); + async justCommit(options:any = null) { + const proven = await this.generateNext(options) + await this.server.writeBlock(proven, true, false) + return proven + } + + async commit(options:any = null) { + const proven = await this.generateNext(options) + await this.server.writeBlock(proven, true, true) // The resolution is done manually + const blocksResolved = await this.server.BlockchainService.blockResolution() + if (!blocksResolved) { + throw Error(DataErrors[DataErrors.BLOCK_WASNT_COMMITTED]) + } + return blocksResolved + } + + async resolveExistingBlock(max = 0) { + const blocksResolved = await this.server.BlockchainService.blockResolution(max) + if (!blocksResolved) { + throw Error(DataErrors[DataErrors.BLOCK_WASNT_COMMITTED]) + } + return blocksResolved + } + + private generateNext(options:any) { + const server = this.server as any + // Brings a priver to the server + if (!server._utProver) { + server._utProver = new BlockProver(server) + server._utGenerator = ProverDependency.duniter.methods.blockGenerator(server, server._utProver) + } + return server._utGenerator.makeNextBlock(null, null, options) } async commitWaitError(options:any, expectedError:string) { @@ -505,8 +520,8 @@ export class TestingServer { })) }), (async () => { - const raw = await commit(this.server, null, true)(options); - return JSON.parse(raw); + const proven = await this.generateNext(options) + await this.server.writeBlock(proven, false) })() ]) return results[1] @@ -514,8 +529,7 @@ export class TestingServer { async commitExpectError(options:any) { try { - const raw = await commit(this.server)(options); - JSON.parse(raw); + await this.commit(options) throw { message: 'Commit operation should have thrown an error' }; } catch (e) { if (e.statusCode) { @@ -612,7 +626,7 @@ export class TestingServer { async prepareForNetwork() { await this.server.initWithDAL(); - const bmaAPI = await bma(this.server); + const bmaAPI = await BmaDependency.duniter.methods.bma(this.server); await bmaAPI.openConnections(); this.bma = bmaAPI; RouterDependency.duniter.methods.routeToNetwork(this.server) @@ -658,10 +672,10 @@ export async function newWS2PBidirectionnalConnection(currency:string, k1:Key, k switch (i) { case 1: s1 = WS2PConnection.newConnectionFromWebSocketServer(ws, serverHandler, new WS2PPubkeyLocalAuth(currency, k1, ""), new WS2PPubkeyRemoteAuth(currency, k1), { - connectionTimeout: 100, - requestTimeout: 100 + connectionTimeout: 1000, + requestTimeout: 1000 }); - s1.connect().catch((e:any) => console.error('WS2P: newConnectionFromWebSocketServer connection error')) + s1.connect().catch((e:any) => console.error('WS2P: newConnectionFromWebSocketServer connection error:', e)) break; } resolveBefore({ @@ -681,7 +695,7 @@ export async function newWS2PBidirectionnalConnection(currency:string, k1:Key, k }) } -export const simpleWS2PNetwork: (s1: TestingServer, s2: TestingServer) => Promise<{ w1: WS2PConnection; ws2pc: WS2PConnection; wss: WS2PServer, cluster1:WS2PCluster, cluster2:WS2PCluster }> = async (s1: TestingServer, s2: TestingServer) => { +export const simpleWS2PNetwork: (s1: TestingServer, s2: TestingServer) => Promise<{ w1: WS2PConnection; ws2pc: WS2PClient; wss: WS2PServer, cluster1:WS2PCluster, cluster2:WS2PCluster }> = async (s1: TestingServer, s2: TestingServer) => { let port = getNewTestingPort() const clientPub = s2.conf.pair.pub let w1: WS2PConnection | null @@ -719,4 +733,22 @@ export function simpleTestingConf(now = 1500000000, pair:{ pub:string, sec:strin sigQty: 1, medianTimeBlocks: 1 // The medianTime always equals previous block's medianTime } +} + +export function catUser(server: TestingServer) { + return new TestUser('cat', { + pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', + sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, + { + server + }) +} + +export function tacUser(server: TestingServer) { + return new TestUser('tac', { + pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', + sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'}, + { + server + }) } \ No newline at end of file diff --git a/test/integration/tools/unit.js b/test/integration/tools/unit.js deleted file mode 100644 index f92646ce2d132c11bc3733f24346eace76e09f08..0000000000000000000000000000000000000000 --- a/test/integration/tools/unit.js +++ /dev/null @@ -1,41 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict"; - -var should = require('should'); -var co = require('co'); - -module.exports = { - - shouldFail: (promise, message) => co(function *() { - try { - yield promise; - throw { "message": '{ "message": "Should have thrown an error" }' }; - } catch(e) { - e.should.have.property('message').equal(message); - } - }), - - shouldNotFail: (promise) => co(function *() { - try { - yield promise; - } catch(e) { - let err = e; - if (typeof e === 'string') { - err = JSON.parse(e.message); - } - should.not.exist(err); - } - }) -}; diff --git a/test/integration/transactions-chaining.ts b/test/integration/transactions-chaining.ts index 7b1142b2ba91f1a5fcd1c064fd234afabc90dc1a..3511ddfd208e6c06bc5ec033b9a5565d1acd88ea 100644 --- a/test/integration/transactions-chaining.ts +++ b/test/integration/transactions-chaining.ts @@ -14,11 +14,11 @@ import {CommonConstants} from "../../app/lib/common-libs/constants" import {TestUser} from "./tools/TestUser" import {TestingServer} from "./tools/toolbox" +import {shouldNotFail} from "../unit-tools" const should = require('should'); const assert = require('assert'); const toolbox = require('./tools/toolbox'); -const unit = require('./tools/unit'); describe("Transaction chaining", () => { @@ -81,8 +81,8 @@ describe("Transaction chaining", () => { }); const tmp = CommonConstants.TRANSACTION_MAX_TRIES; CommonConstants.TRANSACTION_MAX_TRIES = 2; - await unit.shouldNotFail(toc.sendTX(tx1)); - await unit.shouldNotFail(toc.sendTX(tx2)); + await shouldNotFail(toc.sendTX(tx1)); + await shouldNotFail(toc.sendTX(tx2)); (await s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(1); // 1200 (await s1.get('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV')).should.have.property('sources').length(1); // 1200 await s1.commit({ time: now + 7210 }); // TX1 commited only @@ -110,13 +110,13 @@ describe("Transaction chaining", () => { let tx7 = await tic.prepareUTX(tx6, ['SIG(0)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { blockstamp, comment: "PING-PONG TX7" }); const tmp = CommonConstants.TRANSACTION_MAX_TRIES; CommonConstants.TRANSACTION_MAX_TRIES = 2; - await unit.shouldNotFail(toc.sendTX(tx1)); - await unit.shouldNotFail(toc.sendTX(tx2)); - await unit.shouldNotFail(toc.sendTX(tx3)); - await unit.shouldNotFail(toc.sendTX(tx4)); - await unit.shouldNotFail(toc.sendTX(tx5)); - await unit.shouldNotFail(toc.sendTX(tx6)); - await unit.shouldNotFail(toc.sendTX(tx7)); + await shouldNotFail(toc.sendTX(tx1)); + await shouldNotFail(toc.sendTX(tx2)); + await shouldNotFail(toc.sendTX(tx3)); + await shouldNotFail(toc.sendTX(tx4)); + await shouldNotFail(toc.sendTX(tx5)); + await shouldNotFail(toc.sendTX(tx6)); + await shouldNotFail(toc.sendTX(tx7)); await s1.commitWaitError({ dontCareAboutChaining: true }, 'The maximum transaction chaining length per block is 5') CommonConstants.TRANSACTION_MAX_TRIES = tmp; }) diff --git a/test/integration/transactions-csv-cltv-sig.ts b/test/integration/transactions-csv-cltv-sig.ts index 57e4ac69ad930b588302bd1a39b431bee5c175c2..a9ada96076f240df4f03089549f6f4b657d579a3 100644 --- a/test/integration/transactions-csv-cltv-sig.ts +++ b/test/integration/transactions-csv-cltv-sig.ts @@ -18,7 +18,6 @@ import {Buid} from "../../app/lib/common-libs/buid" describe("Transaction: CSV+CLTV+1of2SIG", function() { const now = 1500000000 - const DONT_WAIT_FOR_BLOCKCHAIN_CHANGE = true let s1:TestingServer let cat:any let tac:any @@ -59,7 +58,7 @@ describe("Transaction: CSV+CLTV+1of2SIG", function() { }) txHash = hashf(tx) await cat.sendTX(tx) - const block = await s1.commit({ time: now }, DONT_WAIT_FOR_BLOCKCHAIN_CHANGE) + const block = await s1.justCommit({ time: now }) block.should.have.property('transactions').length(1) await s1.waitToHaveBlock(2) }) @@ -79,7 +78,7 @@ describe("Transaction: CSV+CLTV+1of2SIG", function() { blockstamp: Buid.format.buid(current) }) await tac.sendTX(tx) - const block = await s1.commit(null, DONT_WAIT_FOR_BLOCKCHAIN_CHANGE) + const block = await s1.justCommit(null) block.should.have.property('transactions').length(1) }) diff --git a/test/integration/crosschain-test.js b/test/integration/transactions/transaction-crosschain.ts similarity index 55% rename from test/integration/crosschain-test.js rename to test/integration/transactions/transaction-crosschain.ts index 22c436238f7400100e78be9b5a5a9b7c082031cd..2ea332e92d61197a58bfac30e4e7ca4badcb1b2e 100644 --- a/test/integration/crosschain-test.js +++ b/test/integration/transactions/transaction-crosschain.ts @@ -11,19 +11,16 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {Underscore} from "../../../app/lib/common-libs/underscore" +import {HttpSources} from "../../../app/modules/bma/lib/dtos" +import {shouldFail, shouldNotFail} from "../../unit-tools" +import {expectAnswer} from "../tools/http-expect" -const co = require('co'); -const _ = require('underscore'); const assert = require('assert'); const should = require('should'); const rp = require('request-promise'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const commit = require('./tools/commit'); -const toolbox = require('./tools/toolbox'); -const TestUser = require('./tools/TestUser').TestUser -const unit = require('./tools/unit'); -const httpTest = require('./tools/http'); /** * Test Crosschain algorithm described at https://en.bitcoin.it/wiki/Atomic_cross-chain_trading @@ -44,12 +41,11 @@ describe("Crosschain transactions", function() { describe('Successfull transaction', () => { - let sB, sM, tocB, tocM, ticB, ticM, btx0, mtx0; // Source transactions for coins + let sB:TestingServer, sM:TestingServer, tocB:TestUser, tocM:TestUser, ticB:TestUser, ticM:TestUser, btx0:string, mtx0:string; // Source transactions for coins - before(() => co(function *() { + before(async () => { - - sB = toolbox.server(_.extend({ + sB = NewTestingServer(Underscore.extend({ memory: MEMORY_MODE, name: 'bb11', currency: 'BETA_BROUZOUF', @@ -59,7 +55,7 @@ describe("Crosschain transactions", function() { } }, commonConf)); - sM = toolbox.server(_.extend({ + sM = NewTestingServer(Underscore.extend({ memory: MEMORY_MODE, name: 'bb12', currency: 'META_BROUZOUF', @@ -76,44 +72,43 @@ describe("Crosschain transactions", function() { ticB = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: sB }); ticM = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: sM }); - yield sB.initDalBmaConnections(); - yield sM.initDalBmaConnections(); - - // Initialize BETA - yield ticB.createIdentity(); - yield tocB.createIdentity(); - yield tocB.cert(ticB); - yield ticB.cert(tocB); - yield ticB.join(); - yield tocB.join(); - yield commit(sB)({ time: now }); - yield commit(sB)({ time: now + 10 }); - yield commit(sB)({ time: now + 10 }); - // Preparation: we create a source transaction for our transfer - btx0 = yield tocB.prepareITX(120, tocB); - // We submit it to the network - yield tocB.sendTX(btx0); - // Written - yield commit(sB)({ time: now + 10 }); - - // Initialize META - yield ticM.createIdentity(); - yield tocM.createIdentity(); - yield tocM.cert(ticM); - yield ticM.cert(tocM); - yield ticM.join(); - yield tocM.join(); - yield commit(sM)({ time: now }); - yield commit(sM)({ time: now + 10 }); - yield commit(sM)({ time: now + 10 }); - // Preparation: we create a source transaction for our transfer - mtx0 = yield ticM.prepareITX(120, ticM); - // We submit it to the network - yield ticM.sendTX(mtx0); - // Written - yield commit(sM)({ time: now + 10 }); - }) - ); + await sB.initDalBmaConnections(); + await sM.initDalBmaConnections(); + + // Initialize BETA + await ticB.createIdentity(); + await tocB.createIdentity(); + await tocB.cert(ticB); + await ticB.cert(tocB); + await ticB.join(); + await tocB.join(); + await sB.commit({ time: now }); + await sB.commit({ time: now + 10 }); + await sB.commit({ time: now + 10 }); + // Preparation: we create a source transaction for our transfer + btx0 = await tocB.prepareITX(120, tocB); + // We submit it to the network + await tocB.sendTX(btx0); + // Written + await sB.commit({ time: now + 10 }); + + // Initialize META + await ticM.createIdentity(); + await tocM.createIdentity(); + await tocM.cert(ticM); + await ticM.cert(tocM); + await ticM.join(); + await tocM.join(); + await sM.commit({ time: now }); + await sM.commit({ time: now + 10 }); + await sM.commit({ time: now + 10 }); + // Preparation: we create a source transaction for our transfer + mtx0 = await ticM.prepareITX(120, ticM); + // We submit it to the network + await ticM.sendTX(mtx0); + // Written + await sM.commit({ time: now + 10 }); + }) after(() => { return Promise.all([ @@ -131,35 +126,35 @@ describe("Crosschain transactions", function() { describe('Transfering', () => { - it("commit", () => co(function *() { + it("commit", async () => { - const currentB = yield sB.get('/blockchain/current'); + const currentB = await sB.get('/blockchain/current'); const blockstampB = [currentB.number, currentB.hash].join('-'); - const currentM = yield sM.get('/blockchain/current'); + const currentM = await sM.get('/blockchain/current'); const blockstampM = [currentM.number, currentM.hash].join('-'); // TOCB side (BETA) // 1. toc secretely chooses X password - let btx1 = yield tocB.prepareUTX(btx0, ['SIG(0)'], [{ qty: 120, base: 0, lock: '(XHX(8AFC8DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB) && SIG(' + ticB.pub + ')) || (SIG(' + tocB.pub + ') && SIG(' + ticB.pub + '))' }], { comment: 'BETA toc to tic', blockstamp: blockstampB }); + let btx1 = await tocB.prepareUTX(btx0, ['SIG(0)'], [{ qty: 120, base: 0, lock: '(XHX(8AFC8DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB) && SIG(' + ticB.pub + ')) || (SIG(' + tocB.pub + ') && SIG(' + ticB.pub + '))' }], { comment: 'BETA toc to tic', blockstamp: blockstampB }); // 2. toc makes a rollback transaction from tx1, signed by both parties (through internet): toc and tic - let btx2 = yield tocB.prepareMTX(btx1, ticB, ['XHX(0) SIG(1) SIG(0) SIG(1)'], [{ qty: 120, base: 0, lock: 'SIG(' + tocB.pub + ')' }], { comment: 'money back to tocB in 48h', locktime: 3600 * 48, blockstamp: blockstampB }); // N.B.: locktime should be like 48h in real world + let btx2 = await tocB.prepareMTX(btx1, ticB, ['XHX(0) SIG(1) SIG(0) SIG(1)'], [{ qty: 120, base: 0, lock: 'SIG(' + tocB.pub + ')' }], { comment: 'money back to tocB in 48h', locktime: 3600 * 48, blockstamp: blockstampB }); // N.B.: locktime should be like 48h in real world // TICM side (META) // 3. tic generates a transaction based on H(X) given by toc (through internet) - let mtx3 = yield ticM.prepareUTX(mtx0, ['SIG(0)'], [{ qty: 120, base: 0, lock: '(XHX(8AFC8DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB) && SIG(' + tocM.pub + ')) || (SIG(' + ticM.pub + ') && SIG(' + tocM.pub + '))' }], { comment: 'META tic to toc', blockstamp: blockstampM }); + let mtx3 = await ticM.prepareUTX(mtx0, ['SIG(0)'], [{ qty: 120, base: 0, lock: '(XHX(8AFC8DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB) && SIG(' + tocM.pub + ')) || (SIG(' + ticM.pub + ') && SIG(' + tocM.pub + '))' }], { comment: 'META tic to toc', blockstamp: blockstampM }); // 4. tic makes a rollback transaction from tx1, signed by both parties: toc and tic - let mtx4 = yield ticM.prepareMTX(mtx3, tocM, ['XHX(0) SIG(1) SIG(0) SIG(1)'], [{ qty: 120, base: 0, lock: 'SIG(' + ticM.pub + ')' }], { comment: 'money back to ticM', locktime: 3600 * 24, blockstamp: blockstampM }); // N.B.: locktime should be like 24h in real world + let mtx4 = await ticM.prepareMTX(mtx3, tocM, ['XHX(0) SIG(1) SIG(0) SIG(1)'], [{ qty: 120, base: 0, lock: 'SIG(' + ticM.pub + ')' }], { comment: 'money back to ticM', locktime: 3600 * 24, blockstamp: blockstampM }); // N.B.: locktime should be like 24h in real world // We submit TX1 to the network & write it - yield tocB.sendTX(btx1); + await tocB.sendTX(btx1); // Written - yield commit(sB)({ time: now + 10 }); + await sB.commit({ time: now + 10 }); // We submit TX3 to the network & write it - yield ticM.sendTX(mtx3); + await ticM.sendTX(mtx3); // Written - yield commit(sM)({ time: now + 10 }); + await sM.commit({ time: now + 10 }); /** * So now ... parties can either COMMIT or ROLLBACK. It's UP to the initiator: TOC. @@ -168,58 +163,58 @@ describe("Crosschain transactions", function() { /** * Note: the ROLLBACK transactions have a locktime, and cannot be used before that delay. */ - yield unit.shouldFail(ticM.sendTX(mtx4), 'Locktime not elapsed yet'); - yield unit.shouldFail(tocB.sendTX(btx2), 'Locktime not elapsed yet'); + await shouldFail(ticM.sendTX(mtx4), 'Locktime not elapsed yet'); + await shouldFail(tocB.sendTX(btx2), 'Locktime not elapsed yet'); /** * Let's say TOC agrees & and start COMMIT. */ // TOCM consumes TICM's offered money by revealing the password + signing - let mtx5 = yield tocM.prepareUTX(mtx3, ['XHX(1872767826647264) SIG(0)'], [{ qty: 120, base: 0, lock: 'SIG(' + tocM.pub + ')' }], { comment: 'toc takes money on META_BROUZOUF', blockstamp: blockstampM }); - yield tocM.sendTX(mtx5); + let mtx5 = await tocM.prepareUTX(mtx3, ['XHX(1872767826647264) SIG(0)'], [{ qty: 120, base: 0, lock: 'SIG(' + tocM.pub + ')' }], { comment: 'toc takes money on META_BROUZOUF', blockstamp: blockstampM }); + await tocM.sendTX(mtx5); // Written - yield commit(sM)(); + await sM.commit(); // But now X is revealed: TAC can takes the money offered in TX1 by TOCB - let btx6 = yield ticB.prepareUTX(btx1, ['XHX(1872767826647264) SIG(0)'], [{ qty: 120, base: 0, lock: 'SIG(' + ticB.pub + ')' }], { comment: 'tic takes money on BETA_BROUZOUF', blockstamp: blockstampB }); - yield ticB.sendTX(btx6); + let btx6 = await ticB.prepareUTX(btx1, ['XHX(1872767826647264) SIG(0)'], [{ qty: 120, base: 0, lock: 'SIG(' + ticB.pub + ')' }], { comment: 'tic takes money on BETA_BROUZOUF', blockstamp: blockstampB }); + await ticB.sendTX(btx6); // Written - yield commit(sB)(); + await sB.commit(); /** * Now the transaction is fully COMMITTED! Look at rollback transactions: they will fail. */ - yield unit.shouldFail(tocB.sendTX(btx2), 'Source already consumed'); - yield unit.shouldFail(ticM.sendTX(mtx4), 'Source already consumed'); - })); + await shouldFail(tocB.sendTX(btx2), 'Source already consumed'); + await shouldFail(ticM.sendTX(mtx4), 'Source already consumed'); + }) it('toc should now have 0 BETA_BROUZOUF from Transaction sources due to COMMIT', () => { - return sB.expect('/tx/sources/' + tocB.pub, (res) => { - const txRes = _.filter(res.sources, { type: 'T' }); + return sB.expect('/tx/sources/' + tocB.pub, (res:HttpSources) => { + const txRes = Underscore.where(res.sources, { type: 'T' }); txRes.should.have.length(0); }); }); it('toc should now have 120 META_BROUZOUF from Transaction sources due to COMMIT', () => { - return sM.expect('/tx/sources/' + tocB.pub, (res) => { - const txRes = _.filter(res.sources, { type: 'T' }); + return sM.expect('/tx/sources/' + tocB.pub, (res:HttpSources) => { + const txRes = Underscore.where(res.sources, { type: 'T' }); txRes.should.have.length(1); assert.equal(txRes[0].amount, 120); }); }); it('tic should now have 0 META_BROUZOUF from Transaction sources due to COMMMIT', () => { - return sM.expect('/tx/sources/' + ticM.pub, (res) => { - const txRes = _.filter(res.sources, { type: 'T' }); + return sM.expect('/tx/sources/' + ticM.pub, (res:HttpSources) => { + const txRes = Underscore.where(res.sources, { type: 'T' }); txRes.should.have.length(0); }); }); it('tic should have 120 BETA_BROUZOUF from Transaction sources due to COMMIT', () => { - return sB.expect('/tx/sources/' + ticM.pub, (res) => { - const txRes = _.filter(res.sources, { type: 'T' }); + return sB.expect('/tx/sources/' + ticM.pub, (res:HttpSources) => { + const txRes = Underscore.where(res.sources, { type: 'T' }); txRes.should.have.length(1); assert.equal(txRes[0].amount, 120); }); @@ -229,76 +224,73 @@ describe("Crosschain transactions", function() { describe('Rollbacked transaction', () => { - let sB, sM, tocB, tocM, ticB, ticM, btx0, mtx0; // Source transactions for coins - - before(function() { - - return co(function *() { - - sB = toolbox.server(_.extend({ - memory: MEMORY_MODE, - name: 'bb11', - currency: 'BETA_BROUZOUF2', - pair: { - pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', - sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F' - } - }, commonConf)); - - sM = toolbox.server(_.extend({ - memory: MEMORY_MODE, - name: 'bb12', - currency: 'META_BROUZOUF2', - pair: { - pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', - sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7' - } - }, commonConf)); - - // toc is on 2 currencies - tocB = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: sB }); - tocM = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: sM }); - // tic is on 2 currencies - ticB = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: sB }); - ticM = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: sM }); - - yield sB.initDalBmaConnections(); - yield sM.initDalBmaConnections() - - // Initialize BETA - yield ticB.createIdentity(); - yield tocB.createIdentity(); - yield tocB.cert(ticB); - yield ticB.cert(tocB); - yield ticB.join(); - yield tocB.join(); - yield commit(sB)({ time: now }); - yield commit(sB)({ time: now + 10 }); - yield commit(sB)({ time: now + 10 }); - // Preparation: we create a source transaction for our transfer - btx0 = yield tocB.prepareITX(120, tocB); - // We submit it to the network - yield tocB.sendTX(btx0); - // Written - yield commit(sB)({ time: now + 10 }); - - // Initialize META - yield ticM.createIdentity(); - yield tocM.createIdentity(); - yield tocM.cert(ticM); - yield ticM.cert(tocM); - yield ticM.join(); - yield tocM.join(); - yield commit(sM)({ time: now }); - yield commit(sM)({ time: now + 10 }); - yield commit(sM)({ time: now + 10 }); - // Preparation: we create a source transaction for our transfer - mtx0 = yield ticM.prepareITX(120, ticM); - // We submit it to the network - yield ticM.sendTX(mtx0); - // Written - yield commit(sM)({ time: now + 10 }); - }); + let sB:TestingServer, sM:TestingServer, tocB:TestUser, tocM:TestUser, ticB:TestUser, ticM:TestUser, btx0:string, mtx0:string; // Source transactions for coins + + before(async () => { + + sB = NewTestingServer(Underscore.extend({ + memory: MEMORY_MODE, + name: 'bb11', + currency: 'BETA_BROUZOUF2', + pair: { + pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', + sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F' + } + }, commonConf)) + + sM = NewTestingServer(Underscore.extend({ + memory: MEMORY_MODE, + name: 'bb12', + currency: 'META_BROUZOUF2', + pair: { + pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', + sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7' + } + }, commonConf)); + + // toc is on 2 currencies + tocB = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: sB }); + tocM = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: sM }); + // tic is on 2 currencies + ticB = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: sB }); + ticM = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: sM }); + + await sB.initDalBmaConnections(); + await sM.initDalBmaConnections() + + // Initialize BETA + await ticB.createIdentity(); + await tocB.createIdentity(); + await tocB.cert(ticB); + await ticB.cert(tocB); + await ticB.join(); + await tocB.join(); + await sB.commit({ time: now }); + await sB.commit({ time: now + 10 }); + await sB.commit({ time: now + 10 }); + // Preparation: we create a source transaction for our transfer + btx0 = await tocB.prepareITX(120, tocB); + // We submit it to the network + await tocB.sendTX(btx0); + // Written + await sB.commit({ time: now + 10 }); + + // Initialize META + await ticM.createIdentity(); + await tocM.createIdentity(); + await tocM.cert(ticM); + await ticM.cert(tocM); + await ticM.join(); + await tocM.join(); + await sM.commit({ time: now }); + await sM.commit({ time: now + 10 }); + await sM.commit({ time: now + 10 }); + // Preparation: we create a source transaction for our transfer + mtx0 = await ticM.prepareITX(120, ticM); + // We submit it to the network + await ticM.sendTX(mtx0); + // Written + await sM.commit({ time: now + 10 }); }); after(() => { @@ -317,35 +309,35 @@ describe("Crosschain transactions", function() { describe('Transfering', () => { - it("commit", () => co(function *() { + it("commit", async () => { - const currentB = yield sB.get('/blockchain/current'); + const currentB = await sB.get('/blockchain/current'); const blockstampB = [currentB.number, currentB.hash].join('-'); - const currentM = yield sM.get('/blockchain/current'); + const currentM = await sM.get('/blockchain/current'); const blockstampM = [currentM.number, currentM.hash].join('-'); // TOCB side (BETA) // 1. toc secretely chooses X password - let btx1 = yield tocB.prepareUTX(btx0, ['SIG(0)'], [{ qty: 120, base: 0, lock: '(XHX(8AFC8DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB) && SIG(' + ticB.pub + ')) || (SIG(' + tocB.pub + ') && SIG(' + ticB.pub + '))' }], { comment: 'BETA toc to tic', blockstamp: blockstampB }); + let btx1 = await tocB.prepareUTX(btx0, ['SIG(0)'], [{ qty: 120, base: 0, lock: '(XHX(8AFC8DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB) && SIG(' + ticB.pub + ')) || (SIG(' + tocB.pub + ') && SIG(' + ticB.pub + '))' }], { comment: 'BETA toc to tic', blockstamp: blockstampB }); // 2. toc makes a rollback transaction from tx1, signed by both parties (through internet): toc and tic - let btx2 = yield tocB.prepareMTX(btx1, ticB, ['SIG(0) SIG(1)'], [{ qty: 120, base: 0, lock: 'SIG(' + tocB.pub + ')' }], { comment: 'money back to tocB in 48h', locktime: 3, blockstamp: blockstampB }); // N.B.: locktime should be like 48h in real world + let btx2 = await tocB.prepareMTX(btx1, ticB, ['SIG(0) SIG(1)'], [{ qty: 120, base: 0, lock: 'SIG(' + tocB.pub + ')' }], { comment: 'money back to tocB in 48h', locktime: 3, blockstamp: blockstampB }); // N.B.: locktime should be like 48h in real world // TICM side (META) // 3. tic generates a transaction based on H(X) given by toc (through internet) - let mtx3 = yield ticM.prepareUTX(mtx0, ['SIG(0)'], [{ qty: 120, base: 0, lock: '(XHX(8AFC8DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB) && SIG(' + tocM.pub + ')) || (SIG(' + ticM.pub + ') && SIG(' + tocM.pub + '))' }], { comment: 'META tic to toc', blockstamp: blockstampM }); + let mtx3 = await ticM.prepareUTX(mtx0, ['SIG(0)'], [{ qty: 120, base: 0, lock: '(XHX(8AFC8DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB) && SIG(' + tocM.pub + ')) || (SIG(' + ticM.pub + ') && SIG(' + tocM.pub + '))' }], { comment: 'META tic to toc', blockstamp: blockstampM }); // 4. tic makes a rollback transaction from tx1, signed by both parties: toc and tic - let mtx4 = yield ticM.prepareMTX(mtx3, tocM, ['SIG(0) SIG(1)'], [{ qty: 120, base: 0, lock: 'SIG(' + ticM.pub + ')' }], { comment: 'money back to ticM', locktime: 2, blockstamp: blockstampM }); // N.B.: locktime should be like 24h in real world + let mtx4 = await ticM.prepareMTX(mtx3, tocM, ['SIG(0) SIG(1)'], [{ qty: 120, base: 0, lock: 'SIG(' + ticM.pub + ')' }], { comment: 'money back to ticM', locktime: 2, blockstamp: blockstampM }); // N.B.: locktime should be like 24h in real world // We submit TX1 to the network & write it - yield tocB.sendTX(btx1); + await tocB.sendTX(btx1); // Written - yield commit(sB)({ time: now + 12 }); + await sB.commit({ time: now + 12 }); // We submit TX3 to the network & write it - yield ticM.sendTX(mtx3); + await ticM.sendTX(mtx3); // Written - yield commit(sM)({ time: now + 12 }); + await sM.commit({ time: now + 12 }); /** * So now ... parties can either COMMIT or ROLLBACK. It's UP to the initiator: TOC. @@ -356,50 +348,50 @@ describe("Crosschain transactions", function() { /** * Note: the ROLLBACK transactions have a locktime, and cannot be used before that delay. */ - yield unit.shouldFail(ticM.sendTX(mtx4), 'Locktime not elapsed yet'); - yield unit.shouldFail(tocB.sendTX(btx2), 'Locktime not elapsed yet'); + await shouldFail(ticM.sendTX(mtx4), 'Locktime not elapsed yet'); + await shouldFail(tocB.sendTX(btx2), 'Locktime not elapsed yet'); // Increment the medianTime by 1 - yield commit(sM)({ time: now + 12 }); - yield commit(sB)({ time: now + 14 }); + await sM.commit({ time: now + 12 }); + await sB.commit({ time: now + 14 }); - yield unit.shouldNotFail(ticM.sendTX(mtx4)); // tic can rollback early (24h in real case) if toc does not reveal X - yield unit.shouldFail(tocB.sendTX(btx2), 'Locktime not elapsed yet'); // This one has a longer locktime (48h in real case) + await shouldNotFail(ticM.sendTX(mtx4)); // tic can rollback early (24h in real case) if toc does not reveal X + await shouldFail(tocB.sendTX(btx2), 'Locktime not elapsed yet'); // This one has a longer locktime (48h in real case) // Rollback for TIC(M) should be done - yield commit(sM)({ time: now + 12 }); + await sM.commit({ time: now + 12 }); // Make the medianTime increment by 1 - yield commit(sB)({ time: now + 14 }); + await sB.commit({ time: now + 14 }); - yield unit.shouldNotFail(tocB.sendTX(btx2)); // toc can rollback now (48h has passed). He has not revealed X, so he is safe. - yield commit(sB)({ time: now + 14 }); + await shouldNotFail(tocB.sendTX(btx2)); // toc can rollback now (48h has passed). He has not revealed X, so he is safe. + await sB.commit({ time: now + 14 }); /** * Now the transaction is fully COMMITTED! Look at rollback transactions: they will fail. */ // TOCM consumes TICM's offered money by revealing the password + signing - let mtx5 = yield tocM.prepareUTX(mtx3, ['XHX(1872767826647264) SIG(0)'], [{ qty: 120, base: 0, lock: 'SIG(' + tocM.pub + ')' }], { comment: 'toc takes money on META_BROUZOUF', blockstamp: blockstampM }); + let mtx5 = await tocM.prepareUTX(mtx3, ['XHX(1872767826647264) SIG(0)'], [{ qty: 120, base: 0, lock: 'SIG(' + tocM.pub + ')' }], { comment: 'toc takes money on META_BROUZOUF', blockstamp: blockstampM }); // Assuming X was revealed ... but actually it is not since TOCM did succeed to send the TX - let btx6 = yield ticB.prepareUTX(btx1, ['XHX(1872767826647264) SIG(0)'], [{ qty: 120, base: 0, lock: 'SIG(' + ticB.pub + ')' }], { comment: 'tic takes money on BETA_BROUZOUF', blockstamp: blockstampB }); + let btx6 = await ticB.prepareUTX(btx1, ['XHX(1872767826647264) SIG(0)'], [{ qty: 120, base: 0, lock: 'SIG(' + ticB.pub + ')' }], { comment: 'tic takes money on BETA_BROUZOUF', blockstamp: blockstampB }); - yield unit.shouldFail(tocB.sendTX(btx6), 'Source already consumed'); - yield unit.shouldFail(ticM.sendTX(mtx5), 'Source already consumed'); - })); + await shouldFail(tocB.sendTX(btx6), 'Source already consumed'); + await shouldFail(ticM.sendTX(mtx5), 'Source already consumed'); + }) it('toc should now have 120 BETA_BROUZOUF from Transaction sources due to rollback TX', () => checkHaveSources(tocB, 1, 120)); it('tic should now have 120 META_BROUZOUF from Transaction sources due to rollback TX', () => checkHaveSources(ticM, 1, 120)); it('toc should now have 0 META_BROUZOUF from Transaction sources', () => checkHaveSources(tocM, 0, 0)); it('tic should now have 0 BETA_BROUZOUF from Transaction sources', () => checkHaveSources(ticB, 0, 0)); - }); + }) }); }); -function checkHaveSources(theUser, sourcesCount, sourcesTotalAmount) { - return httpTest.expectAnswer(rp('http://' + theUser.node.server.conf.ipv4 + ':' + theUser.node.server.conf.port + '/tx/sources/' + theUser.pub, { json: true }), (res) => { - const txRes = _.filter(res.sources, { type: 'T' }); +function checkHaveSources(theUser:TestUser, sourcesCount:number, sourcesTotalAmount:number) { + return expectAnswer(rp('http://' + theUser.node.server.conf.ipv4 + ':' + theUser.node.server.conf.port + '/tx/sources/' + theUser.pub, { json: true }), (res:HttpSources) => { + const txRes = Underscore.where(res.sources, { type: 'T' }) txRes.should.have.length(sourcesCount); let sum = 0; for (const result of txRes) { diff --git a/test/integration/transactions-cltv.js b/test/integration/transactions/transactions-cltv.ts similarity index 50% rename from test/integration/transactions-cltv.js rename to test/integration/transactions/transactions-cltv.ts index 4722ef30b60416835d0ddd2e56451a0c6fdec288..4e5515d33d84a270a19a1a1816d58ddcb44fa506 100644 --- a/test/integration/transactions-cltv.js +++ b/test/integration/transactions/transactions-cltv.ts @@ -11,18 +11,12 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {simpleNodeWith2Users, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {shouldFail, shouldNotFail} from "../../unit-tools" -const co = require('co'); -const _ = require('underscore'); const should = require('should'); const assert = require('assert'); -const constants = require('../../app/lib/constants'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const toolbox = require('./tools/toolbox'); -const node = require('./tools/node'); -const unit = require('./tools/unit'); -const http = require('./tools/http'); const now = 1480000000; @@ -33,18 +27,18 @@ const conf = { medianTimeBlocks: 1 // Easy: medianTime(b) = time(b-1) }; -let s1, cat, tac +let s1:TestingServer, cat:TestUser, tac:TestUser -describe("Transactions: CLTV", function() { +describe("Transactions: CLTV", () => { - before(() => co(function*() { - const res = yield toolbox.simpleNodeWith2Users(conf); + before(async () => { + const res = await simpleNodeWith2Users(conf); s1 = res.s1; cat = res.cat; tac = res.tac; - yield s1.commit({ time: now }); - yield s1.commit({ time: now + 1 }); - })); + await s1.commit({ time: now }); + await s1.commit({ time: now + 1 }); + }) after(() => { return Promise.all([ @@ -52,27 +46,27 @@ describe("Transactions: CLTV", function() { ]) }) - it('it should exist block#1 with UD of 200', () => s1.expect('/blockchain/block/1', (block) => { + it('it should exist block#1 with UD of 200', () => s1.expect('/blockchain/block/1', (block:any) => { should.exists(block); assert.equal(block.number, 1); assert.equal(block.dividend, 200); })); - it('with SIG and CLTV', () => co(function *() { - let tx1 = yield cat.prepareITX(200, tac); - yield unit.shouldNotFail(cat.sendTX(tx1)); - yield s1.commit({ time: now + 19 }); // TODO: why not in the same block? - let current = yield s1.get('/blockchain/current'); - let tx2 = yield tac.prepareUTX(tx1, ['SIG(0)'], [{ qty: 200, base: 0, lock: 'SIG(' + cat.pub + ') && CLTV(1480000022)' }], { + it('with SIG and CLTV', async () => { + let tx1 = await cat.prepareITX(200, tac); + await shouldNotFail(cat.sendTX(tx1)); + await s1.commit({ time: now + 19 }); // TODO: why not in the same block? + let current = await s1.get('/blockchain/current'); + let tx2 = await tac.prepareUTX(tx1, ['SIG(0)'], [{ qty: 200, base: 0, lock: 'SIG(' + cat.pub + ') && CLTV(1480000022)' }], { comment: 'must wait until time 1480000022', blockstamp: [current.number, current.hash].join('-') }); - yield unit.shouldNotFail(cat.sendTX(tx2)); - yield s1.commit({ time: now + 21 }); // TODO: why not in the same block? - let tx3 = yield cat.prepareITX(200, tac); - yield unit.shouldFail(cat.sendTX(tx3), 'Wrong unlocker in transaction'); - yield s1.commit({ time: now + 22 }); - yield unit.shouldNotFail(cat.sendTX(tx3)); // Because next block will have medianTime = 22 - yield s1.commit({ time: now + 22 }); - })); -}); + await shouldNotFail(cat.sendTX(tx2)); + await s1.commit({ time: now + 21 }); // TODO: why not in the same block? + let tx3 = await cat.prepareITX(200, tac); + await shouldFail(cat.sendTX(tx3), 'Wrong unlocker in transaction'); + await s1.commit({ time: now + 22 }); + await shouldNotFail(cat.sendTX(tx3)); // Because next block will have medianTime = 22 + await s1.commit({ time: now + 22 }); + }) +}) diff --git a/test/integration/transactions-csv.js b/test/integration/transactions/transactions-csv.ts similarity index 50% rename from test/integration/transactions-csv.js rename to test/integration/transactions/transactions-csv.ts index 31d5e912703daf1ed970d10d761b35e49c65edf8..2b3783e157bc2a2fad5bb01bb7cf70be74af0313 100644 --- a/test/integration/transactions-csv.js +++ b/test/integration/transactions/transactions-csv.ts @@ -11,18 +11,12 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {simpleNodeWith2Users, TestingServer} from "../tools/toolbox" +import {TestUser} from "../tools/TestUser" +import {shouldFail, shouldNotFail} from "../../unit-tools" -const co = require('co'); -const _ = require('underscore'); const should = require('should'); const assert = require('assert'); -const constants = require('../../app/lib/constants'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const toolbox = require('./tools/toolbox'); -const node = require('./tools/node'); -const unit = require('./tools/unit'); -const http = require('./tools/http'); const now = 1480000000; @@ -33,18 +27,18 @@ const conf = { medianTimeBlocks: 1 // Easy: medianTime(b) = time(b-1) }; -let s1, cat, tac +let s1:TestingServer, cat:TestUser, tac:TestUser -describe("Transactions: CSV", function() { +describe("Transactions: CSV", () => { - before(() => co(function*() { - const res = yield toolbox.simpleNodeWith2Users(conf); + before(async () => { + const res = await simpleNodeWith2Users(conf); s1 = res.s1; cat = res.cat; tac = res.tac; - yield s1.commit({ time: now }); - yield s1.commit({ time: now + 1 }); - })); + await s1.commit({ time: now }); + await s1.commit({ time: now + 1 }); + }) after(() => { return Promise.all([ @@ -52,27 +46,27 @@ describe("Transactions: CSV", function() { ]) }) - it('it should exist block#1 with UD of 200', () => s1.expect('/blockchain/block/1', (block) => { + it('it should exist block#1 with UD of 200', () => s1.expect('/blockchain/block/1', (block:any) => { should.exists(block); assert.equal(block.number, 1); assert.equal(block.dividend, 200); })); - it('with SIG and CSV', () => co(function *() { - let tx1 = yield cat.prepareITX(200, tac); - yield unit.shouldNotFail(cat.sendTX(tx1)); - yield s1.commit({ time: now + 19 }); // TODO: why not in the same block? - let current = yield s1.get('/blockchain/current'); - let tx2 = yield tac.prepareUTX(tx1, ['SIG(0)'], [{ qty: 200, base: 0, lock: 'SIG(' + cat.pub + ') && CSV(20)' }], { + it('with SIG and CSV', async () => { + let tx1 = await cat.prepareITX(200, tac); + await shouldNotFail(cat.sendTX(tx1)); + await s1.commit({ time: now + 19 }); // TODO: why not in the same block? + let current = await s1.get('/blockchain/current'); + let tx2 = await tac.prepareUTX(tx1, ['SIG(0)'], [{ qty: 200, base: 0, lock: 'SIG(' + cat.pub + ') && CSV(20)' }], { comment: 'must wait 20 seconds', blockstamp: [current.number, current.hash].join('-') }); - yield unit.shouldNotFail(cat.sendTX(tx2)); - yield s1.commit({ time: now + 38 }); // TODO: why not in the same block? - let tx3 = yield cat.prepareITX(200, tac); - yield unit.shouldFail(cat.sendTX(tx3), 'Wrong unlocker in transaction'); - yield s1.commit({ time: now + 39 }); - yield unit.shouldNotFail(cat.sendTX(tx3)); // Because next block will have medianTime = 39 - yield s1.commit({ time: now + 39 }); - })); -}); + await shouldNotFail(cat.sendTX(tx2)); + await s1.commit({ time: now + 38 }); // TODO: why not in the same block? + let tx3 = await cat.prepareITX(200, tac); + await shouldFail(cat.sendTX(tx3), 'Wrong unlocker in transaction'); + await s1.commit({ time: now + 39 }); + await shouldNotFail(cat.sendTX(tx3)); // Because next block will have medianTime = 39 + await s1.commit({ time: now + 39 }); + }) +}) diff --git a/test/integration/transactions-pruning.js b/test/integration/transactions/transactions-pruning.ts similarity index 63% rename from test/integration/transactions-pruning.js rename to test/integration/transactions/transactions-pruning.ts index 617d5c5491bcd224248fa2f6feef049d4f4d741c..c680ab8c97aa8c41fcb7b97819a0c23c7d964200 100644 --- a/test/integration/transactions-pruning.js +++ b/test/integration/transactions/transactions-pruning.ts @@ -11,23 +11,20 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {TestUser} from "../tools/TestUser" +import {CommonConstants} from "../../../app/lib/common-libs/constants" +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {HttpBlock, HttpTxHistory} from "../../../app/modules/bma/lib/dtos" -const co = require('co'); const should = require('should'); -const TestUser = require('./tools/TestUser').TestUser -const commit = require('./tools/commit'); -const toolbox = require('./tools/toolbox'); -const constants = require('../../app/lib/constants'); -const CommonConstants = require('../../app/lib/common-libs/constants').CommonConstants -let s1, cat1, tac1 +let s1:TestingServer, cat1:TestUser, tac1:TestUser describe("Transactions pruning", function() { - before(() => co(function*() { + before(async () => { - s1 = toolbox.server({ + s1 = NewTestingServer({ currency: 'currency_one', dt: 600, pair: { @@ -39,25 +36,25 @@ describe("Transactions pruning", function() { cat1 = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); tac1 = new TestUser('tac', { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'}, { server: s1 }); - yield s1.prepareForNetwork(); + await s1.prepareForNetwork(); - const now = parseInt(Date.now() / 1000); + const now = parseInt(String(Date.now() / 1000)) // Publishing identities - yield cat1.createIdentity(); - yield tac1.createIdentity(); - yield cat1.cert(tac1); - yield tac1.cert(cat1); - yield cat1.join(); - yield tac1.join(); - yield s1.commit(); - yield s1.commit({ + await cat1.createIdentity(); + await tac1.createIdentity(); + await cat1.cert(tac1); + await tac1.cert(cat1); + await cat1.join(); + await tac1.join(); + await s1.commit(); + await s1.commit({ time: now + 1300 }); - yield s1.commit(); - yield cat1.send(20, tac1); - yield cat1.send(100, tac1); - })); + await s1.commit(); + await cat1.sendMoney(20, tac1) + await cat1.sendMoney(100, tac1) + }) after(() => { return Promise.all([ @@ -65,27 +62,27 @@ describe("Transactions pruning", function() { ]) }) - it('double spending transactions should both exist first', () => s1.expect('/tx/history/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', (res) => { + it('double spending transactions should both exist first', () => s1.expect('/tx/history/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', (res:HttpTxHistory) => { res.history.should.have.property('sending').length(2); })); - it('should only commit 1 tx', () => co(function*() { - yield s1.commit(); - yield s1.expect('/blockchain/block/2', (res) => { + it('should only commit 1 tx', async () => { + await s1.commit(); + await s1.expect('/blockchain/block/2', (res:HttpBlock) => { res.should.have.property('transactions').length(0); }); - yield s1.expect('/blockchain/block/3', (res) => { + await s1.expect('/blockchain/block/3', (res:HttpBlock) => { res.should.have.property('transactions').length(1); }); - })); + }) - it('double spending transaction should have been pruned', () => co(function*() { + it('double spending transaction should have been pruned', async () => { const tmp = CommonConstants.TRANSACTION_MAX_TRIES; CommonConstants.TRANSACTION_MAX_TRIES = 1; - yield s1.commit(); - yield s1.expect('/tx/history/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', (res) => { + await s1.commit(); + await s1.expect('/tx/history/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', (res:HttpTxHistory) => { res.history.should.have.property('sending').length(0); }); CommonConstants.TRANSACTION_MAX_TRIES = tmp; - })); -}); + }) +}) diff --git a/test/integration/transactions-test.js b/test/integration/transactions/transactions-test.ts similarity index 58% rename from test/integration/transactions-test.js rename to test/integration/transactions/transactions-test.ts index 3860f0f8ecd9bc790b39000954582241ea09dab8..3b04c54ea8d7105e90d210aabd97880fc2c677b6 100644 --- a/test/integration/transactions-test.js +++ b/test/integration/transactions/transactions-test.ts @@ -11,30 +11,23 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -"use strict"; +import {TestUser} from "../tools/TestUser" +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {Underscore} from "../../../app/lib/common-libs/underscore" +import {shouldFail, shouldNotFail} from "../../unit-tools" -const co = require('co'); -const _ = require('underscore'); const should = require('should'); const assert = require('assert'); -const constants = require('../../app/lib/constants'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const toolbox = require('./tools/toolbox'); -const node = require('./tools/node'); -const TestUser = require('./tools/TestUser').TestUser -const unit = require('./tools/unit'); -const http = require('./tools/http'); - describe("Testing transactions", function() { const now = 1490000000; - let s1, tic, toc + let s1:TestingServer, tic:TestUser, toc:TestUser - before(() => co(function*() { + before(async () => { - s1 = toolbox.server({ + s1 = NewTestingServer({ pair: { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7' @@ -51,32 +44,32 @@ describe("Testing transactions", function() { tic = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s1 }); toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); - yield s1.initDalBmaConnections(); + await s1.initDalBmaConnections(); // Self certifications - yield tic.createIdentity(); - yield toc.createIdentity(); + await tic.createIdentity(); + await toc.createIdentity(); // Certification; - yield tic.cert(toc); - yield toc.cert(tic); - yield tic.join(); - yield toc.join(); - yield s1.commit({ time: now }); - yield s1.commit({ + await tic.cert(toc); + await toc.cert(tic); + await tic.join(); + await toc.join(); + await s1.commit({ time: now }); + await s1.commit({ time: now + 7210 }); - yield s1.commit({ + await s1.commit({ time: now + 7210 }); - yield tic.sendP(510, toc); - yield s1.expect('/tx/history/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', (res) => { + await tic.sendMoney(510, toc); + await s1.expect('/tx/history/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', (res:any) => { res.should.have.property('pubkey').equal('DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo'); res.should.have.property('history').property('pending').length(1); res.history.pending[0].should.have.property('received').be.a.Number; }); - yield s1.commit({ + await s1.commit({ time: now + 7220 }); - })); + }) after(() => { return Promise.all([ @@ -86,21 +79,21 @@ describe("Testing transactions", function() { describe("Sources", function(){ - it('it should exist block#2 with UD of 1200', () => s1.expect('/blockchain/block/2', (block) => { + it('it should exist block#2 with UD of 1200', () => s1.expect('/blockchain/block/2', (block:any) => { should.exists(block); assert.equal(block.number, 2); assert.equal(block.dividend, 1200); })); - it('tic should be able to send 510 to toc', () => co(function*() { - yield s1.expect('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', (res) => { + it('tic should be able to send 510 to toc', async () => { + await s1.expect('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', (res:any) => { should.exists(res); assert.equal(res.sources.length, 1); assert.equal(res.sources[0].conditions, 'SIG(DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV)') - const txSrc = _.findWhere(res.sources, { type: 'T' }); + const txSrc = (Underscore.findWhere(res.sources, { type: 'T' }) as any) assert.equal(txSrc.amount, 690); }) - const tx = yield s1.get('/tx/hash/B6DCADFB841AC05A902741A8772A70B4086D5AEAB147AD48987DDC3887DD55C8') + const tx = await s1.get('/tx/hash/B6DCADFB841AC05A902741A8772A70B4086D5AEAB147AD48987DDC3887DD55C8') assert.notEqual(tx, null) assert.deepEqual(tx, { "comment": "", @@ -127,94 +120,94 @@ describe("Testing transactions", function() { "version": 10, "written_block": 3 }) - })); + }) - it('toc should have 1510 of sources', () => s1.expect('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', (res) => { + it('toc should have 1510 of sources', () => s1.expect('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', (res:any) => { should.exists(res); assert.equal(res.sources.length, 2); - const txRes = _.findWhere(res.sources, { type: 'T' }); - const duRes = _.filter(res.sources, { type: 'D' }); + const txRes = (Underscore.findWhere(res.sources, { type: 'T' }) as any) + const duRes = (Underscore.where(res.sources, { type: 'D' }) as any) assert.equal(txRes.type, 'T'); assert.equal(txRes.amount, 510); assert.equal(duRes[0].type, 'D'); assert.equal(duRes[0].amount, 1200); })); - it('toc should be able to send 800 to tic', () => co(function *() { - let tx1 = yield toc.prepareITX(1710, tic); - yield toc.sendTX(tx1); - yield s1.commit({ time: now + 15000 }); - (yield s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(0); - })); + it('toc should be able to send 800 to tic', async () => { + let tx1 = await toc.prepareITX(1710, tic); + await toc.sendTX(tx1); + await s1.commit({ time: now + 15000 }); + (await s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(0); + }) }); describe("Chaining", function(){ - it('with SIG and XHX', () => co(function *() { + it('with SIG and XHX', async () => { // Current state - (yield s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(0); - (yield s1.get('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV')).should.have.property('sources').length(2); - (yield s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(0); - (yield s1.get('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV')).should.have.property('sources').length(2); + (await s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(0); + (await s1.get('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV')).should.have.property('sources').length(2); + (await s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(0); + (await s1.get('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV')).should.have.property('sources').length(2); // Make the time go so another UD is available - yield s1.commit({ time: now + 15000 }); - (yield s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(1); - (yield s1.get('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV')).should.have.property('sources').length(3); - let tx1 = yield toc.prepareITX(1200, tic); - yield toc.sendTX(tx1); - yield s1.commit({ time: now + 15000 }); - (yield s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(0); - (yield s1.get('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV')).should.have.property('sources').length(4); + await s1.commit({ time: now + 15000 }); + (await s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(1); + (await s1.get('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV')).should.have.property('sources').length(3); + let tx1 = await toc.prepareITX(1200, tic); + await toc.sendTX(tx1); + await s1.commit({ time: now + 15000 }); + (await s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(0); + (await s1.get('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV')).should.have.property('sources').length(4); // Now cat has all the money... - let current = yield s1.get('/blockchain/current'); - let tx2 = yield tic.prepareUTX(tx1, ['SIG(2)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'wrong', blockstamp: [current.number, current.hash].join('-') }); - let tx3 = yield tic.prepareUTX(tx1, ['SIG(1)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'wrong', blockstamp: [current.number, current.hash].join('-') }); - let tx4 = yield tic.prepareUTX(tx1, ['SIG(0)'], [{ qty: 1200, base: 0, lock: 'XHX(8AFC8DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB)' }], { comment: 'ok', blockstamp: [current.number, current.hash].join('-') }); - let tx5 = yield tic.prepareUTX(tx1, ['XHX(2)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'wrong', blockstamp: [current.number, current.hash].join('-') }); - let tx6 = yield tic.prepareUTX(tx1, ['XHX(4)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'wrong', blockstamp: [current.number, current.hash].join('-') }); - yield unit.shouldFail(toc.sendTX(tx2), 'Wrong unlocker in transaction'); - yield unit.shouldFail(toc.sendTX(tx3), 'Wrong unlocker in transaction'); - yield unit.shouldNotFail(toc.sendTX(tx4)); - yield unit.shouldFail(toc.sendTX(tx5), 'Wrong unlocker in transaction'); - yield unit.shouldFail(toc.sendTX(tx6), 'Wrong unlocker in transaction'); - yield s1.commit({ time: now + 19840 }); // TX4 commited - (yield s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(0); // The tx was not sent to someone, but with an XHX! So toc has nothing more than before. - (yield s1.get('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV')).should.have.property('sources').length(3); - let tx7 = yield tic.prepareUTX(tx4, ['XHX(2872767826647264)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'wrong1', blockstamp: [current.number, current.hash].join('-') }); - let tx8 = yield tic.prepareUTX(tx4, ['XHX(1872767826647264)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'okk', blockstamp: [current.number, current.hash].join('-') }); // tic unlocks the XHX locked amount, and gives it to toc! - yield unit.shouldFail(toc.sendTX(tx7), 'Wrong unlocker in transaction'); - yield unit.shouldNotFail(toc.sendTX(tx8)); - yield s1.commit({ time: now + 19840 }); // TX8 commited - (yield s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(1); // That's why toc now has 1 more source... - (yield s1.get('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV')).should.have.property('sources').length(3); // ...and why tic's number of sources hasn't changed - })); - - it('with MULTISIG', () => co(function *() { - (yield s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(1); - (yield s1.get('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV')).should.have.property('sources').length(3); - let tx1 = yield toc.prepareITX(1200, tic); - yield toc.sendTX(tx1); - yield s1.commit({ time: now + 19840 }); - let current = yield s1.get('/blockchain/current'); - (yield s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(0); - (yield s1.get('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV')).should.have.property('sources').length(4); + let current = await s1.get('/blockchain/current'); + let tx2 = await tic.prepareUTX(tx1, ['SIG(2)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'wrong', blockstamp: [current.number, current.hash].join('-') }); + let tx3 = await tic.prepareUTX(tx1, ['SIG(1)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'wrong', blockstamp: [current.number, current.hash].join('-') }); + let tx4 = await tic.prepareUTX(tx1, ['SIG(0)'], [{ qty: 1200, base: 0, lock: 'XHX(8AFC8DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB)' }], { comment: 'ok', blockstamp: [current.number, current.hash].join('-') }); + let tx5 = await tic.prepareUTX(tx1, ['XHX(2)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'wrong', blockstamp: [current.number, current.hash].join('-') }); + let tx6 = await tic.prepareUTX(tx1, ['XHX(4)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'wrong', blockstamp: [current.number, current.hash].join('-') }); + await shouldFail(toc.sendTX(tx2), 'Wrong unlocker in transaction'); + await shouldFail(toc.sendTX(tx3), 'Wrong unlocker in transaction'); + await shouldNotFail(toc.sendTX(tx4)); + await shouldFail(toc.sendTX(tx5), 'Wrong unlocker in transaction'); + await shouldFail(toc.sendTX(tx6), 'Wrong unlocker in transaction'); + await s1.commit({ time: now + 19840 }); // TX4 commited + (await s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(0); // The tx was not sent to someone, but with an XHX! So toc has nothing more than before. + (await s1.get('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV')).should.have.property('sources').length(3); + let tx7 = await tic.prepareUTX(tx4, ['XHX(2872767826647264)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'wrong1', blockstamp: [current.number, current.hash].join('-') }); + let tx8 = await tic.prepareUTX(tx4, ['XHX(1872767826647264)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'okk', blockstamp: [current.number, current.hash].join('-') }); // tic unlocks the XHX locked amount, and gives it to toc! + await shouldFail(toc.sendTX(tx7), 'Wrong unlocker in transaction'); + await shouldNotFail(toc.sendTX(tx8)); + await s1.commit({ time: now + 19840 }); // TX8 commited + (await s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(1); // That's why toc now has 1 more source... + (await s1.get('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV')).should.have.property('sources').length(3); // ...and why tic's number of sources hasn't changed + }) + + it('with MULTISIG', async () => { + (await s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(1); + (await s1.get('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV')).should.have.property('sources').length(3); + let tx1 = await toc.prepareITX(1200, tic); + await toc.sendTX(tx1); + await s1.commit({ time: now + 19840 }); + let current = await s1.get('/blockchain/current'); + (await s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(0); + (await s1.get('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV')).should.have.property('sources').length(4); // The funding transaction that can be reverted by its issuer (tic here) or consumed by toc if he knowns X for H(X) - let tx2 = yield tic.prepareUTX(tx1, ['SIG(0)'], [{ qty: 1200, base: 0, lock: '(XHX(8AFC8DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB) && SIG(' + toc.pub + ')) || (SIG(' + tic.pub + ') && SIG(' + toc.pub + '))' }], { comment: 'cross1', blockstamp: [current.number, current.hash].join('-') }); - yield unit.shouldNotFail(toc.sendTX(tx2)); - yield s1.commit({ time: now + 19840 }); // TX2 commited - (yield s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(1); // toc is also present in the target of tx2 - (yield s1.get('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV')).should.have.property('sources').length(4); // As well as tic - let tx3 = yield tic.prepareUTX(tx2, ['XHX(1872767826647264) SIG(0)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'wrong', blockstamp: [current.number, current.hash].join('-') }); - let tx4 = yield toc.prepareUTX(tx2, ['XHX(1872767826647264) SIG(0)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'ok', blockstamp: [current.number, current.hash].join('-') }); - let tx5 = yield tic.prepareMTX(tx2, toc, ['XHX(1872767826647264) SIG(1) SIG(0)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'multi OK', blockstamp: [current.number, current.hash].join('-') }); - let tx6 = yield toc.prepareMTX(tx2, tic, ['XHX(1872767826647264) SIG(1) SIG(0) SIG(0) SIG(0)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'multi WRONG', blockstamp: [current.number, current.hash].join('-') }); + let tx2 = await tic.prepareUTX(tx1, ['SIG(0)'], [{ qty: 1200, base: 0, lock: '(XHX(8AFC8DF633FC158F9DB4864ABED696C1AA0FE5D617A7B5F7AB8DE7CA2EFCD4CB) && SIG(' + toc.pub + ')) || (SIG(' + tic.pub + ') && SIG(' + toc.pub + '))' }], { comment: 'cross1', blockstamp: [current.number, current.hash].join('-') }); + await shouldNotFail(toc.sendTX(tx2)); + await s1.commit({ time: now + 19840 }); // TX2 commited + (await s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(1); // toc is also present in the target of tx2 + (await s1.get('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV')).should.have.property('sources').length(4); // As well as tic + let tx3 = await tic.prepareUTX(tx2, ['XHX(1872767826647264) SIG(0)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'wrong', blockstamp: [current.number, current.hash].join('-') }); + let tx4 = await toc.prepareUTX(tx2, ['XHX(1872767826647264) SIG(0)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'ok', blockstamp: [current.number, current.hash].join('-') }); + let tx5 = await tic.prepareMTX(tx2, toc, ['XHX(1872767826647264) SIG(1) SIG(0)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'multi OK', blockstamp: [current.number, current.hash].join('-') }); + let tx6 = await toc.prepareMTX(tx2, tic, ['XHX(1872767826647264) SIG(1) SIG(0) SIG(0) SIG(0)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'multi WRONG', blockstamp: [current.number, current.hash].join('-') }); // nLocktime - let tx7 = yield tic.prepareMTX(tx2, toc, ['XHX(1872767826647264) SIG(1) SIG(0)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'wrong locktime', locktime: 100, blockstamp: [current.number, current.hash].join('-') }); - yield unit.shouldFail(toc.sendTX(tx3), 'Wrong unlocker in transaction'); - yield unit.shouldNotFail(toc.sendTX(tx4)); - yield unit.shouldNotFail(toc.sendTX(tx5)); - yield unit.shouldFail(toc.sendTX(tx6), 'Wrong unlocker in transaction'); - yield unit.shouldFail(toc.sendTX(tx7), 'Locktime not elapsed yet'); - })); - }); -}); + let tx7 = await tic.prepareMTX(tx2, toc, ['XHX(1872767826647264) SIG(1) SIG(0)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { comment: 'wrong locktime', locktime: 100, blockstamp: [current.number, current.hash].join('-') }); + await shouldFail(toc.sendTX(tx3), 'Wrong unlocker in transaction'); + await shouldNotFail(toc.sendTX(tx4)); + await shouldNotFail(toc.sendTX(tx5)); + await shouldFail(toc.sendTX(tx6), 'Wrong unlocker in transaction'); + await shouldFail(toc.sendTX(tx7), 'Locktime not elapsed yet'); + }) + }) +}) diff --git a/test/integration/v1.0-source-garbaging.disabled b/test/integration/v1.0-source-garbaging.disabled deleted file mode 100644 index e667749603216bdd6a0348f5b30e73f426b86a9a..0000000000000000000000000000000000000000 --- a/test/integration/v1.0-source-garbaging.disabled +++ /dev/null @@ -1,206 +0,0 @@ -"use strict"; - -const co = require('co'); -const should = require('should'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const constants = require('../../app/lib/constants'); -const toolbox = require('./tools/toolbox'); - -const now = 1480000000; - -const conf = { - ud0: 9995, - c: .99, - dt: 300, - udTime0: now + 300, - udReevalTime0: now + 300, - avgGenTime: 5000, - medianTimeBlocks: 1 // The medianTime always equals previous block's medianTime -}; - -constants.NB_DIGITS_UD = 4; - -let s1, cat, tac; - -describe("Protocol 1.0 Source Garbaging", function() { - - /***** - * DESCRIPTION - * ----------- - * - * All accounts having less than 100 units of money (current base) must see their money garbaged, i.e. destroyed. - * - * This measure is here to avoid a metastasizing of the database because of users who would spend very little amounts - * of money to random addresses, or to finally destroy very old money (dozens of years). - */ - - before(() => co(function*() { - - const res1 = yield toolbox.simpleNodeWith2Users(conf); - s1 = res1.s1; - cat = res1.cat; // HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd - tac = res1.tac; // 2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc - yield s1.commit({ time: now }); - yield s1.commit({ time: now + 300 }); - })); - - it('cat should have no source initially', () => co(function*() { - yield s1.expectThat('/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', (json) => { - json.sources.should.have.length(0); - }); - })); - - it('cat should have a Dividend, as well as tac', () => co(function*() { - yield s1.commit({ time: now + 300 }); - yield s1.expectThat('/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', (json) => { - json.sources.should.deepEqual([ - { type: 'D', noffset: 2, identifier: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', amount: 9995, base: 0 } - ]); - }); - })); - - it('should be able to send money to tac with no losses', () => co(function*() { - yield cat.sendP(2999, tac); - yield s1.commit({ time: now + 300 }); - yield cat.sendP(1, tac); - yield s1.commit({ time: now + 300 }); - yield s1.expectThat('/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', (json) => { - json.sources.should.deepEqual([ - { type: 'T', noffset: 1, identifier: '50844926EC611BF6BBF9918A657F87E0AA0DE5A5D8DB3D476289BF64C6ED8C25', amount: 6995, base: 0 } - ]); - }); - yield s1.expectThat('/tx/sources/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', (json) => { - json.sources.should.deepEqual([ - { type: 'D', noffset: 2, identifier: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', amount: 9995, base: 0 }, - { type: 'T', noffset: 0, identifier: 'E84C72FBE788F6F52B293676A8314A6F227F14B0A8FD0168E1C4F08E85D1F8E9', amount: 2999, base: 0 }, - { type: 'T', noffset: 0, identifier: '50844926EC611BF6BBF9918A657F87E0AA0DE5A5D8DB3D476289BF64C6ED8C25', amount: 1, base: 0 } - ]); - }); - })); - - it('should be able to send money to tac with still no losses', () => co(function*() { - yield cat.sendP(5495, tac); - yield s1.commit({ time: now + 300 }); - yield s1.expectThat('/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', (json) => { - json.sources.should.deepEqual([ - { type: 'T', noffset: 1, identifier: 'DA453C8B6300F06AC538D7EFB154DA9AE51F30D525236B9D4AD13944E18AA1B0', amount: 1500, base: 0 } - ]); - }); - yield s1.expectThat('/tx/sources/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', (json) => { - json.sources.should.deepEqual([ - { type: 'D', noffset: 2, identifier: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', amount: 9995, base: 0 }, - { type: 'T', noffset: 0, identifier: 'E84C72FBE788F6F52B293676A8314A6F227F14B0A8FD0168E1C4F08E85D1F8E9', amount: 2999, base: 0 }, - { type: 'T', noffset: 0, identifier: '50844926EC611BF6BBF9918A657F87E0AA0DE5A5D8DB3D476289BF64C6ED8C25', amount: 1, base: 0 }, - { type: 'T', noffset: 0, identifier: 'DA453C8B6300F06AC538D7EFB154DA9AE51F30D525236B9D4AD13944E18AA1B0', amount: 5495, base: 0 } - ]); - }); - })); - - it('should be able to lose money by sending 1,99,100,999,1000,300+700 units to random accounts', () => co(function*() { - yield s1.expectThat('/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', (json) => { - json.sources.should.deepEqual([ - { type: 'T', noffset: 1, identifier: 'DA453C8B6300F06AC538D7EFB154DA9AE51F30D525236B9D4AD13944E18AA1B0', amount: 1500, base: 0 } - ]); - }); - yield cat.sendP(1, '6EQoFVnFf2xpaRzieNTXmAKU6XkDHYrvgorJ8ppMFa8b'); - yield s1.commit({ time: now + 300 }); - yield s1.expectThat('/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', (json) => { - json.sources.should.deepEqual([ - { type: 'T', noffset: 1, identifier: 'A6F2C3DFF8EFEBE226F103E86193A8F22A51D25DD63C2BB9BF86D9A5F3DC55B8', amount: 1499, base: 0 } - ]); - }); - yield cat.sendP(99, '2EvWF9XM6TY3zUDjwi3qfGRW5zhN11TXcUDXdgK2XK41'); - yield s1.commit({ time: now + 300 }); - yield s1.expectThat('/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', (json) => { - json.sources.should.deepEqual([ - { type: 'T', noffset: 1, identifier: 'F1C86F38F33B2D37561EE927801D8B630BCADA62336E4BBC718BA06B1101584C', amount: 1400, base: 0 } - ]); - }); - yield cat.sendP(100, 'DPFgnVSB14QnYFjKNhbFRYLxroSmaXZ53TzgFZBcCxbF'); - yield s1.commit({ time: now + 300 }); - yield s1.expectThat('/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', (json) => { - json.sources.should.deepEqual([ - { type: 'T', noffset: 1, identifier: '0FAD3D25899C789C1C2B12FE3D90BF26E5794FB31ECF5072A881DF9B83E7CA00', amount: 1300, base: 0 } - ]); - }); - yield tac.sendP(4, 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'); - yield cat.sendP(999, '4WmQWq4NuJtu6mzFDKkmmu6Cm6BZvgoY4b4MMDMwVvu7'); - yield s1.commit({ time: now + 300 }); - yield s1.expectThat('/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', (json) => { - json.sources.should.deepEqual([ - { type: 'T', noffset: 0, identifier: '3B12EEC97704A8CCA31AFD7B60BA09555744703E22A6A47EE4ECBE6DA20B27E5', amount: 4, base: 0 }, - { type: 'T', noffset: 1, identifier: '9B18E2C2CBF9C856560E76F8684665C8677DD0506AAD5195960E30CC37A5706C', amount: 301, base: 0 } - ]); - }); - yield cat.sendP(300, '7kMAi8wttYKPK5QSfCwoDriNTcCTWKzTbuSjsLsjGJX2'); - yield tac.sendP(700, '7kMAi8wttYKPK5QSfCwoDriNTcCTWKzTbuSjsLsjGJX2'); - yield s1.commit({ time: now + 900 }); - // Has spent all its money, + 1 unit destroyed - yield s1.expectThat('/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', (json) => { - json.sources.should.deepEqual([]); - }); - // Has seen 1 unit destroyed - yield s1.expectThat('/tx/sources/6EQoFVnFf2xpaRzieNTXmAKU6XkDHYrvgorJ8ppMFa8b', (json) => { - json.sources.should.deepEqual([]); - }); - // Has seen 99 unit destroyed - yield s1.expectThat('/tx/sources/2EvWF9XM6TY3zUDjwi3qfGRW5zhN11TXcUDXdgK2XK41', (json) => { - json.sources.should.deepEqual([]); - }); - // Has just enough on the account (100 units) - yield s1.expectThat('/tx/sources/DPFgnVSB14QnYFjKNhbFRYLxroSmaXZ53TzgFZBcCxbF', (json) => { - json.sources.should.deepEqual([ - { type: 'T', noffset: 0, identifier: '0FAD3D25899C789C1C2B12FE3D90BF26E5794FB31ECF5072A881DF9B83E7CA00', amount: 100, base: 0 } - ]); - }); - // Has way enough on the account (999 units) - yield s1.expectThat('/tx/sources/4WmQWq4NuJtu6mzFDKkmmu6Cm6BZvgoY4b4MMDMwVvu7', (json) => { - json.sources.should.deepEqual([ - { type: 'T', noffset: 0, identifier: '9B18E2C2CBF9C856560E76F8684665C8677DD0506AAD5195960E30CC37A5706C', amount: 999, base: 0 } - ]); - }); - // Has way enough on the account (300 + 700 units) - yield s1.expectThat('/tx/sources/7kMAi8wttYKPK5QSfCwoDriNTcCTWKzTbuSjsLsjGJX2', (json) => { - json.sources.should.deepEqual([ - { type: 'T', noffset: 0, identifier: '37CD105D17182155978798C773C70950470EBFB27B082F888B3423670F956F35', amount: 300, base: 0 }, - { type: 'T', noffset: 0, identifier: '6EF384807D1100D51BCCB9ED6E6FF4CA12CC1F4F30392CFD43746D4D1C4BC22E', amount: 700, base: 0 } - ]); - }); - })); - - it('should have lost some money with unitBase bumped from 0 to 1', () => co(function*() { - yield s1.commit({ time: now + 900 }); - yield s1.commit({ time: now + 900 }); - // Has no more enough on the account (100x10^0 < 100x10^1) - yield s1.expectThat('/tx/sources/DPFgnVSB14QnYFjKNhbFRYLxroSmaXZ53TzgFZBcCxbF', (json) => { - json.sources.should.deepEqual([]); - }); - // Has NOT enough on the account (999x10^0 = 99.9x10^1 < 100x10^1) - yield s1.expectThat('/tx/sources/4WmQWq4NuJtu6mzFDKkmmu6Cm6BZvgoY4b4MMDMwVvu7', (json) => { - json.sources.should.deepEqual([]); - }); - // Has enough on the account (300x10^0 + 700x10^0 = 1000x10^0 = 100x10^1) - yield s1.expectThat('/tx/sources/7kMAi8wttYKPK5QSfCwoDriNTcCTWKzTbuSjsLsjGJX2', (json) => { - json.sources.should.deepEqual([ - { type: 'T', noffset: 0, identifier: '37CD105D17182155978798C773C70950470EBFB27B082F888B3423670F956F35', amount: 300, base: 0 }, - { type: 'T', noffset: 0, identifier: '6EF384807D1100D51BCCB9ED6E6FF4CA12CC1F4F30392CFD43746D4D1C4BC22E', amount: 700, base: 0 } - ]); - }); - yield s1.commit({ time: now + 1800 }); - // Has enough on the account (300x10^0 + 700x10^0 = 1000x10^0 = 100x10^1) - yield s1.expectThat('/tx/sources/7kMAi8wttYKPK5QSfCwoDriNTcCTWKzTbuSjsLsjGJX2', (json) => { - json.sources.should.deepEqual([ - { type: 'T', noffset: 0, identifier: '37CD105D17182155978798C773C70950470EBFB27B082F888B3423670F956F35', amount: 300, base: 0 }, - { type: 'T', noffset: 0, identifier: '6EF384807D1100D51BCCB9ED6E6FF4CA12CC1F4F30392CFD43746D4D1C4BC22E', amount: 700, base: 0 } - ]); - }); - yield s1.commit({ time: now + 3600 }); - yield s1.expectThat('/tx/sources/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', (json) => { - json.sources.should.deepEqual([ - { type: 'D', noffset: 11, identifier: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', amount: 9995, base: 0 }, - { type: 'D', noffset: 12, identifier: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', amount: 1980, base: 1 }, - { type: 'D', noffset: 14, identifier: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', amount: 3940, base: 1 } - ]); - }); - })); -}); diff --git a/test/integration/wot/wotb.ts b/test/integration/wot/wotb.ts new file mode 100644 index 0000000000000000000000000000000000000000..4ace7d46a3d0cbc56d57d649262bec5b47e1be4f --- /dev/null +++ b/test/integration/wot/wotb.ts @@ -0,0 +1,450 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import {TestUser} from "../tools/TestUser" +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {BmaDependency} from "../../../app/modules/bma/index" +import {WoTBInstance} from "../../../app/lib/wot" +import {Underscore} from "../../../app/lib/common-libs/underscore" +import {shutDownEngine} from "../tools/shutdown-engine" + +const should = require('should'); + +const MEMORY_MODE = true; +const commonConf = { + ipv4: '127.0.0.1', + currency: 'bb', + httpLogs: true, + forksize: 3, + sigQty: 1 +}; + +let s1:TestingServer, + s2:TestingServer, + s3:TestingServer, + cat:TestUser, + toc:TestUser, + tic:TestUser, + cat2:TestUser, + toc2:TestUser, + tic2:TestUser, + cat3:TestUser, + toc3:TestUser, + tic3:TestUser + +const now = 1482000000; +const _100_PERCENT = 1.0; +const MAX_DISTANCE_1 = 1; +const MAX_DISTANCE_2 = 2; +const FROM_1_LINK_SENTRIES = 1; +const __OUTDISTANCED__ = true; +const __OK__ = false; + +describe("WOTB module", () => { + + describe("Server 1", () => { + + let wotb:WoTBInstance + + before(async () => { + s1 = NewTestingServer( + Underscore.extend({ + name: 'bb11', + memory: MEMORY_MODE, + port: '9337', + pair: { + pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', + sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' + }, + rootoffset: 10, + sigQty: 1, dt: 1, ud0: 120 + }, commonConf)); + + s2 = NewTestingServer( + Underscore.extend({ + name: 'bb41', + memory: MEMORY_MODE, + port: '9338', + pair: { + pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', + sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' + }, + rootoffset: 10, + sigQty: 1, dt: 1, ud0: 120, + msValidity: 400 // Memberships expire after 400 second delay + }, commonConf)); + + s3 = NewTestingServer( + Underscore.extend({ + name: 'bb11', + memory: MEMORY_MODE, + port: '9339', + pair: { + pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', + sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7' + }, + rootoffset: 10, + sigQty: 1, dt: 1, ud0: 120, + sigValidity: 1400, sigPeriod: 0 + }, commonConf)); + + cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); + toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); + tic = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s1 }); + + cat2 = new TestUser('cat2', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s2 }); + toc2 = new TestUser('toc2', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s2 }); + tic2 = new TestUser('tic2', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s2 }); + + cat3 = new TestUser('cat3', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s3 }); + toc3 = new TestUser('toc3', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s3 }); + tic3 = new TestUser('tic3', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s3 }); + + /** + * cat <==> toc + */ + await s1.initWithDAL().then(BmaDependency.duniter.methods.bma).then((bmapi) => bmapi.openConnections()); + wotb = s1.dal.wotb; + await cat.createIdentity(); + await toc.createIdentity(); + await toc.cert(cat); + await cat.cert(toc); + await cat.join(); + await toc.join(); + await s1.commit({ + time: now + 500 + }); + await s1.commit({ + time: now + 500 + }); + }) + + after(() => { + return Promise.all([ + shutDownEngine(s1) + ]) + }) + + it('the wotb_id should be affected to new members', async () => { + let icat = await s1.dal.getWrittenIdtyByUIDForWotbId("cat"); + let itoc = await s1.dal.getWrittenIdtyByUIDForWotbId("toc"); + icat.should.have.property('wotb_id').equal(0); + itoc.should.have.property('wotb_id').equal(1); + wotb.isEnabled(0).should.equal(true); + wotb.isEnabled(1).should.equal(true); + wotb.existsLink(0, 1).should.equal(true); + wotb.existsLink(1, 0).should.equal(true); + wotb.existsLink(1, 1).should.equal(false); + wotb.existsLink(1, 2).should.equal(false); + wotb.existsLink(0, 0).should.equal(false); + wotb.existsLink(0, 2).should.equal(false); + wotb.isOutdistanced(0, FROM_1_LINK_SENTRIES, MAX_DISTANCE_1, _100_PERCENT).should.equal(__OK__); + }); + + it('a newcomer should be affected an ID + links', async () => { + /** + * cat <==> toc --> tic + */ + await tic.createIdentity(); + await toc.cert(tic); + await tic.join(); + await s1.commit(); + let itic = await s1.dal.getWrittenIdtyByUIDForWotbId("tic"); + itic.should.have.property('wotb_id').equal(2); + wotb.isEnabled(2).should.equal(true); + wotb.existsLink(1, 2).should.equal(true); + wotb.existsLink(0, 2).should.equal(false); + wotb.isOutdistanced(0, FROM_1_LINK_SENTRIES, MAX_DISTANCE_1, _100_PERCENT).should.equal(__OK__); + wotb.isOutdistanced(1, FROM_1_LINK_SENTRIES, MAX_DISTANCE_1, _100_PERCENT).should.equal(__OK__); + // tic is outdistanced if k = 1! (cat can't reach him) + wotb.isOutdistanced(2, FROM_1_LINK_SENTRIES, MAX_DISTANCE_1, _100_PERCENT).should.equal(__OUTDISTANCED__); + // but reachable if k = 2 + wotb.isOutdistanced(2, FROM_1_LINK_SENTRIES, MAX_DISTANCE_2, _100_PERCENT).should.equal(__OK__); + }); + }); + + describe("Server 2", () => { + + let wotb:WoTBInstance + + before(async () => { + /** + * tic <==> cat <==> toc + */ + await s2.initWithDAL().then(BmaDependency.duniter.methods.bma).then((bmapi) => bmapi.openConnections()); + wotb = s2.dal.wotb; + await cat2.createIdentity(); + await toc2.createIdentity(); + await tic2.createIdentity(); + // toc2 <==> cat2 + await toc2.cert(cat2); + await cat2.cert(toc2); + // tic2 <==> cat2 + await tic2.cert(cat2); + await cat2.cert(tic2); + await cat2.join(); + await toc2.join(); + await tic2.join(); + await s2.commit({ + time: now + }); + // Should make MS expire for toc2 + await s2.commit({ + time: now + 500 + }); + await s2.commit({ + time: now + 600 + }); + await cat2.join(); // Renew for not to be kicked! + await tic2.join(); // Renew for not to be kicked! + await s2.commit({ + time: now + 800 + }); + await s2.commit({ + time: now + 800 + }); + // Members excluded + await s2.commit({ + time: now + 800 + }); + }); + + after(() => { + return Promise.all([ + shutDownEngine(s2) + ]) + }) + + it('a leaver should still have links but be disabled', async () => { + wotb.isEnabled(0).should.equal(true); + wotb.isEnabled(1).should.equal(true); + wotb.isEnabled(2).should.equal(false); + // tic2 <==> cat2 + wotb.existsLink(0, 1).should.equal(true); + wotb.existsLink(1, 0).should.equal(true); + // toc2 <==> cat2 + wotb.existsLink(0, 2).should.equal(true); + wotb.existsLink(2, 0).should.equal(true); + // toc2 <==> tic2 + wotb.existsLink(1, 2).should.equal(false); + wotb.existsLink(2, 1).should.equal(false); + }); + + it('a leaver who joins back should be enabled', async () => { + await toc2.join(); + await s2.commit(); + wotb.isEnabled(0).should.equal(true); + wotb.isEnabled(1).should.equal(true); + wotb.isEnabled(2).should.equal(true); + // tic2 <==> cat2 + wotb.existsLink(0, 1).should.equal(true); + wotb.existsLink(1, 0).should.equal(true); + // toc2 <==> cat2 + wotb.existsLink(0, 2).should.equal(true); + wotb.existsLink(2, 0).should.equal(true); + // toc2 <==> tic2 + wotb.existsLink(1, 2).should.equal(false); + wotb.existsLink(2, 1).should.equal(false); + }); + }); + + describe("Server 3", () => { + + let wotb:WoTBInstance + + before(async () => { + await s3.initWithDAL().then(BmaDependency.duniter.methods.bma).then((bmapi) => bmapi.openConnections()); + wotb = s3.dal.wotb; + await cat3.createIdentity(); + await tic3.createIdentity(); + // cat <==> tic + await tic3.cert(cat3); + await cat3.cert(tic3); + await cat3.join(); + await tic3.join(); + }); + + after(() => { + return Promise.all([ + shutDownEngine(s3) + ]) + }) + + it('two first commits: the WoT is new and OK', async () => { + await s3.commit({ time: now }); + await s3.commit({ + time: now + 1200 + }); + /** + * cat <==> tic + */ + wotb.isEnabled(0).should.equal(true); + wotb.isEnabled(1).should.equal(true); + // cat3 <==> tic3 + wotb.existsLink(0, 1).should.equal(true); + wotb.existsLink(1, 0).should.equal(true); + // tic3 <==> toc3 + wotb.existsLink(1, 2).should.equal(false); + wotb.existsLink(2, 1).should.equal(false); + }); + + it('third & fourth commits: toc should have joined', async () => { + await s3.commit({ + time: now + 2400 + }); + // MedianTime is now +500 for next certs + await toc3.createIdentity(); + await toc3.join(); + await tic3.cert(toc3); + await s3.commit({ + time: now + 4000 + }); + // MedianTime is now +1000 for next certs + /** + * cat <==> tic --> toc + */ + wotb.isEnabled(0).should.equal(true); + wotb.isEnabled(1).should.equal(true); + wotb.isEnabled(2).should.equal(true); + // cat3 <==> tic3 + wotb.existsLink(0, 1).should.equal(true); + wotb.existsLink(1, 0).should.equal(true); + // tic3 <==> toc3 + wotb.existsLink(1, 2).should.equal(true); + wotb.existsLink(2, 1).should.equal(false); + }); + + it('fifth commit: cat still here, but not its certs', async () => { + await toc3.cert(tic3); + await s3.commit({ + time: now + 4000 + }); + /** + * cat tic <==> toc + */ + wotb.isEnabled(0).should.equal(true); // But marked as to kick: cannot issue new links + wotb.isEnabled(1).should.equal(true); + wotb.isEnabled(2).should.equal(true); + // cat3 <==> tic3 + wotb.existsLink(0, 1).should.equal(false); + wotb.existsLink(1, 0).should.equal(false); + // tic3 <==> toc3 + wotb.existsLink(1, 2).should.equal(true); + wotb.existsLink(2, 1).should.equal(true); + }); + + it('sixth commit: cat is gone with its certs', async () => { + await s3.commit({ + time: now + 2500 + }); + /** + * tic <-- toc + */ + wotb.isEnabled(0).should.equal(false); + wotb.isEnabled(1).should.equal(true); + wotb.isEnabled(2).should.equal(true); + // cat3 <==> tic3 + wotb.existsLink(0, 1).should.equal(false); + wotb.existsLink(1, 0).should.equal(false); + // tic3 --> toc3 + wotb.existsLink(1, 2).should.equal(false); + wotb.existsLink(2, 1).should.equal(true); + }); + + it('seventh commit: toc is gone, but not its cert to tic', async () => { + await tic3.cert(cat3); + await cat3.join(); + await s3.commit({ + time: now + 5000 + }); + /** + * cat <-- tic <-- [toc] + */ + wotb.isEnabled(0).should.equal(true); + wotb.isEnabled(1).should.equal(true); + wotb.isEnabled(2).should.equal(false); + // cat3 <==> tic3 + wotb.existsLink(0, 1).should.equal(false); + wotb.existsLink(1, 0).should.equal(true); + // tic3 --> toc3 + wotb.existsLink(1, 2).should.equal(false); + wotb.existsLink(2, 1).should.equal(true); + }); + + it('revert seventh commit: toc is back, cat is gone', async () => { + await s3.revert(); + wotb.isEnabled(0).should.equal(false); + wotb.isEnabled(1).should.equal(true); + wotb.isEnabled(2).should.equal(true); + // cat3 <==> tic3 + wotb.existsLink(0, 1).should.equal(false); + wotb.existsLink(1, 0).should.equal(false); + // tic3 --> toc3 + wotb.existsLink(1, 2).should.equal(false); + wotb.existsLink(2, 1).should.equal(true); + }); + + it('revert sixth commit: cat is back', async () => { + await s3.revert(); + wotb.isEnabled(0).should.equal(true); // But marked as to kick: cannot issue new links + wotb.isEnabled(1).should.equal(true); + wotb.isEnabled(2).should.equal(true); + // cat3 <==> tic3 + wotb.existsLink(0, 1).should.equal(false); + wotb.existsLink(1, 0).should.equal(false); + // tic3 <==> toc3 + wotb.existsLink(1, 2).should.equal(true); + wotb.existsLink(2, 1).should.equal(true); + }); + + it('revert fifth commit', async () => { + await s3.revert(); + wotb.isEnabled(0).should.equal(true); + wotb.isEnabled(1).should.equal(true); + wotb.isEnabled(2).should.equal(true); + // cat3 <==> tic3 + wotb.existsLink(0, 1).should.equal(true); + wotb.existsLink(1, 0).should.equal(true); + // tic3 <==> toc3 + wotb.existsLink(1, 2).should.equal(true); + wotb.existsLink(2, 1).should.equal(false); + }); + + it('revert third & fourth commits', async () => { + await s3.revert(); + await s3.revert(); + wotb.isEnabled(0).should.equal(true); + wotb.isEnabled(1).should.equal(true); + // cat3 <==> tic3 + wotb.existsLink(0, 1).should.equal(true); + wotb.existsLink(1, 0).should.equal(true); + // tic3 <==> toc3 + wotb.existsLink(1, 2).should.equal(false); + wotb.existsLink(2, 1).should.equal(false); + }); + + it('revert first & second commits', async () => { + await s3.revert(); + await s3.revert(); + wotb.isEnabled(0).should.equal(false); + wotb.isEnabled(1).should.equal(false); + wotb.isEnabled(2).should.equal(false); + // cat3 <==> tic3 + wotb.existsLink(0, 1).should.equal(false); + wotb.existsLink(1, 0).should.equal(false); + // tic3 <==> toc3 + wotb.existsLink(1, 2).should.equal(false); + wotb.existsLink(2, 1).should.equal(false); + }); + }); +}); diff --git a/test/integration/wotb.js b/test/integration/wotb.js deleted file mode 100644 index 6c180b4a83ee20048172b8f584ff725b84a11bc0..0000000000000000000000000000000000000000 --- a/test/integration/wotb.js +++ /dev/null @@ -1,479 +0,0 @@ -// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 -// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -"use strict"; - -const co = require('co'); -const should = require('should'); -const _ = require('underscore'); -const duniter = require('../../index'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const TestUser = require('./tools/TestUser').TestUser -const commit = require('./tools/commit'); -const shutDownEngine = require('./tools/shutDownEngine'); - -const MEMORY_MODE = true; -const commonConf = { - ipv4: '127.0.0.1', - currency: 'bb', - httpLogs: true, - forksize: 3, - sigQty: 1 -}; - -let s1, s2, s3, cat, toc, tic, cat2, toc2, tic2, cat3, toc3, tic3 - -const now = 1482000000; -const _100_PERCENT = 1.0; -const MAX_DISTANCE_1 = 1; -const MAX_DISTANCE_2 = 2; -const FROM_1_LINK_SENTRIES = 1; -const __OUTDISTANCED__ = true; -const __OK__ = false; - -describe("WOTB module", function() { - - describe("Server 1", () => { - - let wotb; - - before(function() { - - return co(function *() { - - s1 = duniter( - '/bb11', - MEMORY_MODE, - _.extend({ - port: '9337', - pair: { - pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', - sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' - }, - rootoffset: 10, - sigQty: 1, dt: 1, ud0: 120 - }, commonConf)); - - s2 = duniter( - '/bb41', - MEMORY_MODE, - _.extend({ - port: '9338', - pair: { - pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', - sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' - }, - rootoffset: 10, - sigQty: 1, dt: 1, ud0: 120, - msValidity: 400 // Memberships expire after 400 second delay - }, commonConf)); - - s3 = duniter( - '/bb11', - MEMORY_MODE, - _.extend({ - port: '9339', - pair: { - pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', - sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7' - }, - rootoffset: 10, - sigQty: 1, dt: 1, ud0: 120, - sigValidity: 1400, sigPeriod: 0 - }, commonConf)); - - cat = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); - toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); - tic = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s1 }); - - cat2 = new TestUser('cat2', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s2 }); - toc2 = new TestUser('toc2', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s2 }); - tic2 = new TestUser('tic2', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s2 }); - - cat3 = new TestUser('cat3', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s3 }); - toc3 = new TestUser('toc3', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s3 }); - tic3 = new TestUser('tic3', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s3 }); - - /** - * cat <==> toc - */ - yield s1.initWithDAL().then(bma).then((bmapi) => bmapi.openConnections()); - wotb = s1.dal.wotb; - yield cat.createIdentity(); - yield toc.createIdentity(); - yield toc.cert(cat); - yield cat.cert(toc); - yield cat.join(); - yield toc.join(); - yield commit(s1)({ - time: now + 500 - }); - yield commit(s1)({ - time: now + 500 - }); - }); - }); - - after(() => { - return Promise.all([ - shutDownEngine(s1) - ]) - }) - - it('the wotb_id should be affected to new members', function() { - return co(function *() { - let icat = yield s1.dal.getWrittenIdtyByUID("cat"); - let itoc = yield s1.dal.getWrittenIdtyByUID("toc"); - icat.should.have.property('wotb_id').equal(0); - itoc.should.have.property('wotb_id').equal(1); - wotb.isEnabled(0).should.equal(true); - wotb.isEnabled(1).should.equal(true); - wotb.existsLink(0, 1).should.equal(true); - wotb.existsLink(1, 0).should.equal(true); - wotb.existsLink(1, 1).should.equal(false); - wotb.existsLink(1, 2).should.equal(false); - wotb.existsLink(0, 0).should.equal(false); - wotb.existsLink(0, 2).should.equal(false); - wotb.isOutdistanced(0, FROM_1_LINK_SENTRIES, MAX_DISTANCE_1, _100_PERCENT).should.equal(__OK__); - }); - }); - - it('a newcomer should be affected an ID + links', function() { - return co(function *() { - /** - * cat <==> toc --> tic - */ - yield tic.createIdentity(); - yield toc.cert(tic); - yield tic.join(); - yield commit(s1)(); - let itic = yield s1.dal.getWrittenIdtyByUID("tic"); - itic.should.have.property('wotb_id').equal(2); - wotb.isEnabled(2).should.equal(true); - wotb.existsLink(1, 2).should.equal(true); - wotb.existsLink(0, 2).should.equal(false); - wotb.isOutdistanced(0, FROM_1_LINK_SENTRIES, MAX_DISTANCE_1, _100_PERCENT).should.equal(__OK__); - wotb.isOutdistanced(1, FROM_1_LINK_SENTRIES, MAX_DISTANCE_1, _100_PERCENT).should.equal(__OK__); - // tic is outdistanced if k = 1! (cat can't reach him) - wotb.isOutdistanced(2, FROM_1_LINK_SENTRIES, MAX_DISTANCE_1, _100_PERCENT).should.equal(__OUTDISTANCED__); - // but reachable if k = 2 - wotb.isOutdistanced(2, FROM_1_LINK_SENTRIES, MAX_DISTANCE_2, _100_PERCENT).should.equal(__OK__); - }); - }); - }); - - describe("Server 2", () => { - - let wotb; - - before(function() { - - return co(function *() { - /** - * tic <==> cat <==> toc - */ - yield s2.initWithDAL().then(bma).then((bmapi) => bmapi.openConnections()); - wotb = s2.dal.wotb; - yield cat2.createIdentity(); - yield toc2.createIdentity(); - yield tic2.createIdentity(); - // toc2 <==> cat2 - yield toc2.cert(cat2); - yield cat2.cert(toc2); - // tic2 <==> cat2 - yield tic2.cert(cat2); - yield cat2.cert(tic2); - yield cat2.join(); - yield toc2.join(); - yield tic2.join(); - yield commit(s2)({ - time: now - }); - // Should make MS expire for toc2 - yield commit(s2)({ - time: now + 500 - }); - yield commit(s2)({ - time: now + 600 - }); - yield cat2.join(); // Renew for not to be kicked! - yield tic2.join(); // Renew for not to be kicked! - yield commit(s2)({ - time: now + 800 - }); - yield commit(s2)({ - time: now + 800 - }); - // Members excluded - yield commit(s2)({ - time: now + 800 - }); - }); - }); - - after(() => { - return Promise.all([ - shutDownEngine(s2) - ]) - }) - - it('a leaver should still have links but be disabled', function() { - return co(function *() { - wotb.isEnabled(0).should.equal(true); - wotb.isEnabled(1).should.equal(true); - wotb.isEnabled(2).should.equal(false); - // tic2 <==> cat2 - wotb.existsLink(0, 1).should.equal(true); - wotb.existsLink(1, 0).should.equal(true); - // toc2 <==> cat2 - wotb.existsLink(0, 2).should.equal(true); - wotb.existsLink(2, 0).should.equal(true); - // toc2 <==> tic2 - wotb.existsLink(1, 2).should.equal(false); - wotb.existsLink(2, 1).should.equal(false); - }); - }); - - it('a leaver who joins back should be enabled', function() { - return co(function *() { - yield toc2.join(); - yield commit(s2)(); - wotb.isEnabled(0).should.equal(true); - wotb.isEnabled(1).should.equal(true); - wotb.isEnabled(2).should.equal(true); - // tic2 <==> cat2 - wotb.existsLink(0, 1).should.equal(true); - wotb.existsLink(1, 0).should.equal(true); - // toc2 <==> cat2 - wotb.existsLink(0, 2).should.equal(true); - wotb.existsLink(2, 0).should.equal(true); - // toc2 <==> tic2 - wotb.existsLink(1, 2).should.equal(false); - wotb.existsLink(2, 1).should.equal(false); - }); - }); - }); - - describe("Server 3", () => { - - let wotb; - - before(function() { - - return co(function *() { - yield s3.initWithDAL().then(bma).then((bmapi) => bmapi.openConnections()); - wotb = s3.dal.wotb; - yield cat3.createIdentity(); - yield tic3.createIdentity(); - // cat <==> tic - yield tic3.cert(cat3); - yield cat3.cert(tic3); - yield cat3.join(); - yield tic3.join(); - }); - }); - - after(() => { - return Promise.all([ - shutDownEngine(s3) - ]) - }) - - it('two first commits: the WoT is new and OK', function() { - return co(function *() { - yield commit(s3)({ time: now }); - yield commit(s3)({ - time: now + 1200 - }); - /** - * cat <==> tic - */ - wotb.isEnabled(0).should.equal(true); - wotb.isEnabled(1).should.equal(true); - // cat3 <==> tic3 - wotb.existsLink(0, 1).should.equal(true); - wotb.existsLink(1, 0).should.equal(true); - // tic3 <==> toc3 - wotb.existsLink(1, 2).should.equal(false); - wotb.existsLink(2, 1).should.equal(false); - }); - }); - - it('third & fourth commits: toc should have joined', function() { - return co(function *() { - yield commit(s3)({ - time: now + 2400 - }); - // MedianTime is now +500 for next certs - yield toc3.createIdentity(); - yield toc3.join(); - yield tic3.cert(toc3); - yield commit(s3)({ - time: now + 4000 - }); - // MedianTime is now +1000 for next certs - /** - * cat <==> tic --> toc - */ - wotb.isEnabled(0).should.equal(true); - wotb.isEnabled(1).should.equal(true); - wotb.isEnabled(2).should.equal(true); - // cat3 <==> tic3 - wotb.existsLink(0, 1).should.equal(true); - wotb.existsLink(1, 0).should.equal(true); - // tic3 <==> toc3 - wotb.existsLink(1, 2).should.equal(true); - wotb.existsLink(2, 1).should.equal(false); - }); - }); - - it('fifth commit: cat still here, but not its certs', function() { - return co(function *() { - yield toc3.cert(tic3); - yield commit(s3)({ - time: now + 4000 - }); - /** - * cat tic <==> toc - */ - wotb.isEnabled(0).should.equal(true); // But marked as to kick: cannot issue new links - wotb.isEnabled(1).should.equal(true); - wotb.isEnabled(2).should.equal(true); - // cat3 <==> tic3 - wotb.existsLink(0, 1).should.equal(false); - wotb.existsLink(1, 0).should.equal(false); - // tic3 <==> toc3 - wotb.existsLink(1, 2).should.equal(true); - wotb.existsLink(2, 1).should.equal(true); - }); - }); - - it('sixth commit: cat is gone with its certs', function() { - return co(function *() { - yield commit(s3)({ - time: now + 2500 - }); - /** - * tic <-- toc - */ - wotb.isEnabled(0).should.equal(false); - wotb.isEnabled(1).should.equal(true); - wotb.isEnabled(2).should.equal(true); - // cat3 <==> tic3 - wotb.existsLink(0, 1).should.equal(false); - wotb.existsLink(1, 0).should.equal(false); - // tic3 --> toc3 - wotb.existsLink(1, 2).should.equal(false); - wotb.existsLink(2, 1).should.equal(true); - }); - }); - - it('seventh commit: toc is gone, but not its cert to tic', function() { - return co(function *() { - yield tic3.cert(cat3); - yield cat3.join(); - yield commit(s3)({ - time: now + 5000 - }); - /** - * cat <-- tic <-- [toc] - */ - wotb.isEnabled(0).should.equal(true); - wotb.isEnabled(1).should.equal(true); - wotb.isEnabled(2).should.equal(false); - // cat3 <==> tic3 - wotb.existsLink(0, 1).should.equal(false); - wotb.existsLink(1, 0).should.equal(true); - // tic3 --> toc3 - wotb.existsLink(1, 2).should.equal(false); - wotb.existsLink(2, 1).should.equal(true); - }); - }); - - it('revert seventh commit: toc is back, cat is gone', function() { - return co(function *() { - yield s3.revert(); - wotb.isEnabled(0).should.equal(false); - wotb.isEnabled(1).should.equal(true); - wotb.isEnabled(2).should.equal(true); - // cat3 <==> tic3 - wotb.existsLink(0, 1).should.equal(false); - wotb.existsLink(1, 0).should.equal(false); - // tic3 --> toc3 - wotb.existsLink(1, 2).should.equal(false); - wotb.existsLink(2, 1).should.equal(true); - }); - }); - - it('revert sixth commit: cat is back', function() { - return co(function *() { - yield s3.revert(); - wotb.isEnabled(0).should.equal(true); // But marked as to kick: cannot issue new links - wotb.isEnabled(1).should.equal(true); - wotb.isEnabled(2).should.equal(true); - // cat3 <==> tic3 - wotb.existsLink(0, 1).should.equal(false); - wotb.existsLink(1, 0).should.equal(false); - // tic3 <==> toc3 - wotb.existsLink(1, 2).should.equal(true); - wotb.existsLink(2, 1).should.equal(true); - }); - }); - - it('revert fifth commit', function() { - return co(function *() { - yield s3.revert(); - wotb.isEnabled(0).should.equal(true); - wotb.isEnabled(1).should.equal(true); - wotb.isEnabled(2).should.equal(true); - // cat3 <==> tic3 - wotb.existsLink(0, 1).should.equal(true); - wotb.existsLink(1, 0).should.equal(true); - // tic3 <==> toc3 - wotb.existsLink(1, 2).should.equal(true); - wotb.existsLink(2, 1).should.equal(false); - }); - }); - - it('revert third & fourth commits', function() { - return co(function *() { - yield s3.revert(); - yield s3.revert(); - wotb.isEnabled(0).should.equal(true); - wotb.isEnabled(1).should.equal(true); - // cat3 <==> tic3 - wotb.existsLink(0, 1).should.equal(true); - wotb.existsLink(1, 0).should.equal(true); - // tic3 <==> toc3 - wotb.existsLink(1, 2).should.equal(false); - wotb.existsLink(2, 1).should.equal(false); - }); - }); - - it('revert first & second commits', function() { - return co(function *() { - yield s3.revert(); - yield s3.revert(); - wotb.isEnabled(0).should.equal(false); - wotb.isEnabled(1).should.equal(false); - wotb.isEnabled(2).should.equal(false); - // cat3 <==> tic3 - wotb.existsLink(0, 1).should.equal(false); - wotb.existsLink(1, 0).should.equal(false); - // tic3 <==> toc3 - wotb.existsLink(1, 2).should.equal(false); - wotb.existsLink(2, 1).should.equal(false); - }); - }); - }); -}); diff --git a/test/integration/ws2p_client_limitations.ts b/test/integration/ws2p/ws2p_client_limitations.ts similarity index 96% rename from test/integration/ws2p_client_limitations.ts rename to test/integration/ws2p/ws2p_client_limitations.ts index 4b16d9f03890068aae9326d02a8683d25de71486..073d71f164f7d6718558ca57cda7f12aa7eed135 100644 --- a/test/integration/ws2p_client_limitations.ts +++ b/test/integration/ws2p/ws2p_client_limitations.ts @@ -19,9 +19,9 @@ import { TestingServer, waitForkWS2PConnection, waitForkWS2PDisconnection -} from "./tools/toolbox" -import {WS2PCluster} from "../../app/modules/ws2p/lib/WS2PCluster" -import {WS2PConstants} from "../../app/modules/ws2p/lib/constants" +} from "../tools/toolbox" +import {WS2PCluster} from "../../../app/modules/ws2p/lib/WS2PCluster" +import {WS2PConstants} from "../../../app/modules/ws2p/lib/constants" const assert = require('assert') @@ -115,10 +115,10 @@ describe("WS2P client limitations", function() { const currentS2 = await s2.BlockchainService.current() const currentS3 = await s3.BlockchainService.current() const currentS4 = await s4.BlockchainService.current() - assert.equal(currentS1.number, 2) - assert.equal(currentS2.number, 2) - assert.equal(currentS3.number, 2) - assert.equal(currentS4.number, 2) + assert.equal(currentS1 && currentS1.number, 2) + assert.equal(currentS2 && currentS2.number, 2) + assert.equal(currentS3 && currentS3.number, 2) + assert.equal(currentS4 && currentS4.number, 2) }) it('should be able to have a connected network on s2 start', async () => { diff --git a/test/integration/ws2p_cluster.ts b/test/integration/ws2p/ws2p_cluster.ts similarity index 94% rename from test/integration/ws2p_cluster.ts rename to test/integration/ws2p/ws2p_cluster.ts index 13adbf4ed14c6aa38bfb387995fa2398db018738..34a30d159ce8cdf1c1e0018134d31a98af22d0f1 100644 --- a/test/integration/ws2p_cluster.ts +++ b/test/integration/ws2p/ws2p_cluster.ts @@ -18,10 +18,10 @@ import { simpleUser, TestingServer, waitForkWS2PConnection -} from "./tools/toolbox" -import {WS2PCluster} from "../../app/modules/ws2p/lib/WS2PCluster" -import {WS2PConstants} from "../../app/modules/ws2p/lib/constants" -import { TestUser } from './tools/TestUser'; +} from "../tools/toolbox" +import {WS2PCluster} from "../../../app/modules/ws2p/lib/WS2PCluster" +import {WS2PConstants} from "../../../app/modules/ws2p/lib/constants" +import {TestUser} from '../tools/TestUser'; const assert = require('assert') @@ -94,9 +94,9 @@ describe("WS2P cluster", function() { const currentS1 = await s1.BlockchainService.current() const currentS2 = await s2.BlockchainService.current() const currentS3 = await s3.BlockchainService.current() - assert.equal(currentS1.number, 2) - assert.equal(currentS2.number, 2) - assert.equal(currentS3.number, 2) + assert.equal(currentS1 && currentS1.number, 2) + assert.equal(currentS2 && currentS2.number, 2) + assert.equal(currentS3 && currentS3.number, 2) }) it('should be able to have a connected network on s2 start', async () => { diff --git a/test/integration/ws2p_connection.ts b/test/integration/ws2p/ws2p_connection.ts similarity index 97% rename from test/integration/ws2p_connection.ts rename to test/integration/ws2p/ws2p_connection.ts index f252d3407b46bd49b45b77e0b8fe36af8ebe9cd4..84defd19e0329e87f6105976ea04209f323d3862 100644 --- a/test/integration/ws2p_connection.ts +++ b/test/integration/ws2p/ws2p_connection.ts @@ -17,15 +17,18 @@ import { WS2PPubkeyLocalAuth, WS2PPubkeyRemoteAuth, WS2PRemoteAuth -} from "../../app/modules/ws2p/lib/WS2PConnection" -import {Key, verify} from "../../app/lib/common-libs/crypto/keyring" -import {assertThrows, getNewTestingPort} from "./tools/toolbox" -import {WS2PMessageHandler} from "../../app/modules/ws2p/lib/impl/WS2PMessageHandler" -import {WS2PResponse} from "../../app/modules/ws2p/lib/impl/WS2PResponse" -import {WS2PConstants} from "../../app/modules/ws2p/lib/constants" +} from "../../../app/modules/ws2p/lib/WS2PConnection" +import {Key, verify} from "../../../app/lib/common-libs/crypto/keyring" +import {getNewTestingPort} from "../tools/toolbox" +import {WS2PMessageHandler} from "../../../app/modules/ws2p/lib/impl/WS2PMessageHandler" +import {WS2PResponse} from "../../../app/modules/ws2p/lib/impl/WS2PResponse" +import {WS2PConstants} from "../../../app/modules/ws2p/lib/constants" +import {assertThrows} from "../../unit-tools" +import {NewLogger} from "../../../app/lib/logger" +import {WebSocketServer} from "../../../app/lib/common-libs/websocket" + const assert = require('assert'); -const WebSocketServer = require('ws').Server -const logger = require('../../app/lib/logger').NewLogger('ws2p') +const logger = NewLogger() const gtest = "gtest" describe('WS2P', () => { diff --git a/test/integration/ws2p_doc_sharing.ts b/test/integration/ws2p/ws2p_doc_sharing.ts similarity index 86% rename from test/integration/ws2p_doc_sharing.ts rename to test/integration/ws2p/ws2p_doc_sharing.ts index b001ef00194768846f24827fb5667de44222abe4..0de1ee131577d8f61133e922781a450d9a4a70ad 100644 --- a/test/integration/ws2p_doc_sharing.ts +++ b/test/integration/ws2p/ws2p_doc_sharing.ts @@ -11,9 +11,9 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -import { TestUser } from './tools/TestUser'; -import {simpleTestingConf, simpleTestingServer, simpleUser, simpleWS2PNetwork, TestingServer} from "./tools/toolbox" -import {WS2PConstants} from "../../app/modules/ws2p/lib/constants" +import {TestUser} from '../tools/TestUser'; +import {simpleTestingConf, simpleTestingServer, simpleUser, simpleWS2PNetwork, TestingServer} from "../tools/toolbox" +import {WS2PConstants} from "../../../app/modules/ws2p/lib/constants" const assert = require('assert') @@ -72,9 +72,9 @@ describe("WS2P doc sharing", function() { await s2.waitToHaveBlock(1) const b1s1 = await s1.BlockchainService.current() const b1s2 = await s2.BlockchainService.current() - assert.equal(b1s1.number, 1) - assert.equal(b1s2.number, 1) - assert.equal(b1s1.hash, b1s2.hash) + assert.equal(b1s1 && b1s1.number, 1) + assert.equal(b1s2 && b1s2.number, 1) + assert.equal(b1s1 && b1s1.hash, b1s2 && b1s2.hash) }) it('should see the identity, certs and memberships in the docpool', async () => { @@ -90,10 +90,10 @@ describe("WS2P doc sharing", function() { await s2.waitToHaveBlock(2) const b2s1 = await s1.BlockchainService.current() const b2s2 = await s2.BlockchainService.current() - assert.equal(b2s1.number, 2) - assert.equal(b2s2.number, 2) - assert.equal(b2s1.hash, b2s2.hash) - assert.equal(b2s2.joiners.length, 1) + assert.equal(b2s1 && b2s1.number, 2) + assert.equal(b2s2 && b2s2.number, 2) + assert.equal(b2s1 && b2s1.hash, b2s2 && b2s2.hash) + assert.equal(b2s2 && b2s2.joiners.length, 1) }) it('should see the transactions pending', async () => { @@ -110,10 +110,10 @@ describe("WS2P doc sharing", function() { await s2.waitToHaveBlock(3) const b3s1 = await s1.BlockchainService.current() const b3s2 = await s2.BlockchainService.current() - assert.equal(b3s1.number, 3) - assert.equal(b3s2.number, 3) - assert.equal(b3s1.hash, b3s2.hash) - assert.equal(b3s2.transactions.length, 1) + assert.equal(b3s1 && b3s1.number, 3) + assert.equal(b3s2 && b3s2.number, 3) + assert.equal(b3s1 && b3s1.hash, b3s2 && b3s2.hash) + assert.equal(b3s2 && b3s2.transactions.length, 1) }) it('should see the peer documents', async () => { diff --git a/test/integration/ws2p_docpool.ts b/test/integration/ws2p/ws2p_docpool.ts similarity index 87% rename from test/integration/ws2p_docpool.ts rename to test/integration/ws2p/ws2p_docpool.ts index fabc209d205ec34a6b4781a0bee393cf427d1b65..1e7c5dde7891b2ccf7c786f53c3b0875c2e2a9d2 100644 --- a/test/integration/ws2p_docpool.ts +++ b/test/integration/ws2p/ws2p_docpool.ts @@ -11,10 +11,10 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -import {simpleTestingConf, simpleTestingServer, simpleUser, simpleWS2PNetwork, TestingServer} from "./tools/toolbox" -import {WS2PCluster} from "../../app/modules/ws2p/lib/WS2PCluster" -import {ProverDependency} from "../../app/modules/prover/index" -import {WS2PConstants} from "../../app/modules/ws2p/lib/constants" +import {simpleTestingConf, simpleTestingServer, simpleUser, simpleWS2PNetwork, TestingServer} from "../tools/toolbox" +import {WS2PCluster} from "../../../app/modules/ws2p/lib/WS2PCluster" +import {ProverDependency} from "../../../app/modules/prover/index" +import {WS2PConstants} from "../../../app/modules/ws2p/lib/constants" const assert = require('assert') @@ -66,8 +66,8 @@ describe("WS2P docpool pulling", function() { it('should have b#2 on s1 and s2', async () => { const currentS1 = await s1.BlockchainService.current() const currentS2 = await s2.BlockchainService.current() - assert.equal(currentS1.number, 2) - assert.equal(currentS2.number, 2) + assert.equal(currentS1 && currentS1.number, 2) + assert.equal(currentS2 && currentS2.number, 2) }) it('should be able to pull the docpool', async () => { @@ -85,7 +85,7 @@ describe("WS2P docpool pulling", function() { }) const currentS1 = await s1.BlockchainService.current() const currentS2 = await s2.BlockchainService.current() - assert.equal(currentS1.number, 2) - assert.equal(currentS2.number, 2) + assert.equal(currentS1 && currentS1.number, 2) + assert.equal(currentS2 && currentS2.number, 2) }) }) diff --git a/test/integration/ws2p_exchange.ts b/test/integration/ws2p/ws2p_exchange.ts similarity index 81% rename from test/integration/ws2p_exchange.ts rename to test/integration/ws2p/ws2p_exchange.ts index 90b43facbabcec943d57b446b7c948828a9b66b1..b5600948c4c8394b4cf7f0f3577dda8778864d6d 100644 --- a/test/integration/ws2p_exchange.ts +++ b/test/integration/ws2p/ws2p_exchange.ts @@ -11,14 +11,15 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -import {WS2PConnection} from "../../app/modules/ws2p/lib/WS2PConnection" -import {Key} from "../../app/lib/common-libs/crypto/keyring" -import {newWS2PBidirectionnalConnection} from "./tools/toolbox" -import {WS2PRequester} from "../../app/modules/ws2p/lib/WS2PRequester" -import {BlockDTO} from "../../app/lib/dto/BlockDTO" -import {WS2PMessageHandler} from "../../app/modules/ws2p/lib/impl/WS2PMessageHandler" -import {WS2PResponse} from "../../app/modules/ws2p/lib/impl/WS2PResponse" -import {WS2PConstants} from "../../app/modules/ws2p/lib/constants" +import {WS2PConnection} from "../../../app/modules/ws2p/lib/WS2PConnection" +import {Key} from "../../../app/lib/common-libs/crypto/keyring" +import {newWS2PBidirectionnalConnection} from "../tools/toolbox" +import {WS2PRequester} from "../../../app/modules/ws2p/lib/WS2PRequester" +import {BlockDTO} from "../../../app/lib/dto/BlockDTO" +import {WS2PMessageHandler} from "../../../app/modules/ws2p/lib/impl/WS2PMessageHandler" +import {WS2PResponse} from "../../../app/modules/ws2p/lib/impl/WS2PResponse" +import {WS2PConstants} from "../../../app/modules/ws2p/lib/constants" + const assert = require('assert'); describe('WS2P exchange', () => { diff --git a/test/integration/ws2p_heads.ts b/test/integration/ws2p/ws2p_heads.ts similarity index 94% rename from test/integration/ws2p_heads.ts rename to test/integration/ws2p/ws2p_heads.ts index a97f7ec425a679c5c17720ba5dc878b95ee31d88..07fbd8e1c982c543c5717bcbb2900158089a4d64 100644 --- a/test/integration/ws2p_heads.ts +++ b/test/integration/ws2p/ws2p_heads.ts @@ -11,9 +11,9 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -import {getNewTestingPort, simpleTestingConf, simpleTestingServer, simpleUser, TestingServer} from "./tools/toolbox" -import {WS2PCluster} from "../../app/modules/ws2p/lib/WS2PCluster" -import {WS2PConstants} from "../../app/modules/ws2p/lib/constants" +import {getNewTestingPort, simpleTestingConf, simpleTestingServer, simpleUser, TestingServer} from "../tools/toolbox" +import {WS2PCluster} from "../../../app/modules/ws2p/lib/WS2PCluster" +import {WS2PConstants} from "../../../app/modules/ws2p/lib/constants" const assert = require('assert') const should = require('should') @@ -72,8 +72,8 @@ describe("WS2P heads propagation", function() { it('should have b#2 on s1, s2 and s3', async () => { const currentS1 = await s1.BlockchainService.current() const currentS2 = await s2.BlockchainService.current() - assert.equal(currentS1.number, 2) - assert.equal(currentS2.number, 2) + assert.equal(currentS1 && currentS1.number, 2) + assert.equal(currentS2 && currentS2.number, 2) }) it('should be able to have a connected network on s2 start', async () => { diff --git a/test/integration/ws2p_network.ts b/test/integration/ws2p/ws2p_network.ts similarity index 93% rename from test/integration/ws2p_network.ts rename to test/integration/ws2p/ws2p_network.ts index e241db09378c52f196d1b15b8b9d8a9d52bea48b..cd3163ec1efe192666cbcf8348c2e44737f979a5 100644 --- a/test/integration/ws2p_network.ts +++ b/test/integration/ws2p/ws2p_network.ts @@ -11,9 +11,9 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -import {getNewTestingPort, simpleTestingConf, simpleTestingServer, simpleUser, TestingServer} from "./tools/toolbox" -import {WS2PCluster} from "../../app/modules/ws2p/lib/WS2PCluster" -import {WS2PConstants} from "../../app/modules/ws2p/lib/constants" +import {getNewTestingPort, simpleTestingConf, simpleTestingServer, simpleUser, TestingServer} from "../tools/toolbox" +import {WS2PCluster} from "../../../app/modules/ws2p/lib/WS2PCluster" +import {WS2PConstants} from "../../../app/modules/ws2p/lib/constants" const assert = require('assert') @@ -85,8 +85,8 @@ describe("WS2P network", function() { it('should have b#2 on s1 and s2', async () => { const currentS1 = await s1.BlockchainService.current() const currentS2 = await s2.BlockchainService.current() - assert.equal(currentS1.number, 2) - assert.equal(currentS2.number, 2) + assert.equal(currentS1 && currentS1.number, 2) + assert.equal(currentS2 && currentS2.number, 2) }) it('should be able to have a connected network on s2 start', async () => { diff --git a/test/integration/ws2p_pulling.ts b/test/integration/ws2p/ws2p_pulling.ts similarity index 64% rename from test/integration/ws2p_pulling.ts rename to test/integration/ws2p/ws2p_pulling.ts index 3f6064c0bdb77a6e843be3fd04ece994dcce4f5e..52f7aaaf448e026d850a17eace500ac704cae3e2 100644 --- a/test/integration/ws2p_pulling.ts +++ b/test/integration/ws2p/ws2p_pulling.ts @@ -11,9 +11,11 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. -import {simpleTestingConf, simpleTestingServer, simpleUser, simpleWS2PNetwork, TestingServer} from "./tools/toolbox" -import {WS2PCluster} from "../../app/modules/ws2p/lib/WS2PCluster" -import {WS2PConstants} from "../../app/modules/ws2p/lib/constants" +import {simpleTestingConf, simpleTestingServer, simpleUser, simpleWS2PNetwork, TestingServer} from "../tools/toolbox" +import {WS2PCluster} from "../../../app/modules/ws2p/lib/WS2PCluster" +import {WS2PConstants} from "../../../app/modules/ws2p/lib/constants" +import {WS2PClient} from "../../../app/modules/ws2p/lib/WS2PClient" +import {TestUser} from "../tools/TestUser" const assert = require('assert') @@ -24,10 +26,13 @@ describe("WS2P block pulling", function() { const now = 1500000000 let s1:TestingServer, s2:TestingServer, wss:any + let ws2pc:WS2PClient + let cluster1:WS2PCluster let cluster2:WS2PCluster - let cat:any, tac:any + let cat:TestUser, tac:TestUser, toc:TestUser const catKeyring = { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'} const tacKeyring = { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'} + const tocKeyring = { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'} let b0, b1, b2 @@ -38,6 +43,7 @@ describe("WS2P block pulling", function() { s2 = simpleTestingServer(conf2) cat = simpleUser('cat', catKeyring, s1) tac = simpleUser('tac', tacKeyring, s1) + toc = simpleUser('toc', tocKeyring, s2) // On S2 await s1.initDalBmaConnections() await s2.initDalBmaConnections() @@ -63,6 +69,8 @@ describe("WS2P block pulling", function() { const network = await simpleWS2PNetwork(s1, s2) wss = network.wss + ws2pc = network.ws2pc + cluster1 = network.cluster1 cluster2 = network.cluster2 }) @@ -71,15 +79,29 @@ describe("WS2P block pulling", function() { it('should have b#6 on s1, b#2 on s2', async () => { const currentS1 = await s1.BlockchainService.current() const currentS2 = await s2.BlockchainService.current() - assert.equal(currentS1.number, 6) - assert.equal(currentS2.number, 2) + assert.equal(currentS1 && currentS1.number, 6) + assert.equal(currentS2 && currentS2.number, 2) }) it('should be able to pull and have the same current block as a result', async () => { await cluster2.pullBlocks() const currentS1 = await s1.BlockchainService.current() const currentS2 = await s2.BlockchainService.current() - assert.equal(currentS1.number, 6) - assert.equal(currentS2.number, 6) + assert.equal(currentS1 && currentS1.number, 6) + assert.equal(currentS2 && currentS2.number, 6) + }) + + it('should be able to pull pending identities', async () => { + assert.equal((await s1.dal.idtyDAL.getPendingIdentities()).length, 0) + assert.equal((await s2.dal.idtyDAL.getPendingIdentities()).length, 0) + // Toc is on S2 by default: we disable the stream s2 => s1 so we can test the pulling + await ws2pc.disableStream() + await toc.createIdentity(); + await toc.join(); + await cat.cert(toc, s2, s2); + await tac.cert(toc, s2, s2); + await cluster1.pullDocpool() + assert.equal((await s1.dal.idtyDAL.getPendingIdentities()).length, 1) + assert.equal((await s2.dal.idtyDAL.getPendingIdentities()).length, 1) }) }) diff --git a/test/integration/ws2p_server_limitations.ts b/test/integration/ws2p/ws2p_server_limitations.ts similarity index 96% rename from test/integration/ws2p_server_limitations.ts rename to test/integration/ws2p/ws2p_server_limitations.ts index 691345d6301374c78cc35419dbb28c6c10484082..36520b9c1874ad683481f0265e03245cf1ea1878 100644 --- a/test/integration/ws2p_server_limitations.ts +++ b/test/integration/ws2p/ws2p_server_limitations.ts @@ -19,9 +19,9 @@ import { TestingServer, waitForkWS2PConnection, waitForkWS2PDisconnection -} from "./tools/toolbox" -import {WS2PCluster} from "../../app/modules/ws2p/lib/WS2PCluster" -import {WS2PConstants} from "../../app/modules/ws2p/lib/constants" +} from "../tools/toolbox" +import {WS2PCluster} from "../../../app/modules/ws2p/lib/WS2PCluster" +import {WS2PConstants} from "../../../app/modules/ws2p/lib/constants" const assert = require('assert') @@ -118,10 +118,10 @@ describe("WS2P server limitations", function() { const currentS2 = await s2.BlockchainService.current() const currentS3 = await s3.BlockchainService.current() const currentS4 = await s4.BlockchainService.current() - assert.equal(currentS1.number, 2) - assert.equal(currentS2.number, 2) - assert.equal(currentS3.number, 2) - assert.equal(currentS4.number, 2) + assert.equal(currentS1 && currentS1.number, 2) + assert.equal(currentS2 && currentS2.number, 2) + assert.equal(currentS3 && currentS3.number, 2) + assert.equal(currentS4 && currentS4.number, 2) }) it('should be able to have a connected network on s2 start', async () => { diff --git a/test/mocha.opts b/test/mocha.opts index 13ee986171fa0dccd4c248679eb3e32bcb7f3517..1d4de1ea87731d261283162bba5ff826bebd5ff4 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -2,7 +2,7 @@ --require source-map-support/register --full-trace --growl ---timeout 20000 +--timeout 30000 --recursive -R spec test/ diff --git a/test/unit-tools.ts b/test/unit-tools.ts new file mode 100644 index 0000000000000000000000000000000000000000..3c85da7e52f1106f53c323128b1c7636e0eb3ce1 --- /dev/null +++ b/test/unit-tools.ts @@ -0,0 +1,62 @@ +// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1 +// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +import * as assert from 'assert' + +export async function shouldThrow(promise:Promise<any>) { + let error = false + try { + await promise + } catch (e) { + error = true + } + promise.should.be.rejected() + error.should.equal(true) +} + +export async function shouldNotFail<T>(promise:Promise<T>) { + try { + await promise + } catch(e) { + let err = e; + if (typeof e === 'string') { + err = JSON.parse((e as any).message) + } + should.not.exist(err); + } +} + +export const shouldFail = async (promise:Promise<any>, message:string|null = null) => { + try { + await promise; + throw '{ "message": "Should have thrown an error" }' + } catch(e) { + let err = e + if (typeof e === "string") { + err = JSON.parse(e) + } + err.should.have.property('message').equal(message); + } +} + +export const assertThrows = async (promise:Promise<any>, message:string|null = null) => { + try { + await promise; + throw "Should have thrown" + } catch(e) { + if (e === "Should have thrown") { + throw e + } + assert.equal(e, message) + } +} diff --git a/tsconfig.json b/tsconfig.json index 0d50c5895ccac504389e3c7489b2006394b331ac..82e16bc8d077bb80f4b440041d75c5aa8eecb323 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,8 @@ "strictNullChecks": true, "noImplicitThis": true, "noImplicitAny": true, - "noImplicitReturns": true + "noImplicitReturns": true, + "experimentalDecorators": true }, "include": [ "server.ts", diff --git a/yarn.lock b/yarn.lock index 27b57764e92f118d2b9267a7f057a165ee4f1760..f2fb840018295aeeb0105b0b57a85800d0ebe0e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,18 +2,78 @@ # yarn lockfile v1 +"@types/events@*": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" + +"@types/fs-extra@5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.0.1.tgz#cd856fbbdd6af2c11f26f8928fd8644c9e9616c9" + dependencies: + "@types/node" "*" + +"@types/glob@*": + version "5.0.35" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-5.0.35.tgz#1ae151c802cece940443b5ac246925c85189f32a" + dependencies: + "@types/events" "*" + "@types/minimatch" "*" + "@types/node" "*" + +"@types/handlebars@4.0.36": + version "4.0.36" + resolved "https://registry.yarnpkg.com/@types/handlebars/-/handlebars-4.0.36.tgz#ff57c77fa1ab6713bb446534ddc4d979707a3a79" + +"@types/highlight.js@9.12.2": + version "9.12.2" + resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-9.12.2.tgz#6ee7cd395effe5ec80b515d3ff1699068cd0cd1d" + +"@types/lodash@4.14.104": + version "4.14.104" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.104.tgz#53ee2357fa2e6e68379341d92eb2ecea4b11bb80" + +"@types/lokijs@^1.5.2": + version "1.5.2" + resolved "https://registry.yarnpkg.com/@types/lokijs/-/lokijs-1.5.2.tgz#ed228f080033ce1fb16eff4acde65cb9ae0f1bf2" + +"@types/marked@0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.3.0.tgz#583c223dd33385a1dda01aaf77b0cd0411c4b524" + +"@types/minimatch@*", "@types/minimatch@3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" + "@types/mocha@^2.2.41": version "2.2.44" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.44.tgz#1d4a798e53f35212fd5ad4d04050620171cd5b5e" +"@types/node@*": + version "10.3.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.3.1.tgz#51092fbacaed768a122a293814474fbf6e5e8b6d" + "@types/node@^8.0.9": version "8.0.53" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.53.tgz#396b35af826fa66aad472c8cb7b8d5e277f4e6d8" +"@types/shelljs@0.7.8": + version "0.7.8" + resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.7.8.tgz#4b4d6ee7926e58d7bca448a50ba442fd9f6715bd" + dependencies: + "@types/glob" "*" + "@types/node" "*" + "@types/should@^8.3.0": version "8.3.0" resolved "https://registry.yarnpkg.com/@types/should/-/should-8.3.0.tgz#e2b460243685dbe377182f39ef38d37f4d157ada" +"@types/ws@^5.1.2": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-5.1.2.tgz#f02d3b1cd46db7686734f3ce83bdf46c49decd64" + dependencies: + "@types/events" "*" + "@types/node" "*" + JSONSelect@0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/JSONSelect/-/JSONSelect-0.4.0.tgz#a08edcc67eb3fcbe99ed630855344a0cf282bb8d" @@ -606,6 +666,12 @@ cli-cursor@^2.1.0: dependencies: restore-cursor "^2.0.0" +cli-table@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23" + dependencies: + colors "1.0.3" + cli-width@^1.0.1: version "1.1.1" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-1.1.1.tgz#a4d293ef67ebb7b88d4a4d42c0ccf00c4d1e366d" @@ -630,7 +696,7 @@ cliui@^3.2.0: strip-ansi "^3.0.1" wrap-ansi "^2.0.0" -co@4.6.0, co@^4.6.0: +co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -658,7 +724,7 @@ colors@0.5.x: version "0.5.1" resolved "https://registry.yarnpkg.com/colors/-/colors-0.5.1.tgz#7d0023eaeb154e8ee9fce75dcb923d0ed1667774" -colors@1.0.x: +colors@1.0.3, colors@1.0.x: version "1.0.3" resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" @@ -1523,6 +1589,14 @@ fs-extra@^0.22.1: jsonfile "^2.1.0" rimraf "^2.2.8" +fs-extra@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-5.0.0.tgz#414d0110cdd06705734d055652c5411260c31abd" + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -1682,7 +1756,7 @@ growl@1.9.2: version "1.9.2" resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" -handlebars@^4.0.3: +handlebars@^4.0.3, handlebars@^4.0.6: version "4.0.11" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc" dependencies: @@ -1773,6 +1847,10 @@ he@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" +highlight.js@^9.0.0: + version "9.12.0" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e" + hoek@0.9.x: version "0.9.1" resolved "https://registry.yarnpkg.com/hoek/-/hoek-0.9.1.tgz#3d322462badf07716ea7eb85baf88079cddce505" @@ -2205,6 +2283,12 @@ jsonfile@^2.1.0: optionalDependencies: graceful-fs "^4.1.6" +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + optionalDependencies: + graceful-fs "^4.1.6" + jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" @@ -2363,10 +2447,18 @@ lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.4, lodash@^4.3.0, lo version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" +lodash@^4.17.5: + version "4.17.10" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" + log-driver@1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.4.tgz#2d62d7faef45d8a71341961a04b0761eca99cfa3" +lokijs@^1.5.3: + version "1.5.3" + resolved "https://registry.yarnpkg.com/lokijs/-/lokijs-1.5.3.tgz#6952722ffa3049a55a5e1c10ee4a0947a3e5e19b" + longest@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" @@ -2392,6 +2484,10 @@ map-stream@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194" +marked@^0.3.17: + version "0.3.19" + resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.19.tgz#5d47f709c4c9fc3c216b6d46127280f40b39d790" + "match-stream@>= 0.0.2 < 1", match-stream@~0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/match-stream/-/match-stream-0.0.2.tgz#99eb050093b34dffade421b9ac0b410a9cfa17cf" @@ -3035,6 +3131,10 @@ progress@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" +progress@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" + proxy-addr@~1.1.3: version "1.1.5" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.5.tgz#71c0ee3b102de3f202f3b64f608d173fcba1a918" @@ -3059,15 +3159,15 @@ punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" -q-io@1.13.2: - version "1.13.2" - resolved "https://registry.yarnpkg.com/q-io/-/q-io-1.13.2.tgz#eea130d481ddb5e1aa1bc5a66855f7391d06f003" +q-io@^1.13.5: + version "1.13.5" + resolved "https://registry.yarnpkg.com/q-io/-/q-io-1.13.5.tgz#6ac39deb5cfe0dc68436e6f8c33d0d7c3f471bb2" dependencies: collections "^0.2.0" mime "^1.2.11" mimeparse "^0.1.4" q "^1.0.1" - qs "^1.2.1" + qs "^6.4.0" url2 "^0.0.0" q@^1.0.1: @@ -3078,11 +3178,7 @@ qs@6.4.0, qs@~6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" -qs@^1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-1.2.2.tgz#19b57ff24dc2a99ce1f8bdf6afcda59f8ef61f88" - -qs@^6.5.1, qs@~6.5.1: +qs@^6.4.0, qs@^6.5.1, qs@~6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" @@ -3518,6 +3614,14 @@ shelljs@^0.7.5: interpret "^1.0.0" rechoir "^0.6.2" +shelljs@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.2.tgz#345b7df7763f4c2340d584abb532c5f752ca9e35" + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + should-equal@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/should-equal/-/should-equal-2.0.0.tgz#6072cf83047360867e68e98b09d71143d04ee0c3" @@ -4023,9 +4127,43 @@ typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" -typescript@^2.4.1: - version "2.6.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" +typedoc-default-themes@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/typedoc-default-themes/-/typedoc-default-themes-0.5.0.tgz#6dc2433e78ed8bea8e887a3acde2f31785bd6227" + +typedoc-plugin-sourcefile-url@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typedoc-plugin-sourcefile-url/-/typedoc-plugin-sourcefile-url-1.0.3.tgz#fbbcc4b71bd92d2f794d1c169a480042296b1fe6" + +typedoc@^0.11.1: + version "0.11.1" + resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.11.1.tgz#9f033887fd2218c769e1045feb88a1efed9f12c9" + dependencies: + "@types/fs-extra" "5.0.1" + "@types/handlebars" "4.0.36" + "@types/highlight.js" "9.12.2" + "@types/lodash" "4.14.104" + "@types/marked" "0.3.0" + "@types/minimatch" "3.0.3" + "@types/shelljs" "0.7.8" + fs-extra "^5.0.0" + handlebars "^4.0.6" + highlight.js "^9.0.0" + lodash "^4.17.5" + marked "^0.3.17" + minimatch "^3.0.0" + progress "^2.0.0" + shelljs "^0.8.1" + typedoc-default-themes "^0.5.0" + typescript "2.7.2" + +typescript@2.7.2: + version "2.7.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.7.2.tgz#2d615a1ef4aee4f574425cdff7026edf81919836" + +typescript@~2.8.1: + version "2.8.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.8.4.tgz#0b1db68e6bdfb0b767fa2ab642136a35b059b199" uglify-js@^2.6: version "2.8.29" @@ -4068,6 +4206,10 @@ underscore@~1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.7.0.tgz#6bbaf0877500d36be34ecaa584e0db9fef035209" +universalify@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7" + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"