diff --git a/.circleci/config.yml b/.circleci/config.yml index 09f26fae65..0cb5376d77 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,17 +1,9 @@ version: 2 jobs: - test-node10-0: + test-node10-unit: working_directory: ~/core - environment: - CORE_DB_DATABASE: core_unitnet - CORE_DB_USERNAME: core docker: - image: 'circleci/node:10-browsers' - - image: 'postgres:alpine' - environment: - POSTGRES_PASSWORD: password - POSTGRES_DB: core_unitnet - POSTGRES_USER: core steps: - checkout - run: @@ -20,20 +12,20 @@ jobs: sudo sh -c 'echo "deb http://ftp.debian.org/debian stable main contrib non-free" >> /etc/apt/sources.list' && sudo apt-get update - run: - name: Install xsel & postgresql-client - command: sudo apt-get install -q xsel postgresql-client + name: Install xsel + command: sudo apt-get install -q xsel - run: name: Generate cache key command: >- find ./packages/ -name package.json -print0 | sort -z | xargs -r0 echo ./package.json | xargs md5sum | md5sum - > checksum.txt - restore_cache: - key: 'core-node10-{{ checksum "checksum.txt" }}-1' + key: 'core-node10-{{ checksum "checksum.txt" }}-unit' - run: name: Install and build packages command: yarn setup - save_cache: - key: 'core-node10-{{ checksum "checksum.txt" }}-1' + key: 'core-node10-{{ checksum "checksum.txt" }}-unit' paths: - ./packages/core/node_modules - ./packages/core-api/node_modules @@ -41,24 +33,28 @@ jobs: - ./packages/core-container/node_modules - ./packages/core-database/node_modules - ./packages/core-database-postgres/node_modules - - ./packages/core-debugger-cli/node_modules - ./packages/core-elasticsearch/node_modules + - ./packages/core-error-tracker-airbrake/node_modules - ./packages/core-error-tracker-bugsnag/node_modules + - ./packages/core-error-tracker-raygun/node_modules + - ./packages/core-error-tracker-rollbar/node_modules - ./packages/core-error-tracker-sentry/node_modules - ./packages/core-event-emitter/node_modules - ./packages/core-forger/node_modules - - ./packages/core-graphql/node_modules - ./packages/core-http-utils/node_modules - ./packages/core-interfaces/node_modules - ./packages/core-jest-matchers/node_modules - ./packages/core-json-rpc/node_modules - ./packages/core-logger/node_modules + - ./packages/core-logger-pino/node_modules + - ./packages/core-logger-signale/node_modules - ./packages/core-logger-winston/node_modules + - ./packages/core-new-relic/node_modules - ./packages/core-p2p/node_modules - ./packages/core-snapshots/node_modules - - ./packages/core-test-utils/node_modules - ./packages/core-tester-cli/node_modules - ./packages/core-transaction-pool/node_modules + - ./packages/core-transactions/node_modules - ./packages/core-utils/node_modules - ./packages/core-vote-report/node_modules - ./packages/core-webhooks/node_modules @@ -68,35 +64,88 @@ jobs: name: Create .core/database directory command: mkdir -p $HOME/.core/database - run: - name: core-api + name: Unit tests command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-api && yarn test:coverage + cd ~/core && yarn test:unit:coverage --coverageDirectory + .coverage/unit/ --maxWorkers=2 - run: - name: core-blockchain - command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-blockchain && yarn test:coverage + name: Last 1000 lines of test output + when: on_fail + command: tail -n 1000 test_output.txt - run: - name: core-container - command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-container && yarn test:coverage + name: Lint + command: yarn lint - run: - name: core-database + name: Codecov + command: ./node_modules/.bin/codecov + test-node11-unit: + working_directory: ~/core + docker: + - image: 'circleci/node:11-browsers' + steps: + - checkout + - run: + name: Apt update command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-database && yarn test:coverage + sudo sh -c 'echo "deb http://ftp.debian.org/debian stable main + contrib non-free" >> /etc/apt/sources.list' && sudo apt-get update + - run: + name: Install xsel + command: sudo apt-get install -q xsel - run: - name: core-database-postgres + name: Generate cache key command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-database-postgres && yarn test:coverage + find ./packages/ -name package.json -print0 | sort -z | xargs -r0 + echo ./package.json | xargs md5sum | md5sum - > checksum.txt + - restore_cache: + key: 'core-node11-{{ checksum "checksum.txt" }}-unit' - run: - name: core-debugger-cli + name: Install and build packages + command: yarn setup + - save_cache: + key: 'core-node11-{{ checksum "checksum.txt" }}-unit' + paths: + - ./packages/core/node_modules + - ./packages/core-api/node_modules + - ./packages/core-blockchain/node_modules + - ./packages/core-container/node_modules + - ./packages/core-database/node_modules + - ./packages/core-database-postgres/node_modules + - ./packages/core-elasticsearch/node_modules + - ./packages/core-error-tracker-airbrake/node_modules + - ./packages/core-error-tracker-bugsnag/node_modules + - ./packages/core-error-tracker-raygun/node_modules + - ./packages/core-error-tracker-rollbar/node_modules + - ./packages/core-error-tracker-sentry/node_modules + - ./packages/core-event-emitter/node_modules + - ./packages/core-forger/node_modules + - ./packages/core-http-utils/node_modules + - ./packages/core-interfaces/node_modules + - ./packages/core-jest-matchers/node_modules + - ./packages/core-json-rpc/node_modules + - ./packages/core-logger/node_modules + - ./packages/core-logger-pino/node_modules + - ./packages/core-logger-signale/node_modules + - ./packages/core-logger-winston/node_modules + - ./packages/core-new-relic/node_modules + - ./packages/core-p2p/node_modules + - ./packages/core-snapshots/node_modules + - ./packages/core-tester-cli/node_modules + - ./packages/core-transaction-pool/node_modules + - ./packages/core-transactions/node_modules + - ./packages/core-utils/node_modules + - ./packages/core-vote-report/node_modules + - ./packages/core-webhooks/node_modules + - ./packages/crypto/node_modules + - ./node_modules + - run: + name: Create .core/database directory + command: mkdir -p $HOME/.core/database + - run: + name: Unit tests command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-debugger-cli && yarn test:coverage + cd ~/core && yarn test:unit:coverage --coverageDirectory + .coverage/unit/ --maxWorkers=2 - run: name: Last 1000 lines of test output when: on_fail @@ -107,13 +156,13 @@ jobs: - run: name: Codecov command: ./node_modules/.bin/codecov - test-node11-0: + test-node10-functional: working_directory: ~/core environment: CORE_DB_DATABASE: core_unitnet CORE_DB_USERNAME: core docker: - - image: 'circleci/node:11-browsers' + - image: 'circleci/node:10-browsers' - image: 'postgres:alpine' environment: POSTGRES_PASSWORD: password @@ -135,12 +184,12 @@ jobs: find ./packages/ -name package.json -print0 | sort -z | xargs -r0 echo ./package.json | xargs md5sum | md5sum - > checksum.txt - restore_cache: - key: 'core-node11-{{ checksum "checksum.txt" }}-1' + key: 'core-node10-{{ checksum "checksum.txt" }}-functional' - run: name: Install and build packages command: yarn setup - save_cache: - key: 'core-node11-{{ checksum "checksum.txt" }}-1' + key: 'core-node10-{{ checksum "checksum.txt" }}-functional' paths: - ./packages/core/node_modules - ./packages/core-api/node_modules @@ -148,24 +197,28 @@ jobs: - ./packages/core-container/node_modules - ./packages/core-database/node_modules - ./packages/core-database-postgres/node_modules - - ./packages/core-debugger-cli/node_modules - ./packages/core-elasticsearch/node_modules + - ./packages/core-error-tracker-airbrake/node_modules - ./packages/core-error-tracker-bugsnag/node_modules + - ./packages/core-error-tracker-raygun/node_modules + - ./packages/core-error-tracker-rollbar/node_modules - ./packages/core-error-tracker-sentry/node_modules - ./packages/core-event-emitter/node_modules - ./packages/core-forger/node_modules - - ./packages/core-graphql/node_modules - ./packages/core-http-utils/node_modules - ./packages/core-interfaces/node_modules - ./packages/core-jest-matchers/node_modules - ./packages/core-json-rpc/node_modules - ./packages/core-logger/node_modules + - ./packages/core-logger-pino/node_modules + - ./packages/core-logger-signale/node_modules - ./packages/core-logger-winston/node_modules + - ./packages/core-new-relic/node_modules - ./packages/core-p2p/node_modules - ./packages/core-snapshots/node_modules - - ./packages/core-test-utils/node_modules - ./packages/core-tester-cli/node_modules - ./packages/core-transaction-pool/node_modules + - ./packages/core-transactions/node_modules - ./packages/core-utils/node_modules - ./packages/core-vote-report/node_modules - ./packages/core-webhooks/node_modules @@ -175,35 +228,96 @@ jobs: name: Create .core/database directory command: mkdir -p $HOME/.core/database - run: - name: core-api + name: Functional tests command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-api && yarn test:coverage + cd ~/core && yarn test:functional:coverage --coverageDirectory + .coverage/functional/ - run: - name: core-blockchain - command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-blockchain && yarn test:coverage + name: Last 1000 lines of test output + when: on_fail + command: tail -n 1000 test_output.txt - run: - name: core-container - command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-container && yarn test:coverage + name: Lint + command: yarn lint + - run: + name: Codecov + command: ./node_modules/.bin/codecov + test-node11-functional: + working_directory: ~/core + environment: + CORE_DB_DATABASE: core_unitnet + CORE_DB_USERNAME: core + docker: + - image: 'circleci/node:11-browsers' + - image: 'postgres:alpine' + environment: + POSTGRES_PASSWORD: password + POSTGRES_DB: core_unitnet + POSTGRES_USER: core + steps: + - checkout - run: - name: core-database + name: Apt update command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-database && yarn test:coverage + sudo sh -c 'echo "deb http://ftp.debian.org/debian stable main + contrib non-free" >> /etc/apt/sources.list' && sudo apt-get update - run: - name: core-database-postgres + name: Install xsel & postgresql-client + command: sudo apt-get install -q xsel postgresql-client + - run: + name: Generate cache key command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-database-postgres && yarn test:coverage + find ./packages/ -name package.json -print0 | sort -z | xargs -r0 + echo ./package.json | xargs md5sum | md5sum - > checksum.txt + - restore_cache: + key: 'core-node11-{{ checksum "checksum.txt" }}-functional' - run: - name: core-debugger-cli + name: Install and build packages + command: yarn setup + - save_cache: + key: 'core-node11-{{ checksum "checksum.txt" }}-functional' + paths: + - ./packages/core/node_modules + - ./packages/core-api/node_modules + - ./packages/core-blockchain/node_modules + - ./packages/core-container/node_modules + - ./packages/core-database/node_modules + - ./packages/core-database-postgres/node_modules + - ./packages/core-elasticsearch/node_modules + - ./packages/core-error-tracker-airbrake/node_modules + - ./packages/core-error-tracker-bugsnag/node_modules + - ./packages/core-error-tracker-raygun/node_modules + - ./packages/core-error-tracker-rollbar/node_modules + - ./packages/core-error-tracker-sentry/node_modules + - ./packages/core-event-emitter/node_modules + - ./packages/core-forger/node_modules + - ./packages/core-http-utils/node_modules + - ./packages/core-interfaces/node_modules + - ./packages/core-jest-matchers/node_modules + - ./packages/core-json-rpc/node_modules + - ./packages/core-logger/node_modules + - ./packages/core-logger-pino/node_modules + - ./packages/core-logger-signale/node_modules + - ./packages/core-logger-winston/node_modules + - ./packages/core-new-relic/node_modules + - ./packages/core-p2p/node_modules + - ./packages/core-snapshots/node_modules + - ./packages/core-tester-cli/node_modules + - ./packages/core-transaction-pool/node_modules + - ./packages/core-transactions/node_modules + - ./packages/core-utils/node_modules + - ./packages/core-vote-report/node_modules + - ./packages/core-webhooks/node_modules + - ./packages/crypto/node_modules + - ./node_modules + - run: + name: Create .core/database directory + command: mkdir -p $HOME/.core/database + - run: + name: Functional tests command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-debugger-cli && yarn test:coverage + cd ~/core && yarn test:functional:coverage --coverageDirectory + .coverage/functional/ - run: name: Last 1000 lines of test output when: on_fail @@ -214,7 +328,7 @@ jobs: - run: name: Codecov command: ./node_modules/.bin/codecov - test-node10-1: + test-node10-integration-0: working_directory: ~/core environment: CORE_DB_DATABASE: core_unitnet @@ -255,24 +369,28 @@ jobs: - ./packages/core-container/node_modules - ./packages/core-database/node_modules - ./packages/core-database-postgres/node_modules - - ./packages/core-debugger-cli/node_modules - ./packages/core-elasticsearch/node_modules + - ./packages/core-error-tracker-airbrake/node_modules - ./packages/core-error-tracker-bugsnag/node_modules + - ./packages/core-error-tracker-raygun/node_modules + - ./packages/core-error-tracker-rollbar/node_modules - ./packages/core-error-tracker-sentry/node_modules - ./packages/core-event-emitter/node_modules - ./packages/core-forger/node_modules - - ./packages/core-graphql/node_modules - ./packages/core-http-utils/node_modules - ./packages/core-interfaces/node_modules - ./packages/core-jest-matchers/node_modules - ./packages/core-json-rpc/node_modules - ./packages/core-logger/node_modules + - ./packages/core-logger-pino/node_modules + - ./packages/core-logger-signale/node_modules - ./packages/core-logger-winston/node_modules + - ./packages/core-new-relic/node_modules - ./packages/core-p2p/node_modules - ./packages/core-snapshots/node_modules - - ./packages/core-test-utils/node_modules - ./packages/core-tester-cli/node_modules - ./packages/core-transaction-pool/node_modules + - ./packages/core-transactions/node_modules - ./packages/core-utils/node_modules - ./packages/core-vote-report/node_modules - ./packages/core-webhooks/node_modules @@ -282,50 +400,96 @@ jobs: name: Create .core/database directory command: mkdir -p $HOME/.core/database - run: - name: core-event-emitter + name: Unit tests command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-event-emitter && yarn test:coverage + cd ~/core && yarn test:unit:coverage --coverageDirectory + .coverage/unit/ --maxWorkers=2 - run: - name: core-forger - command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-forger && yarn test:coverage + name: Last 1000 lines of test output + when: on_fail + command: tail -n 1000 test_output.txt - run: - name: core-graphql - command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-graphql && yarn test:coverage + name: Lint + command: yarn lint - run: - name: core-http-utils - command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-http-utils && yarn test:coverage + name: Codecov + command: ./node_modules/.bin/codecov + test-node11-integration-0: + working_directory: ~/core + environment: + CORE_DB_DATABASE: core_unitnet + CORE_DB_USERNAME: core + docker: + - image: 'circleci/node:11-browsers' + - image: 'postgres:alpine' + environment: + POSTGRES_PASSWORD: password + POSTGRES_DB: core_unitnet + POSTGRES_USER: core + steps: + - checkout - run: - name: core-jest-matchers + name: Apt update command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-jest-matchers && yarn test:coverage + sudo sh -c 'echo "deb http://ftp.debian.org/debian stable main + contrib non-free" >> /etc/apt/sources.list' && sudo apt-get update - run: - name: core-json-rpc - command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-json-rpc && yarn test:coverage + name: Install xsel & postgresql-client + command: sudo apt-get install -q xsel postgresql-client - run: - name: core-logger + name: Generate cache key command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-logger && yarn test:coverage + find ./packages/ -name package.json -print0 | sort -z | xargs -r0 + echo ./package.json | xargs md5sum | md5sum - > checksum.txt + - restore_cache: + key: 'core-node11-{{ checksum "checksum.txt" }}-1' - run: - name: core-logger-winston - command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-logger-winston && yarn test:coverage + name: Install and build packages + command: yarn setup + - save_cache: + key: 'core-node11-{{ checksum "checksum.txt" }}-1' + paths: + - ./packages/core/node_modules + - ./packages/core-api/node_modules + - ./packages/core-blockchain/node_modules + - ./packages/core-container/node_modules + - ./packages/core-database/node_modules + - ./packages/core-database-postgres/node_modules + - ./packages/core-elasticsearch/node_modules + - ./packages/core-error-tracker-airbrake/node_modules + - ./packages/core-error-tracker-bugsnag/node_modules + - ./packages/core-error-tracker-raygun/node_modules + - ./packages/core-error-tracker-rollbar/node_modules + - ./packages/core-error-tracker-sentry/node_modules + - ./packages/core-event-emitter/node_modules + - ./packages/core-forger/node_modules + - ./packages/core-http-utils/node_modules + - ./packages/core-interfaces/node_modules + - ./packages/core-jest-matchers/node_modules + - ./packages/core-json-rpc/node_modules + - ./packages/core-logger/node_modules + - ./packages/core-logger-pino/node_modules + - ./packages/core-logger-signale/node_modules + - ./packages/core-logger-winston/node_modules + - ./packages/core-new-relic/node_modules + - ./packages/core-p2p/node_modules + - ./packages/core-snapshots/node_modules + - ./packages/core-tester-cli/node_modules + - ./packages/core-transaction-pool/node_modules + - ./packages/core-transactions/node_modules + - ./packages/core-utils/node_modules + - ./packages/core-vote-report/node_modules + - ./packages/core-webhooks/node_modules + - ./packages/crypto/node_modules + - ./node_modules - run: - name: core-p2p + name: Create .core/database directory + command: mkdir -p $HOME/.core/database + - run: + name: Unit tests command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-p2p && yarn test:coverage + cd ~/core && yarn test:unit:coverage --coverageDirectory + .coverage/unit/ --maxWorkers=2 - run: name: Last 1000 lines of test output when: on_fail @@ -336,7 +500,7 @@ jobs: - run: name: Codecov command: ./node_modules/.bin/codecov - test-node10-2: + test-node10-integration-1: working_directory: ~/core environment: CORE_DB_DATABASE: core_unitnet @@ -377,24 +541,28 @@ jobs: - ./packages/core-container/node_modules - ./packages/core-database/node_modules - ./packages/core-database-postgres/node_modules - - ./packages/core-debugger-cli/node_modules - ./packages/core-elasticsearch/node_modules + - ./packages/core-error-tracker-airbrake/node_modules - ./packages/core-error-tracker-bugsnag/node_modules + - ./packages/core-error-tracker-raygun/node_modules + - ./packages/core-error-tracker-rollbar/node_modules - ./packages/core-error-tracker-sentry/node_modules - ./packages/core-event-emitter/node_modules - ./packages/core-forger/node_modules - - ./packages/core-graphql/node_modules - ./packages/core-http-utils/node_modules - ./packages/core-interfaces/node_modules - ./packages/core-jest-matchers/node_modules - ./packages/core-json-rpc/node_modules - ./packages/core-logger/node_modules + - ./packages/core-logger-pino/node_modules + - ./packages/core-logger-signale/node_modules - ./packages/core-logger-winston/node_modules + - ./packages/core-new-relic/node_modules - ./packages/core-p2p/node_modules - ./packages/core-snapshots/node_modules - - ./packages/core-test-utils/node_modules - ./packages/core-tester-cli/node_modules - ./packages/core-transaction-pool/node_modules + - ./packages/core-transactions/node_modules - ./packages/core-utils/node_modules - ./packages/core-vote-report/node_modules - ./packages/core-webhooks/node_modules @@ -404,45 +572,140 @@ jobs: name: Create .core/database directory command: mkdir -p $HOME/.core/database - run: - name: core-snapshots + name: core-api - integration command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-snapshots && yarn test:coverage + cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core && yarn + test:coverage /integration/core-api/ --coverageDirectory + .coverage/integration/core-api - run: - name: core-test-utils + name: core-blockchain - integration command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-test-utils && yarn test:coverage + cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core && yarn + test:coverage /integration/core-blockchain/ --coverageDirectory + .coverage/integration/core-blockchain + - run: + name: core-database-postgres - integration + command: >- + cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core && yarn + test:coverage /integration/core-database-postgres/ + --coverageDirectory .coverage/integration/core-database-postgres + - run: + name: core-forger - integration + command: >- + cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core && yarn + test:coverage /integration/core-forger/ --coverageDirectory + .coverage/integration/core-forger + - run: + name: core-json-rpc - integration + command: >- + cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core && yarn + test:coverage /integration/core-json-rpc/ --coverageDirectory + .coverage/integration/core-json-rpc + - run: + name: Last 1000 lines of test output + when: on_fail + command: tail -n 1000 test_output.txt + - run: + name: Lint + command: yarn lint + - run: + name: Codecov + command: ./node_modules/.bin/codecov + test-node10-integration-2: + working_directory: ~/core + environment: + CORE_DB_DATABASE: core_unitnet + CORE_DB_USERNAME: core + docker: + - image: 'circleci/node:10-browsers' + - image: 'postgres:alpine' + environment: + POSTGRES_PASSWORD: password + POSTGRES_DB: core_unitnet + POSTGRES_USER: core + steps: + - checkout - run: - name: core-tester-cli + name: Apt update command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-tester-cli && yarn test:coverage + sudo sh -c 'echo "deb http://ftp.debian.org/debian stable main + contrib non-free" >> /etc/apt/sources.list' && sudo apt-get update + - run: + name: Install xsel & postgresql-client + command: sudo apt-get install -q xsel postgresql-client - run: - name: core-transaction-pool + name: Generate cache key command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-transaction-pool && yarn test:coverage + find ./packages/ -name package.json -print0 | sort -z | xargs -r0 + echo ./package.json | xargs md5sum | md5sum - > checksum.txt + - restore_cache: + key: 'core-node10-{{ checksum "checksum.txt" }}-1' - run: - name: core-utils + name: Install and build packages + command: yarn setup + - save_cache: + key: 'core-node10-{{ checksum "checksum.txt" }}-1' + paths: + - ./packages/core/node_modules + - ./packages/core-api/node_modules + - ./packages/core-blockchain/node_modules + - ./packages/core-container/node_modules + - ./packages/core-database/node_modules + - ./packages/core-database-postgres/node_modules + - ./packages/core-elasticsearch/node_modules + - ./packages/core-error-tracker-airbrake/node_modules + - ./packages/core-error-tracker-bugsnag/node_modules + - ./packages/core-error-tracker-raygun/node_modules + - ./packages/core-error-tracker-rollbar/node_modules + - ./packages/core-error-tracker-sentry/node_modules + - ./packages/core-event-emitter/node_modules + - ./packages/core-forger/node_modules + - ./packages/core-http-utils/node_modules + - ./packages/core-interfaces/node_modules + - ./packages/core-jest-matchers/node_modules + - ./packages/core-json-rpc/node_modules + - ./packages/core-logger/node_modules + - ./packages/core-logger-pino/node_modules + - ./packages/core-logger-signale/node_modules + - ./packages/core-logger-winston/node_modules + - ./packages/core-new-relic/node_modules + - ./packages/core-p2p/node_modules + - ./packages/core-snapshots/node_modules + - ./packages/core-tester-cli/node_modules + - ./packages/core-transaction-pool/node_modules + - ./packages/core-transactions/node_modules + - ./packages/core-utils/node_modules + - ./packages/core-vote-report/node_modules + - ./packages/core-webhooks/node_modules + - ./packages/crypto/node_modules + - ./node_modules + - run: + name: Create .core/database directory + command: mkdir -p $HOME/.core/database + - run: + name: core-tester-cli - integration command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-utils && yarn test:coverage + cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core && yarn + test:coverage /integration/core-tester-cli/ --coverageDirectory + .coverage/integration/core-tester-cli - run: - name: core-vote-report + name: core-transaction-pool - integration command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-vote-report && yarn test:coverage + cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core && yarn + test:coverage /integration/core-transaction-pool/ + --coverageDirectory .coverage/integration/core-transaction-pool - run: - name: core-webhooks + name: core-vote-report - integration command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-webhooks && yarn test:coverage + cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core && yarn + test:coverage /integration/core-vote-report/ --coverageDirectory + .coverage/integration/core-vote-report - run: - name: crypto + name: core-webhooks - integration command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core/packages/crypto - && yarn test:coverage + cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core && yarn + test:coverage /integration/core-webhooks/ --coverageDirectory + .coverage/integration/core-webhooks - run: name: Last 1000 lines of test output when: on_fail @@ -453,7 +716,7 @@ jobs: - run: name: Codecov command: ./node_modules/.bin/codecov - test-node11-1: + test-node11-integration-1: working_directory: ~/core environment: CORE_DB_DATABASE: core_unitnet @@ -494,24 +757,28 @@ jobs: - ./packages/core-container/node_modules - ./packages/core-database/node_modules - ./packages/core-database-postgres/node_modules - - ./packages/core-debugger-cli/node_modules - ./packages/core-elasticsearch/node_modules + - ./packages/core-error-tracker-airbrake/node_modules - ./packages/core-error-tracker-bugsnag/node_modules + - ./packages/core-error-tracker-raygun/node_modules + - ./packages/core-error-tracker-rollbar/node_modules - ./packages/core-error-tracker-sentry/node_modules - ./packages/core-event-emitter/node_modules - ./packages/core-forger/node_modules - - ./packages/core-graphql/node_modules - ./packages/core-http-utils/node_modules - ./packages/core-interfaces/node_modules - ./packages/core-jest-matchers/node_modules - ./packages/core-json-rpc/node_modules - ./packages/core-logger/node_modules + - ./packages/core-logger-pino/node_modules + - ./packages/core-logger-signale/node_modules - ./packages/core-logger-winston/node_modules + - ./packages/core-new-relic/node_modules - ./packages/core-p2p/node_modules - ./packages/core-snapshots/node_modules - - ./packages/core-test-utils/node_modules - ./packages/core-tester-cli/node_modules - ./packages/core-transaction-pool/node_modules + - ./packages/core-transactions/node_modules - ./packages/core-utils/node_modules - ./packages/core-vote-report/node_modules - ./packages/core-webhooks/node_modules @@ -521,50 +788,35 @@ jobs: name: Create .core/database directory command: mkdir -p $HOME/.core/database - run: - name: core-event-emitter - command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-event-emitter && yarn test:coverage - - run: - name: core-forger - command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-forger && yarn test:coverage - - run: - name: core-graphql - command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-graphql && yarn test:coverage - - run: - name: core-http-utils - command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-http-utils && yarn test:coverage - - run: - name: core-jest-matchers + name: core-api - integration command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-jest-matchers && yarn test:coverage + cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core && yarn + test:coverage /integration/core-api/ --coverageDirectory + .coverage/integration/core-api - run: - name: core-json-rpc + name: core-blockchain - integration command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-json-rpc && yarn test:coverage + cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core && yarn + test:coverage /integration/core-blockchain/ --coverageDirectory + .coverage/integration/core-blockchain - run: - name: core-logger + name: core-database-postgres - integration command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-logger && yarn test:coverage + cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core && yarn + test:coverage /integration/core-database-postgres/ + --coverageDirectory .coverage/integration/core-database-postgres - run: - name: core-logger-winston + name: core-forger - integration command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-logger-winston && yarn test:coverage + cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core && yarn + test:coverage /integration/core-forger/ --coverageDirectory + .coverage/integration/core-forger - run: - name: core-p2p + name: core-json-rpc - integration command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-p2p && yarn test:coverage + cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core && yarn + test:coverage /integration/core-json-rpc/ --coverageDirectory + .coverage/integration/core-json-rpc - run: name: Last 1000 lines of test output when: on_fail @@ -575,7 +827,7 @@ jobs: - run: name: Codecov command: ./node_modules/.bin/codecov - test-node11-2: + test-node11-integration-2: working_directory: ~/core environment: CORE_DB_DATABASE: core_unitnet @@ -616,24 +868,28 @@ jobs: - ./packages/core-container/node_modules - ./packages/core-database/node_modules - ./packages/core-database-postgres/node_modules - - ./packages/core-debugger-cli/node_modules - ./packages/core-elasticsearch/node_modules + - ./packages/core-error-tracker-airbrake/node_modules - ./packages/core-error-tracker-bugsnag/node_modules + - ./packages/core-error-tracker-raygun/node_modules + - ./packages/core-error-tracker-rollbar/node_modules - ./packages/core-error-tracker-sentry/node_modules - ./packages/core-event-emitter/node_modules - ./packages/core-forger/node_modules - - ./packages/core-graphql/node_modules - ./packages/core-http-utils/node_modules - ./packages/core-interfaces/node_modules - ./packages/core-jest-matchers/node_modules - ./packages/core-json-rpc/node_modules - ./packages/core-logger/node_modules + - ./packages/core-logger-pino/node_modules + - ./packages/core-logger-signale/node_modules - ./packages/core-logger-winston/node_modules + - ./packages/core-new-relic/node_modules - ./packages/core-p2p/node_modules - ./packages/core-snapshots/node_modules - - ./packages/core-test-utils/node_modules - ./packages/core-tester-cli/node_modules - ./packages/core-transaction-pool/node_modules + - ./packages/core-transactions/node_modules - ./packages/core-utils/node_modules - ./packages/core-vote-report/node_modules - ./packages/core-webhooks/node_modules @@ -643,45 +899,29 @@ jobs: name: Create .core/database directory command: mkdir -p $HOME/.core/database - run: - name: core-snapshots - command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-snapshots && yarn test:coverage - - run: - name: core-test-utils - command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-test-utils && yarn test:coverage - - run: - name: core-tester-cli - command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-tester-cli && yarn test:coverage - - run: - name: core-transaction-pool - command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-transaction-pool && yarn test:coverage - - run: - name: core-utils + name: core-tester-cli - integration command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-utils && yarn test:coverage + cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core && yarn + test:coverage /integration/core-tester-cli/ --coverageDirectory + .coverage/integration/core-tester-cli - run: - name: core-vote-report + name: core-transaction-pool - integration command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-vote-report && yarn test:coverage + cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core && yarn + test:coverage /integration/core-transaction-pool/ + --coverageDirectory .coverage/integration/core-transaction-pool - run: - name: core-webhooks + name: core-vote-report - integration command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd - ~/core/packages/core-webhooks && yarn test:coverage + cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core && yarn + test:coverage /integration/core-vote-report/ --coverageDirectory + .coverage/integration/core-vote-report - run: - name: crypto + name: core-webhooks - integration command: >- - cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core/packages/crypto - && yarn test:coverage + cd ~/core/.circleci && ./rebuild-db.sh && cd ~/core && yarn + test:coverage /integration/core-webhooks/ --coverageDirectory + .coverage/integration/core-webhooks - run: name: Last 1000 lines of test output when: on_fail @@ -696,9 +936,13 @@ workflows: version: 2 build_and_test: jobs: - - test-node10-0 - - test-node10-1 - - test-node10-2 - - test-node11-0 - - test-node11-1 - - test-node11-2 + - test-node10-unit + - test-node11-unit + - test-node10-functional + - test-node11-functional + - test-node10-integration-0 + - test-node11-integration-0 + - test-node10-integration-1 + - test-node10-integration-2 + - test-node11-integration-1 + - test-node11-integration-2 diff --git a/.circleci/configTemplate.json b/.circleci/configTemplate.json index 35d0446efd..e234fa6d7d 100644 --- a/.circleci/configTemplate.json +++ b/.circleci/configTemplate.json @@ -1,7 +1,339 @@ { "version": 2, "jobs": { - "test-node10-0": { + "test-node10-unit": { + "working_directory": "~/core", + "docker": [ + { + "image": "circleci/node:10-browsers" + } + ], + "steps": [ + "checkout", + { + "run": { + "name": "Apt update", + "command": "sudo sh -c 'echo \"deb http://ftp.debian.org/debian stable main contrib non-free\" >> /etc/apt/sources.list' && sudo apt-get update" + } + }, + { + "run": { + "name": "Install xsel", + "command": "sudo apt-get install -q xsel" + } + }, + { + "run": { + "name": "Generate cache key", + "command": "find ./packages/ -name package.json -print0 | sort -z | xargs -r0 echo ./package.json | xargs md5sum | md5sum - > checksum.txt" + } + }, + { + "restore_cache": { + "key": "core-node10-{{ checksum \"checksum.txt\" }}-unit" + } + }, + { + "run": { + "name": "Install and build packages", + "command": "yarn setup" + } + }, + { + "save_cache": { + "key": "core-node10-{{ checksum \"checksum.txt\" }}-unit", + "paths": [] + } + }, + { + "run": { + "name": "Create .core/database directory", + "command": "mkdir -p $HOME/.core/database" + } + }, + { + "run": { + "name": "Unit tests", + "command": "cd ~/core && yarn test:unit:coverage --coverageDirectory .coverage/unit/ --maxWorkers=2" + } + }, + { + "run": { + "name": "Last 1000 lines of test output", + "when": "on_fail", + "command": "tail -n 1000 test_output.txt" + } + }, + { + "run": { + "name": "Lint", + "command": "yarn lint" + } + }, + { + "run": { + "name": "Codecov", + "command": "./node_modules/.bin/codecov" + } + } + ] + }, + "test-node11-unit": { + "working_directory": "~/core", + "docker": [ + { + "image": "circleci/node:11-browsers" + } + ], + "steps": [ + "checkout", + { + "run": { + "name": "Apt update", + "command": "sudo sh -c 'echo \"deb http://ftp.debian.org/debian stable main contrib non-free\" >> /etc/apt/sources.list' && sudo apt-get update" + } + }, + { + "run": { + "name": "Install xsel", + "command": "sudo apt-get install -q xsel" + } + }, + { + "run": { + "name": "Generate cache key", + "command": "find ./packages/ -name package.json -print0 | sort -z | xargs -r0 echo ./package.json | xargs md5sum | md5sum - > checksum.txt" + } + }, + { + "restore_cache": { + "key": "core-node11-{{ checksum \"checksum.txt\" }}-unit" + } + }, + { + "run": { + "name": "Install and build packages", + "command": "yarn setup" + } + }, + { + "save_cache": { + "key": "core-node11-{{ checksum \"checksum.txt\" }}-unit", + "paths": [] + } + }, + { + "run": { + "name": "Create .core/database directory", + "command": "mkdir -p $HOME/.core/database" + } + }, + { + "run": { + "name": "Unit tests", + "command": "cd ~/core && yarn test:unit:coverage --coverageDirectory .coverage/unit/ --maxWorkers=2" + } + }, + { + "run": { + "name": "Last 1000 lines of test output", + "when": "on_fail", + "command": "tail -n 1000 test_output.txt" + } + }, + { + "run": { + "name": "Lint", + "command": "yarn lint" + } + }, + { + "run": { + "name": "Codecov", + "command": "./node_modules/.bin/codecov" + } + } + ] + }, + "test-node10-functional": { + "working_directory": "~/core", + "environment": { + "CORE_DB_DATABASE": "core_unitnet", + "CORE_DB_USERNAME": "core" + }, + "docker": [ + { + "image": "circleci/node:10-browsers" + }, + { + "image": "postgres:alpine", + "environment": { + "POSTGRES_PASSWORD": "password", + "POSTGRES_DB": "core_unitnet", + "POSTGRES_USER": "core" + } + } + ], + "steps": [ + "checkout", + { + "run": { + "name": "Apt update", + "command": "sudo sh -c 'echo \"deb http://ftp.debian.org/debian stable main contrib non-free\" >> /etc/apt/sources.list' && sudo apt-get update" + } + }, + { + "run": { + "name": "Install xsel & postgresql-client", + "command": "sudo apt-get install -q xsel postgresql-client" + } + }, + { + "run": { + "name": "Generate cache key", + "command": "find ./packages/ -name package.json -print0 | sort -z | xargs -r0 echo ./package.json | xargs md5sum | md5sum - > checksum.txt" + } + }, + { + "restore_cache": { + "key": "core-node10-{{ checksum \"checksum.txt\" }}-functional" + } + }, + { + "run": { + "name": "Install and build packages", + "command": "yarn setup" + } + }, + { + "save_cache": { + "key": "core-node10-{{ checksum \"checksum.txt\" }}-functional", + "paths": [] + } + }, + { + "run": { + "name": "Create .core/database directory", + "command": "mkdir -p $HOME/.core/database" + } + }, + { + "run": { + "name": "Functional tests", + "command": "cd ~/core && yarn test:functional:coverage --coverageDirectory .coverage/functional/" + } + }, + { + "run": { + "name": "Last 1000 lines of test output", + "when": "on_fail", + "command": "tail -n 1000 test_output.txt" + } + }, + { + "run": { + "name": "Lint", + "command": "yarn lint" + } + }, + { + "run": { + "name": "Codecov", + "command": "./node_modules/.bin/codecov" + } + } + ] + }, + "test-node11-functional": { + "working_directory": "~/core", + "environment": { + "CORE_DB_DATABASE": "core_unitnet", + "CORE_DB_USERNAME": "core" + }, + "docker": [ + { + "image": "circleci/node:11-browsers" + }, + { + "image": "postgres:alpine", + "environment": { + "POSTGRES_PASSWORD": "password", + "POSTGRES_DB": "core_unitnet", + "POSTGRES_USER": "core" + } + } + ], + "steps": [ + "checkout", + { + "run": { + "name": "Apt update", + "command": "sudo sh -c 'echo \"deb http://ftp.debian.org/debian stable main contrib non-free\" >> /etc/apt/sources.list' && sudo apt-get update" + } + }, + { + "run": { + "name": "Install xsel & postgresql-client", + "command": "sudo apt-get install -q xsel postgresql-client" + } + }, + { + "run": { + "name": "Generate cache key", + "command": "find ./packages/ -name package.json -print0 | sort -z | xargs -r0 echo ./package.json | xargs md5sum | md5sum - > checksum.txt" + } + }, + { + "restore_cache": { + "key": "core-node11-{{ checksum \"checksum.txt\" }}-functional" + } + }, + { + "run": { + "name": "Install and build packages", + "command": "yarn setup" + } + }, + { + "save_cache": { + "key": "core-node11-{{ checksum \"checksum.txt\" }}-functional", + "paths": [] + } + }, + { + "run": { + "name": "Create .core/database directory", + "command": "mkdir -p $HOME/.core/database" + } + }, + { + "run": { + "name": "Functional tests", + "command": "cd ~/core && yarn test:functional:coverage --coverageDirectory .coverage/functional/" + } + }, + { + "run": { + "name": "Last 1000 lines of test output", + "when": "on_fail", + "command": "tail -n 1000 test_output.txt" + } + }, + { + "run": { + "name": "Lint", + "command": "yarn lint" + } + }, + { + "run": { + "name": "Codecov", + "command": "./node_modules/.bin/codecov" + } + } + ] + }, + "test-node10-integration-0": { "working_directory": "~/core", "environment": { "CORE_DB_DATABASE": "core_unitnet", @@ -65,8 +397,8 @@ }, { "run": { - "name": "Test", - "command": "" + "name": "Unit tests", + "command": "cd ~/core && yarn test:unit:coverage --coverageDirectory .coverage/unit/ --maxWorkers=2" } }, { @@ -90,7 +422,7 @@ } ] }, - "test-node11-0": { + "test-node11-integration-0": { "working_directory": "~/core", "environment": { "CORE_DB_DATABASE": "core_unitnet", @@ -154,8 +486,8 @@ }, { "run": { - "name": "Test", - "command": "" + "name": "Unit tests", + "command": "cd ~/core && yarn test:unit:coverage --coverageDirectory .coverage/unit/ --maxWorkers=2" } }, { @@ -183,7 +515,7 @@ "workflows": { "version": 2, "build_and_test": { - "jobs": [] + "jobs": ["test-node10-integration-0", "test-node11-integration-0"] } } } diff --git a/.circleci/generateConfig.js b/.circleci/generateConfig.js index 39f40ecb82..2d07aaa6c6 100644 --- a/.circleci/generateConfig.js +++ b/.circleci/generateConfig.js @@ -1,58 +1,47 @@ const yaml = require("js-yaml"); const fs = require("fs"); const path = require("path"); -const chunk = require("lodash.chunk"); const config = require("./configTemplate.json"); +const fixedJobs = [ + "test-node10-unit", + "test-node11-unit", + "test-node10-functional", + "test-node11-functional", +] + function jason(value) { return JSON.parse(JSON.stringify(value)); } fs.readdir("./packages", (_, packages) => { // test split - const packagesSplit = chunk(packages.sort(), 10); - - const resetSqlCommand = "cd ~/core/.circleci && ./rebuild-db.sh" + const packagesChunks = splitPackages(packages); for (const [name, job] of Object.entries(config.jobs)) { // save cache - const saveCacheStep = config.jobs[name].steps.find(step => typeof step === "object" && step.save_cache); + const saveCacheStep = job.steps.find(step => typeof step === "object" && step.save_cache); saveCacheStep.save_cache.paths = packages .map(package => `./packages/${package}/node_modules`) .concat("./node_modules"); + if (fixedJobs.includes(name)) { + continue; + } + + // copy base unit jobs (unit tests) to adapt for integration tests const jobs = [ - config.jobs[name], - jason(config.jobs[name]), - jason(config.jobs[name]), + jason(job), + jason(job), ]; jobs.forEach((job, index) => { const testStepIndex = job.steps.findIndex( - step => typeof step === "object" && step.run && step.run.name === "Test", + step => typeof step === "object" && step.run && step.run.name === "Unit tests", ); - const pkgs = packagesSplit[index].map(package => `./packages/${package}/`); - - const steps = pkgs - .map(pkg => { - const name = path.basename(pkg); - - return { - run: { - name, - command: `${resetSqlCommand} && cd ~/core/packages/${name} && yarn test:coverage`, - }, - }; - }) - .filter(pkg => { - const { - scripts - } = require(path.resolve(__dirname, `../packages/${pkg.run.name}/package.json`)); - - return Object.keys(scripts).includes("test:coverage"); - }); + const steps = getIntegrationSteps(packagesChunks[index]); const stepLog = job.steps[9]; const stepLint = job.steps[10]; @@ -66,10 +55,42 @@ fs.readdir("./packages", (_, packages) => { job.steps.push(stepLint); job.steps.push(stepCoverage); - config.jobs[name.slice(0, -1) + index] = job; - config.workflows.build_and_test.jobs.push(name.slice(0, -1) + index); + config.jobs[name.slice(0, -1) + (index + 1)] = job; + config.workflows.build_and_test.jobs.push(name.slice(0, -1) + (index + 1)); }); } + config.workflows.build_and_test.jobs = fixedJobs.concat(config.workflows.build_and_test.jobs) + fs.writeFileSync(".circleci/config.yml", yaml.safeDump(config)); }); + +function splitPackages(packageNames) { + // split packages in two for integration tests + const integrationPackages = packageNames.sort() + .map(pkg => path.basename(pkg)) + .filter(pkg => fs.existsSync(path.resolve(__dirname, `../__tests__/integration/${pkg}`))) + + var indexToSplit = Math.floor(integrationPackages.length / 2); + return [ + integrationPackages.slice(0, indexToSplit), + integrationPackages.slice(indexToSplit + 1) + ] +} + +function getIntegrationSteps(packages) { + const resetSqlCommand = "cd ~/core/.circleci && ./rebuild-db.sh" + + const steps = [] + steps.push(...packages + .filter(pkg => fs.existsSync(path.resolve(__dirname, `../__tests__/integration/${pkg}`))) + .map(pkg => ({ + run: { + name: `${pkg} - integration`, + command: `${resetSqlCommand} && cd ~/core && yarn test:coverage /integration/${pkg}/ --coverageDirectory .coverage/integration/${pkg}`, + }, + })) + ); + + return steps; +} diff --git a/.codecov.yml b/.codecov.yml index f33a877fec..f7d095ffad 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -2,14 +2,3 @@ ignore: - "packages/**/src/defaults.ts" - "packages/**/src/index.ts" - "packages/**/src/plugin.ts" - - "packages/core-error-tracker-bugsnag/**/*" - - "packages/core-error-tracker-sentry/**/*" - - "packages/core-graphql/**/*" - - "packages/core-http-utils/**/*" - - "packages/core-logger-winston/src/formatter.ts" - - "packages/core-snapshots-cli/**/*" - - "packages/core-test-utils/**/*" - - "packages/core-test-utils/src/fixtures/**/*" - - "packages/core-tester-cli/**/*" - - "packages/core-webhooks/src/database/migrations/**/*" - - "packages/core/**/*" diff --git a/.github/ISSUE_TEMPLATE/Documentation_issue.md b/.github/ISSUE_TEMPLATE/Documentation_issue.md index 089a660657..acd66739fa 100644 --- a/.github/ISSUE_TEMPLATE/Documentation_issue.md +++ b/.github/ISSUE_TEMPLATE/Documentation_issue.md @@ -1,8 +1,8 @@ --- name: "Documentation Issue" -about: "For documentation issues, see: https://github.com/ArkEcosystem/docs/issues" +about: "For documentation issues, see: https://github.com/ARKEcosystem/docs/issues" --- -The Ark Core documentation has its own dedicated repository. Please open your documentation-related issue at https://github.com/ArkEcosystem/docs/issues. **However, it's best to simply make a pull request to correct the issue you have found!** +The Persona Core documentation has its own dedicated repository. Please open your documentation-related issue at https://github.com/ArkEcosystem/docs/issues. **However, it's best to simply make a pull request to correct the issue you have found!** Thanks! diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e6a0383792..f4d6272e22 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,35 +1,53 @@ -## Proposed changes - + -## Types of changes - +## Summary + + + +## What kind of change does this PR introduce? + + + +- [ ] Bugfix +- [ ] New feature +- [ ] Refactoring / Performance Improvements +- [ ] Build-related changes +- [ ] Documentation +- [ ] Tests / Continuous Integration +- [ ] Other, please describe: + +## Does this PR introduce a breaking change? -- [ ] Bugfix (non-breaking change which fixes an issue) -- [ ] New feature (non-breaking change which adds functionality) -- [ ] Refactoring (improve a current implementation without adding a new feature or fixing a bug) -- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) -- [ ] Build (changes that affect the build system) -- [ ] Docs (documentation only changes) -- [ ] Test (adding missing tests or fixing existing tests) -- [ ] Other... Please describe: + + +- [ ] Yes +- [ ] No + +## Does this PR release a new version? + + + +- [ ] Yes + - [ ] All tests are passing + - [ ] All benchmarks are passing without any _major_ regressions + - [ ] Sync from 0 works on mainnet + - [ ] Sync from 0 works on devnet + - [ ] Starting a new network and forging on it work + - [ ] Explorer is fully functional + - [ ] Wallets are fully functional +- [ ] No ## Checklist - + + - [ ] I have read the [CONTRIBUTING](https://docs.ark.io/guidebook/contribution-guidelines/contributing.html) documentation - [ ] Lint and unit tests pass locally with my changes - [ ] I have added tests that prove my fix is effective or that my feature works -- [ ] I have added necessary documentation (if appropriate) - diff --git a/.yarnrc b/.yarnrc new file mode 100644 index 0000000000..5e7a4dd335 --- /dev/null +++ b/.yarnrc @@ -0,0 +1 @@ +--install.ignore-engines true diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bc0c937f3..ae6a163e91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,90 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [2.3.15] - 2019-04-25 + +Fix release of `2.3.14` due to npm connectivity issues. + +## [2.3.14] - 2019-04-25 + +### Fixed + +- Added missing mainnet exceptions for transactions with invalid recipients ([#2471]) +- Remove support for old release channels from the 2.2 development period ([#2476]) + +## [2.3.12] - 2019-04-24 + +### Fixed + +- Use correct genesis block instead to verify snapshots ([#2462]) +- Don't pass suffix flag to bip38 and bip39 commands ([#2464]) + +## [2.3.1] - 2019-04-23 + +### Fixed + +- Deserialize type > 0 with vendor field instead of skipping it ([#2459]) + +## [2.3.0] - 2019-04-23 + +### Breaking Changes + +- Removed the `wallets` table from the database ([#2209]) + - **Core 2.0 has been fully reliant on in-memory wallets since the 2.0 release. This only removes the dumping of wallets into the database as it is wasted space and doesn't serve any purpose.** + - **If you have applications that rely on the database you should migrate them as soon as possible to using the API as only that data is provided in real-time.** +- Replace SQLite3 with [lowdb](https://github.com/typicode/lowdb) in `core-webhooks` ([#2124]) + - **This significantly reduces the size of the package and it's dependencies.** + - **This requires you to recreate your webhooks as the storage method changed.** +- Replaced `core-logger-winston` with `core-logger-pino` ([#2134]) + - **This significantly improves performance of logging when it occurs a lot in situations like syncing or rollbacks.** +- Rewrote `core-tester-cli` from scratch ([#2133]) +- Merged `core-debugger-cli` into `core-tester-cli` and deprecated it ([#2133]) +- Use the node.js `EventEmitter` from `events` instead of `eventemitter3` ([#2329]) + +### Added + +- Implement AIP29 ([#2122]) +- Search delegates by their username in `core-api` ([#2143]) +- Implemented the `ark reinstall` command in `core` ([#2192]) +- Added the `--force` flag to the `ark update` command in `core` ([#2190]) +- Added more parameters for delegate searches in `core-api` ([#2184]) +- Added restart flags to the `ark update` command in `core` ([#2218]) +- Added the `make:block` command to `core-tester-cli` to create blocks ([#2221]) +- Added the `core-error-tracker-rollbar` package ([#2287]) +- Added the `core-error-tracker-raygun` package ([#2288]) +- Added the `core-error-tracker-airbrake` package ([#2289]) +- Added the `core-logger-signale` package ([#2343]) +- Added more events for blocks and the transaction pool ([#2321]) +- Return `slip44` and `wif` via `v2/node/configuration` ([#2388]) +- Added an `asset` column to the `transactions` table ([#2236]) + +### Fixed + +- Properly sort peers by their version ([#2229]) +- Memory leak in the monitoring process of `core-forger` ([#2341]) +- Handle dynamic round sizes with milestones ([#2370]) +- Validate that a transaction recipient is on the same network ([#2394]) +- Handle empty `rows` in `mapBlocksToTransactions` ([#2404]) +- Prevent indexing/creating of ghost wallets ([#2405]) +- Refuse transactions from senders with pending second signature registrations and do not rollback when refusing a block ([#2458]) + +### Changed + +- Increased the vendor field length to 255 bytes ([#2159]) +- Replaced `micromatch` with `nanomatch` to improve performance ([#2165]) +- Replaced `axios` with `got` to resolve known timeout issues with `axios` ([#2203]) +- Switch block id to full SHA256 ([#2156]) + +### Removed + +- Removed dead fast rebuild code that hasn't been used since 2.0 release ([#2210]) + +## [2.2.2] - 2019-03-19 + +### Removed + +- Remove `/api/v2/delegates/{id}/voters/balances` endpoint ([#2265]) + ## [2.2.0] - 2019-03-11 ### Added @@ -124,7 +208,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Properly verify block slot timestamps ([#1985]) - Return fixed peer states for v1 and v2 API responses ([#2027]) - Validate IP ranges to detect loopbacks ([#2045]) -- https://github.com/ArkEcosystem/security-vulnerabilities/blob/master/core/core-sv-010.md ([#2046]) +- https://github.com/ARKEcosystem/security-vulnerabilities/blob/master/core/core-sv-010.md ([#2046]) - Check if the blockchain state storage is available before performing fork checks ([#2047]) - Gracefully handle a corrupted cached `peers.json` file ([#2061]) - Always sort transactions by sequence and the requested field to make API sorting deterministic ([#2058]) @@ -140,21 +224,21 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Fixed -- https://github.com/ArkEcosystem/security-vulnerabilities/blob/master/core/core-sv-009.md -- https://github.com/ArkEcosystem/security-vulnerabilities/blob/master/core/core-sv-010.md +- https://github.com/ARKEcosystem/security-vulnerabilities/blob/master/core/core-sv-009.md +- https://github.com/ARKEcosystem/security-vulnerabilities/blob/master/core/core-sv-010.md ## [2.0.18] - 2019-01-28 ### Fixed -- https://github.com/ArkEcosystem/security-vulnerabilities/blob/master/core/core-sv-011.md +- https://github.com/ARKEcosystem/security-vulnerabilities/blob/master/core/core-sv-011.md ## [2.0.17] - 2019-01-15 ### Fixed -- https://github.com/ArkEcosystem/security-vulnerabilities/blob/master/core/core-sv-008.md -- https://github.com/ArkEcosystem/security-vulnerabilities/blob/master/core/core-sv-007.md +- https://github.com/ARKEcosystem/security-vulnerabilities/blob/master/core/core-sv-008.md +- https://github.com/ARKEcosystem/security-vulnerabilities/blob/master/core/core-sv-007.md ## [2.0.16] - 2018-12-17 @@ -164,10 +248,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. Closed security vulnerabilities: -- [CORE-SV-004](https://github.com/ArkEcosystem/security-vulnerabilities/blob/master/core/core-sv-004.md) -- [CORE-SV-003](https://github.com/ArkEcosystem/security-vulnerabilities/blob/master/core/core-sv-003.md) -- [CORE-SV-002](https://github.com/ArkEcosystem/security-vulnerabilities/blob/master/core/core-sv-002.md) -- [CORE-SV-001](https://github.com/ArkEcosystem/security-vulnerabilities/blob/master/core/core-sv-001.md) +- [CORE-SV-004](https://github.com/ARKEcosystem/security-vulnerabilities/blob/master/core/core-sv-004.md) +- [CORE-SV-003](https://github.com/ARKEcosystem/security-vulnerabilities/blob/master/core/core-sv-003.md) +- [CORE-SV-002](https://github.com/ARKEcosystem/security-vulnerabilities/blob/master/core/core-sv-002.md) +- [CORE-SV-001](https://github.com/ARKEcosystem/security-vulnerabilities/blob/master/core/core-sv-001.md) ## [2.0.15] - 2018-12-11 @@ -224,120 +308,165 @@ Closed security vulnerabilities: - Initial Release -[unreleased]: https://github.com/ArkEcosystem/core/compare/2.2.0...develop -[2.2.0]: https://github.com/ArkEcosystem/core/compare/2.1.2...2.2.0 -[2.1.2]: https://github.com/ArkEcosystem/core/compare/2.1.1..2.1.2 -[2.1.1]: https://github.com/ArkEcosystem/core/compare/2.1.0..2.1.1 -[2.1.0]: https://github.com/ArkEcosystem/core/compare/2.0.19...2.1.0 -[2.0.19]: https://github.com/ArkEcosystem/core/compare/2.0.18...2.0.19 -[2.0.18]: https://github.com/ArkEcosystem/core/compare/2.0.17...2.0.18 -[2.0.17]: https://github.com/ArkEcosystem/core/compare/2.0.16...2.0.17 -[2.0.16]: https://github.com/ArkEcosystem/core/compare/2.0.15...2.0.16 -[2.0.15]: https://github.com/ArkEcosystem/core/compare/2.0.14...2.0.15 -[2.0.14]: https://github.com/ArkEcosystem/core/compare/2.0.13...2.0.14 -[2.0.13]: https://github.com/ArkEcosystem/core/compare/2.0.12...2.0.13 -[2.0.12]: https://github.com/ArkEcosystem/core/compare/2.0.11...2.0.12 -[2.0.11]: https://github.com/ArkEcosystem/core/compare/2.0.1...2.0.11 -[2.0.1]: https://github.com/ArkEcosystem/core/compare/2.0.0...2.0.1 -[2.0.0]: https://github.com/ArkEcosystem/core/compare/0.1.1...2.0.0 -[#1563]: https://github.com/ArkEcosystem/core/pull/1563 -[#1564]: https://github.com/ArkEcosystem/core/pull/1564 -[#1625]: https://github.com/ArkEcosystem/core/pull/1625 -[#1626]: https://github.com/ArkEcosystem/core/pull/1626 -[#1634]: https://github.com/ArkEcosystem/core/pull/1634 -[#1636]: https://github.com/ArkEcosystem/core/pull/1636 -[#1638]: https://github.com/ArkEcosystem/core/pull/1638 -[#1638]: https://github.com/ArkEcosystem/core/pull/1638 -[#1640]: https://github.com/ArkEcosystem/core/pull/1640 -[#1645]: https://github.com/ArkEcosystem/core/pull/1645 -[#1646]: https://github.com/ArkEcosystem/core/pull/1646 -[#1648]: https://github.com/ArkEcosystem/core/pull/1648 -[#1653]: https://github.com/ArkEcosystem/core/pull/1653 -[#1655]: https://github.com/ArkEcosystem/core/pull/1655 -[#1658]: https://github.com/ArkEcosystem/core/pull/1658 -[#1673]: https://github.com/ArkEcosystem/core/pull/1673 -[#1689]: https://github.com/ArkEcosystem/core/pull/1689 -[#1692]: https://github.com/ArkEcosystem/core/pull/1692 -[#1695]: https://github.com/ArkEcosystem/core/pull/1695 -[#1717]: https://github.com/ArkEcosystem/core/pull/1717 -[#1730]: https://github.com/ArkEcosystem/core/pull/1730 -[#1731]: https://github.com/ArkEcosystem/core/pull/1731 -[#1732]: https://github.com/ArkEcosystem/core/pull/1732 -[#1733]: https://github.com/ArkEcosystem/core/pull/1733 -[#1738]: https://github.com/ArkEcosystem/core/pull/1738 -[#1831]: https://github.com/ArkEcosystem/core/pull/1831 -[#1833]: https://github.com/ArkEcosystem/core/pull/1833 -[#1836]: https://github.com/ArkEcosystem/core/pull/1836 -[#1837]: https://github.com/ArkEcosystem/core/pull/1837 -[#1853]: https://github.com/ArkEcosystem/core/pull/1853 -[#1869]: https://github.com/ArkEcosystem/core/pull/1869 -[#1873]: https://github.com/ArkEcosystem/core/pull/1873 -[#1887]: https://github.com/ArkEcosystem/core/pull/1887 -[#1891]: https://github.com/ArkEcosystem/core/pull/1891 -[#1898]: https://github.com/ArkEcosystem/core/pull/1898 -[#1901]: https://github.com/ArkEcosystem/core/pull/1901 -[#1905]: https://github.com/ArkEcosystem/core/pull/1905 -[#1906]: https://github.com/ArkEcosystem/core/pull/1906 -[#1911]: https://github.com/ArkEcosystem/core/pull/1911 -[#1917]: https://github.com/ArkEcosystem/core/pull/1917 -[#1919]: https://github.com/ArkEcosystem/core/pull/1919 -[#1924]: https://github.com/ArkEcosystem/core/pull/1924 -[#1926]: https://github.com/ArkEcosystem/core/pull/1926 -[#1927]: https://github.com/ArkEcosystem/core/pull/1927 -[#1930]: https://github.com/ArkEcosystem/core/pull/1930 -[#1931]: https://github.com/ArkEcosystem/core/pull/1931 -[#1939]: https://github.com/ArkEcosystem/core/pull/1939 -[#1940]: https://github.com/ArkEcosystem/core/pull/1940 -[#1941]: https://github.com/ArkEcosystem/core/pull/1941 -[#1943]: https://github.com/ArkEcosystem/core/pull/1943 -[#1947]: https://github.com/ArkEcosystem/core/pull/1947 -[#1948]: https://github.com/ArkEcosystem/core/pull/1948 -[#1953]: https://github.com/ArkEcosystem/core/pull/1953 -[#1954]: https://github.com/ArkEcosystem/core/pull/1954 -[#1955]: https://github.com/ArkEcosystem/core/pull/1955 -[#1957]: https://github.com/ArkEcosystem/core/pull/1957 -[#1969]: https://github.com/ArkEcosystem/core/pull/1969 -[#1970]: https://github.com/ArkEcosystem/core/pull/1970 -[#1985]: https://github.com/ArkEcosystem/core/pull/1985 -[#1987]: https://github.com/ArkEcosystem/core/pull/1987 -[#1996]: https://github.com/ArkEcosystem/core/pull/1996 -[#1999]: https://github.com/ArkEcosystem/core/pull/1999 -[#2009]: https://github.com/ArkEcosystem/core/pull/2009 -[#2016]: https://github.com/ArkEcosystem/core/pull/2016 -[#2021]: https://github.com/ArkEcosystem/core/pull/2021 -[#2038]: https://github.com/ArkEcosystem/core/pull/2038 -[#2044]: https://github.com/ArkEcosystem/core/pull/2044 -[#2045]: https://github.com/ArkEcosystem/core/pull/2045 -[#2046]: https://github.com/ArkEcosystem/core/pull/2046 -[#2047]: https://github.com/ArkEcosystem/core/pull/2047 -[#2049]: https://github.com/ArkEcosystem/core/pull/2049 -[#2050]: https://github.com/ArkEcosystem/core/pull/2050 -[#2051]: https://github.com/ArkEcosystem/core/pull/2051 -[#2052]: https://github.com/ArkEcosystem/core/pull/2052 -[#2053]: https://github.com/ArkEcosystem/core/pull/2053 -[#2055]: https://github.com/ArkEcosystem/core/pull/2055 -[#2057]: https://github.com/ArkEcosystem/core/pull/2057 -[#2058]: https://github.com/ArkEcosystem/core/pull/2058 -[#2061]: https://github.com/ArkEcosystem/core/pull/2061 -[#2080]: https://github.com/ArkEcosystem/core/pull/2080 -[#2082]: https://github.com/ArkEcosystem/core/pull/2082 -[#2083]: https://github.com/ArkEcosystem/core/pull/2083 -[#2091]: https://github.com/ArkEcosystem/core/pull/2091 -[#2100]: https://github.com/ArkEcosystem/core/pull/2100 -[#2102]: https://github.com/ArkEcosystem/core/pull/2102 -[#2103]: https://github.com/ArkEcosystem/core/pull/2103 -[#2106]: https://github.com/ArkEcosystem/core/pull/2106 -[#2108]: https://github.com/ArkEcosystem/core/pull/2108 -[#2119]: https://github.com/ArkEcosystem/core/pull/2119 -[#2121]: https://github.com/ArkEcosystem/core/pull/2121 -[#2123]: https://github.com/ArkEcosystem/core/pull/2123 -[#2125]: https://github.com/ArkEcosystem/core/pull/2125 -[#2135]: https://github.com/ArkEcosystem/core/pull/2135 -[#2137]: https://github.com/ArkEcosystem/core/pull/2137 -[#2139]: https://github.com/ArkEcosystem/core/pull/2139 -[#2142]: https://github.com/ArkEcosystem/core/pull/2142 -[#2144]: https://github.com/ArkEcosystem/core/pull/2144 -[#2149]: https://github.com/ArkEcosystem/core/pull/2149 -[#2152]: https://github.com/ArkEcosystem/core/pull/2152 -[#2207]: https://github.com/ArkEcosystem/core/pull/2207 -[#2217]: https://github.com/ArkEcosystem/core/pull/2217 +[unreleased]: https://github.com/ARKEcosystem/core/compare/2.3.0...develop +[2.3.15]: https://github.com/ARKEcosystem/core/compare/2.3.14...2.3.15 +[2.3.14]: https://github.com/ARKEcosystem/core/compare/2.3.12...2.3.14 +[2.3.12]: https://github.com/ARKEcosystem/core/compare/2.3.1...2.3.12 +[2.3.1]: https://github.com/ARKEcosystem/core/compare/2.3.0...2.3.1 +[2.3.0]: https://github.com/ARKEcosystem/core/compare/2.2.2...2.3.0 +[2.2.2]: https://github.com/ARKEcosystem/core/compare/2.2.1...2.2.2 +[2.2.1]: https://github.com/ARKEcosystem/core/compare/2.2.0...2.2.1 +[2.2.0]: https://github.com/ARKEcosystem/core/compare/2.1.2..2.2.0 +[2.1.2]: https://github.com/ARKEcosystem/core/compare/2.1.1..2.1.2 +[2.1.1]: https://github.com/ARKEcosystem/core/compare/2.1.0..2.1.1 +[2.1.0]: https://github.com/ARKEcosystem/core/compare/2.0.19...2.1.0 +[2.0.19]: https://github.com/ARKEcosystem/core/compare/2.0.18...2.0.19 +[2.0.18]: https://github.com/ARKEcosystem/core/compare/2.0.17...2.0.18 +[2.0.17]: https://github.com/ARKEcosystem/core/compare/2.0.16...2.0.17 +[2.0.16]: https://github.com/ARKEcosystem/core/compare/2.0.15...2.0.16 +[2.0.15]: https://github.com/ARKEcosystem/core/compare/2.0.14...2.0.15 +[2.0.14]: https://github.com/ARKEcosystem/core/compare/2.0.13...2.0.14 +[2.0.13]: https://github.com/ARKEcosystem/core/compare/2.0.12...2.0.13 +[2.0.12]: https://github.com/ARKEcosystem/core/compare/2.0.11...2.0.12 +[2.0.11]: https://github.com/ARKEcosystem/core/compare/2.0.1...2.0.11 +[2.0.1]: https://github.com/ARKEcosystem/core/compare/2.0.0...2.0.1 +[2.0.0]: https://github.com/ARKEcosystem/core/compare/0.1.1...2.0.0 +[#1563]: https://github.com/ARKEcosystem/core/pull/1563 +[#1564]: https://github.com/ARKEcosystem/core/pull/1564 +[#1625]: https://github.com/ARKEcosystem/core/pull/1625 +[#1626]: https://github.com/ARKEcosystem/core/pull/1626 +[#1634]: https://github.com/ARKEcosystem/core/pull/1634 +[#1636]: https://github.com/ARKEcosystem/core/pull/1636 +[#1638]: https://github.com/ARKEcosystem/core/pull/1638 +[#1638]: https://github.com/ARKEcosystem/core/pull/1638 +[#1640]: https://github.com/ARKEcosystem/core/pull/1640 +[#1645]: https://github.com/ARKEcosystem/core/pull/1645 +[#1646]: https://github.com/ARKEcosystem/core/pull/1646 +[#1648]: https://github.com/ARKEcosystem/core/pull/1648 +[#1653]: https://github.com/ARKEcosystem/core/pull/1653 +[#1655]: https://github.com/ARKEcosystem/core/pull/1655 +[#1658]: https://github.com/ARKEcosystem/core/pull/1658 +[#1673]: https://github.com/ARKEcosystem/core/pull/1673 +[#1689]: https://github.com/ARKEcosystem/core/pull/1689 +[#1692]: https://github.com/ARKEcosystem/core/pull/1692 +[#1695]: https://github.com/ARKEcosystem/core/pull/1695 +[#1717]: https://github.com/ARKEcosystem/core/pull/1717 +[#1730]: https://github.com/ARKEcosystem/core/pull/1730 +[#1731]: https://github.com/ARKEcosystem/core/pull/1731 +[#1732]: https://github.com/ARKEcosystem/core/pull/1732 +[#1733]: https://github.com/ARKEcosystem/core/pull/1733 +[#1738]: https://github.com/ARKEcosystem/core/pull/1738 +[#1831]: https://github.com/ARKEcosystem/core/pull/1831 +[#1833]: https://github.com/ARKEcosystem/core/pull/1833 +[#1836]: https://github.com/ARKEcosystem/core/pull/1836 +[#1837]: https://github.com/ARKEcosystem/core/pull/1837 +[#1853]: https://github.com/ARKEcosystem/core/pull/1853 +[#1869]: https://github.com/ARKEcosystem/core/pull/1869 +[#1873]: https://github.com/ARKEcosystem/core/pull/1873 +[#1887]: https://github.com/ARKEcosystem/core/pull/1887 +[#1891]: https://github.com/ARKEcosystem/core/pull/1891 +[#1898]: https://github.com/ARKEcosystem/core/pull/1898 +[#1901]: https://github.com/ARKEcosystem/core/pull/1901 +[#1905]: https://github.com/ARKEcosystem/core/pull/1905 +[#1906]: https://github.com/ARKEcosystem/core/pull/1906 +[#1911]: https://github.com/ARKEcosystem/core/pull/1911 +[#1917]: https://github.com/ARKEcosystem/core/pull/1917 +[#1919]: https://github.com/ARKEcosystem/core/pull/1919 +[#1924]: https://github.com/ARKEcosystem/core/pull/1924 +[#1926]: https://github.com/ARKEcosystem/core/pull/1926 +[#1927]: https://github.com/ARKEcosystem/core/pull/1927 +[#1930]: https://github.com/ARKEcosystem/core/pull/1930 +[#1931]: https://github.com/ARKEcosystem/core/pull/1931 +[#1939]: https://github.com/ARKEcosystem/core/pull/1939 +[#1940]: https://github.com/ARKEcosystem/core/pull/1940 +[#1941]: https://github.com/ARKEcosystem/core/pull/1941 +[#1943]: https://github.com/ARKEcosystem/core/pull/1943 +[#1947]: https://github.com/ARKEcosystem/core/pull/1947 +[#1948]: https://github.com/ARKEcosystem/core/pull/1948 +[#1953]: https://github.com/ARKEcosystem/core/pull/1953 +[#1954]: https://github.com/ARKEcosystem/core/pull/1954 +[#1955]: https://github.com/ARKEcosystem/core/pull/1955 +[#1957]: https://github.com/ARKEcosystem/core/pull/1957 +[#1969]: https://github.com/ARKEcosystem/core/pull/1969 +[#1970]: https://github.com/ARKEcosystem/core/pull/1970 +[#1985]: https://github.com/ARKEcosystem/core/pull/1985 +[#1987]: https://github.com/ARKEcosystem/core/pull/1987 +[#1996]: https://github.com/ARKEcosystem/core/pull/1996 +[#1999]: https://github.com/ARKEcosystem/core/pull/1999 +[#2009]: https://github.com/ARKEcosystem/core/pull/2009 +[#2016]: https://github.com/ARKEcosystem/core/pull/2016 +[#2021]: https://github.com/ARKEcosystem/core/pull/2021 +[#2038]: https://github.com/ARKEcosystem/core/pull/2038 +[#2044]: https://github.com/ARKEcosystem/core/pull/2044 +[#2045]: https://github.com/ARKEcosystem/core/pull/2045 +[#2046]: https://github.com/ARKEcosystem/core/pull/2046 +[#2047]: https://github.com/ARKEcosystem/core/pull/2047 +[#2049]: https://github.com/ARKEcosystem/core/pull/2049 +[#2050]: https://github.com/ARKEcosystem/core/pull/2050 +[#2051]: https://github.com/ARKEcosystem/core/pull/2051 +[#2052]: https://github.com/ARKEcosystem/core/pull/2052 +[#2053]: https://github.com/ARKEcosystem/core/pull/2053 +[#2055]: https://github.com/ARKEcosystem/core/pull/2055 +[#2057]: https://github.com/ARKEcosystem/core/pull/2057 +[#2058]: https://github.com/ARKEcosystem/core/pull/2058 +[#2061]: https://github.com/ARKEcosystem/core/pull/2061 +[#2080]: https://github.com/ARKEcosystem/core/pull/2080 +[#2082]: https://github.com/ARKEcosystem/core/pull/2082 +[#2083]: https://github.com/ARKEcosystem/core/pull/2083 +[#2091]: https://github.com/ARKEcosystem/core/pull/2091 +[#2100]: https://github.com/ARKEcosystem/core/pull/2100 +[#2102]: https://github.com/ARKEcosystem/core/pull/2102 +[#2103]: https://github.com/ARKEcosystem/core/pull/2103 +[#2106]: https://github.com/ARKEcosystem/core/pull/2106 +[#2108]: https://github.com/ARKEcosystem/core/pull/2108 +[#2119]: https://github.com/ARKEcosystem/core/pull/2119 +[#2121]: https://github.com/ARKEcosystem/core/pull/2121 +[#2122]: https://github.com/ARKEcosystem/core/pull/2122 +[#2123]: https://github.com/ARKEcosystem/core/pull/2123 +[#2124]: https://github.com/ARKEcosystem/core/pull/2124 +[#2125]: https://github.com/ARKEcosystem/core/pull/2125 +[#2133]: https://github.com/ARKEcosystem/core/pull/2133 +[#2133]: https://github.com/ARKEcosystem/core/pull/2133 +[#2134]: https://github.com/ARKEcosystem/core/pull/2134 +[#2135]: https://github.com/ARKEcosystem/core/pull/2135 +[#2137]: https://github.com/ARKEcosystem/core/pull/2137 +[#2139]: https://github.com/ARKEcosystem/core/pull/2139 +[#2142]: https://github.com/ARKEcosystem/core/pull/2142 +[#2143]: https://github.com/ARKEcosystem/core/pull/2143 +[#2144]: https://github.com/ARKEcosystem/core/pull/2144 +[#2149]: https://github.com/ARKEcosystem/core/pull/2149 +[#2152]: https://github.com/ARKEcosystem/core/pull/2152 +[#2156]: https://github.com/ARKEcosystem/core/pull/2156 +[#2159]: https://github.com/ARKEcosystem/core/pull/2159 +[#2165]: https://github.com/ARKEcosystem/core/pull/2165 +[#2184]: https://github.com/ARKEcosystem/core/pull/2184 +[#2190]: https://github.com/ARKEcosystem/core/pull/2190 +[#2192]: https://github.com/ARKEcosystem/core/pull/2192 +[#2203]: https://github.com/ARKEcosystem/core/pull/2203 +[#2205]: https://github.com/ARKEcosystem/core/pull/2205 +[#2207]: https://github.com/ARKEcosystem/core/pull/2207 +[#2209]: https://github.com/ARKEcosystem/core/pull/2209 +[#2210]: https://github.com/ARKEcosystem/core/pull/2210 +[#2217]: https://github.com/ARKEcosystem/core/pull/2217 +[#2218]: https://github.com/ARKEcosystem/core/pull/2218 +[#2221]: https://github.com/ARKEcosystem/core/pull/2221 +[#2229]: https://github.com/ARKEcosystem/core/pull/2229 +[#2236]: https://github.com/ARKEcosystem/core/pull/2236 +[#2287]: https://github.com/ARKEcosystem/core/pull/2287 +[#2288]: https://github.com/ARKEcosystem/core/pull/2288 +[#2289]: https://github.com/ARKEcosystem/core/pull/2289 +[#2321]: https://github.com/ARKEcosystem/core/pull/2321 +[#2329]: https://github.com/ARKEcosystem/core/pull/2329 +[#2341]: https://github.com/ARKEcosystem/core/pull/2341 +[#2343]: https://github.com/ARKEcosystem/core/pull/2343 +[#2370]: https://github.com/ARKEcosystem/core/pull/2370 +[#2388]: https://github.com/ARKEcosystem/core/pull/2388 +[#2394]: https://github.com/ARKEcosystem/core/pull/2394 +[#2404]: https://github.com/ARKEcosystem/core/pull/2404 +[#2405]: https://github.com/ARKEcosystem/core/pull/2405 +[#2458]: https://github.com/ARKEcosystem/core/pull/2458 +[#2459]: https://github.com/ARKEcosystem/core/pull/2459 +[#2462]: https://github.com/ARKEcosystem/core/pull/2462 +[#2464]: https://github.com/ARKEcosystem/core/pull/2464 +[#2471]: https://github.com/ARKEcosystem/core/pull/2471 +[#2476]: https://github.com/ARKEcosystem/core/pull/2476 diff --git a/LICENSE b/LICENSE index d6dd75272f..ecb1fa8e07 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) Ark Ecosystem +Copyright (c) ARK Ecosystem Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.md b/README.md index 2865acf7e2..f734b7832e 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,13 @@ -# Ark Core +# Persona Core -

- -

-[![Build Status](https://badgen.now.sh/circleci/github/ArkEcosystem/core)](https://circleci.com/gh/ArkEcosystem/core) +[![Build Status](https://badgen.now.sh/circleci/github/ARKEcosystem/core)](https://circleci.com/gh/ARKEcosystem/core) [![Codecov](https://badgen.now.sh/codecov/c/github/arkecosystem/core)](https://codecov.io/gh/arkecosystem/core) [![License: MIT](https://badgen.now.sh/badge/license/MIT/green)](https://opensource.org/licenses/MIT) ## Introduction -This repository contains all plugins that make up the Ark Core. +This repository contains all plugins that make up the Persona Core. ## Documentation @@ -24,40 +21,7 @@ This repository contains all plugins that make up the Ark Core. ## GitHub Development Bounty -- Get involved with Ark development and start earning ARK : https://bounty.ark.io - -## Core Packages - -| Package | Version | Description | -| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------ | -| **[core](/packages/core)** | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/core)](https://www.npmjs.com/package/@arkecosystem/core) | **Includes all packages** | -| [core-api](/packages/core-api) | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/core-api)](https://www.npmjs.com/package/@arkecosystem/core-api) | Public REST API | -| [core-blockchain](/packages/core-blockchain) | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/core-blockchain)](https://www.npmjs.com/package/@arkecosystem/core-blockchain) | Blockchain Managment | -| [core-container](/packages/core-container) | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/core-container)](https://www.npmjs.com/package/@arkecosystem/core-container) | Container Managment | -| [core-database](/packages/core-database) | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/core-database)](https://www.npmjs.com/package/@arkecosystem/core-database) | Database Interface | -| [core-database-postgres](/packages/core-database-postgres) | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/core-database-postgres)](https://www.npmjs.com/package/@arkecosystem/core-database-postgres) | Database Implementation - PostgreSQL | -| [core-debugger-cli](/packages/core-debugger-cli) | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/core-debugger-cli)](https://www.npmjs.com/package/@arkecosystem/core-debugger-cli) | Debugger CLI _(development only)_ | -| [core-deployer](/packages/core-deployer) | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/core-deployer)](https://www.npmjs.com/package/@arkecosystem/core-deployer) | Deployer CLI | -| [core-elasticsearch](/packages/core-elasticsearch) | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/core-elasticsearch)](https://www.npmjs.com/package/@arkecosystem/core-elasticsearch) | Elasticsearch Server | -| [core-error-tracker-bugsnag](/packages/core-error-tracker-bugsnag) | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/core-error-tracker-bugsnag)](https://www.npmjs.com/package/@arkecosystem/core-error-tracker-bugsnag) | Error Tracking - Bugsnag | -| [core-error-tracker-sentry](/packages/core-error-tracker-sentry) | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/core-error-tracker-sentry)](https://www.npmjs.com/package/@arkecosystem/core-error-tracker-sentry) | Error Tracking - Sentry | -| [core-event-emitter](/packages/core-event-emitter) | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/core-event-emitter)](https://www.npmjs.com/package/@arkecosystem/core-event-emitter) | Event Emitter | -| [core-forger](/packages/core-forger) | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/core-forger)](https://www.npmjs.com/package/@arkecosystem/core-forger) | Forger Manager | -| [core-graphql](/packages/core-graphql) | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/core-graphql)](https://www.npmjs.com/package/@arkecosystem/core-graphql) | GraphQL Server | -| [core-http-utils](/packages/core-http-utils) | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/core-http-utils)](https://www.npmjs.com/package/@arkecosystem/core-http-utils) | HTTP Utilities | -| [core-json-rpc](/packages/core-json-rpc) | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/core-json-rpc)](https://www.npmjs.com/package/@arkecosystem/core-json-rpc) | JSON-RPC Server | -| [core-logger](/packages/core-logger) | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/core-logger)](https://www.npmjs.com/package/@arkecosystem/core-logger) | Logger Interface | -| [core-logger-winston](/packages/core-logger-winston) | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/core-logger-winston)](https://www.npmjs.com/package/@arkecosystem/core-logger-winston) | Logger Implementation - Winston | -| [core-p2p](/packages/core-p2p) | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/core-p2p)](https://www.npmjs.com/package/@arkecosystem/core-p2p) | P2P Communication | -| [core-snapshots](/packages/core-snapshots) | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/core-snapshots)](https://www.npmjs.com/package/@arkecosystem/core-snapshots) | Snapshot Manager | -| [core-snapshots-cli](/packages/core-snapshots-cli) | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/core-snapshots-cli)](https://www.npmjs.com/package/@arkecosystem/core-snapshots-cli) | Snapshot CLI | -| [core-test-utils](/packages/core-test-utils) | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/core-test-utils)](https://www.npmjs.com/package/@arkecosystem/core-test-utils) | Test Utilities _(development only)_ | -| [core-tester-cli](/packages/core-tester-cli) | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/core-tester-cli)](https://www.npmjs.com/package/@arkecosystem/core-tester-cli) | Tester CLi _(development only)_ | -| [core-transaction-pool](/packages/core-transaction-pool) | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/core-transaction-pool)](https://www.npmjs.com/package/@arkecosystem/core-transaction-pool) | Transaction Pool | -| [core-utils](/packages/core-utils) | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/core-utils)](https://www.npmjs.com/package/@arkecosystem/core-utils) | Utilities | -| [core-vote-report](/packages/core-vote-report) | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/core-vote-report)](https://www.npmjs.com/package/@arkecosystem/core-vote-report) | Vote Report | -| [core-webhooks](/packages/core-webhooks) | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/core-webhooks)](https://www.npmjs.com/package/@arkecosystem/core-webhooks) | Webhook Server | -| [crypto](/packages/crypto) | [![npm](https://badgen.now.sh/npm/v/@arkecosystem/crypto)](https://www.npmjs.com/package/@arkecosystem/crypto) | Cryptography | +- Get involved with the development and start earning ARK : https://bounty.ark.io ## Security @@ -65,14 +29,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [All Contributors](../../contributors) -- [Alex Barnsley](https://github.com/alexbarnsley) -- [Brian Faust](https://github.com/faustbrian) -- [François-Xavier Thoorens](https://github.com/fix) -- [Joshua Noack](https://github.com/supaiku0) -- [Kristjan Košič](https://github.com/kristjank) -- [Vasil Dimov](https://github.com/vasild) +This project exists thanks to all the people who [contribute](../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/__tests__/functional/transaction-forging/__support__/index.ts b/__tests__/functional/transaction-forging/__support__/index.ts new file mode 100644 index 0000000000..b113c6db61 --- /dev/null +++ b/__tests__/functional/transaction-forging/__support__/index.ts @@ -0,0 +1,70 @@ +import "jest-extended"; + +import { bignumify } from "@arkecosystem/core-utils"; +import { configManager, PublicKey } from "@arkecosystem/crypto"; +import delay from "delay"; +import { RestClient } from "../../../helpers"; +import { secrets } from "../../../utils/config/testnet/delegates.json"; +import { setUpContainer } from "../../../utils/helpers/container"; + +jest.setTimeout(1200000); + +let app; +export async function setUp() { + app = await setUpContainer({ + include: [ + "@arkecosystem/core-event-emitter", + "@arkecosystem/core-logger-pino", + "@arkecosystem/core-database-postgres", + "@arkecosystem/core-transaction-pool", + "@arkecosystem/core-p2p", + "@arkecosystem/core-blockchain", + "@arkecosystem/core-api", + "@arkecosystem/core-forger", + ], + }); + + const databaseService = app.resolvePlugin("database"); + await databaseService.reset(); + await databaseService.buildWallets(); + await databaseService.saveRound( + secrets.map(secret => ({ + round: 1, + publicKey: PublicKey.fromPassphrase(secret), + voteBalance: bignumify("245098000000000"), + })), + ); +} + +export async function tearDown() { + await app.tearDown(); +} + +export async function snoozeForBlock(sleep: number = 0, height: number = 1) { + const blockTime = configManager.getMilestone(height).blocktime * 1000; + const sleepTime = sleep * 1000; + + return delay(blockTime + sleepTime); +} + +export async function expectAcceptAndBroadcast(transactions, id): Promise { + const { body } = await RestClient.broadcast(transactions); + + if (body.data.invalid.length) { + console.log(body.errors); + } + + expect(body.data.accept).toContain(id); + expect(body.data.broadcast).toContain(id); +} + +export async function expectTransactionForged(id): Promise { + const { body } = await RestClient.get(`transactions/${id}`); + + expect(body.data.id).toBe(id); +} + +export const passphrases = { + passphrase: "this is top secret passphrase number 1", + secondPassphrase: "this is top secret passphrase number 2", +}; diff --git a/__tests__/functional/transaction-forging/delegate-registration.test.ts b/__tests__/functional/transaction-forging/delegate-registration.test.ts new file mode 100644 index 0000000000..901e1018ef --- /dev/null +++ b/__tests__/functional/transaction-forging/delegate-registration.test.ts @@ -0,0 +1,63 @@ +import { Address } from "@arkecosystem/crypto"; +import { TransactionFactory } from "../../helpers/transaction-factory"; +import { secrets } from "../../utils/config/testnet/delegates.json"; +import * as support from "./__support__"; + +const { passphrase, secondPassphrase } = support.passphrases; + +beforeAll(support.setUp); +afterAll(support.tearDown); + +describe("Transaction Forging - Delegate Registration", () => { + it("should broadcast, accept and forge it [Signed with 1 Passphase]", async () => { + // Initial Funds + const initialFunds = TransactionFactory.transfer(Address.fromPassphrase(passphrase), 100 * 1e8) + .withPassphrase(secrets[0]) + .create(); + + await support.expectAcceptAndBroadcast(initialFunds, initialFunds[0].id); + await support.snoozeForBlock(1); + await support.expectTransactionForged(initialFunds[0].id); + + // Register a delegate + const transactions = TransactionFactory.delegateRegistration() + .withPassphrase(passphrase) + .create(); + + await support.expectAcceptAndBroadcast(transactions, transactions[0].id); + await support.snoozeForBlock(1); + await support.expectTransactionForged(transactions[0].id); + }); + + it("should broadcast, accept and forge it [Signed with 2 Passphrases]", async () => { + // Make a fresh wallet for the second signature tests + const passphrase = secondPassphrase; + + // Initial Funds + const initialFunds = TransactionFactory.transfer(Address.fromPassphrase(passphrase), 100 * 1e8) + .withPassphrase(secrets[0]) + .create(); + + await support.expectAcceptAndBroadcast(initialFunds, initialFunds[0].id); + await support.snoozeForBlock(1); + await support.expectTransactionForged(initialFunds[0].id); + + // Register a second passphrase + const secondSignature = TransactionFactory.secondSignature(secondPassphrase) + .withPassphrase(passphrase) + .create(); + + await support.expectAcceptAndBroadcast(secondSignature, secondSignature[0].id); + await support.snoozeForBlock(1); + await support.expectTransactionForged(secondSignature[0].id); + + // Register a delegate + const delegateRegistration = TransactionFactory.delegateRegistration() + .withPassphrasePair({ passphrase, secondPassphrase }) + .create(); + + await support.expectAcceptAndBroadcast(delegateRegistration, delegateRegistration[0].id); + await support.snoozeForBlock(1); + await support.expectTransactionForged(delegateRegistration[0].id); + }); +}); diff --git a/__tests__/functional/transaction-forging/second-signature-registration.test.ts b/__tests__/functional/transaction-forging/second-signature-registration.test.ts new file mode 100644 index 0000000000..e44d6495c1 --- /dev/null +++ b/__tests__/functional/transaction-forging/second-signature-registration.test.ts @@ -0,0 +1,18 @@ +import { TransactionFactory } from "../../helpers/transaction-factory"; +import { secrets } from "../../utils/config/testnet/delegates.json"; +import * as support from "./__support__"; + +beforeAll(support.setUp); +afterAll(support.tearDown); + +describe("Transaction Forging - Second Signature Registration", () => { + it("should broadcast, accept and forge it [Signed with 1 Passphase]", async () => { + const secondSignature = TransactionFactory.secondSignature(support.passphrases.secondPassphrase) + .withPassphrase(secrets[0]) + .create(); + + await support.expectAcceptAndBroadcast(secondSignature, secondSignature[0].id); + await support.snoozeForBlock(1); + await support.expectTransactionForged(secondSignature[0].id); + }); +}); diff --git a/__tests__/functional/transaction-forging/transfer.test.ts b/__tests__/functional/transaction-forging/transfer.test.ts new file mode 100644 index 0000000000..e70ff7aa0c --- /dev/null +++ b/__tests__/functional/transaction-forging/transfer.test.ts @@ -0,0 +1,50 @@ +import { Address } from "@arkecosystem/crypto"; +import { TransactionFactory } from "../../helpers/transaction-factory"; +import { secrets } from "../../utils/config/testnet/delegates.json"; +import * as support from "./__support__"; + +const { passphrase, secondPassphrase } = support.passphrases; + +beforeAll(support.setUp); +afterAll(support.tearDown); + +describe("Transaction Forging - Transfer", () => { + it("should broadcast, accept and forge it [Signed with 1 Passphase]", async () => { + const transfer = TransactionFactory.transfer(Address.fromPassphrase(passphrase)) + .withPassphrase(secrets[0]) + .create(); + + await support.expectAcceptAndBroadcast(transfer, transfer[0].id); + await support.snoozeForBlock(1); + await support.expectTransactionForged(transfer[0].id); + }); + + it("should broadcast, accept and forge it [Signed with 2 Passphrases]", async () => { + // Funds to register a second passphrase + const initialFunds = TransactionFactory.transfer(Address.fromPassphrase(passphrase), 50 * 1e8) + .withPassphrase(secrets[0]) + .create(); + + await support.expectAcceptAndBroadcast(initialFunds, initialFunds[0].id); + await support.snoozeForBlock(1); + await support.expectTransactionForged(initialFunds[0].id); + + // Register a second passphrase + const secondSignature = TransactionFactory.secondSignature(secondPassphrase) + .withPassphrase(passphrase) + .create(); + + await support.expectAcceptAndBroadcast(secondSignature, secondSignature[0].id); + await support.snoozeForBlock(1); + await support.expectTransactionForged(secondSignature[0].id); + + // Submit a transfer with 2 passprhases + const transfer = TransactionFactory.transfer(Address.fromPassphrase(passphrase)) + .withPassphrasePair(support.passphrases) + .create(); + + await support.expectAcceptAndBroadcast(transfer, transfer[0].id); + await support.snoozeForBlock(1); + await support.expectTransactionForged(transfer[0].id); + }); +}); diff --git a/__tests__/functional/transaction-forging/vote.test.ts b/__tests__/functional/transaction-forging/vote.test.ts new file mode 100644 index 0000000000..1c85c81b7a --- /dev/null +++ b/__tests__/functional/transaction-forging/vote.test.ts @@ -0,0 +1,63 @@ +import { Address, PublicKey } from "@arkecosystem/crypto"; +import { TransactionFactory } from "../../helpers/transaction-factory"; +import { secrets } from "../../utils/config/testnet/delegates.json"; +import * as support from "./__support__"; + +const { passphrase, secondPassphrase } = support.passphrases; + +beforeAll(support.setUp); +afterAll(support.tearDown); + +describe("Transaction Forging - Vote", () => { + it("should broadcast, accept and forge it [Signed with 1 Passphase]", async () => { + // Initial Funds + const initialFunds = TransactionFactory.transfer(Address.fromPassphrase(passphrase), 100 * 1e8) + .withPassphrase(secrets[0]) + .create(); + + await support.expectAcceptAndBroadcast(initialFunds, initialFunds[0].id); + await support.snoozeForBlock(1); + await support.expectTransactionForged(initialFunds[0].id); + + // Submit a vote + const transactions = TransactionFactory.vote(PublicKey.fromPassphrase(secrets[0])) + .withPassphrase(passphrase) + .create(); + + await support.expectAcceptAndBroadcast(transactions, transactions[0].id); + await support.snoozeForBlock(1); + await support.expectTransactionForged(transactions[0].id); + }); + + it("should broadcast, accept and forge it [Signed with 2 Passphrases]", async () => { + // Make a fresh wallet for the second signature tests + const passphrase = secondPassphrase; + + // Initial Funds + const initialFunds = TransactionFactory.transfer(Address.fromPassphrase(passphrase), 100 * 1e8) + .withPassphrase(secrets[0]) + .create(); + + await support.expectAcceptAndBroadcast(initialFunds, initialFunds[0].id); + await support.snoozeForBlock(1); + await support.expectTransactionForged(initialFunds[0].id); + + // Register a second passphrase + const secondSignature = TransactionFactory.secondSignature(secondPassphrase) + .withPassphrase(passphrase) + .create(); + + await support.expectAcceptAndBroadcast(secondSignature, secondSignature[0].id); + await support.snoozeForBlock(1); + await support.expectTransactionForged(secondSignature[0].id); + + // Submit a vote + const vote = TransactionFactory.vote(PublicKey.fromPassphrase(secrets[0])) + .withPassphrasePair({ passphrase, secondPassphrase }) + .create(); + + await support.expectAcceptAndBroadcast(vote, vote[0].id); + await support.snoozeForBlock(1); + await support.expectTransactionForged(vote[0].id); + }); +}); diff --git a/__tests__/helpers/index.ts b/__tests__/helpers/index.ts new file mode 100644 index 0000000000..554619fbf5 --- /dev/null +++ b/__tests__/helpers/index.ts @@ -0,0 +1,2 @@ +export * from "./rest-client"; +export * from "./transaction-factory"; diff --git a/__tests__/helpers/rest-client.ts b/__tests__/helpers/rest-client.ts new file mode 100644 index 0000000000..cfb68dea8e --- /dev/null +++ b/__tests__/helpers/rest-client.ts @@ -0,0 +1,15 @@ +import { httpie, IHttpieResponse } from "@arkecosystem/core-utils"; + +export class RestClient { + public static async get(path: string, opts?): Promise> { + return httpie.get(`http://localhost:4003/api/v2/${path}`, opts); + } + + public static async post(path: string, body): Promise> { + return httpie.post(`http://localhost:4003/api/v2/${path}`, { body }); + } + + public static async broadcast(transactions): Promise> { + return RestClient.post("transactions", { transactions }); + } +} diff --git a/__tests__/helpers/transaction-factory.ts b/__tests__/helpers/transaction-factory.ts new file mode 100644 index 0000000000..0224d7d4b6 --- /dev/null +++ b/__tests__/helpers/transaction-factory.ts @@ -0,0 +1,181 @@ +import { configManager, ITransactionData, NetworkName, Transaction, transactionBuilder } from "@arkecosystem/crypto"; +import { Address, PublicKey } from "@arkecosystem/crypto"; +import pokemon from "pokemon"; +import { secrets } from "../utils/config/testnet/delegates.json"; + +const defaultPassphrase: string = secrets[0]; + +interface PassphrasePair { + passphrase: string; + secondPassphrase: string; +} + +export class TransactionFactory { + public static transfer(recipientId?: string, amount: number = 2 * 1e8, vendorField?: string): TransactionFactory { + const builder = transactionBuilder + .transfer() + .amount(amount) + .recipientId(recipientId || Address.fromPassphrase(defaultPassphrase)); + + if (vendorField) { + builder.vendorField(vendorField); + } + + return new TransactionFactory(builder); + } + + public static secondSignature(secondPassphrase?: string): TransactionFactory { + return new TransactionFactory( + transactionBuilder.secondSignature().signatureAsset(secondPassphrase || defaultPassphrase), + ); + } + + public static delegateRegistration(username?: string): TransactionFactory { + return new TransactionFactory(transactionBuilder.delegateRegistration().usernameAsset(username)); + } + + public static vote(publicKey?: string): TransactionFactory { + return new TransactionFactory( + transactionBuilder.vote().votesAsset([`+${publicKey || PublicKey.fromPassphrase(defaultPassphrase)}`]), + ); + } + + public static unvote(publicKey?: string): TransactionFactory { + return new TransactionFactory( + transactionBuilder.vote().votesAsset([`-${publicKey || PublicKey.fromPassphrase(defaultPassphrase)}`]), + ); + } + + private builder: any; + private network: NetworkName = "testnet"; + private fee: number; + private milestone: Record; + private passphrase: string = defaultPassphrase; + private secondPassphrase: string; + private passphraseList: string[]; + private passphrasePairs: PassphrasePair[]; + + public constructor(builder) { + this.builder = builder; + } + + public withFee(fee: number): TransactionFactory { + this.fee = fee; + + return this; + } + + public withNetwork(network: NetworkName): TransactionFactory { + this.network = network; + + return this; + } + + public withHeight(height: number): TransactionFactory { + this.milestone = configManager.getMilestone(height); + + return this; + } + + public withPassphrase(passphrase: string): TransactionFactory { + this.passphrase = passphrase; + + return this; + } + + public withSecondPassphrase(secondPassphrase: string): TransactionFactory { + this.secondPassphrase = secondPassphrase; + + return this; + } + + public withPassphraseList(passphrases: string[]): TransactionFactory { + this.passphraseList = passphrases; + + return this; + } + + public withPassphrasePair(passphrases: PassphrasePair): TransactionFactory { + this.passphrase = passphrases.passphrase; + this.secondPassphrase = passphrases.secondPassphrase; + + return this; + } + + public withPassphrasePairs(passphrases: PassphrasePair[]): TransactionFactory { + this.passphrasePairs = passphrases; + + return this; + } + + public create(quantity: number = 1): ITransactionData[] { + return this.make(quantity, "getStruct"); + } + + public build(quantity: number = 1): Transaction[] { + return this.make(quantity, "build"); + } + + private make(quantity: number = 1, method: string): T[] { + if (this.passphrasePairs && this.passphrasePairs.length) { + return this.passphrasePairs.map( + (passphrasePair: PassphrasePair) => + this.withPassphrase(passphrasePair.passphrase) + .withSecondPassphrase(passphrasePair.secondPassphrase) + .sign(quantity, method)[0], + ); + } + + if (this.passphraseList && this.passphraseList.length) { + return this.passphraseList.map( + (passphrase: string) => this.withPassphrase(passphrase).sign(quantity, method)[0], + ); + } + + return this.sign(quantity, method); + } + + private sign(quantity: number, method: string): T[] { + configManager.setFromPreset(this.network); + + const transactions: T[] = []; + + for (let i = 0; i < quantity; i++) { + if (this.builder.constructor.name === "TransferBuilder") { + // @FIXME: when we use any of the "withPassphrase*" methods the builder will + // always remember the previous vendor field instead generating a new one on each iteration + this.builder.vendorField(`Test Transaction ${i + 1}`); + } + + if (this.builder.constructor.name === "DelegateRegistrationBuilder") { + // @FIXME: when we use any of the "withPassphrase*" methods the builder will + // always remember the previous username instead generating a new one on each iteration + if (!this.builder.data.asset.delegate.username) { + this.builder = transactionBuilder.delegateRegistration().usernameAsset(this.getRandomUsername()); + } + } + + if (this.fee) { + this.builder.fee(this.fee); + } + + this.builder.sign(this.passphrase); + + if (this.secondPassphrase) { + this.builder.secondSign(this.secondPassphrase); + } + + transactions.push(this.builder[method]()); + } + + return transactions; + } + + private getRandomUsername(): string { + return pokemon + .random() + .toLowerCase() + .replace(/[^a-z0-9]/g, "_") + .substring(0, 20); + } +} diff --git a/packages/core-api/__tests__/__support__/setup.ts b/__tests__/integration/core-api/__support__/setup.ts similarity index 73% rename from packages/core-api/__tests__/__support__/setup.ts rename to __tests__/integration/core-api/__support__/setup.ts index d990a0a17e..e191472ded 100644 --- a/packages/core-api/__tests__/__support__/setup.ts +++ b/__tests__/integration/core-api/__support__/setup.ts @@ -1,13 +1,13 @@ import { app } from "@arkecosystem/core-container"; import { Database } from "@arkecosystem/core-interfaces"; import delay from "delay"; -import { registerWithContainer, setUpContainer } from "../../../core-test-utils/src/helpers/container"; -import { plugin } from "../../src/plugin"; +import { plugin } from "../../../../packages/core-api/src/plugin"; +import { registerWithContainer, setUpContainer } from "../../../utils/helpers/container"; -import { delegates } from "../../../core-test-utils/src/fixtures"; +import { delegates } from "../../../utils/fixtures"; import { generateRound } from "./utils/generate-round"; -import { queries } from "../../../core-database-postgres/src/queries"; +import { sortBy } from "@arkecosystem/utils"; const round = generateRound(delegates.map(delegate => delegate.publicKey), 1); @@ -24,7 +24,6 @@ async function setUp() { await setUpContainer({ exclude: [ "@arkecosystem/core-webhooks", - "@arkecosystem/core-graphql", "@arkecosystem/core-forger", "@arkecosystem/core-json-rpc", "@arkecosystem/core-api", @@ -33,8 +32,7 @@ async function setUp() { const databaseService = app.resolvePlugin("database"); await databaseService.connection.roundsRepository.truncate(); - await databaseService.buildWallets(1); - await databaseService.saveWallets(true); + await databaseService.buildWallets(); await databaseService.saveRound(round); await registerWithContainer(plugin, options); @@ -50,11 +48,12 @@ async function tearDown() { async function calculateRanks() { const databaseService = app.resolvePlugin("database"); - const rows = await (databaseService.connection as any).query.manyOrNone(queries.spv.delegatesRanks); + const delegateWallets = Object.values(databaseService.walletManager.allByUsername()).sort( + (a: Database.IWallet, b: Database.IWallet) => b.voteBalance.comparedTo(a.voteBalance), + ); - rows.forEach((delegate, i) => { + sortBy(delegateWallets, "publicKey").forEach((delegate, i) => { const wallet = databaseService.walletManager.findByPublicKey(delegate.publicKey); - wallet.missedBlocks = +delegate.missedBlocks; (wallet as any).rate = i + 1; databaseService.walletManager.reindex(wallet); diff --git a/packages/core-api/__tests__/__support__/utils/generate-round.ts b/__tests__/integration/core-api/__support__/utils/generate-round.ts similarity index 100% rename from packages/core-api/__tests__/__support__/utils/generate-round.ts rename to __tests__/integration/core-api/__support__/utils/generate-round.ts diff --git a/packages/core-api/__tests__/repositories/transactions.test.ts b/__tests__/integration/core-api/repositories/transactions.test.ts similarity index 98% rename from packages/core-api/__tests__/repositories/transactions.test.ts rename to __tests__/integration/core-api/repositories/transactions.test.ts index cd2685399a..1e7c1a9cd0 100644 --- a/packages/core-api/__tests__/repositories/transactions.test.ts +++ b/__tests__/integration/core-api/repositories/transactions.test.ts @@ -1,10 +1,10 @@ -import "@arkecosystem/core-test-utils"; import "jest-extended"; +import "../../../utils"; import { crypto } from "@arkecosystem/crypto"; -import genesisBlock from "../../../core-test-utils/src/config/testnet/genesisBlock.json"; // noinspection TypeScriptPreferShortImport -import { TransactionsRepository } from "../../dist/repositories/transactions"; +import { TransactionsRepository } from "../../../../packages/core-api/src/repositories/transactions"; +import genesisBlock from "../../../utils/config/testnet/genesisBlock.json"; import { setUp, tearDown } from "../__support__/setup"; let repository; diff --git a/packages/core-api/__tests__/v1/handlers/accounts.test.ts b/__tests__/integration/core-api/v1/handlers/accounts.test.ts similarity index 98% rename from packages/core-api/__tests__/v1/handlers/accounts.test.ts rename to __tests__/integration/core-api/v1/handlers/accounts.test.ts index 2643fd1b30..d4abbe90e3 100644 --- a/packages/core-api/__tests__/v1/handlers/accounts.test.ts +++ b/__tests__/integration/core-api/v1/handlers/accounts.test.ts @@ -1,4 +1,4 @@ -import "@arkecosystem/core-test-utils"; +import "../../../../utils"; import { setUp, tearDown } from "../../__support__/setup"; import { utils } from "../utils"; diff --git a/packages/core-api/__tests__/v1/handlers/blocks.test.ts b/__tests__/integration/core-api/v1/handlers/blocks.test.ts similarity index 96% rename from packages/core-api/__tests__/v1/handlers/blocks.test.ts rename to __tests__/integration/core-api/v1/handlers/blocks.test.ts index c4f39662a3..951162c16f 100644 --- a/packages/core-api/__tests__/v1/handlers/blocks.test.ts +++ b/__tests__/integration/core-api/v1/handlers/blocks.test.ts @@ -1,6 +1,6 @@ import { app } from "@arkecosystem/core-container"; -import "@arkecosystem/core-test-utils"; -import genesisBlock from "../../../../core-test-utils/src/config/testnet/genesisBlock.json"; +import "../../../../utils"; +import genesisBlock from "../../../../utils/config/testnet/genesisBlock.json"; import { setUp, tearDown } from "../../__support__/setup"; import { utils } from "../utils"; diff --git a/packages/core-api/__tests__/v1/handlers/delegates.test.ts b/__tests__/integration/core-api/v1/handlers/delegates.test.ts similarity index 98% rename from packages/core-api/__tests__/v1/handlers/delegates.test.ts rename to __tests__/integration/core-api/v1/handlers/delegates.test.ts index 6e6a915a9f..be3bdcb043 100644 --- a/packages/core-api/__tests__/v1/handlers/delegates.test.ts +++ b/__tests__/integration/core-api/v1/handlers/delegates.test.ts @@ -1,4 +1,4 @@ -import "@arkecosystem/core-test-utils"; +import "../../../../utils"; import { setUp, tearDown } from "../../__support__/setup"; import { utils } from "../utils"; diff --git a/packages/core-api/__tests__/v1/handlers/loader.test.ts b/__tests__/integration/core-api/v1/handlers/loader.test.ts similarity index 97% rename from packages/core-api/__tests__/v1/handlers/loader.test.ts rename to __tests__/integration/core-api/v1/handlers/loader.test.ts index f8f79f765b..4152e784f8 100644 --- a/packages/core-api/__tests__/v1/handlers/loader.test.ts +++ b/__tests__/integration/core-api/v1/handlers/loader.test.ts @@ -1,4 +1,4 @@ -import "@arkecosystem/core-test-utils"; +import "../../../../utils"; import { setUp, tearDown } from "../../__support__/setup"; import { utils } from "../utils"; diff --git a/packages/core-api/__tests__/v1/handlers/peers.test.ts b/__tests__/integration/core-api/v1/handlers/peers.test.ts similarity index 98% rename from packages/core-api/__tests__/v1/handlers/peers.test.ts rename to __tests__/integration/core-api/v1/handlers/peers.test.ts index 4df8286e72..8b3eb6b30f 100644 --- a/packages/core-api/__tests__/v1/handlers/peers.test.ts +++ b/__tests__/integration/core-api/v1/handlers/peers.test.ts @@ -1,6 +1,6 @@ import { app } from "@arkecosystem/core-container"; import { Peer } from "@arkecosystem/core-p2p/src/peer"; -import "@arkecosystem/core-test-utils"; +import "../../../../utils"; import { setUp, tearDown } from "../../__support__/setup"; import { utils } from "../utils"; diff --git a/packages/core-api/__tests__/v1/handlers/signatures.test.ts b/__tests__/integration/core-api/v1/handlers/signatures.test.ts similarity index 93% rename from packages/core-api/__tests__/v1/handlers/signatures.test.ts rename to __tests__/integration/core-api/v1/handlers/signatures.test.ts index 4e9b1fd137..3e4b76136c 100644 --- a/packages/core-api/__tests__/v1/handlers/signatures.test.ts +++ b/__tests__/integration/core-api/v1/handlers/signatures.test.ts @@ -1,4 +1,4 @@ -import "@arkecosystem/core-test-utils"; +import "../../../../utils"; import { setUp, tearDown } from "../../__support__/setup"; import { utils } from "../utils"; diff --git a/packages/core-api/__tests__/v1/handlers/transactions.test.ts b/__tests__/integration/core-api/v1/handlers/transactions.test.ts similarity index 98% rename from packages/core-api/__tests__/v1/handlers/transactions.test.ts rename to __tests__/integration/core-api/v1/handlers/transactions.test.ts index 795f11a5be..86c921012d 100644 --- a/packages/core-api/__tests__/v1/handlers/transactions.test.ts +++ b/__tests__/integration/core-api/v1/handlers/transactions.test.ts @@ -1,5 +1,5 @@ -import "@arkecosystem/core-test-utils"; -import genesisBlock from "../../../../core-test-utils/src/config/testnet/genesisBlock.json"; +import "../../../../utils"; +import genesisBlock from "../../../../utils/config/testnet/genesisBlock.json"; import { setUp, tearDown } from "../../__support__/setup"; import { utils } from "../utils"; diff --git a/packages/core-api/__tests__/v1/utils.ts b/__tests__/integration/core-api/v1/utils.ts similarity index 87% rename from packages/core-api/__tests__/v1/utils.ts rename to __tests__/integration/core-api/v1/utils.ts index e73a43e51b..ba05a73740 100644 --- a/packages/core-api/__tests__/v1/utils.ts +++ b/__tests__/integration/core-api/v1/utils.ts @@ -1,8 +1,8 @@ import { app } from "@arkecosystem/core-container"; -import { ApiHelpers } from "@arkecosystem/core-test-utils/dist/helpers/api"; +import { httpie } from "@arkecosystem/core-utils"; import { client, NetworkManager, transactionBuilder } from "@arkecosystem/crypto"; -import axios from "axios"; import "jest-extended"; +import { ApiHelpers } from "../../../utils/helpers/api"; class Helpers { public async request(method, path, params = {}) { @@ -55,10 +55,8 @@ class Helpers { expect(delegate.publicKey).toBeString(); expect(delegate.vote).toBeString(); expect(delegate.rate).toBeNumber(); - expect(delegate.missedblocks).toBeNumber(); expect(delegate.producedblocks).toBeNumber(); expect(delegate.approval).toBeNumber(); - expect(delegate.productivity).toBeNumber(); Object.keys(expected || {}).forEach(attr => { expect(delegate[attr]).toBe(expected[attr]); @@ -83,15 +81,12 @@ class Helpers { .sign("clay harbor enemy utility margin pretty hub comic piece aerobic umbrella acquire") .getStruct(); - await axios.post( - "http://127.0.0.1:4003/api/v2/transactions", - { + await httpie.post("http://127.0.0.1:4003/api/v2/transactions", { + body: { transactions: [transaction], }, - { - headers: { "Content-Type": "application/json" }, - }, - ); + headers: { "Content-Type": "application/json" }, + }); return transaction; } diff --git a/packages/core-api/__tests__/v2/handlers/blocks.test.ts b/__tests__/integration/core-api/v2/handlers/blocks.test.ts similarity index 95% rename from packages/core-api/__tests__/v2/handlers/blocks.test.ts rename to __tests__/integration/core-api/v2/handlers/blocks.test.ts index 9d92841bbd..c27d955955 100644 --- a/packages/core-api/__tests__/v2/handlers/blocks.test.ts +++ b/__tests__/integration/core-api/v2/handlers/blocks.test.ts @@ -1,11 +1,11 @@ -import "@arkecosystem/core-test-utils"; +import "../../../../utils"; import { setUp, tearDown } from "../../__support__/setup"; import { utils } from "../utils"; import { models } from "@arkecosystem/crypto"; -import genesisBlock from "../../../../core-test-utils/src/config/testnet/genesisBlock.json"; -import { blocks2to100 } from "../../../../core-test-utils/src/fixtures"; -import { resetBlockchain } from "../../../../core-test-utils/src/helpers"; +import genesisBlock from "../../../../utils/config/testnet/genesisBlock.json"; +import { blocks2to100 } from "../../../../utils/fixtures"; +import { resetBlockchain } from "../../../../utils/helpers"; import { app } from "@arkecosystem/core-container"; import { Database } from "@arkecosystem/core-interfaces"; @@ -55,8 +55,8 @@ describe("API 2.0 - Blocks", () => { expect(response).toBePaginated(); expect(response.data.data).toBeArray(); - const block = response.data.data[0]; - utils.expectBlock(block); + response.data.data.forEach(utils.expectBlock); + expect(response.data.data.sort((a, b) => a.height > b.height)).toEqual(response.data.data); }); }, ); @@ -82,6 +82,27 @@ describe("API 2.0 - Blocks", () => { ); }); + describe("GET /blocks/:height", () => { + describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( + "using the %s header", + (header, request) => { + it("should GET a block by the given height", async () => { + const response = await utils[request]("GET", `blocks/${genesisBlock.height}`); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeObject(); + + const block = response.data.data; + utils.expectBlock(block, { + id: genesisBlock.id, + height: genesisBlock.height, + transactions: genesisBlock.numberOfTransactions, + }); + }); + }, + ); + }); + describe("GET /blocks/:id/transactions", () => { describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( 'using the "%s" header', diff --git a/__tests__/integration/core-api/v2/handlers/delegates.test.ts b/__tests__/integration/core-api/v2/handlers/delegates.test.ts new file mode 100644 index 0000000000..d960c8a374 --- /dev/null +++ b/__tests__/integration/core-api/v2/handlers/delegates.test.ts @@ -0,0 +1,508 @@ +import "../../../../utils"; +import { calculateRanks, setUp, tearDown } from "../../__support__/setup"; +import { utils } from "../utils"; + +import { blocks2to100 } from "../../../../utils/fixtures/testnet/blocks2to100"; + +import { Bignum, models } from "@arkecosystem/crypto"; +const { Block } = models; + +import { app } from "@arkecosystem/core-container"; +import { Database } from "@arkecosystem/core-interfaces"; + +const delegate = { + username: "genesis_10", + address: "AFyf2qVpX2JbpKcy29XbusedCpFDeYFX8Q", + publicKey: "02f7acb179ddfddb2e220aa600921574646ac59fd3f1ae6255ada40b9a7fab75fd", + forgedFees: 50, + forgedRewards: 50, + forgedTotal: 100, + producedBlocks: 75, + voteBalance: 100000, +}; + +const delegate2 = { + username: "genesis_11", +}; + +beforeAll(async () => { + await setUp(); + await calculateRanks(); + + const wm = app.resolvePlugin("database").walletManager; + const wallet = wm.findByUsername("genesis_10"); + wallet.forgedFees = new Bignum(delegate.forgedFees); + wallet.forgedRewards = new Bignum(delegate.forgedRewards); + wallet.producedBlocks = 75; + wallet.voteBalance = new Bignum(delegate.voteBalance); + wm.reindex(wallet); +}); + +afterAll(async () => { + await tearDown(); +}); + +describe("API 2.0 - Delegates", () => { + describe("GET /delegates", () => { + describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( + "using the %s header", + (header, request) => { + it("should GET all the delegates", async () => { + const response = await utils[request]("GET", "delegates"); + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + + response.data.data.forEach(utils.expectDelegate); + expect(response.data.data.sort((a, b) => a.rank < b.rank)).toEqual(response.data.data); + }); + + it("should GET all the delegates sorted by votes,asc", async () => { + const wm = app.resolvePlugin("database").walletManager; + const wallet = wm.findByUsername("genesis_51"); + wallet.voteBalance = new Bignum(1); + wm.reindex(wallet); + + const response = await utils[request]("GET", "delegates", { orderBy: "votes:asc" }); + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + + expect(response.data.data[0].username).toBe(wallet.username); + expect(response.data.data[0].votes).toBe(+wallet.voteBalance.toFixed()); + }); + + it("should GET all the delegates sorted by votes,desc", async () => { + const wm = app.resolvePlugin("database").walletManager; + const wallet = wm.findByUsername("genesis_1"); + wallet.voteBalance = new Bignum(12500000000000000); + wm.reindex(wallet); + + const response = await utils[request]("GET", "delegates", { orderBy: "votes:desc" }); + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + + expect(response.data.data[0].username).toBe(wallet.username); + expect(response.data.data[0].votes).toBe(+wallet.voteBalance.toFixed()); + }); + }, + ); + + describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( + "using the %s header", + (header, request) => { + it("should GET all the delegates ordered by descending rank", async () => { + const response = await utils[request]("GET", "delegates", { orderBy: "rank:desc" }); + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + + response.data.data.forEach(utils.expectDelegate); + expect(response.data.data.sort((a, b) => a.rank > b.rank)).toEqual(response.data.data); + }); + }, + ); + + describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( + "using the %s header", + (header, request) => { + it("should GET all the delegates ordered by descending approval", async () => { + const response = await utils[request]("GET", "delegates", { orderBy: "approval:desc" }); + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + + response.data.data.forEach(utils.expectDelegate); + expect(response.data.data.sort((a, b) => a.production.approval > b.production.approval)).toEqual( + response.data.data, + ); + }); + }, + ); + }); + + describe("GET /delegates/:id", () => { + describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( + "using the %s header", + (header, request) => { + it("should GET a delegate by the given username", async () => { + const response = await utils[request]("GET", `delegates/${delegate.username}`); + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeObject(); + + utils.expectDelegate(response.data.data); + expect(response.data.data.username).toEqual(delegate.username); + }); + }, + ); + + describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( + "using the %s header", + (header, request) => { + it("should GET a delegate by the given address", async () => { + const response = await utils[request]("GET", `delegates/${delegate.address}`); + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeObject(); + + utils.expectDelegate(response.data.data); + expect(response.data.data.address).toEqual(delegate.address); + }); + }, + ); + + describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( + "using the %s header", + (header, request) => { + it("should GET a delegate by the given public key", async () => { + const response = await utils[request]("GET", `delegates/${delegate.publicKey}`); + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeObject(); + + utils.expectDelegate(response.data.data); + expect(response.data.data.publicKey).toEqual(delegate.publicKey); + }); + }, + ); + }); + + describe("POST /delegates/search", () => { + describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( + "using the %s header", + (header, request) => { + it("should POST a search for delegates with an address that matches the given string", async () => { + const response = await utils[request]("POST", "delegates/search", { + address: delegate.address, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(1); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.address).toBe(delegate.address); + } + }); + + it("should POST a search for delegates with a public key that matches the given string", async () => { + const response = await utils[request]("POST", "delegates/search", { + publicKey: delegate.publicKey, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(1); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.publicKey).toBe(delegate.publicKey); + } + }); + + it("should POST a search for delegates with a username that matches the given string", async () => { + const response = await utils[request]("POST", "delegates/search", { + username: delegate.username, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(1); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.username).toEqual(delegate.username); + } + }); + + it("should POST a search for delegates with any of the specified usernames", async () => { + const usernames = [delegate.username, delegate2.username]; + + const response = await utils[request]("POST", "delegates/search", { + usernames, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(2); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(usernames.includes(elem.username)).toBe(true); + } + }); + + it("should POST a search for delegates with the exact specified approval", async () => { + const response = await utils[request]("POST", "delegates/search", { + approval: { + from: 0, + to: 0, + }, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(2); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.production.approval).toEqual(0); + } + }); + + it("should POST a search for delegates with the specified approval range", async () => { + const response = await utils[request]("POST", "delegates/search", { + approval: { + from: 1, + to: 100, + }, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(49); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.production.approval).toBeGreaterThanOrEqual(1); + expect(elem.production.approval).toBeLessThanOrEqual(100); + } + }); + + it("should POST a search for delegates with the exact specified forged fees", async () => { + const response = await utils[request]("POST", "delegates/search", { + forgedFees: { + from: delegate.forgedFees, + to: delegate.forgedFees, + }, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(1); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.forged.fees).toEqual(delegate.forgedFees); + } + }); + + it("should POST a search for delegates with the specified forged fees range", async () => { + const response = await utils[request]("POST", "delegates/search", { + forgedFees: { + from: 0, + to: delegate.forgedFees, + }, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(51); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.forged.fees).toBeGreaterThanOrEqual(0); + expect(elem.forged.fees).toBeLessThanOrEqual(delegate.forgedFees); + } + }); + + it("should POST a search for delegates with the exact specified forged rewards", async () => { + const response = await utils[request]("POST", "delegates/search", { + forgedRewards: { + from: delegate.forgedRewards, + to: delegate.forgedRewards, + }, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(1); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.forged.rewards).toEqual(delegate.forgedRewards); + } + }); + + it("should POST a search for delegates with the specified forged rewards range", async () => { + const response = await utils[request]("POST", "delegates/search", { + forgedRewards: { + from: 0, + to: delegate.forgedRewards, + }, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(51); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.forged.rewards).toBeGreaterThanOrEqual(0); + expect(elem.forged.rewards).toBeLessThanOrEqual(delegate.forgedRewards); + } + }); + + it("should POST a search for delegates with the exact specified forged total", async () => { + const response = await utils[request]("POST", "delegates/search", { + forgedTotal: { + from: delegate.forgedTotal, + to: delegate.forgedTotal, + }, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(1); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.forged.total).toEqual(delegate.forgedTotal); + } + }); + + it("should POST a search for delegates with the specified forged total range", async () => { + const response = await utils[request]("POST", "delegates/search", { + forgedRewards: { + from: 0, + to: delegate.forgedTotal, + }, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(51); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.forged.total).toBeGreaterThanOrEqual(0); + expect(elem.forged.total).toBeLessThanOrEqual(delegate.forgedTotal); + } + }); + + it("should POST a search for delegates with the exact specified produced blocks", async () => { + const response = await utils[request]("POST", "delegates/search", { + producedBlocks: { + from: delegate.producedBlocks, + to: delegate.producedBlocks, + }, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(1); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.blocks.produced).toEqual(delegate.producedBlocks); + } + }); + + it("should POST a search for delegates with the specified produced blocks range", async () => { + const response = await utils[request]("POST", "delegates/search", { + producedBlocks: { + from: 0, + to: delegate.producedBlocks, + }, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(51); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.blocks.produced).toBeGreaterThanOrEqual(0); + expect(elem.blocks.produced).toBeLessThanOrEqual(delegate.producedBlocks); + } + }); + + it("should POST a search for delegates with the exact specified vote balance", async () => { + const response = await utils[request]("POST", "delegates/search", { + voteBalance: { + from: delegate.voteBalance, + to: delegate.voteBalance, + }, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(1); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.votes).toEqual(delegate.voteBalance); + } + }); + + it("should POST a search for delegates with the specified vote balance range", async () => { + const response = await utils[request]("POST", "delegates/search", { + voteBalance: { + from: 0, + to: delegate.voteBalance, + }, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(2); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.votes).toBeGreaterThanOrEqual(0); + expect(elem.votes).toBeLessThanOrEqual(delegate.voteBalance); + } + }); + }, + ); + }); + + describe("GET /delegates/:id/blocks", () => { + describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( + "using the %s header", + (header, request) => { + it("should GET all blocks for a delegate by the given identifier", async () => { + // save a new block so that we can make the request with generatorPublicKey + const block2 = new Block(blocks2to100[0]); + const databaseService = app.resolvePlugin("database"); + await databaseService.saveBlock(block2); + + const response = await utils[request]( + "GET", + `delegates/${blocks2to100[0].generatorPublicKey}/blocks`, + ); + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + response.data.data.forEach(utils.expectBlock); + + await databaseService.deleteBlock(block2); // reset to genesis block + }); + }, + ); + }); + + describe("GET /delegates/:id/voters", () => { + describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( + "using the %s header", + (header, request) => { + it("should GET all voters (wallets) for a delegate by the given identifier", async () => { + const response = await utils[request]("GET", `delegates/${delegate.publicKey}/voters`); + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + + response.data.data.forEach(utils.expectWallet); + expect(response.data.data.sort((a, b) => a.balance > b.balance)).toEqual(response.data.data); + }); + }, + ); + + describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( + "using the %s header", + (header, request) => { + it("should GET all voters (wallets) for a delegate by the given identifier ordered by 'balance:asc'", async () => { + const response = await utils[request]("GET", `delegates/${delegate.publicKey}/voters`); + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + + response.data.data.forEach(utils.expectWallet); + expect(response.data.data.sort((a, b) => a.balance < b.balance)).toEqual(response.data.data); + }); + }, + ); + }); +}); diff --git a/packages/core-api/__tests__/v2/handlers/node.test.ts b/__tests__/integration/core-api/v2/handlers/node.test.ts similarity index 89% rename from packages/core-api/__tests__/v2/handlers/node.test.ts rename to __tests__/integration/core-api/v2/handlers/node.test.ts index 23a90e532b..81647cc86f 100644 --- a/packages/core-api/__tests__/v2/handlers/node.test.ts +++ b/__tests__/integration/core-api/v2/handlers/node.test.ts @@ -1,4 +1,4 @@ -import "@arkecosystem/core-test-utils"; +import "../../../../utils"; import { app } from "@arkecosystem/core-container"; import { setUp, tearDown } from "../../__support__/setup"; @@ -58,20 +58,22 @@ describe("API 2.0 - Loader", () => { expect(response.data.data).toBeObject(); expect(response.data.data.nethash).toBeString(); + expect(response.data.data.slip44).toBeNumber(); + expect(response.data.data.wif).toBeNumber(); expect(response.data.data.token).toBeString(); expect(response.data.data.symbol).toBeString(); expect(response.data.data.explorer).toBeString(); expect(response.data.data.version).toBeNumber(); - const dynamicFees = app.resolveOptions("transactionPool").dynamicFees; + const dynamicFees = app.resolveOptions("transaction-pool").dynamicFees; expect(response.data.data.transactionPool.dynamicFees).toEqual(dynamicFees); - app.resolveOptions("transactionPool").dynamicFees.enabled = false; + app.resolveOptions("transaction-pool").dynamicFees.enabled = false; response = await utils[request]("GET", "node/configuration"); expect(response.data.data.transactionPool.dynamicFees).toEqual({ enabled: false }); - app.resolveOptions("transactionPool").dynamicFees.enabled = true; + app.resolveOptions("transaction-pool").dynamicFees.enabled = true; }); }, ); diff --git a/__tests__/integration/core-api/v2/handlers/peers.test.ts b/__tests__/integration/core-api/v2/handlers/peers.test.ts new file mode 100644 index 0000000000..53f776c22f --- /dev/null +++ b/__tests__/integration/core-api/v2/handlers/peers.test.ts @@ -0,0 +1,88 @@ +import { app } from "@arkecosystem/core-container"; +import { Peer } from "@arkecosystem/core-p2p/src/peer"; +import "../../../../utils"; +import { setUp, tearDown } from "../../__support__/setup"; +import { utils } from "../utils"; + +const peers = [ + { + ip: "1.0.0.99", + port: 4002, + version: "2.3.0-next.3", + }, + { + ip: "1.0.0.98", + port: 4002, + version: "2.3.0-next.1", + }, +]; + +beforeAll(async () => { + await setUp(); + + const peerMocks = peers.map(mock => { + const peerMock = new Peer(mock.ip, mock.port); + peerMock.setStatus("OK"); + peerMock.version = mock.version; + return peerMock; + }); + + const monitor = app.resolvePlugin("p2p"); + monitor.peers = peerMocks.reduce((result, mock) => ({ ...result, [mock.ip]: mock }), {}); +}); + +afterAll(async () => { + const monitor = app.resolvePlugin("p2p"); + monitor.peers = {}; + + await tearDown(); +}); + +describe("API 2.0 - Peers", () => { + describe("GET /peers", () => { + describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( + "using the %s header", + (_, request) => { + it("should GET all the peers", async () => { + const response = await utils[request]("GET", "peers"); + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data[0]).toBeObject(); + }); + + it("should GET all the peers sorted by version,asc", async () => { + const response = await utils[request]("GET", "peers", { orderBy: "version:asc" }); + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArrayOfSize(peers.length); + expect(response.data.data[0]).toBeObject(); + expect(response.data.data[0].ip).toBe(peers[1].ip); + expect(response.data.data[1].ip).toBe(peers[0].ip); + }); + + it("should GET all the peers sorted by version,desc", async () => { + const response = await utils[request]("GET", "peers", { orderBy: "version:desc" }); + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArrayOfSize(peers.length); + expect(response.data.data[0]).toBeObject(); + expect(response.data.data[0].ip).toBe(peers[0].ip); + expect(response.data.data[1].ip).toBe(peers[1].ip); + }); + }, + ); + }); + + describe("GET /peers/:ip", () => { + describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( + "using the %s header", + (_, request) => { + it("should GET a peer by the given ip", async () => { + const response = await utils[request]("GET", `peers/${peers[0].ip}`); + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeObject(); + expect(response.data.data.ip).toBe(peers[0].ip); + expect(response.data.data.port).toBe(peers[0].port); + }); + }, + ); + }); +}); diff --git a/packages/core-api/__tests__/v2/handlers/transactions.test.ts b/__tests__/integration/core-api/v2/handlers/transactions.test.ts similarity index 92% rename from packages/core-api/__tests__/v2/handlers/transactions.test.ts rename to __tests__/integration/core-api/v2/handlers/transactions.test.ts index 02509ac776..7f9e363873 100644 --- a/packages/core-api/__tests__/v2/handlers/transactions.test.ts +++ b/__tests__/integration/core-api/v2/handlers/transactions.test.ts @@ -1,12 +1,11 @@ -import "@arkecosystem/core-test-utils"; -import { constants } from "@arkecosystem/crypto"; +import "../../../../utils"; import { setUp, tearDown } from "../../__support__/setup"; import { utils } from "../utils"; -import genesisBlock from "../../../../core-test-utils/src/config/testnet/genesisBlock.json"; -import { delegates } from "../../../../core-test-utils/src/fixtures/testnet/delegates"; -import { generateTransfers } from "../../../../core-test-utils/src/generators/transactions/transfer"; -import { generateWallets } from "../../../../core-test-utils/src/generators/wallets"; +import { TransactionFactory } from "../../../../helpers/transaction-factory"; +import genesisBlock from "../../../../utils/config/testnet/genesisBlock.json"; +import { delegates } from "../../../../utils/fixtures/testnet/delegates"; +import { generateWallets } from "../../../../utils/generators/wallets"; const transferFee = 10000000; @@ -506,14 +505,10 @@ describe("API 2.0 - Transactions", () => { describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( "using the %s header", (header, request) => { - const transactions = generateTransfers( - "testnet", - delegates[0].secret, - delegates[1].address, - 1, - 40, - true, - ); + const transactions = TransactionFactory.transfer(delegates[1].address) + .withNetwork("testnet") + .withPassphrase(delegates[0].secret) + .create(40); it("should POST all the transactions", async () => { const response = await utils[request]("POST", "transactions", { @@ -527,29 +522,26 @@ describe("API 2.0 - Transactions", () => { transactions: transactions.concat(transactions), }); - expect(response.data.statusCode).toBe(400); - expect(response.data.message).toBe( - 'child "transactions" fails because ["transactions" must contain less than or equal to 40 items]', - ); + expect(response.data.statusCode).toBe(422); + expect(response.data.message).toBe("should NOT have more than 40 items"); }); }, ); it("should POST 2 transactions double spending and get only 1 accepted and broadcasted", async () => { - const transactions = generateTransfers( - "testnet", - delegates[0].secret, + const transactions = TransactionFactory.transfer( delegates[1].address, 245098000000000 - 5098000000000, // a bit less than the delegates' balance - 2, - true, - ); + ) + .withNetwork("testnet") + .withPassphrase(delegates[0].secret) + .create(2); + const response = await utils.requestWithAcceptHeader("POST", "transactions", { transactions, }); expect(response).toBeSuccessfulResponse(); - expect(response.data.data).toBeObject(); expect(response.data.data.accept).toHaveLength(1); expect(response.data.data.accept[0]).toBe(transactions[0].id); @@ -567,22 +559,15 @@ describe("API 2.0 - Transactions", () => { const amountPlusFee = Math.floor(sender.balance / txNumber); const lastAmountPlusFee = sender.balance - (txNumber - 1) * amountPlusFee; - const transactions = generateTransfers( - "testnet", - sender.secret, - receivers[0].address, - amountPlusFee - transferFee, - txNumber - 1, - true, - ); - const lastTransaction = generateTransfers( - "testnet", - sender.secret, - receivers[1].address, - lastAmountPlusFee - transferFee, - 1, - true, - ); + const transactions = TransactionFactory.transfer(receivers[0].address, amountPlusFee - transferFee) + .withNetwork("testnet") + .withPassphrase(sender.secret) + .create(txNumber - 1); + + const lastTransaction = TransactionFactory.transfer(receivers[1].address, lastAmountPlusFee - transferFee) + .withNetwork("testnet") + .withPassphrase(sender.secret) + .create(); // we change the receiver in lastTransaction to prevent having 2 exact same transactions with same id (if not, could be same as transactions[0]) const allTransactions = transactions.concat(lastTransaction); @@ -608,22 +593,17 @@ describe("API 2.0 - Transactions", () => { const amountPlusFee = Math.floor(sender.balance / txNumber); const lastAmountPlusFee = sender.balance - (txNumber - 1) * amountPlusFee + 1; - const transactions = generateTransfers( - "testnet", - sender.secret, - receivers[0].address, - amountPlusFee - transferFee, - txNumber - 1, - true, - ); - const lastTransaction = generateTransfers( - "testnet", - sender.secret, + const transactions = TransactionFactory.transfer(receivers[0].address, amountPlusFee - transferFee) + .withNetwork("testnet") + .withPassphrase(sender.secret) + .create(txNumber - 1); + const lastTransaction = TransactionFactory.transfer( receivers[1].address, lastAmountPlusFee - transferFee, - 1, - true, - ); + ) + .withNetwork("testnet") + .withPassphrase(sender.secret) + .create(); // we change the receiver in lastTransaction to prevent having 2 exact same transactions with same id (if not, could be same as transactions[0]) const allTransactions = transactions.concat(lastTransaction); diff --git a/packages/core-api/__tests__/v2/handlers/votes.test.ts b/__tests__/integration/core-api/v2/handlers/votes.test.ts similarity index 97% rename from packages/core-api/__tests__/v2/handlers/votes.test.ts rename to __tests__/integration/core-api/v2/handlers/votes.test.ts index bb1d378593..ec87bc5798 100644 --- a/packages/core-api/__tests__/v2/handlers/votes.test.ts +++ b/__tests__/integration/core-api/v2/handlers/votes.test.ts @@ -1,4 +1,4 @@ -import "@arkecosystem/core-test-utils"; +import "../../../../utils"; import { setUp, tearDown } from "../../__support__/setup"; import { utils } from "../utils"; diff --git a/packages/core-api/__tests__/v2/handlers/wallets.test.ts b/__tests__/integration/core-api/v2/handlers/wallets.test.ts similarity index 99% rename from packages/core-api/__tests__/v2/handlers/wallets.test.ts rename to __tests__/integration/core-api/v2/handlers/wallets.test.ts index 6177592c85..3398f9e38e 100644 --- a/packages/core-api/__tests__/v2/handlers/wallets.test.ts +++ b/__tests__/integration/core-api/v2/handlers/wallets.test.ts @@ -1,5 +1,4 @@ -import "@arkecosystem/core-test-utils"; -import { Bignum } from "@arkecosystem/crypto"; +import "../../../../utils"; import { setUp, tearDown } from "../../__support__/setup"; import { utils } from "../utils"; diff --git a/packages/core-api/__tests__/v2/utils.ts b/__tests__/integration/core-api/v2/utils.ts similarity index 93% rename from packages/core-api/__tests__/v2/utils.ts rename to __tests__/integration/core-api/v2/utils.ts index f7e6f446aa..32571a9ffe 100644 --- a/packages/core-api/__tests__/v2/utils.ts +++ b/__tests__/integration/core-api/v2/utils.ts @@ -1,8 +1,8 @@ import { app } from "@arkecosystem/core-container"; +import { httpie } from "@arkecosystem/core-utils"; import { client, NetworkManager, transactionBuilder } from "@arkecosystem/crypto"; -import axios from "axios"; import "jest-extended"; -import { ApiHelpers } from "../../../core-test-utils/src/helpers/api"; +import { ApiHelpers } from "../../../utils/helpers/api"; class Helpers { public async request(method, path, params = {}) { @@ -121,11 +121,9 @@ class Helpers { expect(delegate.votes).toBeNumber(); expect(delegate.rank).toBeNumber(); expect(delegate.blocks).toBeObject(); - expect(delegate.blocks.missed).toBeNumber(); expect(delegate.blocks.produced).toBeNumber(); expect(delegate.production).toBeObject(); expect(delegate.production.approval).toBeNumber(); - expect(delegate.production.productivity).toBeNumber(); expect(delegate.forged.fees).toBeNumber(); expect(delegate.forged.rewards).toBeNumber(); expect(delegate.forged.total).toBeNumber(); @@ -155,15 +153,12 @@ class Helpers { .sign("clay harbor enemy utility margin pretty hub comic piece aerobic umbrella acquire") .getStruct(); - await axios.post( - "http://127.0.0.1:4003/api/v2/transactions", - { + await httpie.post("http://127.0.0.1:4003/api/v2/transactions", { + body: { transactions: [transaction], }, - { - headers: { "Content-Type": "application/json" }, - }, - ); + headers: { "Content-Type": "application/json" }, + }); return transaction; } diff --git a/packages/core-blockchain/__tests__/__support__/setup.ts b/__tests__/integration/core-blockchain/__support__/setup.ts similarity index 78% rename from packages/core-blockchain/__tests__/__support__/setup.ts rename to __tests__/integration/core-blockchain/__support__/setup.ts index 110ad1343a..303401a938 100644 --- a/packages/core-blockchain/__tests__/__support__/setup.ts +++ b/__tests__/integration/core-blockchain/__support__/setup.ts @@ -1,5 +1,5 @@ import { app } from "@arkecosystem/core-container"; -import { registerWithContainer, setUpContainer } from "../../../core-test-utils/src/helpers/container"; +import { registerWithContainer, setUpContainer } from "../../../utils/helpers/container"; jest.setTimeout(60000); @@ -9,7 +9,7 @@ export const setUpFull = async () => { exclude: ["@arkecosystem/core-blockchain"], }); - const { plugin } = require("../../src/plugin"); + const { plugin } = require("../../../../packages/core-blockchain/src/plugin"); await registerWithContainer(plugin, {}); return app; @@ -18,7 +18,7 @@ export const setUpFull = async () => { export const tearDownFull = async () => { await app.tearDown(); - const { plugin } = require("../../src/plugin"); + const { plugin } = require("../../../../packages/core-blockchain/src/plugin"); await plugin.deregister(app, {}); }; diff --git a/__tests__/integration/core-blockchain/blockchain.test.ts b/__tests__/integration/core-blockchain/blockchain.test.ts new file mode 100644 index 0000000000..f42e72b73e --- /dev/null +++ b/__tests__/integration/core-blockchain/blockchain.test.ts @@ -0,0 +1,387 @@ +/* tslint:disable:max-line-length */ +import { Wallet } from "@arkecosystem/core-database"; +import { roundCalculator } from "@arkecosystem/core-utils"; +import { + Bignum, + crypto, + HashAlgorithms, + ITransactionData, + models, + slots, + sortTransactions, + transactionBuilder, +} from "@arkecosystem/crypto"; +import { asValue } from "awilix"; +import delay from "delay"; +import { Blockchain } from "../../../packages/core-blockchain/src/blockchain"; +import { defaults } from "../../../packages/core-blockchain/src/defaults"; +import "../../utils"; +import { blocks101to155 } from "../../utils/fixtures/testnet/blocks101to155"; +import { blocks2to100 } from "../../utils/fixtures/testnet/blocks2to100"; +import { delegates } from "../../utils/fixtures/testnet/delegates"; +import { setUp, tearDown } from "./__support__/setup"; + +const { Block } = models; + +let genesisBlock; +let configManager; +let container; +let blockchain: Blockchain; +let loggerDebugBackup; + +describe("Blockchain", () => { + let logger; + beforeAll(async () => { + container = await setUp(); + + // Backup logger.debug function as we are going to mock it in the test suite + logger = container.resolvePlugin("logger"); + loggerDebugBackup = logger.debug; + + // Create the genesis block after the setup has finished or else it uses a potentially + // wrong network config. + genesisBlock = new Block(require("../../utils/config/testnet/genesisBlock.json")); + + configManager = container.getConfig(); + + // Workaround: Add genesis transactions to the exceptions list, because they have a fee of 0 + // and otherwise don't pass validation. + configManager.set("exceptions.transactions", genesisBlock.transactions.map(tx => tx.id)); + + // Manually register the blockchain and start it + await __start(false); + }); + + afterAll(async () => { + configManager.set("exceptions.transactions", []); + + await __resetToHeight1(); + + // Manually stop the blockchain + await blockchain.stop(); + + await tearDown(); + }); + + afterEach(async () => { + // Restore original logger.debug function + logger.debug = loggerDebugBackup; + + await __resetToHeight1(); + await __addBlocks(5); + await __resetBlocksInCurrentRound(); + }); + + describe("postTransactions", () => { + it("should be ok", async () => { + const transactionsWithoutType2 = genesisBlock.transactions.filter(tx => tx.type !== 2); + + blockchain.transactionPool.flush(); + await blockchain.postTransactions(transactionsWithoutType2); + const transactions = blockchain.transactionPool.getTransactions(0, 200); + + expect(transactions.length).toBe(transactionsWithoutType2.length); + + expect(transactions).toEqual(transactionsWithoutType2.map(transaction => transaction.serialized)); + + blockchain.transactionPool.flush(); + }); + }); + + describe("removeBlocks", () => { + it("should remove blocks", async () => { + const lastBlockHeight = blockchain.getLastBlock().data.height; + + await blockchain.removeBlocks(2); + expect(blockchain.getLastBlock().data.height).toBe(lastBlockHeight - 2); + }); + + it("should remove (current height - 1) blocks if we provide a greater value", async () => { + await __resetToHeight1(); + + await blockchain.removeBlocks(9999); + expect(blockchain.getLastBlock().data.height).toBe(1); + }); + }); + + describe("removeTopBlocks", () => { + it("should remove top blocks", async () => { + const dbLastBlockBefore = await blockchain.database.getLastBlock(); + const lastBlockHeight = dbLastBlockBefore.data.height; + + await blockchain.removeTopBlocks(2); + const dbLastBlockAfter = await blockchain.database.getLastBlock(); + + expect(dbLastBlockAfter.data.height).toBe(lastBlockHeight - 2); + }); + }); + + describe("restoreCurrentRound", () => { + it("should restore the active delegates of the current round", async () => { + await __resetToHeight1(); + + // Go to arbitrary height in round 2. + await __addBlocks(55); + + // Pretend blockchain just started + const roundInfo = roundCalculator.calculateRound(blockchain.getLastHeight()); + await blockchain.database.restoreCurrentRound(blockchain.getLastHeight()); + const forgingDelegates = await blockchain.database.getActiveDelegates(roundInfo); + expect(forgingDelegates).toHaveLength(51); + + // Reset again and replay to round 2. In both cases the forging delegates + // have to match. + await __resetToHeight1(); + await __addBlocks(52); + + // FIXME: using jest.spyOn getActiveDelegates with toHaveLastReturnedWith() somehow gets + // overwritten in afterEach + // FIXME: wallet.lastBlock needs to be properly restored when reverting + forgingDelegates.forEach(forger => (forger.lastBlock = null)); + expect(forgingDelegates).toEqual( + (blockchain.database as any).forgingDelegates.map(forger => { + forger.lastBlock = null; + return forger; + }), + ); + }); + }); + + describe("rollback", () => { + beforeEach(async () => { + await __resetToHeight1(); + await __addBlocks(155); + }); + + const getNextForger = async () => { + const lastBlock = blockchain.state.getLastBlock(); + const roundInfo = roundCalculator.calculateRound(lastBlock.data.height); + const activeDelegates = await blockchain.database.getActiveDelegates(roundInfo); + const nextSlot = slots.getSlotNumber(lastBlock.data.timestamp) + 1; + return activeDelegates[nextSlot % activeDelegates.length]; + }; + + const createBlock = (generatorKeys: any, transactions: ITransactionData[]) => { + const transactionData = { + amount: Bignum.ZERO, + fee: Bignum.ZERO, + ids: [], + }; + + const sortedTransactions = sortTransactions(transactions); + sortedTransactions.forEach(transaction => { + transactionData.amount = transactionData.amount.plus(transaction.amount); + transactionData.fee = transactionData.fee.plus(transaction.fee); + transactionData.ids.push(Buffer.from(transaction.id, "hex")); + }); + + const lastBlock = blockchain.state.getLastBlock(); + const data = { + timestamp: slots.getSlotTime(slots.getSlotNumber(lastBlock.data.timestamp) + 1), + version: 0, + previousBlock: lastBlock.data.id, + previousBlockHex: lastBlock.data.idHex, + height: lastBlock.data.height + 1, + numberOfTransactions: sortedTransactions.length, + totalAmount: transactionData.amount, + totalFee: transactionData.fee, + reward: Bignum.ZERO, + payloadLength: 32 * sortedTransactions.length, + payloadHash: HashAlgorithms.sha256(transactionData.ids).toString("hex"), + transactions: sortedTransactions, + }; + + return Block.create(data, crypto.getKeys(generatorKeys.secret)); + }; + + it("should restore vote balances after a rollback", async () => { + const mockCallback = jest.fn(() => true); + + // Create key pair for new voter + const keyPair = crypto.getKeys("secret"); + const recipient = crypto.getAddress(keyPair.publicKey); + + let nextForger = await getNextForger(); + const initialVoteBalance = nextForger.voteBalance; + + // First send funds to new voter wallet + const forgerKeys = delegates.find(wallet => wallet.publicKey === nextForger.publicKey); + const transfer = transactionBuilder + .transfer() + .recipientId(recipient) + .amount(125) + .sign(forgerKeys.passphrase) + .getStruct(); + + const transferBlock = createBlock(forgerKeys, [transfer]); + await blockchain.processBlock(transferBlock, mockCallback); + + const wallet = blockchain.database.walletManager.findByPublicKey(keyPair.publicKey); + const walletForger = blockchain.database.walletManager.findByPublicKey(forgerKeys.publicKey); + + // New wallet received funds and vote balance of delegate has been reduced by the same amount, + // since it forged it's own transaction the fees for the transaction have been recovered. + expect(wallet.balance).toEqual(new Bignum(transfer.amount)); + expect(walletForger.voteBalance).toEqual(new Bignum(initialVoteBalance).minus(transfer.amount)); + + // Now vote with newly created wallet for previous forger. + const vote = transactionBuilder + .vote() + .fee(1) + .votesAsset([`+${forgerKeys.publicKey}`]) + .sign("secret") + .getStruct(); + + nextForger = await getNextForger(); + let nextForgerWallet = delegates.find(wallet => wallet.publicKey === nextForger.publicKey); + + const voteBlock = createBlock(nextForgerWallet, [vote]); + await blockchain.processBlock(voteBlock, mockCallback); + + // Wallet paid a fee of 1 and the vote has been placed. + expect(wallet.balance).toEqual(new Bignum(124)); + expect(wallet.vote).toEqual(forgerKeys.publicKey); + + // Vote balance of delegate now equals initial vote balance minus 1 for the vote fee + // since it was forged by a different delegate. + expect(walletForger.voteBalance).toEqual(new Bignum(initialVoteBalance).minus(vote.fee)); + + // Now unvote again + const unvote = transactionBuilder + .vote() + .fee(1) + .votesAsset([`-${forgerKeys.publicKey}`]) + .sign("secret") + .getStruct(); + + nextForger = await getNextForger(); + nextForgerWallet = delegates.find(wallet => wallet.publicKey === nextForger.publicKey); + + const unvoteBlock = createBlock(nextForgerWallet, [unvote]); + await blockchain.processBlock(unvoteBlock, mockCallback); + + // Wallet paid a fee of 1 and no longer voted a delegate + expect(wallet.balance).toEqual(new Bignum(123)); + expect(wallet.vote).toBeNull(); + + // Vote balance of delegate now equals initial vote balance minus the amount sent to the voter wallet. + expect(walletForger.voteBalance).toEqual(new Bignum(initialVoteBalance).minus(transfer.amount)); + + // Now rewind 3 blocks back to the initial state + await blockchain.removeBlocks(3); + + // Wallet is now a cold wallet and the initial vote balance has been restored. + expect(wallet.balance).toEqual(Bignum.ZERO); + expect(walletForger.voteBalance).toEqual(new Bignum(initialVoteBalance)); + }); + }); + + describe("getUnconfirmedTransactions", () => { + it("should get unconfirmed transactions", async () => { + const transactionsWithoutType2 = genesisBlock.transactions.filter(tx => tx.type !== 2); + + blockchain.transactionPool.flush(); + await blockchain.postTransactions(transactionsWithoutType2); + const unconfirmedTransactions = blockchain.getUnconfirmedTransactions(200); + + expect(unconfirmedTransactions.transactions.length).toBe(transactionsWithoutType2.length); + + expect(unconfirmedTransactions.transactions).toEqual( + transactionsWithoutType2.map(transaction => transaction.serialized.toString("hex")), + ); + + blockchain.transactionPool.flush(); + }); + + it("should return object with count == -1 if getTransactionsForForging returned a falsy value", async () => { + jest.spyOn(blockchain.transactionPool, "getTransactionsForForging").mockReturnValueOnce(null); + + const unconfirmedTransactions = blockchain.getUnconfirmedTransactions(200); + expect(unconfirmedTransactions.count).toBe(-1); + }); + }); + + describe("stop on emit shutdown", () => { + it("should trigger the stop method when receiving 'shutdown' event", async () => { + const emitter = container.resolvePlugin("event-emitter"); + + // @ts-ignore + const stop = jest.spyOn(blockchain, "stop").mockReturnValue(true); + + emitter.emit("shutdown"); + + await delay(200); + + expect(stop).toHaveBeenCalled(); + }); + }); +}); + +async function __start(networkStart) { + process.env.CORE_SKIP_BLOCKCHAIN = "false"; + process.env.CORE_SKIP_PEER_STATE_VERIFICATION = "true"; + process.env.CORE_ENV = "false"; + + const plugin = require("../../../packages/core-blockchain/src").plugin; + + blockchain = await plugin.register(container, { + networkStart, + ...defaults, + }); + + await container.register( + "blockchain", + asValue({ + name: "blockchain", + version: "0.1.0", + plugin: blockchain, + options: {}, + }), + ); + + if (networkStart) { + return; + } + + await __resetToHeight1(); + + await blockchain.start(); + await __addBlocks(5); +} + +async function __resetBlocksInCurrentRound() { + await blockchain.database.loadBlocksFromCurrentRound(); +} + +async function __resetToHeight1() { + const lastBlock = await blockchain.database.getLastBlock(); + if (lastBlock) { + // Make sure the wallet manager has been fed or else revertRound + // cannot determine the previous delegates. This is only necessary, because + // the database is not dropped after the unit tests are done. + await blockchain.database.buildWallets(); + + // Index the genesis wallet or else revert block at height 1 fails + const generator = crypto.getAddress(genesisBlock.data.generatorPublicKey); + const genesis = new Wallet(generator); + genesis.publicKey = genesisBlock.data.generatorPublicKey; + genesis.username = "genesis"; + blockchain.database.walletManager.reindex(genesis); + + blockchain.state.clear(); + + blockchain.state.setLastBlock(lastBlock); + await __resetBlocksInCurrentRound(); + await blockchain.removeBlocks(lastBlock.data.height - 1); + } +} + +async function __addBlocks(untilHeight) { + const allBlocks = [...blocks2to100, ...blocks101to155]; + const lastHeight = blockchain.getLastHeight(); + + for (let height = lastHeight + 1; height < untilHeight && height < 155; height++) { + const blockToProcess = new Block(allBlocks[height - 2]); + await blockchain.processBlock(blockToProcess, () => null); + } +} diff --git a/packages/core-database-postgres/__tests__/__support__/setup.ts b/__tests__/integration/core-database-postgres/__support__/setup.ts similarity index 82% rename from packages/core-database-postgres/__tests__/__support__/setup.ts rename to __tests__/integration/core-database-postgres/__support__/setup.ts index 00e4c2c9d2..adbba68c81 100644 --- a/packages/core-database-postgres/__tests__/__support__/setup.ts +++ b/__tests__/integration/core-database-postgres/__support__/setup.ts @@ -1,5 +1,5 @@ import { app } from "@arkecosystem/core-container"; -import { registerWithContainer, setUpContainer } from "../../../core-test-utils/src/helpers/container"; +import { registerWithContainer, setUpContainer } from "../../../utils/helpers/container"; jest.setTimeout(60000); @@ -24,13 +24,13 @@ export const setUp = async () => { const { plugin: pluginDatabase } = require("@arkecosystem/core-database"); await registerWithContainer(pluginDatabase, options); - const { plugin } = require("../../src/plugin"); + const { plugin } = require("../../../../packages/core-database-postgres/src/plugin"); await registerWithContainer(plugin, options); }; export const tearDown = async () => { await app.tearDown(); - const { plugin } = require("../../src/plugin"); + const { plugin } = require("../../../../packages/core-database-postgres/src/plugin"); await plugin.deregister(app, options); }; diff --git a/packages/core-database-postgres/__tests__/connection.test.ts b/__tests__/integration/core-database-postgres/connection.test.ts similarity index 92% rename from packages/core-database-postgres/__tests__/connection.test.ts rename to __tests__/integration/core-database-postgres/connection.test.ts index 034f28c3dd..530c429711 100644 --- a/packages/core-database-postgres/__tests__/connection.test.ts +++ b/__tests__/integration/core-database-postgres/connection.test.ts @@ -1,7 +1,7 @@ import { app } from "@arkecosystem/core-container"; import { Database } from "@arkecosystem/core-interfaces"; import { models } from "@arkecosystem/crypto"; -import genesisBlock from "../../core-test-utils/src/config/testnet/genesisBlock.json"; +import genesisBlock from "../../utils/config/testnet/genesisBlock.json"; import { setUp, tearDown } from "./__support__/setup"; const { Block } = models; @@ -10,7 +10,7 @@ let databaseService: Database.IDatabaseService; beforeAll(async () => { await setUp(); - + databaseService = app.resolvePlugin("database"); await databaseService.saveBlock(new Block(genesisBlock)); diff --git a/__tests__/integration/core-forger/__fixtures__/blocks.ts b/__tests__/integration/core-forger/__fixtures__/blocks.ts new file mode 100644 index 0000000000..d7667568d2 --- /dev/null +++ b/__tests__/integration/core-forger/__fixtures__/blocks.ts @@ -0,0 +1,38 @@ +import { models } from "@arkecosystem/crypto"; + +export const sampleBlocks = [ + new models.Block({ + id: "7686497416922799951", + version: 0, + timestamp: 62225384, + height: 1760011, + previousBlock: "1111111111111111111", + numberOfTransactions: 0, + totalAmount: 0, + totalFee: 0, + reward: 200000000, + payloadLength: 0, + payloadHash: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + generatorPublicKey: "03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37", + blockSignature: + // tslint:disable-next-line:max-line-length + "304402205b5da8a3cfb28398baaa50e299d735226c4455bdfdf5cb650afb53b0f22a93c60220572c4a4652edcd1bb85720884a7b0732add4dd50e7a0984325807770c99939bd", + }), + new models.Block({ + id: "341e8008227a887b2f658f9071118ebe73720b7b8f58d2ae3d24ae933888f6d7", + version: 0, + timestamp: 62376432, + height: 1000000001, + previousBlock: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + numberOfTransactions: 0, + totalAmount: 0, + totalFee: 0, + reward: 200000000, + payloadLength: 0, + payloadHash: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + generatorPublicKey: "03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37", + blockSignature: + // tslint:disable-next-line:max-line-length + "3045022100afa57d28e227720b166d46c443e7a6c810c2442f4a00f4405258fd73d3806b7b02206f65bc93679f753d840fb5b168b04512aed9bb039f98178ce6b6237b65a8d9cf", + }), +]; diff --git a/__tests__/integration/core-forger/client.test.ts b/__tests__/integration/core-forger/client.test.ts new file mode 100644 index 0000000000..2baf2aac02 --- /dev/null +++ b/__tests__/integration/core-forger/client.test.ts @@ -0,0 +1,165 @@ +import "./mocks/core-container"; + +import { NetworkState, NetworkStateStatus } from "@arkecosystem/core-p2p"; +import { httpie } from "@arkecosystem/core-utils"; +import "jest-extended"; +import nock from "nock"; +import { Client } from "../../../packages/core-forger/src/client"; +import { HostNoResponseError } from "../../../packages/core-forger/src/errors"; +import { sampleBlocks } from "./__fixtures__/blocks"; + +jest.setTimeout(30000); + +const host = "http://127.0.0.1:4000"; + +let client: Client; + +beforeEach(() => { + client = new Client(host); + + nock(host) + .get("/peer/status") + .reply(200); +}); + +afterEach(() => { + nock.cleanAll(); +}); + +describe("Client", () => { + describe("constructor", () => { + it("accepts 1 or more hosts as parameter", () => { + expect(new Client(host).hosts).toEqual([host]); + + const hosts = [host, "http://localhost:4000"]; + + expect(new Client(hosts).hosts).toEqual(hosts); + }); + }); + + describe("broadcast", () => { + describe("when the host is available", () => { + for (const sampleBlock of sampleBlocks) { + it("should be truthy if broadcasts", async () => { + nock(host) + .post("/internal/blocks") + .reply(200, (_, requestBody) => { + expect(requestBody.block).toMatchObject( + expect.objectContaining({ + id: sampleBlock.data.id, + }), + ); + + return requestBody; + }); + + await client.selectHost(); + + const wasBroadcasted = await client.broadcast(sampleBlock.toJson()); + expect(wasBroadcasted).toBeTruthy(); + }); + } + }); + }); + + describe("getRound", () => { + describe("when the host is available", () => { + it("should be ok", async () => { + const expectedResponse = { foo: "bar" }; + nock(host) + .get("/internal/rounds/current") + .reply(200, { data: expectedResponse }); + + const response = await client.getRound(); + + expect(response).toEqual(expectedResponse); + }); + }); + }); + + describe("getTransactions", () => { + describe("when the host is available", () => { + it("should be ok", async () => { + const expectedResponse = { foo: "bar" }; + nock(host) + .get("/internal/transactions/forging") + .reply(200, { data: expectedResponse }); + + await client.selectHost(); + const response = await client.getTransactions(); + + expect(response).toEqual(expectedResponse); + }); + }); + }); + + describe("getNetworkState", () => { + describe("when the host is available", () => { + it("should be ok", async () => { + const expectedResponse = new NetworkState(NetworkStateStatus.Test); + nock(host) + .get("/internal/network/state") + .reply(200, { data: expectedResponse }); + + await client.selectHost(); + const response = await client.getNetworkState(); + + expect(response).toEqual(expectedResponse); + }); + }); + }); + + describe("syncCheck", () => { + it("should induce network sync", async () => { + jest.spyOn(httpie, "get"); + nock(host) + .get("/internal/blockchain/sync") + .reply(200); + + await client.selectHost(); + await client.syncCheck(); + + expect(httpie.get).toHaveBeenCalledWith(`${host}/internal/blockchain/sync`, expect.any(Object)); + }); + }); + + describe("selectHost", () => { + it("should fallback to responsive host", async () => { + client = new Client(["http://127.0.0.2:4000", "http://127.0.0.3:4000", host]); + await expect(client.selectHost()).toResolve(); + }); + + it("should throw error when no host is responsive", async () => { + client = new Client(["http://127.0.0.2:4000", "http://127.0.0.3:4000"]); + await expect(client.selectHost()).rejects.toThrowError(HostNoResponseError); + }); + }); + + describe("emitEvent", () => { + it("should emit events", async () => { + jest.spyOn(httpie, "post"); + nock(host) + .post("/internal/utils/events") + .reply(200, (_, requestBody) => { + expect(requestBody).toMatchObject({ event: "foo", body: "bar" }); + return [200]; + }); + + await client.selectHost(); + await client.emitEvent("foo", "bar"); + + expect(httpie.post).toHaveBeenCalledWith(`${host}/internal/utils/events`, { + body: JSON.stringify({ event: "foo", body: "bar" }), + headers: { + "Content-Type": "application/json", + nethash: {}, + port: 4000, + version: "2.3.0", + "x-auth": "forger", + }, + retry: { retries: 0 }, + timeout: 2000, + }); + }); + }); +}); diff --git a/__tests__/integration/core-forger/mocks/core-container.ts b/__tests__/integration/core-forger/mocks/core-container.ts new file mode 100644 index 0000000000..e46989ad69 --- /dev/null +++ b/__tests__/integration/core-forger/mocks/core-container.ts @@ -0,0 +1,24 @@ +jest.mock("@arkecosystem/core-container", () => { + return { + app: { + getConfig: () => { + return { + get: () => ({}), + }; + }, + getVersion: () => "2.3.0", + resolvePlugin: name => { + if (name === "logger") { + return { + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + debug: jest.fn(), + }; + } + + return {}; + }, + }, + }; +}); diff --git a/__tests__/integration/core-json-rpc/__support__/request.ts b/__tests__/integration/core-json-rpc/__support__/request.ts new file mode 100644 index 0000000000..cce749b2c0 --- /dev/null +++ b/__tests__/integration/core-json-rpc/__support__/request.ts @@ -0,0 +1,20 @@ +import { httpie } from "@arkecosystem/core-utils"; +import uuid from "uuid/v4"; + +export async function sendRequest(method, params: any = {}) { + const id = uuid(); + const response = await httpie.post("http://localhost:8080/", { + body: { + jsonrpc: "2.0", + id, + method, + params, + }, + }); + + await expect(response.status).toBe(200); + await expect(response.body.jsonrpc).toBe("2.0"); + await expect(response.body.id).toBe(id); + + return response; +} diff --git a/packages/core-json-rpc/__tests__/__support__/setup.ts b/__tests__/integration/core-json-rpc/__support__/setup.ts similarity index 64% rename from packages/core-json-rpc/__tests__/__support__/setup.ts rename to __tests__/integration/core-json-rpc/__support__/setup.ts index 4290b0cbc8..537811261c 100644 --- a/packages/core-json-rpc/__tests__/__support__/setup.ts +++ b/__tests__/integration/core-json-rpc/__support__/setup.ts @@ -1,5 +1,5 @@ import { app } from "@arkecosystem/core-container"; -import { registerWithContainer, setUpContainer } from "../../../core-test-utils/src/helpers/container"; +import { registerWithContainer, setUpContainer } from "../../../utils/helpers/container"; jest.setTimeout(60000); @@ -16,15 +16,10 @@ export async function setUp() { process.env.CORE_JSON_RPC_ENABLED = true; await setUpContainer({ - exclude: [ - "@arkecosystem/core-webhooks", - "@arkecosystem/core-graphql", - "@arkecosystem/core-forger", - "@arkecosystem/core-json-rpc", - ], + exclude: ["@arkecosystem/core-webhooks", "@arkecosystem/core-forger", "@arkecosystem/core-json-rpc"], }); - const { plugin } = require("../../src"); + const { plugin } = require("../../../../packages/core-json-rpc/src"); await registerWithContainer(plugin, options); return app; @@ -33,6 +28,6 @@ export async function setUp() { export async function tearDown() { await app.tearDown(); - const { plugin } = require("../../src"); + const { plugin } = require("../../../../packages/core-json-rpc/src"); await plugin.deregister(app, options); } diff --git a/packages/core-json-rpc/__tests__/blocks.test.ts b/__tests__/integration/core-json-rpc/blocks.test.ts similarity index 55% rename from packages/core-json-rpc/__tests__/blocks.test.ts rename to __tests__/integration/core-json-rpc/blocks.test.ts index 5b38a90015..adc9b33a29 100644 --- a/packages/core-json-rpc/__tests__/blocks.test.ts +++ b/__tests__/integration/core-json-rpc/blocks.test.ts @@ -1,101 +1,105 @@ -import "jest-extended"; - import { app } from "@arkecosystem/core-container"; import { Peer } from "@arkecosystem/core-p2p/src/peer"; -import axios from "axios"; -import MockAdapter from "axios-mock-adapter"; +import "jest-extended"; +import nock from "nock"; import { sendRequest } from "./__support__/request"; import { setUp, tearDown } from "./__support__/setup"; -const axiosMock = new MockAdapter(axios); - -jest.mock("is-reachable", () => jest.fn(async peer => true)); +jest.mock("is-reachable", () => jest.fn(async () => true)); let peerMock; +let mockHost; beforeAll(async () => { await setUp(); - peerMock = new Peer("1.0.0.99", 4002); + peerMock = new Peer("1.0.0.99", 4000); Object.assign(peerMock, peerMock.headers, { status: "OK" }); const monitor = app.resolvePlugin("p2p"); monitor.peers = {}; monitor.peers[peerMock.ip] = peerMock; + + nock("http://localhost", { allowUnmocked: true }); + + mockHost = nock("http://localhost:4003"); }); afterAll(async () => { + nock.cleanAll(); await tearDown(); }); beforeEach(async () => { - axiosMock.onPost(/.*:8080.*/).passThrough(); + nock(peerMock.url) + .get("/peer/status") + .reply(200, { success: true, height: 1 }, peerMock.headers); }); afterEach(async () => { - axiosMock.reset(); // important: resets any existing mocking behavior + nock.cleanAll(); }); describe("Blocks", () => { describe("POST blocks.latest", () => { it("should get the latest block", async () => { - axiosMock.onGet(/.*\/api\/blocks/).reply(() => [200, { data: [{ id: "123" }] }, peerMock.headers]); + mockHost + .get("/api/blocks") + .query({ orderBy: "height:desc", limit: 1 }) + .reply(200, { data: [{ id: "123" }] }, peerMock.headers); const response = await sendRequest("blocks.latest"); - expect(response.data.result.id).toBeString(); + expect(response.body.result.id).toBe("123"); }); it("should not find the latest block", async () => { - axiosMock.onGet(/.*\/api\/blocks/).reply(() => [404, null, peerMock.headers]); + mockHost.get("/api/blocks").reply(404, {}, peerMock.headers); const response = await sendRequest("blocks.latest"); - expect(response.data.error.message).toBe("Latest block could not be found."); + expect(response.body.error.message).toBe("Latest block could not be found."); }); }); describe("POST blocks.info", () => { it("should get the block information", async () => { - axiosMock.onGet(/.*\/api\/blocks\/123/).reply(() => [200, { data: { id: "123" } }, peerMock.headers]); + mockHost.get("/api/blocks/123").reply(200, { data: { id: "123" } }, peerMock.headers); const response = await sendRequest("blocks.info", { id: "123", }); - expect(response.data.result.id).toBe("123"); + expect(response.body.result.id).toBe("123"); }); it("should fail to get the block information", async () => { const response = await sendRequest("blocks.info", { id: "123" }); - expect(response.data.error.code).toBe(404); - expect(response.data.error.message).toBe("Block 123 could not be found."); + expect(response.body.error.code).toBe(404); + expect(response.body.error.message).toBe("Block 123 could not be found."); }); }); describe("POST blocks.transactions", () => { it("should get the block transactions", async () => { - axiosMock - .onGet(/.*\/api\/blocks\/123\/transactions/) - .reply(() => [ - 200, - { meta: { totalCount: 1 }, data: [{ id: "123" }, { id: "123" }] }, - peerMock.headers, - ]); + mockHost + .get("/api/blocks/123/transactions") + .query({ orderBy: "timestamp:desc" }) + .reply(200, { meta: { totalCount: 1 }, data: [{ id: "123" }, { id: "123" }] }, peerMock.headers); const response = await sendRequest("blocks.transactions", { id: "123", }); - expect(response.data.result.data).toHaveLength(2); + expect(response.body.result.data).toHaveLength(2); }); it("should fail to get the block transactions", async () => { const response = await sendRequest("blocks.transactions", { id: "123" }); - expect(response.data.error.code).toBe(404); - expect(response.data.error.message).toBe("Block 123 could not be found."); + expect(response.body.error.code).toBe(404); + expect(response.body.error.message).toBe("Block 123 could not be found."); }); }); }); diff --git a/packages/core-json-rpc/__tests__/transactions.test.ts b/__tests__/integration/core-json-rpc/transactions.test.ts similarity index 75% rename from packages/core-json-rpc/__tests__/transactions.test.ts rename to __tests__/integration/core-json-rpc/transactions.test.ts index f86e5b1bb7..3cad7075b6 100644 --- a/packages/core-json-rpc/__tests__/transactions.test.ts +++ b/__tests__/integration/core-json-rpc/transactions.test.ts @@ -1,46 +1,49 @@ -import "jest-extended"; - import { app } from "@arkecosystem/core-container"; import { Peer } from "@arkecosystem/core-p2p/dist/peer"; import { crypto } from "@arkecosystem/crypto"; -import axios from "axios"; -import MockAdapter from "axios-mock-adapter"; +import "jest-extended"; +import nock from "nock"; import { sendRequest } from "./__support__/request"; import { setUp, tearDown } from "./__support__/setup"; -const axiosMock = new MockAdapter(axios); - jest.mock("is-reachable", () => jest.fn(async peer => true)); let peerMock; +let mockHost; beforeAll(async () => { await setUp(); - peerMock = new Peer("1.0.0.99", 4002); + peerMock = new Peer("1.0.0.99", 4000); Object.assign(peerMock, peerMock.headers, { status: "OK" }); const monitor = app.resolvePlugin("p2p"); monitor.peers = {}; monitor.peers[peerMock.ip] = peerMock; + + nock("http://localhost", { allowUnmocked: true }); + + mockHost = nock("http://localhost:4003"); }); afterAll(async () => { + nock.cleanAll(); await tearDown(); }); - beforeEach(async () => { - axiosMock.onPost(/.*:8080.*/).passThrough(); + nock(peerMock.url) + .get("/peer/status") + .reply(200, { success: true, height: 1 }, peerMock.headers); }); afterEach(async () => { - axiosMock.reset(); // important: resets any existing mocking behavior + nock.cleanAll(); }); describe("Transactions", () => { describe("POST transactions.info", () => { it("should get the transaction for the given ID", async () => { - axiosMock.onGet(/.*\/api\/transactions/).reply(() => [ + mockHost.get("/api/transactions/e4311204acf8a86ba833e494f5292475c6e9e0913fc455a12601b4b6b55818d8").reply( 200, { data: { @@ -48,13 +51,13 @@ describe("Transactions", () => { }, }, peerMock.headers, - ]); + ); const response = await sendRequest("transactions.info", { id: "e4311204acf8a86ba833e494f5292475c6e9e0913fc455a12601b4b6b55818d8", }); - expect(response.data.result.id).toBe("e4311204acf8a86ba833e494f5292475c6e9e0913fc455a12601b4b6b55818d8"); + expect(response.body.result.id).toBe("e4311204acf8a86ba833e494f5292475c6e9e0913fc455a12601b4b6b55818d8"); }); it("should fail to get the transaction for the given ID", async () => { @@ -62,8 +65,8 @@ describe("Transactions", () => { id: "e4311204acf8a86ba833e494f5292475c6e9e0913fc455a12601b4b6b55818d8", }); - expect(response.data.error.code).toBe(404); - expect(response.data.error.message).toBe( + expect(response.body.error.code).toBe(404); + expect(response.body.error.message).toBe( "Transaction e4311204acf8a86ba833e494f5292475c6e9e0913fc455a12601b4b6b55818d8 could not be found.", ); }); @@ -77,8 +80,8 @@ describe("Transactions", () => { passphrase: "this is a top secret passphrase", }); - expect(response.data.result.recipientId).toBe("APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn"); - expect(crypto.verify(response.data.result)).toBeTrue(); + expect(response.body.result.recipientId).toBe("APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn"); + expect(crypto.verify(response.body.result)).toBeTrue(); }); }); @@ -90,13 +93,13 @@ describe("Transactions", () => { passphrase: "this is a top secret passphrase", }); - axiosMock.onPost(/.*\/api\/transactions/).reply(() => [200, { success: true }, peerMock.headers]); + mockHost.post("/api/transactions").reply(200, { success: true }, peerMock.headers); const response = await sendRequest("transactions.broadcast", { - id: transaction.data.result.id, + id: transaction.body.result.id, }); - expect(crypto.verify(response.data.result)).toBeTrue(); + expect(crypto.verify(response.body.result)).toBeTrue(); }); it("should fail to broadcast the transaction", async () => { @@ -104,8 +107,8 @@ describe("Transactions", () => { id: "e4311204acf8a86ba833e494f5292475c6e9e0913fc455a12601b4b6b55818d8", }); - expect(response.data.error.code).toBe(404); - expect(response.data.error.message).toBe( + expect(response.body.error.code).toBe(404); + expect(response.body.error.message).toBe( "Transaction e4311204acf8a86ba833e494f5292475c6e9e0913fc455a12601b4b6b55818d8 could not be found.", ); }); @@ -128,8 +131,8 @@ describe("Transactions", () => { recipientId: "AUDud8tvyVZa67p3QY7XPRUTjRGnWQQ9Xv", }); - expect(response.data.result.recipientId).toBe("AUDud8tvyVZa67p3QY7XPRUTjRGnWQQ9Xv"); - expect(crypto.verify(response.data.result)).toBeTrue(); + expect(response.body.result.recipientId).toBe("AUDud8tvyVZa67p3QY7XPRUTjRGnWQQ9Xv"); + expect(crypto.verify(response.body.result)).toBeTrue(); }); it("should fail to create a new transaction", async () => { @@ -140,8 +143,8 @@ describe("Transactions", () => { recipientId: "AUDud8tvyVZa67p3QY7XPRUTjRGnWQQ9Xv", }); - expect(response.data.error.code).toBe(404); - expect(response.data.error.message).toBe("User 123456789 could not be found."); + expect(response.body.error.code).toBe(404); + expect(response.body.error.message).toBe("User 123456789 could not be found."); }); }); }); diff --git a/packages/core-json-rpc/__tests__/wallets.test.ts b/__tests__/integration/core-json-rpc/wallets.test.ts similarity index 57% rename from packages/core-json-rpc/__tests__/wallets.test.ts rename to __tests__/integration/core-json-rpc/wallets.test.ts index 67b43faad5..e8f61f47fc 100644 --- a/packages/core-json-rpc/__tests__/wallets.test.ts +++ b/__tests__/integration/core-json-rpc/wallets.test.ts @@ -1,75 +1,84 @@ -import "jest-extended"; - import { app } from "@arkecosystem/core-container"; import { Peer } from "@arkecosystem/core-p2p/dist/peer"; -import axios from "axios"; -import MockAdapter from "axios-mock-adapter"; +import "jest-extended"; +import nock from "nock"; import { sendRequest } from "./__support__/request"; import { setUp, tearDown } from "./__support__/setup"; -const axiosMock = new MockAdapter(axios); - jest.mock("is-reachable", () => jest.fn(async peer => true)); let peerMock; +let mockHost; beforeAll(async () => { await setUp(); - peerMock = new Peer("1.0.0.99", 4002); + peerMock = new Peer("1.0.0.99", 4000); Object.assign(peerMock, peerMock.headers, { status: "OK" }); const monitor = app.resolvePlugin("p2p"); monitor.peers = {}; monitor.peers[peerMock.ip] = peerMock; + + nock("http://localhost", { allowUnmocked: true }); + + mockHost = nock("http://localhost:4003"); }); afterAll(async () => { + nock.cleanAll(); await tearDown(); }); beforeEach(async () => { - axiosMock.onGet(/.*\/api\/loader\/autoconfigure/).reply(() => [200, { network: {} }, peerMock.headers]); - axiosMock.onGet(/.*\/peer\/status/).reply(() => [200, { success: true, height: 5 }, peerMock.headers]); - axiosMock.onGet(/.*\/peer\/list/).reply(() => [ - 200, - { - success: true, - peers: [ - { - status: "OK", - ip: peerMock.ip, - port: 4002, - height: 5, - delay: 8, - }, - ], - }, - peerMock.headers, - ]); - axiosMock.onPost(/.*:8080.*/).passThrough(); + nock(peerMock.url) + .get("/api/loader/autoconfigure") + .reply(200, { network: {} }, peerMock.headers); + + nock(peerMock.url) + .get("/peer/status") + .reply(200, { success: true, height: 5 }, peerMock.headers); + + nock(peerMock.url) + .get("/peer/list") + .reply( + 200, + { + success: true, + peers: [ + { + status: "OK", + ip: peerMock.ip, + port: 4002, + height: 5, + delay: 8, + }, + ], + }, + peerMock.headers, + ); }); afterEach(async () => { - axiosMock.reset(); // important: resets any existing mocking behavior + nock.cleanAll(); }); describe("Wallets", () => { describe("POST wallets.info", () => { it("should get information about the given wallet", async () => { - axiosMock - .onGet(/.*\/api\/wallets\/AUDud8tvyVZa67p3QY7XPRUTjRGnWQQ9Xv/) - .reply(() => [200, { data: { address: "AUDud8tvyVZa67p3QY7XPRUTjRGnWQQ9Xv" } }, peerMock.headers]); + mockHost + .get("/api/wallets/AUDud8tvyVZa67p3QY7XPRUTjRGnWQQ9Xv") + .reply(200, { data: { address: "AUDud8tvyVZa67p3QY7XPRUTjRGnWQQ9Xv" } }, peerMock.headers); const response = await sendRequest("wallets.info", { address: "AUDud8tvyVZa67p3QY7XPRUTjRGnWQQ9Xv", }); - expect(response.data.result.address).toBe("AUDud8tvyVZa67p3QY7XPRUTjRGnWQQ9Xv"); + expect(response.body.result.address).toBe("AUDud8tvyVZa67p3QY7XPRUTjRGnWQQ9Xv"); }); it("should fail to get information about the given wallet", async () => { - axiosMock.onGet(/.*\/api\/wallets\/AUDud8tvyVZa67p3QY7XPRUTjRGnWQQ9Xv/).reply(() => [ + mockHost.get("/api/wallets/AUDud8tvyVZa67p3QY7XPRUTjRGnWQQ9Xv").reply( 404, { error: { @@ -78,33 +87,34 @@ describe("Wallets", () => { }, }, peerMock.headers, - ]); + ); const response = await sendRequest("wallets.info", { address: "AUDud8tvyVZa67p3QY7XPRUTjRGnWQQ9Xv", }); - expect(response.data.error.code).toBe(404); - expect(response.data.error.message).toBe("Wallet AUDud8tvyVZa67p3QY7XPRUTjRGnWQQ9Xv could not be found."); + expect(response.body.error.code).toBe(404); + expect(response.body.error.message).toBe("Wallet AUDud8tvyVZa67p3QY7XPRUTjRGnWQQ9Xv could not be found."); }); }); describe("POST wallets.transactions", () => { it("should get the transactions for the given wallet", async () => { - axiosMock - .onGet(/.*\/api\/transactions/) - .reply(() => [ - 200, - { meta: { totalCount: 2 }, data: [{ id: "123" }, { id: "1234" }] }, - peerMock.headers, - ]); + mockHost + .get("/api/transactions") + .query({ + offset: 0, + orderBy: "timestamp:desc", + ownerId: "AUDud8tvyVZa67p3QY7XPRUTjRGnWQQ9Xv", + }) + .reply(200, { meta: { totalCount: 2 }, data: [{ id: "123" }, { id: "1234" }] }, peerMock.headers); const response = await sendRequest("wallets.transactions", { address: "AUDud8tvyVZa67p3QY7XPRUTjRGnWQQ9Xv", }); - expect(response.data.result.count).toBe(2); - expect(response.data.result.data).toHaveLength(2); + expect(response.body.result.count).toBe(2); + expect(response.body.result.data).toHaveLength(2); }); it("should fail to get transactions for the given wallet", async () => { @@ -112,8 +122,8 @@ describe("Wallets", () => { address: "AUDud8tvyVZa67p3QY7XPRUTjRGnWQQ9Xv", }); - expect(response.data.error.code).toBe(404); - expect(response.data.error.message).toBe("Wallet AUDud8tvyVZa67p3QY7XPRUTjRGnWQQ9Xv could not be found."); + expect(response.body.error.code).toBe(404); + expect(response.body.error.message).toBe("Wallet AUDud8tvyVZa67p3QY7XPRUTjRGnWQQ9Xv could not be found."); }); }); @@ -123,8 +133,8 @@ describe("Wallets", () => { passphrase: "this is a top secret passphrase", }); - expect(response.data.result.address).toBe("AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC"); - expect(response.data.result.publicKey).toBe( + expect(response.body.result.address).toBe("AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC"); + expect(response.body.result.publicKey).toBe( "034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192", ); }); @@ -143,11 +153,11 @@ describe("Wallets", () => { userId, }); - expect(response.data.result).toHaveProperty("address"); - expect(response.data.result).toHaveProperty("publicKey"); - expect(response.data.result).toHaveProperty("wif"); + expect(response.body.result).toHaveProperty("address"); + expect(response.body.result).toHaveProperty("publicKey"); + expect(response.body.result).toHaveProperty("wif"); - bip38wif = response.data.result.wif; + bip38wif = response.body.result.wif; }); }); @@ -158,10 +168,10 @@ describe("Wallets", () => { userId, }); - expect(response.data.result).toHaveProperty("address"); - expect(response.data.result).toHaveProperty("publicKey"); - expect(response.data.result).toHaveProperty("wif"); - expect(response.data.result.wif).toBe(bip38wif); + expect(response.body.result).toHaveProperty("address"); + expect(response.body.result).toHaveProperty("publicKey"); + expect(response.body.result).toHaveProperty("wif"); + expect(response.body.result.wif).toBe(bip38wif); }); it("should fail to find the wallet for the given userId", async () => { @@ -170,8 +180,8 @@ describe("Wallets", () => { userId: "123456789", }); - expect(response.data.error.code).toBe(404); - expect(response.data.error.message).toBe("User 123456789 could not be found."); + expect(response.body.error.code).toBe(404); + expect(response.body.error.message).toBe("User 123456789 could not be found."); }); }); }); diff --git a/packages/core-p2p/__tests__/__support__/setup.ts b/__tests__/integration/core-p2p/__support__/setup.ts similarity index 74% rename from packages/core-p2p/__tests__/__support__/setup.ts rename to __tests__/integration/core-p2p/__support__/setup.ts index 2c4e2bcfd6..f6417c8904 100644 --- a/packages/core-p2p/__tests__/__support__/setup.ts +++ b/__tests__/integration/core-p2p/__support__/setup.ts @@ -1,5 +1,5 @@ import { app } from "@arkecosystem/core-container"; -import { registerWithContainer, setUpContainer } from "../../../core-test-utils/src/helpers/container"; +import { registerWithContainer, setUpContainer } from "../../../utils/helpers/container"; jest.setTimeout(60000); @@ -17,13 +17,13 @@ export const setUp = async () => { }); // register p2p plugin - await registerWithContainer(require("../../src/plugin").plugin, options); + await registerWithContainer(require("../../../../packages/core-p2p/src/plugin").plugin, options); await registerWithContainer(require("@arkecosystem/core-blockchain").plugin, {}); }; export const tearDown = async () => { await require("@arkecosystem/core-blockchain").plugin.deregister(app, {}); - await require("../../src/plugin").plugin.deregister(app, options); + await require("../../../../packages/core-p2p/src/plugin").plugin.deregister(app, options); await app.tearDown(); }; diff --git a/packages/core-p2p/__tests__/__support__/utils.ts b/__tests__/integration/core-p2p/__support__/utils.ts similarity index 91% rename from packages/core-p2p/__tests__/__support__/utils.ts rename to __tests__/integration/core-p2p/__support__/utils.ts index 39f0f9e1a4..a5a51396c7 100644 --- a/packages/core-p2p/__tests__/__support__/utils.ts +++ b/__tests__/integration/core-p2p/__support__/utils.ts @@ -1,5 +1,5 @@ import { app } from "@arkecosystem/core-container"; -import { ApiHelpers } from "@arkecosystem/core-test-utils/src/helpers/api"; +import { ApiHelpers } from "../../../utils/helpers/api"; class Helpers { public headers: any; diff --git a/packages/core-p2p/__tests__/fixtures/block-with-transactions.json b/__tests__/integration/core-p2p/fixtures/block-with-transactions.json similarity index 100% rename from packages/core-p2p/__tests__/fixtures/block-with-transactions.json rename to __tests__/integration/core-p2p/fixtures/block-with-transactions.json diff --git a/packages/core-debugger-cli/__tests__/__fixtures__/block.json b/__tests__/integration/core-p2p/fixtures/block.json similarity index 89% rename from packages/core-debugger-cli/__tests__/__fixtures__/block.json rename to __tests__/integration/core-p2p/fixtures/block.json index d1228aa285..ec148aa9e2 100644 --- a/packages/core-debugger-cli/__tests__/__fixtures__/block.json +++ b/__tests__/integration/core-p2p/fixtures/block.json @@ -24,7 +24,7 @@ "vendorField": "Goose Voter - True Block Weight", "senderPublicKey": "0265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c0", "signature": "304402204f12469157b19edd06ba25fcad3d4a5ef5b057c23f9e02de4641e6f8eef0553e022010121ab282f83efe1043de9c16bbf2c6845a03684229a0d7c965ffb9abdfb978", - "signSignature": "30450221008327862f0b9178d6665f7d6674978c5caf749649558d814244b1c66cdf945c40022015918134ef01fed3fe2a2efde3327917731344332724522c75c2799a14f78717", + "secondSignature": "30450221008327862f0b9178d6665f7d6674978c5caf749649558d814244b1c66cdf945c40022015918134ef01fed3fe2a2efde3327917731344332724522c75c2799a14f78717", "id": "170543154a3b79459cbaa529f9f62b6f1342682799eb549dbf09fcca2d1f9c11", "senderId": "DB8LnnQqYvHpG4WkGJ9AJWBYEct7G3yRZg", "hop": 2, @@ -41,7 +41,7 @@ "vendorField": "Goose Voter - True Block Weight", "senderPublicKey": "0265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c0", "signature": "304402205f82feb8c5d1d79c565c2ff7badb93e4c9827b132d135dda11cb25427d4ef8ac02205ff136f970533c4ec4c7d0cd1ea7e02d7b62629b66c6c93265f608d7f2389727", - "signSignature": "304402207e912031fcc700d8a55fbc415993302a0d8e6aea128397141b640b6dba52331702201fd1ad3984e42af44f548907add6cb7ad72ca0070c8cc1d8dc9bbda208c56bd9", + "secondSignature": "304402207e912031fcc700d8a55fbc415993302a0d8e6aea128397141b640b6dba52331702201fd1ad3984e42af44f548907add6cb7ad72ca0070c8cc1d8dc9bbda208c56bd9", "id": "1da153f37eceda233ff1b407ac18e47b3cae47c14cdcd5297d929618a916c4a7", "senderId": "DB8LnnQqYvHpG4WkGJ9AJWBYEct7G3yRZg", "hop": 2, @@ -58,7 +58,7 @@ "vendorField": "Goose Voter - True Block Weight", "senderPublicKey": "0265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c0", "signature": "304502210083216e6969e068770e6d2fe5c244881002309df84d20290ddf3f858967ed010202202a479b3da5080ea475d310ff13494654b42db75886a8808bd211b4bdb9146a7a", - "signSignature": "3045022100e1dcab3406bbeb968146a4a391909ce41df9b71592a753b001e7c2ee1d382c5102202a74aeafd4a152ec61854636fbae829c41f1416c1e0637a0809408394973099f", + "secondSignature": "3045022100e1dcab3406bbeb968146a4a391909ce41df9b71592a753b001e7c2ee1d382c5102202a74aeafd4a152ec61854636fbae829c41f1416c1e0637a0809408394973099f", "id": "1e255f07dc25ce22d900ea81663c8f00d05a7b7c061e6fc3c731b05d642fa0b9", "senderId": "DB8LnnQqYvHpG4WkGJ9AJWBYEct7G3yRZg", "hop": 2, @@ -75,7 +75,7 @@ "vendorField": "Goose Voter - True Block Weight", "senderPublicKey": "0265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c0", "signature": "3045022100cd4fa9855227be11e17201419dacfbbd5d9946df8d6792a9488160025693821402207fb83969bad6a26959f437b5bb88e255b0a48eb04964d0c0d29f7ee94bd15e11", - "signSignature": "304402205f50c2991a17743d17ffbb09159cadc35a3f848044261842879ccf5be9d81c5e022023bf21c32fb6e94494104f15f8d3a942ab120d0abd6fb4c93790b68e1b307a79", + "secondSignature": "304402205f50c2991a17743d17ffbb09159cadc35a3f848044261842879ccf5be9d81c5e022023bf21c32fb6e94494104f15f8d3a942ab120d0abd6fb4c93790b68e1b307a79", "id": "66336c61d6ec623f8a1d2fd156a0fac16a4fe93bb3fba337859355c2119923a8", "senderId": "DB8LnnQqYvHpG4WkGJ9AJWBYEct7G3yRZg", "hop": 2, @@ -92,7 +92,7 @@ "vendorField": "Goose Voter - True Block Weight", "senderPublicKey": "0265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c0", "signature": "30450221009c792062e13399ac6756b2e9f137194d06e106360ac0f3e24e55c7249cee0b3602205dc1d9c76d0451d1cb5a2396783a13e6d2d790ccfd49291e3d0a78349f7ea0e8", - "signSignature": "30440220083ba8a9af49b8be6e93794d71ec43ffc96a158375810e5d9f2478e71655315b0220278402ecaa1d224dab9f0f3b28295bbaea339c85c7400edafdc49df87439fc64", + "secondSignature": "30440220083ba8a9af49b8be6e93794d71ec43ffc96a158375810e5d9f2478e71655315b0220278402ecaa1d224dab9f0f3b28295bbaea339c85c7400edafdc49df87439fc64", "id": "78db36f7d79f51c67d7210ee3819dfb8d0d47b16a7484ebf55c5a055b17209a3", "senderId": "DB8LnnQqYvHpG4WkGJ9AJWBYEct7G3yRZg", "hop": 2, @@ -109,7 +109,7 @@ "vendorField": "Goose Voter - True Block Weight", "senderPublicKey": "0265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c0", "signature": "3044022063c65263e42be02bd9831b375c1d76a88332f00ed0557ecc1e7d2375ca40070902206797b5932c0bad68444beb5a38daa7cadf536ee2144e0d9777b812284d14374e", - "signSignature": "3045022100b04da6692f75d43229ffd8486c1517e8952d38b4c03dfac38b6b360190a5c33e0220776622e5f09f92a1258b4a011f22181c977b622b8d1bbb2f83b42f4126d00739", + "secondSignature": "3045022100b04da6692f75d43229ffd8486c1517e8952d38b4c03dfac38b6b360190a5c33e0220776622e5f09f92a1258b4a011f22181c977b622b8d1bbb2f83b42f4126d00739", "id": "83c80bb58777bb43f5037544b44ef69f191d3548fd1b2a00bed368f9f0d694c5", "senderId": "DB8LnnQqYvHpG4WkGJ9AJWBYEct7G3yRZg", "hop": 2, @@ -126,7 +126,7 @@ "vendorField": "Goose Voter - True Block Weight", "senderPublicKey": "0265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c0", "signature": "3045022100d4513c3608c2072e38e7a0e3bb8daf2cd5f7cc6fec9a5570dccd1eda696c591902202ecbbf3c9d0757be7b23c8b1cc6481c51600d158756c47fcb6f4a7f4893e31c4", - "signSignature": "304402201fed4858d0806dd32220960900a871dd2f60e1f623af75feef9b1034a9a0a46402205a29b27c63fcc3e1ee1e77ecbbf4dd6e7db09901e7a09b9fd490cd68d62392cb", + "secondSignature": "304402201fed4858d0806dd32220960900a871dd2f60e1f623af75feef9b1034a9a0a46402205a29b27c63fcc3e1ee1e77ecbbf4dd6e7db09901e7a09b9fd490cd68d62392cb", "id": "d2faf992fdd5da96d6d15038b6ddb65230338fa2096e45e44da51daad5e2f3ca", "senderId": "DB8LnnQqYvHpG4WkGJ9AJWBYEct7G3yRZg", "hop": 2, diff --git a/packages/core-p2p/__tests__/server/1.test.ts b/__tests__/integration/core-p2p/server/1.test.ts similarity index 85% rename from packages/core-p2p/__tests__/server/1.test.ts rename to __tests__/integration/core-p2p/server/1.test.ts index 3ff5a2fb9b..c40de103b8 100644 --- a/packages/core-p2p/__tests__/server/1.test.ts +++ b/__tests__/integration/core-p2p/server/1.test.ts @@ -1,10 +1,10 @@ -import { generateTransfers } from "@arkecosystem/core-test-utils/src/generators/transactions/transfer"; import { models } from "@arkecosystem/crypto"; +import { TransactionFactory } from "../../../helpers/transaction-factory"; import { setUp, tearDown } from "../__support__/setup"; import { utils } from "../__support__/utils"; import fullBlock from "../fixtures/block-with-transactions.json"; -const { Block, Transaction } = models; +const { Block } = models; let genesisBlock; @@ -13,7 +13,7 @@ beforeAll(async () => { // Create the genesis block after the setup has finished or else it uses a potentially // wrong network config. - genesisBlock = new Block(require("@arkecosystem/core-test-utils/src/config/testnet/genesisBlock.json")); + genesisBlock = new Block(require("../../../utils/config/testnet/genesisBlock.json")); }); afterAll(async () => { @@ -153,31 +153,39 @@ describe("API - Version 1", () => { }); it("should not be ok, because previous block id is missing", async () => { + const block = new Block(fullBlock as any); + const blockPayload = block.toJson(); + blockPayload.previousBlock = null; + const response = await utils.POST("peer/blocks", { - block: genesisBlock.toJson(), + block: blockPayload, }); - expect(response.status).toBe(400); + expect(response.status).toBe(422); }); }); describe("POST /peer/transactions", () => { it("should succeed with an existing wallet", async () => { - const transactions = generateTransfers( - "testnet", - "clay harbor enemy utility margin pretty hub comic piece aerobic umbrella acquire", - null, - 40, - ); - const response = await utils.POST("peer/transactions", { transactions }); + const transactions = TransactionFactory.transfer(null, 40) + .withNetwork("testnet") + .withPassphrase("clay harbor enemy utility margin pretty hub comic piece aerobic umbrella acquire") + .create(); + const response = await utils.POST("peer/transactions", { + transactions, + }); expect(response.data).toBeObject(); expect(response.data.success).toBeTrue(); }); it("should fail with a cold wallet", async () => { - const transactions = generateTransfers("testnet", "wallet does not exist"); - const response = await utils.POST("peer/transactions", { transactions }); + const transactions = TransactionFactory.transfer("wallet does not exist") + .withNetwork("testnet") + .create(); + const response = await utils.POST("peer/transactions", { + transactions, + }); expect(response.data).toBeObject(); expect(response.data.success).toBeFalse(); diff --git a/packages/core-p2p/__tests__/server/internal.test.ts b/__tests__/integration/core-p2p/server/internal.test.ts similarity index 84% rename from packages/core-p2p/__tests__/server/internal.test.ts rename to __tests__/integration/core-p2p/server/internal.test.ts index 456adb707e..710bf69ccd 100644 --- a/packages/core-p2p/__tests__/server/internal.test.ts +++ b/__tests__/integration/core-p2p/server/internal.test.ts @@ -1,12 +1,12 @@ -import { generateTransfers } from "@arkecosystem/core-test-utils/src/generators/transactions/transfer"; -import { models } from "@arkecosystem/crypto"; -import blockFixture from "../../../core-debugger-cli/__tests__/__fixtures__/block.json"; +import { models, Transaction } from "@arkecosystem/crypto"; +import { TransactionFactory } from "../../../helpers/transaction-factory"; import { setUp, tearDown } from "../__support__/setup"; import { utils } from "../__support__/utils"; +import blockFixture from "../fixtures/block.json"; -const { Block, Transaction } = models; +const { Block } = models; -let genesisBlock; +let genesisBlock: models.Block; let genesisTransaction; beforeAll(async () => { @@ -14,8 +14,8 @@ beforeAll(async () => { // Create the genesis block after the setup has finished or else it uses a potentially // wrong network config. - genesisBlock = new Block(require("@arkecosystem/core-test-utils/src/config/testnet/genesisBlock.json")); - genesisTransaction = new Transaction(genesisBlock.transactions[0]); + genesisBlock = new Block(require("../../../utils/config/testnet/genesisBlock.json")); + genesisTransaction = Transaction.fromData(genesisBlock.transactions[0].data); }); beforeEach(() => { @@ -68,9 +68,9 @@ describe("API - Internal", () => { describe("POST /transactions/verify", () => { it("should be ok", async () => { - const transaction = generateTransfers("testnet")[0]; + const transaction = TransactionFactory.transfer("testnet").build()[0]; const response = await utils.POST("internal/transactions/verify", { - transaction: Transaction.serialize(transaction).toString("hex"), + transaction: Transaction.toBytes(transaction.data).toString("hex"), }); expect(response.status).toBe(200); diff --git a/__tests__/integration/core-tester-cli/__fixtures__/block.json b/__tests__/integration/core-tester-cli/__fixtures__/block.json new file mode 100644 index 0000000000..7c33aa01a1 --- /dev/null +++ b/__tests__/integration/core-tester-cli/__fixtures__/block.json @@ -0,0 +1,126 @@ +{ + "data": { + "id": "17605317082329008056", + "version": 0, + "height": 1760000, + "timestamp": 62222080, + "previousBlock": "3112633353705641986", + "numberOfTransactions": 7, + "totalAmount": "10500000000", + "totalFee": "70000000", + "reward": "200000000", + "payloadLength": 224, + "payloadHash": "de56269cae3ab156f6979b94a04c30b82ed7d6f9a97d162583c98215c18c65db", + "generatorPublicKey": "03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37", + "blockSignature": "30450221008c59bd2379061ad3539b73284fc0bbb57dbc97efd54f55010ba3f198c04dde7402202e482126b3084c6313c1378d686df92a3e2ef5581323de11e74fe07eeab339f3", + "transactions": [ + { + "version": 1, + "network": 30, + "type": 0, + "timestamp": 62222080, + "senderPublicKey": "03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37", + "fee": "10000000", + "amount": "1300000000", + "expiration": 0, + "recipientId": "DBYyh2vXcigrJGUHfvmYxVxEqeH7vomw6x", + "signature": "30440220714c2627f0e9c3bd6bf13b8b4faa5ec2d677694c27f580e2f9e3875bde9bc36f02201c33faacab9eafd799d9ceecaa153e3b87b4cd04535195261fd366e552652549", + "id": "188b4d9d95a58e4e18d9ce9db28f2010323b90b5afd36a474d7ae7bf70772bb0", + "blockId": "17605317082329008056", + "sequence": 0 + }, + { + "version": 1, + "network": 30, + "type": 0, + "timestamp": 62222080, + "senderPublicKey": "03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37", + "fee": "10000000", + "amount": "1700000000", + "expiration": 0, + "recipientId": "DBYyh2vXcigrJGUHfvmYxVxEqeH7vomw6x", + "signature": "3045022100e6039f810684515c0d6b31039040a76c98f3624b6454cb156a0a2137e5f8dba7022001ada19bcca5798e1c7cc8cc39bab5d4019525e3d72a42bd2c4129352b8ead87", + "id": "23084f2cc566f6144a8f447bc784de64a0b0646776060482d8550856145e11e2", + "blockId": "17605317082329008056", + "sequence": 1 + }, + { + "version": 1, + "network": 30, + "type": 0, + "timestamp": 62222080, + "senderPublicKey": "03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37", + "fee": "10000000", + "amount": "1500000000", + "expiration": 0, + "recipientId": "DBYyh2vXcigrJGUHfvmYxVxEqeH7vomw6x", + "signature": "3045022100c2b5ef772b36e468e95ec2e457bfaba7bad0e13b3faf57e229ff5d67a0e017c902202339664595ea5c70ce20e4dd182532f7fa385d86575b0476ff3eda9f9785e1e9", + "id": "743ce0a590c2af90e4734db3630b52d7a7cbc2bc228d75ae6409c0b6d184bfad", + "blockId": "17605317082329008056", + "sequence": 2 + }, + { + "version": 1, + "network": 30, + "type": 0, + "timestamp": 62222080, + "senderPublicKey": "03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37", + "fee": "10000000", + "amount": "1600000000", + "expiration": 0, + "recipientId": "DBYyh2vXcigrJGUHfvmYxVxEqeH7vomw6x", + "signature": "30450221009ceb56688705e6b12000bde726ca123d84982231d7434f059612ff5f987409c602200d908667877c902e7ba35024951046b883e0bce9103d4717928d94ecc958884a", + "id": "877780706b62b437913ef4ea30c6e370f8877ef7a5bac58d8cebca83b7e20060", + "blockId": "17605317082329008056", + "sequence": 3 + }, + { + "version": 1, + "network": 30, + "type": 0, + "timestamp": 62222080, + "senderPublicKey": "03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37", + "fee": "10000000", + "amount": "1200000000", + "expiration": 0, + "recipientId": "DBYyh2vXcigrJGUHfvmYxVxEqeH7vomw6x", + "signature": "30440220464beac6d49943ad8afaac4fdc863c9cd7cf3a84f9938c1d7269ed522298f11a02203581bf180de1966f86d914afeb005e1e818c9213514f96a34e1391c2a08514fa", + "id": "947fe8745eeed8fa6e5ad62a8dad29bcf3d50ce001907926c486460d1cc1f1c0", + "blockId": "17605317082329008056", + "sequence": 4 + }, + { + "version": 1, + "network": 30, + "type": 0, + "timestamp": 62222080, + "senderPublicKey": "03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37", + "fee": "10000000", + "amount": "1800000000", + "expiration": 0, + "recipientId": "DBYyh2vXcigrJGUHfvmYxVxEqeH7vomw6x", + "signature": "3045022100c7b40d7134d909762d18d6bfb7ac1c32be0ee8c047020131f499faea70ca0b2b0220117c0cf026f571f5a85e3ae800a6fd595185076ff38e64c7a4bd14f34e1d4dd1", + "id": "98387933d65fabffe2642464d4c7b1ff5fe1fa5a35992f834b0ac145dff462ea", + "blockId": "17605317082329008056", + "sequence": 5 + }, + { + "version": 1, + "network": 30, + "type": 0, + "timestamp": 62222080, + "senderPublicKey": "03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37", + "fee": "10000000", + "amount": "1400000000", + "expiration": 0, + "recipientId": "DBYyh2vXcigrJGUHfvmYxVxEqeH7vomw6x", + "signature": "304402206a4a8e4e6918fbc15728653b117f51db716aeb04e5ee1de047f80b0476ee4efb02200f486dfaf0def3f3e8636d46ee75a2c07de9714ce4283a25fde9b6218b5e7923", + "id": "e93345dd9a87ac4e84d9bfd892dfbfeb02e546e5bd7822168d0f72c7662e6176", + "blockId": "17605317082329008056", + "sequence": 6 + } + ] + }, + "serialized": "00000000006fb50300db1a002b324b8b33a85802070000000049d97102000000801d2c040000000000c2eb0b00000000e0000000de56269cae3ab156f6979b94a04c30b82ed7d6f9a97d162583c98215c18c65db03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac3730450221008c59bd2379061ad3539b73284fc0bbb57dbc97efd54f55010ba3f198c04dde7402202e482126b3084c6313c1378d686df92a3e2ef5581323de11e74fe07eeab339f3", + "serializedFull": "00000000006fb50300db1a002b324b8b33a85802070000000049d97102000000801d2c040000000000c2eb0b00000000e0000000de56269cae3ab156f6979b94a04c30b82ed7d6f9a97d162583c98215c18c65db03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac3730450221008c59bd2379061ad3539b73284fc0bbb57dbc97efd54f55010ba3f198c04dde7402202e482126b3084c6313c1378d686df92a3e2ef5581323de11e74fe07eeab339f3990000009a0000009a0000009a000000990000009a00000099000000ff011e00006fb50303287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37809698000000000000006d7c4d00000000000000001e46550551e12d2531ea9d2968696b75f68ae7f29530440220714c2627f0e9c3bd6bf13b8b4faa5ec2d677694c27f580e2f9e3875bde9bc36f02201c33faacab9eafd799d9ceecaa153e3b87b4cd04535195261fd366e552652549ff011e00006fb50303287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac3780969800000000000000f1536500000000000000001e46550551e12d2531ea9d2968696b75f68ae7f2953045022100e6039f810684515c0d6b31039040a76c98f3624b6454cb156a0a2137e5f8dba7022001ada19bcca5798e1c7cc8cc39bab5d4019525e3d72a42bd2c4129352b8ead87ff011e00006fb50303287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37809698000000000000002f685900000000000000001e46550551e12d2531ea9d2968696b75f68ae7f2953045022100c2b5ef772b36e468e95ec2e457bfaba7bad0e13b3faf57e229ff5d67a0e017c902202339664595ea5c70ce20e4dd182532f7fa385d86575b0476ff3eda9f9785e1e9ff011e00006fb50303287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac3780969800000000000000105e5f00000000000000001e46550551e12d2531ea9d2968696b75f68ae7f29530450221009ceb56688705e6b12000bde726ca123d84982231d7434f059612ff5f987409c602200d908667877c902e7ba35024951046b883e0bce9103d4717928d94ecc958884aff011e00006fb50303287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37809698000000000000008c864700000000000000001e46550551e12d2531ea9d2968696b75f68ae7f29530440220464beac6d49943ad8afaac4fdc863c9cd7cf3a84f9938c1d7269ed522298f11a02203581bf180de1966f86d914afeb005e1e818c9213514f96a34e1391c2a08514faff011e00006fb50303287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac3780969800000000000000d2496b00000000000000001e46550551e12d2531ea9d2968696b75f68ae7f2953045022100c7b40d7134d909762d18d6bfb7ac1c32be0ee8c047020131f499faea70ca0b2b0220117c0cf026f571f5a85e3ae800a6fd595185076ff38e64c7a4bd14f34e1d4dd1ff011e00006fb50303287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37809698000000000000004e725300000000000000001e46550551e12d2531ea9d2968696b75f68ae7f295304402206a4a8e4e6918fbc15728653b117f51db716aeb04e5ee1de047f80b0476ee4efb02200f486dfaf0def3f3e8636d46ee75a2c07de9714ce4283a25fde9b6218b5e7923" +} diff --git a/packages/core-tester-cli/__tests__/__fixtures__/delegates-page-1.json b/__tests__/integration/core-tester-cli/__fixtures__/delegates-page-1.json similarity index 88% rename from packages/core-tester-cli/__tests__/__fixtures__/delegates-page-1.json rename to __tests__/integration/core-tester-cli/__fixtures__/delegates-page-1.json index 259580076b..2caa75a4da 100644 --- a/packages/core-tester-cli/__tests__/__fixtures__/delegates-page-1.json +++ b/__tests__/integration/core-tester-cli/__fixtures__/delegates-page-1.json @@ -18,7 +18,6 @@ "rank": 1, "blocks": { "produced": 15605, - "missed": 709, "last": { "id": "9508080167740922063", "timestamp": { @@ -29,8 +28,7 @@ } }, "production": { - "approval": 0.34, - "productivity": 95.65 + "approval": 0.34 }, "forged": { "fees": 74694705072, @@ -46,7 +44,6 @@ "rank": 2, "blocks": { "produced": 15104, - "missed": 493, "last": { "id": "6408769526029163610", "timestamp": { @@ -57,8 +54,7 @@ } }, "production": { - "approval": 0.06, - "productivity": 96.84 + "approval": 0.06 }, "forged": { "fees": 50980000000, @@ -73,12 +69,10 @@ "votes": 6343253260000, "rank": 3, "blocks": { - "produced": 4165, - "missed": 785 + "produced": 4165 }, "production": { - "approval": 0.05, - "productivity": 84.14 + "approval": 0.05 }, "forged": { "fees": 3550000000, diff --git a/packages/core-tester-cli/__tests__/__fixtures__/delegates-page-2.json b/__tests__/integration/core-tester-cli/__fixtures__/delegates-page-2.json similarity index 85% rename from packages/core-tester-cli/__tests__/__fixtures__/delegates-page-2.json rename to __tests__/integration/core-tester-cli/__fixtures__/delegates-page-2.json index c3d3a340e1..5841e31024 100644 --- a/packages/core-tester-cli/__tests__/__fixtures__/delegates-page-2.json +++ b/__tests__/integration/core-tester-cli/__fixtures__/delegates-page-2.json @@ -18,7 +18,6 @@ "rank": 4, "blocks": { "produced": 1551, - "missed": 180, "last": { "id": "3658545612136752676", "timestamp": { @@ -29,8 +28,7 @@ } }, "production": { - "approval": 0.05, - "productivity": 89.6 + "approval": 0.05 }, "forged": { "fees": 5060000000, @@ -45,12 +43,10 @@ "votes": 4500000000000, "rank": 5, "blocks": { - "produced": 14572, - "missed": 772 + "produced": 14572 }, "production": { - "approval": 0.04, - "productivity": 94.97 + "approval": 0.04 }, "forged": { "fees": 29670020960, @@ -65,12 +61,10 @@ "votes": 4013860773791, "rank": 6, "blocks": { - "produced": 2650, - "missed": 950 + "produced": 2650 }, "production": { - "approval": 0.03, - "productivity": 73.61 + "approval": 0.03 }, "forged": { "fees": 3000000000, diff --git a/packages/core-debugger-cli/__tests__/__fixtures__/identities.json b/__tests__/integration/core-tester-cli/__fixtures__/identities.json similarity index 100% rename from packages/core-debugger-cli/__tests__/__fixtures__/identities.json rename to __tests__/integration/core-tester-cli/__fixtures__/identities.json diff --git a/packages/core-tester-cli/__tests__/__fixtures__/transaction-1.json b/__tests__/integration/core-tester-cli/__fixtures__/transaction-1.json similarity index 100% rename from packages/core-tester-cli/__tests__/__fixtures__/transaction-1.json rename to __tests__/integration/core-tester-cli/__fixtures__/transaction-1.json diff --git a/packages/core-tester-cli/__tests__/__fixtures__/transaction-response-1.json b/__tests__/integration/core-tester-cli/__fixtures__/transaction-response-1.json similarity index 100% rename from packages/core-tester-cli/__tests__/__fixtures__/transaction-response-1.json rename to __tests__/integration/core-tester-cli/__fixtures__/transaction-response-1.json diff --git a/packages/core-debugger-cli/__tests__/__fixtures__/transaction-second.json b/__tests__/integration/core-tester-cli/__fixtures__/transaction-second.json similarity index 100% rename from packages/core-debugger-cli/__tests__/__fixtures__/transaction-second.json rename to __tests__/integration/core-tester-cli/__fixtures__/transaction-second.json diff --git a/packages/core-debugger-cli/__tests__/__fixtures__/transaction.json b/__tests__/integration/core-tester-cli/__fixtures__/transaction.json similarity index 100% rename from packages/core-debugger-cli/__tests__/__fixtures__/transaction.json rename to __tests__/integration/core-tester-cli/__fixtures__/transaction.json diff --git a/packages/core-tester-cli/__tests__/__fixtures__/voters-page-1.json b/__tests__/integration/core-tester-cli/__fixtures__/voters-page-1.json similarity index 100% rename from packages/core-tester-cli/__tests__/__fixtures__/voters-page-1.json rename to __tests__/integration/core-tester-cli/__fixtures__/voters-page-1.json diff --git a/packages/core-tester-cli/__tests__/__fixtures__/voters-page-2.json b/__tests__/integration/core-tester-cli/__fixtures__/voters-page-2.json similarity index 100% rename from packages/core-tester-cli/__tests__/__fixtures__/voters-page-2.json rename to __tests__/integration/core-tester-cli/__fixtures__/voters-page-2.json diff --git a/packages/core-tester-cli/__tests__/__fixtures__/wallet-1.json b/__tests__/integration/core-tester-cli/__fixtures__/wallet-1.json similarity index 100% rename from packages/core-tester-cli/__tests__/__fixtures__/wallet-1.json rename to __tests__/integration/core-tester-cli/__fixtures__/wallet-1.json diff --git a/packages/core-debugger-cli/__tests__/commands/deserialize.test.ts b/__tests__/integration/core-tester-cli/commands/debug/deserialize.test.ts similarity index 77% rename from packages/core-debugger-cli/__tests__/commands/deserialize.test.ts rename to __tests__/integration/core-tester-cli/commands/debug/deserialize.test.ts index a7b7fa572e..c1d2ffcf26 100644 --- a/packages/core-debugger-cli/__tests__/commands/deserialize.test.ts +++ b/__tests__/integration/core-tester-cli/commands/debug/deserialize.test.ts @@ -1,13 +1,15 @@ import "jest-extended"; -import { DeserializeCommand } from "../../src/commands/deserialize"; +import { DeserializeCommand } from "../../../../../packages/core-tester-cli/src/commands/debug/deserialize"; describe("Commands - Deserialize", () => { - const fixtureBlock = require("../__fixtures__/block.json"); - const fixtureTransaction = require("../__fixtures__/transaction.json"); + const fixtureBlock = require("../../__fixtures__/block.json"); + const fixtureTransaction = require("../../__fixtures__/transaction.json"); it("should deserialize a block (not-full)", async () => { - const actual = JSON.parse(await DeserializeCommand.run(["--data", fixtureBlock.serialized, "--type", "block"])); + const actual = JSON.parse( + await DeserializeCommand.run(["--data", fixtureBlock.serialized, "--type", "block", "--network", "devnet"]), + ); expect(actual.data.version).toBe(fixtureBlock.data.version); expect(actual.data.timestamp).toBe(fixtureBlock.data.timestamp); @@ -25,7 +27,14 @@ describe("Commands - Deserialize", () => { it("should deserialize a block (full)", async () => { const actual = JSON.parse( - await DeserializeCommand.run(["--data", fixtureBlock.serializedFull, "--type", "block"]), + await DeserializeCommand.run([ + "--data", + fixtureBlock.serializedFull, + "--type", + "block", + "--network", + "devnet", + ]), ); expect(actual.data.version).toBe(fixtureBlock.data.version); @@ -45,7 +54,14 @@ describe("Commands - Deserialize", () => { it("should deserialize a transaction", async () => { const actual = JSON.parse( - await DeserializeCommand.run(["--data", fixtureTransaction.serialized, "--type", "transaction"]), + await DeserializeCommand.run([ + "--data", + fixtureTransaction.serialized, + "--type", + "transaction", + "--network", + "devnet", + ]), ); expect(actual.type).toBe(fixtureTransaction.data.type); diff --git a/__tests__/integration/core-tester-cli/commands/debug/identity.test.ts b/__tests__/integration/core-tester-cli/commands/debug/identity.test.ts new file mode 100644 index 0000000000..eeb4927ab4 --- /dev/null +++ b/__tests__/integration/core-tester-cli/commands/debug/identity.test.ts @@ -0,0 +1,64 @@ +import "jest-extended"; + +import { IdentityCommand } from "../../../../../packages/core-tester-cli/src/commands/debug/identity"; + +describe("Commands - Identity", () => { + const fixtureIdentities = require("../../__fixtures__/identities.json"); + + it("should return identities from passphrase", async () => { + const expected = { + passphrase: "this is a top secret passphrase", + publicKey: "034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192", + privateKey: "d8839c2432bfd0a67ef10a804ba991eabba19f154a3d707917681d45822a5712", + address: "D61mfSggzbvQgTUe6JhYKH2doHaqJ3Dyib", + }; + + expect( + await IdentityCommand.run([ + "--data", + fixtureIdentities.passphrase, + "--type", + "passphrase", + "--network", + "devnet", + ]), + ).toEqual(expected); + }); + + it("should return identities from privateKey", async () => { + const expected = { + publicKey: "034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192", + privateKey: "d8839c2432bfd0a67ef10a804ba991eabba19f154a3d707917681d45822a5712", + address: "D61mfSggzbvQgTUe6JhYKH2doHaqJ3Dyib", + }; + + expect( + await IdentityCommand.run([ + "--data", + fixtureIdentities.privateKey, + "--type", + "privateKey", + "--network", + "devnet", + ]), + ).toEqual(expected); + }); + + it("should return identities from publicKey", async () => { + const expected = { + publicKey: "034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192", + address: "D61mfSggzbvQgTUe6JhYKH2doHaqJ3Dyib", + }; + + expect( + await IdentityCommand.run([ + "--data", + fixtureIdentities.publicKey, + "--type", + "publicKey", + "--network", + "devnet", + ]), + ).toEqual(expected); + }); +}); diff --git a/__tests__/integration/core-tester-cli/commands/debug/serialize.test.ts b/__tests__/integration/core-tester-cli/commands/debug/serialize.test.ts new file mode 100644 index 0000000000..7c24d61f0d --- /dev/null +++ b/__tests__/integration/core-tester-cli/commands/debug/serialize.test.ts @@ -0,0 +1,48 @@ +import "jest-extended"; + +import { SerializeCommand } from "../../../../../packages/core-tester-cli/src/commands/debug/serialize"; + +describe("Commands - Serialize", () => { + const fixtureBlock = require("../../__fixtures__/block.json"); + const fixtureTransaction = require("../../__fixtures__/transaction.json"); + + it("should serialize a block (not-full)", async () => { + expect( + await SerializeCommand.run([ + "--data", + JSON.stringify(fixtureBlock.data), + "--type", + "block", + "--network", + "devnet", + ]), + ).toEqual(fixtureBlock.serialized); + }); + + it("should serialize a block (full)", async () => { + expect( + await SerializeCommand.run([ + "--data", + JSON.stringify(fixtureBlock.data), + "--type", + "block", + "--full", + "--network", + "devnet", + ]), + ).toEqual(fixtureBlock.serializedFull); + }); + + it("should serialize a transaction", async () => { + expect( + await SerializeCommand.run([ + "--data", + JSON.stringify(fixtureTransaction.data), + "--type", + "transaction", + "--network", + "devnet", + ]), + ).toEqual(fixtureTransaction.serialized); + }); +}); diff --git a/packages/core-debugger-cli/__tests__/commands/verify-second.test.ts b/__tests__/integration/core-tester-cli/commands/debug/verify-second.test.ts similarity index 61% rename from packages/core-debugger-cli/__tests__/commands/verify-second.test.ts rename to __tests__/integration/core-tester-cli/commands/debug/verify-second.test.ts index 5cb6fb2b1a..36ee54bf8a 100644 --- a/packages/core-debugger-cli/__tests__/commands/verify-second.test.ts +++ b/__tests__/integration/core-tester-cli/commands/debug/verify-second.test.ts @@ -1,9 +1,9 @@ import "jest-extended"; -import { VerifySecondSignatureCommand } from "../../src/commands/verify-second"; +import { VerifySecondSignatureCommand } from "../../../../../packages/core-tester-cli/src/commands/debug/verify-second-signature"; describe("Commands - Verify Second", () => { - const fixtureTransaction = require("../__fixtures__/transaction-second.json"); + const fixtureTransaction = require("../../__fixtures__/transaction-second.json"); it("should verify a second signature", async () => { expect( @@ -12,6 +12,8 @@ describe("Commands - Verify Second", () => { fixtureTransaction.serialized, "--publicKey", "03699e966b2525f9088a6941d8d94f7869964a000efe65783d78ac82e1199fe609", + "--network", + "devnet", ]), ).toBeTrue(); }); diff --git a/__tests__/integration/core-tester-cli/commands/debug/verify.test.ts b/__tests__/integration/core-tester-cli/commands/debug/verify.test.ts new file mode 100644 index 0000000000..f0df9a3aeb --- /dev/null +++ b/__tests__/integration/core-tester-cli/commands/debug/verify.test.ts @@ -0,0 +1,27 @@ +import "jest-extended"; + +import { VerifyCommand } from "../../../../../packages/core-tester-cli/src/commands/debug/verify"; + +describe("Commands - Verify", () => { + const fixtureBlock = require("../../__fixtures__/block.json"); + const fixtureTransaction = require("../../__fixtures__/transaction.json"); + + it("should verify a block", async () => { + expect( + await VerifyCommand.run(["--data", fixtureBlock.serializedFull, "--type", "block", "--network", "devnet"]), + ).toBeTrue(); + }); + + it("should verify a transaction", async () => { + expect( + await VerifyCommand.run([ + "--data", + fixtureTransaction.serialized, + "--type", + "transaction", + "--network", + "devnet", + ]), + ).toBeTrue(); + }); +}); diff --git a/__tests__/integration/core-tester-cli/commands/make/block.test.ts b/__tests__/integration/core-tester-cli/commands/make/block.test.ts new file mode 100644 index 0000000000..a2b9188123 --- /dev/null +++ b/__tests__/integration/core-tester-cli/commands/make/block.test.ts @@ -0,0 +1,39 @@ +import "jest-extended"; + +import { BlockCommand } from "../../../../../packages/core-tester-cli/dist/commands/make/block"; + +describe("make:block", () => { + it("should generate 1 block with default flags", async () => { + const blocks = await BlockCommand.run(["--network=unitnet"]); + + expect(blocks).toHaveLength(1); + + expect(blocks[0].generatorPublicKey).toBe("03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37"); + }); + + it("should generate 1 block with 10 transactions", async () => { + const blocks = await BlockCommand.run(["--network=unitnet", "--transactions=10"]); + + expect(blocks[0].transactions).toHaveLength(10); + }); + + it("should generate 10 blocks", async () => { + const blocks = await BlockCommand.run(["--network=unitnet", "--number=10"]); + + expect(blocks).toHaveLength(10); + }); + + it("should generate 10 blocks with 10 transactions", async () => { + const blocks = await BlockCommand.run(["--network=unitnet", "--transactions=10"]); + + for (const block of blocks) { + expect(block.transactions).toHaveLength(10); + } + }); + + it("should generate a block with a custom passphrase", async () => { + const blocks = await BlockCommand.run(["--network=unitnet", "--passphrase=123"]); + + expect(blocks[0].generatorPublicKey).toBe("03be686ed7f0539affbaf634f3bcc2b235e8e220e7be57e9397ab1c14c39137eb4"); + }); +}); diff --git a/__tests__/integration/core-tester-cli/commands/send/delegate-registration.test.ts b/__tests__/integration/core-tester-cli/commands/send/delegate-registration.test.ts new file mode 100644 index 0000000000..001d150181 --- /dev/null +++ b/__tests__/integration/core-tester-cli/commands/send/delegate-registration.test.ts @@ -0,0 +1,61 @@ +import { httpie } from "@arkecosystem/core-utils"; +import "jest-extended"; +import nock from "nock"; +import pokemon from "pokemon"; +import { DelegateRegistrationCommand } from "../../../../../packages/core-tester-cli/src/commands/send/delegate-registration"; +import { arkToSatoshi, captureTransactions, expectTransactions, toFlags } from "../../shared"; + +beforeEach(() => { + // Just passthru. We'll test the Command class logic in its own test file more thoroughly + nock("http://localhost:4003") + .get("/api/v2/node/configuration") + .thrice() + .reply(200, { data: { constants: {} } }); + + nock("http://localhost:4000") + .get("/config") + .thrice() + .reply(200, { data: { network: { name: "unitnet" } } }); + + jest.spyOn(httpie, "get"); + jest.spyOn(httpie, "post"); +}); + +afterEach(() => { + nock.cleanAll(); +}); + +describe("Commands - Delegate Registration", () => { + it("should register as delegate", async () => { + const opts = { + delegateFee: 1, + number: 1, + }; + + const expectedDelegateName = "mr_bojangles"; + // call to delegates/{publicKey}/voters returns zero delegates + nock("http://localhost:4003") + .get("/api/v2/delegates") + .reply(200, { + meta: { pageCount: 1 }, + data: [], + }); + jest.spyOn(pokemon, "random").mockImplementation(() => expectedDelegateName); + + const expectedTransactions = []; + captureTransactions(nock, expectedTransactions); + + await DelegateRegistrationCommand.run(toFlags(opts)); + + expect(httpie.post).toHaveBeenCalledTimes(2); + + expectTransactions(expectedTransactions, { + fee: arkToSatoshi(opts.delegateFee), + asset: { + delegate: { + username: expectedDelegateName, + }, + }, + }); + }); +}); diff --git a/__tests__/integration/core-tester-cli/commands/send/second-signature.test.ts b/__tests__/integration/core-tester-cli/commands/send/second-signature.test.ts new file mode 100644 index 0000000000..3df285e13f --- /dev/null +++ b/__tests__/integration/core-tester-cli/commands/send/second-signature.test.ts @@ -0,0 +1,50 @@ +import { httpie } from "@arkecosystem/core-utils"; +import "jest-extended"; +import nock from "nock"; +import { SecondSignatureRegistrationCommand } from "../../../../../packages/core-tester-cli/src/commands/send/second-signature-registration"; +import { arkToSatoshi, captureTransactions, expectTransactions, toFlags } from "../../shared"; + +beforeEach(() => { + // Just passthru. We'll test the Command class logic in its own test file more thoroughly + nock("http://localhost:4003") + .get("/api/v2/node/configuration") + .thrice() + .reply(200, { data: { constants: {} } }); + + nock("http://localhost:4000") + .get("/config") + .thrice() + .reply(200, { data: { network: { name: "unitnet" } } }); + + jest.spyOn(httpie, "get"); + jest.spyOn(httpie, "post"); +}); + +afterEach(() => { + nock.cleanAll(); +}); + +describe("Commands - Second signature", () => { + it("should apply second signature", async () => { + const opts = { + signatureFee: 1, + number: 1, + }; + + const expectedTransactions = []; + captureTransactions(nock, expectedTransactions); + + await SecondSignatureRegistrationCommand.run(toFlags(opts)); + + expect(httpie.post).toHaveBeenCalledTimes(2); + + expectTransactions(expectedTransactions, { + fee: arkToSatoshi(opts.signatureFee), + asset: { + signature: { + publicKey: expect.any(String), + }, + }, + }); + }); +}); diff --git a/__tests__/integration/core-tester-cli/commands/send/transfer.test.ts b/__tests__/integration/core-tester-cli/commands/send/transfer.test.ts new file mode 100644 index 0000000000..bcee5a8ef5 --- /dev/null +++ b/__tests__/integration/core-tester-cli/commands/send/transfer.test.ts @@ -0,0 +1,119 @@ +import { httpie } from "@arkecosystem/core-utils"; +import "jest-extended"; +import nock from "nock"; +import { TransferCommand } from "../../../../../packages/core-tester-cli/src/commands/send/transfer"; +import { arkToSatoshi, captureTransactions, expectTransactions, toFlags } from "../../shared"; + +beforeEach(() => { + // Just passthru. We'll test the Command class logic in its own test file more thoroughly + nock("http://localhost:4003") + .get("/api/v2/node/configuration") + .twice() + .reply(200, { data: { constants: {} } }); + + nock("http://localhost:4000") + .get("/config") + .twice() + .reply(200, { data: { network: { name: "unitnet" } } }); + + jest.spyOn(httpie, "post"); +}); + +afterEach(() => { + nock.cleanAll(); +}); + +describe("Commands - Transfer", () => { + it("should postTransactions using custom smartBridge value", async () => { + const expectedRecipientId = "DFyUhQW52sNB5PZdS7VD9HknwYrSNHPQDq"; + const expectedTransactionAmount = 2; + const expectedFee = 0.1; + const opts = { + amount: expectedTransactionAmount, + transferFee: expectedFee, + number: 1, + vendorField: "foo bar", + recipient: expectedRecipientId, + }; + + const expectedTransactions = []; + captureTransactions(nock, expectedTransactions); + + await TransferCommand.run(toFlags(opts)); + + expectTransactions(expectedTransactions, { + vendorField: "foo bar", + amount: arkToSatoshi(expectedTransactionAmount), + fee: arkToSatoshi(expectedFee), + recipientId: expectedRecipientId, + }); + }); + + it("should generate n transactions", async () => { + const expectedTxCount = 5; + const expectedRecipientId = "DFyUhQW52sNB5PZdS7VD9HknwYrSNHPQDq"; + const opts = { + amount: 2, + transferFee: 2, + number: expectedTxCount, + recipient: expectedRecipientId, + }; + + const expectedTransactions = []; + captureTransactions(nock, expectedTransactions); + + await TransferCommand.run(toFlags(opts)); + + expect(expectedTransactions).toHaveLength(expectedTxCount); + for (const t of expectedTransactions) { + expect(t.vendorField).toMatch(/Transaction \d/); + expect(t.amount).toBeDefined(); + expect(t.fee).toBeDefined(); + } + }); + + it("should send n transactions to specified recipient", async () => { + const expectedTxCount = 10; + const expectedRecipientId = "DFyUhQW52sNB5PZdS7VD9HknwYrSNHPQDq"; + const opts = { + amount: 2, + transferFee: 0.1, + number: expectedTxCount, + recipient: expectedRecipientId, + }; + + const expectedTransactions = []; + captureTransactions(nock, expectedTransactions); + + await TransferCommand.run(toFlags(opts)); + + expect(expectedTransactions).toHaveLength(expectedTxCount); + for (const t of expectedTransactions) { + expect(t.recipientId).toEqual(expectedRecipientId); + } + }); + + it("should sign with 2nd passphrase if specified", async () => { + const expectedTransactionAmount = 2; + const expectedFee = 0.1; + const expectedRecipientId = "DFyUhQW52sNB5PZdS7VD9HknwYrSNHPQDq"; + + const opts = { + amount: expectedTransactionAmount, + transferFee: expectedFee, + number: 1, + secondPassphrase: "she sells sea shells down by the sea shore", + recipient: expectedRecipientId, + }; + + const expectedTransactions = []; + captureTransactions(nock, expectedTransactions); + + await TransferCommand.run(toFlags(opts)); + + expect(expectedTransactions).toHaveLength(1); + for (const transaction of expectedTransactions) { + expect(transaction.secondSignature).toBeDefined(); + } + }); +}); diff --git a/__tests__/integration/core-tester-cli/commands/send/vote.test.ts b/__tests__/integration/core-tester-cli/commands/send/vote.test.ts new file mode 100644 index 0000000000..15ba15ee3a --- /dev/null +++ b/__tests__/integration/core-tester-cli/commands/send/vote.test.ts @@ -0,0 +1,80 @@ +import { httpie } from "@arkecosystem/core-utils"; +import "jest-extended"; +import nock from "nock"; +import { VoteCommand } from "../../../../../packages/core-tester-cli/src/commands/send/vote"; +import { arkToSatoshi, captureTransactions, expectTransactions, toFlags } from "../../shared"; + +beforeEach(() => { + // Just passthru. We'll test the Command class logic in its own test file more thoroughly + nock("http://localhost:4003") + .get("/api/v2/node/configuration") + .thrice() + .reply(200, { data: { constants: {} } }); + + nock("http://localhost:4000") + .get("/config") + .thrice() + .reply(200, { data: { network: { name: "unitnet" } } }); + + jest.spyOn(httpie, "get"); + jest.spyOn(httpie, "post"); +}); + +afterEach(() => { + nock.cleanAll(); + jest.restoreAllMocks(); +}); + +describe("Commands - Vote", () => { + it("should vote for specified delegate", async () => { + const expectedDelegate = "03f294777f7376e970b2bd4805b4a90c8449b5935d530bdb566d02800ac44a4c00"; + const opts = { + number: 1, + voteFee: 1, + delegate: expectedDelegate, + }; + + const expectedTransactions = []; + captureTransactions(nock, expectedTransactions); + + await VoteCommand.run(toFlags(opts)); + + expect(httpie.post).toHaveBeenCalledTimes(2); + + expectTransactions(expectedTransactions, { + fee: arkToSatoshi(opts.voteFee), + asset: { + votes: [`+${expectedDelegate}`], + }, + }); + }); + + it("should vote random delegate if non specified", async () => { + const expectedDelegate = "03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37"; + const opts = { + number: 1, + voteFee: 1, + }; + + nock("http://localhost:4003") + .get("/api/v2/delegates") + .reply(200, { + meta: { pageCount: 1 }, + data: [{ publicKey: expectedDelegate }], + }); + + const expectedTransactions = []; + captureTransactions(nock, expectedTransactions); + + await VoteCommand.run(toFlags(opts)); + + expect(httpie.post).toHaveBeenCalledTimes(2); + + expectTransactions(expectedTransactions, { + fee: arkToSatoshi(opts.voteFee), + asset: { + votes: [`+${expectedDelegate}`], + }, + }); + }); +}); diff --git a/__tests__/integration/core-tester-cli/shared.ts b/__tests__/integration/core-tester-cli/shared.ts new file mode 100644 index 0000000000..1b69b2244a --- /dev/null +++ b/__tests__/integration/core-tester-cli/shared.ts @@ -0,0 +1,31 @@ +import { bignumify, httpie } from "@arkecosystem/core-utils"; + +const defaultOpts = ["--skipProbing"]; + +export const toFlags = (opts: object): string[] => { + return Object.keys(opts) + .map(k => [`--${k}`, String(opts[k])]) + .reduce((a, b) => a.concat(b), defaultOpts); +}; + +export const arkToSatoshi = value => + bignumify(value) + .times(1e8) + .toFixed(); + +export const expectTransactions = (transactions, obj) => + expect(transactions).toEqual(expect.arrayContaining([expect.objectContaining(obj)])); + +export const captureTransactions = (nock, expectedTransactions) => { + nock("http://localhost:4003") + .post("/api/v2/transactions") + .thrice() + .reply(200, { data: {} }); + + // @ts-ignore + jest.spyOn(httpie, "post").mockImplementation((url, { body }) => { + for (const transaction of body.transactions) { + expectedTransactions.push(transaction); + } + }); +}; diff --git a/__tests__/integration/core-transaction-pool/__fixtures__/transactions.ts b/__tests__/integration/core-transaction-pool/__fixtures__/transactions.ts new file mode 100644 index 0000000000..bfbf35d37a --- /dev/null +++ b/__tests__/integration/core-transaction-pool/__fixtures__/transactions.ts @@ -0,0 +1,86 @@ +import { TransactionFactory } from "../../../helpers/transaction-factory"; +import { delegates } from "../../../utils/fixtures/unitnet/delegates"; + +export const transactions = { + dummy1: TransactionFactory.transfer("AFzQCx5YpGg5vKMBg4xbuYbqkhvMkKfKe5") + .withNetwork("unitnet") + .withPassphrase(delegates[0].passphrase) + .create()[0], + + dummy2: TransactionFactory.transfer("DFyDKsyvR4x9D9zrfEaPmeJxSniT5N5qY8", 10000000) + .withNetwork("unitnet") + .withFee(10000000) + .withPassphrase(delegates[0].passphrase) + .create()[0], + + dummy3: TransactionFactory.transfer("ANqvJEMZcmUpcKBC8xiP1TntVkJeuZ3Lw3") + .withNetwork("unitnet") + .withPassphrase(delegates[0].passphrase) + .create()[0], + + dummy4: TransactionFactory.transfer("AJ5eV59hu4xrbRCpoP3of7fEYWUteSVa8k") + .withNetwork("unitnet") + .withPassphrase(delegates[0].passphrase) + .create()[0], + + dummy5: TransactionFactory.transfer("ASvC1E9hMLfANTi63S2gUMvr7rVZYJBj3u") + .withNetwork("unitnet") + .withPassphrase(delegates[0].passphrase) + .create()[0], + + dummy6: TransactionFactory.transfer("Ac8utEr7XRebWRvArSBnbVoxbq6bXftAmL") + .withNetwork("unitnet") + .withPassphrase(delegates[0].passphrase) + .create()[0], + + dummy7: TransactionFactory.transfer("ANWEaVfvAh3VTyZNYcuFESUum1XBmAvAdj") + .withNetwork("unitnet") + .withPassphrase(delegates[0].passphrase) + .create()[0], + + dummy8: TransactionFactory.transfer("ALsZS24Dn4HYXwed5kAC5fKyB9BFzdmcSx") + .withNetwork("unitnet") + .withPassphrase(delegates[0].passphrase) + .create()[0], + + dummy9: TransactionFactory.transfer("ANuaLhRuBJhTcHao7kTfDcfsewLQGr7x5G") + .withNetwork("unitnet") + .withPassphrase(delegates[0].passphrase) + .create()[0], + + dummy10: TransactionFactory.transfer("DFyDKsyvR4x9D9zrfEaPmeJxSniT5N5qY8") + .withNetwork("unitnet") + .withPassphrase(delegates[1].passphrase) + .create()[0], + + dummyLarge1: TransactionFactory.transfer("AFzQCx5YpGg5vKMBg4xbuYbqkhvMkKfKe5") + .withNetwork("unitnet") + .withPassphrase(delegates[1].passphrase) + .create()[0], + + dummyLarge2: TransactionFactory.transfer("DFyDKsyvR4x9D9zrfEaPmeJxSniT5N5qY8") + .withNetwork("unitnet") + .withPassphrase(delegates[1].passphrase) + .create()[0], + + dynamicFeeNormalDummy1: TransactionFactory.transfer("AcjGpvDJEQdBVwspYsAs16B8Rv66zo7gyd") + .withNetwork("unitnet") + .withFee(280000) + .withPassphrase(delegates[1].passphrase) + .create()[0], + + dynamicFeeLowDummy2: TransactionFactory.transfer("AabMvWPVKbdTHRcGBpATq9TEMiMD5xeJh") + .withNetwork("unitnet") + .withPassphrase(delegates[1].passphrase) + .create()[0], + + dummyExp1: TransactionFactory.transfer("AFzQCx5YpGg5vKMBg4xbuYbqkhvMkKfKe5") + .withNetwork("unitnet") + .withPassphrase(delegates[1].passphrase) + .create()[0], + + dummyExp2: TransactionFactory.transfer("DFyDKsyvR4x9D9zrfEaPmeJxSniT5N5qY8") + .withNetwork("unitnet") + .withPassphrase(delegates[1].passphrase) + .create()[0], +}; diff --git a/packages/core-transaction-pool/__tests__/__support__/setup.ts b/__tests__/integration/core-transaction-pool/__support__/setup.ts similarity index 87% rename from packages/core-transaction-pool/__tests__/__support__/setup.ts rename to __tests__/integration/core-transaction-pool/__support__/setup.ts index 330882c231..5befa1dfb0 100644 --- a/packages/core-transaction-pool/__tests__/__support__/setup.ts +++ b/__tests__/integration/core-transaction-pool/__support__/setup.ts @@ -1,5 +1,5 @@ import { app } from "@arkecosystem/core-container"; -import { registerWithContainer, setUpContainer } from "../../../core-test-utils/src/helpers/container"; +import { registerWithContainer, setUpContainer } from "../../../utils/helpers/container"; jest.setTimeout(60000); @@ -40,7 +40,7 @@ export const setUpFull = async () => { network: "unitnet", }); - await registerWithContainer(require("../../src/plugin").plugin, options); + await registerWithContainer(require("../../../../packages/core-transaction-pool/src/plugin").plugin, options); // now registering the plugins that need to be registered after transaction pool // register p2p @@ -59,7 +59,7 @@ export const tearDown = async () => { }; export const tearDownFull = async () => { - await require("../../src/plugin").plugin.deregister(app, options); + await require("../../../../packages/core-transaction-pool/src/plugin").plugin.deregister(app, options); await require("@arkecosystem/core-p2p").plugin.deregister(app, {}); await require("@arkecosystem/core-blockchain").plugin.deregister(app, {}); diff --git a/__tests__/integration/core-transaction-pool/guard.test.ts b/__tests__/integration/core-transaction-pool/guard.test.ts new file mode 100644 index 0000000000..37eac86cdd --- /dev/null +++ b/__tests__/integration/core-transaction-pool/guard.test.ts @@ -0,0 +1,740 @@ +import { Container } from "@arkecosystem/core-interfaces"; +import { TransactionHandlerRegistry } from "@arkecosystem/core-transactions"; +import { crypto, ITransactionData, models } from "@arkecosystem/crypto"; +import bip39 from "bip39"; +import "jest-extended"; +import { config as localConfig } from "../../../packages/core-transaction-pool/src/config"; +import { TransactionFactory } from "../../helpers/transaction-factory"; +import { delegates, genesisBlock, wallets, wallets2ndSig } from "../../utils/fixtures/unitnet"; +import { generateWallets } from "../../utils/generators/wallets"; +import { setUpFull, tearDownFull } from "./__support__/setup"; + +const { Block } = models; + +let TransactionGuard; + +let container: Container.IContainer; +let guard; +let transactionPool; +let blockchain; + +beforeAll(async () => { + container = await setUpFull(); + + TransactionGuard = require("../../../packages/core-transaction-pool/src").TransactionGuard; + + transactionPool = container.resolvePlugin("transaction-pool"); + blockchain = container.resolvePlugin("blockchain"); + localConfig.init(transactionPool.options); +}); + +afterAll(async () => { + await tearDownFull(); +}); + +beforeEach(() => { + transactionPool.flush(); + guard = new TransactionGuard(transactionPool); +}); + +describe("Transaction Guard", () => { + describe("validate", () => { + it.each([false, true])( + "should not apply transactions for chained transfers involving cold wallets", + async inverseOrder => { + /* The logic here is we can't have a chained transfer A => B => C if B is a cold wallet. + A => B needs to be first confirmed (forged), then B can transfer to C + */ + + const satoshi = 10 ** 8; + // don't re-use the same delegate (need clean balance) + const delegate = inverseOrder ? delegates[8] : delegates[9]; + const delegateWallet = transactionPool.walletManager.findByAddress(delegate.address); + + const newWallets = generateWallets("unitnet", 2); + const poolWallets = newWallets.map(w => transactionPool.walletManager.findByAddress(w.address)); + + expect(+delegateWallet.balance).toBe(+delegate.balance); + poolWallets.forEach(w => { + expect(+w.balance).toBe(0); + }); + + const transfer0 = { + // transfer from delegate to wallet 0 + from: delegate, + to: newWallets[0], + amount: 100 * satoshi, + }; + const transfer1 = { + // transfer from wallet 0 to wallet 1 + from: newWallets[0], + to: newWallets[1], + amount: 55 * satoshi, + }; + const transfers = [transfer0, transfer1]; + if (inverseOrder) { + transfers.reverse(); + } + + for (const t of transfers) { + const transferTx = TransactionFactory.transfer(t.to.address, t.amount) + .withNetwork("unitnet") + .withPassphrase(t.from.passphrase) + .build()[0]; + + await guard.validate([transferTx.data]); + } + + // apply again transfer from 0 to 1 + const transfer = TransactionFactory.transfer(transfer1.to.address, transfer1.amount) + .withNetwork("unitnet") + .withPassphrase(transfer1.from.passphrase) + .build()[0]; + + await guard.validate([transfer.data]); + + const expectedError = { + message: '["Cold wallet is not allowed to send until receiving transaction is confirmed."]', + type: "ERR_APPLY", + }; + expect(guard.errors[transfer.id]).toContainEqual(expectedError); + + // check final balances + expect(+delegateWallet.balance).toBe(delegate.balance - (100 + 0.1) * satoshi); + expect(+poolWallets[0].balance).toBe(0); + expect(+poolWallets[1].balance).toBe(0); + }, + ); + + it("should not apply the tx to the balance of the sender & recipient with dyn fee < min fee", async () => { + const delegate0 = delegates[14]; + const { publicKey } = crypto.getKeys(bip39.generateMnemonic()); + const newAddress = crypto.getAddress(publicKey); + + const delegateWallet = transactionPool.walletManager.findByPublicKey(delegate0.publicKey); + const newWallet = transactionPool.walletManager.findByPublicKey(publicKey); + + expect(+delegateWallet.balance).toBe(+delegate0.balance); + expect(+newWallet.balance).toBe(0); + + const amount1 = 123 * 10 ** 8; + const fee = 10; + const transfers = TransactionFactory.transfer(newAddress, amount1) + .withNetwork("unitnet") + .withFee(fee) + .withPassphrase(delegate0.secret) + .build(); + + await guard.validate(transfers.map(tx => tx.data)); + + expect(+delegateWallet.balance).toBe(+delegate0.balance); + expect(+newWallet.balance).toBe(0); + }); + + it("should update the balance of the sender & recipient with dyn fee > min fee", async () => { + const delegate1 = delegates[1]; + const { publicKey } = crypto.getKeys(bip39.generateMnemonic()); + const newAddress = crypto.getAddress(publicKey); + + const delegateWallet = transactionPool.walletManager.findByPublicKey(delegate1.publicKey); + const newWallet = transactionPool.walletManager.findByPublicKey(publicKey); + + expect(+delegateWallet.balance).toBe(+delegate1.balance); + expect(+newWallet.balance).toBe(0); + + const amount1 = +delegateWallet.balance / 2; + const fee = 0.1 * 10 ** 8; + const transfers = TransactionFactory.transfer(newAddress, amount1) + .withNetwork("unitnet") + .withFee(fee) + .withPassphrase(delegate1.secret) + .build(); + await guard.validate(transfers.map(tx => tx.data)); + expect(guard.errors).toEqual({}); + + // simulate forged transaction + const transactionHandler = TransactionHandlerRegistry.get(transfers[0].type); + transactionHandler.applyToRecipient(transfers[0], newWallet); + + expect(+delegateWallet.balance).toBe(+delegate1.balance - amount1 - fee); + expect(+newWallet.balance).toBe(amount1); + }); + + it("should update the balance of the sender & recipient with multiple transactions type", async () => { + const delegate2 = delegates[2]; + const newWalletPassphrase = bip39.generateMnemonic(); + const { publicKey } = crypto.getKeys(newWalletPassphrase); + const newAddress = crypto.getAddress(publicKey); + + const delegateWallet = transactionPool.walletManager.findByPublicKey(delegate2.publicKey); + const newWallet = transactionPool.walletManager.findByPublicKey(publicKey); + + expect(+delegateWallet.balance).toBe(+delegate2.balance); + expect(+newWallet.balance).toBe(0); + expect(guard.errors).toEqual({}); + + const amount1 = +delegateWallet.balance / 2; + const fee = 0.1 * 10 ** 8; + const voteFee = 10 ** 8; + const delegateRegFee = 25 * 10 ** 8; + const signatureFee = 5 * 10 ** 8; + const transfers = TransactionFactory.transfer(newAddress, amount1) + .withNetwork("unitnet") + .withFee(fee) + .withPassphrase(delegate2.secret) + .build(); + const votes = TransactionFactory.vote(delegate2.publicKey) + .withNetwork("unitnet") + .withPassphrase(newWalletPassphrase) + .build(); + const delegateRegs = TransactionFactory.delegateRegistration() + .withNetwork("unitnet") + .withPassphrase(newWalletPassphrase) + .build(); + const signatures = TransactionFactory.secondSignature() + .withNetwork("unitnet") + .withPassphrase(newWalletPassphrase) + .build(); + + // Index wallets to not encounter cold wallet error + const allTransactions = [...transfers, ...votes, ...delegateRegs, ...signatures]; + + allTransactions.forEach(transaction => { + container.resolvePlugin("database").walletManager.findByPublicKey(transaction.data.senderPublicKey); + }); + + // first validate the 1st transfer so that new wallet is updated with the amount + await guard.validate(transfers.map(tx => tx.data)); + + // simulate forged transaction + const transactionHandler = TransactionHandlerRegistry.get(transfers[0].type); + transactionHandler.applyToRecipient(transfers[0], newWallet); + + expect(guard.errors).toEqual({}); + expect(+newWallet.balance).toBe(amount1); + + // reset guard, if not the 1st transaction will still be in this.accept and mess up + guard = new TransactionGuard(transactionPool); + + await guard.validate([votes[0].data, delegateRegs[0].data, signatures[0].data]); + + expect(guard.errors).toEqual({}); + expect(+delegateWallet.balance).toBe(+delegate2.balance - amount1 - fee); + expect(+newWallet.balance).toBe(amount1 - voteFee - delegateRegFee - signatureFee); + }); + + it("should not accept transaction in excess", async () => { + const delegate3 = delegates[3]; + const newWalletPassphrase = bip39.generateMnemonic(); + const { publicKey } = crypto.getKeys(newWalletPassphrase); + const newAddress = crypto.getAddress(publicKey); + + const delegateWallet = transactionPool.walletManager.findByPublicKey(delegate3.publicKey); + const newWallet = transactionPool.walletManager.findByPublicKey(publicKey); + + // Make sure it is not considered a cold wallet + container.resolvePlugin("database").walletManager.reindex(newWallet); + + expect(+delegateWallet.balance).toBe(+delegate3.balance); + expect(+newWallet.balance).toBe(0); + + // first, transfer coins to new wallet so that we can test from it then + const amount1 = 1000 * 10 ** 8; + const fee = 0.1 * 10 ** 8; + const transfers1 = TransactionFactory.transfer(newAddress, amount1) + .withNetwork("unitnet") + .withPassphrase(delegate3.secret) + .build(); + await guard.validate(transfers1.map(tx => tx.data)); + + // simulate forged transaction + const transactionHandler = TransactionHandlerRegistry.get(transfers1[0].type); + transactionHandler.applyToRecipient(transfers1[0], newWallet); + + expect(+delegateWallet.balance).toBe(+delegate3.balance - amount1 - fee); + expect(+newWallet.balance).toBe(amount1); + + // transfer almost everything from new wallet so that we don't have enough for any other transaction + const amount2 = 999 * 10 ** 8; + const transfers2 = TransactionFactory.transfer(delegate3.address, amount2) + .withNetwork("unitnet") + .withPassphrase(newWalletPassphrase) + .build(); + await guard.validate(transfers2.map(tx => tx.data)); + + // simulate forged transaction + transactionHandler.applyToRecipient(transfers2[0], delegateWallet); + + expect(+newWallet.balance).toBe(amount1 - amount2 - fee); + + // now try to validate any other transaction - should not be accepted because in excess + const transferAmount = 0.5 * 10 ** 8; + const transferDynFee = 0.5 * 10 ** 8; + const allTransactions = [ + TransactionFactory.transfer(delegate3.address, transferAmount) + .withNetwork("unitnet") + .withFee(transferDynFee) + .withPassphrase(newWalletPassphrase) + .build(), + TransactionFactory.secondSignature() + .withNetwork("unitnet") + .withPassphrase(newWalletPassphrase) + .build(), + TransactionFactory.vote(delegate3.publicKey) + .withNetwork("unitnet") + .withPassphrase(newWalletPassphrase) + .build(), + TransactionFactory.delegateRegistration() + .withNetwork("unitnet") + .withPassphrase(newWalletPassphrase) + .build(), + ]; + + for (const transaction of allTransactions) { + await guard.validate(transaction.map(tx => tx.data)); + + const errorExpected = [ + { + message: `["Insufficient balance in the wallet."]`, + type: "ERR_APPLY", + }, + ]; + expect(guard.errors[transaction[0].id]).toEqual(errorExpected); + + expect(+delegateWallet.balance).toBe(+delegate3.balance - amount1 - fee + amount2); + expect(+newWallet.balance).toBe(amount1 - amount2 - fee); + } + }); + + it("should not validate 2 double spending transactions", async () => { + const amount = 245098000000000 - 5098000000000; // a bit less than the delegates' balance + const transactions = TransactionFactory.transfer(delegates[1].address, amount) + .withNetwork("unitnet") + .withPassphrase(delegates[0].secret) + .create(2); + + const result = await guard.validate(transactions); + + expect(result.errors[transactions[1].id]).toEqual([ + { + message: `["Insufficient balance in the wallet."]`, + type: "ERR_APPLY", + }, + ]); + }); + + it.each([3, 5, 8])("should validate emptying wallet with %i transactions", async txNumber => { + // use txNumber so that we use a different delegate for each test case + const sender = delegates[txNumber]; + const senderWallet = transactionPool.walletManager.findByPublicKey(sender.publicKey); + const receivers = generateWallets("unitnet", 2); + const amountPlusFee = Math.floor(senderWallet.balance / txNumber); + const lastAmountPlusFee = senderWallet.balance - (txNumber - 1) * amountPlusFee; + const transferFee = 10000000; + + const transactions = TransactionFactory.transfer(receivers[0].address, amountPlusFee - transferFee) + .withNetwork("unitnet") + .withPassphrase(sender.secret) + .create(txNumber - 1); + const lastTransaction = TransactionFactory.transfer(receivers[1].address, lastAmountPlusFee - transferFee) + .withNetwork("unitnet") + .withPassphrase(sender.secret) + .create(); + // we change the receiver in lastTransaction to prevent having 2 exact + // same transactions with same id (if not, could be same as transactions[0]) + + const result = await guard.validate(transactions.concat(lastTransaction)); + + expect(result.errors).toEqual(null); + }); + + it.each([3, 5, 8])( + "should not validate emptying wallet with %i transactions when the last one is 1 satoshi too much", + async txNumber => { + // use txNumber + 1 so that we don't use the same delegates as the above test + const sender = delegates[txNumber + 1]; + const receivers = generateWallets("unitnet", 2); + const amountPlusFee = Math.floor(sender.balance / txNumber); + const lastAmountPlusFee = sender.balance - (txNumber - 1) * amountPlusFee + 1; + const transferFee = 10000000; + + const transactions = TransactionFactory.transfer(receivers[0].address, amountPlusFee - transferFee) + .withNetwork("unitnet") + .withPassphrase(sender.secret) + .create(txNumber - 1); + const lastTransaction = TransactionFactory.transfer( + receivers[1].address, + lastAmountPlusFee - transferFee, + ) + .withNetwork("unitnet") + .withPassphrase(sender.secret) + .create(); + // we change the receiver in lastTransaction to prevent having 2 + // exact same transactions with same id (if not, could be same as transactions[0]) + + const allTransactions = transactions.concat(lastTransaction); + + const result = await guard.validate(allTransactions); + + expect(Object.keys(result.errors).length).toBe(1); + expect(result.errors[lastTransaction[0].id]).toEqual([ + { + message: `["Insufficient balance in the wallet."]`, + type: "ERR_APPLY", + }, + ]); + }, + ); + + it("should compute transaction id and therefore validate transactions with wrong id", async () => { + const sender = delegates[21]; + const receivers = generateWallets("unitnet", 1); + + const transactions: ITransactionData[] = TransactionFactory.transfer(receivers[0].address, 50) + .withNetwork("unitnet") + .withPassphrase(sender.secret) + .create(); + const transactionId = transactions[0].id; + transactions[0].id = "a".repeat(64); + + const result = await guard.validate(transactions); + expect(result.accept).toEqual([transactionId]); + expect(result.broadcast).toEqual([transactionId]); + expect(result.errors).toBeNull(); + }); + + it("should not validate when multiple wallets register the same username in the same transaction payload", async () => { + const delegateRegistrations = [ + TransactionFactory.delegateRegistration("test_delegate") + .withNetwork("unitnet") + .withPassphrase(wallets[14].passphrase) + .build()[0], + TransactionFactory.delegateRegistration("test_delegate") + .withNetwork("unitnet") + .withPassphrase(wallets[15].passphrase) + .build()[0], + ]; + + const result = await guard.validate(delegateRegistrations.map(transaction => transaction.data)); + expect(result.invalid).toEqual(delegateRegistrations.map(transaction => transaction.id)); + + delegateRegistrations.forEach(tx => { + expect(guard.errors[tx.id]).toEqual([ + { + type: "ERR_CONFLICT", + message: `Multiple delegate registrations for "${ + tx.data.asset.delegate.username + }" in transaction payload`, + }, + ]); + }); + + const wallet1 = transactionPool.walletManager.findByPublicKey(wallets[14].keys.publicKey); + const wallet2 = transactionPool.walletManager.findByPublicKey(wallets[15].keys.publicKey); + + expect(wallet1.username).toBe(null); + expect(wallet2.username).toBe(null); + }); + + describe("Sign a transaction then change some fields shouldn't pass validation", () => { + it("should not validate when changing fields after signing - transfer", async () => { + const sender = delegates[21]; + const notSender = delegates[22]; + + // the fields we are going to modify after signing + const modifiedFields = [ + { timestamp: 111111 }, + { amount: 111 }, + { fee: 1111111 }, + { recipientId: "ANqvJEMZcmUpcKBC8xiP1TntVkJeuZ3Lw3" }, + // we are also going to modify senderPublicKey but separately + ]; + + // generate transfers, "simple" and 2nd signed + const transfers = TransactionFactory.transfer("AFzQCx5YpGg5vKMBg4xbuYbqkhvMkKfKe5", 50) + .withNetwork("unitnet") + .withPassphrase(sender.secret) + .create(modifiedFields.length + 1); // + 1 because we will use it to modify senderPublicKey separately + const transfers2ndSigned = TransactionFactory.transfer("AFzQCx5YpGg5vKMBg4xbuYbqkhvMkKfKe5", 50) + .withNetwork("unitnet") + .withPassphrasePair(wallets2ndSig[0]) + .create(modifiedFields.length + 1); // + 1 because we will use it to modify senderPublicKey separately + + // modify transaction fields and try to validate + const modifiedTransactions = [ + ...modifiedFields.map((objField, index) => Object.assign({}, transfers[index], objField)), + Object.assign({}, transfers[transfers.length - 1], { senderPublicKey: notSender.publicKey }), + ...modifiedFields.map((objField, index) => Object.assign({}, transfers2ndSigned[index], objField)), + Object.assign({}, transfers2ndSigned[transfers2ndSigned.length - 1], { + senderPublicKey: wallets2ndSig[1].keys.publicKey, + }), + ]; + const result = await guard.validate(modifiedTransactions); + + const expectedErrors = [ + ...[...transfers, ...transfers2ndSigned].map(transfer => [ + transfer.id, + "ERR_BAD_DATA", + "Transaction didn't pass the verification process.", + ]), + ]; + + expect( + Object.keys(result.errors).map(id => [id, result.errors[id][0].type, result.errors[id][0].message]), + ).toEqual(expectedErrors); + expect(result.invalid).toEqual(modifiedTransactions.map(transaction => transaction.id)); + expect(result.accept).toEqual([]); + expect(result.broadcast).toEqual([]); + }); + + it("should not validate when changing fields after signing - delegate registration", async () => { + // the fields we are going to modify after signing + const modifiedFieldsDelReg = [ + { + timestamp: 111111, + }, + { + fee: 1111111, + }, + // we are also going to modify senderPublicKey but separately + ]; + + // generate delegate registrations, "simple" and 2nd signed + const delegateRegs = []; + for (const wallet of wallets.slice(0, modifiedFieldsDelReg.length + 1)) { + delegateRegs.push( + TransactionFactory.delegateRegistration() + .withNetwork("unitnet") + .withPassphrase(wallet.passphrase) + .create()[0], + ); + } + + const delegateRegs2ndSigned = []; + for (const wallet of wallets2ndSig.slice(0, modifiedFieldsDelReg.length + 1)) { + delegateRegs2ndSigned.push( + TransactionFactory.delegateRegistration() + .withNetwork("unitnet") + .withPassphrasePair(wallet) + .create()[0], + ); + } + + // console.log(delegateRegs.map(d => ({ id: d.id, username: d.asset.delegate }))); + + // modify transaction fields and try to validate + const modifiedTransactions = [ + ...modifiedFieldsDelReg.map((objField, index) => Object.assign({}, delegateRegs[index], objField)), + Object.assign({}, delegateRegs[delegateRegs.length - 1], { + senderPublicKey: wallets[50].keys.publicKey, + }), + ...modifiedFieldsDelReg.map((objField, index) => + Object.assign({}, delegateRegs2ndSigned[index], objField), + ), + Object.assign({}, delegateRegs2ndSigned[delegateRegs2ndSigned.length - 1], { + senderPublicKey: wallets2ndSig[50].keys.publicKey, + }), + ]; + const result = await guard.validate(modifiedTransactions); + + const expectedErrors = [ + ...[...delegateRegs, ...delegateRegs2ndSigned].map(transfer => [ + transfer.id, + "ERR_BAD_DATA", + "Transaction didn't pass the verification process.", + ]), + ]; + + expect( + Object.keys(result.errors).map(id => [id, result.errors[id][0].type, result.errors[id][0].message]), + ).toEqual(expectedErrors); + expect(result.invalid).toEqual(modifiedTransactions.map(transaction => transaction.id)); + expect(result.accept).toEqual([]); + expect(result.broadcast).toEqual([]); + }); + + it("should not validate when changing fields after signing - vote", async () => { + // the fields we are going to modify after signing + const modifiedFieldsVote = [ + { timestamp: 111111 }, + { fee: 1111111 }, + // we are also going to modify senderPublicKey but separately + ]; + + // generate votes, "simple" and 2nd signed + const votes = []; + for (const wallet of wallets.slice(0, modifiedFieldsVote.length + 1)) { + votes.push( + TransactionFactory.vote(delegates[21].publicKey) + .withNetwork("unitnet") + .withPassphrase(wallet.passphrase) + .create()[0], + ); + } + + const votes2ndSigned = []; + for (const wallet of wallets2ndSig.slice(0, modifiedFieldsVote.length + 1)) { + votes2ndSigned.push( + TransactionFactory.vote(delegates[21].publicKey) + .withNetwork("unitnet") + .withPassphrasePair(wallet) + .create()[0], + ); + } + + // modify transaction fields and try to validate + const modifiedTransactions = [ + ...modifiedFieldsVote.map((objField, index) => Object.assign({}, votes[index], objField)), + Object.assign({}, votes[votes.length - 1], { senderPublicKey: wallets[50].keys.publicKey }), + ...modifiedFieldsVote.map((objField, index) => Object.assign({}, votes2ndSigned[index], objField)), + Object.assign({}, votes2ndSigned[votes2ndSigned.length - 1], { + senderPublicKey: wallets2ndSig[50].keys.publicKey, + }), + ]; + const result = await guard.validate(modifiedTransactions); + + const expectedErrors = [ + ...votes.map(tx => [tx.id, "ERR_BAD_DATA", "Transaction didn't pass the verification process."]), + ...votes2ndSigned.map(tx => [ + tx.id, + "ERR_BAD_DATA", + "Transaction didn't pass the verification process.", + ]), + ]; + + expect( + Object.keys(result.errors).map(id => [id, result.errors[id][0].type, result.errors[id][0].message]), + ).toEqual(expectedErrors); + expect(result.invalid).toEqual(modifiedTransactions.map(transaction => transaction.id)); + expect(result.accept).toEqual([]); + expect(result.broadcast).toEqual([]); + }); + + it("should not validate when changing fields after signing - 2nd signature registration", async () => { + // the fields we are going to modify after signing + const modifiedFields2ndSig = [ + { timestamp: 111111 }, + { fee: 1111111 }, + { senderPublicKey: wallets[50].keys.publicKey }, + ]; + + const secondSigs = []; + + for (const wallet of wallets.slice(0, modifiedFields2ndSig.length)) { + secondSigs.push( + TransactionFactory.secondSignature(wallet.passphrase) + .withNetwork("unitnet") + .withPassphrase(wallet.passphrase) + .create()[0], + ); + } + + const modifiedTransactions = modifiedFields2ndSig.map((objField, index) => + Object.assign({}, secondSigs[index], objField), + ); + const result = await guard.validate(modifiedTransactions); + + expect( + Object.keys(result.errors).map(id => [id, result.errors[id][0].type, result.errors[id][0].message]), + ).toEqual( + secondSigs.map(tx => [tx.id, "ERR_BAD_DATA", "Transaction didn't pass the verification process."]), + ); + expect(result.invalid).toEqual(modifiedTransactions.map(transaction => transaction.id)); + expect(result.accept).toEqual([]); + expect(result.broadcast).toEqual([]); + }); + }); + + describe("Transaction replay shouldn't pass validation", () => { + afterEach(async () => blockchain.removeBlocks(blockchain.getLastHeight() - 1)); // resets to height 1 + + const addBlock = async transactions => { + // makes blockchain accept a new block with the transactions specified + const block = { + id: "17882607875259085966", + version: 0, + timestamp: 46583330, + height: 2, + reward: 0, + previousBlock: genesisBlock.id, + numberOfTransactions: 1, + transactions, + totalAmount: transactions.reduce((acc, curr) => acc + curr.amount), + totalFee: transactions.reduce((acc, curr) => acc + curr.fee), + payloadLength: 0, + payloadHash: genesisBlock.payloadHash, + generatorPublicKey: delegates[0].publicKey, + blockSignature: + "3045022100e7385c6ea42bd950f7f6ab8c8619cf2f66a41d8f8f185b0bc99af032cb25f30d02200b6210176a6cedfdcbe483167fd91c21d740e0e4011d24d679c601fdd46b0de9", + createdAt: "2019-07-11T16:48:50.550Z", + }; + const blockVerified = new Block(block); + blockVerified.verification.verified = true; + + await blockchain.processBlock(blockVerified, () => null); + }; + const forgedErrorMessage = id => ({ + [id]: [ + { + message: "Already forged.", + type: "ERR_FORGED", + }, + ], + }); + + it("should not validate an already forged transaction", async () => { + const transfers = TransactionFactory.transfer(wallets[1].address, 11) + .withNetwork("unitnet") + .withPassphrase(wallets[0].passphrase) + .create(); + await addBlock(transfers); + + const result = await guard.validate(transfers); + + expect(result.errors).toEqual(forgedErrorMessage(transfers[0].id)); + }); + + it("should not validate an already forged transaction - trying to tweak tx id", async () => { + const transfers = TransactionFactory.transfer(wallets[1].address, 11) + .withNetwork("unitnet") + .withPassphrase(wallets[0].passphrase) + .create(); + await addBlock(transfers); + + const realTransferId = transfers[0].id; + transfers[0].id = "c".repeat(64); + + const result = await guard.validate(transfers); + + expect(result.errors).toEqual(forgedErrorMessage(realTransferId)); + }); + }); + }); + + describe("__cacheTransactions", () => { + it("should add transactions to cache", () => { + const transactions = TransactionFactory.transfer(wallets[11].address, 35) + .withNetwork("unitnet") + .withPassphrase(wallets[10].passphrase) + .build(); + expect(guard.__cacheTransactions(transactions.map(tx => tx.data))).toEqual(transactions.map(tx => tx.data)); + }); + + it("should not add a transaction already in cache and add it as an error", () => { + const transactions = TransactionFactory.transfer(wallets[12].address, 35) + .withNetwork("unitnet") + .withPassphrase(wallets[11].passphrase) + .build(); + expect(guard.__cacheTransactions(transactions.map(tx => tx.data))).toEqual(transactions.map(tx => tx.data)); + expect(guard.__cacheTransactions([transactions[0].data])).toEqual([]); + expect(guard.errors).toEqual({ + [transactions[0].id]: [ + { + message: "Already in cache.", + type: "ERR_DUPLICATE", + }, + ], + }); + }); + }); +}); diff --git a/packages/core-transaction-pool/__tests__/pool-wallet-manager.test.ts b/__tests__/integration/core-transaction-pool/pool-wallet-manager.test.ts similarity index 71% rename from packages/core-transaction-pool/__tests__/pool-wallet-manager.test.ts rename to __tests__/integration/core-transaction-pool/pool-wallet-manager.test.ts index 16a95097d6..59d504ad69 100644 --- a/packages/core-transaction-pool/__tests__/pool-wallet-manager.test.ts +++ b/__tests__/integration/core-transaction-pool/pool-wallet-manager.test.ts @@ -1,12 +1,13 @@ import { Blockchain, Container, Database } from "@arkecosystem/core-interfaces"; -import { generators } from "@arkecosystem/core-test-utils"; -import { delegates, genesisBlock, wallets } from "@arkecosystem/core-test-utils/src/fixtures/unitnet"; +import { TransactionHandlerRegistry } from "@arkecosystem/core-transactions"; import { crypto, models } from "@arkecosystem/crypto"; import bip39 from "bip39"; +import { TransactionFactory } from "../../helpers/transaction-factory"; +import { delegates, genesisBlock, wallets } from "../../utils/fixtures/unitnet"; +import { generateWallets } from "../../utils/generators/wallets"; import { setUpFull, tearDownFull } from "./__support__/setup"; const { Block } = models; -const { generateTransfers, generateWallets, generateDelegateRegistration, generateVote } = generators; const satoshi = 10 ** 8; let container: Container.IContainer; @@ -17,7 +18,7 @@ let blockchain: Blockchain.IBlockchain; beforeAll(async () => { container = await setUpFull(); - PoolWalletManager = require("../src").PoolWalletManager; + PoolWalletManager = require("../../../packages/core-transaction-pool/src").PoolWalletManager; poolWalletManager = new PoolWalletManager(); blockchain = container.resolvePlugin("blockchain"); }); @@ -28,21 +29,29 @@ afterAll(async () => { describe("canApply", () => { it("should add an error for delegate registration when username is already taken", () => { - const delegateReg = generateDelegateRegistration("unitnet", wallets[11].passphrase, 1, false, "genesis_11")[0]; + const delegateReg = TransactionFactory.delegateRegistration("genesis_11") + .withNetwork("unitnet") + .withPassphrase(wallets[11].passphrase) + .build()[0]; const errors = []; expect(poolWalletManager.canApply(delegateReg, errors)).toBeFalse(); - expect(errors).toEqual([`Can't apply transaction ${delegateReg.id}: delegate name already taken.`]); + expect(errors).toEqual([ + `Failed to apply transaction, because the username '${ + delegateReg.data.asset.delegate.username + }' is already registered.`, + ]); }); it("should add an error when voting for a delegate that doesn't exist", () => { - const vote = generateVote("unitnet", wallets[11].passphrase, wallets[12].keys.publicKey, 1)[0]; + const vote = TransactionFactory.vote(wallets[12].keys.publicKey) + .withNetwork("unitnet") + .withPassphrase(wallets[11].passphrase) + .build()[0]; const errors = []; expect(poolWalletManager.canApply(vote, errors)).toBeFalse(); - expect(errors).toEqual([ - `Can't apply transaction ${vote.id}: delegate +${wallets[12].keys.publicKey} does not exist.`, - ]); + expect(errors).toEqual([`Failed to apply transaction, because only delegates can be voted.`]); }); }); @@ -60,9 +69,13 @@ describe("applyPoolTransactionToSender", () => { expect(+newWallet.balance).toBe(0); const amount1 = 123 * 10 ** 8; - const transfer = generateTransfers("unitnet", delegate0.secret, newAddress, amount1, 1)[0]; + const transfer = TransactionFactory.transfer(newAddress, amount1) + .withNetwork("unitnet") + .withPassphrase(delegate0.secret) + .build()[0]; - delegateWallet.applyTransactionToSender(transfer); + const transactionHandler = TransactionHandlerRegistry.get(transfer.type); + transactionHandler.applyToSender(transfer, delegateWallet); expect(+delegateWallet.balance).toBe(+delegate0.balance - amount1 - 0.1 * 10 ** 8); expect(newWallet.balance.isZero()).toBeTrue(); @@ -81,9 +94,14 @@ describe("applyPoolTransactionToSender", () => { const amount1 = 123 * 10 ** 8; const fee = 10; - const transfer = generateTransfers("unitnet", delegate0.secret, newAddress, amount1, 1, false, fee)[0]; + const transfer = TransactionFactory.transfer(newAddress, amount1) + .withNetwork("unitnet") + .withFee(fee) + .withPassphrase(delegate0.secret) + .build()[0]; - delegateWallet.applyTransactionToSender(transfer); + const transactionHandler = TransactionHandlerRegistry.get(transfer.type); + transactionHandler.applyToSender(transfer, delegateWallet); expect(+delegateWallet.balance).toBe(+delegate0.balance - amount1 - fee); expect(newWallet.balance.isZero()).toBeTrue(); @@ -117,30 +135,31 @@ describe("applyPoolTransactionToSender", () => { ]; transfers.forEach(t => { - const transfer = generateTransfers("unitnet", t.from.passphrase, t.to.address, t.amount, 1)[0]; + const transfer = TransactionFactory.transfer(t.to.address, t.amount) + .withNetwork("unitnet") + .withPassphrase(t.from.passphrase) + .build()[0]; + const transactionHandler = TransactionHandlerRegistry.get(transfer.type); // This is normally refused because it's a cold wallet, but since we want // to test if chained transfers are refused, pretent it is not a cold wallet. container .resolvePlugin("database") - .walletManager.findByPublicKey(transfer.senderPublicKey); + .walletManager.findByPublicKey(transfer.data.senderPublicKey); const errors = []; if (poolWalletManager.canApply(transfer, errors)) { - poolWalletManager.findByPublicKey(transfer.senderPublicKey).applyTransactionToSender(transfer); + const senderWallet = poolWalletManager.findByPublicKey(transfer.data.senderPublicKey); + transactionHandler.applyToSender(transfer, senderWallet); expect(t.from).toBe(delegate); } else { expect(t.from).toBe(walletsGen[0]); - expect(JSON.stringify(errors)).toEqual( - `["[PoolWalletManager] Can't apply transaction id:${transfer.id} from sender:${ - t.from.address - }","Insufficient balance in the wallet"]`, - ); + expect(errors).toEqual(["Insufficient balance in the wallet."]); } (container.resolvePlugin("database").walletManager as any).forgetByPublicKey( - transfer.publicKey, + transfer.data.senderPublicKey, ); }); @@ -164,14 +183,10 @@ describe("Apply transactions and block rewards to wallets on new block", () => { const wallet = generateWallets("unitnet", 1)[0]; const transferAmount = 1234; const transferDelegate = delegates[4]; - const transfer = generateTransfers( - "unitnet", - transferDelegate.passphrase, - wallet.address, - transferAmount, - 1, - true, - )[0]; + const transfer = TransactionFactory.transfer(wallet.address, transferAmount) + .withNetwork("unitnet") + .withPassphrase(transferDelegate.passphrase) + .create()[0]; const totalFee = 0.1 * satoshi; const blockWithReward = { diff --git a/__tests__/integration/core-vote-report/__support__/setup.ts b/__tests__/integration/core-vote-report/__support__/setup.ts new file mode 100644 index 0000000000..a6c5e586c2 --- /dev/null +++ b/__tests__/integration/core-vote-report/__support__/setup.ts @@ -0,0 +1,20 @@ +import { app } from "@arkecosystem/core-container"; +import { defaults } from "../../../../packages/core-vote-report/src/defaults"; +import { startServer } from "../../../../packages/core-vote-report/src/server"; +import { setUpContainer } from "../../../utils/helpers/container"; + +jest.setTimeout(60000); + +let server; +export async function setUp() { + await setUpContainer({ + exit: "@arkecosystem/core-blockchain", + }); + + server = await startServer(defaults); +} + +export async function tearDown() { + await server.stop(); + await app.tearDown(); +} diff --git a/packages/core-vote-report/__tests__/server.test.ts b/__tests__/integration/core-vote-report/server.test.ts similarity index 56% rename from packages/core-vote-report/__tests__/server.test.ts rename to __tests__/integration/core-vote-report/server.test.ts index f81c4cc817..3a0fa27426 100644 --- a/packages/core-vote-report/__tests__/server.test.ts +++ b/__tests__/integration/core-vote-report/server.test.ts @@ -1,4 +1,4 @@ -import axios from "axios"; +import got from "got"; import "jest-extended"; import { setUp, tearDown } from "./__support__/setup"; @@ -12,9 +12,9 @@ afterAll(async () => { describe("Server", () => { it("should render the page", async () => { - const response = await axios.get("http://localhost:4006/"); + const { body, statusCode } = await got.get("http://localhost:4006/"); - expect(response.status).toBe(200); - expect(response.data).toContain("Top 51 Delegates Stats"); + expect(statusCode).toBe(200); + expect(body).toContain("Top 51 Delegates Stats"); }); }); diff --git a/__tests__/integration/core-webhooks/__support__/setup.ts b/__tests__/integration/core-webhooks/__support__/setup.ts new file mode 100644 index 0000000000..14e155b2e7 --- /dev/null +++ b/__tests__/integration/core-webhooks/__support__/setup.ts @@ -0,0 +1,26 @@ +import { app } from "@arkecosystem/core-container"; +import { tmpdir } from "os"; +import { database } from "../../../../packages/core-webhooks/src/database"; +import { startServer } from "../../../../packages/core-webhooks/src/server"; +import { setUpContainer } from "../../../utils/helpers/container"; + +export async function setUp() { + process.env.CORE_PATH_CACHE = tmpdir(); + process.env.CORE_WEBHOOKS_ENABLED = "true"; + + await setUpContainer({ + exit: "@arkecosystem/core-logger-pino", + }); + + database.make(); + + await startServer({ + host: process.env.CORE_WEBHOOKS_HOST || "0.0.0.0", + port: process.env.CORE_WEBHOOKS_PORT || 4004, + whitelist: ["127.0.0.1", "::ffff:127.0.0.1"], + }); +} + +export async function tearDown() { + await app.tearDown(); +} diff --git a/__tests__/integration/core-webhooks/__support__/utils.ts b/__tests__/integration/core-webhooks/__support__/utils.ts new file mode 100644 index 0000000000..dd60eaf6cd --- /dev/null +++ b/__tests__/integration/core-webhooks/__support__/utils.ts @@ -0,0 +1,51 @@ +import { httpie } from "@arkecosystem/core-utils"; +import "jest-extended"; + +export async function request(method, path, params = {}) { + const url = `http://localhost:4004/api/${path}`; + + return ["GET", "DELETE"].includes(method) + ? httpie[method.toLowerCase()](url, { query: params }) + : httpie[method.toLowerCase()](url, { body: params }); +} + +export function expectJson(response) { + expect(response.body).toBeObject(); +} + +export function expectStatus(response, code) { + expect(response.status).toBe(code); +} + +export function expectResource(response) { + expect(response.body.data).toBeObject(); +} + +export function expectCollection(response) { + expect(Array.isArray(response.body.data)).toBe(true); +} + +export function expectPaginator(response) { + expect(response.body.meta).toBeObject(); + expect(response.body.meta).toHaveProperty("count"); + expect(response.body.meta).toHaveProperty("pageCount"); + expect(response.body.meta).toHaveProperty("totalCount"); + expect(response.body.meta).toHaveProperty("next"); + expect(response.body.meta).toHaveProperty("previous"); + expect(response.body.meta).toHaveProperty("self"); + expect(response.body.meta).toHaveProperty("first"); + expect(response.body.meta).toHaveProperty("last"); +} + +export function expectSuccessful(response, statusCode = 200) { + this.expectStatus(response, statusCode); + this.expectJson(response); +} + +export function expectError(response, statusCode = 404) { + this.expectStatus(response, statusCode); + this.expectJson(response); + expect(response.body.statusCode).toBeNumber(); + expect(response.body.error).toBeString(); + expect(response.body.message).toBeString(); +} diff --git a/__tests__/integration/core-webhooks/server.test.ts b/__tests__/integration/core-webhooks/server.test.ts new file mode 100644 index 0000000000..e03b453fa9 --- /dev/null +++ b/__tests__/integration/core-webhooks/server.test.ts @@ -0,0 +1,105 @@ +import "jest-extended"; +import { setUp, tearDown } from "./__support__/setup"; +import * as utils from "./__support__/utils"; + +beforeAll(async () => { + await setUp(); +}); + +afterAll(async () => { + await tearDown(); +}); + +const postData = { + event: "block.forged", + target: "https://httpbin.org/post", + enabled: true, + conditions: [ + { + key: "generatorPublicKey", + condition: "eq", + value: "test-generator", + }, + { + key: "fee", + condition: "gte", + value: "123", + }, + ], +}; + +function createWebhook(data = null) { + return utils.request("POST", "webhooks", data || postData); +} + +describe("API 2.0 - Webhooks", () => { + it("should GET all the webhooks", async () => { + const response = await utils.request("GET", "webhooks"); + + utils.expectSuccessful(response); + utils.expectCollection(response); + }); + + it("should POST a new webhook with a simple condition", async () => { + const response = await createWebhook(); + utils.expectSuccessful(response, 201); + utils.expectResource(response); + }); + + it("should POST a new webhook with a complex condition", async () => { + const response = await createWebhook({ + event: "block.forged", + target: "https://httpbin.org/post", + enabled: true, + conditions: [ + { + key: "fee", + condition: "between", + value: { + min: 1, + max: 2, + }, + }, + ], + }); + utils.expectSuccessful(response, 201); + utils.expectResource(response); + }); + + it("should POST a new webhook with an empty array as condition", async () => { + const response = await createWebhook({ + event: "block.forged", + target: "https://httpbin.org/post", + enabled: true, + conditions: [], + }); + utils.expectSuccessful(response, 201); + utils.expectResource(response); + }); + + it("should GET a webhook by the given id", async () => { + const webhook = await createWebhook(); + + const response = await utils.request("GET", `webhooks/${webhook.body.data.id}`); + utils.expectSuccessful(response); + utils.expectResource(response); + + delete webhook.body.data.token; + + expect(response.body.data).toEqual(webhook.body.data); + }); + + it("should PUT a webhook by the given id", async () => { + const webhook = await createWebhook(); + + const response = await utils.request("PUT", `webhooks/${webhook.body.data.id}`, postData); + utils.expectStatus(response, 204); + }); + + it("should DELETE a webhook by the given id", async () => { + const webhook = await createWebhook(); + + const response = await utils.request("DELETE", `webhooks/${webhook.body.data.id}`); + utils.expectStatus(response, 204); + }); +}); diff --git a/packages/core-api/__tests__/repositories/utils/build-filter-query.test.ts b/__tests__/unit/core-api/repositories/utils/build-filter-query.test.ts similarity index 91% rename from packages/core-api/__tests__/repositories/utils/build-filter-query.test.ts rename to __tests__/unit/core-api/repositories/utils/build-filter-query.test.ts index 68d10d9046..cb8ac653ec 100644 --- a/packages/core-api/__tests__/repositories/utils/build-filter-query.test.ts +++ b/__tests__/unit/core-api/repositories/utils/build-filter-query.test.ts @@ -1,6 +1,6 @@ import "jest-extended"; -import { buildFilterQuery } from "../../../dist/repositories/utils/build-filter-query"; +import { buildFilterQuery } from "../../../../../packages/core-api/src/repositories/utils/build-filter-query"; describe("Repository utils > buildFilterQuery", () => { describe("`in` filter", () => { diff --git a/__tests__/unit/core-blockchain/blockchain.test.ts b/__tests__/unit/core-blockchain/blockchain.test.ts new file mode 100644 index 0000000000..1ebbc72c51 --- /dev/null +++ b/__tests__/unit/core-blockchain/blockchain.test.ts @@ -0,0 +1,307 @@ +/* tslint:disable:max-line-length */ +import "./mocks/"; + +import { models, slots } from "@arkecosystem/crypto"; +import delay from "delay"; +import { Blockchain } from "../../../packages/core-blockchain/src/blockchain"; +import { config as localConfig } from "../../../packages/core-blockchain/src/config"; +import { stateMachine } from "../../../packages/core-blockchain/src/state-machine"; +import "../../utils"; +import { blocks101to155 } from "../../utils/fixtures/testnet/blocks101to155"; +import { blocks2to100 } from "../../utils/fixtures/testnet/blocks2to100"; +import { config } from "./mocks/config"; +import { logger } from "./mocks/logger"; + +const { Block } = models; + +let genesisBlock; + +const blockchain = new Blockchain({}); + +describe("Blockchain", () => { + beforeAll(async () => { + // Create the genesis block after the setup has finished or else it uses a potentially + // wrong network config. + genesisBlock = new Block(require("../../utils/config/testnet/genesisBlock.json")); + + // Workaround: Add genesis transactions to the exceptions list, because they have a fee of 0 + // and otherwise don't pass validation. + config["exceptions.transactions"] = genesisBlock.transactions.map(tx => tx.id); + }); + + describe("dispatch", () => { + it("should be ok", () => { + jest.spyOn(stateMachine, "transition").mockReturnValueOnce({ actions: [] }); + const nextState = blockchain.dispatch("START"); + + expect(blockchain.state.blockchain).toEqual(nextState); + }); + + it("should log an error if no action is found", () => { + const loggerError = jest.spyOn(logger, "error"); + + // @ts-ignore + jest.spyOn(stateMachine, "transition").mockReturnValueOnce({ + actions: ["yooo"], + }); + + blockchain.dispatch("STOP"); + expect(loggerError).toHaveBeenCalledWith("No action 'yooo' found"); + }); + }); + + describe("start", () => { + it("should be ok", async () => { + jest.spyOn(stateMachine, "transition").mockReturnValueOnce({ actions: [] }); + process.env.CORE_SKIP_BLOCKCHAIN = "false"; + + const started = await blockchain.start(true); + + expect(started).toBeTrue(); + }); + }); + + describe("updateNetworkStatus", () => { + it("should call p2p updateNetworkStatus", async () => { + const p2pUpdateNetworkStatus = jest.spyOn(blockchain.p2p, "updateNetworkStatus"); + + await blockchain.updateNetworkStatus(); + + expect(p2pUpdateNetworkStatus).toHaveBeenCalled(); + }); + }); + + describe("enqueueBlocks", () => { + it("should just return if blocks provided are an empty array", async () => { + const processQueuePush = jest.spyOn(blockchain.queue, "push"); + + blockchain.enqueueBlocks([]); + expect(processQueuePush).not.toHaveBeenCalled(); + }); + + it("should enqueue the blocks provided", async () => { + const processQueuePush = jest.spyOn(blockchain.queue, "push"); + + const blocksToEnqueue = [blocks101to155[54]]; + blockchain.enqueueBlocks(blocksToEnqueue); + expect(processQueuePush).toHaveBeenCalledWith(blocksToEnqueue); + }); + }); + + describe("processBlock", () => { + const block3 = new Block(blocks2to100[1]); + let getLastBlock; + let setLastBlock; + beforeEach(() => { + getLastBlock = jest.fn(() => block3); + setLastBlock = jest.fn(() => null); + jest.spyOn(blockchain, "state", "get").mockReturnValue({ + getLastBlock, + setLastBlock, + } as any); + }); + afterAll(() => { + jest.restoreAllMocks(); + }); + + it("should process a new chained block", async () => { + const mockCallback = jest.fn(() => true); + blockchain.state.blockchain = {}; + + await blockchain.processBlock(new Block(blocks2to100[2]), mockCallback); + await delay(200); + + expect(mockCallback.mock.calls.length).toBe(1); + }); + + it("should process a valid block already known", async () => { + const mockCallback = jest.fn(() => true); + const lastBlock = blockchain.getLastBlock(); + + await blockchain.processBlock(lastBlock, mockCallback); + await delay(200); + + expect(mockCallback.mock.calls.length).toBe(1); + expect(blockchain.getLastBlock()).toEqual(lastBlock); + }); + + it("should broadcast a block if (slots.getSlotNumber() * blocktime <= block.data.timestamp)", async () => { + blockchain.state.started = true; + + const mockCallback = jest.fn(() => true); + const lastBlock = blockchain.getLastBlock(); + lastBlock.data.timestamp = slots.getSlotNumber() * 8000; + + const broadcastBlock = jest.spyOn(blockchain.p2p, "broadcastBlock"); + + await blockchain.processBlock(lastBlock, mockCallback); + await delay(200); + + expect(mockCallback.mock.calls.length).toBe(1); + expect(broadcastBlock).toHaveBeenCalled(); + }); + }); + + describe("getLastBlock", () => { + it("should be ok", () => { + jest.spyOn(localConfig, "get").mockReturnValueOnce(50); + blockchain.state.setLastBlock(genesisBlock); + + expect(blockchain.getLastBlock()).toEqual(genesisBlock); + }); + }); + + describe("handleIncomingBlock", () => { + it("should be ok", () => { + blockchain.state.started = true; + const dispatch = blockchain.dispatch; + const enqueueBlocks = blockchain.enqueueBlocks; + blockchain.dispatch = jest.fn(() => true); + blockchain.enqueueBlocks = jest.fn(() => true); + + const block = { + height: 100, + timestamp: slots.getEpochTime(), + }; + + blockchain.handleIncomingBlock(block); + + expect(blockchain.dispatch).toHaveBeenCalled(); + expect(blockchain.enqueueBlocks).toHaveBeenCalled(); + + blockchain.dispatch = dispatch; + blockchain.enqueueBlocks = enqueueBlocks; + }); + + it("should not handle block from future slot", () => { + blockchain.state.started = true; + const dispatch = blockchain.dispatch; + const enqueueBlocks = blockchain.enqueueBlocks; + blockchain.dispatch = jest.fn(() => true); + blockchain.enqueueBlocks = jest.fn(() => true); + + const block = { + height: 100, + timestamp: slots.getSlotTime(slots.getNextSlot()), + }; + + blockchain.handleIncomingBlock(block); + + expect(blockchain.dispatch).not.toHaveBeenCalled(); + expect(blockchain.enqueueBlocks).not.toHaveBeenCalled(); + + blockchain.dispatch = dispatch; + blockchain.enqueueBlocks = enqueueBlocks; + }); + + it("should disregard block when blockchain is not ready", async () => { + blockchain.state.started = false; + const loggerInfo = jest.spyOn(logger, "info"); + + const mockGetSlotNumber = jest + .spyOn(slots, "getSlotNumber") + .mockReturnValueOnce(1) + .mockReturnValueOnce(1); + + await blockchain.handleIncomingBlock(blocks101to155[54]); + + expect(loggerInfo).toHaveBeenCalledWith("Block disregarded because blockchain is not ready"); + blockchain.state.started = true; + + mockGetSlotNumber.mockRestore(); + }); + }); + + describe("forceWakeup", () => { + it("should dispatch WAKEUP", () => { + expect(() => blockchain.forceWakeup()).toDispatch(blockchain, "WAKEUP"); + }); + }); + + describe("forkBlock", () => { + it("should dispatch FORK and set state.forkedBlock", () => { + const forkedBlock = new Block(blocks2to100[11]); + expect(() => blockchain.forkBlock(forkedBlock)).toDispatch(blockchain, "FORK"); + expect(blockchain.state.forkedBlock).toBe(forkedBlock); + + blockchain.state.forkedBlock = null; // reset + }); + }); + + describe("isSynced", () => { + describe("with a block param", () => { + it("should be ok", () => { + expect( + blockchain.isSynced({ + data: { + timestamp: slots.getTime(), + height: genesisBlock.height, + }, + } as models.IBlock), + ).toBeTrue(); + }); + }); + + describe("without a block param", () => { + it("should use the last block", () => { + jest.spyOn(blockchain.p2p, "hasPeers").mockReturnValueOnce(true); + const getLastBlock = jest.spyOn(blockchain, "getLastBlock").mockReturnValueOnce({ + // @ts-ignore + data: { + timestamp: slots.getTime(), + height: genesisBlock.height, + }, + }); + expect(blockchain.isSynced()).toBeTrue(); + expect(getLastBlock).toHaveBeenCalled(); + }); + }); + }); + + describe("getBlockPing", () => { + it("should return state.blockPing", () => { + const blockPing = { + count: 1, + first: new Date().getTime(), + last: new Date().getTime(), + block: {}, + }; + blockchain.state.blockPing = blockPing; + + expect(blockchain.getBlockPing()).toBe(blockPing); + }); + }); + + describe("pingBlock", () => { + it("should call state.pingBlock", () => { + blockchain.state.blockPing = null; + + // returns false if no state.blockPing + expect(blockchain.pingBlock(blocks2to100[3])).toBeFalse(); + }); + }); + + describe("pushPingBlock", () => { + it("should call state.pushPingBlock", () => { + blockchain.state.blockPing = null; + + blockchain.pushPingBlock(blocks2to100[3]); + expect(blockchain.state.blockPing).toBeObject(); + expect(blockchain.state.blockPing.block).toBe(blocks2to100[3]); + }); + }); + + describe("constructor - networkStart", () => { + it("should output log messages if launched in networkStart mode", async () => { + const loggerWarn = jest.spyOn(logger, "warn"); + const loggerInfo = jest.spyOn(logger, "info"); + + const blockchainNetworkStart = new Blockchain({ networkStart: true }); + + expect(loggerWarn).toHaveBeenCalledWith( + "Persona Core is launched in Genesis Start mode. This is usually for starting the first node on the blockchain. Unless you know what you are doing, this is likely wrong.", + ); + expect(loggerInfo).toHaveBeenCalledWith("Starting Persona Core for a new world, welcome aboard"); + }); + }); +}); diff --git a/packages/core-blockchain/__tests__/machines/actions/fork.test.ts b/__tests__/unit/core-blockchain/machines/actions/fork.test.ts similarity index 78% rename from packages/core-blockchain/__tests__/machines/actions/fork.test.ts rename to __tests__/unit/core-blockchain/machines/actions/fork.test.ts index f9566de037..5174d2d66f 100644 --- a/packages/core-blockchain/__tests__/machines/actions/fork.test.ts +++ b/__tests__/unit/core-blockchain/machines/actions/fork.test.ts @@ -1,6 +1,6 @@ -import "@arkecosystem/core-test-utils/"; +import "../../../../utils"; -import { blockchainMachine } from "../../../src/machines/blockchain"; +import { blockchainMachine } from "../../../../../packages/core-blockchain/src/machines/blockchain"; describe("Blockchain machine > Fork", () => { it("should start with the `analysing` state", () => { @@ -15,14 +15,6 @@ describe("Blockchain machine > Fork", () => { }); }); - it("should transition to `revertBlocks` on `REBUILD`", () => { - expect(blockchainMachine).toTransition({ - from: "fork.analysing", - on: "REBUILD", - to: "fork.revertBlocks", - }); - }); - it("should transition to `exit` on `NOFORK`", () => { expect(blockchainMachine).toTransition({ from: "fork.analysing", diff --git a/packages/core-blockchain/__tests__/machines/actions/sync-with-network.test.ts b/__tests__/unit/core-blockchain/machines/actions/sync-with-network.test.ts similarity index 97% rename from packages/core-blockchain/__tests__/machines/actions/sync-with-network.test.ts rename to __tests__/unit/core-blockchain/machines/actions/sync-with-network.test.ts index ca7d86b7d1..92f92ac7f9 100644 --- a/packages/core-blockchain/__tests__/machines/actions/sync-with-network.test.ts +++ b/__tests__/unit/core-blockchain/machines/actions/sync-with-network.test.ts @@ -1,6 +1,6 @@ -import "@arkecosystem/core-test-utils/"; +import "../../../../utils"; -import { blockchainMachine } from "../../../src/machines/blockchain"; +import { blockchainMachine } from "../../../../../packages/core-blockchain/src/machines/blockchain"; describe("Blockchain machine > SyncWithNetwork", () => { it("should start with the `syncing` state", () => { diff --git a/packages/core-blockchain/__tests__/machines/blockchain.test.ts b/__tests__/unit/core-blockchain/machines/blockchain.test.ts similarity index 82% rename from packages/core-blockchain/__tests__/machines/blockchain.test.ts rename to __tests__/unit/core-blockchain/machines/blockchain.test.ts index e7adfef260..e13ffbaee1 100644 --- a/packages/core-blockchain/__tests__/machines/blockchain.test.ts +++ b/__tests__/unit/core-blockchain/machines/blockchain.test.ts @@ -1,6 +1,6 @@ -import "@arkecosystem/core-test-utils/"; +import "../../../utils"; -import { blockchainMachine } from "../../src/machines/blockchain"; +import { blockchainMachine } from "../../../../packages/core-blockchain/src/machines/blockchain"; describe("Blockchain machine", () => { it("should use `blockchain` as the key", () => { @@ -26,15 +26,7 @@ describe("Blockchain machine", () => { expect(blockchainMachine).toExecuteOnEntry({ state: "init", actions: ["init"] }); }); - it("should transition to `rebuild` on `REBUILD`", () => { - expect(blockchainMachine).toTransition({ - from: "init", - on: "REBUILD", - to: "rebuild", - }); - }); - - it("should transition to `rebuild` on `NETWORKSTART`", () => { + it("should transition to `idle` on `NETWORKSTART`", () => { expect(blockchainMachine).toTransition({ from: "init", on: "NETWORKSTART", @@ -42,7 +34,7 @@ describe("Blockchain machine", () => { }); }); - it("should transition to `rebuild` on `STARTED`", () => { + it("should transition to `syncWithNetwork` on `STARTED`", () => { expect(blockchainMachine).toTransition({ from: "init", on: "STARTED", @@ -50,25 +42,11 @@ describe("Blockchain machine", () => { }); }); - it("should transition to `rebuild` on `FAILURE`", () => { + it("should transition to `exit` on `FAILURE`", () => { expect(blockchainMachine).toTransition({ from: "init", on: "FAILURE", to: "exit" }); }); }); - describe("state `rebuild`", () => { - it("should transition to `syncWithNetwork` on `REBUILDCOMPLETE`", () => { - expect(blockchainMachine).toTransition({ - from: "rebuild", - on: "REBUILDCOMPLETE", - to: "syncWithNetwork", - }); - }); - - it("should transition to `fork` on `FORK`", () => { - expect(blockchainMachine).toTransition({ from: "rebuild", on: "FORK", to: "fork" }); - }); - }); - describe("state `syncWithNetwork`", () => { it("should transition to `idle` on `TEST`", () => { expect(blockchainMachine).toTransition({ diff --git a/__tests__/unit/core-blockchain/mocks/blockchain.ts b/__tests__/unit/core-blockchain/mocks/blockchain.ts new file mode 100644 index 0000000000..7c47d45e68 --- /dev/null +++ b/__tests__/unit/core-blockchain/mocks/blockchain.ts @@ -0,0 +1,31 @@ +import { database } from "./database"; +import { p2p } from "./p2p"; +import { transactionPool } from "./transactionPool"; + +export const blockchain = { + queue: { length: () => 0, push: () => null, clear: () => null }, + isStopped: false, + + setWakeUp: () => null, + dispatch: () => null, + isSynced: () => true, + isRebuildSynced: () => true, + enqueueBlocks: () => null, + clearAndStopQueue: () => null, + removeBlocks: () => null, + removeTopBlocks: () => null, + processBlock: () => null, + rebuildBlock: () => null, + forkBlock: () => null, + resetLastDownloadedBlock: () => null, + getLastBlock: () => null, + clearQueue: () => null, + + p2p, + database, + transactionPool, + state: { + forkedBlock: null, + setLastBlock: () => null, + }, +}; diff --git a/__tests__/unit/core-blockchain/mocks/config.ts b/__tests__/unit/core-blockchain/mocks/config.ts new file mode 100644 index 0000000000..fb0e9abce2 --- /dev/null +++ b/__tests__/unit/core-blockchain/mocks/config.ts @@ -0,0 +1,7 @@ +import genesisBlock from "../../../utils/config/testnet/genesisBlock.json"; + +export const config = { + "network.nethash": genesisBlock.payloadHash, + genesisBlock, + "state.maxLastBlocks": 50, +}; diff --git a/__tests__/unit/core-blockchain/mocks/container.ts b/__tests__/unit/core-blockchain/mocks/container.ts new file mode 100644 index 0000000000..4eaab13df1 --- /dev/null +++ b/__tests__/unit/core-blockchain/mocks/container.ts @@ -0,0 +1,59 @@ +import { blockchain } from "./blockchain"; +import { config } from "./config"; +import { database } from "./database"; +import { logger } from "./logger"; +import { p2p } from "./p2p"; +import { transactionPool } from "./transactionPool"; + +export const container = { + app: { + getConfig: () => { + return { + config: { milestones: [{ activeDelegates: 51, height: 1 }] }, + get: key => config[key], + getMilestone: () => ({ + activeDelegates: 51, + blocktime: 8000, + }), + }; + }, + resolve: name => { + if (name === "state") { + return {}; + } + + return {}; + }, + resolvePlugin: name => { + if (name === "logger") { + return logger; + } + + if (name === "blockchain") { + return blockchain; + } + + if (name === "event-emitter") { + return { + emit: () => ({}), + once: () => ({}), + }; + } + + if (name === "database") { + return database; + } + + if (name === "p2p") { + return p2p; + } + + if (name === "transaction-pool") { + return transactionPool; + } + + return null; + }, + forceExit: () => null, + }, +}; diff --git a/__tests__/unit/core-blockchain/mocks/database.ts b/__tests__/unit/core-blockchain/mocks/database.ts new file mode 100644 index 0000000000..b5923d7914 --- /dev/null +++ b/__tests__/unit/core-blockchain/mocks/database.ts @@ -0,0 +1,24 @@ +export const database = { + restoredDatabaseIntegrity: true, + + walletManager: { + findByPublicKey: pubKey => "username", + }, + + commitQueuedQueries: () => null, + buildWallets: () => null, + saveWallets: () => null, + getLastBlock: () => null, + saveBlock: () => null, + verifyBlockchain: () => ({ valid: true }), + deleteRound: () => null, + applyRound: () => null, + getActiveDelegates: () => [], + restoreCurrentRound: () => null, + enqueueDeleteBlock: () => null, + getBlocks: () => [], + getBlock: () => null, + revertBlock: () => null, + applyBlock: () => null, + getForgedTransactionsIds: () => [], +}; diff --git a/__tests__/unit/core-blockchain/mocks/index.ts b/__tests__/unit/core-blockchain/mocks/index.ts new file mode 100644 index 0000000000..eac4b6748d --- /dev/null +++ b/__tests__/unit/core-blockchain/mocks/index.ts @@ -0,0 +1,5 @@ +import { container } from "./container"; + +jest.mock("@arkecosystem/core-container", () => { + return container; +}); diff --git a/__tests__/unit/core-blockchain/mocks/logger.ts b/__tests__/unit/core-blockchain/mocks/logger.ts new file mode 100644 index 0000000000..8ae6c64f14 --- /dev/null +++ b/__tests__/unit/core-blockchain/mocks/logger.ts @@ -0,0 +1,7 @@ +export const logger = { + info: console.log, + warn: console.log, + error: console.error, + debug: console.log, + verbose: console.log, +}; diff --git a/__tests__/unit/core-blockchain/mocks/p2p.ts b/__tests__/unit/core-blockchain/mocks/p2p.ts new file mode 100644 index 0000000000..3ea0a37526 --- /dev/null +++ b/__tests__/unit/core-blockchain/mocks/p2p.ts @@ -0,0 +1,10 @@ +export const p2p = { + getNetworkHeight: () => 1, + updateNetworkStatus: () => null, + // tslint:disable-next-line: no-empty + checkNetworkHealth: () => {}, + downloadBlocks: () => [], + refreshPeersAfterFork: () => null, + broadcastBlock: () => null, + hasPeers: () => false, +}; diff --git a/__tests__/unit/core-blockchain/mocks/transactionPool.ts b/__tests__/unit/core-blockchain/mocks/transactionPool.ts new file mode 100644 index 0000000000..4ae1c39675 --- /dev/null +++ b/__tests__/unit/core-blockchain/mocks/transactionPool.ts @@ -0,0 +1,6 @@ +export const transactionPool = { + buildWallets: () => null, + purgeBlock: () => null, + acceptChainedBlock: () => null, + purgeSendersWithInvalidTransactions: () => null, +}; diff --git a/packages/core-blockchain/__tests__/processor/block-processor.test.ts b/__tests__/unit/core-blockchain/processor/block-processor.test.ts similarity index 60% rename from packages/core-blockchain/__tests__/processor/block-processor.test.ts rename to __tests__/unit/core-blockchain/processor/block-processor.test.ts index f5249322c9..90bb2e5007 100644 --- a/packages/core-blockchain/__tests__/processor/block-processor.test.ts +++ b/__tests__/unit/core-blockchain/processor/block-processor.test.ts @@ -1,36 +1,30 @@ -import "@arkecosystem/core-test-utils"; -import { fixtures, generators } from "@arkecosystem/core-test-utils"; -import genesisBlockTestnet from "@arkecosystem/core-test-utils/src/config/testnet/genesisBlock.json"; -import { models } from "@arkecosystem/crypto"; -import { Blockchain } from "../../src/blockchain"; -import { BlockProcessor, BlockProcessorResult } from "../../src/processor"; -import * as handlers from "../../src/processor/handlers"; -import { ExceptionHandler, VerificationFailedHandler } from "../../src/processor/handlers"; -import { setUpFull, tearDownFull } from "../__support__/setup"; +import "../mocks/"; +import { blockchain } from "../mocks/blockchain"; +import { config } from "../mocks/config"; +import { database } from "../mocks/database"; +import { logger } from "../mocks/logger"; + +import { configManager, models } from "@arkecosystem/crypto"; +import { BlockProcessor, BlockProcessorResult } from "../../../../packages/core-blockchain/src/processor"; +import * as handlers from "../../../../packages/core-blockchain/src/processor/handlers"; +import { + ExceptionHandler, + VerificationFailedHandler, +} from "../../../../packages/core-blockchain/src/processor/handlers"; +import { TransactionFactory } from "../../../helpers/transaction-factory"; +import "../../../utils"; +import { fixtures, generators } from "../../../utils"; +import genesisBlockTestnet from "../../../utils/config/testnet/genesisBlock.json"; const { Block } = models; const { delegates } = fixtures; -const { generateTransfers } = generators; -let app; -let blockchain: Blockchain; let blockProcessor: BlockProcessor; beforeAll(async () => { - app = await setUpFull(); - blockchain = app.resolvePlugin("blockchain"); - blockProcessor = new BlockProcessor(blockchain); + blockProcessor = new BlockProcessor(blockchain as any); }); -afterAll(async () => { - await tearDownFull(); -}); - -const resetBlocks = async () => blockchain.removeBlocks(blockchain.getLastHeight() - 1); // reset to block height 1 - -beforeEach(resetBlocks); -afterEach(resetBlocks); - describe("Block processor", () => { const blockTemplate = { id: "17882607875259085966", @@ -56,9 +50,7 @@ describe("Block processor", () => { const exceptionBlock = new Block(blockTemplate); exceptionBlock.data.id = "998877"; - const configManager = app.getConfig(); - - configManager.set("exceptions.blocks", ["998877"]); + jest.spyOn(configManager, "get").mockReturnValueOnce(["998877"]); expect(await blockProcessor.getHandler(exceptionBlock)).toBeInstanceOf(ExceptionHandler); }); @@ -79,63 +71,48 @@ describe("Block processor", () => { totalFee: transactions.reduce((acc, curr) => acc + curr.fee, 0), numberOfTransactions: transactions.length, }); - const processBlock = async transactions => { - const block = getBlock(transactions); - const blockVerified = new Block(block); - blockVerified.verification.verified = true; - await blockchain.processBlock(blockVerified, () => null); + describe("should not accept replay transactions", () => { + let block; + beforeEach(() => { + const transfers = TransactionFactory.transfer(delegates[1].address) + .withNetwork("unitnet") + .withPassphrase(delegates[0].passphrase) + .create(11); - return Object.assign(block, { id: blockVerified.data.id }); - }; + const lastBlock = new Block(getBlock(transfers)); - describe("should not accept replay transactions", () => { - it("should not validate an already forged transaction", async () => { - const transfers = generateTransfers( - "unitnet", - delegates[0].passphrase, - delegates[1].address, - 11, - 1, - true, - ); - const block = await processBlock(transfers); + block = getBlock(transfers); block.height = 3; - block.previousBlock = block.id; - block.id = "17882607875259085967"; + block.previousBlock = lastBlock.data.id; block.timestamp += 1000; + jest.spyOn(blockchain, "getLastBlock").mockReturnValue(lastBlock); + jest.spyOn(database, "getForgedTransactionsIds").mockReturnValue([blockTemplate.id]); + }); + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("should not validate an already forged transaction", async () => { const blockVerified = new Block(block); blockVerified.verification.verified = true; const handler = await blockProcessor.getHandler(blockVerified); - expect(handler instanceof handlers.AlreadyForgedHandler).toBeTrue(); + expect(handler).toBeInstanceOf(handlers.AlreadyForgedHandler); const result = await blockProcessor.process(blockVerified); expect(result).toBe(BlockProcessorResult.DiscardedButCanBeBroadcasted); }); it("should not validate an already forged transaction - trying to tweak the tx id", async () => { - const transfers = generateTransfers( - "unitnet", - delegates[0].passphrase, - delegates[1].address, - 11, - 1, - true, - ); - const block = await processBlock(transfers); - block.height = 3; - block.previousBlock = block.id; - block.id = "17882607875259085967"; - block.timestamp += 1000; - block.transactions[0].id = "123456"; // change the tx id to try to make it accept as a new transaction + block.transactions[0].id = "5".repeat(64); // change the tx id to try to make it accept as a new transaction const blockVerified = new Block(block); blockVerified.verification.verified = true; const handler = await blockProcessor.getHandler(blockVerified); - expect(handler instanceof handlers.AlreadyForgedHandler).toBeTrue(); + expect(handler).toBeInstanceOf(handlers.AlreadyForgedHandler); const result = await blockProcessor.process(blockVerified); expect(result).toBe(BlockProcessorResult.DiscardedButCanBeBroadcasted); @@ -143,38 +120,61 @@ describe("Block processor", () => { }); describe("lastDownloadedBlock", () => { + let resetLastDownloadedBlock; + beforeEach(() => { + jest.spyOn(blockchain, "getLastBlock").mockReturnValueOnce({ + // @ts-ignore + data: { + height: 1, + }, + }); + + resetLastDownloadedBlock = jest.spyOn(blockchain, "resetLastDownloadedBlock"); + }); + afterEach(() => { + jest.restoreAllMocks(); + }); + it.each([ "AlreadyForgedHandler", "InvalidGeneratorHandler", "UnchainedHandler", "VerificationFailedHandler", - ])( - "should not increment lastDownloadedBlock or lastBlock when processing block fails with %s", - async handler => { - const lastBlock = blockchain.getLastBlock(); - const lastDownloadedBlock = blockchain.getLastDownloadedBlock(); - const blockToProcess = new Block(blockTemplate); + ])("should call resetLastDownloadedBlock when processing block fails with %s", async handler => { + const blockToProcess = new Block(blockTemplate); - const getHanderBackup = blockProcessor.getHandler; // save for restoring afterwards - blockProcessor.getHandler = jest.fn(() => new handlers[handler](blockchain, blockToProcess)); + const getHanderBackup = blockProcessor.getHandler; // save for restoring afterwards + blockProcessor.getHandler = jest.fn(() => new handlers[handler](blockchain, blockToProcess)); - await blockProcessor.process(blockToProcess); + await blockProcessor.process(blockToProcess); - expect(blockchain.getLastBlock()).toEqual(lastBlock); - expect(blockchain.getLastDownloadedBlock()).toEqual(lastDownloadedBlock); + expect(resetLastDownloadedBlock).toHaveBeenCalledTimes(1); - blockProcessor.getHandler = getHanderBackup; // restore original function - }, - ); + blockProcessor.getHandler = getHanderBackup; // restore original function + }); }); describe("Forging delegates", () => { + let block; + beforeEach(() => { + const lastBlock = new Block(getBlock([])); + + block = getBlock([]); + block.height = 3; + block.previousBlock = lastBlock.data.id; + block.timestamp += 1000; + + jest.spyOn(blockchain, "getLastBlock").mockReturnValue(lastBlock); + }); + afterEach(() => { + jest.restoreAllMocks(); + }); + it("should use InvalidGeneratorHandler if forging delegate is invalid", async () => { - const database = app.resolvePlugin("database"); const getActiveDelegatesBackup = database.getActiveDelegates; // save for restoring afterwards database.getActiveDelegates = jest.fn(() => [delegates[50]]); - const blockVerified = new Block(getBlock([])); + const blockVerified = new Block(block); blockVerified.verification.verified = true; const handler = await blockProcessor.getHandler(blockVerified); @@ -188,17 +188,23 @@ describe("Block processor", () => { }); describe("Unchained blocks", () => { + beforeEach(() => { + const lastBlock = new Block(getBlock([])); + + jest.spyOn(blockchain, "getLastBlock").mockReturnValue(lastBlock); + }); + afterEach(() => { + jest.restoreAllMocks(); + }); + it("should 'discard but broadcast' when same block comes again", async () => { /* We process a valid block then try processing the same block again. Should detect as "double-forging" and reject the duplicate block. */ - const blockVerified = new Block(getBlock([])); + const block = getBlock([]); + const blockVerified = new Block(block); blockVerified.verification.verified = true; - // accept a valid first block - const accepted = await blockProcessor.process(blockVerified); - expect(accepted).toBe(BlockProcessorResult.Accepted); - - // get handler on same block, should be handled by UnchainedHandler + // same block as last block, should be handled by UnchainedHandler const handler = await blockProcessor.getHandler(blockVerified); expect(handler instanceof handlers.UnchainedHandler).toBeTrue(); @@ -208,31 +214,21 @@ describe("Block processor", () => { }); it("should reject a double-forging block", async () => { - /* We process a valid block then try processing the same block again. - Should detect as "double-forging" and reject the duplicate block. */ + // new block for double-forging : same height different id const blockVerified = new Block(getBlock([])); + blockVerified.data.id = "1111"; blockVerified.verification.verified = true; - // accept a valid first block - const accepted = await blockProcessor.process(blockVerified); - expect(accepted).toBe(BlockProcessorResult.Accepted); - - // new block for double-forging : same height different id - const blockDoubleForging = new Block(getBlock([])); - blockDoubleForging.verification.verified = true; - blockDoubleForging.data.id = "123456"; - // get handler on the "new" block, should be handled by UnchainedHandler - const handler = await blockProcessor.getHandler(blockDoubleForging); + const handler = await blockProcessor.getHandler(blockVerified); expect(handler instanceof handlers.UnchainedHandler).toBeTrue(); // if we try to process the block, it should be rejected - const rejected = await blockProcessor.process(blockDoubleForging); + const rejected = await blockProcessor.process(blockVerified); expect(rejected).toBe(BlockProcessorResult.Rejected); }); it("should reject a block with invalid timestamp", async () => { - const database = app.resolvePlugin("database"); const getActiveDelegatesBackup = database.getActiveDelegates; database.getActiveDelegates = jest.fn(() => [delegates[0]]); @@ -241,15 +237,13 @@ describe("Block processor", () => { const block = new Block(getBlock([])); block.verification.verified = true; - block.data.timestamp = 46582922; - - blockchain.getLastBlock().data.timestamp = 46583330; + block.data.timestamp -= 100; + block.data.height = 3; const rejected = await blockProcessor.process(block); expect(blockchain.forkBlock).not.toHaveBeenCalled(); expect(rejected).toBe(BlockProcessorResult.Rejected); - blockchain.getLastBlock().data.timestamp = 0; blockchain.forkBlock = forkBlockBackup; database.getActiveDelegates = getActiveDelegatesBackup; }); @@ -257,7 +251,7 @@ describe("Block processor", () => { it("should 'discard but broadcast' a block higher than current height + 1", async () => { const blockVerified = new Block(getBlock([])); blockVerified.verification.verified = true; - blockVerified.data.height = 3; + blockVerified.data.height = 4; const handler = await blockProcessor.getHandler(blockVerified); expect(handler instanceof handlers.UnchainedHandler).toBeTrue(); @@ -267,14 +261,7 @@ describe("Block processor", () => { }); it("should 'discard but broadcast' a block lower than current height", async () => { - const blockVerified = new Block(getBlock([])); - blockVerified.verification.verified = true; - - // accept a valid first block - const accepted = await blockProcessor.process(blockVerified); - expect(accepted).toBe(BlockProcessorResult.Accepted); - - // new block with height < current + // new block with height < last block const blockLowerHeight = new Block(getBlock([])); blockLowerHeight.verification.verified = true; blockLowerHeight.data.id = "123456"; diff --git a/packages/core-blockchain/__tests__/processor/handlers/accept-handler.test.ts b/__tests__/unit/core-blockchain/processor/handlers/accept-handler.test.ts similarity index 63% rename from packages/core-blockchain/__tests__/processor/handlers/accept-handler.test.ts rename to __tests__/unit/core-blockchain/processor/handlers/accept-handler.test.ts index f6652a9136..3bb7993f80 100644 --- a/packages/core-blockchain/__tests__/processor/handlers/accept-handler.test.ts +++ b/__tests__/unit/core-blockchain/processor/handlers/accept-handler.test.ts @@ -1,22 +1,17 @@ -import "@arkecosystem/core-test-utils"; -import { AcceptBlockHandler } from "../../../src/processor/handlers"; +import "../../mocks/"; +import { blockchain } from "../../mocks/blockchain"; +import { logger } from "../../mocks/logger"; + +import { AcceptBlockHandler } from "../../../../../packages/core-blockchain/src/processor/handlers"; +import "../../../../utils"; import { models } from "@arkecosystem/crypto"; -import { blocks2to100 } from "../../../../core-test-utils/src/fixtures/testnet/blocks2to100"; -import { Blockchain } from "../../../src/blockchain"; -import { BlockProcessorResult } from "../../../src/processor"; -import { setUpFull, tearDownFull } from "../../__support__/setup"; +import { BlockProcessorResult } from "../../../../../packages/core-blockchain/src/processor"; +import { blocks2to100 } from "../../../../utils/fixtures/testnet/blocks2to100"; const { Block } = models; -let app; -let blockchain: Blockchain; -let logger; beforeAll(async () => { - app = await setUpFull(); - blockchain = app.resolvePlugin("blockchain"); - logger = app.resolvePlugin("logger"); - // mock apply / saveBlock - we dont want to actually do anything to the db // @ts-ignore jest.spyOn(blockchain.database, "applyBlock").mockReturnValue(true); @@ -24,25 +19,21 @@ beforeAll(async () => { jest.spyOn(blockchain.database, "saveBlock").mockReturnValue(true); }); -afterAll(async () => { - await tearDownFull(); -}); - describe("Accept handler", () => { describe("execute", () => { it("should log message if we recovered from fork and update state.forkedBlock", async () => { - const handler = new AcceptBlockHandler(blockchain, new Block(blocks2to100[0])); + const handler = new AcceptBlockHandler(blockchain as any, new Block(blocks2to100[0])); const loggerInfo = jest.spyOn(logger, "info"); blockchain.state.forkedBlock = new Block(blocks2to100[0]); expect(await handler.execute()).toBe(BlockProcessorResult.Accepted); - expect(loggerInfo).toHaveBeenCalledWith("Successfully recovered from fork :star2:"); + expect(loggerInfo).toHaveBeenCalledWith("Successfully recovered from fork"); expect(blockchain.state.forkedBlock).toBe(null); }); it("should log warning message if transactionPool accepChainedBlock threw an exception", async () => { - const handler = new AcceptBlockHandler(blockchain, new Block(blocks2to100[0])); + const handler = new AcceptBlockHandler(blockchain as any, new Block(blocks2to100[0])); const loggerWarn = jest.spyOn(logger, "warn"); jest.spyOn(blockchain.transactionPool, "acceptChainedBlock").mockImplementationOnce(() => { @@ -55,16 +46,16 @@ describe("Accept handler", () => { it("should log error message if an exception was thrown", async () => { const block = new Block(blocks2to100[0]); - const handler = new AcceptBlockHandler(blockchain, block); + const handler = new AcceptBlockHandler(blockchain as any, block); jest.restoreAllMocks(); - const loggerError = jest.spyOn(logger, "error"); + const loggerWarn = jest.spyOn(logger, "warn"); jest.spyOn(blockchain.database, "applyBlock").mockImplementationOnce(() => { throw new Error("¯_(ツ)_/¯"); }); expect(await handler.execute()).toBe(BlockProcessorResult.Rejected); - expect(loggerError).toHaveBeenCalledWith(`Refused new block ${JSON.stringify(block.data)}`); + expect(loggerWarn).toHaveBeenCalledWith(`Refused new block ${JSON.stringify(block.data)}`); }); }); }); diff --git a/__tests__/unit/core-blockchain/processor/handlers/exception-handler.test.ts b/__tests__/unit/core-blockchain/processor/handlers/exception-handler.test.ts new file mode 100644 index 0000000000..96f36e8df7 --- /dev/null +++ b/__tests__/unit/core-blockchain/processor/handlers/exception-handler.test.ts @@ -0,0 +1,30 @@ +import "../../mocks/"; +import { blockchain } from "../../mocks/blockchain"; + +import { ExceptionHandler } from "../../../../../packages/core-blockchain/src/processor/handlers"; +import "../../../../utils"; + +import { models } from "@arkecosystem/crypto"; +import { BlockProcessorResult } from "../../../../../packages/core-blockchain/src/processor"; +import { blocks2to100 } from "../../../../utils/fixtures/testnet/blocks2to100"; + +const { Block } = models; + +describe("Exception handler", () => { + describe("execute", () => { + it("should reject if block has already been forged", async () => { + const handler = new ExceptionHandler(blockchain as any, new Block(blocks2to100[0])); + + // @ts-ignore + jest.spyOn(blockchain.database, "getBlock").mockReturnValueOnce(true); + + expect(await handler.execute()).toBe(BlockProcessorResult.Rejected); + }); + + it("should accept if block has not already been forged", async () => { + const handler = new ExceptionHandler(blockchain as any, new Block(blocks2to100[0])); + + expect(await handler.execute()).toBe(BlockProcessorResult.Accepted); + }); + }); +}); diff --git a/packages/core-blockchain/__tests__/processor/handlers/unchained-handler.test.ts b/__tests__/unit/core-blockchain/processor/handlers/unchained-handler.test.ts similarity index 59% rename from packages/core-blockchain/__tests__/processor/handlers/unchained-handler.test.ts rename to __tests__/unit/core-blockchain/processor/handlers/unchained-handler.test.ts index c1f4a88555..84eb50c744 100644 --- a/packages/core-blockchain/__tests__/processor/handlers/unchained-handler.test.ts +++ b/__tests__/unit/core-blockchain/processor/handlers/unchained-handler.test.ts @@ -1,24 +1,14 @@ -import "@arkecosystem/core-test-utils"; -import { UnchainedHandler } from "../../../src/processor/handlers"; +import "../../mocks/"; import { models } from "@arkecosystem/crypto"; -import { blocks2to100 } from "../../../../core-test-utils/src/fixtures/testnet/blocks2to100"; -import { Blockchain } from "../../../src/blockchain"; -import { BlockProcessorResult } from "../../../src/processor"; -import { setUpFull, tearDownFull } from "../../__support__/setup"; +import { BlockProcessorResult } from "../../../../../packages/core-blockchain/src/processor"; +import { UnchainedHandler } from "../../../../../packages/core-blockchain/src/processor/handlers"; +import "../../../../utils"; +import { blocks2to100 } from "../../../../utils/fixtures/testnet/blocks2to100"; +import { blockchain } from "../../mocks/blockchain"; +import { logger } from "../../mocks/logger"; const { Block } = models; -let app; -let blockchain: Blockchain; - -beforeAll(async () => { - app = await setUpFull(); - blockchain = app.resolvePlugin("blockchain"); -}); - -afterAll(async () => { - await tearDownFull(); -}); describe("Exception handler", () => { describe("execute", () => { @@ -36,7 +26,7 @@ describe("Exception handler", () => { const sameBlockDifferentId = new Block(blocks2to100[0]); sameBlockDifferentId.data.id = "7536951"; - const handler = new UnchainedHandler(blockchain, sameBlockDifferentId, true); + const handler = new UnchainedHandler(blockchain as any, sameBlockDifferentId, true); expect(await handler.execute()).toBe(BlockProcessorResult.Rejected); expect(forkBlock).toHaveBeenCalled(); @@ -44,11 +34,11 @@ describe("Exception handler", () => { it("should log that blocks are being discarded when discarding blocks with height > current + 1", async () => { jest.spyOn(blockchain, "getLastBlock").mockReturnValue(new Block(blocks2to100[0])); - blockchain.processQueue.length = () => 5; + blockchain.queue.length = () => 5; - const loggerDebug = jest.spyOn(app.resolvePlugin("logger"), "debug"); + const loggerDebug = jest.spyOn(logger, "debug"); - const handler = new UnchainedHandler(blockchain, new Block(blocks2to100[5]), true); + const handler = new UnchainedHandler(blockchain as any, new Block(blocks2to100[5]), true); expect(await handler.execute()).toBe(BlockProcessorResult.DiscardedButCanBeBroadcasted); expect(loggerDebug).toHaveBeenCalledWith("Discarded 5 downloaded blocks."); diff --git a/packages/core-blockchain/__tests__/state-machine.test.ts b/__tests__/unit/core-blockchain/state-machine.test.ts similarity index 64% rename from packages/core-blockchain/__tests__/state-machine.test.ts rename to __tests__/unit/core-blockchain/state-machine.test.ts index 62797d112a..cee101b513 100644 --- a/packages/core-blockchain/__tests__/state-machine.test.ts +++ b/__tests__/unit/core-blockchain/state-machine.test.ts @@ -1,53 +1,23 @@ -import "@arkecosystem/core-test-utils"; +import "../../utils"; +import "./mocks/"; + import { roundCalculator } from "@arkecosystem/core-utils"; import { slots } from "@arkecosystem/crypto"; import { Block } from "@arkecosystem/crypto/dist/models"; -import { asValue } from "awilix"; -import { Blockchain } from "../src/blockchain"; -import { stateStorage } from "../src/state-storage"; -import { config as localConfig } from "./../src/config"; -import { setUp, tearDown } from "./__support__/setup"; +import { config as localConfig } from "../../../packages/core-blockchain/src/config"; +import { stateStorage } from "../../../packages/core-blockchain/src/state-storage"; +import genesisBlockJSON from "../../utils/config/testnet/genesisBlock.json"; +import { blockchain } from "./mocks/blockchain"; +import { config } from "./mocks/config"; +import { container } from "./mocks/container"; +import { logger } from "./mocks/logger"; let stateMachine; -let container; -let blockchain: Blockchain; beforeAll(async () => { - container = await setUp(); + stateMachine = require("../../../packages/core-blockchain/src/state-machine").stateMachine; - process.env.CORE_SKIP_BLOCKCHAIN = "true"; process.env.CORE_ENV = ""; - - // Manually register the blockchain - const plugin = require("../src").plugin; - - blockchain = await plugin.register(container, { - networkStart: false, - }); - - await container.register( - "blockchain", - asValue({ - name: "blockchain", - version: "0.1.0", - plugin: blockchain, - options: {}, - }), - ); - - stateMachine = require("../src/state-machine").stateMachine; -}); - -afterAll(async () => { - // Manually stop the blockchain - await blockchain.stop(); - - await tearDown(); -}); - -beforeEach(async () => { - process.env.CORE_SKIP_BLOCKCHAIN = "false"; - blockchain.resetState(); }); describe("State Machine", () => { @@ -59,18 +29,11 @@ describe("State Machine", () => { }); describe("checkLater", () => { - it('should dispatch the event "WAKEUP" after a delay', async () => { - jest.useFakeTimers(); - blockchain.dispatch = jest.fn(); - + it("should call blockchain.setWakeUp", async () => { + const setWakeUp = jest.spyOn(blockchain, "setWakeUp").mockReturnValueOnce(null); actionMap.checkLater(); - expect(blockchain.dispatch).not.toBeCalled(); - - jest.runAllTimers(); - expect(blockchain.dispatch).toHaveBeenCalled(); - expect(blockchain.dispatch).toHaveBeenCalledWith("WAKEUP"); - jest.useRealTimers(); // restore standard timers + expect(setWakeUp).toHaveBeenCalledTimes(1); }); }); @@ -86,39 +49,22 @@ describe("State Machine", () => { }); }); - describe("checkRebuildBlockSynced", () => { - it('should dispatch the event "SYNCED" if the blockchain is synced after a rebuild', () => { - blockchain.isRebuildSynced = jest.fn(() => true); - expect(() => actionMap.checkRebuildBlockSynced()).toDispatch(blockchain, "SYNCED"); - }); - - it('should dispatch the event "NOTSYNCED" if the blockchain is not synced after a rebuild', () => { - blockchain.isRebuildSynced = jest.fn(() => false); - expect(() => actionMap.checkRebuildBlockSynced()).toDispatch(blockchain, "NOTSYNCED"); - }); - }); - describe("checkLastDownloadedBlockSynced", () => { it('should dispatch the event "NOTSYNCED" by default', async () => { blockchain.isSynced = jest.fn(() => false); - blockchain.processQueue.length = jest.fn(() => 1); + blockchain.queue.length = jest.fn(() => 1); await expect(actionMap.checkLastDownloadedBlockSynced).toDispatch(blockchain, "NOTSYNCED"); }); - it('should dispatch the event "PAUSED" if the blockchain rebuild / process queue is more than 10000 long', async () => { + it('should dispatch the event "PAUSED" if the blockchain process queue is more than 10000 long', async () => { blockchain.isSynced = jest.fn(() => false); - blockchain.rebuildQueue.length = jest.fn(() => 10001); - blockchain.processQueue.length = jest.fn(() => 1); - await expect(actionMap.checkLastDownloadedBlockSynced).toDispatch(blockchain, "PAUSED"); - - blockchain.rebuildQueue.length = jest.fn(() => 1); - blockchain.processQueue.length = jest.fn(() => 10001); + blockchain.queue.length = jest.fn(() => 10001); await expect(actionMap.checkLastDownloadedBlockSynced).toDispatch(blockchain, "PAUSED"); }); it('should dispatch the event "NETWORKHALTED" if stateStorage.noBlockCounter > 5 and process queue is empty', async () => { blockchain.isSynced = jest.fn(() => false); - blockchain.processQueue.length = jest.fn(() => 0); + blockchain.queue.length = jest.fn(() => 0); stateStorage.noBlockCounter = 6; await expect(actionMap.checkLastDownloadedBlockSynced).toDispatch(blockchain, "NETWORKHALTED"); }); @@ -128,7 +74,7 @@ describe("State Machine", () => { - stateStorage.p2pUpdateCounter + 1 > 3 (network keeps missing blocks) - blockchain.p2p.checkNetworkHealth() returns a forked network status`, async () => { blockchain.isSynced = jest.fn(() => false); - blockchain.processQueue.length = jest.fn(() => 0); + blockchain.queue.length = jest.fn(() => 0); stateStorage.noBlockCounter = 6; stateStorage.p2pUpdateCounter = 3; // @ts-ignore @@ -177,31 +123,11 @@ describe("State Machine", () => { }); }); - describe("rebuildFinished", () => { - it('should dispatch the event "PROCESSFINISHED"', async () => { - localConfig.set("state.maxLastBlocks", 50); - const config = container.getConfig(); - const genesisBlock = config.get("genesisBlock"); - - stateStorage.setLastBlock(new Block(genesisBlock)); - - await expect(actionMap.rebuildFinished).toDispatch(blockchain, "PROCESSFINISHED"); - }); - - it('should dispatch the event "FAILURE" when some called method threw an exception', async () => { - jest.spyOn(blockchain.database, "commitQueuedQueries").mockImplementationOnce(() => { - throw new Error("oops"); - }); - await expect(actionMap.rebuildFinished).toDispatch(blockchain, "FAILURE"); - }); - }); - describe("downloadPaused", () => { it('should log the info message "Blockchain download paused"', () => { - const logger = container.resolvePlugin("logger"); const loggerInfo = jest.spyOn(logger, "info"); actionMap.downloadPaused(); - expect(loggerInfo).lastCalledWith("Blockchain download paused :clock1030:"); + expect(loggerInfo).lastCalledWith("Blockchain download paused"); }); }); @@ -211,26 +137,19 @@ describe("State Machine", () => { }); }); - describe("rebuildingComplete", () => { - it('should dispatch the event "REBUILDCOMPLETE"', () => { - expect(() => actionMap.rebuildingComplete()).toDispatch(blockchain, "REBUILDCOMPLETE"); - }); - }); - describe("stopped", () => { it('should log the info message "The blockchain has been stopped"', () => { - const logger = container.resolvePlugin("logger"); const loggerInfo = jest.spyOn(logger, "info"); actionMap.stopped(); - expect(loggerInfo).lastCalledWith("The blockchain has been stopped :guitar:"); + expect(loggerInfo).lastCalledWith("The blockchain has been stopped"); }); }); describe("exitApp", () => { it("should call container forceExit with error message", () => { - const forceExit = jest.spyOn(container, "forceExit").mockImplementationOnce(() => null); + const forceExit = jest.spyOn(container.app, "forceExit").mockImplementationOnce(() => null); actionMap.exitApp(); - expect(forceExit).lastCalledWith("Failed to startup blockchain. Exiting Ark Core! :rotating_light:"); + expect(forceExit).lastCalledWith("Failed to startup blockchain. Exiting Persona Core! :rotating_light:"); }); }); @@ -240,14 +159,14 @@ describe("State Machine", () => { let loggerError; let loggerWarn; - beforeAll(() => { - const logger = container.resolvePlugin("logger"); + beforeEach(() => { + const config = container.app.getConfig(); + jest.spyOn(config, "get").mockImplementation(key => (key === "genesisBlock" ? genesisBlockJSON : "")); + loggerInfo = jest.spyOn(logger, "info"); loggerError = jest.spyOn(logger, "error"); loggerWarn = jest.spyOn(logger, "warn"); - }); - beforeEach(() => { databaseMocks = { getLastBlock: jest.spyOn(blockchain.database, "getLastBlock").mockReturnValue({ // @ts-ignore @@ -267,15 +186,13 @@ describe("State Machine", () => { // @ts-ignore buildWallets: jest.spyOn(blockchain.database, "buildWallets").mockReturnValue(true), // @ts-ignore - saveWallets: jest.spyOn(blockchain.database, "saveWallets").mockReturnValue(true), - // @ts-ignore applyRound: jest.spyOn(blockchain.database, "applyRound").mockReturnValue(true), // @ts-ignore - getActiveDelegates: jest.spyOn(blockchain.database, "getActiveDelegates").mockReturnValue(true), + restoreCurrentRound: jest.spyOn(blockchain.database, "restoreCurrentRound").mockReturnValue(true), }; }); - afterEach(() => jest.resetAllMocks()); + afterEach(() => jest.restoreAllMocks()); afterAll(() => { jest.restoreAllMocks(); @@ -291,31 +208,36 @@ describe("State Machine", () => { it("should dispatch FAILURE if there is no last block in database and genesis block payload hash != configured nethash", async () => { jest.spyOn(blockchain.database, "getLastBlock").mockReturnValue(null); - const config = container.getConfig(); - const genesisBlock = config.get("genesisBlock"); - const mockConfigGet = jest - .spyOn(config, "get") - .mockImplementation(key => (key === "genesisBlock" ? genesisBlock : "")); + const backupConfig = Object.assign({}, config); + config["network.nethash"] = null; await expect(() => actionMap.init()).toDispatch(blockchain, "FAILURE"); - mockConfigGet.mockRestore(); + config["network.nethash"] = backupConfig["network.nethash"]; }); it("should verify database integrity if database recovery was not successful (!restoredDatabaseIntegrity)", async () => { + blockchain.database.restoredDatabaseIntegrity = false; + await expect(() => actionMap.init()).toDispatch(blockchain, "STARTED"); - expect(loggerInfo).nthCalledWith(1, "Verifying database integrity :hourglass_flowing_sand:"); - expect(loggerInfo).nthCalledWith(2, "Verified database integrity :smile_cat:"); + expect(loggerInfo).nthCalledWith(1, "Verifying database integrity"); + expect(loggerInfo).nthCalledWith(2, "Verified database integrity"); + + blockchain.database.restoredDatabaseIntegrity = true; }); it("should dispatch ROLLBACK if database recovery was not successful and verifyBlockchain failed", async () => { + blockchain.database.restoredDatabaseIntegrity = false; + jest.spyOn(blockchain.database, "verifyBlockchain").mockReturnValue({ // @ts-ignore valid: false, }); await expect(() => actionMap.init()).toDispatch(blockchain, "ROLLBACK"); - expect(loggerError).nthCalledWith(1, "FATAL: The database is corrupted :fire:"); + expect(loggerError).nthCalledWith(1, "FATAL: The database is corrupted"); + + blockchain.database.restoredDatabaseIntegrity = true; }); it("should skip database integrity check if database recovery was successful (restoredDatabaseIntegrity)", async () => { @@ -324,7 +246,7 @@ describe("State Machine", () => { await expect(() => actionMap.init()).toDispatch(blockchain, "STARTED"); expect(loggerInfo).nthCalledWith( 1, - "Skipping database integrity check after successful database recovery :smile_cat:", + "Skipping database integrity check after successful database recovery", ); }); @@ -332,8 +254,7 @@ describe("State Machine", () => { stateStorage.networkStart = true; await expect(() => actionMap.init()).toDispatch(blockchain, "STARTED"); - expect(databaseMocks.buildWallets).toHaveBeenCalledWith(1); - expect(databaseMocks.saveWallets).toHaveBeenCalledWith(true); + expect(databaseMocks.buildWallets).toHaveBeenCalled(); expect(databaseMocks.applyRound).toHaveBeenCalledWith(1); stateStorage.networkStart = false; // reset to default value @@ -341,45 +262,15 @@ describe("State Machine", () => { it('should dispatch STARTED if NODE_ENV === "test"', async () => { process.env.NODE_ENV = "test"; - const logger = container.resolvePlugin("logger"); const loggerVerbose = jest.spyOn(logger, "verbose"); await expect(() => actionMap.init()).toDispatch(blockchain, "STARTED"); - expect(databaseMocks.buildWallets).toHaveBeenCalledWith(1); + expect(databaseMocks.buildWallets).toHaveBeenCalled(); expect(loggerVerbose).toHaveBeenCalledWith( - "TEST SUITE DETECTED! SYNCING WALLETS AND STARTING IMMEDIATELY. :bangbang:", + "TEST SUITE DETECTED! SYNCING WALLETS AND STARTING IMMEDIATELY.", ); }); - it("should dispatch REBUILD if stateStorage.fastRebuild", async () => { - process.env.NODE_ENV = ""; - - // mock getLastBlock() timestamp and fastRebuild config to trigger stateStorage.fastRebuild = true - jest.spyOn(blockchain.database, "getLastBlock").mockReturnValue({ - // @ts-ignore - data: { - height: 1, - timestamp: 0, - }, - }); - const mockConfigGet = jest - .spyOn(localConfig, "get") - .mockImplementation(key => (key === "fastRebuild" ? true : "")); - - await expect(() => actionMap.init()).toDispatch(blockchain, "REBUILD"); - - mockConfigGet.mockRestore(); - }); - - it("should rollbackCurrentRound and dispatch STARTED if couldnt get activeDelegates", async () => { - process.env.NODE_ENV = ""; - jest.spyOn(blockchain.database, "getActiveDelegates").mockReturnValue(undefined); - const spyRollbackCurrentRound = jest.spyOn(blockchain, "rollbackCurrentRound").mockReturnThis(); - - await expect(() => actionMap.init()).toDispatch(blockchain, "STARTED"); - expect(spyRollbackCurrentRound).toHaveBeenCalled(); - }); - it("should rebuild wallets table and dispatch STARTED if database.buildWallets() failed", async () => { process.env.NODE_ENV = ""; jest.spyOn(blockchain.database, "getLastBlock").mockReturnValue({ @@ -394,9 +285,8 @@ describe("State Machine", () => { await expect(() => actionMap.init()).toDispatch(blockchain, "STARTED"); expect(loggerWarn).toHaveBeenCalledWith( - "Rebuilding wallets table because of some inconsistencies. Most likely due to an unfortunate shutdown. :hammer:", + "Rebuilding wallets table because of some inconsistencies. Most likely due to an unfortunate shutdown.", ); - expect(databaseMocks.saveWallets).toHaveBeenCalledWith(true); }); it("should clean round data if new round starts at block.height + 1 (and dispatch STARTED)", async () => { @@ -422,77 +312,17 @@ describe("State Machine", () => { }); }); - describe("rebuildBlocks", () => { - let genesisBlock; - - beforeAll(() => { - const config = container.getConfig(); - genesisBlock = config.get("genesisBlock"); - }); - - it("should dispatch NOBLOCK if no new blocks were downloaded from peer", async () => { - stateStorage.lastDownloadedBlock = new Block(genesisBlock); - - const logger = container.resolvePlugin("logger"); - const loggerInfo = jest.spyOn(logger, "info"); - - jest.spyOn(blockchain.p2p, "downloadBlocks").mockReturnValue([]); - await expect(() => actionMap.rebuildBlocks()).toDispatch(blockchain, "NOBLOCK"); - expect(loggerInfo).toHaveBeenCalledWith("No new blocks found on this peer"); - }); - - it("should dispatch DOWNLOADED if new blocks were successfully downloaded from peer", async () => { - stateStorage.lastDownloadedBlock = new Block(genesisBlock); - - const logger = container.resolvePlugin("logger"); - const loggerInfo = jest.spyOn(logger, "info"); - - jest.spyOn(blockchain.p2p, "downloadBlocks").mockReturnValue([ - { - numberOfTransactions: 2, - previousBlock: genesisBlock.id, - }, - ]); - await expect(() => actionMap.rebuildBlocks()).toDispatch(blockchain, "DOWNLOADED"); - expect(loggerInfo).toHaveBeenCalledWith( - "Downloaded 1 new block accounting for a total of 2 transactions", - ); - }); - - it("should dispatch NOBLOCK if new blocks were downloaded from peer but didnt match last known block", async () => { - stateStorage.lastDownloadedBlock = new Block(genesisBlock); - - const logger = container.resolvePlugin("logger"); - const loggerWarn = jest.spyOn(logger, "warn"); - - const downloadedBlock = { - numberOfTransactions: 2, - previousBlock: "123456", - }; - jest.spyOn(blockchain.p2p, "downloadBlocks").mockReturnValue([downloadedBlock]); - await expect(() => actionMap.rebuildBlocks()).toDispatch(blockchain, "NOBLOCK"); - expect(loggerWarn).toHaveBeenCalledWith( - `Downloaded block not accepted: ${JSON.stringify(downloadedBlock)}`, - ); - }); - }); - describe("downloadBlocks", () => { - let genesisBlock; let loggerInfo; let loggerWarn; beforeAll(() => { - const config = container.getConfig(); - genesisBlock = config.get("genesisBlock"); - - const logger = container.resolvePlugin("logger"); loggerInfo = jest.spyOn(logger, "info"); loggerWarn = jest.spyOn(logger, "warn"); }); beforeEach(() => { - stateStorage.lastDownloadedBlock = new Block(genesisBlock); + stateStorage.lastDownloadedBlock = new Block(genesisBlockJSON as any); }); afterEach(() => jest.resetAllMocks()); @@ -508,9 +338,9 @@ describe("State Machine", () => { jest.spyOn(blockchain.p2p, "downloadBlocks").mockReturnValue([ { numberOfTransactions: 2, - previousBlock: genesisBlock.id, + previousBlock: genesisBlockJSON.id, height: 2, - timestamp: genesisBlock.timestamp + 115, + timestamp: genesisBlockJSON.timestamp + 115, }, ]); // @ts-ignore @@ -528,9 +358,9 @@ describe("State Machine", () => { it("should dispatch NOBLOCK if new blocks downloaded are not chained", async () => { const downloadedBlock = { numberOfTransactions: 2, - previousBlock: genesisBlock.id, + previousBlock: genesisBlockJSON.id, height: 3, - timestamp: genesisBlock.timestamp + 115, + timestamp: genesisBlockJSON.timestamp + 115, }; jest.spyOn(blockchain.p2p, "downloadBlocks").mockReturnValue([downloadedBlock]); await expect(() => actionMap.downloadBlocks()).toDispatch(blockchain, "NOBLOCK"); @@ -548,18 +378,16 @@ describe("State Machine", () => { describe("analyseFork", () => { it("should log 'analysing fork' message", () => { - const logger = container.resolvePlugin("logger"); const loggerInfo = jest.spyOn(logger, "info"); actionMap.analyseFork(); - expect(loggerInfo).toHaveBeenCalledWith("Analysing fork :mag:"); + expect(loggerInfo).toHaveBeenCalledWith("Analysing fork"); }); }); describe("startForkRecovery", () => { it("should proceed to fork recovery and dispatch SUCCESS", async () => { - const logger = container.resolvePlugin("logger"); const loggerInfo = jest.spyOn(logger, "info"); const methodsCalled = [ @@ -575,7 +403,7 @@ describe("State Machine", () => { ]; await expect(() => actionMap.startForkRecovery()).toDispatch(blockchain, "SUCCESS"); - expect(loggerInfo).toHaveBeenCalledWith("Starting fork recovery :fork_and_knife:"); + expect(loggerInfo).toHaveBeenCalledWith("Starting fork recovery"); methodsCalled.forEach(method => { expect(method).toHaveBeenCalled(); }); @@ -586,7 +414,6 @@ describe("State Machine", () => { afterEach(() => jest.restoreAllMocks()); it("should try to remove X blocks based on databaseRollback config until database.verifyBlockchain() passes - and dispatch SUCCESS", async () => { - const logger = container.resolvePlugin("logger"); const loggerInfo = jest.spyOn(logger, "info"); jest.spyOn(localConfig, "get").mockReturnValue({ @@ -609,15 +436,12 @@ describe("State Machine", () => { await expect(() => actionMap.rollbackDatabase()).toDispatch(blockchain, "SUCCESS"); - expect(loggerInfo).toHaveBeenCalledWith( - "Database integrity verified again after rollback to height 1 :green_heart:", - ); + expect(loggerInfo).toHaveBeenCalledWith("Database integrity verified again after rollback to height 1"); expect(removeTopBlocks).toHaveBeenCalledTimes(3); // because the 3rd time verifyBlockchain returned true }); it(`should try to remove X blocks based on databaseRollback config until database.verifyBlockchain() passes and dispatch FAILURE as verifyBlockchain never passed`, async () => { - const logger = container.resolvePlugin("logger"); const loggerError = jest.spyOn(logger, "error"); jest.spyOn(localConfig, "get").mockReturnValue({ @@ -637,9 +461,7 @@ describe("State Machine", () => { await expect(() => actionMap.rollbackDatabase()).toDispatch(blockchain, "FAILURE"); - expect(loggerError).toHaveBeenCalledWith( - "FATAL: Failed to restore database integrity :skull: :skull: :skull:", - ); + expect(loggerError).toHaveBeenCalledWith("FATAL: Failed to restore database integrity"); expect(removeTopBlocks).toHaveBeenCalledTimes(5); // because after 5 times we get past maxBlockRewind }); }); diff --git a/packages/core-blockchain/__tests__/state-storage.test.ts b/__tests__/unit/core-blockchain/state-storage.test.ts similarity index 94% rename from packages/core-blockchain/__tests__/state-storage.test.ts rename to __tests__/unit/core-blockchain/state-storage.test.ts index 4be0ba851e..4856e02902 100644 --- a/packages/core-blockchain/__tests__/state-storage.test.ts +++ b/__tests__/unit/core-blockchain/state-storage.test.ts @@ -1,26 +1,22 @@ -import "@arkecosystem/core-test-utils"; -import { blocks101to155 } from "@arkecosystem/core-test-utils/src/fixtures/testnet/blocks101to155"; -import { blocks2to100 } from "@arkecosystem/core-test-utils/src/fixtures/testnet/blocks2to100"; -import { models } from "@arkecosystem/crypto"; +import "./mocks/"; +import { logger } from "./mocks/logger"; + +import { ITransactionData, models } from "@arkecosystem/crypto"; import delay from "delay"; -import { config } from "../src/config"; -import { defaults } from "../src/defaults"; -import { setUp, tearDown } from "./__support__/setup"; +import { config } from "../../../packages/core-blockchain/src/config"; +import { defaults } from "../../../packages/core-blockchain/src/defaults"; +import "../../utils"; +import { blocks101to155 } from "../../utils/fixtures/testnet/blocks101to155"; +import { blocks2to100 } from "../../utils/fixtures/testnet/blocks2to100"; const { Block } = models; const blocks = blocks2to100.concat(blocks101to155).map(block => new Block(block)); -let app; let stateStorage; beforeAll(async () => { - app = await setUp(); config.init(defaults); - stateStorage = require("../src").stateStorage; -}); - -afterAll(async () => { - await tearDown(); + stateStorage = require("../../../packages/core-blockchain/src").stateStorage; }); beforeEach(() => { @@ -180,7 +176,7 @@ describe("State Storage", () => { describe("cacheTransactions", () => { it("should add transaction id", () => { - expect(stateStorage.cacheTransactions([{ id: "1" } as models.ITransactionData])).toEqual({ + expect(stateStorage.cacheTransactions([{ id: "1" } as ITransactionData])).toEqual({ added: [{ id: "1" }], notAdded: [], }); @@ -188,11 +184,11 @@ describe("State Storage", () => { }); it("should not add duplicate transaction ids", () => { - expect(stateStorage.cacheTransactions([{ id: "1" } as models.ITransactionData])).toEqual({ + expect(stateStorage.cacheTransactions([{ id: "1" } as ITransactionData])).toEqual({ added: [{ id: "1" }], notAdded: [], }); - expect(stateStorage.cacheTransactions([{ id: "1" } as models.ITransactionData])).toEqual({ + expect(stateStorage.cacheTransactions([{ id: "1" } as ITransactionData])).toEqual({ added: [], notAdded: [{ id: "1" }], }); @@ -322,7 +318,6 @@ describe("State Storage", () => { block: blocks2to100[3], }; - const logger = app.resolvePlugin("logger"); const loggerInfo = jest.spyOn(logger, "info"); stateStorage.pushPingBlock(blocks2to100[5]); diff --git a/packages/core-blockchain/__tests__/utils/is-blocked-chained.test.ts b/__tests__/unit/core-blockchain/utils/is-blocked-chained.test.ts similarity index 97% rename from packages/core-blockchain/__tests__/utils/is-blocked-chained.test.ts rename to __tests__/unit/core-blockchain/utils/is-blocked-chained.test.ts index 295e567452..29ada1b555 100644 --- a/packages/core-blockchain/__tests__/utils/is-blocked-chained.test.ts +++ b/__tests__/unit/core-blockchain/utils/is-blocked-chained.test.ts @@ -1,7 +1,7 @@ import "jest-extended"; import { models, slots } from "@arkecosystem/crypto"; -import { isBlockChained } from "../../src/utils"; +import { isBlockChained } from "../../../../packages/core-blockchain/src/utils"; describe("isChained", () => { it("should be ok", () => { diff --git a/packages/core-blockchain/__tests__/utils/tick-sync-tracker.test.ts b/__tests__/unit/core-blockchain/utils/tick-sync-tracker.test.ts similarity index 94% rename from packages/core-blockchain/__tests__/utils/tick-sync-tracker.test.ts rename to __tests__/unit/core-blockchain/utils/tick-sync-tracker.test.ts index 6c41cf973a..48caf75a71 100644 --- a/packages/core-blockchain/__tests__/utils/tick-sync-tracker.test.ts +++ b/__tests__/unit/core-blockchain/utils/tick-sync-tracker.test.ts @@ -18,7 +18,7 @@ const DateBackup = Date; describe("tickSyncTracker", () => { beforeEach(() => { global.Date = DateBackup; - tickSyncTracker = require("../../src/utils").tickSyncTracker; + tickSyncTracker = require("../../../../packages/core-blockchain/src/utils").tickSyncTracker; }); it("print tracker stats when percent < 100", () => { diff --git a/packages/core-container/__tests__/__stubs__/config/delegates.json b/__tests__/unit/core-container/__stubs__/config/delegates.json similarity index 100% rename from packages/core-container/__tests__/__stubs__/config/delegates.json rename to __tests__/unit/core-container/__stubs__/config/delegates.json diff --git a/packages/core-container/__tests__/__stubs__/config/exceptions.json b/__tests__/unit/core-container/__stubs__/config/exceptions.json similarity index 100% rename from packages/core-container/__tests__/__stubs__/config/exceptions.json rename to __tests__/unit/core-container/__stubs__/config/exceptions.json diff --git a/packages/core-container/__tests__/__stubs__/config/genesisBlock.json b/__tests__/unit/core-container/__stubs__/config/genesisBlock.json similarity index 100% rename from packages/core-container/__tests__/__stubs__/config/genesisBlock.json rename to __tests__/unit/core-container/__stubs__/config/genesisBlock.json diff --git a/packages/core-container/__tests__/__stubs__/config/milestones.json b/__tests__/unit/core-container/__stubs__/config/milestones.json similarity index 86% rename from packages/core-container/__tests__/__stubs__/config/milestones.json rename to __tests__/unit/core-container/__stubs__/config/milestones.json index 51c32313cf..740754fc8d 100644 --- a/packages/core-container/__tests__/__stubs__/config/milestones.json +++ b/__tests__/unit/core-container/__stubs__/config/milestones.json @@ -22,10 +22,15 @@ "multiPayment": 0, "delegateResignation": 0 } - } + }, + "vendorFieldLength": 64 }, { "height": 75600, "reward": 200000000 + }, + { + "height": 100000, + "vendorFieldLength": 255 } ] diff --git a/packages/core-container/__tests__/__stubs__/config/network.json b/__tests__/unit/core-container/__stubs__/config/network.json similarity index 95% rename from packages/core-container/__tests__/__stubs__/config/network.json rename to __tests__/unit/core-container/__stubs__/config/network.json index 0f373e462e..4d47f016a6 100644 --- a/packages/core-container/__tests__/__stubs__/config/network.json +++ b/__tests__/unit/core-container/__stubs__/config/network.json @@ -8,6 +8,7 @@ "pubKeyHash": 23, "nethash": "d9acd04bde4234a81addb8482333b4ac906bed7be5a9970ce8ada428bd083192", "wif": 186, + "slip44": 1, "aip20": 0, "client": { "token": "TARK", diff --git a/packages/core-container/__tests__/__stubs__/config/peers.json b/__tests__/unit/core-container/__stubs__/config/peers.json similarity index 100% rename from packages/core-container/__tests__/__stubs__/config/peers.json rename to __tests__/unit/core-container/__stubs__/config/peers.json diff --git a/packages/core-container/__tests__/__stubs__/config/plugins.js b/__tests__/unit/core-container/__stubs__/config/plugins.js similarity index 100% rename from packages/core-container/__tests__/__stubs__/config/plugins.js rename to __tests__/unit/core-container/__stubs__/config/plugins.js diff --git a/packages/core-container/__tests__/__stubs__/plugin-a.js b/__tests__/unit/core-container/__stubs__/plugin-a.js similarity index 100% rename from packages/core-container/__tests__/__stubs__/plugin-a.js rename to __tests__/unit/core-container/__stubs__/plugin-a.js diff --git a/packages/core-container/__tests__/__stubs__/plugin-b.js b/__tests__/unit/core-container/__stubs__/plugin-b.js similarity index 100% rename from packages/core-container/__tests__/__stubs__/plugin-b.js rename to __tests__/unit/core-container/__stubs__/plugin-b.js diff --git a/packages/core-container/__tests__/__stubs__/plugin-c.js b/__tests__/unit/core-container/__stubs__/plugin-c.js similarity index 100% rename from packages/core-container/__tests__/__stubs__/plugin-c.js rename to __tests__/unit/core-container/__stubs__/plugin-c.js diff --git a/packages/core-container/__tests__/__stubs__/plugins.js b/__tests__/unit/core-container/__stubs__/plugins.js similarity index 100% rename from packages/core-container/__tests__/__stubs__/plugins.js rename to __tests__/unit/core-container/__stubs__/plugins.js diff --git a/packages/core-container/__tests__/container.test.ts b/__tests__/unit/core-container/container.test.ts similarity index 93% rename from packages/core-container/__tests__/container.test.ts rename to __tests__/unit/core-container/container.test.ts index d2dee6e9bf..d95354883b 100644 --- a/packages/core-container/__tests__/container.test.ts +++ b/__tests__/unit/core-container/container.test.ts @@ -2,7 +2,7 @@ import "jest-extended"; import { asValue } from "awilix"; import { resolve } from "path"; -import { app } from "../src"; +import { app } from "../../../packages/core-container/src"; const dummyPlugin = { name: "dummy", @@ -17,7 +17,7 @@ beforeEach(async () => { await app.setUp( "2.0.0", { - token: "ark", + token: "persona", network: "testnet", }, { @@ -64,10 +64,6 @@ describe("Container", () => { expect(app.has("fake")).toBeTrue(); }); - it("should get the hashid", () => { - expect(app.getHashid()).toBeString(); - }); - it("should get the version", () => { expect(app.getVersion()).toBe("2.0.0"); }); diff --git a/packages/core-container/__tests__/registrars/plugin.test.ts b/__tests__/unit/core-container/registrars/plugin.test.ts similarity index 95% rename from packages/core-container/__tests__/registrars/plugin.test.ts rename to __tests__/unit/core-container/registrars/plugin.test.ts index 76c782d5ac..0211b3247c 100644 --- a/packages/core-container/__tests__/registrars/plugin.test.ts +++ b/__tests__/unit/core-container/registrars/plugin.test.ts @@ -1,8 +1,8 @@ import "jest-extended"; import { resolve } from "path"; -import { Container } from "../../src/container"; -import { PluginRegistrar } from "../../src/registrars/plugin"; +import { Container } from "../../../../packages/core-container/src/container"; +import { PluginRegistrar } from "../../../../packages/core-container/src/registrars/plugin"; const stubPluginPath = resolve(__dirname, "../__stubs__"); diff --git a/packages/core-database/__tests__/__fixtures__/database-connection-stub.ts b/__tests__/unit/core-database/__fixtures__/database-connection-stub.ts similarity index 58% rename from packages/core-database/__tests__/__fixtures__/database-connection-stub.ts rename to __tests__/unit/core-database/__fixtures__/database-connection-stub.ts index 34459b572c..c7ca8a9f46 100644 --- a/packages/core-database/__tests__/__fixtures__/database-connection-stub.ts +++ b/__tests__/unit/core-database/__fixtures__/database-connection-stub.ts @@ -3,19 +3,18 @@ import { Database } from "@arkecosystem/core-interfaces"; import { models } from "@arkecosystem/crypto"; -export class DatabaseConnectionStub implements Database.IDatabaseConnection { +export class DatabaseConnectionStub implements Database.IConnection { public blocksRepository: Database.IBlocksRepository; public roundsRepository: Database.IRoundsRepository; public transactionsRepository: Database.ITransactionsRepository; public walletsRepository: Database.IWalletsRepository; public options: any; - public buildWallets(height: number): Promise { + public buildWallets(): Promise { return undefined; } - public commitQueuedQueries(): any { - } + public commitQueuedQueries(): any {} public connect(): Promise { return undefined; @@ -29,25 +28,15 @@ export class DatabaseConnectionStub implements Database.IDatabaseConnection { return undefined; } - public enqueueDeleteBlock(block: models.Block): any { - } - - public enqueueDeleteRound(height: number): any { - } + public enqueueDeleteBlock(block: models.Block): any {} - public enqueueSaveBlock(block: models.Block): any { - return null; - } + public enqueueDeleteRound(height: number): any {} - public async make(): Promise { + public async make(): Promise { return this; } public saveBlock(block: models.Block): Promise { return undefined; } - - public saveWallets(wallets: any[], force?: boolean): Promise { - return undefined; - } } diff --git a/__tests__/unit/core-database/__fixtures__/mock-database-model.ts b/__tests__/unit/core-database/__fixtures__/mock-database-model.ts new file mode 100644 index 0000000000..6365c87fd4 --- /dev/null +++ b/__tests__/unit/core-database/__fixtures__/mock-database-model.ts @@ -0,0 +1,48 @@ +import { Database } from "@arkecosystem/core-interfaces"; + +export class MockDatabaseModel implements Database.IModel { + public getName(): string { + return this.constructor.name; + } + + public getTable(): string { + return "test"; + } + + public query(): any { + return; + } + + public getColumnSet(): any { + return; + } + + public getSearchableFields(): Database.SearchableField[] { + return [ + { + fieldName: "id", + supportedOperators: [Database.SearchOperator.OP_EQ], + }, + { + fieldName: "timestamp", + supportedOperators: [Database.SearchOperator.OP_GTE, Database.SearchOperator.OP_LTE], + }, + { + fieldName: "sentence", + supportedOperators: [Database.SearchOperator.OP_EQ, Database.SearchOperator.OP_LIKE], + }, + { + fieldName: "basket", + supportedOperators: [Database.SearchOperator.OP_IN], + }, + { + fieldName: "range", + supportedOperators: [ + Database.SearchOperator.OP_EQ, + Database.SearchOperator.OP_GTE, + Database.SearchOperator.OP_LTE, + ], + }, + ]; + } +} diff --git a/packages/core-database/__tests__/__fixtures__/state-storage-stub.ts b/__tests__/unit/core-database/__fixtures__/state-storage-stub.ts similarity index 65% rename from packages/core-database/__tests__/__fixtures__/state-storage-stub.ts rename to __tests__/unit/core-database/__fixtures__/state-storage-stub.ts index 947a552faf..652113835f 100644 --- a/packages/core-database/__tests__/__fixtures__/state-storage-stub.ts +++ b/__tests__/unit/core-database/__fixtures__/state-storage-stub.ts @@ -1,17 +1,17 @@ /* tslint:disable:no-empty */ import { Blockchain } from "@arkecosystem/core-interfaces"; -import { models } from "@arkecosystem/crypto"; +import { ITransactionData, models } from "@arkecosystem/crypto"; export class StateStorageStub implements Blockchain.IStateStorage { - public cacheTransactions(transactions: models.ITransactionData[]): { added: models.ITransactionData[]; notAdded: models.ITransactionData[] } { + public cacheTransactions( + transactions: ITransactionData[], + ): { added: ITransactionData[]; notAdded: ITransactionData[] } { return undefined; } - public clear(): void { - } + public clear(): void {} - public clearWakeUpTimeout(): void { - } + public clearWakeUpTimeout(): void {} public getCachedTransactionIds(): string[] { return []; @@ -41,16 +41,11 @@ export class StateStorageStub implements Blockchain.IStateStorage { return false; } - public pushPingBlock(block: models.IBlockData): void { - } + public pushPingBlock(block: models.IBlockData): void {} - public removeCachedTransactionIds(transactionIds: string[]): void { - } + public removeCachedTransactionIds(transactionIds: string[]): void {} - public reset(): void { - } - - public setLastBlock(block: models.Block): void { - } + public reset(): void {} + public setLastBlock(block: models.Block): void {} } diff --git a/packages/core-database/__tests__/__fixtures__/wallets.json b/__tests__/unit/core-database/__fixtures__/wallets.json similarity index 100% rename from packages/core-database/__tests__/__fixtures__/wallets.json rename to __tests__/unit/core-database/__fixtures__/wallets.json diff --git a/packages/core-database/__tests__/database-service.test.ts b/__tests__/unit/core-database/database-service.test.ts similarity index 82% rename from packages/core-database/__tests__/database-service.test.ts rename to __tests__/unit/core-database/database-service.test.ts index cb50f0dae6..3160fa7eb1 100644 --- a/packages/core-database/__tests__/database-service.test.ts +++ b/__tests__/unit/core-database/database-service.test.ts @@ -1,41 +1,43 @@ -import { Container, Database, EventEmitter } from "@arkecosystem/core-interfaces"; -import { Bignum, constants, models, transactionBuilder } from "@arkecosystem/crypto"; import "jest-extended"; -import { WalletManager } from "../src"; -import { DatabaseService } from "../src/database-service"; +import "./mocks/core-container"; + +import { app } from "@arkecosystem/core-container"; +import { Database, EventEmitter } from "@arkecosystem/core-interfaces"; +import { TransactionHandlerRegistry } from "@arkecosystem/core-transactions"; +import { Address, Bignum, constants, models, Transaction, transactionBuilder } from "@arkecosystem/crypto"; +import { Wallet, WalletManager } from "../../../packages/core-database/src"; +import { DatabaseService } from "../../../packages/core-database/src/database-service"; +import { roundCalculator } from "../../../packages/core-utils/dist"; +import { genesisBlock } from "../../utils/fixtures/testnet/block-model"; import { DatabaseConnectionStub } from "./__fixtures__/database-connection-stub"; import { StateStorageStub } from "./__fixtures__/state-storage-stub"; -import { setUp, tearDown } from "./__support__/setup"; - -const { Block, Transaction, Wallet } = models; +const { Block } = models; const { SATOSHI, TransactionTypes } = constants; -let connection: Database.IDatabaseConnection; +let connection: Database.IConnection; let databaseService: DatabaseService; let walletManager: Database.IWalletManager; -let genesisBlock: models.Block; -let container: Container.IContainer; +let container; let emitter: EventEmitter.EventEmitter; -beforeAll(async () => { - container = await setUp(); +beforeAll(() => { + container = app; + // @ts-ignore emitter = container.resolvePlugin("event-emitter"); - genesisBlock = new Block(require("@arkecosystem/core-test-utils/src/config/testnet/genesisBlock.json")); connection = new DatabaseConnectionStub(); walletManager = new WalletManager(); }); -afterAll(async () => { - await tearDown(); -}); - beforeEach(() => { jest.restoreAllMocks(); }); function createService() { - return new DatabaseService({}, connection, walletManager, null, null); + const service = new DatabaseService({}, connection, walletManager, null, null, null, null); + service.emitter = emitter; + + return service; } describe("Database Service", () => { @@ -45,9 +47,8 @@ describe("Database Service", () => { databaseService = createService(); - expect(emitter.on).toHaveBeenCalledWith("state:started", expect.toBeFunction()); + expect(emitter.on).toHaveBeenCalledWith("state.started", expect.toBeFunction()); expect(emitter.on).toHaveBeenCalledWith("wallet.created.cold", expect.toBeFunction()); - expect(emitter.once).toHaveBeenCalledWith("shutdown", expect.toBeFunction()); }); describe("applyBlock", () => { @@ -84,25 +85,10 @@ describe("Database Service", () => { jest.spyOn(container, "resolve").mockReturnValue(stateStorageStub); databaseService = createService(); - databaseService.connection.blocksRepository = { - findById: null, - findByHeight: jest.fn(heights => { - const r = heights.map(h => ({ height: Number(h), fromDb: true })); - return r; - }), - count: null, - common: null, - heightRange: null, - latest: null, - recent: null, - statistics: null, - top: null, - delete: null, - estimate: null, - truncate: null, - insert: null, - update: null, - }; + + connection.blocksRepository = { + findByHeights: (heights: number[]) => heights.map(h => ({ height: Number(h), fromDb: true })), + } as any; // FIXME: use Database.IBlocksRepository let requestHeights = requestHeightsHigh; @@ -197,16 +183,17 @@ describe("Database Service", () => { describe("calcPreviousActiveDelegates", () => { it("should calculate the previous delegate list", async () => { + databaseService = createService(); + walletManager = new WalletManager(); const initialHeight = 52; // Create delegates for (const transaction of genesisBlock.transactions) { if (transaction.type === TransactionTypes.DelegateRegistration) { - const wallet = walletManager.findByPublicKey(transaction.senderPublicKey); - wallet.username = Transaction.deserialize( - transaction.serialized.toString(), - ).asset.delegate.username; + const wallet = walletManager.findByPublicKey(transaction.data.senderPublicKey); + wallet.username = Transaction.fromBytes(transaction.serialized).data.asset.delegate.username; + wallet.address = Address.fromPublicKey(transaction.data.senderPublicKey); walletManager.reindex(wallet); } } @@ -219,12 +206,16 @@ describe("Database Service", () => { }; // Beginning of round 2 with all delegates 0 vote balance. - const delegatesRound2 = walletManager.loadActiveDelegateList(51, initialHeight); + const roundInfo1 = roundCalculator.calculateRound(initialHeight); + const delegatesRound2 = walletManager.loadActiveDelegateList(roundInfo1); // Prepare sender wallet + const transactionHandler = TransactionHandlerRegistry.get(TransactionTypes.Transfer); + const originalApply = transactionHandler.canBeApplied; + transactionHandler.canBeApplied = jest.fn(() => true); + const sender = new Wallet(keys.address); sender.publicKey = keys.publicKey; - sender.canApply = jest.fn(() => true); walletManager.reindex(sender); // Apply 51 blocks, where each increases the vote balance of a delegate to @@ -233,7 +224,7 @@ describe("Database Service", () => { for (let i = 0; i < 51; i++) { const transfer = transactionBuilder .transfer() - .amount(i * SATOSHI) + .amount((i + 1) * SATOSHI) .recipientId(delegatesRound2[i].address) .sign(keys.passphrase) .build(); @@ -248,12 +239,12 @@ describe("Database Service", () => { timestamp: 0, height: initialHeight + i, numberOfTransactions: 1, - totalAmount: transfer.amount, + totalAmount: transfer.data.amount, totalFee: new Bignum(0.1), reward: new Bignum(2), payloadLength: 0, payloadHash: "a".repeat(64), - transactions: [transfer], + transactions: [transfer.data], }, keys, ); @@ -265,7 +256,8 @@ describe("Database Service", () => { } // The delegates from round 2 are now reversed in rank in round 3. - const delegatesRound3 = walletManager.loadActiveDelegateList(51, initialHeight + 51); + const roundInfo2 = roundCalculator.calculateRound(initialHeight + 51); + const delegatesRound3 = walletManager.loadActiveDelegateList(roundInfo2); for (let i = 0; i < delegatesRound3.length; i++) { expect(delegatesRound3[i].rate).toBe(i + 1); expect(delegatesRound3[i].publicKey).toBe(delegatesRound2[delegatesRound3.length - i - 1].publicKey); @@ -284,12 +276,14 @@ describe("Database Service", () => { }); // Finally recalculate the round 2 list and compare against the original list - const restoredDelegatesRound2 = await (databaseService as any).calcPreviousActiveDelegates(2); + const restoredDelegatesRound2 = await (databaseService as any).calcPreviousActiveDelegates(roundInfo2); for (let i = 0; i < restoredDelegatesRound2.length; i++) { expect(restoredDelegatesRound2[i].rate).toBe(i + 1); expect(restoredDelegatesRound2[i].publicKey).toBe(delegatesRound2[i].publicKey); } + + transactionHandler.canBeApplied = originalApply; }); }); }); diff --git a/__tests__/unit/core-database/mocks/core-container.ts b/__tests__/unit/core-database/mocks/core-container.ts new file mode 100644 index 0000000000..2cd322838a --- /dev/null +++ b/__tests__/unit/core-database/mocks/core-container.ts @@ -0,0 +1,35 @@ +import { emitter } from "./emitter"; + +jest.mock("@arkecosystem/core-container", () => { + return { + app: { + getConfig: () => { + return { + config: { milestones: [{ activeDelegates: 51, height: 1 }] }, + get: () => ({}), + getMilestone: () => ({ + activeDelegates: 51, + }), + }; + }, + has: () => true, + resolve: () => ({}), + resolvePlugin: name => { + if (name === "logger") { + return { + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + debug: jest.fn(), + }; + } + + if (name === "event-emitter") { + return emitter; + } + + return {}; + }, + }, + }; +}); diff --git a/__tests__/unit/core-database/mocks/emitter.ts b/__tests__/unit/core-database/mocks/emitter.ts new file mode 100644 index 0000000000..31b2125505 --- /dev/null +++ b/__tests__/unit/core-database/mocks/emitter.ts @@ -0,0 +1,5 @@ +export const emitter = { + on: jest.fn(), + emit: jest.fn(), + once: jest.fn(), +}; diff --git a/__tests__/unit/core-database/repositories/blocks-business-repository.test.ts b/__tests__/unit/core-database/repositories/blocks-business-repository.test.ts new file mode 100644 index 0000000000..374ea819d2 --- /dev/null +++ b/__tests__/unit/core-database/repositories/blocks-business-repository.test.ts @@ -0,0 +1,145 @@ +import { Database } from "@arkecosystem/core-interfaces"; +import { BlocksBusinessRepository } from "../../../../packages/core-database/src/repositories/blocks-business-repository"; +import { DatabaseConnectionStub } from "../__fixtures__/database-connection-stub"; +import { MockDatabaseModel } from "../__fixtures__/mock-database-model"; + +describe("Blocks Business Repository", () => { + let blocksBusinessRepository: Database.IBlocksBusinessRepository; + let databaseService: Database.IDatabaseService; + beforeEach(() => { + blocksBusinessRepository = new BlocksBusinessRepository(() => databaseService); + databaseService = { + connection: new DatabaseConnectionStub(), + } as Database.IDatabaseService; + }); + + describe("findAll", () => { + it("should invoke findAll on db repository", async () => { + databaseService.connection.blocksRepository = { + findAll: async params => params, + getModel: () => new MockDatabaseModel(), + } as Database.IBlocksRepository; + jest.spyOn(databaseService.connection.blocksRepository, "findAll").mockImplementation(async () => true); + + await blocksBusinessRepository.findAll({ + limit: 50, + offset: 20, + }); + + expect(databaseService.connection.blocksRepository.findAll).toHaveBeenCalledWith( + expect.objectContaining({ + orderBy: [{ field: "height", direction: "desc" }], + }), + ); + }); + }); + + describe("findByHeight", () => { + it("should invoke findByHeight on db repository", async () => { + databaseService.connection.blocksRepository = { + findByHeight: async id => id, + } as Database.IBlocksRepository; + jest.spyOn(databaseService.connection.blocksRepository, "findByHeight").mockImplementation( + async () => true, + ); + + await blocksBusinessRepository.findByHeight(1); + + expect(databaseService.connection.blocksRepository.findByHeight).toHaveBeenCalledWith(1); + }); + }); + + describe("findById", () => { + it("should invoke findById on db repository", async () => { + databaseService.connection.blocksRepository = { + findById: async id => id, + } as Database.IBlocksRepository; + jest.spyOn(databaseService.connection.blocksRepository, "findById").mockImplementation(async () => true); + + await blocksBusinessRepository.findById("some id"); + + expect(databaseService.connection.blocksRepository.findById).toHaveBeenCalledWith("some id"); + }); + }); + + describe("search", () => { + it("should invoke search on db repository", async () => { + databaseService.connection.blocksRepository = { + search: async params => params, + getModel: () => new MockDatabaseModel(), + } as Database.IBlocksRepository; + jest.spyOn(databaseService.connection.blocksRepository, "search").mockImplementation(async () => true); + + await blocksBusinessRepository.search({ + limit: 50, + offset: 20, + id: 20, + }); + + expect(databaseService.connection.blocksRepository.search).toHaveBeenCalledWith( + expect.objectContaining({ + parameters: [ + { + field: "id", + operator: expect.anything(), + value: 20, + }, + ], + orderBy: [{ field: "height", direction: "desc" }], + }), + ); + }); + }); + + describe("findAllByGenerator", () => { + it("should search by generatorPublicKey", async () => { + databaseService.connection.blocksRepository = { + findAll: async params => params, + getModel: () => new MockDatabaseModel(), + } as Database.IBlocksRepository; + jest.spyOn(databaseService.connection.blocksRepository, "findAll").mockImplementation(async () => true); + + await blocksBusinessRepository.findAllByGenerator("pubKey", { limit: 50, offset: 13 }); + + expect(databaseService.connection.blocksRepository.findAll).toHaveBeenCalledWith( + expect.objectContaining({ + parameters: [ + { + field: "generatorPublicKey", + operator: expect.anything(), + value: "pubKey", + }, + ], + paginate: { + offset: 13, + limit: 50, + }, + }), + ); + }); + }); + + describe("findLastByPublicKey", () => { + it("should search by publicKey", async () => { + databaseService.connection.blocksRepository = { + findAll: async params => params, + getModel: () => new MockDatabaseModel(), + } as Database.IBlocksRepository; + jest.spyOn(databaseService.connection.blocksRepository, "findAll").mockImplementation(async () => true); + + await blocksBusinessRepository.findLastByPublicKey("pubKey"); + + expect(databaseService.connection.blocksRepository.findAll).toHaveBeenCalledWith( + expect.objectContaining({ + parameters: [ + { + field: "generatorPublicKey", + operator: expect.anything(), + value: "pubKey", + }, + ], + }), + ); + }); + }); +}); diff --git a/packages/core-database/__tests__/repositories/delegates.test.ts b/__tests__/unit/core-database/repositories/delegates-business-repository.test.ts similarity index 56% rename from packages/core-database/__tests__/repositories/delegates.test.ts rename to __tests__/unit/core-database/repositories/delegates-business-repository.test.ts index f1803ec650..67d64cdcdb 100644 --- a/packages/core-database/__tests__/repositories/delegates.test.ts +++ b/__tests__/unit/core-database/repositories/delegates-business-repository.test.ts @@ -1,51 +1,30 @@ +import "../mocks/core-container"; + import { Database } from "@arkecosystem/core-interfaces"; import { delegateCalculator } from "@arkecosystem/core-utils"; -import { Bignum, constants, crypto, models } from "@arkecosystem/crypto"; -import genesisBlockTestnet from "../../../core-test-utils/src/config/testnet/genesisBlock.json"; -import { DelegatesRepository, WalletsRepository } from "../../src"; -import { DatabaseService } from "../../src/database-service"; -import { setUp, tearDown } from "../__support__/setup"; - -const { SATOSHI } = constants; -const { Block } = models; +import { Bignum, constants, crypto } from "@arkecosystem/crypto"; +import { DelegatesBusinessRepository, Wallet, WalletsBusinessRepository } from "../../../../packages/core-database/src"; +import { DatabaseService } from "../../../../packages/core-database/src/database-service"; +import { genesisBlock } from "../../../utils/fixtures/testnet/block-model"; -let genesisBlock; let repository; let walletsRepository: Database.IWalletsBusinessRepository; let walletManager: Database.IWalletManager; let databaseService: Database.IDatabaseService; -beforeAll(async done => { - await setUp(); - - // Create the genesis block after the setup has finished or else it uses a potentially - // wrong network config. - genesisBlock = new Block(genesisBlockTestnet); - - done(); -}); - -afterAll(async done => { - await tearDown(); - - done(); -}); - -beforeEach(async done => { - const { WalletManager } = require("../../src/wallet-manager"); +beforeEach(async () => { + const { WalletManager } = require("../../../../packages/core-database/src/wallet-manager"); walletManager = new WalletManager(); - repository = new DelegatesRepository(() => databaseService); - walletsRepository = new WalletsRepository(() => databaseService); - databaseService = new DatabaseService(null, null, walletManager, walletsRepository, repository); - - done(); + repository = new DelegatesBusinessRepository(() => databaseService); + walletsRepository = new WalletsBusinessRepository(() => databaseService); + databaseService = new DatabaseService(null, null, walletManager, walletsRepository, repository, null, null); }); -function generateWallets() { +function generateWallets(): Wallet[] { return genesisBlock.transactions.map((transaction, index) => { - const address = crypto.getAddress(transaction.senderPublicKey); + const address = crypto.getAddress(transaction.data.senderPublicKey); return { address, @@ -56,23 +35,42 @@ function generateWallets() { balance: new Bignum(100), voteBalance: new Bignum(200), rate: index + 1, - }; + } as Wallet; }); } describe("Delegate Repository", () => { describe("getLocalDelegates", () => { - const delegates = [{ username: "delegate-0" }, { username: "delegate-1" }, { username: "delegate-2" }]; - const wallets = [delegates[0], {}, delegates[1], { username: "" }, delegates[2], {}]; + const delegates = [ + { username: "delegate-0", forgedFees: new Bignum(10), forgedRewards: new Bignum(10) }, + { username: "delegate-1", forgedFees: new Bignum(20), forgedRewards: new Bignum(20) }, + { username: "delegate-2", forgedFees: new Bignum(30), forgedRewards: new Bignum(30) }, + ]; + + const wallets = [delegates[0], {}, delegates[1], { username: "" }, delegates[2], {}].map(delegate => { + const wallet = new Wallet(""); + return Object.assign(wallet, delegate); + }); it("should return the local wallets of the connection that are delegates", () => { + jest.spyOn(walletManager, "allByUsername").mockReturnValue(wallets); + + const actualDelegates = repository.getLocalDelegates(); + + expect(actualDelegates).toEqual(expect.arrayContaining(wallets)); + expect(walletManager.allByUsername).toHaveBeenCalled(); + }); + + it("should be ok with params (forgedTotal)", () => { // @ts-ignore jest.spyOn(walletManager, "allByAddress").mockReturnValue(wallets); - const actualDelegates = repository.getLocalDelegates(); + const actualDelegates = repository.getLocalDelegates({ forgedTotal: null }); - expect(actualDelegates).toEqual(expect.arrayContaining(delegates)); - expect(walletManager.allByAddress).toHaveBeenCalled(); + actualDelegates.forEach(delegate => { + expect(delegate.hasOwnProperty("forgedTotal")); + expect(+delegate.forgedTotal.toFixed()).toBe(delegateCalculator.calculateForgedTotal(delegate)); + }); }); }); @@ -212,6 +210,114 @@ describe("Delegate Repository", () => { }); }); + describe("by `usernames`", () => { + it("should search by exact match", () => { + const usernames = [ + "username-APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn", + "username-AG8kwwk4TsYfA2HdwaWBVAJQBj6VhdcpMo", + "username-AHXtmB84sTZ9Zd35h9Y1vfFvPE2Xzqj8ri", + ]; + const { count, rows } = repository.search({ usernames }); + + expect(count).toBe(3); + expect(rows).toHaveLength(3); + + rows.forEach(row => { + expect(usernames.includes(row.username)).toBeTrue(); + }); + }); + + describe('when a username is "undefined"', () => { + it("should return it", () => { + // Index a wallet with username "undefined" + walletManager.allByAddress()[0].username = "undefined"; + + const usernames = ["undefined"]; + const { count, rows } = repository.search({ usernames }); + + expect(count).toBe(1); + expect(rows).toHaveLength(1); + expect(rows[0].username).toEqual(usernames[0]); + }); + }); + + describe("when the username does not exist", () => { + it("should return no results", () => { + const { count, rows } = repository.search({ + usernames: ["unknown-dummy-username"], + }); + + expect(count).toBe(0); + expect(rows).toHaveLength(0); + }); + }); + + it("should be ok with params", () => { + const { count, rows } = repository.search({ + usernames: [ + "username-APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn", + "username-AG8kwwk4TsYfA2HdwaWBVAJQBj6VhdcpMo", + ], + offset: 1, + limit: 10, + }); + expect(count).toBe(2); + expect(rows).toHaveLength(1); + }); + + it("should be ok with params (no offset)", () => { + const { count, rows } = repository.search({ + usernames: [ + "username-APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn", + "username-AG8kwwk4TsYfA2HdwaWBVAJQBj6VhdcpMo", + ], + limit: 1, + }); + expect(count).toBe(2); + expect(rows).toHaveLength(1); + }); + + it("should be ok with params (offset = 0)", () => { + const { count, rows } = repository.search({ + usernames: [ + "username-APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn", + "username-AG8kwwk4TsYfA2HdwaWBVAJQBj6VhdcpMo", + ], + offset: 0, + limit: 2, + }); + expect(count).toBe(2); + expect(rows).toHaveLength(2); + }); + + it("should be ok with params (no limit)", () => { + const { count, rows } = repository.search({ + usernames: [ + "username-APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn", + "username-AG8kwwk4TsYfA2HdwaWBVAJQBj6VhdcpMo", + ], + offset: 1, + }); + expect(count).toBe(2); + expect(rows).toHaveLength(1); + }); + }); + + describe("when searching by `username` and `usernames`", () => { + it("should search delegates only by `username`", () => { + const username = "username-APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn"; + const usernames = [ + "username-AG8kwwk4TsYfA2HdwaWBVAJQBj6VhdcpMo", + "username-AHXtmB84sTZ9Zd35h9Y1vfFvPE2Xzqj8ri", + ]; + + const { count, rows } = repository.search({ username, usernames }); + + expect(count).toBe(1); + expect(rows).toHaveLength(1); + }); + }); + describe("when searching without params", () => { it("should return all results", () => { const { count, rows } = repository.search({}); @@ -257,34 +363,4 @@ describe("Delegate Repository", () => { expectWallet("username"); }); }); - - describe("getActiveAtHeight", () => { - it("should be ok", async () => { - const wallets = generateWallets(); - walletManager.index(wallets); - - const delegate = { - username: "test", - publicKey: "test", - voteBalance: new Bignum(10000 * SATOSHI), - producedBlocks: 1000, - missedBlocks: 500, - }; - const height = 1; - - // @ts-ignore - jest.spyOn(databaseService, "getActiveDelegates").mockReturnValue([delegate]); - // @ts-ignore - jest.spyOn(walletsRepository, "findById").mockReturnValue(delegate); - - const results = await repository.getActiveAtHeight(height); - - expect(results).toBeArray(); - expect(results[0].username).toBeString(); - expect(results[0].approval).toBeNumber(); - expect(results[0].productivity).toBeNumber(); - expect(results[0].approval).toBe(delegateCalculator.calculateApproval(delegate, height)); - expect(results[0].productivity).toBe(delegateCalculator.calculateProductivity(delegate)); - }); - }); }); diff --git a/__tests__/unit/core-database/repositories/transactions-business-repository.test.ts b/__tests__/unit/core-database/repositories/transactions-business-repository.test.ts new file mode 100644 index 0000000000..0a92928299 --- /dev/null +++ b/__tests__/unit/core-database/repositories/transactions-business-repository.test.ts @@ -0,0 +1,565 @@ +import { app } from "@arkecosystem/core-container"; +import { Database } from "@arkecosystem/core-interfaces"; +import { constants } from "@arkecosystem/crypto"; +import { TransactionsBusinessRepository } from "../../../../packages/core-database/src/repositories/transactions-business-repository"; +import { DatabaseConnectionStub } from "../__fixtures__/database-connection-stub"; +import { MockDatabaseModel } from "../__fixtures__/mock-database-model"; + +describe("Transactions Business Repository", () => { + let transactionsBusinessRepository: Database.ITransactionsBusinessRepository; + let databaseService: Database.IDatabaseService; + beforeEach(() => { + transactionsBusinessRepository = new TransactionsBusinessRepository(() => databaseService); + databaseService = { + connection: new DatabaseConnectionStub(), + } as Database.IDatabaseService; + }); + + describe("findAll", () => { + it("should invoke findAll on db repository", async () => { + databaseService.connection.transactionsRepository = { + findAll: async parameters => parameters, + getModel: () => new MockDatabaseModel(), + } as Database.ITransactionsRepository; + jest.spyOn(databaseService.connection.transactionsRepository, "findAll").mockImplementation(async () => ({ + rows: [], + })); + + await transactionsBusinessRepository.findAll( + { + id: "id", + offset: 10, + limit: 1000, + orderBy: "id:asc", + }, + "asc", + ); + + expect(databaseService.connection.transactionsRepository.findAll).toHaveBeenCalledWith( + expect.objectContaining({ + parameters: [ + { + field: "id", + operator: expect.anything(), + value: "id", + }, + ], + paginate: { + offset: 10, + limit: 1000, + }, + orderBy: expect.arrayContaining([ + { + field: "id", + direction: "asc", + }, + { + field: "sequence", + direction: "asc", + }, + ]), + }), + ); + }); + }); + + describe("allVotesBySender", () => { + it("should search by senderPublicKey and type=vote", async () => { + databaseService.connection.transactionsRepository = { + findAll: async parameters => parameters, + getModel: () => new MockDatabaseModel(), + } as Database.ITransactionsRepository; + jest.spyOn(databaseService.connection.transactionsRepository, "findAll").mockImplementation(async () => ({ + rows: [], + })); + + await transactionsBusinessRepository.allVotesBySender("pubKey"); + + expect(databaseService.connection.transactionsRepository.findAll).toHaveBeenCalledWith( + expect.objectContaining({ + parameters: expect.arrayContaining([ + { + field: "senderPublicKey", + operator: expect.anything(), + value: "pubKey", + }, + { + field: "type", + operator: expect.anything(), + value: constants.TransactionTypes.Vote, + }, + ]), + }), + ); + }); + }); + + describe("findAllByBlock", () => { + it("should search by blockId", async () => { + databaseService.connection.transactionsRepository = { + findAll: async parameters => parameters, + getModel: () => new MockDatabaseModel(), + } as Database.ITransactionsRepository; + jest.spyOn(databaseService.connection.transactionsRepository, "findAll").mockImplementation(async () => ({ + rows: [], + })); + + await transactionsBusinessRepository.findAllByBlock("blockId"); + + expect(databaseService.connection.transactionsRepository.findAll).toHaveBeenCalledWith( + expect.objectContaining({ + parameters: [ + { + field: "blockId", + operator: expect.anything(), + value: "blockId", + }, + ], + orderBy: [ + { + field: "timestamp", + direction: "desc", + }, + { + field: "sequence", + direction: "asc", + }, + ], + }), + ); + }); + }); + + describe("findAllByRecipient", () => { + it("should search by recipientId", async () => { + databaseService.connection.transactionsRepository = { + findAll: async parameters => parameters, + getModel: () => new MockDatabaseModel(), + } as Database.ITransactionsRepository; + jest.spyOn(databaseService.connection.transactionsRepository, "findAll").mockImplementation(async () => ({ + rows: [], + })); + + await transactionsBusinessRepository.findAllByRecipient("recipientId"); + + expect(databaseService.connection.transactionsRepository.findAll).toHaveBeenCalledWith( + expect.objectContaining({ + parameters: [ + { + field: "recipientId", + operator: expect.anything(), + value: "recipientId", + }, + ], + }), + ); + }); + }); + + describe("findAllBySender", () => { + it("should search senderPublicKey", async () => { + databaseService.connection.transactionsRepository = { + findAll: async parameters => parameters, + getModel: () => new MockDatabaseModel(), + } as Database.ITransactionsRepository; + jest.spyOn(databaseService.connection.transactionsRepository, "findAll").mockImplementation(async () => ({ + rows: [], + })); + + await transactionsBusinessRepository.findAllBySender("pubKey"); + + expect(databaseService.connection.transactionsRepository.findAll).toHaveBeenCalledWith( + expect.objectContaining({ + parameters: [ + { + field: "senderPublicKey", + operator: expect.anything(), + value: "pubKey", + }, + ], + }), + ); + }); + }); + + describe("findAllByType", () => { + it("should search transaction type", async () => { + databaseService.connection.transactionsRepository = { + findAll: async parameters => parameters, + getModel: () => new MockDatabaseModel(), + } as Database.ITransactionsRepository; + jest.spyOn(databaseService.connection.transactionsRepository, "findAll").mockImplementation(async () => ({ + rows: [], + })); + + await transactionsBusinessRepository.findAllByType(constants.TransactionTypes.Transfer); + + expect(databaseService.connection.transactionsRepository.findAll).toHaveBeenCalledWith( + expect.objectContaining({ + parameters: [ + { + field: "type", + operator: expect.anything(), + value: constants.TransactionTypes.Transfer, + }, + ], + }), + ); + }); + }); + + describe("findAllByWallet", () => { + it("should search by wallet", async () => { + databaseService.connection.transactionsRepository = { + findAllByWallet: async wallet => wallet, + getModel: () => new MockDatabaseModel(), + } as Database.ITransactionsRepository; + jest.spyOn(databaseService.connection.transactionsRepository, "findAllByWallet").mockImplementation( + async () => ({ rows: [] }), + ); + + await transactionsBusinessRepository.findAllByWallet({}); + + expect(databaseService.connection.transactionsRepository.findAllByWallet).toHaveBeenCalled(); + }); + }); + + describe("findById", () => { + it("should invoke findById on db repository", async () => { + const expectedBlockId = "id"; + const expectedHeight = 100; + databaseService.connection.transactionsRepository = { + findById: async id => id, + getModel: () => new MockDatabaseModel(), + } as Database.ITransactionsRepository; + jest.spyOn(databaseService.connection.transactionsRepository, "findById").mockImplementation(async () => ({ + rows: [ + { + blockId: expectedBlockId, + }, + ], + })); + databaseService.cache = new Map(); + jest.spyOn(databaseService.cache, "get").mockReturnValue(expectedHeight); + + await transactionsBusinessRepository.findById("id"); + + expect(databaseService.connection.transactionsRepository.findById).toHaveBeenCalledWith("id"); + }); + }); + + describe("findByTypeAndId", () => { + it("should search type & id", async () => { + databaseService.connection.transactionsRepository = { + findAll: async parameters => parameters, + getModel: () => new MockDatabaseModel(), + } as Database.ITransactionsRepository; + jest.spyOn(databaseService.connection.transactionsRepository, "findAll").mockImplementation(async () => ({ + rows: [], + })); + + await transactionsBusinessRepository.findByTypeAndId(constants.TransactionTypes.Transfer, "id"); + + expect(databaseService.connection.transactionsRepository.findAll).toHaveBeenCalledWith( + expect.objectContaining({ + parameters: [ + { + field: "type", + operator: expect.anything(), + value: constants.TransactionTypes.Transfer, + }, + { + field: "id", + operator: expect.anything(), + value: "id", + }, + ], + }), + ); + }); + }); + + describe("findWithVendorField", () => { + it("should invoke findWithVendorField on db repository", async () => { + databaseService.connection.transactionsRepository = { + findWithVendorField: async () => true, + getModel: () => new MockDatabaseModel(), + } as Database.ITransactionsRepository; + jest.spyOn(databaseService.connection.transactionsRepository, "findWithVendorField").mockImplementation( + async () => [], + ); + + await transactionsBusinessRepository.findWithVendorField(); + + expect(databaseService.connection.transactionsRepository.findWithVendorField).toHaveBeenCalled(); + }); + }); + + describe("getFeeStatistics", () => { + it("should invoke getFeeStatistics on db repository", async () => { + databaseService.connection.transactionsRepository = { + getFeeStatistics: async minFeeBroadcast => minFeeBroadcast, + getModel: () => new MockDatabaseModel(), + } as Database.ITransactionsRepository; + jest.spyOn(databaseService.connection.transactionsRepository, "getFeeStatistics").mockImplementation( + async () => true, + ); + jest.spyOn(app, "resolveOptions").mockReturnValue({ + dynamicFees: { + minFeeBroadcast: 100, + }, + }); + + await transactionsBusinessRepository.getFeeStatistics(); + + expect(databaseService.connection.transactionsRepository.getFeeStatistics).toHaveBeenCalledWith(100); + }); + }); + + describe("search", () => { + it("should invoke search on db repository", async () => { + databaseService.connection.transactionsRepository = { + search: async parameters => parameters, + getModel: () => new MockDatabaseModel(), + } as Database.ITransactionsRepository; + jest.spyOn(databaseService.connection.transactionsRepository, "search").mockImplementation(async () => ({ + rows: [], + })); + + await transactionsBusinessRepository.search({}); + + expect(databaseService.connection.transactionsRepository.search).toHaveBeenCalled(); + }); + + it("should return no rows if exception thrown", async () => { + databaseService.connection.transactionsRepository = { + search: async parameters => parameters, + getModel: () => new MockDatabaseModel(), + } as Database.ITransactionsRepository; + jest.spyOn(databaseService.connection.transactionsRepository, "search").mockImplementation(async () => { + throw new Error("bollocks"); + }); + + const result = await transactionsBusinessRepository.search({}); + + expect(databaseService.connection.transactionsRepository.search).toHaveBeenCalled(); + expect(result.rows).toHaveLength(0); + expect(result.count).toEqual(0); + }); + + it("should return no rows if senderId is an invalid address", async () => { + databaseService.walletManager = { + exists: addressOrPublicKey => false, + } as Database.IWalletManager; + databaseService.connection.transactionsRepository = { + search: async parameters => parameters, + getModel: () => new MockDatabaseModel(), + } as Database.ITransactionsRepository; + + const result = await transactionsBusinessRepository.search({ + senderId: "some invalid address", + }); + + expect(result.rows).toHaveLength(0); + expect(result.count).toEqual(0); + }); + + it("should lookup senders address from senderId", async () => { + databaseService.walletManager = { + exists: addressOrPublicKey => true, + findByAddress: address => ({ publicKey: "pubKey" }), + } as Database.IWalletManager; + + databaseService.connection.transactionsRepository = { + search: async parameters => parameters, + getModel: () => new MockDatabaseModel(), + } as Database.ITransactionsRepository; + jest.spyOn(databaseService.connection.transactionsRepository, "search").mockImplementation(async () => ({ + rows: [], + })); + + await transactionsBusinessRepository.search({ + senderId: "some invalid address", + }); + + expect(databaseService.connection.transactionsRepository.search).toHaveBeenCalledWith( + expect.objectContaining({ + parameters: [ + { + field: "senderPublicKey", + operator: expect.anything(), + value: "pubKey", + }, + ], + }), + ); + }); + + it("should lookup ownerIds wallet when searching", async () => { + databaseService.connection.transactionsRepository = { + search: async parameters => parameters, + getModel: () => new MockDatabaseModel(), + } as Database.ITransactionsRepository; + jest.spyOn(databaseService.connection.transactionsRepository, "search").mockImplementation(async () => ({ + rows: [], + })); + const expectedWallet = {}; + databaseService.walletManager = { + findByAddress: address => expectedWallet, + } as Database.IWalletManager; + + await transactionsBusinessRepository.search({ + ownerId: "ownerId", + }); + + expect(databaseService.connection.transactionsRepository.search).toHaveBeenCalledWith( + expect.objectContaining({ + parameters: [ + { + field: "ownerWallet", + operator: expect.anything(), + value: expectedWallet, + }, + ], + }), + ); + }); + + it("should set recipientId=addresses if former not supplied", async () => { + databaseService.connection.transactionsRepository = { + search: async parameters => parameters, + getModel: () => new MockDatabaseModel(), + } as Database.ITransactionsRepository; + jest.spyOn(databaseService.connection.transactionsRepository, "search").mockImplementation(async () => ({ + rows: [], + })); + + const addressList = ["addy1", "addy2"]; + const params = { + addresses: addressList, + senderPublicKey: "pubKey", + }; + await transactionsBusinessRepository.search(params); + + expect(databaseService.connection.transactionsRepository.search).toHaveBeenCalledWith( + expect.objectContaining({ + parameters: expect.arrayContaining([ + { + field: "recipientId", + operator: expect.anything(), + value: addressList, + }, + { + field: "senderPublicKey", + operator: expect.anything(), + value: "pubKey", + }, + ]), + }), + ); + }); + + it("should set senderPublicKey=addresses if former not supplied", async () => { + databaseService.connection.transactionsRepository = { + search: async parameters => parameters, + getModel: () => new MockDatabaseModel(), + } as Database.ITransactionsRepository; + jest.spyOn(databaseService.connection.transactionsRepository, "search").mockImplementation(async () => ({ + rows: [], + })); + const expectedWallet = {}; + databaseService.walletManager = { + exists: addressOrPublicKey => false, + } as Database.IWalletManager; + jest.spyOn(databaseService.walletManager, "exists").mockReturnValue(false); + + await transactionsBusinessRepository.search({ + addresses: ["addy1", "addy2"], + recipientId: "someId", + }); + + expect(databaseService.connection.transactionsRepository.search).toHaveBeenCalledWith( + expect.objectContaining({ + parameters: expect.arrayContaining([ + { + field: "recipientId", + operator: expect.anything(), + value: "someId", + }, + { + field: "senderPublicKey", + operator: expect.anything(), + value: expect.anything(), + }, + ]), + }), + ); + expect(databaseService.walletManager.exists).toHaveBeenNthCalledWith(1, "addy1"); + expect(databaseService.walletManager.exists).toHaveBeenNthCalledWith(2, "addy2"); + }); + + it("should cache blocks if cache-miss ", async () => { + const expectedBlockId = 1; + const expectedHeight = 100; + databaseService.connection.transactionsRepository = { + search: async parameters => parameters, + getModel: () => new MockDatabaseModel(), + } as Database.ITransactionsRepository; + jest.spyOn(databaseService.connection.transactionsRepository, "search").mockImplementation(async () => ({ + rows: [ + { + blockId: expectedBlockId, + }, + ], + })); + databaseService.connection.blocksRepository = { + findByIds: async id => [{}], + } as Database.IBlocksRepository; + jest.spyOn(databaseService.connection.blocksRepository, "findByIds").mockImplementation(async () => [ + { + id: expectedBlockId, + height: expectedHeight, + }, + ]); + databaseService.cache = new Map(); + jest.spyOn(databaseService.cache, "set").mockReturnThis(); + + await transactionsBusinessRepository.search({}); + + expect(databaseService.connection.blocksRepository.findByIds).toHaveBeenCalled(); + expect(databaseService.cache.set).toHaveBeenCalledWith(`heights:${expectedBlockId}`, expectedHeight); + }); + + it("should not cache blocks if cache-hit", async () => { + const expectedBlockId = 1; + const expectedHeight = 100; + databaseService.connection.transactionsRepository = { + search: async parameters => parameters, + getModel: () => new MockDatabaseModel(), + } as Database.ITransactionsRepository; + jest.spyOn(databaseService.connection.transactionsRepository, "search").mockImplementation(async () => ({ + rows: [ + { + blockId: expectedBlockId, + }, + ], + })); + databaseService.connection.blocksRepository = { + findByIds: async id => [{}], + } as Database.IBlocksRepository; + jest.spyOn(databaseService.connection.blocksRepository, "findByIds").mockImplementation(async () => [{}]); + databaseService.cache = new Map(); + jest.spyOn(databaseService.cache, "get").mockReturnValue(expectedHeight); + + const result = await transactionsBusinessRepository.search({}); + + expect(databaseService.connection.blocksRepository.findByIds).not.toHaveBeenCalled(); + expect(databaseService.cache.get).toHaveBeenCalledWith(`heights:${expectedBlockId}`); + expect(result.rows).toHaveLength(1); + expect(result.rows[0].block).toEqual({ + id: expectedBlockId, + height: expectedHeight, + }); + }); + }); +}); diff --git a/packages/core-database/__tests__/repositories/utils/filter-rows.test.ts b/__tests__/unit/core-database/repositories/utils/filter-rows.test.ts similarity index 91% rename from packages/core-database/__tests__/repositories/utils/filter-rows.test.ts rename to __tests__/unit/core-database/repositories/utils/filter-rows.test.ts index 42a70699ca..b54a6c2d08 100644 --- a/packages/core-database/__tests__/repositories/utils/filter-rows.test.ts +++ b/__tests__/unit/core-database/repositories/utils/filter-rows.test.ts @@ -1,21 +1,9 @@ import "jest-extended"; -import { setUp, tearDown } from "../../__support__/setup"; - let filterRows; -beforeAll(async done => { - await setUp(); - - filterRows = require("../../../src/repositories/utils/filter-rows"); - - done(); -}); - -afterAll(async done => { - await tearDown(); - - done(); +beforeAll(() => { + filterRows = require("../../../../../packages/core-database/src/repositories/utils/filter-rows"); }); describe("Filter Rows", () => { diff --git a/__tests__/unit/core-database/repositories/utils/search-parameter-converter.test.ts b/__tests__/unit/core-database/repositories/utils/search-parameter-converter.test.ts new file mode 100644 index 0000000000..6e9d5d68de --- /dev/null +++ b/__tests__/unit/core-database/repositories/utils/search-parameter-converter.test.ts @@ -0,0 +1,127 @@ +import { Database } from "@arkecosystem/core-interfaces"; +import { SearchParameterConverter } from "../../../../../packages/core-database/src/repositories/utils/search-parameter-converter"; +import { MockDatabaseModel } from "../../__fixtures__/mock-database-model"; + +describe("SearchParameterConverter", () => { + let searchParameterConverter: SearchParameterConverter; + beforeEach(() => { + searchParameterConverter = new SearchParameterConverter(new MockDatabaseModel()); + }); + + it("should parse all supported operators", () => { + const params = { + id: "343-guilty-spark", + timestamp: { from: "100", to: "1000" }, + sentence: "a partial", + basket: ["apples", "pears", "bananas"], + }; + + const searchParameters = searchParameterConverter.convert(params); + + expect(searchParameters.orderBy).toHaveLength(0); + expect(searchParameters.paginate).toBeNull(); + expect(searchParameters.parameters).toHaveLength(5); + expect(searchParameters.parameters[0].field).toEqual("id"); + expect(searchParameters.parameters[0].value).toEqual("343-guilty-spark"); + expect(searchParameters.parameters[0].operator).toEqual(Database.SearchOperator.OP_EQ); + + expect(searchParameters.parameters[1].field).toEqual("timestamp"); + expect(searchParameters.parameters[1].value).toEqual("100"); + expect(searchParameters.parameters[1].operator).toEqual(Database.SearchOperator.OP_GTE); + + expect(searchParameters.parameters[2].field).toEqual("timestamp"); + expect(searchParameters.parameters[2].value).toEqual("1000"); + expect(searchParameters.parameters[2].operator).toEqual(Database.SearchOperator.OP_LTE); + + expect(searchParameters.parameters[3].field).toEqual("sentence"); + expect(searchParameters.parameters[3].value).toEqual("%a partial%"); + expect(searchParameters.parameters[3].operator).toEqual(Database.SearchOperator.OP_LIKE); + + expect(searchParameters.parameters[4].field).toEqual("basket"); + expect(searchParameters.parameters[4].value).toEqual(["apples", "pears", "bananas"]); + expect(searchParameters.parameters[4].operator).toEqual(Database.SearchOperator.OP_IN); + }); + + it('should default to "equals" when from,to fields not set', () => { + const params = { + range: "10", + }; + + const searchParameters = searchParameterConverter.convert(params); + + expect(searchParameters.parameters).toHaveLength(1); + expect(searchParameters.parameters[0].field).toEqual("range"); + expect(searchParameters.parameters[0].value).toEqual("10"); + expect(searchParameters.parameters[0].operator).toEqual(Database.SearchOperator.OP_EQ); + }); + + it("should parse from,to fields when present", () => { + const params = { + range: { from: 10, to: 20 }, + }; + + const searchParameters = searchParameterConverter.convert(params); + + expect(searchParameters.parameters).toHaveLength(2); + expect(searchParameters.parameters[0].field).toEqual("range"); + expect(searchParameters.parameters[0].value).toEqual(10); + expect(searchParameters.parameters[0].operator).toEqual(Database.SearchOperator.OP_GTE); + + expect(searchParameters.parameters[1].field).toEqual("range"); + expect(searchParameters.parameters[1].value).toEqual(20); + expect(searchParameters.parameters[1].operator).toEqual(Database.SearchOperator.OP_LTE); + }); + + it("should parse unknown fields as custom", () => { + const params = { + john: "doe", + }; + + const searchParameters = searchParameterConverter.convert(params); + + expect(searchParameters.parameters).toHaveLength(1); + expect(searchParameters.parameters[0].field).toEqual("john"); + expect(searchParameters.parameters[0].value).toEqual("doe"); + expect(searchParameters.parameters[0].operator).toEqual(Database.SearchOperator.OP_CUSTOM); + }); + + it("should parse orderBy & paginate from params", () => { + const params = { + orderBy: "field:asc", + offset: 20, + limit: 50, + }; + + const searchParameters = searchParameterConverter.convert(params); + + expect(searchParameters.orderBy).toHaveLength(1); + expect(searchParameters.orderBy[0].field).toEqual("field"); + expect(searchParameters.orderBy[0].direction).toEqual("asc"); + + expect(searchParameters.paginate.offset).toEqual(20); + expect(searchParameters.paginate.limit).toEqual(50); + }); + + it("should should apply default paginate values", () => { + const params = { + offset: 1, + }; + + const searchParameters = searchParameterConverter.convert(params); + + expect(searchParameters.paginate.limit).toEqual(100); + expect(searchParameters.paginate.offset).toEqual(1); + }); + + it("should apply default paginate values if nonsensical data provided", () => { + const params = { + offset: NaN, + limit: NaN, + }; + + const searchParameters = searchParameterConverter.convert(params); + + expect(searchParameters.paginate.offset).toEqual(0); + expect(searchParameters.paginate.limit).toEqual(100); + }); +}); diff --git a/packages/core-database/__tests__/repositories/wallets.test.ts b/__tests__/unit/core-database/repositories/wallets-business-repository.test.ts similarity index 90% rename from packages/core-database/__tests__/repositories/wallets.test.ts rename to __tests__/unit/core-database/repositories/wallets-business-repository.test.ts index 7abc9c99d7..d2dbe4345f 100644 --- a/packages/core-database/__tests__/repositories/wallets.test.ts +++ b/__tests__/unit/core-database/repositories/wallets-business-repository.test.ts @@ -1,59 +1,43 @@ -import { Database } from "@arkecosystem/core-interfaces"; -import { Bignum, crypto, models } from "@arkecosystem/crypto"; -import compact from "lodash/compact"; -import uniq from "lodash/uniq"; -import genesisBlockTestnet from "../../../core-test-utils/src/config/testnet/genesisBlock.json"; -import { setUp, tearDown } from "../__support__/setup"; +import "../mocks/core-container"; -import { WalletsRepository } from "../../src"; -import { DatabaseService } from "../../src/database-service"; +import { Database } from "@arkecosystem/core-interfaces"; +import { Bignum, crypto } from "@arkecosystem/crypto"; +import compact from "lodash.compact"; +import uniq from "lodash.uniq"; +import { genesisBlock } from "../../../utils/fixtures/testnet/block-model"; -const { Block, Wallet } = models; +import { Wallet, WalletsBusinessRepository } from "../../../../packages/core-database/src"; +import { DatabaseService } from "../../../../packages/core-database/src/database-service"; -let genesisBlock; let genesisSenders; let repository; let walletManager: Database.IWalletManager; let databaseService: Database.IDatabaseService; -beforeAll(async done => { - await setUp(); - - // Create the genesis block after the setup has finished or else it uses a potentially - // wrong network config. - genesisBlock = new Block(genesisBlockTestnet); - genesisSenders = uniq(compact(genesisBlock.transactions.map(tx => tx.senderPublicKey))); - - done(); +beforeAll(() => { + genesisSenders = uniq(compact(genesisBlock.transactions.map(tx => tx.data.senderPublicKey))); }); -afterAll(async done => { - await tearDown(); - - done(); -}); - -beforeEach(async done => { - const { WalletManager } = require("../../src/wallet-manager"); +beforeEach(async () => { + const { WalletManager } = require("../../../../packages/core-database/src/wallet-manager"); walletManager = new WalletManager(); - repository = new WalletsRepository(() => databaseService); + repository = new WalletsBusinessRepository(() => databaseService); - databaseService = new DatabaseService(null, null, walletManager, repository, null); - - done(); + databaseService = new DatabaseService(null, null, walletManager, repository, null, null, null); }); function generateWallets() { - return genesisSenders.map(senderPublicKey => ({ + return genesisSenders.map((senderPublicKey, index) => ({ address: crypto.getAddress(senderPublicKey), + balance: new Bignum(index), })); } function generateVotes() { return genesisSenders.map(senderPublicKey => ({ address: crypto.getAddress(senderPublicKey), - vote: genesisBlock.transactions[0].senderPublicKey, + vote: genesisBlock.transactions[0].data.senderPublicKey, })); } @@ -67,8 +51,8 @@ function generateFullWallets() { secondPublicKey: `secondPublicKey-${address}`, vote: `vote-${address}`, username: `username-${address}`, - balance: 100, - voteBalance: 200, + balance: new Bignum(100), + voteBalance: new Bignum(200), }; }); } @@ -362,9 +346,9 @@ describe("Wallet Repository", () => { const wallets = generateFullWallets(); wallets.forEach((wallet, i) => { if (i < 13) { - wallet.balance = 53; + wallet.balance = new Bignum(53); } else if (i < 36) { - wallet.balance = 99; + wallet.balance = new Bignum(99); } }); walletManager.index(wallets); @@ -385,9 +369,9 @@ describe("Wallet Repository", () => { const wallets = generateFullWallets(); wallets.forEach((wallet, i) => { if (i < 17) { - wallet.voteBalance = 12; + wallet.voteBalance = new Bignum(12); } else if (i < 29) { - wallet.voteBalance = 17; + wallet.voteBalance = new Bignum(17); } }); walletManager.index(wallets); diff --git a/packages/core-database/__tests__/wallet-manager.test.ts b/__tests__/unit/core-database/wallet-manager.test.ts similarity index 84% rename from packages/core-database/__tests__/wallet-manager.test.ts rename to __tests__/unit/core-database/wallet-manager.test.ts index 37964fb4bc..0c005ac1b6 100644 --- a/packages/core-database/__tests__/wallet-manager.test.ts +++ b/__tests__/unit/core-database/wallet-manager.test.ts @@ -1,50 +1,32 @@ /* tslint:disable:max-line-length no-empty */ +import "./mocks/core-container"; + import { Database } from "@arkecosystem/core-interfaces"; -import { fixtures, generators } from "@arkecosystem/core-test-utils"; +import { InsufficientBalanceError } from "@arkecosystem/core-transactions/src/errors"; import { Bignum, constants, crypto, models, transactionBuilder } from "@arkecosystem/crypto"; -import { IMultiSignatureAsset } from "@arkecosystem/crypto/dist/models"; -import genesisBlockTestnet from "../../core-test-utils/src/config/testnet/genesisBlock.json"; +import { IMultiSignatureAsset, Transaction } from "@arkecosystem/crypto"; +import { Wallet } from "../../../packages/core-database/src"; +import { TransactionFactory } from "../../helpers/transaction-factory"; +import { fixtures } from "../../utils"; + import wallets from "./__fixtures__/wallets.json"; -import { setUp, tearDown } from "./__support__/setup"; -const { Block, Transaction, Wallet } = models; +const { Block } = models; const { SATOSHI, TransactionTypes } = constants; -const { generateDelegateRegistration, generateSecondSignature, generateTransfers, generateVote } = generators; - const block3 = fixtures.blocks2to100[1]; const block = new Block(block3); const walletData1 = wallets[0]; const walletData2 = wallets[1]; -let genesisBlock; let walletManager: Database.IWalletManager; -beforeAll(async done => { - await setUp(); - - // Create the genesis block after the setup has finished or else it uses a potentially - // wrong network config. - genesisBlock = new Block(genesisBlockTestnet); - - const { WalletManager } = require("../src/wallet-manager"); - walletManager = new WalletManager(); - - done(); -}); - beforeEach(() => { - const { WalletManager } = require("../src/wallet-manager"); + const { WalletManager } = require("../../../packages/core-database/src/wallet-manager"); walletManager = new WalletManager(); }); -afterAll(async done => { - await tearDown(); - - done(); -}); - describe("Wallet Manager", () => { describe("reset", () => { it("should reset the index", () => { @@ -71,11 +53,11 @@ describe("Wallet Manager", () => { describe("applyBlock", () => { let delegateMock; - let block2; + let block2: models.Block; const delegatePublicKey = block3.generatorPublicKey; // '0299deebff24ebf2bb53ad78f3ea3ada5b3c8819132e191b02c263ee4aa4af3d9b' - const txs = []; + const txs: Transaction[] = []; for (let i = 0; i < 3; i++) { txs[i] = transactionBuilder .vote() @@ -93,9 +75,9 @@ describe("Wallet Manager", () => { const { data } = block; data.transactions = []; - data.transactions.push(txs[0]); - data.transactions.push(txs[1]); - data.transactions.push(txs[2]); + data.transactions.push(txs[0].data); + data.transactions.push(txs[1].data); + data.transactions.push(txs[2].data); block2 = new Block(data); walletManager.reindex(delegateMock); @@ -175,12 +157,28 @@ describe("Wallet Manager", () => { }); describe("applyTransaction", () => { - describe("when the recipient is a cold wallet", () => {}); + it.todo("when the recipient is a cold wallet"); + + const transfer = TransactionFactory.transfer(walletData2.address, 96579) + .withNetwork("testnet") + .withPassphrase(Math.random().toString(36)) + .build()[0]; + + const delegateReg = TransactionFactory.delegateRegistration() + .withNetwork("testnet") + .withPassphrase(Math.random().toString(36)) + .build()[0]; + + const secondSign = TransactionFactory.secondSignature() + .withNetwork("testnet") + .withPassphrase(Math.random().toString(36)) + .build()[0]; + + const vote = TransactionFactory.vote(walletData2.publicKey) + .withNetwork("testnet") + .withPassphrase(Math.random().toString(36)) + .build()[0]; - const transfer = generateTransfers("testnet", Math.random().toString(36), walletData2.address, 96579, 1)[0]; - const delegateReg = generateDelegateRegistration("testnet", Math.random().toString(36), 1)[0]; - const secondSign = generateSecondSignature("testnet", Math.random().toString(36), 1)[0]; - const vote = generateVote("testnet", Math.random().toString(36), walletData2.publicKey, 1)[0]; describe.each` type | transaction | amount | balanceSuccess | balanceFail ${"transfer"} | ${transfer} | ${new Bignum(96579)} | ${new Bignum(SATOSHI)} | ${Bignum.ONE} @@ -196,7 +194,7 @@ describe("Wallet Manager", () => { recipient = new Wallet(walletData2.address); recipient.publicKey = walletData2.publicKey; - sender.publicKey = transaction.senderPublicKey; + sender.publicKey = transaction.data.senderPublicKey; walletManager.reindex(sender); walletManager.reindex(recipient); @@ -213,7 +211,7 @@ describe("Wallet Manager", () => { await walletManager.applyTransaction(transaction); - expect(sender.balance).toEqual(balanceSuccess.minus(amount).minus(transaction.fee)); + expect(sender.balance).toEqual(balanceSuccess.minus(amount).minus(transaction.data.fee)); if (type === "transfer") { expect(recipient.balance).toEqual(amount); @@ -227,10 +225,7 @@ describe("Wallet Manager", () => { expect(+recipient.balance.toFixed()).toBe(0); try { - expect(async () => { - await walletManager.applyTransaction(transaction); - }).toThrow(/apply transaction/); - + expect(walletManager.applyTransaction(transaction)).rejects.toThrow(InsufficientBalanceError); expect(null).toBe("this should fail if no error is thrown"); } catch (error) { expect(+sender.balance.toFixed()).toBe(+balanceFail); @@ -242,7 +237,7 @@ describe("Wallet Manager", () => { describe("revertTransaction", () => { it("should revert the transaction from the sender & recipient", async () => { - const transaction = new Transaction({ + const transaction = Transaction.fromData({ type: TransactionTypes.Transfer, amount: new Bignum(245098000000000), fee: 0, @@ -295,7 +290,7 @@ describe("Wallet Manager", () => { walletManager.applyTransaction(voteTransaction); - expect(voter.balance).toEqual(new Bignum(100_000).minus(voteTransaction.fee)); + expect(voter.balance).toEqual(new Bignum(100_000).minus(voteTransaction.data.fee)); expect(delegate.voteBalance).toEqual(new Bignum(100_000_000).plus(voter.balance)); walletManager.revertTransaction(voteTransaction); @@ -331,7 +326,7 @@ describe("Wallet Manager", () => { walletManager.applyTransaction(voteTransaction); - expect(voter.balance).toEqual(new Bignum(100_000).minus(voteTransaction.fee)); + expect(voter.balance).toEqual(new Bignum(100_000).minus(voteTransaction.data.fee)); expect(delegate.voteBalance).toEqual(new Bignum(100_000_000).plus(voter.balance)); const unvoteTransaction = transactionBuilder @@ -343,12 +338,14 @@ describe("Wallet Manager", () => { walletManager.applyTransaction(unvoteTransaction); - expect(voter.balance).toEqual(new Bignum(100_000).minus(voteTransaction.fee).minus(unvoteTransaction.fee)); + expect(voter.balance).toEqual( + new Bignum(100_000).minus(voteTransaction.data.fee).minus(unvoteTransaction.data.fee), + ); expect(delegate.voteBalance).toEqual(new Bignum(100_000_000)); walletManager.revertTransaction(unvoteTransaction); - expect(voter.balance).toEqual(new Bignum(100_000).minus(voteTransaction.fee)); + expect(voter.balance).toEqual(new Bignum(100_000).minus(voteTransaction.data.fee)); expect(delegate.voteBalance).toEqual(new Bignum(100_000_000).plus(voter.balance)); }); }); @@ -517,4 +514,34 @@ describe("Wallet Manager", () => { } }); }); + + describe("buildDelegateRanking", () => { + it("should build ranking and sort delegates by vote balance", async () => { + for (let i = 0; i < 5; i++) { + const delegateKey = i.toString().repeat(66); + const delegate = new Wallet(crypto.getAddress(delegateKey)); + delegate.publicKey = delegateKey; + delegate.username = `delegate${i}`; + delegate.voteBalance = Bignum.ZERO; + + const voter = new Wallet(crypto.getAddress((i + 5).toString().repeat(66))); + voter.balance = new Bignum((i + 1) * 1000 * SATOSHI); + voter.publicKey = `v${delegateKey}`; + voter.vote = delegateKey; + + walletManager.index([delegate, voter]); + } + + walletManager.buildVoteBalances(); + + let delegates = walletManager.allByUsername(); + delegates = walletManager.buildDelegateRanking(delegates); + + for (let i = 0; i < 5; i++) { + const delegate = delegates[i]; + expect(delegate.rate).toEqual(i + 1); + expect(delegate.voteBalance).toEqual(new Bignum((5 - i) * 1000 * SATOSHI)); + } + }); + }); }); diff --git a/packages/crypto/__tests__/models/wallet.test.ts b/__tests__/unit/core-database/wallet.test.ts similarity index 76% rename from packages/crypto/__tests__/models/wallet.test.ts rename to __tests__/unit/core-database/wallet.test.ts index b85e0fe798..92de180b9d 100644 --- a/packages/crypto/__tests__/models/wallet.test.ts +++ b/__tests__/unit/core-database/wallet.test.ts @@ -1,18 +1,13 @@ import "jest-extended"; -import { SATOSHI, TransactionTypes } from "../../src/constants"; -import { transactionHandler } from "../../src/handlers/transactions"; -import { configManager } from "../../src/managers/config"; -import { Wallet } from "../../src/models/wallet"; -import { Bignum } from "../../src/utils"; +import { Bignum, configManager, constants } from "@arkecosystem/crypto"; +import { Wallet } from "../../../packages/core-database/src"; +import { TransactionFactory } from "../../helpers/transaction-factory"; -import { generators } from "@arkecosystem/core-test-utils"; -const { generateTransfers, generateDelegateRegistration, generateSecondSignature, generateVote } = generators; -import { devnet } from "../../src/networks"; -import { multiTransaction } from "../fixtures/multi-transaction"; +const { SATOSHI, TransactionTypes } = constants; describe("Models - Wallet", () => { - beforeEach(() => configManager.setConfig(devnet)); + beforeEach(() => configManager.setFromPreset("devnet")); describe("toString", () => { // TODO implementation is right? @@ -25,29 +20,6 @@ describe("Models - Wallet", () => { }); }); - describe("apply transaction", () => { - const testWallet = new Wallet("D61xc3yoBQDitwjqUspMPx1ooET6r1XLt7"); - const data = { - publicKey: "02337316a26d8d49ec27059bd0589c49ba474029c3627715380f4df83fb431aece", - secondPublicKey: "020d3c837d0a47ee7de1082cd48885003c5e92964e58bb34af3b58c6e42208ae03", - balance: new Bignum(109390000000), - vote: null, - username: null, - voteBalance: Bignum.ZERO, - multisignature: null, - dirty: false, - producedBlocks: 0, - missedBlocks: 0, - }; - - it.skip("should be ok for a multi-transaction", () => { - Object.keys(data).forEach(k => { - testWallet[k] = data[k]; - }); - expect(testWallet.canApply(multiTransaction, [])).toBeTrue(); - }); - }); - describe("apply block", () => { let testWallet; let block; @@ -76,7 +48,6 @@ describe("Models - Wallet", () => { expect(testWallet.forgedFees).toEqual(block.totalFee); expect(testWallet.forgedRewards).toEqual(block.totalFee); expect(testWallet.lastBlock).toBeObject(); - expect(testWallet.dirty).toBeTrue(); }); it("should not apply incorrect block", () => { @@ -88,7 +59,6 @@ describe("Models - Wallet", () => { expect(testWallet.forgedFees).toEqual(originalWallet.forgedFees); expect(testWallet.forgedRewards).toEqual(originalWallet.forgedRewards); expect(testWallet.lastBlock).toBe(originalWallet.lastBlock); - expect(testWallet.dirty).toBeTrue(); }); }); @@ -125,7 +95,6 @@ describe("Models - Wallet", () => { expect(testWallet.forgedFees).toEqual(walletInit.forgedFees.minus(block.totalFee)); expect(testWallet.forgedRewards).toEqual(walletInit.forgedRewards.minus(block.reward)); expect(testWallet.lastBlock).toBeNull(); - expect(testWallet.dirty).toBeTrue(); }); it("should revert block if generator public key matches the wallet address", () => { @@ -138,7 +107,6 @@ describe("Models - Wallet", () => { expect(testWallet.forgedFees).toEqual(walletInit.forgedFees.minus(block.totalFee)); expect(testWallet.forgedRewards).toEqual(walletInit.forgedRewards.minus(block.reward)); expect(testWallet.lastBlock).toBeNull(); - expect(testWallet.dirty).toBeTrue(); }); it("should not revert block if generator public key doesn't match the wallet address / publicKey", () => { @@ -153,34 +121,6 @@ describe("Models - Wallet", () => { }); }); - describe("calling transactionHandler canApply, applyTransaction, revertTransaction", () => { - const mockBackup = { - canApply: transactionHandler.canApply, - applyTransactionToSender: transactionHandler.applyTransactionToSender, - revertTransactionForSender: transactionHandler.revertTransactionForSender, - applyTransactionToRecipient: transactionHandler.applyTransactionToRecipient, - revertTransactionForRecipient: transactionHandler.revertTransactionForRecipient, - }; - let testWallet; - - beforeEach(() => { - Object.assign(transactionHandler, mockBackup); - testWallet = new Wallet("D61xc3yoBQDitwjqUspMPx1ooET6r1XLt7"); - }); - - afterAll(() => { - Object.assign(transactionHandler, mockBackup); - }); - - it.each(Object.keys(mockBackup))("should call transactionHandler %s with correct parameters", fn => { - transactionHandler[fn] = jest.fn(); - const transaction = { id: 123456 }; - const params = fn === "canApply" ? [transaction, []] : [transaction]; - testWallet[fn](...params); - expect(transactionHandler[fn]).toHaveBeenCalledWith(testWallet, ...params); - }); - }); - describe("audit transaction - auditApply", () => { const walletInit = { balance: new Bignum(1000 * SATOSHI), @@ -196,7 +136,10 @@ describe("Models - Wallet", () => { const generateTransactionType = (type, asset = {}) => { // use 2nd signature as a base - const transaction = generateSecondSignature("devnet", "super secret passphrase", 1, true)[0]; + const transaction = TransactionFactory.secondSignature() + .withNetwork("devnet") + .withPassphrase("super secret passphrase") + .create()[0]; return Object.assign(transaction, { type, asset }); }; @@ -206,14 +149,10 @@ describe("Models - Wallet", () => { }); it("should return correct audit data for Transfer type", () => { - const transaction = generateTransfers( - "devnet", - "super secret passphrase", - "D61xc3yoBQDitwjqUspMPx1ooET6r1XLt7", - SATOSHI, - 1, - true, - )[0]; + const transaction = TransactionFactory.transfer("D61xc3yoBQDitwjqUspMPx1ooET6r1XLt7") + .withNetwork("devnet") + .withPassphrase("super secret passphrase") + .create()[0]; const audit = testWallet.auditApply(transaction); expect(audit).toEqual([ @@ -226,7 +165,10 @@ describe("Models - Wallet", () => { }); it("should return correct audit data for 2nd signature type", () => { - const transaction = generateSecondSignature("devnet", "super secret passphrase", 1, true)[0]; + const transaction = TransactionFactory.secondSignature() + .withNetwork("devnet") + .withPassphrase("super secret passphrase") + .create()[0]; const audit = testWallet.auditApply(transaction); expect(audit).toEqual([ @@ -239,7 +181,10 @@ describe("Models - Wallet", () => { }); it("should return correct audit data for delegate registration type", () => { - const transaction = generateDelegateRegistration("devnet", "super secret passphrase", 1, true)[0]; + const transaction = TransactionFactory.delegateRegistration() + .withNetwork("devnet") + .withPassphrase("super secret passphrase") + .create()[0]; const audit = testWallet.auditApply(transaction); expect(audit).toEqual([ @@ -253,13 +198,12 @@ describe("Models - Wallet", () => { }); it("should return correct audit data for vote type", () => { - const transaction = generateVote( - "devnet", - "super secret passphrase", + const transaction = TransactionFactory.vote( "02337316a26d8d49ec27059bd0589c49ba474029c3627715380f4df83fb431aece", - 1, - true, - )[0]; + ) + .withNetwork("devnet") + .withPassphrase("super secret passphrase") + .create()[0]; const audit = testWallet.auditApply(transaction); expect(audit).toEqual([ @@ -366,14 +310,10 @@ describe("Models - Wallet", () => { describe("when wallet has multisignature", () => { it("should return correct audit data for Transfer type", () => { - const transaction = generateTransfers( - "devnet", - "super secret passphrase", - "D61xc3yoBQDitwjqUspMPx1ooET6r1XLt7", - SATOSHI, - 1, - true, - )[0]; + const transaction = TransactionFactory.transfer("D61xc3yoBQDitwjqUspMPx1ooET6r1XLt7") + .withNetwork("devnet") + .withPassphrase("super secret passphrase") + .create()[0]; testWallet.multisignature = { keysgroup: [ "+02db1d199f20038e569500895b3521a453b2924e4a07c75aa9f7bf2aa4ad71392d", @@ -395,17 +335,13 @@ describe("Models - Wallet", () => { describe("when wallet has 2nd public key", () => { it("should return correct audit data for Transfer type", () => { - const transaction = generateTransfers( - "devnet", - { + const transaction = TransactionFactory.transfer("D61xc3yoBQDitwjqUspMPx1ooET6r1XLt7") + .withNetwork("devnet") + .withPassphrasePair({ passphrase: "super secret passphrase", secondPassphrase: "super secret secondpassphrase", - }, - "D61xc3yoBQDitwjqUspMPx1ooET6r1XLt7", - SATOSHI, - 1, - true, - )[0]; + }) + .create()[0]; testWallet.secondPublicKey = "02db1d199f20038e569500895b3521a453b2924e4a07c75aa9f7bf2aa4ad71392d"; const audit = testWallet.auditApply(transaction); diff --git a/packages/core-event-emitter/__tests__/emitter.test.ts b/__tests__/unit/core-event-emitter/emitter.test.ts similarity index 73% rename from packages/core-event-emitter/__tests__/emitter.test.ts rename to __tests__/unit/core-event-emitter/emitter.test.ts index 45d997fa12..8c06e6bb4e 100755 --- a/packages/core-event-emitter/__tests__/emitter.test.ts +++ b/__tests__/unit/core-event-emitter/emitter.test.ts @@ -1,5 +1,5 @@ -import EventEmitter from "eventemitter3"; -import { plugin } from "../src"; +import { plugin } from "../../../packages/core-event-emitter/src"; +import { EventEmitter } from "../../../packages/core-event-emitter/src/emitter"; const emitter = plugin.register(); diff --git a/packages/core-forger/__tests__/__fixtures__/block.ts b/__tests__/unit/core-forger/__fixtures__/block.ts similarity index 86% rename from packages/core-forger/__tests__/__fixtures__/block.ts rename to __tests__/unit/core-forger/__fixtures__/block.ts index 58780a3a8b..2df42e9c66 100644 --- a/packages/core-forger/__tests__/__fixtures__/block.ts +++ b/__tests__/unit/core-forger/__fixtures__/block.ts @@ -1,4 +1,6 @@ -import { models } from "@arkecosystem/crypto"; +import { client, models } from "@arkecosystem/crypto"; + +client.getConfigManager().setFromPreset("unitnet"); export const sampleBlock = new models.Block({ id: "4398082439836560423", diff --git a/packages/core-forger/__tests__/__fixtures__/delegate.ts b/__tests__/unit/core-forger/__fixtures__/delegate.ts similarity index 100% rename from packages/core-forger/__tests__/__fixtures__/delegate.ts rename to __tests__/unit/core-forger/__fixtures__/delegate.ts diff --git a/packages/core-forger/__tests__/__fixtures__/transaction.ts b/__tests__/unit/core-forger/__fixtures__/transaction.ts similarity index 82% rename from packages/core-forger/__tests__/__fixtures__/transaction.ts rename to __tests__/unit/core-forger/__fixtures__/transaction.ts index 556098b367..1325162678 100644 --- a/packages/core-forger/__tests__/__fixtures__/transaction.ts +++ b/__tests__/unit/core-forger/__fixtures__/transaction.ts @@ -1,6 +1,4 @@ -import { models } from "@arkecosystem/crypto"; - -export const sampleTransaction = new models.Transaction({ +export const sampleTransaction = { type: 0, amount: 245098000000000, fee: 0, @@ -12,4 +10,4 @@ export const sampleTransaction = new models.Transaction({ // tslint:disable-next-line:max-line-length "304402205fcb0677e06bde7aac3dc776665615f4b93ef8c3ed0fddecef9900e74fcb00f302206958a0c9868ea1b1f3d151bdfa92da1ce24de0b1fcd91933e64fb7971e92f48d", id: "db1aa687737858cc9199bfa336f9b1c035915c30aaee60b1e0f8afadfdb946bd", -}); +}; diff --git a/packages/core-forger/__tests__/manager.test.ts b/__tests__/unit/core-forger/manager.test.ts similarity index 63% rename from packages/core-forger/__tests__/manager.test.ts rename to __tests__/unit/core-forger/manager.test.ts index 69c731bf2e..fb5c9c2f66 100644 --- a/packages/core-forger/__tests__/manager.test.ts +++ b/__tests__/unit/core-forger/manager.test.ts @@ -1,30 +1,25 @@ -import { generators } from "@arkecosystem/core-test-utils"; +import "./mocks/core-container"; + import "jest-extended"; import { NetworkState, NetworkStateStatus } from "@arkecosystem/core-p2p"; -import { Bignum, models } from "@arkecosystem/crypto"; -import { testnet } from "../../crypto/src/networks"; -import { defaults } from "../src/defaults"; -import { ForgerManager } from "../src/manager"; +import { models, Transaction } from "@arkecosystem/crypto"; +import { defaults } from "../../../packages/core-forger/src/defaults"; +import { ForgerManager } from "../../../packages/core-forger/src/manager"; +import { testnet } from "../../../packages/crypto/src/networks"; +import { TransactionFactory } from "../../helpers/transaction-factory"; import { sampleBlock } from "./__fixtures__/block"; import { delegate } from "./__fixtures__/delegate"; import { sampleTransaction } from "./__fixtures__/transaction"; -import { setUp, tearDown } from "./__support__/setup"; -const { Delegate, Transaction } = models; -const { generateTransfers } = generators; +const { Delegate } = models; jest.setTimeout(30000); -jest.mock("../src/client"); +jest.mock("../../../packages/core-forger/src/client"); let forgeManager; -beforeAll(async () => { - await setUp(); -}); - afterAll(async () => { - await tearDown(); jest.restoreAllMocks(); }); @@ -38,28 +33,25 @@ describe("Forger Manager", () => { it("should be ok with configured delegates", async () => { const secret = "a secret"; forgeManager.secrets = [secret]; - // @ts-ignore - forgeManager.client.getUsernames.mockReturnValue([]); const delegates = await forgeManager.loadDelegates(); expect(delegates).toBeArray(); delegates.forEach(value => expect(value).toBeInstanceOf(Delegate)); - expect(forgeManager.client.getUsernames).toHaveBeenCalled(); }); }); - describe("__forgeNewBlock", () => { + describe("forgeNewBlock", () => { it("should forge a block", async () => { // NOTE: make sure we have valid transactions from an existing wallet - const transactions = generateTransfers( - "testnet", - "clay harbor enemy utility margin pretty hub comic piece aerobic umbrella acquire", - ); + const transactions = TransactionFactory.transfer() + .withNetwork("testnet") + .withPassphrase("clay harbor enemy utility margin pretty hub comic piece aerobic umbrella acquire") + .build(); // @ts-ignore forgeManager.client.getTransactions.mockReturnValue({ - transactions: transactions.map(tx => tx.serialized), + transactions: transactions.map(tx => tx.serialized.toString("hex")), }); forgeManager.usernames = []; @@ -71,7 +63,10 @@ describe("Forger Manager", () => { reward: 2 * 1e8, }; - await forgeManager.__forgeNewBlock(del, round); + await forgeManager.forgeNewBlock(del, round, { + lastBlockId: round.lastBlock.id, + nodeHeight: round.lastBlock.height, + }); expect(forgeManager.client.broadcast).toHaveBeenCalledWith( expect.objectContaining({ @@ -86,7 +81,7 @@ describe("Forger Manager", () => { describe("__monitor", () => { it("should emit failed event if error while monitoring", async () => { - forgeManager.client.getUsernames.mockRejectedValue(new Error("oh bollocks")); + forgeManager.client.getRound.mockRejectedValue(new Error("oh bollocks")); setTimeout(() => forgeManager.stop(), 1000); await forgeManager.__monitor(); @@ -95,12 +90,12 @@ describe("Forger Manager", () => { }); }); - describe("__getTransactionsForForging", () => { + describe("getTransactionsForForging", () => { it("should return zero transactions if none to forge", async () => { // @ts-ignore forgeManager.client.getTransactions.mockReturnValue({}); - const transactions = await forgeManager.__getTransactionsForForging(); + const transactions = await forgeManager.getTransactionsForForging(); expect(transactions).toHaveLength(0); expect(forgeManager.client.getTransactions).toHaveBeenCalled(); @@ -108,44 +103,24 @@ describe("Forger Manager", () => { it("should return deserialized transactions", async () => { // @ts-ignore forgeManager.client.getTransactions.mockReturnValue({ - transactions: [Transaction.serialize(sampleTransaction).toString("hex")], + transactions: [Transaction.fromData(sampleTransaction).serialized.toString("hex")], }); - const transactions = await forgeManager.__getTransactionsForForging(); + const transactions = await forgeManager.getTransactionsForForging(); expect(transactions).toHaveLength(1); expect(forgeManager.client.getTransactions).toHaveBeenCalled(); - expect(transactions[0]).toBeInstanceOf(Transaction); - expect(transactions[0].data.recipientId).toEqual(sampleTransaction.data.recipientId); - expect(transactions[0].data.senderPublicKey).toEqual(sampleTransaction.data.senderPublicKey); - }); - }); - - describe("__isDelegateActivated", () => { - it("should be ok", async () => { - forgeManager.delegates = [ - { - username: "arkxdev", - publicKey: "0310ad026647eed112d1a46145eed58b8c19c67c505a67f1199361a511ce7860c0", - }, - ]; - - const forger = await forgeManager.__isDelegateActivated( - "0310ad026647eed112d1a46145eed58b8c19c67c505a67f1199361a511ce7860c0", - ); - - expect(forger).toBeObject(); - expect(forger.username).toBe("arkxdev"); - expect(forger.publicKey).toBe("0310ad026647eed112d1a46145eed58b8c19c67c505a67f1199361a511ce7860c0"); + expect(transactions[0].recipientId).toEqual(sampleTransaction.recipientId); + expect(transactions[0].senderPublicKey).toEqual(sampleTransaction.senderPublicKey); }); }); - describe("__parseNetworkState", () => { + describe("parseNetworkState", () => { it("should be TRUE when quorum > 0.66", async () => { const networkState = new NetworkState(NetworkStateStatus.Default); Object.assign(networkState, { getQuorum: () => 0.9, nodeHeight: 100, lastBlockId: "1233443" }); - const canForge = await forgeManager.__parseNetworkState(networkState, delegate); + const canForge = await forgeManager.parseNetworkState(networkState, delegate); expect(canForge).toBeTrue(); }); @@ -154,7 +129,7 @@ describe("Forger Manager", () => { const networkState = new NetworkState(NetworkStateStatus.Unknown); Object.assign(networkState, { getQuorum: () => 1, nodeHeight: 100, lastBlockId: "1233443" }); - const canForge = await forgeManager.__parseNetworkState(networkState, delegate); + const canForge = await forgeManager.parseNetworkState(networkState, delegate); expect(canForge).toBeFalse(); }); @@ -163,21 +138,21 @@ describe("Forger Manager", () => { const networkState = new NetworkState(NetworkStateStatus.Default); Object.assign(networkState, { getQuorum: () => 0.65, nodeHeight: 100, lastBlockId: "1233443" }); - const canForge = await forgeManager.__parseNetworkState(networkState, delegate); + const canForge = await forgeManager.parseNetworkState(networkState, delegate); expect(canForge).toBeFalse(); }); it("should be FALSE when coldStart is active", async () => { const networkState = new NetworkState(NetworkStateStatus.ColdStart); - const canForge = await forgeManager.__parseNetworkState(networkState, delegate); + const canForge = await forgeManager.parseNetworkState(networkState, delegate); expect(canForge).toBeFalse(); }); it("should be FALSE when minimumNetworkReach is not sufficient", async () => { const networkState = new NetworkState(NetworkStateStatus.BelowMinimumPeers); - const canForge = await forgeManager.__parseNetworkState(networkState, delegate); + const canForge = await forgeManager.parseNetworkState(networkState, delegate); expect(canForge).toBeFalse(); }); @@ -201,7 +176,7 @@ describe("Forger Manager", () => { }, }); - const canForge = await forgeManager.__parseNetworkState(networkState, delegate); + const canForge = await forgeManager.parseNetworkState(networkState, delegate); expect(canForge).toBeFalse(); }); }); diff --git a/__tests__/unit/core-forger/mocks/core-container.ts b/__tests__/unit/core-forger/mocks/core-container.ts new file mode 100644 index 0000000000..fab1b79e7b --- /dev/null +++ b/__tests__/unit/core-forger/mocks/core-container.ts @@ -0,0 +1,23 @@ +jest.mock("@arkecosystem/core-container", () => { + return { + app: { + getConfig: () => { + return { + get: () => ({}), + }; + }, + resolvePlugin: name => { + if (name === "logger") { + return { + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + debug: jest.fn(), + }; + } + + return {}; + }, + }, + }; +}); diff --git a/packages/core-http-utils/__tests__/__support__/mocks/core-container.ts b/__tests__/unit/core-http-utils/mocks/core-container.ts similarity index 100% rename from packages/core-http-utils/__tests__/__support__/mocks/core-container.ts rename to __tests__/unit/core-http-utils/mocks/core-container.ts diff --git a/packages/core-http-utils/__tests__/plugins/content-type.test.ts b/__tests__/unit/core-http-utils/plugins/content-type.test.ts similarity index 57% rename from packages/core-http-utils/__tests__/plugins/content-type.test.ts rename to __tests__/unit/core-http-utils/plugins/content-type.test.ts index 7f9a3df0fc..4d04402b24 100644 --- a/packages/core-http-utils/__tests__/plugins/content-type.test.ts +++ b/__tests__/unit/core-http-utils/plugins/content-type.test.ts @@ -1,9 +1,9 @@ -import "../__support__/mocks/core-container"; +import "../mocks/core-container"; -import axios from "axios"; -import { contentType } from "../../src/plugins/content-type"; -import { createServer } from "../../src/server/create"; -import { mountServer } from "../../src/server/mount"; +import got from "got"; +import { contentType } from "../../../../packages/core-http-utils/src/plugins/content-type"; +import { createServer } from "../../../../packages/core-http-utils/src/server/create"; +import { mountServer } from "../../../../packages/core-http-utils/src/server/mount"; let server; beforeAll(async () => { @@ -17,7 +17,7 @@ beforeAll(async () => { server.route({ method: "GET", path: "/", - handler: (request, h) => "Hello!", + handler: () => "Hello!", }); await mountServer("Dummy", server); @@ -30,20 +30,20 @@ afterAll(async () => { describe("Plugins - Content-Type", () => { describe("GET /", () => { it("should return code 200", async () => { - const response = await axios.get("http://0.0.0.0:3000/", { + const response = await got.get("http://0.0.0.0:3000/", { headers: { "Content-Type": "application/json" }, }); - expect(response.status).toBe(200); + expect(response.statusCode).toBe(200); }); it("should return code 415", async () => { try { - await axios.get("http://0.0.0.0:3000/", { + await got.get("http://0.0.0.0:3000/", { headers: { "Content-Type": "application/text" }, }); } catch (e) { - expect(e.response.status).toBe(415); + expect(e.response.statusCode).toBe(415); } }); }); diff --git a/packages/core-jest-matchers/__tests__/api/block.test.ts b/__tests__/unit/core-jest-matchers/api/block.test.ts similarity index 95% rename from packages/core-jest-matchers/__tests__/api/block.test.ts rename to __tests__/unit/core-jest-matchers/api/block.test.ts index 1275c5346e..5ddb6929e7 100644 --- a/packages/core-jest-matchers/__tests__/api/block.test.ts +++ b/__tests__/unit/core-jest-matchers/api/block.test.ts @@ -1,4 +1,4 @@ -import "../../src/api/block"; +import "../../../../packages/core-jest-matchers/src/api/block"; let block; diff --git a/packages/core-jest-matchers/__tests__/api/peer.test.ts b/__tests__/unit/core-jest-matchers/api/peer.test.ts similarity index 93% rename from packages/core-jest-matchers/__tests__/api/peer.test.ts rename to __tests__/unit/core-jest-matchers/api/peer.test.ts index d6c7e9a786..72ecc21d30 100644 --- a/packages/core-jest-matchers/__tests__/api/peer.test.ts +++ b/__tests__/unit/core-jest-matchers/api/peer.test.ts @@ -1,4 +1,4 @@ -import "../../src/api/peer"; +import "../../../../packages/core-jest-matchers/src/api/peer"; let peer; diff --git a/packages/core-jest-matchers/__tests__/api/response.test.ts b/__tests__/unit/core-jest-matchers/api/response.test.ts similarity index 95% rename from packages/core-jest-matchers/__tests__/api/response.test.ts rename to __tests__/unit/core-jest-matchers/api/response.test.ts index b405a58b93..e528c46179 100644 --- a/packages/core-jest-matchers/__tests__/api/response.test.ts +++ b/__tests__/unit/core-jest-matchers/api/response.test.ts @@ -1,4 +1,4 @@ -import "../../src/api/response"; +import "../../../../packages/core-jest-matchers/src/api/response"; let response; diff --git a/packages/core-jest-matchers/__tests__/api/transaction.test.ts b/__tests__/unit/core-jest-matchers/api/transaction.test.ts similarity index 89% rename from packages/core-jest-matchers/__tests__/api/transaction.test.ts rename to __tests__/unit/core-jest-matchers/api/transaction.test.ts index 870982590f..97189dadad 100644 --- a/packages/core-jest-matchers/__tests__/api/transaction.test.ts +++ b/__tests__/unit/core-jest-matchers/api/transaction.test.ts @@ -1,4 +1,4 @@ -import "../../src/api/transaction"; +import "../../../../packages/core-jest-matchers/src/api/transaction"; const transaction = { id: "", diff --git a/packages/core-jest-matchers/__tests__/blockchain/dispatch.test.ts b/__tests__/unit/core-jest-matchers/blockchain/dispatch.test.ts similarity index 89% rename from packages/core-jest-matchers/__tests__/blockchain/dispatch.test.ts rename to __tests__/unit/core-jest-matchers/blockchain/dispatch.test.ts index 7d157f2a04..b591892f98 100644 --- a/packages/core-jest-matchers/__tests__/blockchain/dispatch.test.ts +++ b/__tests__/unit/core-jest-matchers/blockchain/dispatch.test.ts @@ -1,4 +1,4 @@ -import "../../src/blockchain/dispatch"; +import "../../../../packages/core-jest-matchers/src/blockchain/dispatch"; describe(".toDispatch", () => { const blockchain = { diff --git a/packages/core-jest-matchers/__tests__/blockchain/execute-on-entry.test.ts b/__tests__/unit/core-jest-matchers/blockchain/execute-on-entry.test.ts similarity index 92% rename from packages/core-jest-matchers/__tests__/blockchain/execute-on-entry.test.ts rename to __tests__/unit/core-jest-matchers/blockchain/execute-on-entry.test.ts index 90d4aab3ff..e8f53e09f8 100644 --- a/packages/core-jest-matchers/__tests__/blockchain/execute-on-entry.test.ts +++ b/__tests__/unit/core-jest-matchers/blockchain/execute-on-entry.test.ts @@ -1,5 +1,5 @@ import { Machine } from "xstate"; -import "../../src/blockchain/execute-on-entry"; +import "../../../../packages/core-jest-matchers/src/blockchain/execute-on-entry"; describe(".toExecuteOnEntry", () => { const machine = Machine({ diff --git a/packages/core-jest-matchers/__tests__/blockchain/transition.test.ts b/__tests__/unit/core-jest-matchers/blockchain/transition.test.ts similarity index 94% rename from packages/core-jest-matchers/__tests__/blockchain/transition.test.ts rename to __tests__/unit/core-jest-matchers/blockchain/transition.test.ts index b04619008a..c210cb854e 100644 --- a/packages/core-jest-matchers/__tests__/blockchain/transition.test.ts +++ b/__tests__/unit/core-jest-matchers/blockchain/transition.test.ts @@ -1,5 +1,5 @@ import { Machine } from "xstate"; -import "../../src/blockchain/transition"; +import "../../../../packages/core-jest-matchers/src/blockchain/transition"; describe(".toTransition", () => { const machine = Machine({ diff --git a/packages/core-jest-matchers/__tests__/fields/address.test.ts b/__tests__/unit/core-jest-matchers/fields/address.test.ts similarity index 83% rename from packages/core-jest-matchers/__tests__/fields/address.test.ts rename to __tests__/unit/core-jest-matchers/fields/address.test.ts index ab407dd005..5471512a6b 100644 --- a/packages/core-jest-matchers/__tests__/fields/address.test.ts +++ b/__tests__/unit/core-jest-matchers/fields/address.test.ts @@ -1,4 +1,4 @@ -import "../../src/fields/address"; +import "../../../../packages/core-jest-matchers/src/fields/address"; describe(".toBeAddress", () => { test("passes when given a valid address", () => { diff --git a/packages/core-jest-matchers/__tests__/fields/public-key.test.ts b/__tests__/unit/core-jest-matchers/fields/public-key.test.ts similarity index 84% rename from packages/core-jest-matchers/__tests__/fields/public-key.test.ts rename to __tests__/unit/core-jest-matchers/fields/public-key.test.ts index b8bc3dc207..da0e6bb0a5 100644 --- a/packages/core-jest-matchers/__tests__/fields/public-key.test.ts +++ b/__tests__/unit/core-jest-matchers/fields/public-key.test.ts @@ -1,4 +1,4 @@ -import "../../src/fields/public-key"; +import "../../../../packages/core-jest-matchers/src/fields/public-key"; describe(".toBePublicKey", () => { test("passes when given a valid public key", () => { diff --git a/packages/core-jest-matchers/__tests__/models/delegate.test.ts b/__tests__/unit/core-jest-matchers/models/delegate.test.ts similarity index 88% rename from packages/core-jest-matchers/__tests__/models/delegate.test.ts rename to __tests__/unit/core-jest-matchers/models/delegate.test.ts index 24ed905f2f..369730981f 100644 --- a/packages/core-jest-matchers/__tests__/models/delegate.test.ts +++ b/__tests__/unit/core-jest-matchers/models/delegate.test.ts @@ -1,4 +1,4 @@ -import "../../src/models/delegate"; +import "../../../../packages/core-jest-matchers/src/models/delegate"; describe(".toBeDelegate", () => { const delegate = { diff --git a/packages/core-jest-matchers/__tests__/models/transaction.test.ts b/__tests__/unit/core-jest-matchers/models/transaction.test.ts similarity index 93% rename from packages/core-jest-matchers/__tests__/models/transaction.test.ts rename to __tests__/unit/core-jest-matchers/models/transaction.test.ts index 09707b4271..0fa9728413 100644 --- a/packages/core-jest-matchers/__tests__/models/transaction.test.ts +++ b/__tests__/unit/core-jest-matchers/models/transaction.test.ts @@ -1,4 +1,4 @@ -import "../../src/models/transaction"; +import "../../../../packages/core-jest-matchers/src/models/transaction"; describe(".toBeTransaction", () => { const transaction = { diff --git a/packages/core-jest-matchers/__tests__/models/wallet.test.ts b/__tests__/unit/core-jest-matchers/models/wallet.test.ts similarity index 87% rename from packages/core-jest-matchers/__tests__/models/wallet.test.ts rename to __tests__/unit/core-jest-matchers/models/wallet.test.ts index 2435817eb8..f622ec9453 100644 --- a/packages/core-jest-matchers/__tests__/models/wallet.test.ts +++ b/__tests__/unit/core-jest-matchers/models/wallet.test.ts @@ -1,4 +1,4 @@ -import "../../src/models/wallet"; +import "../../../../packages/core-jest-matchers/src/models/wallet"; describe(".toBeWallet", () => { const wallet = { diff --git a/packages/core-jest-matchers/__tests__/transactions/types/delegate-resignation.test.ts b/__tests__/unit/core-jest-matchers/transactions/types/delegate-resignation.test.ts similarity index 85% rename from packages/core-jest-matchers/__tests__/transactions/types/delegate-resignation.test.ts rename to __tests__/unit/core-jest-matchers/transactions/types/delegate-resignation.test.ts index 297b284e74..1fd2f28354 100644 --- a/packages/core-jest-matchers/__tests__/transactions/types/delegate-resignation.test.ts +++ b/__tests__/unit/core-jest-matchers/transactions/types/delegate-resignation.test.ts @@ -1,4 +1,4 @@ -import "../../../src/transactions/types/delegate-resignation"; +import "../../../../../packages/core-jest-matchers/src/transactions/types/delegate-resignation"; import { constants } from "@arkecosystem/crypto"; const { TransactionTypes } = constants; diff --git a/packages/core-jest-matchers/__tests__/transactions/types/delegate.test.ts b/__tests__/unit/core-jest-matchers/transactions/types/delegate.test.ts similarity index 86% rename from packages/core-jest-matchers/__tests__/transactions/types/delegate.test.ts rename to __tests__/unit/core-jest-matchers/transactions/types/delegate.test.ts index b18381d22d..34d695dad8 100644 --- a/packages/core-jest-matchers/__tests__/transactions/types/delegate.test.ts +++ b/__tests__/unit/core-jest-matchers/transactions/types/delegate.test.ts @@ -1,4 +1,4 @@ -import "../../../src/transactions/types/delegate"; +import "../../../../../packages/core-jest-matchers/src/transactions/types/delegate"; import { constants } from "@arkecosystem/crypto"; const { TransactionTypes } = constants; diff --git a/packages/core-jest-matchers/__tests__/transactions/types/ipfs.test.ts b/__tests__/unit/core-jest-matchers/transactions/types/ipfs.test.ts similarity index 84% rename from packages/core-jest-matchers/__tests__/transactions/types/ipfs.test.ts rename to __tests__/unit/core-jest-matchers/transactions/types/ipfs.test.ts index 0c21fa6449..7f99285891 100644 --- a/packages/core-jest-matchers/__tests__/transactions/types/ipfs.test.ts +++ b/__tests__/unit/core-jest-matchers/transactions/types/ipfs.test.ts @@ -1,4 +1,4 @@ -import "../../../src/transactions/types/ipfs"; +import "../../../../../packages/core-jest-matchers/src/transactions/types/ipfs"; import { constants } from "@arkecosystem/crypto"; const { TransactionTypes } = constants; diff --git a/packages/core-jest-matchers/__tests__/transactions/types/multi-payment.test.ts b/__tests__/unit/core-jest-matchers/transactions/types/multi-payment.test.ts similarity index 85% rename from packages/core-jest-matchers/__tests__/transactions/types/multi-payment.test.ts rename to __tests__/unit/core-jest-matchers/transactions/types/multi-payment.test.ts index f57f367e0d..aae684671e 100644 --- a/packages/core-jest-matchers/__tests__/transactions/types/multi-payment.test.ts +++ b/__tests__/unit/core-jest-matchers/transactions/types/multi-payment.test.ts @@ -1,4 +1,4 @@ -import "../../../src/transactions/types/multi-payment"; +import "../../../../../packages/core-jest-matchers/src/transactions/types/multi-payment"; import { constants } from "@arkecosystem/crypto"; const { TransactionTypes } = constants; diff --git a/packages/core-jest-matchers/__tests__/transactions/types/multi-signature.test.ts b/__tests__/unit/core-jest-matchers/transactions/types/multi-signature.test.ts similarity index 85% rename from packages/core-jest-matchers/__tests__/transactions/types/multi-signature.test.ts rename to __tests__/unit/core-jest-matchers/transactions/types/multi-signature.test.ts index 3804fdba1f..ab68cb799a 100644 --- a/packages/core-jest-matchers/__tests__/transactions/types/multi-signature.test.ts +++ b/__tests__/unit/core-jest-matchers/transactions/types/multi-signature.test.ts @@ -1,4 +1,4 @@ -import "../../../src/transactions/types/multi-signature"; +import "../../../../../packages/core-jest-matchers/src/transactions/types/multi-signature"; import { constants } from "@arkecosystem/crypto"; const { TransactionTypes } = constants; diff --git a/packages/core-jest-matchers/__tests__/transactions/types/second-signature.test.ts b/__tests__/unit/core-jest-matchers/transactions/types/second-signature.test.ts similarity index 85% rename from packages/core-jest-matchers/__tests__/transactions/types/second-signature.test.ts rename to __tests__/unit/core-jest-matchers/transactions/types/second-signature.test.ts index 84fff3080a..3883e525d1 100644 --- a/packages/core-jest-matchers/__tests__/transactions/types/second-signature.test.ts +++ b/__tests__/unit/core-jest-matchers/transactions/types/second-signature.test.ts @@ -1,4 +1,4 @@ -import "../../../src/transactions/types/second-signature"; +import "../../../../../packages/core-jest-matchers/src/transactions/types/second-signature"; import { constants } from "@arkecosystem/crypto"; const { TransactionTypes } = constants; diff --git a/packages/core-jest-matchers/__tests__/transactions/types/timelock-transfer.test.ts b/__tests__/unit/core-jest-matchers/transactions/types/timelock-transfer.test.ts similarity index 85% rename from packages/core-jest-matchers/__tests__/transactions/types/timelock-transfer.test.ts rename to __tests__/unit/core-jest-matchers/transactions/types/timelock-transfer.test.ts index 5496680bdb..52283eb441 100644 --- a/packages/core-jest-matchers/__tests__/transactions/types/timelock-transfer.test.ts +++ b/__tests__/unit/core-jest-matchers/transactions/types/timelock-transfer.test.ts @@ -1,4 +1,4 @@ -import "../../../src/transactions/types/timelock-transfer"; +import "../../../../../packages/core-jest-matchers/src/transactions/types/timelock-transfer"; import { constants } from "@arkecosystem/crypto"; const { TransactionTypes } = constants; diff --git a/packages/core-jest-matchers/__tests__/transactions/types/transfer.test.ts b/__tests__/unit/core-jest-matchers/transactions/types/transfer.test.ts similarity index 85% rename from packages/core-jest-matchers/__tests__/transactions/types/transfer.test.ts rename to __tests__/unit/core-jest-matchers/transactions/types/transfer.test.ts index 7bafd29abb..98c27c1831 100644 --- a/packages/core-jest-matchers/__tests__/transactions/types/transfer.test.ts +++ b/__tests__/unit/core-jest-matchers/transactions/types/transfer.test.ts @@ -1,4 +1,4 @@ -import "../../../src/transactions/types/transfer"; +import "../../../../../packages/core-jest-matchers/src/transactions/types/transfer"; import { constants } from "@arkecosystem/crypto"; const { TransactionTypes } = constants; diff --git a/packages/core-jest-matchers/__tests__/transactions/types/vote.test.ts b/__tests__/unit/core-jest-matchers/transactions/types/vote.test.ts similarity index 84% rename from packages/core-jest-matchers/__tests__/transactions/types/vote.test.ts rename to __tests__/unit/core-jest-matchers/transactions/types/vote.test.ts index 82a8c6f2e6..b8de166f4a 100644 --- a/packages/core-jest-matchers/__tests__/transactions/types/vote.test.ts +++ b/__tests__/unit/core-jest-matchers/transactions/types/vote.test.ts @@ -1,4 +1,4 @@ -import "../../../src/transactions/types/vote"; +import "../../../../../packages/core-jest-matchers/src/transactions/types/vote"; import { constants } from "@arkecosystem/crypto"; const { TransactionTypes } = constants; diff --git a/packages/core-jest-matchers/__tests__/transactions/valid-second-signature.test.ts b/__tests__/unit/core-jest-matchers/transactions/valid-second-signature.test.ts similarity index 93% rename from packages/core-jest-matchers/__tests__/transactions/valid-second-signature.test.ts rename to __tests__/unit/core-jest-matchers/transactions/valid-second-signature.test.ts index af98d2c564..794db6f8b7 100644 --- a/packages/core-jest-matchers/__tests__/transactions/valid-second-signature.test.ts +++ b/__tests__/unit/core-jest-matchers/transactions/valid-second-signature.test.ts @@ -1,8 +1,4 @@ -import "../../src/transactions/valid-second-signature"; - -// import { generators } from "@arkecosystem/core-test-utils"; -// const wallets = generators.generateWallets("testnet", 2); -// const transaction = generators.generateTransfers("testnet", wallets.map(w => w.passphrase))[0]; +import "../../../../packages/core-jest-matchers/src/transactions/valid-second-signature"; const wallets = [ { diff --git a/packages/core-jest-matchers/__tests__/transactions/valid.test.ts b/__tests__/unit/core-jest-matchers/transactions/valid.test.ts similarity index 93% rename from packages/core-jest-matchers/__tests__/transactions/valid.test.ts rename to __tests__/unit/core-jest-matchers/transactions/valid.test.ts index 158bce599c..72f776deb0 100644 --- a/packages/core-jest-matchers/__tests__/transactions/valid.test.ts +++ b/__tests__/unit/core-jest-matchers/transactions/valid.test.ts @@ -1,4 +1,4 @@ -import "../../src/transactions/valid"; +import "../../../../packages/core-jest-matchers/src/transactions/valid"; const transaction = { version: 1, diff --git a/__tests__/unit/core-logger-pino/logger.test.ts b/__tests__/unit/core-logger-pino/logger.test.ts new file mode 100644 index 0000000000..76e6b22ca0 --- /dev/null +++ b/__tests__/unit/core-logger-pino/logger.test.ts @@ -0,0 +1,43 @@ +import delay from "delay"; +import { createFileSync, readdirSync, removeSync } from "fs-extra"; +import { tmpdir } from "os"; +import { PinoLogger } from "../../../packages/core-logger-pino/src"; +import { defaults } from "../../../packages/core-logger-pino/src/defaults"; +import { expectLogger } from "../shared/logger"; + +expectLogger( + () => + new PinoLogger({ + levels: { + console: "trace", + file: "trace", + }, + }), +); + +describe("filestream", () => { + beforeAll(() => { + process.env.CORE_PATH_LOG = `${tmpdir()}/core-logger-pino/`; + }); + + beforeEach(() => { + removeSync(process.env.CORE_PATH_LOG); + }); + + afterEach(() => { + removeSync(process.env.CORE_PATH_LOG); + }); + + it("should rotate the log 3 times", async () => { + const logger = new PinoLogger({ ...defaults, fileRotator: { interval: "1s" } }).make(); + + for (let i = 0; i < 3; i++) { + logger.info(`Test ${i + 1}`); + await delay(1000); + } + + const files = readdirSync(process.env.CORE_PATH_LOG); + expect(files.filter(file => file.endsWith(".log.gz"))).toHaveLength(3); + expect(files).toHaveLength(5); + }); +}); diff --git a/__tests__/unit/core-logger-signale/logger.test.ts b/__tests__/unit/core-logger-signale/logger.test.ts new file mode 100644 index 0000000000..eb2ee31cbc --- /dev/null +++ b/__tests__/unit/core-logger-signale/logger.test.ts @@ -0,0 +1,4 @@ +import { SignaleLogger } from "../../../packages/core-logger-signale/src"; +import { expectLogger } from "../shared/logger"; + +expectLogger(() => new SignaleLogger({ logLevel: "info" })); diff --git a/__tests__/unit/core-logger-winston/logger.test.ts b/__tests__/unit/core-logger-winston/logger.test.ts new file mode 100644 index 0000000000..572bda84bf --- /dev/null +++ b/__tests__/unit/core-logger-winston/logger.test.ts @@ -0,0 +1,16 @@ +import { WinstonLogger } from "../../../packages/core-logger-winston/src"; +import { expectLogger } from "../shared/logger"; + +expectLogger( + () => + new WinstonLogger({ + transports: [ + { + constructor: "Console", + options: { + level: "debug", + }, + }, + ], + }), +); diff --git a/packages/core-logger/__tests__/__stubs__/logger.ts b/__tests__/unit/core-logger/__stubs__/logger.ts similarity index 87% rename from packages/core-logger/__tests__/__stubs__/logger.ts rename to __tests__/unit/core-logger/__stubs__/logger.ts index 0d9730c65c..f50083c056 100644 --- a/packages/core-logger/__tests__/__stubs__/logger.ts +++ b/__tests__/unit/core-logger/__stubs__/logger.ts @@ -1,4 +1,4 @@ -import { AbstractLogger } from "../../src"; +import { AbstractLogger } from "../../../../packages/core-logger/src"; export class Logger extends AbstractLogger { public make(): any { diff --git a/packages/core-logger/__tests__/manager.test.ts b/__tests__/unit/core-logger/manager.test.ts similarity index 61% rename from packages/core-logger/__tests__/manager.test.ts rename to __tests__/unit/core-logger/manager.test.ts index eca1bec3ca..5806f0cf0e 100644 --- a/packages/core-logger/__tests__/manager.test.ts +++ b/__tests__/unit/core-logger/manager.test.ts @@ -1,13 +1,13 @@ import "jest-extended"; -import { AbstractLogger, LogManager } from "../src"; +import { AbstractLogger, LoggerManager } from "../../../packages/core-logger/src"; import { Logger } from "./__stubs__/logger"; -const manager = new LogManager(); +const manager = new LoggerManager(); describe("Config Manager", () => { describe("driver", () => { it("should return the driver", async () => { - await manager.makeDriver(new Logger({})); + await manager.createDriver(new Logger({})); expect(manager.driver()).toBeInstanceOf(AbstractLogger); }); diff --git a/packages/core-p2p/__tests__/court/guard.test.ts b/__tests__/unit/core-p2p/court/guard.test.ts similarity index 84% rename from packages/core-p2p/__tests__/court/guard.test.ts rename to __tests__/unit/core-p2p/court/guard.test.ts index deeb3fbd29..2a92755b33 100644 --- a/packages/core-p2p/__tests__/court/guard.test.ts +++ b/__tests__/unit/core-p2p/court/guard.test.ts @@ -1,44 +1,60 @@ -import { app } from "@arkecosystem/core-container"; -import dayjs from "dayjs-ext"; -import { offences } from "../../src/court/offences"; -import { defaults } from "../../src/defaults"; -import { Peer } from "../../src/peer"; -import { setUp, tearDown } from "../__support__/setup"; - -let guard; -let peerMock; +import "jest-extended"; +import "../mocks/core-container"; + +import { dato } from "@faustbrian/dato"; +import nock from "nock"; +import { config as localConfig } from "../../../../packages/core-p2p/src/config"; +import { guard } from "../../../../packages/core-p2p/src/court/guard"; +import { offences } from "../../../../packages/core-p2p/src/court/offences"; +import { defaults } from "../../../../packages/core-p2p/src/defaults"; +import { monitor } from "../../../../packages/core-p2p/src/monitor"; +import { Peer } from "../../../../packages/core-p2p/src/peer"; -beforeAll(async () => { - await setUp(); +let peerMock; - guard = require("../../src/court/guard").guard; - guard.config.set("minimumVersions", [">=2.0.0"]); -}); +beforeEach(async () => { + localConfig.init(defaults); -afterAll(async () => { - await tearDown(); -}); + monitor.config = localConfig; + monitor.guard = guard; + monitor.guard.init(monitor); -beforeEach(async () => { - guard.monitor.config = defaults; - guard.monitor.peers = {}; + guard.config.set("minimumVersions", [">=2.0.0"]); // this peer is here to be ready for future use in tests (not added to initial peers) peerMock = new Peer("1.0.0.99", 4002); Object.assign(peerMock, peerMock.headers); + nock.cleanAll(); +}); + +afterAll(() => { + nock.cleanAll(); }); describe("Guard", () => { describe("isSuspended", () => { - it("should return true", async () => { + it("should not ban for timeout", async () => { await guard.monitor.acceptNewPeer(peerMock); - expect(guard.isSuspended(peerMock)).toBe(true); + expect(guard.isSuspended(peerMock)).toBe(false); }); it("should return false because passed", async () => { + nock(peerMock.url) + .get("/peer/status") + .reply( + 200, + { + success: true, + header: { height: 1, id: "213432344" }, + }, + peerMock.headers, + ); + await guard.monitor.acceptNewPeer(peerMock); - guard.suspensions[peerMock.ip].until = dayjs().subtract(1, "minute"); + expect(guard.isSuspended(peerMock)).toBe(true); + + guard.suspensions[peerMock.ip].until = dato().subMinutes(1); expect(guard.isSuspended(peerMock)).toBe(false); }); @@ -92,7 +108,7 @@ describe("Guard", () => { }); describe("__determineOffence", () => { - const convertToMinutes = actual => Math.ceil(actual.diff(dayjs()) / 1000) / 60; + const convertToMinutes = actual => Math.ceil(actual.diff(dato()) / 1000) / 60; const dummy = { nethash: "d9acd04bde4234a81addb8482333b4ac906bed7be5a9970ce8ada428bd083192", @@ -170,7 +186,7 @@ describe("Guard", () => { }); expect(reason).toBe("Timeout"); - expect(convertToMinutes(until)).toBe(2); + expect(convertToMinutes(until)).toBe(0.5); }); it('should return a 1 minutes suspension for "High Latency"', () => { diff --git a/__tests__/unit/core-p2p/mocks/core-container.ts b/__tests__/unit/core-p2p/mocks/core-container.ts new file mode 100644 index 0000000000..e6b882f3ce --- /dev/null +++ b/__tests__/unit/core-p2p/mocks/core-container.ts @@ -0,0 +1,79 @@ +import { configManager } from "@arkecosystem/crypto"; +import { blocks2to100 } from "../../../utils/fixtures"; +import { delegates } from "../../../utils/fixtures/testnet/delegates"; +import { genesisBlock } from "../../../utils/fixtures/unitnet/block-model"; + +configManager.setFromPreset("testnet"); + +jest.mock("@arkecosystem/core-container", () => { + return { + app: { + getConfig: () => { + return { + get: key => { + if (key === "network.nethash") { + return configManager.get("nethash"); + } + + return null; + }, + config: { milestones: [{ activeDelegates: 51, height: 1 }] }, + getMilestone: () => ({ + activeDelegates: 51, + }), + }; + }, + getVersion: () => "2.3.0", + has: () => true, + resolvePlugin: name => { + if (name === "logger") { + return { + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + debug: jest.fn(), + }; + } + + if (name === "database") { + return { + getBlocksByHeight: heights => { + if (heights[0] === 1) { + return [genesisBlock.data]; + } + + return []; + }, + getActiveDelegates: height => { + return delegates; + }, + loadActiveDelegateList: (count, height) => { + if (height === 2) { + return [blocks2to100[1]]; + } + + return delegates; + }, + }; + } + + if (name === "event-emitter") { + return { + emit: jest.fn(), + }; + } + + return {}; + }, + resolve: name => { + if (name === "state") { + return { + getLastBlock: () => genesisBlock, + }; + } + + return {}; + }, + }, + }; +}); diff --git a/__tests__/unit/core-p2p/monitor.test.ts b/__tests__/unit/core-p2p/monitor.test.ts new file mode 100644 index 0000000000..8cd3719a5b --- /dev/null +++ b/__tests__/unit/core-p2p/monitor.test.ts @@ -0,0 +1,226 @@ +/* tslint:disable:max-line-length */ + +import "./mocks/core-container"; + +import nock from "nock"; +import { config as localConfig } from "../../../packages/core-p2p/src/config"; +import { Guard } from "../../../packages/core-p2p/src/court"; +import { defaults } from "../../../packages/core-p2p/src/defaults"; +import { monitor } from "../../../packages/core-p2p/src/monitor"; +import { Peer } from "../../../packages/core-p2p/src/peer"; +import { genesisBlock } from "../../utils/fixtures/unitnet/block-model"; + +let peerMock: Peer; + +beforeEach(() => { + monitor.config = defaults; + localConfig.init(defaults); + localConfig.set("port", 4000); + + monitor.guard = new Guard(); + monitor.guard.init(monitor); + monitor.guard.config = localConfig; + + const initialPeersMock = {}; + ["1.0.0.0", "1.0.0.1", "1.0.0.2", "1.0.0.3", "1.0.0.4"].forEach(ip => { + const initialPeer = new Peer(ip, 4000); + initialPeersMock[ip] = Object.assign(initialPeer, initialPeer.headers, { + ban: 0, + verification: { forked: false }, + }); + }); + + monitor.peers = initialPeersMock; + + peerMock = new Peer("1.0.0.99", 4000); // this peer is just here to be picked up by tests below (not added to initial peers) + Object.assign(peerMock, peerMock.headers, { status: 200 }); + peerMock.nethash = "d9acd04bde4234a81addb8482333b4ac906bed7be5a9970ce8ada428bd083192"; + + nock.cleanAll(); +}); + +afterAll(() => { + nock.cleanAll(); +}); + +describe("Monitor", () => { + describe("cleanPeers", () => { + it("should be ok", async () => { + const previousLength = Object.keys(monitor.peers).length; + + await monitor.cleanPeers(true); + + expect(Object.keys(monitor.peers).length).toBeLessThan(previousLength); + }); + }); + + describe("acceptNewPeer", () => { + it("should be ok", async () => { + nock(peerMock.url) + .get("/peer/status") + .reply( + 200, + { + header: { + height: 1, + id: genesisBlock.data.id, + }, + success: true, + }, + peerMock.headers, + ); + + await monitor.acceptNewPeer(peerMock); + + expect(monitor.peers[peerMock.ip]).toBeObject(); + }); + }); + + describe("getPeers", () => { + it("should be ok", async () => { + const peers = monitor.getPeers(); + + expect(peers).toBeArray(); + expect(peers.length).toBe(5); // 5 from peers.json + }); + }); + + describe("discoverPeers", () => { + it("should be ok", async () => { + nock(/.*/) + .get("/peer/status") + .reply( + 200, + { + header: { + height: 1, + id: genesisBlock.data.id, + }, + success: true, + }, + peerMock.headers, + ); + + nock(/.*/) + .get("/peer/list") + .reply( + 200, + { + peers: [peerMock.toBroadcastInfo()], + success: true, + }, + peerMock.headers, + ); + + await monitor.discoverPeers(); + const peers = monitor.getPeers(); + + expect(peers).toBeArray(); + expect(Object.keys(peers).length).toBe(6); // 5 from initial peers + 1 from peerMock + expect(peers.find(e => e.ip === peerMock.ip)).toBeDefined(); + }); + }); + + describe("getNetworkHeight", () => { + it("should be ok", async () => { + nock(/.*/) + .get("/peer/status") + .reply( + 200, + { + header: { + height: 1, + id: genesisBlock.data.id, + }, + height: 2, + success: true, + }, + peerMock.headers, + ); + + nock(/.*/) + .get("/peer/list") + .reply(200, { peers: [] }, peerMock.headers); + + await monitor.discoverPeers(); + await monitor.cleanPeers(); + + const height = await monitor.getNetworkHeight(); + expect(height).toBe(2); + }); + + // TODO test with peers with different heights (use replyOnce) and check that median is OK + }); + + describe("getPBFTForgingStatus", () => { + it("should be ok", async () => { + nock(/.*/) + .get("/peer/status") + .reply(200, { success: true, height: 2 }, peerMock.headers); + + nock(/.*/) + .get("/peer/list") + .reply(200, { peers: [] }, peerMock.headers); + + await monitor.discoverPeers(); + const pbftForgingStatus = monitor.getPBFTForgingStatus(); + + expect(pbftForgingStatus).toBeNumber(); + // TODO test mocking peers currentSlot & forgingAllowed + }); + }); + + describe("downloadBlocks", () => { + it("should be ok", async () => { + nock(/.*/) + .get("/peer/blocks/common") + .reply( + 200, + { + success: true, + common: true, + }, + peerMock.headers, + ); + + nock(/.*/) + .get("/peer/status") + .reply( + 200, + { + success: true, + height: 2, + }, + peerMock.headers, + ); + + nock(/.*/) + .get("/peer/blocks") + .query({ lastBlockHeight: 1 }) + .reply( + 200, + { + blocks: [{ height: 1, id: "1" }, { height: 2, id: "2" }], + }, + peerMock.headers, + ); + + const blocks = await monitor.downloadBlocks(1); + + expect(blocks).toBeArray(); + expect(blocks.length).toBe(2); + }); + }); + + describe("getNetworkHeight", () => { + it("should return correct network height", () => { + monitor.peers = { + "1.1.1.1": { state: { height: 1 } }, + "1.1.1.2": { state: { height: 7 } }, + "1.1.1.3": { state: { height: 10 } }, + }; + + expect(monitor.getNetworkHeight()).toBe(7); + }); + }); +}); diff --git a/packages/core-p2p/__tests__/peer-verifier.test.ts b/__tests__/unit/core-p2p/peer-verifier.test.ts similarity index 57% rename from packages/core-p2p/__tests__/peer-verifier.test.ts rename to __tests__/unit/core-p2p/peer-verifier.test.ts index be53fd022b..1240a00fc1 100644 --- a/packages/core-p2p/__tests__/peer-verifier.test.ts +++ b/__tests__/unit/core-p2p/peer-verifier.test.ts @@ -1,38 +1,22 @@ -import genesisBlockJson from "@arkecosystem/core-test-utils/src/config/testnet/genesisBlock.json"; -import { blocks2to100 as blocks2to100Json } from "@arkecosystem/core-test-utils/src/fixtures"; -import { models } from "@arkecosystem/crypto"; -import axios from "axios"; -import MockAdapter from "axios-mock-adapter"; -import { Peer } from "../src/peer"; -import { PeerVerifier } from "../src/peer-verifier"; -import { setUp, tearDown } from "./__support__/setup"; +import "./mocks/core-container"; -const axiosMock = new MockAdapter(axios); -const { Block, Transaction } = models; - -let genesisBlock; -let genesisTransaction; +import nock from "nock"; +import { Peer } from "../../../packages/core-p2p/src/peer"; +import { PeerVerifier } from "../../../packages/core-p2p/src/peer-verifier"; +import { blocks2to100 as blocks2to100Json } from "../../utils/fixtures"; +import { genesisBlock } from "../../utils/fixtures/unitnet/block-model"; let peerMock: Peer; -beforeAll(async () => { - await setUp(); - - // Create the genesis block after the setup has finished or else it uses a potentially - // wrong network config. - genesisBlock = new Block(genesisBlockJson); - genesisTransaction = new Transaction(genesisBlock.transactions[0]); -}); - -afterAll(async () => { - await tearDown(); -}); - beforeEach(() => { peerMock = new Peer("1.0.0.99", 4002); Object.assign(peerMock, peerMock.headers); - axiosMock.reset(); // important: resets any existing mocking behavior + nock.cleanAll(); +}); + +afterAll(() => { + nock.cleanAll(); }); describe("Peer Verifier", () => { @@ -46,14 +30,17 @@ describe("Peer Verifier", () => { }); it("different chains, including the genesis block", async () => { - axiosMock.onGet(`${peerMock.url}/peer/blocks/common?ids=${genesisBlock.data.id},`).reply( - 200, - { - common: null, - success: true, - }, - peerMock.headers, - ); + nock(peerMock.url) + .get("/peer/blocks/common") + .query({ ids: genesisBlock.data.id }) + .reply( + 200, + { + common: null, + success: true, + }, + peerMock.headers, + ); const peerVerifier = new PeerVerifier(peerMock); const state = { header: { height: 1, id: "123" } }; @@ -71,14 +58,17 @@ describe("Peer Verifier", () => { ]; for (const commonBlockReply of commonBlockReplies) { - axiosMock.onGet(`${peerMock.url}/peer/blocks/common?ids=${genesisBlock.data.id},`).reply( - 200, - { - common: commonBlockReply, - success: true, - }, - peerMock.headers, - ); + nock(peerMock.url) + .get("/peer/blocks/common") + .query({ ids: genesisBlock.data.id }) + .reply( + 200, + { + common: commonBlockReply, + success: true, + }, + peerMock.headers, + ); const peerVerifier = new PeerVerifier(peerMock); const state = { header: { height: 1, id: "123" } }; @@ -88,15 +78,17 @@ describe("Peer Verifier", () => { }); it("higher than our chain (invalid)", async () => { - axiosMock.reset(); - axiosMock.onGet(`${peerMock.url}/peer/blocks/common?ids=${genesisBlock.data.id},`).reply( - 200, - { - common: { id: `${genesisBlock.data.id}`, height: 1 }, - success: true, - }, - peerMock.headers, - ); + nock(peerMock.url) + .get("/peer/blocks/common") + .query({ ids: genesisBlock.data.id }) + .reply( + 200, + { + common: { id: `${genesisBlock.data.id}`, height: 1 }, + success: true, + }, + peerMock.headers, + ); const overrides = [ // Altered payload (timestamp) @@ -110,13 +102,15 @@ describe("Peer Verifier", () => { for (const override of overrides) { const block2 = Object.assign({}, blocks2to100Json[0], override); - axiosMock.onGet(`${peerMock.url}/peer/blocks`).reply( - 200, - { - blocks: [block2], - }, - peerMock.headers, - ); + nock(peerMock.url) + .get("/peer/blocks") + .reply( + 200, + { + blocks: [block2], + }, + peerMock.headers, + ); const peerVerifier = new PeerVerifier(peerMock); const state = { header: { height: 2, id: block2.id } }; @@ -126,23 +120,28 @@ describe("Peer Verifier", () => { }); it("higher than our chain (legit)", async () => { - axiosMock.reset(); - axiosMock.onGet(`${peerMock.url}/peer/blocks/common?ids=${genesisBlock.data.id},`).reply( - 200, - { - common: { id: `${genesisBlock.data.id}`, height: 1 }, - success: true, - }, - peerMock.headers, - ); - - axiosMock.onGet(`${peerMock.url}/peer/blocks`).reply( - 200, - { - blocks: [blocks2to100Json[0]], - }, - peerMock.headers, - ); + nock(peerMock.url) + .get("/peer/blocks/common") + .query({ ids: `${genesisBlock.data.id},` }) + .reply( + 200, + { + common: { id: genesisBlock.data.id, height: 1 }, + success: true, + }, + peerMock.headers, + ); + + nock(peerMock.url) + .get("/peer/blocks") + .query({ lastBlockHeight: 1 }) + .reply( + 200, + { + blocks: [blocks2to100Json[0]], + }, + peerMock.headers, + ); const peerVerifier = new PeerVerifier(peerMock); const state = { header: { height: 2, id: blocks2to100Json[0].id } }; diff --git a/packages/core-p2p/__tests__/peer.test.ts b/__tests__/unit/core-p2p/peer.test.ts similarity index 57% rename from packages/core-p2p/__tests__/peer.test.ts rename to __tests__/unit/core-p2p/peer.test.ts index ad717fb87e..3d228218c7 100644 --- a/packages/core-p2p/__tests__/peer.test.ts +++ b/__tests__/unit/core-p2p/peer.test.ts @@ -1,35 +1,27 @@ -import { models } from "@arkecosystem/crypto"; -import axios from "axios"; -import MockAdapter from "axios-mock-adapter"; -import { Peer } from "../src/peer"; -import { setUp, tearDown } from "./__support__/setup"; +import "./mocks/core-container"; -const axiosMock = new MockAdapter(axios); -const { Block, Transaction } = models; +import { Transaction } from "@arkecosystem/crypto"; +import nock from "nock"; +import { Peer } from "../../../packages/core-p2p/src/peer"; +import { genesisBlock } from "../../utils/fixtures/unitnet/block-model"; -let genesisBlock; let genesisTransaction; let peerMock: Peer; -beforeAll(async () => { - await setUp(); - - // Create the genesis block after the setup has finished or else it uses a potentially - // wrong network config. - genesisBlock = new Block(require("@arkecosystem/core-test-utils/src/config/testnet/genesisBlock.json")); - genesisTransaction = new Transaction(genesisBlock.transactions[0]); -}); - -afterAll(async () => { - await tearDown(); +beforeAll(() => { + genesisTransaction = Transaction.fromData(genesisBlock.transactions[0].data); }); beforeEach(() => { peerMock = new Peer("1.0.0.99", 4002); Object.assign(peerMock, peerMock.headers); - axiosMock.reset(); // important: resets any existing mocking behavior + nock.cleanAll(); +}); + +afterAll(() => { + nock.cleanAll(); }); describe("Peer", () => { @@ -48,21 +40,23 @@ describe("Peer", () => { }); }); - describe("postBlock", () => { - it("should be ok", async () => { - axiosMock.onPost(`${peerMock.url}/peer/blocks`).reply(200, { success: true }, peerMock.headers); + it("it should post a block ", async () => { + nock(peerMock.url) + .post("/peer/blocks") + .reply(200, { success: true }, peerMock.headers); - const response = await peerMock.postBlock(genesisBlock.toJson()); + const response = await peerMock.postBlock(genesisBlock.toJson()); - expect(response).toBeObject(); - expect(response).toHaveProperty("success"); - expect(response.success).toBeTrue(); - }); + expect(response).toBeObject(); + expect(response).toHaveProperty("success"); + expect(response.success).toBeTrue(); }); - describe.skip("postTransactions", () => { + describe("postTransactions", () => { it("should be ok", async () => { - axiosMock.onPost(`${peerMock.url}/peer/transactions`).reply(200, { success: true }, peerMock.headers); + nock(peerMock.url) + .post("/peer/transactions") + .reply(200, { success: true }, peerMock.headers); const response = await peerMock.postTransactions([genesisTransaction.toJson()]); @@ -83,32 +77,41 @@ describe("Peer", () => { it("should return the blocks with status 200", async () => { const blocks = []; - axiosMock.onGet(`${peerMock.url}/peer/blocks`).reply(200, { blocks }, peerMock.headers); + nock(peerMock.url) + .get("/peer/blocks") + .query({ lastBlockHeight: 1 }) + .reply(200, { blocks }, peerMock.headers); + const result = await peerMock.downloadBlocks(1); expect(result).toEqual(blocks); }); it("should not return the blocks with status 500", async () => { - axiosMock.onGet(`${peerMock.url}/peer/blocks`).reply(500, { data: {} }, peerMock.headers); + nock(peerMock.url) + .get("/peer/blocks") + .query({ lastBlockHeight: 1 }) + .reply(500); - expect(await errorCapturer(peerMock.downloadBlocks(1))).toThrow(/request.*500/i); + await expect(peerMock.downloadBlocks(1)).rejects.toThrow(); }); }); describe("ping", () => { it("should be ok", async () => { - axiosMock.onGet(`${peerMock.url}/peer/status`).reply(() => [ - 200, - { - header: { - height: 1, - id: genesisBlock.data.id, + nock(peerMock.url) + .get("/peer/status") + .reply( + 200, + { + header: { + height: 1, + id: genesisBlock.data.id, + }, + success: true, }, - success: true, - }, - peerMock.headers, - ]); + peerMock.headers, + ); const response = await peerMock.ping(5000); @@ -118,16 +121,23 @@ describe("Peer", () => { }); it("should not be ok", async () => { - axiosMock.onGet(`${peerMock.url}/peer/status`).reply(() => [500, {}, peerMock.headers]); - return expect(peerMock.ping(1)).rejects.toThrowError("could not get status response"); + nock(peerMock.url) + .get("/peer/status") + .reply(500, {}, peerMock.headers); + + return expect(peerMock.ping(1)).rejects.toThrowError(`Failed to retrieve status from peer ${peerMock.ip}.`); }); it.each([200, 500, 503])("should update peer status from http response %i", async status => { - axiosMock.onGet(`${peerMock.url}/peer/status`).replyOnce(() => [status, {}, peerMock.headers]); + nock(peerMock.url) + .get("/peer/status") + .reply(status, {}, peerMock.headers); + try { await peerMock.ping(1000); // tslint:disable-next-line:no-empty } catch (e) {} + expect(peerMock.status).toBe(status); }); }); @@ -138,17 +148,19 @@ describe("Peer", () => { expect(peerMock.recentlyPinged()).toBeFalse(); - axiosMock.onGet(`${peerMock.url}/peer/status`).reply(() => [ - 200, - { - header: { - height: 1, - id: genesisBlock.data.id, + nock(peerMock.url) + .get("/peer/status") + .reply( + 200, + { + header: { + height: 1, + id: genesisBlock.data.id, + }, + success: true, }, - success: true, - }, - peerMock.headers, - ]); + peerMock.headers, + ); const response = await peerMock.ping(5000); @@ -162,25 +174,31 @@ describe("Peer", () => { describe("getPeers", () => { it("should be ok", async () => { const peersMock = [{ ip: "1.1.1.1" }]; - axiosMock.onGet(`${peerMock.url}/peer/status`).reply(() => [ - 200, - { - header: { - height: 1, - id: genesisBlock.data.id, + + nock(peerMock.url) + .get("/peer/status") + .reply( + 200, + { + header: { + height: 1, + id: genesisBlock.data.id, + }, + success: true, + }, + peerMock.headers, + ); + + nock(peerMock.url) + .get("/peer/list") + .reply( + 200, + { + peers: peersMock, + success: true, }, - success: true, - }, - peerMock.headers, - ]); - axiosMock.onGet(`${peerMock.url}/peer/list`).reply(() => [ - 200, - { - peers: peersMock, - success: true, - }, - peerMock.headers, - ]); + peerMock.headers, + ); const peers = await peerMock.getPeers(); @@ -193,7 +211,10 @@ describe("Peer", () => { const blocks = []; const headers = Object.assign({}, peerMock.headers, { height: 1 }); - axiosMock.onGet(`${peerMock.url}/peer/blocks`).reply(200, { blocks }, headers); + nock(peerMock.url) + .get("/peer/blocks") + .query({ lastBlockHeight: 1 }) + .reply(200, { blocks }, headers); expect(peerMock.state.height).toBeFalsy(); await peerMock.downloadBlocks(1); @@ -204,7 +225,9 @@ describe("Peer", () => { const blocks = [{}]; const headers = Object.assign({}, peerMock.headers, { height: 1 }); - axiosMock.onPost(`${peerMock.url}/peer/blocks`).reply(200, { blocks }, headers); + nock(peerMock.url) + .post("/peer/blocks") + .reply(200, { blocks }, headers); expect(peerMock.state.height).toBeFalsy(); await peerMock.postBlock(genesisBlock.toJson()); @@ -215,7 +238,9 @@ describe("Peer", () => { const transactions = [{}]; const headers = Object.assign({}, peerMock.headers, { height: 1 }); - axiosMock.onPost(`${peerMock.url}/peer/transactions`).reply(200, { transactions }, headers); + nock(peerMock.url) + .post("/peer/transactions") + .reply(200, { transactions }, headers); expect(peerMock.state.height).toBeFalsy(); await peerMock.postTransactions([genesisTransaction.toJson()]); diff --git a/__tests__/unit/core-p2p/utils/check-dns.test.ts b/__tests__/unit/core-p2p/utils/check-dns.test.ts new file mode 100644 index 0000000000..4844152d9b --- /dev/null +++ b/__tests__/unit/core-p2p/utils/check-dns.test.ts @@ -0,0 +1,8 @@ +import { checkDNS } from "../../../../packages/core-p2p/src/utils"; + +describe("Check DNS", () => { + it("should be ok", async () => { + const response = await checkDNS(["1.1.1.1"]); + expect(response).toBe("1.1.1.1"); + }); +}); diff --git a/packages/core-p2p/__tests__/utils/check-ntp.test.ts b/__tests__/unit/core-p2p/utils/check-ntp.test.ts similarity index 68% rename from packages/core-p2p/__tests__/utils/check-ntp.test.ts rename to __tests__/unit/core-p2p/utils/check-ntp.test.ts index 7f8635de0c..479d5ce938 100644 --- a/packages/core-p2p/__tests__/utils/check-ntp.test.ts +++ b/__tests__/unit/core-p2p/utils/check-ntp.test.ts @@ -1,12 +1,16 @@ -import { checkNTP } from "../../src/utils"; -import { setUp, tearDown } from "../__support__/setup"; +import "../mocks/core-container"; -beforeAll(async () => { - await setUp(); -}); +import { checkNTP } from "../../../../packages/core-p2p/src/utils"; -afterAll(async () => { - await tearDown(); +jest.mock("sntp", () => { + return { + time: jest.fn().mockImplementation(options => { + if (options.host === "notime.unknown.not") { + throw new Error("Host unreachable"); + } + return { t: 111 }; + }), + }; }); describe("Check NTP", () => { diff --git a/packages/core-p2p/__tests__/utils/is-valid-peer.test.ts b/__tests__/unit/core-p2p/utils/is-valid-peer.test.ts similarity index 96% rename from packages/core-p2p/__tests__/utils/is-valid-peer.test.ts rename to __tests__/unit/core-p2p/utils/is-valid-peer.test.ts index 61f1970ece..db22ba1c3d 100644 --- a/packages/core-p2p/__tests__/utils/is-valid-peer.test.ts +++ b/__tests__/unit/core-p2p/utils/is-valid-peer.test.ts @@ -1,7 +1,7 @@ import "jest-extended"; import os from "os"; -import { isValidPeer } from "../../src/utils"; +import { isValidPeer } from "../../../../packages/core-p2p/src/utils"; describe("isValidPeer", () => { it("should not be ok for 127.0.0.1", () => { diff --git a/packages/core-p2p/__tests__/utils/is-whitelist.test.ts b/__tests__/unit/core-p2p/utils/is-whitelist.test.ts similarity index 86% rename from packages/core-p2p/__tests__/utils/is-whitelist.test.ts rename to __tests__/unit/core-p2p/utils/is-whitelist.test.ts index 8894feab69..eee76ab5b9 100644 --- a/packages/core-p2p/__tests__/utils/is-whitelist.test.ts +++ b/__tests__/unit/core-p2p/utils/is-whitelist.test.ts @@ -1,4 +1,4 @@ -import { isWhitelisted } from "../../src/utils"; +import { isWhitelisted } from "../../../../packages/core-p2p/src/utils"; const whitelist = ["127.0.0.1", "::ffff:127.0.0.1"]; diff --git a/deprecated/core-snapshots-cli/__tests__/.gitkeep b/__tests__/unit/core-snapshots/.gitkeep similarity index 100% rename from deprecated/core-snapshots-cli/__tests__/.gitkeep rename to __tests__/unit/core-snapshots/.gitkeep diff --git a/packages/core-snapshots/__tests__/fixtures/blocks.ts b/__tests__/unit/core-snapshots/fixtures/blocks.ts similarity index 100% rename from packages/core-snapshots/__tests__/fixtures/blocks.ts rename to __tests__/unit/core-snapshots/fixtures/blocks.ts diff --git a/packages/core-snapshots/__tests__/fixtures/transactions.ts b/__tests__/unit/core-snapshots/fixtures/transactions.ts similarity index 100% rename from packages/core-snapshots/__tests__/fixtures/transactions.ts rename to __tests__/unit/core-snapshots/fixtures/transactions.ts diff --git a/packages/core-snapshots/__tests__/transport/codec/core/core.test.ts b/__tests__/unit/core-snapshots/transport/codec.test.ts similarity index 80% rename from packages/core-snapshots/__tests__/transport/codec/core/core.test.ts rename to __tests__/unit/core-snapshots/transport/codec.test.ts index 67e0aad829..70fc209661 100644 --- a/packages/core-snapshots/__tests__/transport/codec/core/core.test.ts +++ b/__tests__/unit/core-snapshots/transport/codec.test.ts @@ -1,14 +1,12 @@ /* tslint:disable:no-console */ -import pick from "lodash/pick"; +import pick from "lodash.pick"; import msgpack from "msgpack-lite"; -import { blocks } from "../../../fixtures/blocks"; -import { transactions } from "../../../fixtures/transactions"; +import { blocks } from "../fixtures/blocks"; +import { transactions } from "../fixtures/transactions"; -import { CoreCodec } from "../../../../dist/transport/codecs/core-codec"; - -const codec = new CoreCodec(); +import { Codec } from "../../../../packages/core-snapshots/src/transport/codec"; beforeAll(async () => { transactions.forEach((transaction: any) => { @@ -16,11 +14,11 @@ beforeAll(async () => { }); }); -describe("Ark codec testing", () => { +describe("Codec testing", () => { test("Encode/Decode single block", () => { console.time("singleblock"); - const encoded = msgpack.encode(blocks[1], { codec: codec.blocks }); - const decoded = msgpack.decode(encoded, { codec: codec.blocks }); + const encoded = msgpack.encode(blocks[1], { codec: Codec.blocks }); + const decoded = msgpack.decode(encoded, { codec: Codec.blocks }); // removing helper property delete decoded.previous_block_hex; @@ -38,8 +36,8 @@ describe("Ark codec testing", () => { continue; } - const encoded = msgpack.encode(block, { codec: codec.blocks }); - const decoded = msgpack.decode(encoded, { codec: codec.blocks }); + const encoded = msgpack.encode(block, { codec: Codec.blocks }); + const decoded = msgpack.decode(encoded, { codec: Codec.blocks }); // removing helper property delete decoded.previous_block_hex; @@ -69,9 +67,9 @@ describe("Ark codec testing", () => { for (let i = 0; i < 100; i++) { for (const transaction of transferTransactions) { const encoded = msgpack.encode(transaction, { - codec: codec.transactions, + codec: Codec.transactions, }); - const decoded = msgpack.decode(encoded, { codec: codec.transactions }); + const decoded = msgpack.decode(encoded, { codec: Codec.transactions }); const source = pick(transaction, properties); const dest = pick(decoded, properties); @@ -98,8 +96,8 @@ describe("Ark codec testing", () => { const otherTransactions = transactions.filter(trx => trx.type > 0); for (const transaction of otherTransactions) { - const encoded = msgpack.encode(transaction, { codec: codec.transactions }); - const decoded = msgpack.decode(encoded, { codec: codec.transactions }); + const encoded = msgpack.encode(transaction, { codec: Codec.transactions }); + const decoded = msgpack.decode(encoded, { codec: Codec.transactions }); const source = pick(transaction, properties); const dest = pick(decoded, properties); diff --git a/__tests__/unit/core-transaction-pool/__fixtures__/transactions.ts b/__tests__/unit/core-transaction-pool/__fixtures__/transactions.ts new file mode 100644 index 0000000000..ff58020a73 --- /dev/null +++ b/__tests__/unit/core-transaction-pool/__fixtures__/transactions.ts @@ -0,0 +1,86 @@ +import { TransactionFactory } from "../../../helpers/transaction-factory"; +import { delegates } from "../../../utils/fixtures/unitnet/delegates"; + +export const transactions = { + dummy1: TransactionFactory.transfer("AFzQCx5YpGg5vKMBg4xbuYbqkhvMkKfKe5") + .withNetwork("unitnet") + .withPassphrase(delegates[0].passphrase) + .build()[0], + + dummy2: TransactionFactory.transfer("AJ5eV59hu4xrbRCpoP3of7fEYWUteSVa8k", 10000000) + .withNetwork("unitnet") + .withPassphrase(delegates[0].passphrase) + .build()[0], + + dummy3: TransactionFactory.transfer("ANqvJEMZcmUpcKBC8xiP1TntVkJeuZ3Lw3") + .withNetwork("unitnet") + .withPassphrase(delegates[0].passphrase) + .build()[0], + + dummy4: TransactionFactory.transfer("AJ5eV59hu4xrbRCpoP3of7fEYWUteSVa8k") + .withNetwork("unitnet") + .withPassphrase(delegates[0].passphrase) + .build()[0], + + dummy5: TransactionFactory.transfer("ASvC1E9hMLfANTi63S2gUMvr7rVZYJBj3u") + .withNetwork("unitnet") + .withPassphrase(delegates[0].passphrase) + .build()[0], + + dummy6: TransactionFactory.transfer("Ac8utEr7XRebWRvArSBnbVoxbq6bXftAmL") + .withNetwork("unitnet") + .withPassphrase(delegates[0].passphrase) + .build()[0], + + dummy7: TransactionFactory.transfer("ANWEaVfvAh3VTyZNYcuFESUum1XBmAvAdj") + .withNetwork("unitnet") + .withPassphrase(delegates[0].passphrase) + .build()[0], + + dummy8: TransactionFactory.transfer("ALsZS24Dn4HYXwed5kAC5fKyB9BFzdmcSx") + .withNetwork("unitnet") + .withPassphrase(delegates[0].passphrase) + .build()[0], + + dummy9: TransactionFactory.transfer("ANuaLhRuBJhTcHao7kTfDcfsewLQGr7x5G") + .withNetwork("unitnet") + .withPassphrase(delegates[0].passphrase) + .build()[0], + + dummy10: TransactionFactory.transfer("AJ5eV59hu4xrbRCpoP3of7fEYWUteSVa8k") + .withNetwork("unitnet") + .withPassphrase(delegates[1].passphrase) + .build()[0], + + dummyLarge1: TransactionFactory.transfer("AFzQCx5YpGg5vKMBg4xbuYbqkhvMkKfKe5") + .withNetwork("unitnet") + .withPassphrase(delegates[1].passphrase) + .build()[0], + + dummyLarge2: TransactionFactory.transfer("AJ5eV59hu4xrbRCpoP3of7fEYWUteSVa8k") + .withNetwork("unitnet") + .withPassphrase(delegates[1].passphrase) + .build()[0], + + dynamicFeeNormalDummy1: TransactionFactory.transfer("AcjGpvDJEQdBVwspYsAs16B8Rv66zo7gyd") + .withNetwork("unitnet") + .withFee(280000) + .withPassphrase(delegates[0].passphrase) + .build()[0], + + dynamicFeeLowDummy2: TransactionFactory.transfer("AabMvWPVKbdTHRcGBpATq9TEMiMD5xeJh9") + .withNetwork("unitnet") + .withFee(100) + .withPassphrase(delegates[0].passphrase) + .build()[0], + + dummyExp1: TransactionFactory.transfer("AFzQCx5YpGg5vKMBg4xbuYbqkhvMkKfKe5") + .withNetwork("unitnet") + .withPassphrase(delegates[1].passphrase) + .build()[0], + + dummyExp2: TransactionFactory.transfer("AabMvWPVKbdTHRcGBpATq9TEMiMD5xeJh9") + .withNetwork("unitnet") + .withPassphrase(delegates[1].passphrase) + .build()[0], +}; diff --git a/__tests__/unit/core-transaction-pool/__stubs__/connection.ts b/__tests__/unit/core-transaction-pool/__stubs__/connection.ts new file mode 100644 index 0000000000..2e0354c93f --- /dev/null +++ b/__tests__/unit/core-transaction-pool/__stubs__/connection.ts @@ -0,0 +1,123 @@ +import { TransactionPool } from "@arkecosystem/core-interfaces"; +import { Dato } from "@faustbrian/dato"; + +import { constants, ITransactionData, models, Transaction } from "@arkecosystem/crypto"; + +export class Connection implements TransactionPool.IConnection { + public options: any; + public loggedAllowedSenders: string[]; + public walletManager: any; + + public async make(): Promise { + return this; + } + + public driver(): any { + return; + } + + public disconnect(): void { + return; + } + + public getPoolSize(): number { + return 0; + } + + public getSenderSize(senderPublicKey: string): number { + return 0; + } + + public addTransactions( + transactions: Transaction[], + ): { + added: Transaction[]; + notAdded: TransactionPool.IAddTransactionErrorResponse[]; + } { + return { added: [], notAdded: [] }; + } + + public addTransaction(transaction: Transaction): TransactionPool.IAddTransactionResponse { + return null; + } + + public removeTransaction(transaction: Transaction): void { + return; + } + + public removeTransactionById(id: string, senderPublicKey?: string): void { + return; + } + + public getTransactionsForForging(blockSize: number): string[] { + return []; + } + + public getTransaction(id: string): Transaction { + return null; + } + + public getTransactions(start: number, size: number, maxBytes?: number): Buffer[] { + return []; + } + + public getTransactionIdsForForging(start: number, size: number): string[] { + return null; + } + + public getTransactionsData(start: number, size: number, property: string, maxBytes?: number): string[] | Buffer[] { + return null; + } + + public getTransactionsByType(type: any): any { + return; + } + + public removeTransactionsForSender(senderPublicKey: string): void { + return; + } + + public hasExceededMaxTransactions(transaction: ITransactionData): boolean { + return true; + } + + public flush(): void { + return; + } + + public transactionExists(transactionId: string): any { + return; + } + + public isSenderBlocked(senderPublicKey: string): boolean { + return true; + } + + public blockSender(senderPublicKey: string): Dato { + return null; + } + + public acceptChainedBlock(block: models.Block): void { + return; + } + + public async buildWallets(): Promise { + return; + } + + public purgeByPublicKey(senderPublicKey: string): void { + return; + } + + public purgeSendersWithInvalidTransactions(block: models.Block): void { + return; + } + + public purgeBlock(block: models.Block): void { + return; + } + + public senderHasTransactionsOfType(senderPublicKey: string, transactionType: constants.TransactionTypes): boolean { + return true; + } +} diff --git a/packages/core-transaction-pool/__tests__/connection.test.ts b/__tests__/unit/core-transaction-pool/connection.test.ts similarity index 57% rename from packages/core-transaction-pool/__tests__/connection.test.ts rename to __tests__/unit/core-transaction-pool/connection.test.ts index 107aafc575..b8a02d32c2 100644 --- a/packages/core-transaction-pool/__tests__/connection.test.ts +++ b/__tests__/unit/core-transaction-pool/connection.test.ts @@ -1,46 +1,32 @@ /* tslint:disable:max-line-length */ -import { app } from "@arkecosystem/core-container"; -import { Database } from "@arkecosystem/core-interfaces"; +import "./mocks/core-container"; + +import { Wallet } from "@arkecosystem/core-database"; +import { TransactionHandlerRegistry } from "@arkecosystem/core-transactions"; import { bignumify } from "@arkecosystem/core-utils"; -import { Bignum, constants, models, slots } from "@arkecosystem/crypto"; -import dayjs from "dayjs-ext"; +import { Bignum, constants, models, slots, Transaction } from "@arkecosystem/crypto"; +import { dato } from "@faustbrian/dato"; import delay from "delay"; +import cloneDeep from "lodash.clonedeep"; import randomSeed from "random-seed"; -import { generators } from "../../core-test-utils"; -import { block2, delegates } from "../../core-test-utils/src/fixtures/unitnet"; -import { TransactionPool } from "../dist"; +import { Connection } from "../../../packages/core-transaction-pool/src"; +import { defaults } from "../../../packages/core-transaction-pool/src/defaults"; +import { MemPoolTransaction } from "../../../packages/core-transaction-pool/src/mem-pool-transaction"; +import { TransactionFactory } from "../../helpers/transaction-factory"; +import { block2, delegates } from "../../utils/fixtures/unitnet"; import { transactions as mockData } from "./__fixtures__/transactions"; -import { setUpFull, tearDownFull } from "./__support__/setup"; +import { database as databaseService } from "./mocks/database"; const { SATOSHI, TransactionTypes } = constants; -const { Block, Transaction } = models; -const { generateTransfers } = generators; +const { Block } = models; const delegatesSecrets = delegates.map(d => d.secret); +const maxTransactionAge = 4036608000; -let config; -let databaseService: Database.IDatabaseService; -let connection: TransactionPool; +let connection: Connection; beforeAll(async () => { - await setUpFull(); - - config = app.getConfig(); - databaseService = app.resolvePlugin("database"); - connection = app.resolvePlugin("transactionPool"); - - // Ensure no cold wallet and enough funds - databaseService.walletManager.findByPublicKey("000000000000000000000000000000000000000420000000000000000000000000"); - databaseService.walletManager.findByPublicKey( - "0310c283aac7b35b4ae6fab201d36e8322c3408331149982e16013a5bcb917081c", - ).balance = bignumify(200 * 1e8); - - // 100+ years in the future to avoid our hardcoded transactions used in these - // tests to expire - connection.options.maxTransactionAge = 4036608000; -}); - -afterAll(async () => { - await tearDownFull(); + connection = new Connection(defaults); + await connection.make(); }); beforeEach(() => { @@ -48,6 +34,12 @@ beforeEach(() => { }); describe("Connection", () => { + const addTransactions = transactions => { + for (const tx of transactions) { + connection.mem.add(new MemPoolTransaction(tx), maxTransactionAge); + } + }; + describe("getPoolSize", () => { it("should return 0 if no transactions were added", () => { expect(connection.getPoolSize()).toBe(0); @@ -56,11 +48,11 @@ describe("Connection", () => { it("should return 2 if transactions were added", () => { expect(connection.getPoolSize()).toBe(0); - expect(connection.addTransaction(mockData.dummy1)).toEqual({ success: true }); + connection.mem.add(new MemPoolTransaction(mockData.dummy1), maxTransactionAge); expect(connection.getPoolSize()).toBe(1); - expect(connection.addTransaction(mockData.dummy2)).toEqual({ success: true }); + connection.mem.add(new MemPoolTransaction(mockData.dummy2), maxTransactionAge); expect(connection.getPoolSize()).toBe(2); }); @@ -72,21 +64,30 @@ describe("Connection", () => { }); it("should return 2 if transactions were added", () => { - const senderPublicKey = mockData.dummy1.senderPublicKey; + const senderPublicKey = mockData.dummy1.data.senderPublicKey; expect(connection.getSenderSize(senderPublicKey)).toBe(0); - expect(connection.addTransaction(mockData.dummy1)).toEqual({ success: true }); + connection.mem.add(new MemPoolTransaction(mockData.dummy1), maxTransactionAge); expect(connection.getSenderSize(senderPublicKey)).toBe(1); - expect(connection.addTransaction(mockData.dummy3)).toEqual({ success: true }); + connection.mem.add(new MemPoolTransaction(mockData.dummy3), maxTransactionAge); expect(connection.getSenderSize(senderPublicKey)).toBe(2); }); }); describe("addTransaction", () => { + beforeAll(() => { + const mockWallet = new Wallet(delegates[0].address); + jest.spyOn(connection.walletManager, "findByPublicKey").mockReturnValue(mockWallet); + jest.spyOn(connection.walletManager, "canApply").mockReturnValue(true); + }); + afterAll(() => { + jest.restoreAllMocks(); + }); + it("should add the transaction to the pool", () => { expect(connection.getPoolSize()).toBe(0); @@ -113,7 +114,7 @@ describe("Connection", () => { type: "ERR_POOL_FULL", message: `Pool is full (has 4 transactions) and this transaction's fee ` + - `${mockData.dummy5.fee.toFixed()} is not higher than the lowest fee already in pool 10000000`, + `${mockData.dummy5.data.fee} is not higher than the lowest fee already in pool 10000000`, success: false, }); @@ -147,9 +148,22 @@ describe("Connection", () => { connection.options.maxTransactionsInPool = maxTransactionsInPoolOrig; }); + + it.skip("should raise ERR_ALREADY_IN_POOL when adding existing transactions", () => { + // TODO + }); }); describe("addTransactions", () => { + beforeAll(() => { + const mockWallet = new Wallet(delegates[0].address); + jest.spyOn(connection.walletManager, "findByPublicKey").mockReturnValue(mockWallet); + jest.spyOn(connection.walletManager, "canApply").mockReturnValue(true); + }); + afterAll(() => { + jest.restoreAllMocks(); + }); + it("should add the transactions to the pool", () => { expect(connection.getPoolSize()).toBe(0); @@ -160,55 +174,57 @@ describe("Connection", () => { it("should not add not-appliable transactions", () => { // This should be skipped due to insufficient funds - const highFeeTransaction = new Transaction(mockData.dummy3); + const highFeeTransaction = Transaction.fromData(cloneDeep(mockData.dummy3.data)); highFeeTransaction.data.fee = bignumify(1e9 * SATOSHI); // changing public key as fixture transactions have the same one highFeeTransaction.data.senderPublicKey = "000000000000000000000000000000000000000420000000000000000000000000"; - const transactions = [ - mockData.dummy1, - mockData.dummy2, - highFeeTransaction, - mockData.dummy4, - mockData.dummy5, - mockData.dummy6, - ]; - - const { added, notAdded } = connection.addTransactions(transactions); - expect(notAdded[0].message).toEqual( - `["[PoolWalletManager] Can't apply transaction id:${ - mockData.dummy3.id - } from sender:AHkZLLjUdjjjJzNe1zCXqHh27bUhzg8GZw","Insufficient balance in the wallet"]`, - ); - expect(connection.getPoolSize()).toBe(5); + jest.spyOn(connection.walletManager, "canApply").mockImplementation((tx, errors) => { + errors.push("Some error in canApply"); + return false; + }); + const { added, notAdded } = connection.addTransactions([highFeeTransaction]); + expect(notAdded[0]).toEqual({ + message: '["Some error in canApply"]', + transaction: highFeeTransaction, + type: "ERR_APPLY", + success: false, + }); + expect(connection.getPoolSize()).toBe(0); }); }); describe("addTransactions with expiration", () => { + beforeAll(() => { + const mockWallet = new Wallet(delegates[0].address); + jest.spyOn(connection.walletManager, "findByPublicKey").mockReturnValue(mockWallet); + jest.spyOn(connection.walletManager, "canApply").mockReturnValue(true); + }); + afterAll(() => { + jest.restoreAllMocks(); + }); + it("should add the transactions to the pool and they should expire", async () => { expect(connection.getPoolSize()).toBe(0); const expireAfterSeconds = 3; const expiration = slots.getTime() + expireAfterSeconds; - const transactions = []; + const transactions: Transaction[] = []; - transactions.push(new Transaction(mockData.dummyExp1)); - transactions[transactions.length - 1].expiration = expiration; + transactions.push(Transaction.fromData(cloneDeep(mockData.dummyExp1.data))); + transactions[transactions.length - 1].data.expiration = expiration; - transactions.push(new Transaction(mockData.dummy1)); + transactions.push(Transaction.fromData(cloneDeep(mockData.dummy1.data))); // Workaround: Increase balance of sender wallet to succeed - const insufficientBalanceTx: any = new Transaction(mockData.dummyExp2); + const insufficientBalanceTx: any = Transaction.fromData(cloneDeep(mockData.dummyExp2.data)); transactions.push(insufficientBalanceTx); - insufficientBalanceTx.expiration = expiration; + insufficientBalanceTx.data.expiration = expiration; transactions.push(mockData.dummy2); - // Ensure no cold wallets - transactions.forEach(tx => databaseService.walletManager.findByPublicKey(tx.senderPublicKey)); - const { added, notAdded } = connection.addTransactions(transactions); expect(added).toHaveLength(4); expect(notAdded).toBeEmpty(); @@ -223,7 +239,7 @@ describe("Connection", () => { describe("removeTransaction", () => { it("should remove the specified transaction from the pool", () => { - connection.addTransaction(mockData.dummy1); + connection.mem.add(new MemPoolTransaction(mockData.dummy1), maxTransactionAge); expect(connection.getPoolSize()).toBe(1); @@ -235,7 +251,7 @@ describe("Connection", () => { describe("removeTransactionById", () => { it("should remove the specified transaction from the pool (by id)", () => { - connection.addTransaction(mockData.dummy1); + connection.mem.add(new MemPoolTransaction(mockData.dummy1), maxTransactionAge); expect(connection.getPoolSize()).toBe(1); @@ -245,7 +261,7 @@ describe("Connection", () => { }); it("should do nothing when asked to delete a non-existent transaction", () => { - connection.addTransaction(mockData.dummy1); + connection.mem.add(new MemPoolTransaction(mockData.dummy1), maxTransactionAge); connection.removeTransactionById("nonexistenttransactionid"); @@ -255,16 +271,18 @@ describe("Connection", () => { describe("removeTransactionsForSender", () => { it("should remove the senders transactions from the pool", () => { - connection.addTransaction(mockData.dummy1); - connection.addTransaction(mockData.dummy3); - connection.addTransaction(mockData.dummy4); - connection.addTransaction(mockData.dummy5); - connection.addTransaction(mockData.dummy6); - connection.addTransaction(mockData.dummy10); + addTransactions([ + mockData.dummy1, + mockData.dummy3, + mockData.dummy4, + mockData.dummy5, + mockData.dummy6, + mockData.dummy10, + ]); expect(connection.getPoolSize()).toBe(6); - connection.removeTransactionsForSender(mockData.dummy1.senderPublicKey); + connection.removeTransactionsForSender(mockData.dummy1.data.senderPublicKey); expect(connection.getPoolSize()).toBe(1); }); @@ -272,7 +290,7 @@ describe("Connection", () => { describe("transactionExists", () => { it("should return true if transaction is IN pool", () => { - connection.addTransactions([mockData.dummy1, mockData.dummy2]); + addTransactions([mockData.dummy1, mockData.dummy2]); expect(connection.transactionExists(mockData.dummy1.id)).toBeTrue(); expect(connection.transactionExists(mockData.dummy2.id)).toBeTrue(); @@ -288,16 +306,18 @@ describe("Connection", () => { it("should be true if exceeded", () => { connection.options.maxTransactionsPerSender = 5; connection.options.allowedSenders = []; - connection.addTransaction(mockData.dummy3); - connection.addTransaction(mockData.dummy4); - connection.addTransaction(mockData.dummy5); - connection.addTransaction(mockData.dummy6); - connection.addTransaction(mockData.dummy7); - connection.addTransaction(mockData.dummy8); - connection.addTransaction(mockData.dummy9); + addTransactions([ + mockData.dummy3, + mockData.dummy4, + mockData.dummy5, + mockData.dummy6, + mockData.dummy7, + mockData.dummy8, + mockData.dummy9, + ]); expect(connection.getPoolSize()).toBe(7); - const exceeded = connection.hasExceededMaxTransactions(mockData.dummy3); + const exceeded = connection.hasExceededMaxTransactions(mockData.dummy3.data); expect(exceeded).toBeTrue(); }); @@ -305,12 +325,10 @@ describe("Connection", () => { connection.options.maxTransactionsPerSender = 7; connection.options.allowedSenders = []; - connection.addTransaction(mockData.dummy4); - connection.addTransaction(mockData.dummy5); - connection.addTransaction(mockData.dummy6); + addTransactions([mockData.dummy4, mockData.dummy5, mockData.dummy6]); expect(connection.getPoolSize()).toBe(3); - const exceeded = connection.hasExceededMaxTransactions(mockData.dummy3); + const exceeded = connection.hasExceededMaxTransactions(mockData.dummy3.data); expect(exceeded).toBeFalse(); }); @@ -318,23 +336,25 @@ describe("Connection", () => { connection.flush(); connection.options.maxTransactionsPerSender = 5; connection.options.allowedSenders = [delegates[0].publicKey, delegates[1].publicKey]; - connection.addTransaction(mockData.dummy3); - connection.addTransaction(mockData.dummy4); - connection.addTransaction(mockData.dummy5); - connection.addTransaction(mockData.dummy6); - connection.addTransaction(mockData.dummy7); - connection.addTransaction(mockData.dummy8); - connection.addTransaction(mockData.dummy9); + addTransactions([ + mockData.dummy3, + mockData.dummy4, + mockData.dummy5, + mockData.dummy6, + mockData.dummy7, + mockData.dummy8, + mockData.dummy9, + ]); expect(connection.getPoolSize()).toBe(7); - const exceeded = connection.hasExceededMaxTransactions(mockData.dummy3); + const exceeded = connection.hasExceededMaxTransactions(mockData.dummy3.data); expect(exceeded).toBeFalse(); }); }); describe("getTransaction", () => { it("should return the specified transaction", () => { - connection.addTransaction(mockData.dummy1); + addTransactions([mockData.dummy1]); const poolTransaction = connection.getTransaction(mockData.dummy1.id); expect(poolTransaction).toBeObject(); @@ -351,16 +371,16 @@ describe("Connection", () => { it("should return transactions within the specified range", () => { const transactions = [mockData.dummy1, mockData.dummy2]; - connection.addTransactions(transactions); + addTransactions(transactions); - if (transactions[1].fee > transactions[0].fee) { + if (transactions[1].data.fee > transactions[0].data.fee) { transactions.reverse(); } for (const i of [0, 1]) { const retrieved = connection - .getTransactions(i, 1, 0) - .map(serializedTx => new Transaction(serializedTx)); + .getTransactions(i, 1) + .map(serializedTx => Transaction.fromBytes(serializedTx)); expect(retrieved.length).toBe(1); expect(retrieved[0]).toBeObject(); @@ -371,12 +391,14 @@ describe("Connection", () => { describe("getTransactionIdsForForging", () => { it("should return an array of transactions ids", () => { - connection.addTransaction(mockData.dummy1); - connection.addTransaction(mockData.dummy2); - connection.addTransaction(mockData.dummy3); - connection.addTransaction(mockData.dummy4); - connection.addTransaction(mockData.dummy5); - connection.addTransaction(mockData.dummy6); + addTransactions([ + mockData.dummy1, + mockData.dummy2, + mockData.dummy3, + mockData.dummy4, + mockData.dummy5, + mockData.dummy6, + ]); const transactionIds = connection.getTransactionIdsForForging(0, 6); @@ -390,9 +412,12 @@ describe("Connection", () => { }); it("should only return transaction ids for transactions not exceeding the maximum payload size", () => { + // @FIXME: Uhm excuse me, what the? mockData.dummyLarge1.data.signatures = mockData.dummyLarge2.data.signatures = [""]; for (let i = 0; i < connection.options.maxTransactionBytes * 0.6; i++) { + // @ts-ignore mockData.dummyLarge1.data.signatures += "1"; + // @ts-ignore mockData.dummyLarge2.data.signatures += "2"; } @@ -406,10 +431,7 @@ describe("Connection", () => { mockData.dummy7, ]; - // Add exception for oversized transactions with extra signatures data - config.set("exceptions.transactions", [mockData.dummyLarge1.id, mockData.dummyLarge2.id]); - - connection.addTransactions(transactions); + addTransactions(transactions); let transactionIds = connection.getTransactionIdsForForging(0, 7); expect(transactionIds).toBeArray(); @@ -438,16 +460,19 @@ describe("Connection", () => { describe("getTransactionsForForging", () => { it("should return an array of transactions serialized", () => { const transactions = [mockData.dummy1, mockData.dummy2, mockData.dummy3, mockData.dummy4]; - connection.addTransactions(transactions); + addTransactions(transactions); const transactionsForForging = connection.getTransactionsForForging(4); - expect(transactionsForForging).toEqual(transactions.map(tx => tx.serialized)); + expect(transactionsForForging).toEqual(transactions.map(tx => tx.serialized.toString("hex"))); }); it("should only return transactions not exceeding the maximum payload size", () => { + // @FIXME: Uhm excuse me, what the? mockData.dummyLarge1.data.signatures = mockData.dummyLarge2.data.signatures = [""]; for (let i = 0; i < connection.options.maxTransactionBytes * 0.6; i++) { + // @ts-ignore mockData.dummyLarge1.data.signatures += "1"; + // @ts-ignore mockData.dummyLarge2.data.signatures += "2"; } @@ -461,20 +486,17 @@ describe("Connection", () => { mockData.dummy7, ]; - // Add exception for oversized transactions with extra signatures data - config.set("exceptions.transactions", [mockData.dummyLarge1.id, mockData.dummyLarge2.id]); - - connection.addTransactions(transactions); + addTransactions(transactions); let transactionsForForging = connection.getTransactionsForForging(7); expect(transactionsForForging.length).toBe(6); - expect(transactionsForForging[0]).toEqual(mockData.dummyLarge1.serialized); - expect(transactionsForForging[1]).toEqual(mockData.dummy3.serialized); - expect(transactionsForForging[2]).toEqual(mockData.dummy4.serialized); - expect(transactionsForForging[3]).toEqual(mockData.dummy5.serialized); - expect(transactionsForForging[4]).toEqual(mockData.dummy6.serialized); - expect(transactionsForForging[5]).toEqual(mockData.dummy7.serialized); + expect(transactionsForForging[0]).toEqual(mockData.dummyLarge1.serialized.toString("hex")); + expect(transactionsForForging[1]).toEqual(mockData.dummy3.serialized.toString("hex")); + expect(transactionsForForging[2]).toEqual(mockData.dummy4.serialized.toString("hex")); + expect(transactionsForForging[3]).toEqual(mockData.dummy5.serialized.toString("hex")); + expect(transactionsForForging[4]).toEqual(mockData.dummy6.serialized.toString("hex")); + expect(transactionsForForging[5]).toEqual(mockData.dummy7.serialized.toString("hex")); connection.removeTransactionById(mockData.dummyLarge1.id); connection.removeTransactionById(mockData.dummy3.id); @@ -485,13 +507,13 @@ describe("Connection", () => { transactionsForForging = connection.getTransactionsForForging(7); expect(transactionsForForging.length).toBe(1); - expect(transactionsForForging[0]).toEqual(mockData.dummyLarge2.serialized); + expect(transactionsForForging[0]).toEqual(mockData.dummyLarge2.serialized.toString("hex")); }); }); describe("flush", () => { it("should flush the pool", () => { - connection.addTransaction(mockData.dummy1); + addTransactions([mockData.dummy1]); expect(connection.getPoolSize()).toBe(1); @@ -509,173 +531,188 @@ describe("Connection", () => { it("should return true if sender is blocked", () => { const publicKey = "thisPublicKeyIsBlocked"; - connection.blockedByPublicKey[publicKey] = dayjs().add(1, "hour"); + (connection as any).blockedByPublicKey[publicKey] = dato().addHours(1); expect(connection.isSenderBlocked(publicKey)).toBeTrue(); }); it("should return false and remove blockedByPublicKey[senderPublicKey] when sender is not blocked anymore", async () => { const publicKey = "thisPublicKeyIsNotBlockedAnymore"; - connection.blockedByPublicKey[publicKey] = dayjs().add(1, "second"); - await delay(1100); + (connection as any).blockedByPublicKey[publicKey] = dato().subSeconds(1); expect(connection.isSenderBlocked(publicKey)).toBeFalse(); - expect(connection.blockedByPublicKey[publicKey]).toBeUndefined(); + expect((connection as any).blockedByPublicKey[publicKey]).toBeUndefined(); }); }); describe("blockSender", () => { it("should block sender for 1 hour", () => { const publicKey = "publicKeyToBlock"; - const plus1HourBefore = dayjs().add(1, "hour"); + const plus1HourBefore = dato().addHours(1); const blockReleaseTime = connection.blockSender(publicKey); - const plus1HourAfter = dayjs().add(1, "hour"); - expect(connection.blockedByPublicKey[publicKey]).toBe(blockReleaseTime); + const plus1HourAfter = dato().addHours(1); + expect((connection as any).blockedByPublicKey[publicKey]).toBe(blockReleaseTime); expect(blockReleaseTime >= plus1HourBefore).toBeTrue(); expect(blockReleaseTime <= plus1HourAfter).toBeTrue(); }); }); describe("acceptChainedBlock", () => { - beforeEach(() => connection.walletManager.reset()); - afterEach(() => connection.walletManager.reset()); + let mockWallet; + beforeEach(() => { + const transactionHandler = TransactionHandlerRegistry.get(TransactionTypes.Transfer); + jest.spyOn(transactionHandler, "canBeApplied").mockReturnValue(true); + + mockWallet = new Wallet(block2.transactions[0].recipientId); + mockWallet.balance = new Bignum(1e12); + jest.spyOn(connection.walletManager, "exists").mockReturnValue(true); + jest.spyOn(connection.walletManager, "findByPublicKey").mockImplementation(publicKey => { + if (publicKey === block2.generatorPublicKey) { + return new Wallet("thisIsTheDelegateGeneratorAddress0"); + } + return mockWallet; + }); + jest.spyOn(connection.walletManager, "findByAddress").mockReturnValue(mockWallet); + }); + afterEach(() => { + jest.restoreAllMocks(); + }); it("should update wallet when accepting a chained block", () => { - const senderRecipientWallet = connection.walletManager.findByAddress(block2.transactions[0].recipientId); - const balanceBefore = senderRecipientWallet.balance; + const balanceBefore = mockWallet.balance; connection.acceptChainedBlock(new Block(block2)); - expect(+senderRecipientWallet.balance).toBe(balanceBefore - block2.totalFee); + expect(+mockWallet.balance).toBe(+balanceBefore.minus(block2.totalFee)); }); it("should remove transaction from pool if it's in the chained block", () => { - const transaction0 = new Transaction(block2.transactions[0]); - connection.addTransaction(transaction0); + addTransactions([mockData.dummy2]); - expect(connection.getTransactions(0, 10, 0)).toEqual([transaction0.serialized]); + expect(connection.getTransactions(0, 10)).toEqual([mockData.dummy2.serialized]); - connection.acceptChainedBlock(new Block(block2)); + const chainedBlock = new Block(block2); + chainedBlock.transactions.push(mockData.dummy2); + + connection.acceptChainedBlock(chainedBlock); - expect(connection.getTransactions(0, 10, 0)).toEqual([]); + expect(connection.getTransactions(0, 10)).toEqual([]); }); it("should purge and block sender if canApply() failed for a transaction in the chained block", () => { - const senderRecipientWallet = connection.walletManager.findByAddress(block2.transactions[0].recipientId); - senderRecipientWallet.balance = new Bignum(10); // not enough funds for transactions in block - - expect(connection.walletManager.allByAddress()).toEqual([senderRecipientWallet]); + const transactionHandler = TransactionHandlerRegistry.get(TransactionTypes.Transfer); + jest.spyOn(transactionHandler, "canBeApplied").mockImplementation(() => { + throw new Error("test error"); + }); + const purgeByPublicKey = jest.spyOn(connection, "purgeByPublicKey"); - // canApply should fail because wallet has not enough funds connection.acceptChainedBlock(new Block(block2)); - expect(connection.walletManager.allByAddress()).toEqual([]); + expect(purgeByPublicKey).toHaveBeenCalledTimes(1); expect(connection.isSenderBlocked(block2.transactions[0].senderPublicKey)).toBeTrue(); }); it("should delete wallet of transaction sender if its balance is down to zero", () => { - const senderRecipientWallet = connection.walletManager.findByAddress(block2.transactions[0].recipientId); - senderRecipientWallet.balance = new Bignum(block2.totalFee); // exactly enough funds for transactions in block - - expect(connection.walletManager.allByAddress()).toEqual([senderRecipientWallet]); + jest.spyOn(connection.walletManager, "canBePurged").mockReturnValue(true); + const deleteWallet = jest.spyOn(connection.walletManager, "deleteWallet"); connection.acceptChainedBlock(new Block(block2)); - expect(connection.walletManager.allByAddress()).toEqual([]); + expect(deleteWallet).toHaveBeenCalledTimes(block2.transactions.length); }); }); describe("buildWallets", () => { - beforeEach(() => connection.walletManager.reset()); - afterEach(() => connection.walletManager.reset()); + let findByPublicKey; + let canBeApplied; + let applyToSender; + const findByPublicKeyWallet = new Wallet("thisIsAnAddressIMadeUpJustLikeThis"); + beforeEach(() => { + const transactionHandler = TransactionHandlerRegistry.get(TransactionTypes.Transfer); + canBeApplied = jest.spyOn(transactionHandler, "canBeApplied").mockReturnValue(true); + applyToSender = jest.spyOn(transactionHandler, "applyToSender").mockReturnValue(); + + jest.spyOn(connection.walletManager, "exists").mockReturnValue(true); + findByPublicKey = jest + .spyOn(connection.walletManager, "findByPublicKey") + .mockReturnValue(findByPublicKeyWallet as any); + jest.spyOn(connection.walletManager, "findByAddress").mockReturnValue(new Wallet( + "nowThisIsAnotherCoolAddressIMadeUp", + ) as any); + }); + afterEach(() => { + jest.restoreAllMocks(); + }); it("should build wallets from transactions in the pool", async () => { - const transaction0 = new Transaction(block2.transactions[0]); - connection.addTransaction(transaction0); + addTransactions([mockData.dummy1]); - expect(connection.getTransactions(0, 10, 0)).toEqual([transaction0.serialized]); - - connection.walletManager.reset(); - - expect(connection.walletManager.allByAddress()).toEqual([]); + expect(connection.getTransactions(0, 10)).toEqual([mockData.dummy1.serialized]); await connection.buildWallets(); - const allWallets = connection.walletManager.allByAddress(); - expect(allWallets).toHaveLength(1); - expect(allWallets[0].publicKey).toBe(transaction0.senderPublicKey); + expect(findByPublicKey).toHaveBeenCalledWith(mockData.dummy1.data.senderPublicKey); + expect(canBeApplied).toHaveBeenCalledWith(mockData.dummy1, findByPublicKeyWallet); + expect(applyToSender).toHaveBeenCalledWith(mockData.dummy1, findByPublicKeyWallet); }); it("should handle getTransaction() not finding transaction", async () => { - const transaction0 = new Transaction(block2.transactions[0]); - connection.addTransaction(transaction0); - - expect(connection.getTransactions(0, 10, 0)).toEqual([transaction0.serialized]); - - connection.walletManager.reset(); - - expect(connection.walletManager.allByAddress()).toEqual([]); - - jest.spyOn(connection, "getTransaction").mockImplementationOnce(id => undefined); + const getTransaction = jest.spyOn(connection, "getTransaction").mockImplementationOnce(id => undefined); + addTransactions([mockData.dummy1]); await connection.buildWallets(); - expect(connection.walletManager.allByAddress()).toEqual([]); + expect(getTransaction).toHaveBeenCalled(); + expect(findByPublicKey).not.toHaveBeenCalled(); + expect(canBeApplied).not.toHaveBeenCalled(); + expect(applyToSender).not.toHaveBeenCalled(); }); - it("should not apply transaction to wallet if canApply() failed", async () => { - const transaction0 = new Transaction(block2.transactions[0]); - connection.addTransaction(transaction0); - expect(connection.getTransactions(0, 10, 0)).toEqual([transaction0.serialized]); - - connection.walletManager.reset(); - expect(connection.walletManager.allByAddress()).toEqual([]); - - const senderRecipientWallet = connection.walletManager.findByAddress(block2.transactions[0].recipientId); - senderRecipientWallet.balance = new Bignum(10); // not enough funds for transactions in block - - jest.spyOn(connection.walletManager, "findByPublicKey").mockImplementationOnce( - publicKey => senderRecipientWallet, - ); + it("should not apply transaction to wallet if canBeApplied() failed", async () => { + const transactionHandler = TransactionHandlerRegistry.get(TransactionTypes.Transfer); + canBeApplied = jest.spyOn(transactionHandler, "canBeApplied").mockImplementation(() => { + throw new Error("throw from test"); + }); + const purgeByPublicKey = jest.spyOn(connection, "purgeByPublicKey").mockReturnValue(); + addTransactions([mockData.dummy1]); await connection.buildWallets(); - expect(connection.walletManager.allByAddress()).toEqual([]); // canApply() failed, wallet was purged + expect(applyToSender).not.toHaveBeenCalled(); + expect(canBeApplied).toHaveBeenCalledWith(mockData.dummy1, findByPublicKeyWallet); + expect(purgeByPublicKey).toHaveBeenCalledWith(mockData.dummy1.data.senderPublicKey); }); }); describe("senderHasTransactionsOfType", () => { it("should be false for non-existent sender", () => { - connection.addTransaction(mockData.dummy1); + addTransactions([mockData.dummy1]); expect(connection.senderHasTransactionsOfType("nonexistent", TransactionTypes.Vote)).toBeFalse(); }); it("should be false for existent sender with no votes", () => { - const tx = mockData.dummy1; - - connection.addTransaction(tx); + addTransactions([mockData.dummy1]); - expect(connection.senderHasTransactionsOfType(tx.senderPublicKey, TransactionTypes.Vote)).toBeFalse(); + expect( + connection.senderHasTransactionsOfType(mockData.dummy1.data.senderPublicKey, TransactionTypes.Vote), + ).toBeFalse(); }); it("should be true for existent sender with votes", () => { const tx = mockData.dummy1; - // Prevent 'wallet has already voted' error - connection.walletManager.findByPublicKey(tx.senderPublicKey).vote = ""; - - const voteTx = new Transaction(tx); - voteTx.id = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; - voteTx.type = TransactionTypes.Vote; - voteTx.amount = bignumify(0); - voteTx.asset = { votes: [`+${tx.senderPublicKey}`] }; + const voteTx = Transaction.fromData(cloneDeep(tx.data)); + voteTx.data.id = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; + voteTx.data.type = TransactionTypes.Vote; + voteTx.data.amount = bignumify(0); + voteTx.data.asset = { votes: [`+${tx.data.senderPublicKey}`] }; const transactions = [tx, voteTx, mockData.dummy2]; - connection.addTransactions(transactions); + addTransactions(transactions); - expect(connection.senderHasTransactionsOfType(tx.senderPublicKey, TransactionTypes.Vote)).toBeTrue(); + expect(connection.senderHasTransactionsOfType(tx.data.senderPublicKey, TransactionTypes.Vote)).toBeTrue(); }); }); @@ -685,7 +722,7 @@ describe("Connection", () => { const transactions = [mockData.dummy1, mockData.dummy4]; - connection.addTransactions(transactions); + addTransactions(transactions); expect(connection.getPoolSize()).toBe(2); @@ -695,9 +732,7 @@ describe("Connection", () => { expect(connection.getPoolSize()).toBe(2); - transactions.forEach(t => - expect(connection.getTransaction(t.id).serialized.toLowerCase()).toBe(t.serialized.toLowerCase()), - ); + transactions.forEach(t => expect(connection.getTransaction(t.id).serialized).toEqual(t.serialized)); connection.flush(); }); @@ -705,27 +740,11 @@ describe("Connection", () => { it("remove forged when starting", async () => { expect(connection.getPoolSize()).toBe(0); - const block = await databaseService.getLastBlock(); - - // XXX This accesses directly block.transactions which is not even - // documented in packages/crypto/src/models/block.js - const forgedTransaction = block.transactions[0]; - - // Workaround: Add tx to exceptions so it gets applied, because the fee is 0. - config.set("exceptions.transactions", [forgedTransaction.id]); - - // For some reason all genesis transactions fail signature verification, so - // they are not loaded from the local storage and this fails otherwise. - // TODO: Use jest.spyOn() to change behavior instead. jest.restoreAllMocks() will reset afterwards - const original = databaseService.getForgedTransactionsIds; - // @ts-ignore - databaseService.getForgedTransactionsIds = jest.fn(() => [forgedTransaction.id]); + jest.spyOn(databaseService, "getForgedTransactionsIds").mockReturnValue([mockData.dummy2.id]); - expect(forgedTransaction instanceof Transaction).toBeTrue(); + const transactions = [mockData.dummy1, mockData.dummy2, mockData.dummy4]; - const transactions = [mockData.dummy1, forgedTransaction, mockData.dummy4]; - - connection.addTransactions(transactions); + addTransactions(transactions); expect(connection.getPoolSize()).toBe(3); @@ -737,27 +756,41 @@ describe("Connection", () => { transactions.splice(1, 1); - transactions.forEach(t => - expect(connection.getTransaction(t.id).serialized.toLowerCase()).toBe(t.serialized.toLowerCase()), - ); + transactions.forEach(t => expect(connection.getTransaction(t.id).serialized).toEqual(t.serialized)); connection.flush(); - databaseService.getForgedTransactionsIds = original; + jest.restoreAllMocks(); }); }); describe("stress", () => { - const fakeTransactionId = i => `id${String(i)}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`; + beforeAll(() => { + const mockWallet = new Wallet(delegates[0].address); + jest.spyOn(connection.walletManager, "findByPublicKey").mockReturnValue(mockWallet); + jest.spyOn(connection.walletManager, "canApply").mockReturnValue(true); + }); + afterAll(() => { + jest.restoreAllMocks(); + }); + + const fakeTransactionId = i => `${String(i)}${"a".repeat(64 - String(i).length)}`; it("multiple additions and retrievals", () => { // Abstract number which decides how many iterations are run by the test. // Increase it to run more iterations. const testSize = connection.options.syncInterval * 2; + const usedId = {}; for (let i = 0; i < testSize; i++) { - const transaction = new Transaction(mockData.dummy1); - transaction.id = fakeTransactionId(i); + const transaction = Transaction.fromData(cloneDeep(mockData.dummy1.data)); + transaction.data.id = fakeTransactionId(i); + if (usedId[transaction.data.id]) { + console.log("AAAAA"); + } else { + usedId[transaction.data.id] = true; + } + connection.addTransaction(transaction); if (i % 27 === 0) { @@ -767,17 +800,19 @@ describe("Connection", () => { for (let i = 0; i < testSize * 2; i++) { connection.getPoolSize(); - for (const sender of ["nonexistent", mockData.dummy1.senderPublicKey]) { + for (const sender of ["nonexistent", mockData.dummy1.data.senderPublicKey]) { connection.getSenderSize(sender); + // @FIXME: Uhm excuse me, what the? + // @ts-ignore connection.hasExceededMaxTransactions(sender); } connection.getTransaction(fakeTransactionId(i)); - connection.getTransactions(0, i, 0); + connection.getTransactions(0, i); } for (let i = 0; i < testSize; i++) { - const transaction = new Transaction(mockData.dummy1); - transaction.id = fakeTransactionId(i); + const transaction = Transaction.fromData(cloneDeep(mockData.dummy1.data)); + transaction.data.id = fakeTransactionId(i); connection.removeTransaction(transaction); } }); @@ -785,13 +820,13 @@ describe("Connection", () => { it("delete + add after sync", () => { for (let i = 0; i < connection.options.syncInterval; i++) { // tslint:disable-next-line:no-shadowed-variable - const transaction = new Transaction(mockData.dummy1); - transaction.id = fakeTransactionId(i); + const transaction = Transaction.fromData(cloneDeep(mockData.dummy1.data)); + transaction.data.id = fakeTransactionId(i); connection.addTransaction(transaction); } - const transaction = new Transaction(mockData.dummy1); - transaction.id = fakeTransactionId(0); + const transaction = Transaction.fromData(cloneDeep(mockData.dummy1.data)); + transaction.data.id = fakeTransactionId(0); connection.removeTransaction(transaction); connection.addTransaction(transaction); }); @@ -803,12 +838,12 @@ describe("Connection", () => { // a deterministic test. const rand = randomSeed.create("0"); - const allTransactions = []; + const allTransactions: Transaction[] = []; for (let i = 0; i < nAdd; i++) { - const transaction = new Transaction(mockData.dummy1); - transaction.id = fakeTransactionId(i); - transaction.fee = bignumify(rand.intBetween(0.002 * SATOSHI, 2 * SATOSHI)); - transaction.serialized = Transaction.serialize(transaction).toString("hex"); + const transaction = Transaction.fromData(cloneDeep(mockData.dummy1.data)); + transaction.data.id = fakeTransactionId(i); + transaction.data.fee = bignumify(rand.intBetween(0.002 * SATOSHI, 2 * SATOSHI)); + transaction.serialized = Transaction.toBytes(transaction.data); allTransactions.push(transaction); } @@ -819,16 +854,16 @@ describe("Connection", () => { const nGet = 150; const topFeesExpected = allTransactions - .map(t => t.fee) + .map(t => t.data.fee as any) .sort((a, b) => b - a) .slice(0, nGet) .map(f => f.toString()); // console.time(`time to get first ${nGet}`) - const topTransactionsSerialized = connection.getTransactions(0, nGet, 0); + const topTransactionsSerialized = connection.getTransactions(0, nGet); // console.timeEnd(`time to get first ${nGet}`) - const topFeesReceived = topTransactionsSerialized.map(e => new Transaction(e).fee.toString()); + const topFeesReceived = topTransactionsSerialized.map(e => Transaction.fromBytes(e).data.fee.toString()); expect(topFeesReceived).toEqual(topFeesExpected); }); @@ -836,26 +871,32 @@ describe("Connection", () => { describe("purgeSendersWithInvalidTransactions", () => { it("should purge transactions from sender when invalid", async () => { - const transfersA = generateTransfers("unitnet", delegatesSecrets[0], mockData.dummy1.recipientId, 1, 5); + const transfersA = TransactionFactory.transfer(mockData.dummy1.data.recipientId) + .withNetwork("unitnet") + .withPassphrase(delegatesSecrets[0]) + .build(5); - const transfersB = generateTransfers("unitnet", delegatesSecrets[1], mockData.dummy1.recipientId, 1, 1); + const transfersB = TransactionFactory.transfer(mockData.dummy1.data.recipientId) + .withNetwork("unitnet") + .withPassphrase(delegatesSecrets[1]) + .build(); const block = { transactions: [...transfersA, ...transfersB], - }; + } as any; - block.transactions.forEach(tx => connection.addTransaction(tx)); + addTransactions(block.transactions); expect(connection.getPoolSize()).toBe(6); // Last tx has a unique sender - block.transactions[5].verified = false; + block.transactions[5].isVerified = false; connection.purgeSendersWithInvalidTransactions(block); expect(connection.getPoolSize()).toBe(5); // The remaining tx all have the same sender - block.transactions[0].verified = false; + block.transactions[0].isVerified = false; connection.purgeSendersWithInvalidTransactions(block); expect(connection.getPoolSize()).toBe(0); @@ -864,15 +905,26 @@ describe("Connection", () => { describe("purgeBlock", () => { it("should purge transactions from block", async () => { - const transactions = generateTransfers("unitnet", delegatesSecrets[0], mockData.dummy1.recipientId, 1, 5); - const block = { transactions }; + const revertTransactionForSender = jest + .spyOn(connection.walletManager, "revertTransactionForSender") + .mockReturnValue(); - block.transactions.forEach(tx => connection.addTransaction(tx)); + const transactions = TransactionFactory.transfer(mockData.dummy1.data.recipientId) + .withNetwork("unitnet") + .withPassphrase(delegatesSecrets[0]) + .build(5); + + const block = { transactions } as models.Block; + + addTransactions(block.transactions); expect(connection.getPoolSize()).toBe(5); connection.purgeBlock(block); + expect(revertTransactionForSender).toHaveBeenCalledTimes(5); expect(connection.getPoolSize()).toBe(0); + + jest.restoreAllMocks(); }); }); diff --git a/packages/core-transaction-pool/__tests__/dynamic-fee.test.ts b/__tests__/unit/core-transaction-pool/dynamic-fee.test.ts similarity index 66% rename from packages/core-transaction-pool/__tests__/dynamic-fee.test.ts rename to __tests__/unit/core-transaction-pool/dynamic-fee.test.ts index 03e9ca9907..a6ec8e5935 100644 --- a/packages/core-transaction-pool/__tests__/dynamic-fee.test.ts +++ b/__tests__/unit/core-transaction-pool/dynamic-fee.test.ts @@ -1,35 +1,14 @@ -import { Blockchain, Container } from "@arkecosystem/core-interfaces"; -import { calculateFee, dynamicFeeMatcher } from "../src/dynamic-fee"; -import { transactions } from "./__fixtures__/transactions"; -import { setUpFull, tearDownFull } from "./__support__/setup"; - -let config; -let blockchain: Blockchain.IBlockchain; -let container: Container.IContainer; - -beforeAll(async () => { - container = await setUpFull(); +import "./mocks/core-container"; - config = require("../src").config; - config.init(container.resolveOptions("transactionPool")); - blockchain = container.resolvePlugin("blockchain"); -}); +import { config } from "../../../packages/core-transaction-pool/src"; +import { defaults } from "../../../packages/core-transaction-pool/src/defaults"; +import { calculateFee, dynamicFeeMatcher } from "../../../packages/core-transaction-pool/src/dynamic-fee"; +import { transactions } from "./__fixtures__/transactions"; -afterAll(async () => { - await tearDownFull(); -}); +beforeEach(() => config.init(defaults)); describe("static fees", () => { - beforeAll(() => { - // @ts-ignore - blockchain.getLastBlock = jest.fn(plugin => ({ - data: { - height: 20, - }, - })); - - config.set("dynamicFees.enabled", false); - }); + beforeEach(() => config.set("dynamicFees.enabled", false)); it("should accept transactions matching the static fee for broadcast", () => { expect(dynamicFeeMatcher(transactions.dummy1).broadcast).toBeTrue(); @@ -43,38 +22,32 @@ describe("static fees", () => { it("should not broadcast transactions with a fee other than the static fee", () => { expect(dynamicFeeMatcher(transactions.dynamicFeeNormalDummy1).broadcast).toBeFalse(); - expect(dynamicFeeMatcher(transactions.dynamicFeeZero).broadcast).toBeFalse(); }); it("should not allow transactions with a fee other than the static fee to enter the pool", () => { expect(dynamicFeeMatcher(transactions.dynamicFeeNormalDummy1).enterPool).toBeFalse(); - expect(dynamicFeeMatcher(transactions.dynamicFeeZero).enterPool).toBeFalse(); }); }); describe("dynamic fees", () => { - let dynFeeConfig; - beforeAll(() => { - // @ts-ignore - blockchain.getLastBlock = jest.fn(plugin => ({ - data: { - height: 20, - }, - })); - + let dynamicFeeConfig; + beforeEach(() => { config.set("dynamicFees.enabled", true); - dynFeeConfig = config.get("dynamicFees"); + dynamicFeeConfig = config.get("dynamicFees"); }); it("should broadcast transactions with high enough fee", () => { expect(dynamicFeeMatcher(transactions.dummy1).broadcast).toBeTrue(); expect(dynamicFeeMatcher(transactions.dummy2).broadcast).toBeTrue(); + + transactions.dynamicFeeNormalDummy1.data.fee = + calculateFee(dynamicFeeConfig.minFeeBroadcast, transactions.dynamicFeeNormalDummy1) + 100; expect(dynamicFeeMatcher(transactions.dynamicFeeNormalDummy1).broadcast).toBeTrue(); // testing with transaction fee === min fee for transaction broadcast - transactions.dummy3.fee = calculateFee(dynFeeConfig.minFeeBroadcast, transactions.dummy3); - transactions.dummy4.fee = calculateFee(dynFeeConfig.minFeeBroadcast, transactions.dummy4); + transactions.dummy3.data.fee = calculateFee(dynamicFeeConfig.minFeeBroadcast, transactions.dummy3); + transactions.dummy4.data.fee = calculateFee(dynamicFeeConfig.minFeeBroadcast, transactions.dummy4); expect(dynamicFeeMatcher(transactions.dummy3).broadcast).toBeTrue(); expect(dynamicFeeMatcher(transactions.dummy4).broadcast).toBeTrue(); }); @@ -82,23 +55,24 @@ describe("dynamic fees", () => { it("should accept transactions with high enough fee to enter the pool", () => { expect(dynamicFeeMatcher(transactions.dummy1).enterPool).toBeTrue(); expect(dynamicFeeMatcher(transactions.dummy2).enterPool).toBeTrue(); + + transactions.dynamicFeeNormalDummy1.data.fee = + calculateFee(dynamicFeeConfig.minFeePool, transactions.dynamicFeeNormalDummy1) + 100; expect(dynamicFeeMatcher(transactions.dynamicFeeNormalDummy1).enterPool).toBeTrue(); // testing with transaction fee === min fee for transaction enter pool - transactions.dummy3.fee = calculateFee(dynFeeConfig.minFeePool, transactions.dummy3); - transactions.dummy4.fee = calculateFee(dynFeeConfig.minFeePool, transactions.dummy4); + transactions.dummy3.data.fee = calculateFee(dynamicFeeConfig.minFeePool, transactions.dummy3); + transactions.dummy4.data.fee = calculateFee(dynamicFeeConfig.minFeePool, transactions.dummy4); expect(dynamicFeeMatcher(transactions.dummy3).enterPool).toBeTrue(); expect(dynamicFeeMatcher(transactions.dummy4).enterPool).toBeTrue(); }); it("should not broadcast transactions with too low fee", () => { expect(dynamicFeeMatcher(transactions.dynamicFeeLowDummy2).broadcast).toBeFalse(); - expect(dynamicFeeMatcher(transactions.dynamicFeeZero).broadcast).toBeFalse(); }); it("should not allow transactions with too low fee to enter the pool", () => { expect(dynamicFeeMatcher(transactions.dynamicFeeLowDummy2).enterPool).toBeFalse(); - expect(dynamicFeeMatcher(transactions.dynamicFeeZero).enterPool).toBeFalse(); }); }); diff --git a/__tests__/unit/core-transaction-pool/guard.test.ts b/__tests__/unit/core-transaction-pool/guard.test.ts new file mode 100644 index 0000000000..5d90973957 --- /dev/null +++ b/__tests__/unit/core-transaction-pool/guard.test.ts @@ -0,0 +1,574 @@ +import "./mocks/core-container"; + +import { configManager, constants, slots } from "@arkecosystem/crypto"; +import "jest-extended"; +import { config as localConfig } from "../../../packages/core-transaction-pool/src/config"; +import { Connection } from "../../../packages/core-transaction-pool/src/connection"; +import { defaults } from "../../../packages/core-transaction-pool/src/defaults"; +import { TransactionGuard } from "../../../packages/core-transaction-pool/src/guard"; +import { MemPoolTransaction } from "../../../packages/core-transaction-pool/src/mem-pool-transaction"; +import { TransactionFactory } from "../../helpers/transaction-factory"; +import { delegates, wallets } from "../../utils/fixtures/unitnet"; +import { database } from "./mocks/database"; +import { state } from "./mocks/state"; + +let guard; +let transactionPool; + +beforeAll(async () => { + localConfig.init(defaults); + + transactionPool = new Connection(defaults); + await transactionPool.make(); +}); + +beforeEach(async () => { + transactionPool.flush(); + + guard = new TransactionGuard(transactionPool); +}); + +describe("Transaction Guard", () => { + describe("__cacheTransactions", () => { + it("should add transactions to cache", () => { + const transactions = TransactionFactory.transfer(wallets[11].address, 35) + .withNetwork("unitnet") + .withPassphrase(wallets[10].passphrase) + .create(3); + jest.spyOn(state, "cacheTransactions").mockReturnValueOnce({ added: transactions, notAdded: [] }); + + expect(guard.__cacheTransactions(transactions)).toEqual(transactions); + }); + + it("should not add a transaction already in cache and add it as an error", () => { + const transactions = TransactionFactory.transfer(wallets[12].address, 35) + .withNetwork("unitnet") + .withPassphrase(wallets[11].passphrase) + .create(3); + + jest.spyOn(state, "cacheTransactions") + .mockReturnValueOnce({ added: transactions, notAdded: [] }) + .mockReturnValueOnce({ added: [], notAdded: [transactions[0]] }); + + expect(guard.__cacheTransactions(transactions)).toEqual(transactions); + expect(guard.__cacheTransactions([transactions[0]])).toEqual([]); + expect(guard.errors).toEqual({ + [transactions[0].id]: [ + { + message: "Already in cache.", + type: "ERR_DUPLICATE", + }, + ], + }); + }); + }); + + describe("getBroadcastTransactions", () => { + it("should return broadcast transaction", async () => { + const transactions = TransactionFactory.transfer(wallets[11].address, 25) + .withNetwork("unitnet") + .withPassphrase(wallets[10].passphrase) + .build(3); + + jest.spyOn(state, "cacheTransactions").mockReturnValueOnce({ added: transactions, notAdded: [] }); + + for (const tx of transactions) { + guard.broadcast.set(tx.id, tx); + } + + expect(guard.getBroadcastTransactions()).toEqual(transactions); + }); + }); + + describe("__filterAndTransformTransactions", () => { + it("should reject duplicate transactions", () => { + const transactionExists = guard.pool.transactionExists; + guard.pool.transactionExists = jest.fn(() => true); + + const tx = { id: "1" }; + guard.__filterAndTransformTransactions([tx]); + + expect(guard.errors[tx.id]).toEqual([ + { + message: `Duplicate transaction ${tx.id}`, + type: "ERR_DUPLICATE", + }, + ]); + + guard.pool.transactionExists = transactionExists; + }); + + it("should reject blocked senders", () => { + const transactionExists = guard.pool.transactionExists; + guard.pool.transactionExists = jest.fn(() => false); + const isSenderBlocked = guard.pool.isSenderBlocked; + guard.pool.isSenderBlocked = jest.fn(() => true); + + const tx = { id: "1", senderPublicKey: "affe" }; + guard.__filterAndTransformTransactions([tx]); + + expect(guard.errors[tx.id]).toEqual([ + { + message: `Transaction ${tx.id} rejected. Sender ${tx.senderPublicKey} is blocked.`, + type: "ERR_SENDER_BLOCKED", + }, + ]); + + guard.pool.isSenderBlocked = isSenderBlocked; + guard.pool.transactionExists = transactionExists; + }); + + it("should reject transactions that are too large", () => { + const tx = TransactionFactory.transfer(wallets[12].address) + .withNetwork("unitnet") + .withPassphrase(wallets[11].passphrase) + .build(3)[0]; + + // @FIXME: Uhm excuse me, what the? + tx.data.signatures = [""]; + for (let i = 0; i < transactionPool.options.maxTransactionBytes; i++) { + // @ts-ignore + tx.data.signatures += "1"; + } + guard.__filterAndTransformTransactions([tx]); + + expect(guard.errors[tx.id]).toEqual([ + { + message: `Transaction ${tx.id} is larger than ${ + transactionPool.options.maxTransactionBytes + } bytes.`, + type: "ERR_TOO_LARGE", + }, + ]); + }); + + it("should reject transactions from the future", () => { + const now = 47157042; // seconds since genesis block + const transactionExists = guard.pool.transactionExists; + guard.pool.transactionExists = jest.fn(() => false); + const getTime = slots.getTime; + slots.getTime = jest.fn(() => now); + + const secondsInFuture = 3601; + const tx = { + id: "1", + senderPublicKey: "affe", + timestamp: slots.getTime() + secondsInFuture, + }; + guard.__filterAndTransformTransactions([tx]); + + expect(guard.errors[tx.id]).toEqual([ + { + message: `Transaction ${tx.id} is ${secondsInFuture} seconds in the future`, + type: "ERR_FROM_FUTURE", + }, + ]); + + slots.getTime = getTime; + guard.pool.transactionExists = transactionExists; + }); + + it("should accept transaction with correct network byte", () => { + const transactionExists = guard.pool.transactionExists; + guard.pool.transactionExists = jest.fn(() => false); + + const canApply = guard.pool.walletManager.canApply; + guard.pool.walletManager.canApply = jest.fn(() => true); + + const tx = { + id: "1", + network: 23, + type: constants.TransactionTypes.Transfer, + senderPublicKey: "023ee98f453661a1cb765fd60df95b4efb1e110660ffb88ae31c2368a70f1f7359", + recipientId: "DEJHR83JFmGpXYkJiaqn7wPGztwjheLAmY", + }; + guard.__filterAndTransformTransactions([tx]); + + expect(guard.errors[tx.id]).not.toEqual([ + { + message: `Transaction network '${tx.network}' does not match '${configManager.get("pubKeyHash")}'`, + type: "ERR_WRONG_NETWORK", + }, + ]); + + guard.pool.transactionExists = transactionExists; + guard.pool.walletManager.canApply = canApply; + }); + + it("should accept transaction with missing network byte", () => { + const transactionExists = guard.pool.transactionExists; + guard.pool.transactionExists = jest.fn(() => false); + + const canApply = guard.pool.walletManager.canApply; + guard.pool.walletManager.canApply = jest.fn(() => true); + + const tx = { + id: "1", + type: constants.TransactionTypes.Transfer, + senderPublicKey: "023ee98f453661a1cb765fd60df95b4efb1e110660ffb88ae31c2368a70f1f7359", + recipientId: "DEJHR83JFmGpXYkJiaqn7wPGztwjheLAmY", + }; + guard.__filterAndTransformTransactions([tx]); + + expect(guard.errors[tx.id].type).not.toEqual("ERR_WRONG_NETWORK"); + + guard.pool.transactionExists = transactionExists; + guard.pool.walletManager.canApply = canApply; + }); + + it("should not accept transaction with wrong network byte", () => { + const transactionExists = guard.pool.transactionExists; + guard.pool.transactionExists = jest.fn(() => false); + + const canApply = guard.pool.walletManager.canApply; + guard.pool.walletManager.canApply = jest.fn(() => true); + + const tx = { + id: "1", + network: 2, + senderPublicKey: "023ee98f453661a1cb765fd60df95b4efb1e110660ffb88ae31c2368a70f1f7359", + }; + guard.__filterAndTransformTransactions([tx]); + + expect(guard.errors[tx.id]).toEqual([ + { + message: `Transaction network '${tx.network}' does not match '${configManager.get("pubKeyHash")}'`, + type: "ERR_WRONG_NETWORK", + }, + ]); + + guard.pool.transactionExists = transactionExists; + guard.pool.walletManager.canApply = canApply; + }); + + it("should not accept transaction if pool hasExceededMaxTransactions and add it to excess", () => { + const transactions = TransactionFactory.transfer(wallets[11].address, 35) + .withNetwork("unitnet") + .withPassphrase(wallets[10].passphrase) + .create(3); + + jest.spyOn(guard.pool, "hasExceededMaxTransactions").mockImplementationOnce(tx => true); + + guard.__filterAndTransformTransactions(transactions); + + expect(guard.excess).toEqual([transactions[0].id]); + expect(guard.accept).toEqual(new Map()); + expect(guard.broadcast).toEqual(new Map()); + }); + + it("should push a ERR_UNKNOWN error if something threw in validated transaction block", () => { + const transactions = TransactionFactory.transfer(wallets[11].address, 35) + .withNetwork("unitnet") + .withPassphrase(wallets[10].passphrase) + .build(3); + + // use guard.accept.set() call to introduce a throw + jest.spyOn(guard.pool.walletManager, "canApply").mockImplementationOnce(() => { + throw new Error("hey"); + }); + + guard.__filterAndTransformTransactions(transactions.map(tx => tx.data)); + + expect(guard.accept).toEqual(new Map()); + expect(guard.broadcast).toEqual(new Map()); + expect(guard.errors[transactions[0].id]).toEqual([ + { + message: `hey`, + type: "ERR_UNKNOWN", + }, + ]); + }); + }); + + describe("__validateTransaction", () => { + it("should not validate when recipient is not on the same network", async () => { + const transactions = TransactionFactory.transfer("DEJHR83JFmGpXYkJiaqn7wPGztwjheLAmY", 35) + .withNetwork("unitnet") + .withPassphrase(wallets[10].passphrase) + .create(3); + + expect(guard.__validateTransaction(transactions[0])).toBeFalse(); + expect(guard.errors).toEqual({ + [transactions[0].id]: [ + { + type: "ERR_INVALID_RECIPIENT", + message: `Recipient ${ + transactions[0].recipientId + } is not on the same network: ${configManager.get("pubKeyHash")}`, + }, + ], + }); + }); + + it("should not validate a delegate registration if an existing registration for the same username from a different wallet exists in the pool", async () => { + const delegateRegistrations = [ + TransactionFactory.delegateRegistration("test_delegate") + .withNetwork("unitnet") + .withPassphrase(wallets[16].passphrase) + .build()[0], + TransactionFactory.delegateRegistration("test_delegate") + .withNetwork("unitnet") + .withPassphrase(wallets[17].passphrase) + .build()[0], + ]; + const memPoolTx = new MemPoolTransaction(delegateRegistrations[0]); + jest.spyOn(guard.pool, "getTransactionsByType").mockReturnValueOnce(new Set([memPoolTx])); + + expect(guard.__validateTransaction(delegateRegistrations[1].data)).toBeFalse(); + expect(guard.errors[delegateRegistrations[1].id]).toEqual([ + { + type: "ERR_PENDING", + message: `Delegate registration for "${ + delegateRegistrations[1].data.asset.delegate.username + }" already in the pool`, + }, + ]); + }); + + it("should not validate a transaction if a second signature registration for the same wallet exists in the pool", async () => { + const secondSignatureTransaction = TransactionFactory.secondSignature() + .withNetwork("unitnet") + .withPassphrase(wallets[16].passphrase) + .build()[0]; + + const transferTransaction = TransactionFactory.transfer("AFzQCx5YpGg5vKMBg4xbuYbqkhvMkKfKe5") + .withNetwork("unitnet") + .withPassphrase(wallets[16].passphrase) + .withSecondPassphrase(wallets[17].passphrase) + .build()[0]; + + const memPoolTx = new MemPoolTransaction(secondSignatureTransaction); + jest.spyOn(guard.pool, "senderHasTransactionsOfType").mockReturnValueOnce(true); + + expect(guard.__validateTransaction(transferTransaction.data)).toBeFalse(); + expect(guard.errors[transferTransaction.id]).toEqual([ + { + type: "ERR_PENDING", + message: `Cannot accept transaction from sender ${ + transferTransaction.data.senderPublicKey + } while its second signature registration is in the pool`, + }, + ]); + }); + + it("should not validate when sender has same type transactions in the pool (only for 2nd sig, delegate registration, vote)", async () => { + jest.spyOn(guard.pool.walletManager, "canApply").mockImplementation(() => true); + jest.spyOn(guard.pool, "senderHasTransactionsOfType").mockReturnValue(true); + const vote = TransactionFactory.vote(delegates[0].publicKey) + .withNetwork("unitnet") + .withPassphrase(wallets[10].passphrase) + .build()[0]; + + const delegateReg = TransactionFactory.delegateRegistration() + .withNetwork("unitnet") + .withPassphrase(wallets[11].passphrase) + .build()[0]; + + const signature = TransactionFactory.secondSignature(wallets[12].passphrase) + .withNetwork("unitnet") + .withPassphrase(wallets[12].passphrase) + .build()[0]; + + for (const tx of [vote, delegateReg, signature]) { + expect(guard.__validateTransaction(tx.data)).toBeFalse(); + expect(guard.errors[tx.id]).toEqual([ + { + type: "ERR_PENDING", + message: + `Sender ${tx.data.senderPublicKey} already has a transaction of type ` + + `'${constants.TransactionTypes[tx.type]}' in the pool`, + }, + ]); + } + + jest.restoreAllMocks(); + }); + + it("should not validate unsupported transaction types", async () => { + jest.spyOn(guard.pool.walletManager, "canApply").mockImplementation(() => true); + + // use a random transaction as a base - then play with type + const baseTransaction = TransactionFactory.delegateRegistration() + .withNetwork("unitnet") + .withPassphrase(wallets[11].passphrase) + .build()[0]; + + for (const transactionType of [ + constants.TransactionTypes.MultiSignature, + constants.TransactionTypes.Ipfs, + constants.TransactionTypes.TimelockTransfer, + constants.TransactionTypes.MultiPayment, + constants.TransactionTypes.DelegateResignation, + 99, + ]) { + baseTransaction.data.type = transactionType; + // @FIXME: Uhm excuse me, what the? + // @ts-ignore + baseTransaction.data.id = transactionType; + + expect(guard.__validateTransaction(baseTransaction)).toBeFalse(); + expect(guard.errors[baseTransaction.id]).toEqual([ + { + type: "ERR_UNSUPPORTED", + message: `Invalidating transaction of unsupported type '${ + constants.TransactionTypes[transactionType] + }'`, + }, + ]); + } + + jest.restoreAllMocks(); + }); + }); + + describe("__removeForgedTransactions", () => { + it("should remove forged transactions", async () => { + const transfers = TransactionFactory.transfer(delegates[0].senderPublicKey) + .withNetwork("unitnet") + .withPassphrase(delegates[0].secret) + .build(4); + + transfers.forEach(tx => { + guard.accept.set(tx.id, tx); + guard.broadcast.set(tx.id, tx); + }); + + const forgedTx = transfers[2]; + jest.spyOn(database, "getForgedTransactionsIds").mockReturnValueOnce([forgedTx.id]); + + await guard.__removeForgedTransactions(); + + expect(guard.accept.size).toBe(3); + expect(guard.broadcast.size).toBe(3); + + expect(guard.errors[forgedTx.id]).toHaveLength(1); + expect(guard.errors[forgedTx.id][0].type).toEqual("ERR_FORGED"); + }); + }); + + describe("__addTransactionsToPool", () => { + it("should add transactions to the pool", () => { + const transfers = TransactionFactory.transfer(delegates[0].senderPublicKey) + .withNetwork("unitnet") + .withPassphrase(delegates[0].secret) + .create(4); + + transfers.forEach(tx => { + guard.accept.set(tx.id, tx); + guard.broadcast.set(tx.id, tx); + }); + + expect(guard.errors).toEqual({}); + jest.spyOn(guard.pool, "addTransactions").mockReturnValueOnce({ added: transfers, notAdded: [] }); + + guard.__addTransactionsToPool(); + + expect(guard.errors).toEqual({}); + expect(guard.accept.size).toBe(4); + expect(guard.broadcast.size).toBe(4); + }); + + it("should delete from accept and broadcast transactions that were not added to the pool", () => { + const added = TransactionFactory.transfer(delegates[0].address) + .withNetwork("unitnet") + .withPassphrase(delegates[0].secret) + .build(2); + const notAddedError = { type: "ERR_TEST", message: "" }; + const notAdded = TransactionFactory.transfer(delegates[1].address) + .withNetwork("unitnet") + .withPassphrase(delegates[0].secret) + .build(2) + .map(tx => ({ + transaction: tx, + ...notAddedError, + })); + + added.forEach(tx => { + guard.accept.set(tx.id, tx); + guard.broadcast.set(tx.id, tx); + }); + notAdded.forEach(tx => { + guard.accept.set(tx.transaction.id, tx); + guard.broadcast.set(tx.transaction.id, tx); + }); + + jest.spyOn(guard.pool, "addTransactions").mockReturnValueOnce({ added, notAdded }); + guard.__addTransactionsToPool(); + + expect(guard.accept.size).toBe(2); + expect(guard.broadcast.size).toBe(2); + + expect(guard.errors[notAdded[0].transaction.id]).toEqual([notAddedError]); + expect(guard.errors[notAdded[1].transaction.id]).toEqual([notAddedError]); + }); + + it("should delete from accept but keep in broadcast transactions that were not added to the pool because of ERR_POOL_FULL", () => { + const added = TransactionFactory.transfer(delegates[0].address) + .withNetwork("unitnet") + .withPassphrase(delegates[0].secret) + .build(2); + + const notAddedError = { type: "ERR_POOL_FULL", message: "" }; + const notAdded = TransactionFactory.transfer(delegates[1].address) + .withNetwork("unitnet") + .withPassphrase(delegates[0].secret) + .build(2) + .map(tx => ({ + transaction: tx, + ...notAddedError, + })); + + added.forEach(tx => { + guard.accept.set(tx.id, tx); + guard.broadcast.set(tx.id, tx); + }); + notAdded.forEach(tx => { + guard.accept.set(tx.transaction.id, tx); + guard.broadcast.set(tx.transaction.id, tx); + }); + + jest.spyOn(guard.pool, "addTransactions").mockReturnValueOnce({ added, notAdded }); + guard.__addTransactionsToPool(); + + expect(guard.accept.size).toBe(2); + expect(guard.broadcast.size).toBe(4); + + expect(guard.errors[notAdded[0].transaction.id]).toEqual([notAddedError]); + expect(guard.errors[notAdded[1].transaction.id]).toEqual([notAddedError]); + }); + }); + + describe("pushError", () => { + it("should have error for transaction", () => { + expect(guard.errors).toBeEmpty(); + + guard.pushError({ id: 1 }, "ERR_INVALID", "Invalid."); + + expect(guard.errors).toBeObject(); + expect(guard.errors["1"]).toBeArray(); + expect(guard.errors["1"]).toHaveLength(1); + expect(guard.errors["1"]).toEqual([{ message: "Invalid.", type: "ERR_INVALID" }]); + + expect(guard.invalid.size).toEqual(1); + expect(guard.invalid.entries().next().value[1]).toEqual({ id: 1 }); + }); + + it("should have multiple errors for transaction", () => { + expect(guard.errors).toBeEmpty(); + + guard.pushError({ id: 1 }, "ERR_INVALID", "Invalid 1."); + guard.pushError({ id: 1 }, "ERR_INVALID", "Invalid 2."); + + expect(guard.errors).toBeObject(); + expect(guard.errors["1"]).toBeArray(); + expect(guard.errors["1"]).toHaveLength(2); + expect(guard.errors["1"]).toEqual([ + { message: "Invalid 1.", type: "ERR_INVALID" }, + { message: "Invalid 2.", type: "ERR_INVALID" }, + ]); + + expect(guard.invalid.size).toEqual(1); + expect(guard.invalid.entries().next().value[1]).toEqual({ id: 1 }); + }); + }); +}); diff --git a/__tests__/unit/core-transaction-pool/manager.test.ts b/__tests__/unit/core-transaction-pool/manager.test.ts new file mode 100644 index 0000000000..5f9a8ed92b --- /dev/null +++ b/__tests__/unit/core-transaction-pool/manager.test.ts @@ -0,0 +1,20 @@ +import { ConnectionManager } from "../../../packages/core-transaction-pool/src/manager"; +import { Connection } from "./__stubs__/connection"; + +const manager: ConnectionManager = new ConnectionManager(); + +describe("Transaction Pool Manager", () => { + describe("connection", () => { + it("should return the drive-connection", async () => { + await manager.createConnection(new Connection()); + + expect(manager.connection()).toBeInstanceOf(Connection); + }); + + it("should return the drive-connection for a different name", async () => { + await manager.createConnection(new Connection(), "testing"); + + expect(manager.connection("testing")).toBeInstanceOf(Connection); + }); + }); +}); diff --git a/packages/core-transaction-pool/__tests__/mem-pool-transaction.test.ts b/__tests__/unit/core-transaction-pool/mem-pool-transaction.test.ts similarity index 60% rename from packages/core-transaction-pool/__tests__/mem-pool-transaction.test.ts rename to __tests__/unit/core-transaction-pool/mem-pool-transaction.test.ts index a80d129c8d..788fb2f926 100644 --- a/packages/core-transaction-pool/__tests__/mem-pool-transaction.test.ts +++ b/__tests__/unit/core-transaction-pool/mem-pool-transaction.test.ts @@ -1,26 +1,26 @@ -import { constants } from "@arkecosystem/crypto"; -import { MemPoolTransaction } from "../src/mem-pool-transaction"; +import { constants, Transaction } from "@arkecosystem/crypto"; +import { MemPoolTransaction } from "../../../packages/core-transaction-pool/src/mem-pool-transaction"; import { transactions } from "./__fixtures__/transactions"; describe("MemPoolTransaction", () => { describe("expireAt", () => { it("should return transaction expiration when it is set", () => { - const transaction = transactions.dummy1; - transaction.expiration = 1123; + const transaction: Transaction = transactions.dummy1; + transaction.data.expiration = 1123; const memPoolTransaction = new MemPoolTransaction(transaction); expect(memPoolTransaction.expireAt(1)).toBe(1123); }); it("should return timestamp + maxTransactionAge when expiration is not set", () => { - const transaction = transactions.dummy2; - transaction.timestamp = 344; + const transaction: Transaction = transactions.dummy2; + transaction.data.timestamp = 344; const memPoolTransaction = new MemPoolTransaction(transaction); - expect(memPoolTransaction.expireAt(131)).toBe(transaction.timestamp + 131); + expect(memPoolTransaction.expireAt(131)).toBe(transaction.data.timestamp + 131); }); it("should return null for timelock transfer with no expiration set", () => { - const transaction = transactions.dummy3; - transaction.type = constants.TransactionTypes.TimelockTransfer; + const transaction: Transaction = transactions.dummy3; + transaction.data.type = constants.TransactionTypes.TimelockTransfer; const memPoolTransaction = new MemPoolTransaction(transaction); expect(memPoolTransaction.expireAt(1)).toBe(null); }); diff --git a/__tests__/unit/core-transaction-pool/mocks/core-container.ts b/__tests__/unit/core-transaction-pool/mocks/core-container.ts new file mode 100644 index 0000000000..6b2e897301 --- /dev/null +++ b/__tests__/unit/core-transaction-pool/mocks/core-container.ts @@ -0,0 +1,53 @@ +import { database } from "./database"; +import { state } from "./state"; + +jest.mock("@arkecosystem/core-container", () => { + return { + app: { + getConfig: () => { + return { + get: () => ({}), + }; + }, + resolve: name => { + if (name === "state") { + return state; + } + + return {}; + }, + resolvePlugin: name => { + if (name === "logger") { + return { + info: console.log, + warn: console.log, + error: console.error, + debug: console.log, + }; + } + + if (name === "blockchain") { + return { + getLastBlock: () => ({ + data: { + height: 20, + }, + }), + }; + } + + if (name === "event-emitter") { + return { + emit: () => ({}), + }; + } + + if (name === "database") { + return database; + } + + return {}; + }, + }, + }; +}); diff --git a/__tests__/unit/core-transaction-pool/mocks/database.ts b/__tests__/unit/core-transaction-pool/mocks/database.ts new file mode 100644 index 0000000000..d3667777b7 --- /dev/null +++ b/__tests__/unit/core-transaction-pool/mocks/database.ts @@ -0,0 +1,3 @@ +export const database = { + getForgedTransactionsIds: () => [], +}; diff --git a/__tests__/unit/core-transaction-pool/mocks/state.ts b/__tests__/unit/core-transaction-pool/mocks/state.ts new file mode 100644 index 0000000000..decd939eb2 --- /dev/null +++ b/__tests__/unit/core-transaction-pool/mocks/state.ts @@ -0,0 +1,4 @@ +export const state = { + cacheTransactions: () => null, + removeCachedTransactionIds: () => null, +}; diff --git a/packages/core-transaction-pool/__tests__/storage.test.ts b/__tests__/unit/core-transaction-pool/storage.test.ts similarity index 86% rename from packages/core-transaction-pool/__tests__/storage.test.ts rename to __tests__/unit/core-transaction-pool/storage.test.ts index 7a81a7e955..a1dc05db86 100644 --- a/packages/core-transaction-pool/__tests__/storage.test.ts +++ b/__tests__/unit/core-transaction-pool/storage.test.ts @@ -1,5 +1,5 @@ -import { MemPoolTransaction } from "../src/mem-pool-transaction"; -import { Storage } from "../src/storage"; +import { MemPoolTransaction } from "../../../packages/core-transaction-pool/src/mem-pool-transaction"; +import { Storage } from "../../../packages/core-transaction-pool/src/storage"; import { transactions } from "./__fixtures__/transactions"; describe("Storage", () => { @@ -20,7 +20,7 @@ describe("Storage", () => { storage.bulkAdd(memPoolTransactions); const allMemPoolTransactions = storage.loadAll(); - expect(allMemPoolTransactions.map(pooltx => pooltx.transaction)).toEqual( + expect(allMemPoolTransactions.map(pooltx => pooltx.transaction)).toMatchObject( memPoolTransactions.map(pooltx => pooltx.transaction), ); }); @@ -37,7 +37,7 @@ describe("Storage", () => { storage.bulkAdd([...memPoolTransactions, anotherMemPoolTransaction]); storage.bulkRemoveById([transactions.dummy3.id]); const allMemPoolTransactions = storage.loadAll(); - expect(allMemPoolTransactions.map(pooltx => pooltx.transaction)).toEqual( + expect(allMemPoolTransactions.map(pooltx => pooltx.transaction)).toMatchObject( memPoolTransactions.map(pooltx => pooltx.transaction), ); }); diff --git a/packages/core-transaction-pool/__tests__/storage.with-mock.test.ts b/__tests__/unit/core-transaction-pool/storage.with-mock.test.ts similarity index 84% rename from packages/core-transaction-pool/__tests__/storage.with-mock.test.ts rename to __tests__/unit/core-transaction-pool/storage.with-mock.test.ts index c0c2849e6b..da8b0145ab 100644 --- a/packages/core-transaction-pool/__tests__/storage.with-mock.test.ts +++ b/__tests__/unit/core-transaction-pool/storage.with-mock.test.ts @@ -1,5 +1,5 @@ -import { MemPoolTransaction } from "../src/mem-pool-transaction"; -import { Storage } from "../src/storage"; +import { MemPoolTransaction } from "../../../packages/core-transaction-pool/src/mem-pool-transaction"; +import { Storage } from "../../../packages/core-transaction-pool/src/storage"; import { transactions } from "./__fixtures__/transactions"; // first mock the whole better-sqlite3 module to be able to control 'inTransaction' prop diff --git a/__tests__/unit/core-transactions/handler-registry.test.ts b/__tests__/unit/core-transactions/handler-registry.test.ts new file mode 100644 index 0000000000..ace3b0231f --- /dev/null +++ b/__tests__/unit/core-transactions/handler-registry.test.ts @@ -0,0 +1,159 @@ +import "jest-extended"; + +import { Database, TransactionPool } from "@arkecosystem/core-interfaces"; +import { + Bignum, + configManager, + constants, + crypto, + ITransactionData, + schemas, + slots, + Transaction, + TransactionConstructor, + TransactionRegistry, +} from "@arkecosystem/crypto"; +import bs58check from "bs58check"; +import ByteBuffer from "bytebuffer"; +import { errors, TransactionHandler, TransactionHandlerRegistry } from "../../../packages/core-transactions/src"; + +const { transactionBaseSchema, extend } = schemas; +const { TransactionTypes } = constants; + +const TEST_TRANSACTION_TYPE = 100; + +class TestTransaction extends Transaction { + public static type = TEST_TRANSACTION_TYPE; + + public static getSchema(): schemas.TransactionSchema { + return extend(transactionBaseSchema, { + $id: "test", + required: ["recipientId", "amount", "asset"], + properties: { + type: { transactionType: TEST_TRANSACTION_TYPE }, + recipientId: { $ref: "address" }, + asset: { + type: "object", + required: ["test"], + properties: { + test: { + type: "number", + }, + }, + }, + }, + }); + } + + public serialize(): ByteBuffer { + const { data } = this; + const buffer = new ByteBuffer(24, true); + buffer.writeUint64(+data.amount); + buffer.writeUint32(data.expiration || 0); + buffer.append(bs58check.decode(data.recipientId)); + buffer.writeInt32(data.asset.test); + + return buffer; + } + + public deserialize(buf: ByteBuffer): void { + const { data } = this; + data.amount = new Bignum(buf.readUint64().toString()); + data.expiration = buf.readUint32(); + data.recipientId = bs58check.encode(buf.readBytes(21).toBuffer()); + data.asset = { + test: buf.readInt32(), + }; + } +} + +// tslint:disable-next-line:max-classes-per-file +class TestTransactionHandler extends TransactionHandler { + public getConstructor(): TransactionConstructor { + return TestTransaction; + } + + public apply(transaction: Transaction, wallet: Database.IWallet): void { + return; + } + public revert(transaction: Transaction, wallet: Database.IWallet): void { + return; + } + + public canEnterTransactionPool(data: ITransactionData, guard: TransactionPool.IGuard): boolean { + return true; + } +} + +beforeAll(() => { + configManager.setFromPreset("testnet"); + configManager.milestone.data.fees.staticFees.test = 1234; +}); + +afterAll(() => { + delete configManager.milestone.data.fees.staticFees.test; +}); + +afterEach(() => { + TransactionHandlerRegistry.deregisterCustomTransactionHandler(TestTransactionHandler); +}); + +describe("TransactionHandlerRegistry", () => { + it("should register core transaction types", () => { + expect(() => { + TransactionHandlerRegistry.get(TransactionTypes.Transfer); + TransactionHandlerRegistry.get(TransactionTypes.SecondSignature); + TransactionHandlerRegistry.get(TransactionTypes.DelegateRegistration); + TransactionHandlerRegistry.get(TransactionTypes.Vote); + TransactionHandlerRegistry.get(TransactionTypes.MultiSignature); + }).not.toThrow(errors.InvalidTransactionTypeError); + }); + + it("should register a custom type", () => { + expect(() => + TransactionHandlerRegistry.registerCustomTransactionHandler(TestTransactionHandler), + ).not.toThrowError(); + + expect(TransactionHandlerRegistry.get(TEST_TRANSACTION_TYPE)).toBeInstanceOf(TestTransactionHandler); + expect(TransactionRegistry.get(TEST_TRANSACTION_TYPE)).toBe(TestTransaction); + }); + + it("should be able to instantiate a custom transaction", () => { + TransactionHandlerRegistry.registerCustomTransactionHandler(TestTransactionHandler); + + const keys = crypto.getKeys("secret"); + const data: ITransactionData = { + type: TEST_TRANSACTION_TYPE, + timestamp: slots.getTime(), + senderPublicKey: keys.publicKey, + fee: "10000000", + amount: "200000000", + recipientId: "APyFYXxXtUrvZFnEuwLopfst94GMY5Zkeq", + asset: { + test: 256, + }, + }; + + data.signature = crypto.sign(data, keys); + data.id = crypto.getId(data); + + const transaction = Transaction.fromData(data); + expect(transaction).toBeInstanceOf(TestTransaction); + expect(transaction.verified).toBeTrue(); + + const bytes = Transaction.toBytes(transaction.data); + const deserialized = Transaction.fromBytes(bytes); + expect(deserialized.verified); + expect(deserialized.data.asset.test).toBe(256); + }); + + it("should not be ok when registering the same type again", () => { + expect(() => + TransactionHandlerRegistry.registerCustomTransactionHandler(TestTransactionHandler), + ).not.toThrowError(errors.TransactionHandlerAlreadyRegisteredError); + + expect(() => TransactionHandlerRegistry.registerCustomTransactionHandler(TestTransactionHandler)).toThrowError( + errors.TransactionHandlerAlreadyRegisteredError, + ); + }); +}); diff --git a/__tests__/unit/core-transactions/handler.test.ts b/__tests__/unit/core-transactions/handler.test.ts new file mode 100644 index 0000000000..79e72d33ad --- /dev/null +++ b/__tests__/unit/core-transactions/handler.test.ts @@ -0,0 +1,812 @@ +import "jest-extended"; + +import { Wallet } from "@arkecosystem/core-database"; +import { Bignum, configManager, constants, crypto, ITransactionData, Transaction } from "@arkecosystem/crypto"; +import { + AlreadyVotedError, + InsufficientBalanceError, + InvalidMultiSignatureError, + InvalidSecondSignatureError, + MultiSignatureAlreadyRegisteredError, + MultiSignatureKeyCountMismatchError, + MultiSignatureMinimumKeysError, + NoVoteError, + SecondSignatureAlreadyRegisteredError, + SenderWalletMismatchError, + UnexpectedSecondSignatureError, + UnvoteMismatchError, + WalletNoUsernameError, + WalletUsernameNotEmptyError, +} from "../../../packages/core-transactions/src/errors"; +import { TransactionHandler } from "../../../packages/core-transactions/src/handlers/transaction"; +import { TransactionHandlerRegistry } from "../../../packages/core-transactions/src/index"; +import { transaction as transactionFixture } from "../crypto/transactions/__fixtures__/transaction"; +import { wallet as walletFixture } from "../crypto/transactions/__fixtures__/wallet"; + +const { ARKTOSHI } = constants; + +let wallet: Wallet; +let transaction: ITransactionData; +let transactionWithSecondSignature: ITransactionData; +let handler: TransactionHandler; +let instance: any; + +beforeEach(() => { + wallet = { + address: "D5q7YfEFDky1JJVQQEy4MGyiUhr5cGg47F", + balance: new Bignum(4527654310), + publicKey: "02a47a2f594635737d2ce9898680812ff7fa6aaa64ddea1360474c110e9985a087", + } as Wallet; + + transaction = { + id: "65a4f09a3a19d212a65d27de05d1ae7e0c461e088a35499996667f98d2a3897c", + signature: + "304402206974568da7c363155decbc20ddc17746a2e7e663901c426f5a41411374cc6d18022052f4353ec93227713f9907f2bb2549e6bc42584b736aa5f9ff36e2c239154648", + timestamp: 54836734, + type: 0, + fee: new Bignum(10000000), + senderPublicKey: "02a47a2f594635737d2ce9898680812ff7fa6aaa64ddea1360474c110e9985a087", + amount: new Bignum(10000000), + recipientId: "D5q7YfEFDky1JJVQQEy4MGyiUhr5cGg47F", + }; + + transactionWithSecondSignature = { + id: "e3b29bba60d5f1f2aad2087dea44644f166b00ae3db1a16a99b622dc4f3900f8", + signature: + "304402206974568da7c363155decbc20ddc17746a2e7e663901c426f5a41411374cc6d18022052f4353ec93227713f9907f2bb2549e6bc42584b736aa5f9ff36e2c239154648", + secondSignature: + "304402202d0ae57c6a0afb225443b56c6e049cb08df48b5813362f7e11574b96f225738f0220055b5a941cc70100404a7788c57b37e2e806acf58c4284c567dc53477f546540", + timestamp: 54836734, + type: 0, + fee: new Bignum(10000000), + senderPublicKey: "02a47a2f594635737d2ce9898680812ff7fa6aaa64ddea1360474c110e9985a087", + amount: new Bignum(10000000), + recipientId: "D5q7YfEFDky1JJVQQEy4MGyiUhr5cGg47F", + }; +}); + +describe("General Tests", () => { + beforeEach(() => { + handler = TransactionHandlerRegistry.get(transaction.type); + instance = Transaction.fromData(transaction); + }); + + describe("canBeApplied", () => { + it("should be true", () => { + expect(handler.canBeApplied(instance, wallet)).toBeTrue(); + }); + + it("should be true if the transaction has a second signature but wallet does not, when ignoreInvalidSecondSignatureField=true", () => { + configManager.getMilestone().ignoreInvalidSecondSignatureField = true; + instance = Transaction.fromData(transactionWithSecondSignature); + expect(handler.canBeApplied(instance, wallet)).toBeTrue(); + }); + + it("should be false if wallet publicKey does not match tx senderPublicKey", () => { + instance.data.senderPublicKey = "a".repeat(66); + expect(() => handler.canBeApplied(instance, wallet)).toThrow(SenderWalletMismatchError); + }); + + it("should be false if the transaction has a second signature but wallet does not", () => { + delete configManager.getMilestone().ignoreInvalidSecondSignatureField; + instance = Transaction.fromData(transactionWithSecondSignature); + expect(() => handler.canBeApplied(instance, wallet)).toThrow(UnexpectedSecondSignatureError); + }); + + it("should be false if the wallet has a second public key but the transaction second signature does not match", () => { + wallet.secondPublicKey = "invalid-public-key"; + expect(() => handler.canBeApplied(instance, wallet)).toThrow(InvalidSecondSignatureError); + }); + + it("should be false if wallet has not enough balance", () => { + // 1 arktoshi short + wallet.balance = new Bignum(transaction.amount).plus(transaction.fee).minus(1); + expect(() => handler.canBeApplied(instance, wallet)).toThrow(InsufficientBalanceError); + }); + + it("should be true even with publicKey case mismatch", () => { + transaction.senderPublicKey = transaction.senderPublicKey.toUpperCase(); + wallet.publicKey = wallet.publicKey.toLowerCase(); + instance = Transaction.fromData(transaction); + expect(handler.canBeApplied(instance, wallet)).toBeTrue(); + }); + }); + + describe("applyTransactionToSender", () => { + it("should be ok", () => { + const initialBalance = 1000 * ARKTOSHI; + wallet.balance = new Bignum(initialBalance); + handler.applyToSender(instance, wallet); + expect(wallet.balance).toEqual(new Bignum(initialBalance).minus(transaction.amount).minus(transaction.fee)); + }); + + it("should not be ok", () => { + const initialBalance = 1000 * ARKTOSHI; + wallet.balance = new Bignum(initialBalance); + instance.data.senderPublicKey = "a".repeat(66); + + handler.applyToSender(instance, wallet); + expect(wallet.balance).toEqual(new Bignum(initialBalance)); + }); + + it("should not fail due to case mismatch", () => { + const initialBalance = 1000 * ARKTOSHI; + wallet.balance = new Bignum(initialBalance); + + transaction.senderPublicKey = transaction.senderPublicKey.toUpperCase(); + const instance = Transaction.fromData(transaction); + wallet.publicKey = wallet.publicKey.toLowerCase(); + + handler.applyToSender(instance, wallet); + expect(wallet.balance).toEqual(new Bignum(initialBalance).minus(transaction.amount).minus(transaction.fee)); + }); + }); + + describe("revertTransactionForSender", () => { + it("should be ok", () => { + const initialBalance = 1000 * ARKTOSHI; + wallet.balance = new Bignum(initialBalance); + + handler.revertForSender(instance, wallet); + expect(wallet.balance).toEqual(new Bignum(initialBalance).plus(transaction.amount).plus(transaction.fee)); + }); + + it("should not be ok", () => { + const initialBalance = 1000 * ARKTOSHI; + wallet.balance = new Bignum(initialBalance); + transaction.senderPublicKey = "a".repeat(66); + + handler.revertForSender(instance, wallet); + expect(wallet.balance).toEqual(new Bignum(initialBalance)); + }); + }); + + describe("applyTransactionToRecipient", () => { + it("should be ok", () => { + const initialBalance = 1000 * ARKTOSHI; + wallet.balance = new Bignum(initialBalance); + + handler.applyToRecipient(instance, wallet); + expect(wallet.balance).toEqual(new Bignum(initialBalance).plus(transaction.amount)); + }); + + it("should not be ok", () => { + const initialBalance = 1000 * ARKTOSHI; + wallet.balance = new Bignum(initialBalance); + transaction.recipientId = "invalid-recipientId"; + + handler.applyToRecipient(instance, wallet); + expect(wallet.balance).toEqual(new Bignum(initialBalance)); + }); + }); + + describe("revertTransactionForRecipient", () => { + it("should be ok", () => { + const initialBalance = 1000 * ARKTOSHI; + wallet.balance = new Bignum(initialBalance); + + handler.revertForRecipient(instance, wallet); + expect(wallet.balance).toEqual(new Bignum(initialBalance).minus(transaction.amount)); + }); + + it("should not be ok", () => { + const initialBalance = 1000 * ARKTOSHI; + wallet.balance = new Bignum(initialBalance); + + transaction.recipientId = "invalid-recipientId"; + + handler.revertForRecipient(instance, wallet); + expect(wallet.balance).toEqual(new Bignum(initialBalance)); + }); + }); +}); + +describe("TransferTransaction", () => { + beforeEach(() => { + wallet = walletFixture; + transaction = transactionFixture; + handler = TransactionHandlerRegistry.get(transaction.type); + instance = Transaction.fromData(transaction); + }); + + describe("canApply", () => { + it("should be true", () => { + expect(handler.canBeApplied(instance, wallet)).toBeTrue(); + }); + + it("should be false", () => { + instance.data.senderPublicKey = "a".repeat(66); + expect(() => handler.canBeApplied(instance, wallet)).toThrow(SenderWalletMismatchError); + }); + }); +}); + +describe("SecondSignatureRegistrationTransaction", () => { + beforeEach(() => { + wallet = { + address: "DSD9Wi2rfqzDb3REUB5MELQGrsUAjY67gj", + balance: new Bignum("6453530000000"), + publicKey: "03cba4fd60f856ad034ee0a9146432757ae35956b640c26fb6674061924b05a5c9", + secondPublicKey: null, + } as Wallet; + + transaction = { + version: 1, + network: 30, + type: 1, + timestamp: 53995738, + senderPublicKey: "03cba4fd60f856ad034ee0a9146432757ae35956b640c26fb6674061924b05a5c9", + fee: new Bignum(500000000), + asset: { + signature: { + publicKey: "02d5cfcbc4920d041d2a54b29e1f69173536796fd50f62af0f88ad6adc6df07cb8", + }, + }, + signature: + "3044022064e7abe87c186b201eaeeb9587097432816c94b52b85520a70da1d78b93456aa0220205e263a278c64771d46038f116c37dc16c86e73664e7e829951d7c5544c6d3e", + amount: Bignum.ZERO, + recipientId: "DSD9Wi2rfqzDb3REUB5MELQGrsUAjY67gj", + id: "e5a4cf622a24d459987f093e14a14c6b0492834358f86099afe1a2d14457cf31", + }; + + handler = TransactionHandlerRegistry.get(transaction.type); + instance = Transaction.fromData(transaction); + }); + + describe("canApply", () => { + it("should be true", () => { + expect(handler.canBeApplied(instance, wallet)).toBeTrue(); + }); + + it("should be false if wallet already has a second signature", () => { + wallet.secondPublicKey = "02d5cfcbc4920d041d2a54b29e1f69173536796fd50f62af0f88ad6adc6df07cb8"; + + expect(() => handler.canBeApplied(instance, wallet)).toThrow(SecondSignatureAlreadyRegisteredError); + }); + + it("should be false if wallet has insufficient funds", () => { + wallet.balance = Bignum.ZERO; + + expect(() => handler.canBeApplied(instance, wallet)).toThrow(InsufficientBalanceError); + }); + }); + + describe("apply", () => { + it("should apply second signature registration", () => { + expect(handler.canBeApplied(instance, wallet)).toBeTrue(); + + handler.applyToSender(instance, wallet); + expect(wallet.secondPublicKey).toBe("02d5cfcbc4920d041d2a54b29e1f69173536796fd50f62af0f88ad6adc6df07cb8"); + }); + + it("should be invalid to apply a second signature registration twice", () => { + expect(handler.canBeApplied(instance, wallet)).toBeTrue(); + + handler.applyToSender(instance, wallet); + expect(wallet.secondPublicKey).toBe("02d5cfcbc4920d041d2a54b29e1f69173536796fd50f62af0f88ad6adc6df07cb8"); + + expect(() => handler.canBeApplied(instance, wallet)).toThrow(SecondSignatureAlreadyRegisteredError); + }); + }); + + describe("revert", () => { + it("should be ok", () => { + expect(wallet.secondPublicKey).toBeNull(); + expect(handler.canBeApplied(instance, wallet)).toBeTrue(); + + handler.applyToSender(instance, wallet); + expect(wallet.secondPublicKey).toBe("02d5cfcbc4920d041d2a54b29e1f69173536796fd50f62af0f88ad6adc6df07cb8"); + + handler.revertForSender(instance, wallet); + expect(wallet.secondPublicKey).toBeNull(); + }); + }); +}); + +describe("DelegateRegistrationTransaction", () => { + beforeEach(() => { + wallet = walletFixture; + + transaction = { + version: 1, + id: "943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4", + type: 2, + timestamp: 36482198, + amount: Bignum.ZERO, + fee: new Bignum(10000000), + recipientId: "DTRdbaUW3RQQSL5By4G43JVaeHiqfVp9oh", + senderPublicKey: "034da006f958beba78ec54443df4a3f52237253f7ae8cbdb17dccf3feaa57f3126", + signature: + "304402205881204c6e515965098099b0e20a7bf104cd1bad6cfe8efd1641729fcbfdbf1502203cfa3bd9efb2ad250e2709aaf719ac0db04cb85d27a96bc8149aeaab224de82b", + asset: { + delegate: { + username: "dummy", + publicKey: ("a" as any).repeat(66), + }, + }, + }; + + handler = TransactionHandlerRegistry.get(transaction.type); + instance = Transaction.fromData(transaction); + }); + + describe("canApply", () => { + it("should be true", () => { + expect(handler.canBeApplied(instance, wallet)).toBeTrue(); + }); + + it("should be false if wallet already registered a username", () => { + wallet.username = "dummy"; + + expect(() => handler.canBeApplied(instance, wallet)).toThrow(WalletUsernameNotEmptyError); + }); + + it("should be false if wallet has insufficient funds", () => { + wallet.username = ""; + wallet.balance = Bignum.ZERO; + + expect(() => handler.canBeApplied(instance, wallet)).toThrow(InsufficientBalanceError); + }); + }); + + describe("apply", () => { + it("should set username", () => { + handler.applyToSender(instance, wallet); + expect(wallet.username).toBe("dummy"); + }); + }); + + describe("revert", () => { + it("should unset username", () => { + handler.revertForSender(instance, wallet); + expect(wallet.username).toBeNull(); + }); + }); +}); + +describe("VoteTransaction", () => { + let voteTransaction; + let unvoteTransaction; + + beforeEach(() => { + wallet = { + address: "DQ7VAW7u171hwDW75R1BqfHbA9yiKRCBSh", + balance: new Bignum("6453530000000"), + publicKey: "02a47a2f594635737d2ce9898680812ff7fa6aaa64ddea1360474c110e9985a087", + vote: null, + } as Wallet; + + voteTransaction = { + id: "73cbce62d69308ff7e69f1a7836106a16dc59907198aea4bb80d340232e53041", + signature: + "3045022100f53da6eb18ca7954bb7c620ceeaf5cb3433685d173401146aea35ee8e5f5c95002204ea57f573745c8f5c57b256e38397d3e1977bdbfac295128320401c6117bb2f3", + timestamp: 54833694, + type: 3, + fee: new Bignum(100000000), + senderPublicKey: "02a47a2f594635737d2ce9898680812ff7fa6aaa64ddea1360474c110e9985a087", + amount: Bignum.ZERO, + recipientId: "DLvBAvLePTJ9DfDzby5AAkqPqwCVDCT647", + asset: { + votes: ["+02d0d835266297f15c192be2636eb3fbc30b39b87fc583ff112062ef8ae1a1f2af"], + }, + }; + + unvoteTransaction = { + id: "d714bc0443208f9281ad83f9f3d941628b875c84f65a09601148ce87ca879cb9", + signature: + "3045022100957106a924eb40df6ff530cff80fede0195c30284fdb5671e736c7d0b57696f6022072b0fd80af235d79701e9aea74ef48366ef9f5aecedbb5d502e6392569c059c8", + timestamp: 54833718, + type: 3, + fee: new Bignum(100000000), + senderPublicKey: "02a47a2f594635737d2ce9898680812ff7fa6aaa64ddea1360474c110e9985a087", + amount: Bignum.ZERO, + recipientId: "DLvBAvLePTJ9DfDzby5AAkqPqwCVDCT647", + asset: { + votes: ["-02d0d835266297f15c192be2636eb3fbc30b39b87fc583ff112062ef8ae1a1f2af"], + }, + }; + + handler = TransactionHandlerRegistry.get(voteTransaction.type); + instance = Transaction.fromData(voteTransaction); + }); + + describe("canApply", () => { + it("should be true if the vote is valid and the wallet has not voted", () => { + expect(handler.canBeApplied(instance, wallet)).toBeTrue(); + }); + + it("should be true if the unvote is valid and the wallet has voted", () => { + wallet.vote = "02d0d835266297f15c192be2636eb3fbc30b39b87fc583ff112062ef8ae1a1f2af"; + instance = Transaction.fromData(unvoteTransaction); + expect(handler.canBeApplied(instance, wallet)).toBeTrue(); + }); + + it("should be false if wallet has already voted", () => { + wallet.vote = "02d0d835266297f15c192be2636eb3fbc30b39b87fc583ff112062ef8ae1a1f2af"; + expect(() => handler.canBeApplied(instance, wallet)).toThrow(AlreadyVotedError); + }); + + it("should be false if the asset public key differs from the currently voted one", () => { + wallet.vote = "a310ad026647eed112d1a46145eed58b8c19c67c505a67f1199361a511ce7860c0"; + instance = Transaction.fromData(unvoteTransaction); + expect(() => handler.canBeApplied(instance, wallet)).toThrow(UnvoteMismatchError); + }); + + it("should be false if unvoting a non-voted wallet", () => { + instance = Transaction.fromData(unvoteTransaction); + expect(() => handler.canBeApplied(instance, wallet)).toThrow(NoVoteError); + }); + + it("should be false if wallet has insufficient funds", () => { + wallet.balance = Bignum.ZERO; + expect(() => handler.canBeApplied(instance, wallet)).toThrow(InsufficientBalanceError); + }); + }); + + describe("apply", () => { + describe("vote", () => { + it("should be ok", () => { + expect(wallet.vote).toBeNull(); + + handler.applyToSender(instance, wallet); + expect(wallet.vote).not.toBeNull(); + }); + + it("should not be ok", () => { + wallet.vote = "02d0d835266297f15c192be2636eb3fbc30b39b87fc583ff112062ef8ae1a1f2af"; + + expect(wallet.vote).not.toBeNull(); + + handler.applyToSender(instance, wallet); + + expect(wallet.vote).not.toBeNull(); + }); + }); + + describe("unvote", () => { + it("should remove the vote from the wallet", () => { + wallet.vote = "02d0d835266297f15c192be2636eb3fbc30b39b87fc583ff112062ef8ae1a1f2af"; + + expect(wallet.vote).not.toBeNull(); + + instance = Transaction.fromData(unvoteTransaction); + handler.applyToSender(instance, wallet); + + expect(wallet.vote).toBeNull(); + }); + }); + }); + + describe("revert", () => { + describe("vote", () => { + it("should remove the vote from the wallet", () => { + wallet.vote = "02d0d835266297f15c192be2636eb3fbc30b39b87fc583ff112062ef8ae1a1f2af"; + + expect(wallet.vote).not.toBeNull(); + + handler.revertForSender(instance, wallet); + + expect(wallet.vote).toBeNull(); + }); + }); + + describe("unvote", () => { + it("should add the vote to the wallet", () => { + expect(wallet.vote).toBeNull(); + + instance = Transaction.fromData(unvoteTransaction); + handler.revertForSender(instance, wallet); + + expect(wallet.vote).toBe("02d0d835266297f15c192be2636eb3fbc30b39b87fc583ff112062ef8ae1a1f2af"); + }); + }); + }); +}); + +describe.skip("MultiSignatureRegistrationTransaction", () => { + let multisignatureTest; + + beforeEach(() => { + wallet = new Wallet("D61xc3yoBQDitwjqUspMPx1ooET6r1XLt7"); + wallet.balance = new Bignum(100390000000); + wallet.publicKey = "026f717e50bf3dbb9d8593996df5435ba22217410fc7a132f3d2c942a01a00a202"; + wallet.secondPublicKey = "0380728436880a0a11eadf608c4d4e7f793719e044ee5151074a5f2d5d43cb9066"; + wallet.multisignature = multisignatureTest; + + transaction = { + version: 1, + id: "e22ddd7385b42c00f79b9c6ecd253333ddef6e0bf955341ace2e63dad1f4bd70", + type: 4, + timestamp: 48059808, + amount: Bignum.ZERO, + fee: new Bignum(8000000000), + recipientId: "DGN48KSVFx88chiSu7JbqkAXstqtM1uLJQ", + senderPublicKey: "026f717e50bf3dbb9d8593996df5435ba22217410fc7a132f3d2c942a01a00a202", + signature: + "30450221008baddfae37be66d725e22d9e93c10334d859558f2aef38762803178dbb39354f022025a9bdc7fc4c86d3f67cd1d012dbee3d5691ab3188b5457fdeae82fdd5995767", + secondSignature: + "3045022100eb9844a235309309f805235ec40336260cc3dc2c3cbb4cb687dd55b32d8f405402202a98ca5b3b2ad31cec0ed01d9c085a828dd5c07c3893858d4c127fce57d6d410", + signatures: [ + "3045022100f073a3f59ed753f98734462dbe7c9082bb7cb9d46348c671708c93df2fdd2a7602206dc19039d3561f8d1226755dd3b0ca25f359347729eff066eaf3cc3b5c18bc59", + "3045022100c560d6d8504b6761245f7bb3e3b723380b50c380ae30c9544c781f3a9b1359a702206b50506ba6c0a39bed7bec226b55bf9ece979716eb95e2a757f025d3592fde17", + "30440220344345bcb9754ab242dc27bd3d705e5213597914183818005ff1f2e91466f17a0220474c27d05cd5f121c3cad0295e6fc9f8a8cdfa03647e70eb3783e4c1139dde04", + "3045022100998e29255a8f1c140aa41d93ec43271fd8d0e5b9c18df366e3c7b59cc0c293d902205292dd36e9db18f072f00559267361b9426ab26bff2ee613ec0c3627317b4dab", + "3044022007379b5643032d9e9d3395298776be041b2a85a211be2d7b6a5855cf030ae0ca02203c5d3da458034483fdef9f43ee4db4428999cfeb8893795f695e663407238090", + "3044022060461195aeb4386dfab1e3618cdec48f4b988ea394461962379cdbfe8f17b7110220415522adf0239bff7e44e6c0cc8d57211d9d9fe745a6ba2911a81586d5dfc5dc", + "3044022057355ab8ad9502745895a649aede98dfe829c46465eda57438720baeaa6ece5c02200ed3c2eb019579b243380ac066d691f6f27012dc6b93a1403e1a49c992cc0812", + "3044022010cf1079e46cbac198e49f763795095c3a1f33b772cf3e6f335c313f786eb0570220450a110a813cc5453265f0e97850794b0f9d5c6efd6d9ea08009df3d4b9f2299", + "3044022000f35223b23f03413f17538b157e62388c0b150fc046fdcc35792a48d694499402205c2e494ca74565e7841cd6034228cd3d9b57bb832f0da5834991bf92b415c0b8", + "304402206b69cbd52335fca4a510fa1dcb1417617ad1128aa06dcf543d1f11890e46fdef022012d3054adc0a924429d34091910bf82c0abc757f39cfc0887c7e4d9b35f21ad6", + "30440220490bf3e963aa500404e5d559dc06bb1ce176ddbab92f46add87c17c19c3781c90220775f0a3f65d95e3e268eb1f2f2fc86044995e7ebdd1f51f99a973fd00c952d57", + "30450221008795d2e1a454c2cbda92d5fcb7e539372cefbe9f8a181d658abcbd2ba18da8e702207b395488d31f037dc158c12799885edb94f36b1437b2bda79d9074c9a82aa686", + "3045022100cf706e93a9984a958dba6e17287d17febae005d277afc77890e0a3912ee7ed3d02206618718ee68cae209c42a801b7b295fa2564878838712a1b22beaa3637b57c58", + "30440220743aba2fdb663dda73b64ada17812a98adf26f2419c6ab2cfba8f66666527a91022036ade1b37b72079eb43b51a8fe5e31da2e42f7b6d0b437f8a693cc276b9123b8", + "304402206902fc8f519670a7768ad13f1b2d69373aa14c89b70020f83273d6bb0cfd89d102207de30b9ac0c17ff11e364b72c41f1cd8d4e6dccbe28399cb78e96eea32deef12", + ], + asset: { + multisignature: { + min: 15, + lifetime: 72, + keysgroup: [ + "+0217e9e2a1aca300a7011acaadf60af94252875568373546895f227c050d48aac5", + "+02b3b3233c171a122f88c1dbe44539dfefb36530ca3ec04163aef9f448a1823795", + "+03a3013f144160e1964b97e78117571e571a631f0042efcd0de309c7159c7886c8", + "+02fb475ef881b8f56e00407095a87319934c34467db11d3230e54d9328c6cddbe5", + "+03ab9cc2c5364f1676a94b2b5ff3fbc3705e8ce94c6e7e4712890905addf765a3f", + "+024be9e731a63f86b56e5f48dbdfb3443a0628c82ea308ee4c88d3fcbe3183eb9d", + "+0371b8fd17fb1f31095e8a1586bbe29e205904c9100de07c84090a423929a20dcf", + "+02cc09a7c5560db72e312f58a9f5ca4b60b5109efc5ce9dd58a116fa16516bb493", + "+02145fbe9309ebb1547eb332686efb4d8b6e2aaa1fe636663bf6ab1000e5cf72d3", + "+0274966781d4d23f8991530b33bdb051905cde809ae52e58e45cfd1bc8f6f70cc6", + "+0347288f8db9be069415c6c97fd4825867f4bd9b9f78557e8aa1244890beb85001", + "+035359097c405e90516be78104de0ca17001da2826397e0937b8b1e8e613fff352", + "+021aa343234514f8fdaf5e668bdc822a42805382567fa2ca9a5e06e92065f5658a", + "+033a28a0a9592952336918ddded08dd55503b82852fe67df1d358f07a575910844", + "+02747bec17b02cc09345c8c0dbeb09bff2db74d1c355135e10af0001eb1dc00265", + ], + }, + }, + }; + + multisignatureTest = { + min: 15, + lifetime: 72, + keysgroup: [ + "034a7aca6841cfbdc688f09d55345f21c7ffbd1844693fa68d607fc94f729cbbea", + "02fd6743ddfdc7c5bac24145e449c2e4f2d569b5297dd7bf088c3bc219f582a2f0", + "02f9c51812f4be127b9f1f21cb4e146eca6aecc85739a243db0f1064981deda216", + "0214d60ca95cd87a097ed6e6e42281acb68ae1815c8f494b8ff18d24dc9e072171", + "02a14634e04e80b05acd56bc361af98498d76fbf5233f8d62773ceaa07172ddaa6", + "021a9ba0e72f02b8fa7bad386582ec1d6c05b7664c892bf2a86035a85350f37203", + "02e3ba373c6c352316b748e75358ead36504b0ef5139d215fb6a83a330c4eb60d5", + "0309039bfa18d6fd790edb79438783b27fbcab06040a2fdaa66fb81ad53ca8db5f", + "0393d12aff5962fa9065487959719a81c5d991e7c48a823039acd9254c2b673841", + "03d3265264f06fe1dd752798403a73e537eb461fc729c83a84b579e8434adfe7c4", + "02acfa496a6c12cb9acc3219993b17c62d19f4b570996c12a37d6e89eaa9716859", + "03136f2101f1767b0d63d9545410bcaf3a941b2b6f06851093f3c679e0d31ca1cd", + "02e6ec3941be86177bf0b998589c07da1b73e990466fdaee347c972c10f61b3797", + "037dcd05d921a9f2ddd11960fec2ea9904fc55cad030549a6c5f5a41b2e35e56d2", + "03206f7ae26f14cffb62b8c28b5e632952cdeb84b7c74ac0c2198b08bd84ee4f23", + ], + }; + + handler = TransactionHandlerRegistry.get(transaction.type); + instance = Transaction.fromData(transaction); + }); + + describe("canApply", () => { + it("should be true", () => { + delete wallet.multisignature; + expect(handler.canBeApplied(instance, wallet)).toBeTrue(); + }); + + it("should be false if the wallet already has multisignatures", () => { + wallet.verifySignatures = jest.fn(() => true); + wallet.multisignature = multisignatureTest; + + expect(() => handler.canBeApplied(instance, wallet)).toThrow(MultiSignatureAlreadyRegisteredError); + }); + + it("should be false if failure to verify signatures", () => { + wallet.verifySignatures = jest.fn(() => false); + delete wallet.multisignature; + + expect(() => handler.canBeApplied(instance, wallet)).toThrow(InvalidMultiSignatureError); + }); + + it("should be false if failure to verify signatures in asset", () => { + wallet.verifySignatures = jest.fn(() => false); + delete wallet.multisignature; + + expect(() => handler.canBeApplied(instance, wallet)).toThrow(InvalidMultiSignatureError); + }); + + it("should be false if the number of keys is less than minimum", () => { + delete wallet.multisignature; + + wallet.verifySignatures = jest.fn(() => true); + crypto.verifySecondSignature = jest.fn(() => true); + + instance.data.asset.multisignature.keysgroup.splice(0, 5); + expect(() => handler.canBeApplied(instance, wallet)).toThrow(MultiSignatureMinimumKeysError); + }); + + it("should be false if the number of keys does not equal the signature count", () => { + delete wallet.multisignature; + + wallet.verifySignatures = jest.fn(() => true); + crypto.verifySecondSignature = jest.fn(() => true); + + instance.data.signatures.splice(0, 5); + expect(() => handler.canBeApplied(instance, wallet)).toThrow(MultiSignatureKeyCountMismatchError); + }); + + it("should be false if wallet has insufficient funds", () => { + delete wallet.multisignature; + wallet.balance = Bignum.ZERO; + + expect(() => handler.canBeApplied(instance, wallet)).toThrow(InsufficientBalanceError); + }); + }); + + describe("apply", () => { + it("should be ok", () => { + wallet.multisignature = null; + + expect(wallet.multisignature).toBeNull(); + + handler.applyToSender(instance, wallet); + + expect(wallet.multisignature).toEqual(transaction.asset.multisignature); + }); + }); + + describe("revert", () => { + it("should be ok", () => { + handler.revertForSender(instance, wallet); + + expect(wallet.multisignature).toBeNull(); + }); + }); +}); + +describe.skip("IpfsTransaction", () => { + beforeEach(() => { + transaction = transactionFixture; + wallet = walletFixture; + wallet.balance = new Bignum(transaction.amount).plus(transaction.fee); + handler = TransactionHandlerRegistry.get(transaction.type); + instance = Transaction.fromData(transaction); + }); + + describe("canApply", () => { + it("should be true", () => { + expect(handler.canBeApplied(instance, wallet)).toBeTrue(); + }); + + it("should be false", () => { + instance.data.senderPublicKey = "a".repeat(66); + expect(() => handler.canBeApplied(instance, wallet)).toThrow(SenderWalletMismatchError); + }); + + it("should be false if wallet has insufficient funds", () => { + wallet.balance = Bignum.ZERO; + expect(() => handler.canBeApplied(instance, wallet)).toThrow(InsufficientBalanceError); + }); + }); +}); + +describe.skip("TimelockTransferTransaction", () => { + beforeEach(() => { + transaction = transactionFixture; + wallet = walletFixture; + wallet.balance = new Bignum(transaction.amount).plus(transaction.fee); + handler = TransactionHandlerRegistry.get(transaction.type); + instance = Transaction.fromData(transaction); + }); + + describe("canApply", () => { + it("should be true", () => { + expect(handler.canBeApplied(instance, wallet)).toBeTrue(); + }); + + it("should be false", () => { + instance.data.senderPublicKey = "a".repeat(66); + expect(() => handler.canBeApplied(instance, wallet)).toThrow(SenderWalletMismatchError); + }); + + it("should be false if wallet has insufficient funds", () => { + wallet.balance = Bignum.ZERO; + expect(() => handler.canBeApplied(instance, wallet)).toThrow(InsufficientBalanceError); + }); + }); +}); + +describe.skip("MultiPaymentTransaction", () => { + beforeEach(() => { + transaction = { + version: 1, + id: "943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4", + type: 7, + timestamp: 36482198, + amount: new Bignum(0), + fee: new Bignum(10000000), + recipientId: "DTRdbaUW3RQQSL5By4G43JVaeHiqfVp9oh", + senderPublicKey: "034da006f958beba78ec54443df4a3f52237253f7ae8cbdb17dccf3feaa57f3126", + signature: + "304402205881204c6e515965098099b0e20a7bf104cd1bad6cfe8efd1641729fcbfdbf1502203cfa3bd9efb2ad250e2709aaf719ac0db04cb85d27a96bc8149aeaab224de82b", + asset: { + payments: [ + { + amount: new Bignum(10), + recipientId: "a", + }, + { + amount: new Bignum(20), + recipientId: "b", + }, + { + amount: new Bignum(30), + recipientId: "c", + }, + { + amount: new Bignum(40), + recipientId: "d", + }, + { + amount: new Bignum(50), + recipientId: "e", + }, + ], + }, + }; + + wallet = walletFixture; + wallet.balance = new Bignum(transaction.amount).plus(transaction.fee); + handler = TransactionHandlerRegistry.get(transaction.type); + instance = Transaction.fromData(transaction); + }); + + describe("canApply", () => { + it("should be true", () => { + expect(handler.canBeApplied(instance, wallet)).toBeTrue(); + }); + + it("should be false if wallet has insufficient funds", () => { + wallet.balance = Bignum.ZERO; + expect(() => handler.canBeApplied(instance, wallet)).toThrow(InsufficientBalanceError); + }); + + it("should be false if wallet has insufficient funds send all payouts", () => { + wallet.balance = Bignum.ZERO; + expect(() => handler.canBeApplied(instance, wallet)).toThrow(InsufficientBalanceError); + }); + }); +}); + +describe.skip("DelegateResignationTransaction", () => { + beforeEach(() => { + transaction = transactionFixture; + wallet = walletFixture; + wallet.balance = new Bignum(transaction.amount).plus(transaction.fee); + handler = TransactionHandlerRegistry.get(transaction.type); + instance = Transaction.fromData(transaction); + }); + + describe("canApply", () => { + it("should be truth", () => { + wallet.username = "dummy"; + expect(handler.canBeApplied(instance, wallet)).toBeTrue(); + }); + + it.skip("should be false if wallet has no registered username", () => { + wallet.username = null; + expect(() => handler.canBeApplied(instance, wallet)).toThrow(WalletNoUsernameError); + }); + + it("should be false if wallet has insufficient funds", () => { + wallet.balance = Bignum.ZERO; + expect(() => handler.canBeApplied(instance, wallet)).toThrow(InsufficientBalanceError); + }); + }); +}); diff --git a/packages/core-utils/__tests__/bignumify.test.ts b/__tests__/unit/core-utils/bignumify.test.ts similarity index 88% rename from packages/core-utils/__tests__/bignumify.test.ts rename to __tests__/unit/core-utils/bignumify.test.ts index baee3f80c2..f9958e1566 100644 --- a/packages/core-utils/__tests__/bignumify.test.ts +++ b/__tests__/unit/core-utils/bignumify.test.ts @@ -1,6 +1,6 @@ import { Bignum } from "@arkecosystem/crypto"; import "jest-extended"; -import { bignumify } from "../src/bignumify"; +import { bignumify } from "../../../packages/core-utils/src/bignumify"; describe("Bignumify", () => { it("should create a bignumber instance", () => { diff --git a/packages/core-utils/__tests__/capped-set.test.ts b/__tests__/unit/core-utils/capped-set.test.ts similarity index 86% rename from packages/core-utils/__tests__/capped-set.test.ts rename to __tests__/unit/core-utils/capped-set.test.ts index b4d45ae51a..595a7cc5f2 100644 --- a/packages/core-utils/__tests__/capped-set.test.ts +++ b/__tests__/unit/core-utils/capped-set.test.ts @@ -1,8 +1,8 @@ -import "./__support__/mocks/core-container"; +import "./mocks/core-container"; import { app } from "@arkecosystem/core-container"; import "jest-extended"; -import { CappedSet } from "../src/capped-set"; +import { CappedSet } from "../../../packages/core-utils/src/capped-set"; describe("CappedSet", () => { it("basic", () => { diff --git a/__tests__/unit/core-utils/delegate-calculator.test.ts b/__tests__/unit/core-utils/delegate-calculator.test.ts new file mode 100644 index 0000000000..bf6df11a10 --- /dev/null +++ b/__tests__/unit/core-utils/delegate-calculator.test.ts @@ -0,0 +1,44 @@ +import "jest-extended"; +import "./mocks/core-container-calculator"; + +import { Wallet } from "@arkecosystem/core-database"; +import { Bignum } from "@arkecosystem/crypto"; +import { calculateApproval, calculateForgedTotal } from "../../../packages/core-utils/src/delegate-calculator"; + +let delegate: Wallet; + +beforeEach(() => { + delegate = new Wallet("D61xc3yoBQDitwjqUspMPx1ooET6r1XLt7"); + delegate.producedBlocks = 0; +}); + +describe("Delegate Calculator", () => { + describe("calculateApproval", () => { + it("should calculate correctly with a height", () => { + delegate.voteBalance = new Bignum(10000 * 1e8); + + expect(calculateApproval(delegate, 1)).toBe(1); + }); + + it("should calculate correctly without a height", () => { + delegate.voteBalance = new Bignum(10000 * 1e8); + + expect(calculateApproval(delegate)).toBe(1); + }); + + it("should calculate correctly with 2 decimals", () => { + delegate.voteBalance = new Bignum(16500 * 1e8); + + expect(calculateApproval(delegate, 1)).toBe(1.65); + }); + }); + + describe("calculateForgedTotal", () => { + it("should calculate correctly", () => { + delegate.forgedFees = new Bignum(10); + delegate.forgedRewards = new Bignum(100); + + expect(calculateForgedTotal(delegate)).toBe(110); + }); + }); +}); diff --git a/packages/core-utils/__tests__/format-timestamp.test.ts b/__tests__/unit/core-utils/format-timestamp.test.ts similarity index 81% rename from packages/core-utils/__tests__/format-timestamp.test.ts rename to __tests__/unit/core-utils/format-timestamp.test.ts index 17a7a19370..8ff9d6975e 100644 --- a/packages/core-utils/__tests__/format-timestamp.test.ts +++ b/__tests__/unit/core-utils/format-timestamp.test.ts @@ -1,8 +1,8 @@ -import "./__support__/mocks/core-container"; +import "./mocks/core-container"; import { app } from "@arkecosystem/core-container"; import "jest-extended"; -import { formatTimestamp } from "../src/format-timestamp"; +import { formatTimestamp } from "../../../packages/core-utils/src/format-timestamp"; describe("Format Timestamp", () => { it("should compute the correct epoch value", () => { diff --git a/__tests__/unit/core-utils/has-some-property.test.ts b/__tests__/unit/core-utils/has-some-property.test.ts new file mode 100644 index 0000000000..21ffac6e3e --- /dev/null +++ b/__tests__/unit/core-utils/has-some-property.test.ts @@ -0,0 +1,23 @@ +import "jest-extended"; + +import { hasSomeProperty } from "../../../packages/core-utils/src/has-some-property"; + +let object; + +beforeEach(() => { + object = { property: null }; +}); + +describe("hasSomeProperty", () => { + it("should return true if the object has a given property", () => { + expect(hasSomeProperty(object, ["property"])).toBe(true); + }); + + it("should return true if the object has any of the given properties", () => { + expect(hasSomeProperty(object, ["not-present", "property"])).toBe(true); + }); + + it("should return false if the object doesn't have a given property", () => { + expect(hasSomeProperty(object, ["not-present"])).toBe(false); + }); +}); diff --git a/packages/core-utils/__tests__/__support__/mocks/core-container-calculator.ts b/__tests__/unit/core-utils/mocks/core-container-calculator.ts similarity index 100% rename from packages/core-utils/__tests__/__support__/mocks/core-container-calculator.ts rename to __tests__/unit/core-utils/mocks/core-container-calculator.ts diff --git a/packages/core-utils/__tests__/__support__/mocks/core-container.ts b/__tests__/unit/core-utils/mocks/core-container.ts similarity index 75% rename from packages/core-utils/__tests__/__support__/mocks/core-container.ts rename to __tests__/unit/core-utils/mocks/core-container.ts index 17aa34a5e2..9a0e106b24 100644 --- a/packages/core-utils/__tests__/__support__/mocks/core-container.ts +++ b/__tests__/unit/core-utils/mocks/core-container.ts @@ -3,9 +3,11 @@ jest.mock("@arkecosystem/core-container", () => { app: { getConfig: () => { return { + config: { milestones: [{ activeDelegates: 51, height: 1 }] }, getMilestone: () => ({ epoch: "2017-03-21T13:00:00.000Z", activeDelegates: 51, + height: 1, }), }; }, diff --git a/packages/core-utils/__tests__/nsect.test.ts b/__tests__/unit/core-utils/nsect.test.ts similarity index 86% rename from packages/core-utils/__tests__/nsect.test.ts rename to __tests__/unit/core-utils/nsect.test.ts index 5f049bba55..4443a394a4 100644 --- a/packages/core-utils/__tests__/nsect.test.ts +++ b/__tests__/unit/core-utils/nsect.test.ts @@ -1,8 +1,7 @@ -import "./__support__/mocks/core-container"; +import "./mocks/core-container"; -import { app } from "@arkecosystem/core-container"; import "jest-extended"; -import { NSect } from "../src/nsect"; +import { NSect } from "../../../packages/core-utils/src/nsect"; let data: number[]; let nAry: number; @@ -109,6 +108,20 @@ describe("N-section (8-ary search)", () => { expect(numberOfProbeCalls).toBe(3); }); + it("search in a narrow range", async () => { + numberOfProbeCalls = 0; + searchCondition = element => element <= 4000; + expect(data[await nSect.find(398, 402)]).toBe(4000); + expect(numberOfProbeCalls).toBe(1); + }); + + it("search in a range with length 9", async () => { + numberOfProbeCalls = 0; + searchCondition = element => element <= 4000; + expect(data[await nSect.find(398, 407)]).toBe(4000); + expect(numberOfProbeCalls).toBe(1); + }); + it("nonexistent", async () => { numberOfProbeCalls = 0; searchCondition = element => false; diff --git a/__tests__/unit/core-utils/round-calculator.test.ts b/__tests__/unit/core-utils/round-calculator.test.ts new file mode 100644 index 0000000000..052f69335f --- /dev/null +++ b/__tests__/unit/core-utils/round-calculator.test.ts @@ -0,0 +1,142 @@ +import "jest-extended"; +import "./mocks/core-container"; + +import { app } from "@arkecosystem/core-container"; +import { calculateRound, isNewRound } from "../../../packages/core-utils/src/round-calculator"; + +describe("Round calculator", () => { + describe("calculateRound", () => { + describe("static delegate count", () => { + it("should calculate the round when nextRound is the same", () => { + for (let i = 0, height = 51; i < 1000; i++, height += 51) { + const { round, nextRound } = calculateRound(height - 1); + expect(round).toBe(i + 1); + expect(nextRound).toBe(i + 1); + } + }); + + it("should calculate the round when nextRound is not the same", () => { + for (let i = 0, height = 51; i < 1000; i++, height += 51) { + const { round, nextRound } = calculateRound(height); + expect(round).toBe(i + 1); + expect(nextRound).toBe(i + 2); + } + }); + + it("should calculate the correct round", () => { + const activeDelegates = 51; + for (let i = 0; i < 1000; i++) { + const { round, nextRound } = calculateRound(i + 1); + expect(round).toBe(Math.floor(i / activeDelegates) + 1); + expect(nextRound).toBe(Math.floor((i + 1) / activeDelegates) + 1); + } + }); + }); + + describe("dynamic delegate count", () => { + it("should calculate the correct with dynamic delegate count", () => { + const testVector = [ + { height: 1, round: 1, roundHeight: 1, nextRound: 1, activeDelegates: 2 }, + { height: 3, round: 2, roundHeight: 3, nextRound: 2, activeDelegates: 3 }, + { height: 6, round: 3, roundHeight: 6, nextRound: 4, activeDelegates: 1 }, + { height: 10, round: 7, roundHeight: 10, nextRound: 7, activeDelegates: 51 }, + { height: 112, round: 9, roundHeight: 112, nextRound: 10, activeDelegates: 1 }, + { height: 115, round: 12, roundHeight: 115, nextRound: 12, activeDelegates: 2 }, + { height: 131, round: 20, roundHeight: 131, nextRound: 20, activeDelegates: 51 }, + ]; + + const milestones = testVector.reduce((acc, vector) => acc.set(vector.height, vector), new Map()); + + const backup = app.getConfig; + app.getConfig = jest.fn(() => { + return { + config: { + milestones: Array.from(milestones.values()), + }, + getMilestone: height => { + return milestones.get(height); + }, + }; + }); + + testVector.forEach(({ height, round, roundHeight, nextRound, activeDelegates }) => { + const result = calculateRound(height); + expect(result.round).toBe(round); + expect(result.roundHeight).toBe(roundHeight); + expect(isNewRound(result.roundHeight)).toBeTrue(); + expect(result.nextRound).toBe(nextRound); + expect(result.maxDelegates).toBe(activeDelegates); + }); + + app.getConfig = backup; + }); + }); + }); + + describe("isNewRound", () => { + const setMilestones = milestones => { + app.getConfig = jest.fn(() => { + return { + config: { + milestones, + }, + getMilestone: height => { + for (let i = milestones.length - 1; i >= 0; i--) { + if (milestones[i].height <= height) { + return milestones[i]; + } + } + + return milestones[0]; + }, + }; + }); + }; + + it("should determine the beginning of a new round", () => { + expect(isNewRound(1)).toBeTrue(); + expect(isNewRound(2)).toBeFalse(); + expect(isNewRound(52)).toBeTrue(); + expect(isNewRound(53)).toBeFalse(); + expect(isNewRound(54)).toBeFalse(); + expect(isNewRound(103)).toBeTrue(); + expect(isNewRound(104)).toBeFalse(); + expect(isNewRound(154)).toBeTrue(); + }); + + it("should be ok when changing delegate count", () => { + const milestones = [ + { height: 1, activeDelegates: 2 }, // R1 + { height: 3, activeDelegates: 3 }, // R2 + { height: 6, activeDelegates: 1 }, // R3 + { height: 10, activeDelegates: 51 }, // R7 + { height: 62, activeDelegates: 51 }, // R8 + ]; + + const backup = app.getConfig; + setMilestones(milestones); + + // 2 Delegates + expect(isNewRound(1)).toBeTrue(); + expect(isNewRound(2)).toBeFalse(); + + // 3 Delegates + expect(isNewRound(3)).toBeTrue(); + expect(isNewRound(4)).toBeFalse(); + expect(isNewRound(5)).toBeFalse(); + + // 1 Delegate + expect(isNewRound(6)).toBeTrue(); + expect(isNewRound(7)).toBeTrue(); + expect(isNewRound(8)).toBeTrue(); + expect(isNewRound(9)).toBeTrue(); + + // 51 Delegates + expect(isNewRound(10)).toBeTrue(); + expect(isNewRound(11)).toBeFalse(); + expect(isNewRound(61)).toBeTrue(); + + app.getConfig = backup; + }); + }); +}); diff --git a/packages/core-utils/__tests__/supply-calculator.test.ts b/__tests__/unit/core-utils/supply-calculator.test.ts similarity index 97% rename from packages/core-utils/__tests__/supply-calculator.test.ts rename to __tests__/unit/core-utils/supply-calculator.test.ts index 8bfb35f7bd..5972fe1aab 100644 --- a/packages/core-utils/__tests__/supply-calculator.test.ts +++ b/__tests__/unit/core-utils/supply-calculator.test.ts @@ -1,6 +1,6 @@ import { app } from "@arkecosystem/core-container"; import "jest-extended"; -import { calculate } from "../src/supply-calculator"; +import { calculate } from "../../../packages/core-utils/src/supply-calculator"; let config; diff --git a/packages/core-webhooks/__tests__/conditions.test.ts b/__tests__/unit/core-webhooks/conditions.test.ts similarity index 94% rename from packages/core-webhooks/__tests__/conditions.test.ts rename to __tests__/unit/core-webhooks/conditions.test.ts index 26eeed7b51..036e00e239 100644 --- a/packages/core-webhooks/__tests__/conditions.test.ts +++ b/__tests__/unit/core-webhooks/conditions.test.ts @@ -1,5 +1,18 @@ import "jest-extended"; -import { between, contains, eq, falsy, gt, gte, lt, lte, ne, notBetween, regexp, truthy } from "../src/conditions"; +import { + between, + contains, + eq, + falsy, + gt, + gte, + lt, + lte, + ne, + notBetween, + regexp, + truthy, +} from "../../../packages/core-webhooks/src/conditions"; describe("Conditions - between", () => { it("should be true", () => { diff --git a/__tests__/unit/core-webhooks/database.test.ts b/__tests__/unit/core-webhooks/database.test.ts new file mode 100644 index 0000000000..0fdbb2307f --- /dev/null +++ b/__tests__/unit/core-webhooks/database.test.ts @@ -0,0 +1,66 @@ +import "jest-extended"; + +process.env.CORE_PATH_CACHE = process.env.HOME; + +import { database } from "../../../packages/core-webhooks/src/database"; + +beforeEach(() => database.make()); +afterEach(() => database.reset()); + +describe("Conditions - between", () => { + it("should paginate all webhooks", () => { + database.create({ hello: "world" }); + + const { count, rows } = database.paginate({ offset: 0, limit: 1 }); + + expect(count).toBe(1); + expect(rows).toHaveLength(1); + }); + + it("should find a webhook by its id", () => { + const webhook = database.create({ hello: "world" }); + + expect(database.findById(webhook.id)).toEqual(webhook); + }); + + it("should find webhooks by their event", () => { + const webhook = database.create({ hello: "world", event: "dummy" }); + database.create({ hello: "world", event: "dummy" }); + + const { count, rows } = database.findByEvent("dummy"); + + expect(count).toBe(2); + expect(rows).toHaveLength(2); + expect(rows[0]).toEqual(webhook); + }); + + it("should return an empty array if there are no webhooks for an event", () => { + const { count, rows } = database.findByEvent("dummy"); + + expect(count).toBe(0); + expect(rows).toHaveLength(0); + }); + + it("should create a new webhook", () => { + const webhook = database.create({ hello: "world" }); + + expect(database.create(webhook)).toEqual(webhook); + }); + + it("should update an existing webhook", () => { + const webhook = database.create({ hello: "world" }); + const updated = database.update(webhook.id, { world: "hello" }); + + expect(database.findById(webhook.id)).toEqual(updated); + }); + + it("should delete an existing webhook", () => { + const webhook = database.create({ hello: "world" }); + + expect(database.findById(webhook.id)).toEqual(webhook); + + database.destroy(webhook.id); + + expect(database.findById(webhook.id)).toBeUndefined(); + }); +}); diff --git a/packages/crypto/__tests__/builder/transaction-builder-factory.test.ts b/__tests__/unit/crypto/builder/transaction-builder-factory.test.ts similarity index 61% rename from packages/crypto/__tests__/builder/transaction-builder-factory.test.ts rename to __tests__/unit/crypto/builder/transaction-builder-factory.test.ts index 16fc9d32b7..53610339d0 100644 --- a/packages/crypto/__tests__/builder/transaction-builder-factory.test.ts +++ b/__tests__/unit/crypto/builder/transaction-builder-factory.test.ts @@ -1,16 +1,16 @@ import "jest-extended"; -import { DelegateRegistrationBuilder } from "../../src/builder/transactions/delegate-registration"; -import { DelegateResignationBuilder } from "../../src/builder/transactions/delegate-resignation"; -import { IPFSBuilder } from "../../src/builder/transactions/ipfs"; -import { MultiPaymentBuilder } from "../../src/builder/transactions/multi-payment"; -import { MultiSignatureBuilder } from "../../src/builder/transactions/multi-signature"; -import { SecondSignatureBuilder } from "../../src/builder/transactions/second-signature"; -import { TimelockTransferBuilder } from "../../src/builder/transactions/timelock-transfer"; -import { TransferBuilder } from "../../src/builder/transactions/transfer"; -import { VoteBuilder } from "../../src/builder/transactions/vote"; - -import { transactionBuilder, TransactionBuilderFactory } from "../../src/builder"; +import { DelegateRegistrationBuilder } from "../../../../packages/crypto/src/builder/transactions/delegate-registration"; +import { DelegateResignationBuilder } from "../../../../packages/crypto/src/builder/transactions/delegate-resignation"; +import { IPFSBuilder } from "../../../../packages/crypto/src/builder/transactions/ipfs"; +import { MultiPaymentBuilder } from "../../../../packages/crypto/src/builder/transactions/multi-payment"; +import { MultiSignatureBuilder } from "../../../../packages/crypto/src/builder/transactions/multi-signature"; +import { SecondSignatureBuilder } from "../../../../packages/crypto/src/builder/transactions/second-signature"; +import { TimelockTransferBuilder } from "../../../../packages/crypto/src/builder/transactions/timelock-transfer"; +import { TransferBuilder } from "../../../../packages/crypto/src/builder/transactions/transfer"; +import { VoteBuilder } from "../../../../packages/crypto/src/builder/transactions/vote"; + +import { transactionBuilder, TransactionBuilderFactory } from "../../../../packages/crypto/src/builder"; describe("Transaction Builder Factory", () => { it("should be instantiated", () => { diff --git a/packages/crypto/__tests__/builder/transactions/__shared__/transaction-builder.ts b/__tests__/unit/crypto/builder/transactions/__shared__/transaction-builder.ts similarity index 83% rename from packages/crypto/__tests__/builder/transactions/__shared__/transaction-builder.ts rename to __tests__/unit/crypto/builder/transactions/__shared__/transaction-builder.ts index f1d6fa5f9b..a195b9811f 100644 --- a/packages/crypto/__tests__/builder/transactions/__shared__/transaction-builder.ts +++ b/__tests__/unit/crypto/builder/transactions/__shared__/transaction-builder.ts @@ -1,7 +1,6 @@ -import { TransactionBuilder } from "../../../../src/builder/transactions/transaction"; -import { crypto, slots } from "../../../../src/crypto"; -import { Transaction } from "../../../../src/models/transaction"; -import { Bignum } from "../../../../src/utils"; +import { TransactionBuilder } from "../../../../../../packages/crypto/src/builder/transactions/transaction"; +import { crypto, slots } from "../../../../../../packages/crypto/src/crypto"; +import { Bignum } from "../../../../../../packages/crypto/src/utils"; export const transactionBuilder = >(provider: () => TransactionBuilder) => { describe("TransactionBuilder", () => { @@ -25,14 +24,14 @@ export const transactionBuilder = >(provider: () timestamp = slots.getTime(); data = { - id: "fake-id", - amount: 0, - fee: 0, + id: "02d0d835266297f15c192be2636eb3fbc30b39b87fc583ff112062ef8dae1a1f", + amount: 1, + fee: 1, recipientId: "DK2v39r3hD9Lw8R5fFFHjUyCtXm1VETi42", senderPublicKey: "035440a82cb44faef75c3d7d881696530aac4d50da314b91795740cdbeaba9113c", timestamp, type: 0, - version: 0x03, + version: 0x01, }; }); @@ -43,16 +42,15 @@ export const transactionBuilder = >(provider: () const transaction = builder.build(); - expect(transaction).toBeInstanceOf(Transaction); - expect(transaction.amount).toEqual(Bignum.ZERO); - expect(transaction.fee).toEqual(Bignum.ZERO); - expect(transaction.recipientId).toBe("DK2v39r3hD9Lw8R5fFFHjUyCtXm1VETi42"); - expect(transaction.senderPublicKey).toBe( + expect(transaction.type).toBe(0); + expect(transaction.data.amount).toEqual(new Bignum(1)); + expect(transaction.data.fee).toEqual(new Bignum(1)); + expect(transaction.data.recipientId).toBe("DK2v39r3hD9Lw8R5fFFHjUyCtXm1VETi42"); + expect(transaction.data.senderPublicKey).toBe( "035440a82cb44faef75c3d7d881696530aac4d50da314b91795740cdbeaba9113c", ); - expect(transaction.timestamp).toBe(timestamp); - expect(transaction.type).toBe(0); - expect(transaction.version).toBe(0x03); + expect(transaction.data.timestamp).toBe(timestamp); + expect(transaction.data.version).toBe(0x01); }); it("could merge and override the builder data", () => { @@ -65,15 +63,14 @@ export const transactionBuilder = >(provider: () fee: 1000, }); - expect(transaction).toBeInstanceOf(Transaction); - expect(transaction.amount).toEqual(new Bignum(33)); - expect(transaction.fee).toEqual(new Bignum(1000)); - expect(transaction.recipientId).toBe("DK2v39r3hD9Lw8R5fFFHjUyCtXm1VETi42"); - expect(transaction.senderPublicKey).toBe( + expect(transaction.data.amount).toEqual(new Bignum(33)); + expect(transaction.data.fee).toEqual(new Bignum(1000)); + expect(transaction.data.recipientId).toBe("DK2v39r3hD9Lw8R5fFFHjUyCtXm1VETi42"); + expect(transaction.data.senderPublicKey).toBe( "035440a82cb44faef75c3d7d881696530aac4d50da314b91795740cdbeaba9113c", ); - expect(transaction.timestamp).toBe(timestamp); - expect(transaction.version).toBe(0x03); + expect(transaction.data.timestamp).toBe(timestamp); + expect(transaction.data.version).toBe(0x01); }); }); diff --git a/packages/crypto/__tests__/builder/transactions/delegate-registration.test.ts b/__tests__/unit/crypto/builder/transactions/delegate-registration.test.ts similarity index 88% rename from packages/crypto/__tests__/builder/transactions/delegate-registration.test.ts rename to __tests__/unit/crypto/builder/transactions/delegate-registration.test.ts index 25a57724e5..57cb2e78fb 100644 --- a/packages/crypto/__tests__/builder/transactions/delegate-registration.test.ts +++ b/__tests__/unit/crypto/builder/transactions/delegate-registration.test.ts @@ -1,10 +1,9 @@ import "jest-extended"; -import { DelegateRegistrationBuilder } from "../../../src/builder/transactions/delegate-registration"; -import { client } from "../../../src/client"; -import { TransactionTypes } from "../../../src/constants"; -import { crypto } from "../../../src/crypto/crypto"; -import { feeManager } from "../../../src/managers/fee"; -import { transactionBuilder } from "./__shared__/transaction-builder"; +import { DelegateRegistrationBuilder } from "../../../../../packages/crypto/src/builder/transactions/delegate-registration"; +import { client } from "../../../../../packages/crypto/src/client"; +import { TransactionTypes } from "../../../../../packages/crypto/src/constants"; +import { crypto } from "../../../../../packages/crypto/src/crypto/crypto"; +import { feeManager } from "../../../../../packages/crypto/src/managers/fee"; let builder: DelegateRegistrationBuilder; @@ -17,7 +16,7 @@ describe("Delegate Registration Transaction", () => { it("should be valid with a signature", () => { const actual = builder.usernameAsset("homer").sign("dummy passphrase"); - expect(actual.build().verify()).toBeTrue(); + expect(actual.build().verified).toBeTrue(); expect(actual.verify()).toBeTrue(); }); @@ -27,7 +26,7 @@ describe("Delegate Registration Transaction", () => { .sign("dummy passphrase") .secondSign("dummy passphrase"); - expect(actual.build().verify()).toBeTrue(); + expect(actual.build().verified).toBeTrue(); expect(actual.verify()).toBeTrue(); }); }); diff --git a/packages/crypto/__tests__/builder/transactions/delegate-resignation.test.ts b/__tests__/unit/crypto/builder/transactions/delegate-resignation.test.ts similarity index 62% rename from packages/crypto/__tests__/builder/transactions/delegate-resignation.test.ts rename to __tests__/unit/crypto/builder/transactions/delegate-resignation.test.ts index dfe6587d40..482f836622 100644 --- a/packages/crypto/__tests__/builder/transactions/delegate-resignation.test.ts +++ b/__tests__/unit/crypto/builder/transactions/delegate-resignation.test.ts @@ -1,8 +1,8 @@ import "jest-extended"; -import { DelegateResignationBuilder } from "../../../src/builder/transactions/delegate-resignation"; -import { client } from "../../../src/client"; -import { TransactionTypes } from "../../../src/constants"; -import { feeManager } from "../../../src/managers/fee"; +import { DelegateResignationBuilder } from "../../../../../packages/crypto/src/builder/transactions/delegate-resignation"; +import { client } from "../../../../../packages/crypto/src/client"; +import { TransactionTypes } from "../../../../../packages/crypto/src/constants"; +import { feeManager } from "../../../../../packages/crypto/src/managers/fee"; import { transactionBuilder } from "./__shared__/transaction-builder"; let builder: DelegateResignationBuilder; diff --git a/packages/crypto/__tests__/builder/transactions/ipfs.test.ts b/__tests__/unit/crypto/builder/transactions/ipfs.test.ts similarity index 82% rename from packages/crypto/__tests__/builder/transactions/ipfs.test.ts rename to __tests__/unit/crypto/builder/transactions/ipfs.test.ts index a21fffe909..c979390961 100644 --- a/packages/crypto/__tests__/builder/transactions/ipfs.test.ts +++ b/__tests__/unit/crypto/builder/transactions/ipfs.test.ts @@ -1,8 +1,8 @@ import "jest-extended"; -import { IPFSBuilder } from "../../../src/builder/transactions/ipfs"; -import { client } from "../../../src/client"; -import { TransactionTypes } from "../../../src/constants"; -import { feeManager } from "../../../src/managers/fee"; +import { IPFSBuilder } from "../../../../../packages/crypto/src/builder/transactions/ipfs"; +import { client } from "../../../../../packages/crypto/src/client"; +import { TransactionTypes } from "../../../../../packages/crypto/src/constants"; +import { feeManager } from "../../../../../packages/crypto/src/managers/fee"; import { transactionBuilder } from "./__shared__/transaction-builder"; let builder: IPFSBuilder; diff --git a/packages/crypto/__tests__/builder/transactions/multi-payment.test.ts b/__tests__/unit/crypto/builder/transactions/multi-payment.test.ts similarity index 78% rename from packages/crypto/__tests__/builder/transactions/multi-payment.test.ts rename to __tests__/unit/crypto/builder/transactions/multi-payment.test.ts index 7a7342acd1..549fde99f0 100644 --- a/packages/crypto/__tests__/builder/transactions/multi-payment.test.ts +++ b/__tests__/unit/crypto/builder/transactions/multi-payment.test.ts @@ -1,10 +1,10 @@ import "jest-extended"; -import { MultiPaymentBuilder } from "../../../src/builder/transactions/multi-payment"; -import { client } from "../../../src/client"; -import { TransactionTypes } from "../../../src/constants"; -import { MaximumPaymentCountExceededError } from "../../../src/errors"; -import { feeManager } from "../../../src/managers/fee"; -import { Bignum } from "../../../src/utils"; +import { MultiPaymentBuilder } from "../../../../../packages/crypto/src/builder/transactions/multi-payment"; +import { client } from "../../../../../packages/crypto/src/client"; +import { TransactionTypes } from "../../../../../packages/crypto/src/constants"; +import { MaximumPaymentCountExceededError } from "../../../../../packages/crypto/src/errors"; +import { feeManager } from "../../../../../packages/crypto/src/managers/fee"; +import { Bignum } from "../../../../../packages/crypto/src/utils"; import { transactionBuilder } from "./__shared__/transaction-builder"; let builder: MultiPaymentBuilder; diff --git a/packages/crypto/__tests__/builder/transactions/multi-signature.test.ts b/__tests__/unit/crypto/builder/transactions/multi-signature.test.ts similarity index 87% rename from packages/crypto/__tests__/builder/transactions/multi-signature.test.ts rename to __tests__/unit/crypto/builder/transactions/multi-signature.test.ts index 8caedb96cd..365a28ef2b 100644 --- a/packages/crypto/__tests__/builder/transactions/multi-signature.test.ts +++ b/__tests__/unit/crypto/builder/transactions/multi-signature.test.ts @@ -1,9 +1,9 @@ import "jest-extended"; -import { MultiSignatureBuilder } from "../../../src/builder/transactions/multi-signature"; -import { client } from "../../../src/client"; -import { TransactionTypes } from "../../../src/constants"; -import { crypto } from "../../../src/crypto/crypto"; -import { feeManager } from "../../../src/managers/fee"; +import { MultiSignatureBuilder } from "../../../../../packages/crypto/src/builder/transactions/multi-signature"; +import { client } from "../../../../../packages/crypto/src/client"; +import { TransactionTypes } from "../../../../../packages/crypto/src/constants"; +import { crypto } from "../../../../../packages/crypto/src/crypto/crypto"; +import { feeManager } from "../../../../../packages/crypto/src/managers/fee"; import { transactionBuilder } from "./__shared__/transaction-builder"; let builder: MultiSignatureBuilder; @@ -30,7 +30,7 @@ describe("Multi Signature Transaction", () => { .multiSignatureSign("multi passphrase 2") .multiSignatureSign("multi passphrase 3"); - expect(actual.build().verify()).toBeTrue(); + expect(actual.build().verified).toBeTrue(); expect(actual.verify()).toBeTrue(); }); }); diff --git a/packages/crypto/__tests__/builder/transactions/second-signature.test.ts b/__tests__/unit/crypto/builder/transactions/second-signature.test.ts similarity index 76% rename from packages/crypto/__tests__/builder/transactions/second-signature.test.ts rename to __tests__/unit/crypto/builder/transactions/second-signature.test.ts index befbfab705..e8f22566ea 100644 --- a/packages/crypto/__tests__/builder/transactions/second-signature.test.ts +++ b/__tests__/unit/crypto/builder/transactions/second-signature.test.ts @@ -1,9 +1,9 @@ import "jest-extended"; -import { SecondSignatureBuilder } from "../../../src/builder/transactions/second-signature"; -import { client } from "../../../src/client"; -import { TransactionTypes } from "../../../src/constants"; -import { crypto } from "../../../src/crypto/crypto"; -import { feeManager } from "../../../src/managers/fee"; +import { SecondSignatureBuilder } from "../../../../../packages/crypto/src/builder/transactions/second-signature"; +import { client } from "../../../../../packages/crypto/src/client"; +import { TransactionTypes } from "../../../../../packages/crypto/src/constants"; +import { crypto } from "../../../../../packages/crypto/src/crypto/crypto"; +import { feeManager } from "../../../../../packages/crypto/src/managers/fee"; import { transactionBuilder } from "./__shared__/transaction-builder"; let builder: SecondSignatureBuilder; @@ -17,7 +17,7 @@ describe("Second Signature Transaction", () => { it("should be valid with a signature", () => { const actual = builder.signatureAsset("signature").sign("dummy passphrase"); - expect(actual.build().verify()).toBeTrue(); + expect(actual.build().verified).toBeTrue(); expect(actual.verify()).toBeTrue(); }); }); diff --git a/packages/crypto/__tests__/builder/transactions/timelock-transfer.test.ts b/__tests__/unit/crypto/builder/transactions/timelock-transfer.test.ts similarity index 80% rename from packages/crypto/__tests__/builder/transactions/timelock-transfer.test.ts rename to __tests__/unit/crypto/builder/transactions/timelock-transfer.test.ts index 1fa5c3af69..b16d42a4c3 100644 --- a/packages/crypto/__tests__/builder/transactions/timelock-transfer.test.ts +++ b/__tests__/unit/crypto/builder/transactions/timelock-transfer.test.ts @@ -1,8 +1,8 @@ import "jest-extended"; -import { TimelockTransferBuilder } from "../../../src/builder/transactions/timelock-transfer"; -import { client } from "../../../src/client"; -import { TransactionTypes } from "../../../src/constants"; -import { feeManager } from "../../../src/managers/fee"; +import { TimelockTransferBuilder } from "../../../../../packages/crypto/src/builder/transactions/timelock-transfer"; +import { client } from "../../../../../packages/crypto/src/client"; +import { TransactionTypes } from "../../../../../packages/crypto/src/constants"; +import { feeManager } from "../../../../../packages/crypto/src/managers/fee"; import { transactionBuilder } from "./__shared__/transaction-builder"; let builder: TimelockTransferBuilder; diff --git a/packages/crypto/__tests__/builder/transactions/transfer.test.ts b/__tests__/unit/crypto/builder/transactions/transfer.test.ts similarity index 84% rename from packages/crypto/__tests__/builder/transactions/transfer.test.ts rename to __tests__/unit/crypto/builder/transactions/transfer.test.ts index e9cb10996e..db2bf52958 100644 --- a/packages/crypto/__tests__/builder/transactions/transfer.test.ts +++ b/__tests__/unit/crypto/builder/transactions/transfer.test.ts @@ -1,9 +1,9 @@ import "jest-extended"; -import { TransferBuilder } from "../../../src/builder/transactions/transfer"; -import { client } from "../../../src/client"; -import { TransactionTypes } from "../../../src/constants"; -import { crypto } from "../../../src/crypto"; -import { feeManager } from "../../../src/managers/fee"; +import { TransferBuilder } from "../../../../../packages/crypto/src/builder/transactions/transfer"; +import { client } from "../../../../../packages/crypto/src/client"; +import { TransactionTypes } from "../../../../../packages/crypto/src/constants"; +import { crypto } from "../../../../../packages/crypto/src/crypto"; +import { feeManager } from "../../../../../packages/crypto/src/managers/fee"; import { transactionBuilder } from "./__shared__/transaction-builder"; let builder: TransferBuilder; @@ -21,7 +21,7 @@ describe("Transfer Transaction", () => { .vendorField("dummy") .sign("dummy passphrase"); - expect(actual.build().verify()).toBeTrue(); + expect(actual.build().verified).toBeTrue(); expect(actual.verify()).toBeTrue(); }); @@ -33,7 +33,7 @@ describe("Transfer Transaction", () => { .sign("dummy passphrase") .secondSign("dummy passphrase"); - expect(actual.build().verify()).toBeTrue(); + expect(actual.build().verified).toBeTrue(); expect(actual.verify()).toBeTrue(); }); }); @@ -80,7 +80,7 @@ describe("Transfer Transaction", () => { wifTransaction.secondSignWithWif(wif, 170); passphraseTransaction.secondSign(secondPassphrase); - expect(wifTransaction.data.signSignature).toBe(passphraseTransaction.data.signSignature); + expect(wifTransaction.data.secondSignature).toBe(passphraseTransaction.data.secondSignature); }); }); diff --git a/packages/crypto/__tests__/builder/transactions/vote.test.ts b/__tests__/unit/crypto/builder/transactions/vote.test.ts similarity index 84% rename from packages/crypto/__tests__/builder/transactions/vote.test.ts rename to __tests__/unit/crypto/builder/transactions/vote.test.ts index 59bfa77e9f..1f89b3f4de 100644 --- a/packages/crypto/__tests__/builder/transactions/vote.test.ts +++ b/__tests__/unit/crypto/builder/transactions/vote.test.ts @@ -1,9 +1,9 @@ import "jest-extended"; -import { VoteBuilder } from "../../../src/builder/transactions/vote"; -import { client } from "../../../src/client"; -import { TransactionTypes } from "../../../src/constants"; -import { crypto } from "../../../src/crypto"; -import { feeManager } from "../../../src/managers/fee"; +import { VoteBuilder } from "../../../../../packages/crypto/src/builder/transactions/vote"; +import { client } from "../../../../../packages/crypto/src/client"; +import { TransactionTypes } from "../../../../../packages/crypto/src/constants"; +import { crypto } from "../../../../../packages/crypto/src/crypto"; +import { feeManager } from "../../../../../packages/crypto/src/managers/fee"; import { transactionBuilder } from "./__shared__/transaction-builder"; let builder: VoteBuilder; @@ -19,7 +19,7 @@ describe("Vote Transaction", () => { .votesAsset(["+02d0d835266297f15c192be2636eb3fbc30b39b87fc583ff112062ef8ae1a1f2af"]) .sign("dummy passphrase"); - expect(actual.build().verify()).toBeTrue(); + expect(actual.build().verified).toBeTrue(); expect(actual.verify()).toBeTrue(); }); @@ -29,7 +29,7 @@ describe("Vote Transaction", () => { .sign("dummy passphrase") .secondSign("dummy passphrase"); - expect(actual.build().verify()).toBeTrue(); + expect(actual.build().verified).toBeTrue(); expect(actual.verify()).toBeTrue(); }); }); diff --git a/packages/crypto/__tests__/client.test.ts b/__tests__/unit/crypto/client.test.ts similarity index 67% rename from packages/crypto/__tests__/client.test.ts rename to __tests__/unit/crypto/client.test.ts index b8ce67766e..1a45b43207 100644 --- a/packages/crypto/__tests__/client.test.ts +++ b/__tests__/unit/crypto/client.test.ts @@ -1,7 +1,7 @@ import "jest-extended"; -import { transactionBuilder } from "../src/builder"; -import { client, Client } from "../src/client"; -import { configManager, feeManager } from "../src/managers"; +import { transactionBuilder } from "../../../packages/crypto/src/builder"; +import { client, Client } from "../../../packages/crypto/src/client"; +import { configManager, feeManager } from "../../../packages/crypto/src/managers"; describe("Client", () => { it("should be instantiated", () => { diff --git a/packages/crypto/__tests__/constants.test.ts b/__tests__/unit/crypto/constants.test.ts similarity index 95% rename from packages/crypto/__tests__/constants.test.ts rename to __tests__/unit/crypto/constants.test.ts index 7796c76812..a9124d3260 100644 --- a/packages/crypto/__tests__/constants.test.ts +++ b/__tests__/unit/crypto/constants.test.ts @@ -1,5 +1,5 @@ import "jest-extended"; -import * as constants from "../src/constants"; +import * as constants from "../../../packages/crypto/src/constants"; describe("Constants", () => { it("satoshi is valid", () => { diff --git a/packages/crypto/__tests__/crypto/bip38.test.ts b/__tests__/unit/crypto/crypto/bip38.test.ts similarity index 97% rename from packages/crypto/__tests__/crypto/bip38.test.ts rename to __tests__/unit/crypto/crypto/bip38.test.ts index 399f257789..3f9cd7e9a0 100644 --- a/packages/crypto/__tests__/crypto/bip38.test.ts +++ b/__tests__/unit/crypto/crypto/bip38.test.ts @@ -3,9 +3,9 @@ import "jest-extended"; import bs58check from "bs58check"; import ByteBuffer from "bytebuffer"; import wif from "wif"; -import { bip38 } from "../../src/crypto"; +import { bip38 } from "../../../../packages/crypto/src/crypto"; -import * as errors from "../../src/errors"; +import * as errors from "../../../../packages/crypto/src/errors"; import fixtures from "./fixtures/bip38.json"; describe("BIP38", () => { diff --git a/packages/crypto/__tests__/crypto/crypto.test.ts b/__tests__/unit/crypto/crypto/crypto.test.ts similarity index 66% rename from packages/crypto/__tests__/crypto/crypto.test.ts rename to __tests__/unit/crypto/crypto/crypto.test.ts index b3d07a8256..c623fa8b01 100644 --- a/packages/crypto/__tests__/crypto/crypto.test.ts +++ b/__tests__/unit/crypto/crypto/crypto.test.ts @@ -1,122 +1,16 @@ import "jest-extended"; -import { TransactionTypes } from "../../src/constants"; -import { crypto } from "../../src/crypto/crypto"; -import { PublicKeyError, TransactionVersionError } from "../../src/errors"; -import { configManager } from "../../src/managers/config"; -import { ITransactionData } from "../../src/models"; +import { TransactionTypes } from "../../../../packages/crypto/src/constants"; +import { crypto } from "../../../../packages/crypto/src/crypto/crypto"; +import { PublicKeyError, TransactionVersionError } from "../../../../packages/crypto/src/errors"; +import { configManager } from "../../../../packages/crypto/src/managers/config"; +import { ITransactionData } from "../../../../packages/crypto/src/transactions"; const networkMainnet = configManager.getPreset("mainnet"); const networkDevnet = configManager.getPreset("devnet"); beforeEach(() => configManager.setFromPreset("devnet")); -describe("crypto.js", () => { - describe("getBytes", () => { - let bytes = null; - - // it('should return Buffer of simply transaction and buffer must be 292 length', () => { - // const transaction = { - // type: 0, - // amount: 1000, - // fee: 2000, - // recipientId: 'AJWRd23HNEhPLkK1ymMnwnDBX2a7QBZqff', - // timestamp: 141738, - // asset: {}, - // senderPublicKey: '5d036a858ce89f844491762eb89e2bfbd50a4a0a0da658e4b2628b25b117ae09', - // signature: '618a54975212ead93df8c881655c625544bce8ed7ccdfe6f08a42eecfb1adebd051307be5014bb051617baf7815d50f62129e70918190361e5d4dd4796541b0a' - // } - - // bytes = crypto.getBytes(transaction) - // expect(bytes).toBeObject() - // expect(bytes.toString('hex') + transaction.signature).toHaveLength(292) - // }) - - it("should return Buffer of simply transaction and buffer must be 202 length", () => { - const transaction = { - type: 0, - amount: 1000, - fee: 2000, - recipientId: "AJWRd23HNEhPLkK1ymMnwnDBX2a7QBZqff", - timestamp: 141738, - asset: {}, - senderPublicKey: "5d036a858ce89f844491762eb89e2bfbd50a4a0a0da658e4b2628b25b117ae09", - signature: - "618a54975212ead93df8c881655c625544bce8ed7ccdfe6f08a42eecfb1adebd051307be5014bb051617baf7815d50f62129e70918190361e5d4dd4796541b0a", - id: "13987348420913138422", - }; - - bytes = crypto.getBytes(transaction); - expect(bytes).toBeObject(); - expect(bytes.length).toBe(202); - expect(bytes.toString("hex")).toBe( - "00aa2902005d036a858ce89f844491762eb89e2bfbd50a4a0a0da658e4b2628b25b117ae09171dfc69b54c7fe901e91d5a9ab78388645e2427ea00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e803000000000000d007000000000000618a54975212ead93df8c881655c625544bce8ed7ccdfe6f08a42eecfb1adebd051307be5014bb051617baf7815d50f62129e70918190361e5d4dd4796541b0a", - ); - }); - - // it('should return Buffer of transaction with second signature and buffer must be 420 length', () => { - // const transaction = { - // type: 0, - // amount: 1000, - // fee: 2000, - // recipientId: 'AJWRd23HNEhPLkK1ymMnwnDBX2a7QBZqff', - // timestamp: 141738, - // asset: {}, - // senderPublicKey: '5d036a858ce89f844491762eb89e2bfbd50a4a0a0da658e4b2628b25b117ae09', - // signature: '618a54975212ead93df8c881655c625544bce8ed7ccdfe6f08a42eecfb1adebd051307be5014bb051617baf7815d50f62129e70918190361e5d4dd4796541b0a', - // signSignature: '618a54975212ead93df8c881655c625544bce8ed7ccdfe6f08a42eecfb1adebd051307be5014bb051617baf7815d50f62129e70918190361e5d4dd4796541b0a' - // } - - // bytes = crypto.getBytes(transaction) - // expect(bytes).toBeObject() - // expect(bytes.toString('hex') + transaction.signature + transaction.signSignature).toHaveLength(420) - // }) - - it("should return Buffer of transaction with second signature and buffer must be 266 length", () => { - const transaction = { - version: 1, - type: 0, - amount: 1000, - fee: 2000, - recipientId: "AJWRd23HNEhPLkK1ymMnwnDBX2a7QBZqff", - timestamp: 141738, - asset: {}, - senderPublicKey: "5d036a858ce89f844491762eb89e2bfbd50a4a0a0da658e4b2628b25b117ae09", - signature: - "618a54975212ead93df8c881655c625544bce8ed7ccdfe6f08a42eecfb1adebd051307be5014bb051617baf7815d50f62129e70918190361e5d4dd4796541b0a", - signSignature: - "618a54975212ead93df8c881655c625544bce8ed7ccdfe6f08a42eecfb1adebd051307be5014bb051617baf7815d50f62129e70918190361e5d4dd4796541b0a", - id: "13987348420913138422", - }; - - bytes = crypto.getBytes(transaction); - expect(bytes).toBeObject(); - expect(bytes.length).toBe(266); - expect(bytes.toString("hex")).toBe( - "00aa2902005d036a858ce89f844491762eb89e2bfbd50a4a0a0da658e4b2628b25b117ae09171dfc69b54c7fe901e91d5a9ab78388645e2427ea00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e803000000000000d007000000000000618a54975212ead93df8c881655c625544bce8ed7ccdfe6f08a42eecfb1adebd051307be5014bb051617baf7815d50f62129e70918190361e5d4dd4796541b0a618a54975212ead93df8c881655c625544bce8ed7ccdfe6f08a42eecfb1adebd051307be5014bb051617baf7815d50f62129e70918190361e5d4dd4796541b0a", - ); - }); - - it("should throw for unsupported version", () => { - const transaction = { - version: 110, - type: 0, - amount: 1000, - fee: 2000, - recipientId: "AJWRd23HNEhPLkK1ymMnwnDBX2a7QBZqff", - timestamp: 141738, - asset: {}, - senderPublicKey: "5d036a858ce89f844491762eb89e2bfbd50a4a0a0da658e4b2628b25b117ae09", - signature: - "618a54975212ead93df8c881655c625544bce8ed7ccdfe6f08a42eecfb1adebd051307be5014bb051617baf7815d50f62129e70918190361e5d4dd4796541b0a", - signSignature: - "618a54975212ead93df8c881655c625544bce8ed7ccdfe6f08a42eecfb1adebd051307be5014bb051617baf7815d50f62129e70918190361e5d4dd4796541b0a", - id: "13987348420913138422", - }; - - expect(() => crypto.getBytes(transaction)).toThrow(TransactionVersionError); - }); - }); - +describe("crypto.ts", () => { describe("getHash", () => { const transaction = { version: 1, @@ -269,15 +163,10 @@ describe("crypto.js", () => { expect(crypto.verifySecondSignature(transactionWithoutSignature, keys2.publicKey)).toBeFalse(); }); - it("should call this.getHash without parameters skipSignature and skipSecondSignature if transaction.version != 1", () => { + it("should fail this.getHash for transaction version > 1", () => { const transactionV2 = Object.assign({}, transaction, { version: 2 }); - delete transactionV2.secondSignature; - delete transactionV2.signSignature; - // @ts-ignore - const getHashMock = jest.spyOn(crypto, "getHash").mockImplementation(() => ""); - crypto.verifySecondSignature(transactionV2, keys2.publicKey); - expect(getHashMock).toHaveBeenLastCalledWith(transactionV2); + expect(() => crypto.verifySecondSignature(transactionV2, keys2.publicKey)).toThrow(TransactionVersionError); }); }); @@ -298,8 +187,7 @@ describe("crypto.js", () => { it("should return address", () => { const keys = crypto.getKeys("SDgGxWHHQHnpm5sth7MBUoeSw7V7nbimJ1RBU587xkryTh4qe9ov"); - // @ts-ignore - const address = crypto.getAddress(keys.publicKey.toString("hex")); + const address = crypto.getAddress(keys.publicKey); expect(address).toBe("DUMjDrT8mgqGLWZtkCqzvy7yxWr55mBEub"); }); }); diff --git a/packages/crypto/__tests__/crypto/fixtures/bip38.json b/__tests__/unit/crypto/crypto/fixtures/bip38.json similarity index 100% rename from packages/crypto/__tests__/crypto/fixtures/bip38.json rename to __tests__/unit/crypto/crypto/fixtures/bip38.json diff --git a/packages/crypto/__tests__/crypto/fixtures/crypto.json b/__tests__/unit/crypto/crypto/fixtures/crypto.json similarity index 100% rename from packages/crypto/__tests__/crypto/fixtures/crypto.json rename to __tests__/unit/crypto/crypto/fixtures/crypto.json diff --git a/packages/crypto/__tests__/crypto/hash-algorithms.test.ts b/__tests__/unit/crypto/crypto/hash-algorithms.test.ts similarity index 90% rename from packages/crypto/__tests__/crypto/hash-algorithms.test.ts rename to __tests__/unit/crypto/crypto/hash-algorithms.test.ts index 489ba4bea7..b43dab1644 100644 --- a/packages/crypto/__tests__/crypto/hash-algorithms.test.ts +++ b/__tests__/unit/crypto/crypto/hash-algorithms.test.ts @@ -1,6 +1,6 @@ import "jest-extended"; -import { HashAlgorithms } from "../../src/crypto/hash-algorithms"; +import { HashAlgorithms } from "../../../../packages/crypto/src/crypto/hash-algorithms"; import fixtures from "./fixtures/crypto.json"; const buffer = Buffer.from("Hello World"); diff --git a/packages/crypto/__tests__/crypto/hdwallet.test.ts b/__tests__/unit/crypto/crypto/hdwallet.test.ts similarity index 96% rename from packages/crypto/__tests__/crypto/hdwallet.test.ts rename to __tests__/unit/crypto/crypto/hdwallet.test.ts index 1b07c315eb..15a7818b53 100644 --- a/packages/crypto/__tests__/crypto/hdwallet.test.ts +++ b/__tests__/unit/crypto/crypto/hdwallet.test.ts @@ -1,9 +1,9 @@ import "jest-extended"; import bip32 from "bip32"; -import { crypto, HDWallet } from "../../src/crypto"; -import { configManager } from "../../src/managers/config"; -import { mainnet } from "../../src/networks"; +import { crypto, HDWallet } from "../../../../packages/crypto/src/crypto"; +import { configManager } from "../../../../packages/crypto/src/managers/config"; +import { mainnet } from "../../../../packages/crypto/src/networks"; const mnemonic = "sorry hawk one science reject employ museum ride into post machine attack bar seminar myself unhappy faculty differ grain fish chest bird muffin mesh"; diff --git a/packages/crypto/__tests__/crypto/message.test.ts b/__tests__/unit/crypto/crypto/message.test.ts similarity index 92% rename from packages/crypto/__tests__/crypto/message.test.ts rename to __tests__/unit/crypto/crypto/message.test.ts index bae473b8c9..dd8648c7fe 100644 --- a/packages/crypto/__tests__/crypto/message.test.ts +++ b/__tests__/unit/crypto/crypto/message.test.ts @@ -1,7 +1,7 @@ import "jest-extended"; -import { crypto } from "../../src/crypto"; -import { Message } from "../../src/crypto/message"; +import { crypto } from "../../../../packages/crypto/src/crypto"; +import { Message } from "../../../../packages/crypto/src/crypto/message"; const passphrase = "sample passphrase"; const wif = crypto.keysToWIF(crypto.getKeys(passphrase), { wif: 170 }); diff --git a/packages/crypto/__tests__/crypto/slots.test.ts b/__tests__/unit/crypto/crypto/slots.test.ts similarity index 84% rename from packages/crypto/__tests__/crypto/slots.test.ts rename to __tests__/unit/crypto/crypto/slots.test.ts index c1994571e2..1cf845b6e1 100644 --- a/packages/crypto/__tests__/crypto/slots.test.ts +++ b/__tests__/unit/crypto/crypto/slots.test.ts @@ -1,8 +1,8 @@ import "jest-extended"; -import { slots } from "../../src/crypto/slots"; -import { configManager } from "../../src/managers/config"; -import { devnet } from "../../src/networks"; +import { slots } from "../../../../packages/crypto/src/crypto/slots"; +import { configManager } from "../../../../packages/crypto/src/managers/config"; +import { devnet } from "../../../../packages/crypto/src/networks"; beforeEach(() => configManager.setConfig(devnet)); @@ -41,11 +41,11 @@ describe("Slots", () => { describe("beginEpochTime", () => { it("return epoch datetime", () => { - expect(slots.beginEpochTime().toISOString()).toBe("2017-03-21T13:00:00.000Z"); + expect(slots.beginEpochTime().toISO()).toBe("2017-03-21T13:00:00.000Z"); }); it("return epoch unix", () => { - expect(slots.beginEpochTime().unix()).toBe(1490101200); + expect(slots.beginEpochTime().toUnix()).toBe(1490101200); }); }); @@ -88,12 +88,6 @@ describe("Slots", () => { }); }); - describe("getLastSlot", () => { - it("returns last slot", () => { - expect(slots.getLastSlot(1)).toBe(52); - }); - }); - describe("isForgingAllowed", () => { it("returns boolean", () => { expect(slots.isForgingAllowed()).toBeDefined(); diff --git a/__tests__/unit/crypto/fixtures/block.ts b/__tests__/unit/crypto/fixtures/block.ts new file mode 100644 index 0000000000..5df510e82a --- /dev/null +++ b/__tests__/unit/crypto/fixtures/block.ts @@ -0,0 +1,156 @@ +export const dummyBlock = { + id: "17605317082329008056", + version: 0, + height: 1760000, + timestamp: 62222080, + previousBlock: "3112633353705641986", + numberOfTransactions: 7, + totalAmount: "10500000000", + totalFee: "70000000", + reward: "200000000", + payloadLength: 224, + payloadHash: "de56269cae3ab156f6979b94a04c30b82ed7d6f9a97d162583c98215c18c65db", + generatorPublicKey: "03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37", + blockSignature: + "30450221008c59bd2379061ad3539b73284fc0bbb57dbc97efd54f55010ba3f198c04dde7402202e482126b3084c6313c1378d686df92a3e2ef5581323de11e74fe07eeab339f3", + transactions: [ + { + version: 1, + network: 30, + type: 0, + timestamp: 62222080, + senderPublicKey: "03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37", + fee: "10000000", + amount: "1300000000", + expiration: 0, + recipientId: "DBYyh2vXcigrJGUHfvmYxVxEqeH7vomw6x", + signature: + "30440220714c2627f0e9c3bd6bf13b8b4faa5ec2d677694c27f580e2f9e3875bde9bc36f02201c33faacab9eafd799d9ceecaa153e3b87b4cd04535195261fd366e552652549", + id: "188b4d9d95a58e4e18d9ce9db28f2010323b90b5afd36a474d7ae7bf70772bb0", + blockId: "17605317082329008056", + sequence: 0, + }, + { + version: 1, + network: 30, + type: 0, + timestamp: 62222080, + senderPublicKey: "03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37", + fee: "10000000", + amount: "1700000000", + expiration: 0, + recipientId: "DBYyh2vXcigrJGUHfvmYxVxEqeH7vomw6x", + signature: + "3045022100e6039f810684515c0d6b31039040a76c98f3624b6454cb156a0a2137e5f8dba7022001ada19bcca5798e1c7cc8cc39bab5d4019525e3d72a42bd2c4129352b8ead87", + id: "23084f2cc566f6144a8f447bc784de64a0b0646776060482d8550856145e11e2", + blockId: "17605317082329008056", + sequence: 1, + }, + { + version: 1, + network: 30, + type: 0, + timestamp: 62222080, + senderPublicKey: "03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37", + fee: "10000000", + amount: "1500000000", + expiration: 0, + recipientId: "DBYyh2vXcigrJGUHfvmYxVxEqeH7vomw6x", + signature: + "3045022100c2b5ef772b36e468e95ec2e457bfaba7bad0e13b3faf57e229ff5d67a0e017c902202339664595ea5c70ce20e4dd182532f7fa385d86575b0476ff3eda9f9785e1e9", + id: "743ce0a590c2af90e4734db3630b52d7a7cbc2bc228d75ae6409c0b6d184bfad", + blockId: "17605317082329008056", + sequence: 2, + }, + { + version: 1, + network: 30, + type: 0, + timestamp: 62222080, + senderPublicKey: "03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37", + fee: "10000000", + amount: "1600000000", + expiration: 0, + recipientId: "DBYyh2vXcigrJGUHfvmYxVxEqeH7vomw6x", + signature: + "30450221009ceb56688705e6b12000bde726ca123d84982231d7434f059612ff5f987409c602200d908667877c902e7ba35024951046b883e0bce9103d4717928d94ecc958884a", + id: "877780706b62b437913ef4ea30c6e370f8877ef7a5bac58d8cebca83b7e20060", + blockId: "17605317082329008056", + sequence: 3, + }, + { + version: 1, + network: 30, + type: 0, + timestamp: 62222080, + senderPublicKey: "03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37", + fee: "10000000", + amount: "1200000000", + expiration: 0, + recipientId: "DBYyh2vXcigrJGUHfvmYxVxEqeH7vomw6x", + signature: + "30440220464beac6d49943ad8afaac4fdc863c9cd7cf3a84f9938c1d7269ed522298f11a02203581bf180de1966f86d914afeb005e1e818c9213514f96a34e1391c2a08514fa", + id: "947fe8745eeed8fa6e5ad62a8dad29bcf3d50ce001907926c486460d1cc1f1c0", + blockId: "17605317082329008056", + sequence: 4, + }, + { + version: 1, + network: 30, + type: 0, + timestamp: 62222080, + senderPublicKey: "03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37", + fee: "10000000", + amount: "1800000000", + expiration: 0, + recipientId: "DBYyh2vXcigrJGUHfvmYxVxEqeH7vomw6x", + signature: + "3045022100c7b40d7134d909762d18d6bfb7ac1c32be0ee8c047020131f499faea70ca0b2b0220117c0cf026f571f5a85e3ae800a6fd595185076ff38e64c7a4bd14f34e1d4dd1", + id: "98387933d65fabffe2642464d4c7b1ff5fe1fa5a35992f834b0ac145dff462ea", + blockId: "17605317082329008056", + sequence: 5, + }, + { + version: 1, + network: 30, + type: 0, + timestamp: 62222080, + senderPublicKey: "03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37", + fee: "10000000", + amount: "1400000000", + expiration: 0, + recipientId: "DBYyh2vXcigrJGUHfvmYxVxEqeH7vomw6x", + signature: + "304402206a4a8e4e6918fbc15728653b117f51db716aeb04e5ee1de047f80b0476ee4efb02200f486dfaf0def3f3e8636d46ee75a2c07de9714ce4283a25fde9b6218b5e7923", + id: "e93345dd9a87ac4e84d9bfd892dfbfeb02e546e5bd7822168d0f72c7662e6176", + blockId: "17605317082329008056", + sequence: 6, + }, + ], +}; + +export const dummyBlock2 = { + data: dummyBlock, + serialized: + "00000000006fb50300db1a002b324b8b33a85802070000000049d97102000000801d2c040000000000c2eb0b00000000e0000000de56269cae3ab156f6979b94a04c30b82ed7d6f9a97d162583c98215c18c65db03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac3730450221008c59bd2379061ad3539b73284fc0bbb57dbc97efd54f55010ba3f198c04dde7402202e482126b3084c6313c1378d686df92a3e2ef5581323de11e74fe07eeab339f3", + serializedFull: + "00000000006fb50300db1a002b324b8b33a85802070000000049d97102000000801d2c040000000000c2eb0b00000000e0000000de56269cae3ab156f6979b94a04c30b82ed7d6f9a97d162583c98215c18c65db03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac3730450221008c59bd2379061ad3539b73284fc0bbb57dbc97efd54f55010ba3f198c04dde7402202e482126b3084c6313c1378d686df92a3e2ef5581323de11e74fe07eeab339f3990000009a0000009a0000009a000000990000009a00000099000000ff011e00006fb50303287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37809698000000000000006d7c4d00000000000000001e46550551e12d2531ea9d2968696b75f68ae7f29530440220714c2627f0e9c3bd6bf13b8b4faa5ec2d677694c27f580e2f9e3875bde9bc36f02201c33faacab9eafd799d9ceecaa153e3b87b4cd04535195261fd366e552652549ff011e00006fb50303287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac3780969800000000000000f1536500000000000000001e46550551e12d2531ea9d2968696b75f68ae7f2953045022100e6039f810684515c0d6b31039040a76c98f3624b6454cb156a0a2137e5f8dba7022001ada19bcca5798e1c7cc8cc39bab5d4019525e3d72a42bd2c4129352b8ead87ff011e00006fb50303287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37809698000000000000002f685900000000000000001e46550551e12d2531ea9d2968696b75f68ae7f2953045022100c2b5ef772b36e468e95ec2e457bfaba7bad0e13b3faf57e229ff5d67a0e017c902202339664595ea5c70ce20e4dd182532f7fa385d86575b0476ff3eda9f9785e1e9ff011e00006fb50303287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac3780969800000000000000105e5f00000000000000001e46550551e12d2531ea9d2968696b75f68ae7f29530450221009ceb56688705e6b12000bde726ca123d84982231d7434f059612ff5f987409c602200d908667877c902e7ba35024951046b883e0bce9103d4717928d94ecc958884aff011e00006fb50303287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37809698000000000000008c864700000000000000001e46550551e12d2531ea9d2968696b75f68ae7f29530440220464beac6d49943ad8afaac4fdc863c9cd7cf3a84f9938c1d7269ed522298f11a02203581bf180de1966f86d914afeb005e1e818c9213514f96a34e1391c2a08514faff011e00006fb50303287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac3780969800000000000000d2496b00000000000000001e46550551e12d2531ea9d2968696b75f68ae7f2953045022100c7b40d7134d909762d18d6bfb7ac1c32be0ee8c047020131f499faea70ca0b2b0220117c0cf026f571f5a85e3ae800a6fd595185076ff38e64c7a4bd14f34e1d4dd1ff011e00006fb50303287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37809698000000000000004e725300000000000000001e46550551e12d2531ea9d2968696b75f68ae7f295304402206a4a8e4e6918fbc15728653b117f51db716aeb04e5ee1de047f80b0476ee4efb02200f486dfaf0def3f3e8636d46ee75a2c07de9714ce4283a25fde9b6218b5e7923", +}; + +export const dummyBlock3 = { + id: "7242383292164246617", + version: 0, + timestamp: 46583338, + height: 3, + reward: "0", + previousBlock: "17882607875259085966", + numberOfTransactions: 0, + totalAmount: "0", + totalFee: "0", + payloadLength: 0, + payloadHash: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + generatorPublicKey: "038082dad560a22ea003022015e3136b21ef1ffd9f2fd50049026cbe8e2258ca17", + blockSignature: + "304402204087bb1d2c82b9178b02b9b3f285de260cdf0778643064fe6c7aef27321d49520220594c57009c1fca543350126d277c6adeb674c00685a464c3e4bf0d634dc37e39", + createdAt: "2018-09-11T16:48:58.431Z", +}; diff --git a/packages/crypto/__tests__/fixtures/multi-transaction.ts b/__tests__/unit/crypto/fixtures/multi-transaction.ts similarity index 100% rename from packages/crypto/__tests__/fixtures/multi-transaction.ts rename to __tests__/unit/crypto/fixtures/multi-transaction.ts diff --git a/__tests__/unit/crypto/fixtures/transaction.ts b/__tests__/unit/crypto/fixtures/transaction.ts new file mode 100644 index 0000000000..04ac5e5a9c --- /dev/null +++ b/__tests__/unit/crypto/fixtures/transaction.ts @@ -0,0 +1,16 @@ +export const transaction = { + id: "0db225779a7da86bdeaf3a24494fcf3cae7bfe74451fdc3bcb866f62319badeb", + timestamp: 60093626, + version: 1, + type: 0, + fee: 10000000, + amount: 1000, + expiration: 0, + network: 30, + recipientId: "DJLxkgm7JMortrGVh1ZrvDH39XALWLa83e", + senderPublicKey: "03aacac6c98daaf3d433fe90e9295ce380916946f850bcdc6f6880ae6503ca1e40", + vendorField: "Test", + vendorFieldHex: "54657374", + signature: + "304402203f6d69c0d066c6392c397ae6570f2e0296bfd3e6087f11b7243a71c18436eaa90220201e6ed6cc7e6e2a176ba92d8cabf239d0d87b22597cd6d7b90d9003379cb436", +}; diff --git a/packages/crypto/__tests__/identities/address.test.ts b/__tests__/unit/crypto/identities/address.test.ts similarity index 84% rename from packages/crypto/__tests__/identities/address.test.ts rename to __tests__/unit/crypto/identities/address.test.ts index a614954c2c..67a496858c 100644 --- a/packages/crypto/__tests__/identities/address.test.ts +++ b/__tests__/unit/crypto/identities/address.test.ts @@ -1,8 +1,8 @@ import "jest-extended"; -import { PublicKeyError } from "../../src/errors"; -import { Address } from "../../src/identities/address"; -import { Keys } from "../../src/identities/keys"; +import { PublicKeyError } from "../../../../packages/crypto/src/errors"; +import { Address } from "../../../../packages/crypto/src/identities/address"; +import { Keys } from "../../../../packages/crypto/src/identities/keys"; import { data, passphrase } from "./fixture.json"; describe("Identities - Address", () => { diff --git a/packages/crypto/__tests__/identities/fixture.json b/__tests__/unit/crypto/identities/fixture.json similarity index 100% rename from packages/crypto/__tests__/identities/fixture.json rename to __tests__/unit/crypto/identities/fixture.json diff --git a/packages/crypto/__tests__/identities/keys.test.ts b/__tests__/unit/crypto/identities/keys.test.ts similarity index 92% rename from packages/crypto/__tests__/identities/keys.test.ts rename to __tests__/unit/crypto/identities/keys.test.ts index 25aa82ca44..1790e1cc08 100644 --- a/packages/crypto/__tests__/identities/keys.test.ts +++ b/__tests__/unit/crypto/identities/keys.test.ts @@ -1,9 +1,9 @@ import "jest-extended"; import wif from "wif"; -import { NetworkVersionError } from "../../src/errors"; -import { Address } from "../../src/identities/address"; -import { Keys } from "../../src/identities/keys"; +import { NetworkVersionError } from "../../../../packages/crypto/src/errors"; +import { Address } from "../../../../packages/crypto/src/identities/address"; +import { Keys } from "../../../../packages/crypto/src/identities/keys"; import { data, passphrase } from "./fixture.json"; describe("Identities - Keys", () => { diff --git a/packages/crypto/__tests__/identities/private-key.test.ts b/__tests__/unit/crypto/identities/private-key.test.ts similarity index 84% rename from packages/crypto/__tests__/identities/private-key.test.ts rename to __tests__/unit/crypto/identities/private-key.test.ts index 249efc9342..a15f256f68 100644 --- a/packages/crypto/__tests__/identities/private-key.test.ts +++ b/__tests__/unit/crypto/identities/private-key.test.ts @@ -1,6 +1,6 @@ import "jest-extended"; -import { PrivateKey } from "../../src/identities/private-key"; +import { PrivateKey } from "../../../../packages/crypto/src/identities/private-key"; import { data, passphrase } from "./fixture.json"; describe("Identities - Private Key", () => { diff --git a/packages/crypto/__tests__/identities/public-key.test.ts b/__tests__/unit/crypto/identities/public-key.test.ts similarity index 90% rename from packages/crypto/__tests__/identities/public-key.test.ts rename to __tests__/unit/crypto/identities/public-key.test.ts index b63be2f612..0ae334c293 100644 --- a/packages/crypto/__tests__/identities/public-key.test.ts +++ b/__tests__/unit/crypto/identities/public-key.test.ts @@ -1,6 +1,6 @@ import "jest-extended"; -import { PublicKey } from "../../src/identities/public-key"; +import { PublicKey } from "../../../../packages/crypto/src/identities/public-key"; import { data, passphrase } from "./fixture.json"; describe("Identities - Public Key", () => { diff --git a/packages/crypto/__tests__/identities/wif.test.ts b/__tests__/unit/crypto/identities/wif.test.ts similarity index 79% rename from packages/crypto/__tests__/identities/wif.test.ts rename to __tests__/unit/crypto/identities/wif.test.ts index 5e6394809b..798776949b 100644 --- a/packages/crypto/__tests__/identities/wif.test.ts +++ b/__tests__/unit/crypto/identities/wif.test.ts @@ -1,6 +1,6 @@ import "jest-extended"; -import { WIF } from "../../src/identities/wif"; +import { WIF } from "../../../../packages/crypto/src/identities/wif"; import { data, passphrase } from "./fixture.json"; describe("Identities - WIF", () => { diff --git a/__tests__/unit/crypto/managers/config.test.ts b/__tests__/unit/crypto/managers/config.test.ts new file mode 100644 index 0000000000..f775a80ca6 --- /dev/null +++ b/__tests__/unit/crypto/managers/config.test.ts @@ -0,0 +1,100 @@ +import "jest-extended"; + +import { TransactionTypes } from "../../../../packages/crypto/src/constants"; +import { configManager } from "../../../../packages/crypto/src/managers/config"; +import { feeManager } from "../../../../packages/crypto/src/managers/fee"; +import { devnet, mainnet } from "../../../../packages/crypto/src/networks"; + +beforeEach(() => configManager.setConfig(devnet)); + +describe("Configuration", () => { + it("should be instantiated", () => { + expect(configManager).toBeObject(); + }); + + it("should be set on runtime", () => { + configManager.setConfig(mainnet); + + expect(configManager.all()).toContainAllKeys([ + ...Object.keys(mainnet.network), + ...["milestones", "exceptions", "genesisBlock"], + ]); + }); + + it('key should be "set"', () => { + configManager.set("key", "value"); + + expect(configManager.get("key")).toBe("value"); + }); + + it('key should be "get"', () => { + expect(configManager.get("nethash")).toBe("2a44f340d76ffc3df204c5f38cd355b7496c9065a1ade2ef92071436bd72e867"); + }); + + it("should build milestones", () => { + expect(configManager.milestones).toEqual(devnet.milestones); + }); + + it("should build fees", () => { + const feesStatic = devnet.milestones[0].fees.staticFees; + + expect(feeManager.get(TransactionTypes.Transfer)).toEqual(feesStatic.transfer); + expect(feeManager.get(TransactionTypes.SecondSignature)).toEqual(feesStatic.secondSignature); + expect(feeManager.get(TransactionTypes.DelegateRegistration)).toEqual(feesStatic.delegateRegistration); + expect(feeManager.get(TransactionTypes.Vote)).toEqual(feesStatic.vote); + expect(feeManager.get(TransactionTypes.MultiSignature)).toEqual(feesStatic.multiSignature); + expect(feeManager.get(TransactionTypes.Ipfs)).toEqual(feesStatic.ipfs); + expect(feeManager.get(TransactionTypes.TimelockTransfer)).toEqual(feesStatic.timelockTransfer); + expect(feeManager.get(TransactionTypes.MultiPayment)).toEqual(feesStatic.multiPayment); + expect(feeManager.get(TransactionTypes.DelegateResignation)).toEqual(feesStatic.delegateResignation); + }); + + it("should update fees on milestone change", () => { + devnet.milestones.push({ + height: 100_000_000, + fees: { staticFees: { transfer: 1234 } }, + } as any); + + configManager.setHeight(100_000_000); + + let { staticFees } = configManager.getMilestone().fees; + expect(feeManager.get(TransactionTypes.Transfer)).toEqual(1234); + expect(feeManager.get(TransactionTypes.SecondSignature)).toEqual(staticFees.secondSignature); + expect(feeManager.get(TransactionTypes.DelegateRegistration)).toEqual(staticFees.delegateRegistration); + expect(feeManager.get(TransactionTypes.Vote)).toEqual(staticFees.vote); + expect(feeManager.get(TransactionTypes.MultiSignature)).toEqual(staticFees.multiSignature); + expect(feeManager.get(TransactionTypes.Ipfs)).toEqual(staticFees.ipfs); + expect(feeManager.get(TransactionTypes.TimelockTransfer)).toEqual(staticFees.timelockTransfer); + expect(feeManager.get(TransactionTypes.MultiPayment)).toEqual(staticFees.multiPayment); + expect(feeManager.get(TransactionTypes.DelegateResignation)).toEqual(staticFees.delegateResignation); + + configManager.setHeight(1); + staticFees = configManager.getMilestone().fees.staticFees; + expect(feeManager.get(TransactionTypes.Transfer)).toEqual(staticFees.transfer); + expect(feeManager.get(TransactionTypes.SecondSignature)).toEqual(staticFees.secondSignature); + expect(feeManager.get(TransactionTypes.DelegateRegistration)).toEqual(staticFees.delegateRegistration); + expect(feeManager.get(TransactionTypes.Vote)).toEqual(staticFees.vote); + expect(feeManager.get(TransactionTypes.MultiSignature)).toEqual(staticFees.multiSignature); + expect(feeManager.get(TransactionTypes.Ipfs)).toEqual(staticFees.ipfs); + expect(feeManager.get(TransactionTypes.TimelockTransfer)).toEqual(staticFees.timelockTransfer); + expect(feeManager.get(TransactionTypes.MultiPayment)).toEqual(staticFees.multiPayment); + expect(feeManager.get(TransactionTypes.DelegateResignation)).toEqual(staticFees.delegateResignation); + + devnet.milestones.pop(); + }); + + it("should get milestone for height", () => { + expect(configManager.getMilestone(21600)).toEqual(devnet.milestones[2]); + }); + + it("should get milestone for this.height if height is not provided as parameter", () => { + configManager.setHeight(21600); + expect(configManager.getMilestone()).toEqual(devnet.milestones[2]); + }); + + it("should set the height", () => { + configManager.setHeight(21600); + + expect(configManager.getHeight()).toEqual(21600); + }); +}); diff --git a/packages/crypto/__tests__/managers/fee.test.ts b/__tests__/unit/crypto/managers/fee.test.ts similarity index 82% rename from packages/crypto/__tests__/managers/fee.test.ts rename to __tests__/unit/crypto/managers/fee.test.ts index 2af984f3c4..f879c562c5 100644 --- a/packages/crypto/__tests__/managers/fee.test.ts +++ b/__tests__/unit/crypto/managers/fee.test.ts @@ -1,8 +1,8 @@ import "jest-extended"; -import { TransactionTypes } from "../../src/constants"; -import { feeManager } from "../../src/managers/fee"; -import { ITransactionData } from "../../src/models"; +import { TransactionTypes } from "../../../../packages/crypto/src/constants"; +import { feeManager } from "../../../../packages/crypto/src/managers/fee"; +import { ITransactionData } from "../../../../packages/crypto/src/transactions"; describe("Fee Manager", () => { it("should be instantiated", () => { diff --git a/packages/crypto/__tests__/managers/network.test.ts b/__tests__/unit/crypto/managers/network.test.ts similarity index 75% rename from packages/crypto/__tests__/managers/network.test.ts rename to __tests__/unit/crypto/managers/network.test.ts index 6cf99b9f34..e77cb32628 100644 --- a/packages/crypto/__tests__/managers/network.test.ts +++ b/__tests__/unit/crypto/managers/network.test.ts @@ -1,7 +1,7 @@ import "jest-extended"; -import { NetworkManager } from "../../src/managers/network"; -import * as networks from "../../src/networks"; +import { NetworkManager } from "../../../../packages/crypto/src/managers/network"; +import * as networks from "../../../../packages/crypto/src/networks"; describe("Network Manager", () => { it("should be instantiated", () => { diff --git a/packages/crypto/__tests__/models/block.test.ts b/__tests__/unit/crypto/models/block.test.ts similarity index 76% rename from packages/crypto/__tests__/models/block.test.ts rename to __tests__/unit/crypto/models/block.test.ts index 505f5716b2..5c64f56ef8 100644 --- a/packages/crypto/__tests__/models/block.test.ts +++ b/__tests__/unit/crypto/models/block.test.ts @@ -1,14 +1,12 @@ import "jest-extended"; -import { generators } from "@arkecosystem/core-test-utils"; -const { generateTransfers } = generators; - import ByteBuffer from "bytebuffer"; -import { configManager } from "../../src"; -import { slots } from "../../src/crypto"; -import { Block, Delegate } from "../../src/models"; -import { testnet } from "../../src/networks"; -import { Bignum } from "../../src/utils"; +import { configManager, NetworkName } from "../../../../packages/crypto/src"; +import { slots } from "../../../../packages/crypto/src/crypto"; +import { Block, Delegate } from "../../../../packages/crypto/src/models"; +import { testnet } from "../../../../packages/crypto/src/networks"; +import { Bignum } from "../../../../packages/crypto/src/utils"; +import { TransactionFactory } from "../../../helpers/transaction-factory"; import { dummyBlock, dummyBlock2 } from "../fixtures/block"; const { outlookTable } = configManager.getPreset("mainnet").exceptions; @@ -59,18 +57,15 @@ describe("Models - Block", () => { expect(block.verification.verified).toBeFalse(); }); - it("should fail to verify a block with no previous block", () => { - const deserializeFunction = Block.deserialize; - jest.spyOn(Block, "deserialize").mockImplementation(ser => { - const deser = deserializeFunction(ser); - return Object.assign(deser, { previousBlock: undefined }); - }); + it("should fail to verify a block with an invalid previous block", () => { + const previousBlockBackup = dummyBlock.previousBlock; + dummyBlock.previousBlock = "0000000000000000000"; const block = new Block(dummyBlock); expect(block.verification.verified).toBeFalse(); - expect(block.verification.errors).toEqual(["Invalid previous block", "Failed to verify block signature"]); + expect(block.verification.errors).toContain("Failed to verify block signature"); - jest.restoreAllMocks(); + dummyBlock.previousBlock = previousBlockBackup; }); it("should fail to verify a block with incorrect timestamp", () => { @@ -78,7 +73,7 @@ describe("Models - Block", () => { const block = new Block(dummyBlock); expect(block.verification.verified).toBeFalse(); - expect(block.verification.errors).toEqual(["Invalid block timestamp"]); + expect(block.verification.errors).toContain("Invalid block timestamp"); jest.restoreAllMocks(); }); @@ -94,21 +89,17 @@ describe("Models - Block", () => { }, reward: new Bignum(0), }; - const transactions = generateTransfers( - "testnet", - "super cool passphrase", - "DB4gFuDztmdGALMb8i1U4Z4R5SktxpNTAY", - 10, - 210, - true, - ); + const transactions = TransactionFactory.transfer("DB4gFuDztmdGALMb8i1U4Z4R5SktxpNTAY", 10) + .withNetwork("devnet") + .withPassphrase("super cool passphrase") + .create(210); const blockForged = delegate.forge(transactions, optionsDefault); const block = new Block(blockForged.toJson()); expect(block.verification.verified).toBeFalse(); - expect(block.verification.errors).toEqual(["Transactions length is too high"]); + expect(block.verification.errors).toContain("Transactions length is too high"); }); it("should fail to verify a block with duplicate transactions", () => { @@ -122,21 +113,17 @@ describe("Models - Block", () => { }, reward: new Bignum(0), }; - const transactions = generateTransfers( - "testnet", - "super cool passphrase", - "DB4gFuDztmdGALMb8i1U4Z4R5SktxpNTAY", - 10, - 1, - true, - ); + const transactions = TransactionFactory.transfer("DB4gFuDztmdGALMb8i1U4Z4R5SktxpNTAY", 10) + .withNetwork("devnet") + .withPassphrase("super cool passphrase") + .create(); const blockForged = delegate.forge([transactions[0], transactions[0]], optionsDefault); const block = new Block(blockForged.toJson()); expect(block.verification.verified).toBeFalse(); - expect(block.verification.errors).toEqual([`Encountered duplicate transaction: ${transactions[0].id}`]); + expect(block.verification.errors).toContain(`Encountered duplicate transaction: ${transactions[0].id}`); }); it("should fail to verify a block with too large payload", () => { @@ -147,18 +134,19 @@ describe("Models - Block", () => { maxPayload: 0, }, reward: 200000000, + vendorFieldLength: 64, })); const block = new Block(dummyBlock); expect(block.verification.verified).toBeFalse(); - expect(block.verification.errors).toEqual(["Payload is too large"]); + expect(block.verification.errors).toContain("Payload is too large"); jest.restoreAllMocks(); }); it("should fail to verify a block if error is thrown", () => { const errorMessage = "Very very, very bad error"; - jest.spyOn(configManager, "getMilestone").mockImplementation(height => { + jest.spyOn(slots, "getSlotNumber").mockImplementation(height => { throw errorMessage; }); const block = new Block(dummyBlock); @@ -356,30 +344,28 @@ describe("Models - Block", () => { }); }); - describe("getIdFromSerialized", () => { - it("should get the id from serialized buffer", () => { - const serialized = Block.serialize(data); - - expect(Block.getIdFromSerialized(serialized)).toBe(data.id); - }); - }); - describe("serializeFull", () => { describe("genesis block", () => { - describe.each([["mainnet", 468048], ["devnet", 14492], ["testnet", 46488]])("%s", (network, length) => { - const genesis = require(`@arkecosystem/crypto/src/networks/${network}/genesisBlock.json`); - const serialized = Block.serializeFull(genesis).toString("hex"); - const genesisBlock = new Block(Block.deserialize(serialized)); - expect(serialized).toHaveLength(length); - expect(genesisBlock.verifySignature()).toBeTrue(); - }); + describe.each([["mainnet", 468048], ["devnet", 14492], ["testnet", 46488]])( + "%s", + (network: NetworkName, length: number) => { + configManager.setFromPreset(network); + const genesis = require(`@arkecosystem/crypto/src/networks/${network}/genesisBlock.json`); + const serialized = Block.serializeFull(genesis).toString("hex"); + const genesisBlock = new Block(Block.deserialize(serialized)); + expect(serialized).toHaveLength(length); + expect(genesisBlock.verifySignature()).toBeTrue(); + }, + ); + + configManager.setFromPreset("devnet"); }); describe("should validate hash", () => { // @ts-ignore const s = Block.serializeFull(dummyBlock).toString("hex"); const serialized = - "0000000078d07901593a22002b324b8b33a85802070000007c5c3b0000000000801d2c040000000000c2eb0b00000000e00000003784b953afcf936bdffd43fdf005b5732b49c1fc6b11e195c364c20b2eb06282020f5df4d2bc736d12ce43af5b1663885a893fade7ee5e62b3cc59315a63e6a3253045022100eee6c37b5e592e99811d588532726353592923f347c701d52912e6d583443e400220277ffe38ad31e216ba0907c4738fed19b2071246b150c72c0a52bae4477ebe29ff000000fe00000000010000ff000000ff000000ff000000ff000000ff011e0062d079010265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c080969800000000001f476f6f736520566f746572202d205472756520426c6f636b20576569676874f07a080000000000000000001e40fad23d21da7a4fd4decb5c49726ea22f5e6bf6304402204f12469157b19edd06ba25fcad3d4a5ef5b057c23f9e02de4641e6f8eef0553e022010121ab282f83efe1043de9c16bbf2c6845a03684229a0d7c965ffb9abdfb97830450221008327862f0b9178d6665f7d6674978c5caf749649558d814244b1c66cdf945c40022015918134ef01fed3fe2a2efde3327917731344332724522c75c2799a14f78717ff011e0060d079010265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c080969800000000001f476f6f736520566f746572202d205472756520426c6f636b20576569676874e67a080000000000000000001e79c579fb08f448879c22fe965906b4e3b88d02ed304402205f82feb8c5d1d79c565c2ff7badb93e4c9827b132d135dda11cb25427d4ef8ac02205ff136f970533c4ec4c7d0cd1ea7e02d7b62629b66c6c93265f608d7f2389727304402207e912031fcc700d8a55fbc415993302a0d8e6aea128397141b640b6dba52331702201fd1ad3984e42af44f548907add6cb7ad72ca0070c8cc1d8dc9bbda208c56bd9ff011e0064d079010265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c080969800000000001f476f6f736520566f746572202d205472756520426c6f636b20576569676874fa7a080000000000000000001e84fee45dde2b11525afe192a2e991d014ff93a36304502210083216e6969e068770e6d2fe5c244881002309df84d20290ddf3f858967ed010202202a479b3da5080ea475d310ff13494654b42db75886a8808bd211b4bdb9146a7a3045022100e1dcab3406bbeb968146a4a391909ce41df9b71592a753b001e7c2ee1d382c5102202a74aeafd4a152ec61854636fbae829c41f1416c1e0637a0809408394973099fff011e0061d079010265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c080969800000000001f476f6f736520566f746572202d205472756520426c6f636b20576569676874e67a080000000000000000001e1d69583ede5ee82d220e74bffb36bae2ce762dfb3045022100cd4fa9855227be11e17201419dacfbbd5d9946df8d6792a9488160025693821402207fb83969bad6a26959f437b5bb88e255b0a48eb04964d0c0d29f7ee94bd15e11304402205f50c2991a17743d17ffbb09159cadc35a3f848044261842879ccf5be9d81c5e022023bf21c32fb6e94494104f15f8d3a942ab120d0abd6fb4c93790b68e1b307a79ff011e0062d079010265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c080969800000000001f476f6f736520566f746572202d205472756520426c6f636b20576569676874f07a080000000000000000001e56f9a37a859f4f84e93ce7593e809b15a524db2930450221009c792062e13399ac6756b2e9f137194d06e106360ac0f3e24e55c7249cee0b3602205dc1d9c76d0451d1cb5a2396783a13e6d2d790ccfd49291e3d0a78349f7ea0e830440220083ba8a9af49b8be6e93794d71ec43ffc96a158375810e5d9f2478e71655315b0220278402ecaa1d224dab9f0f3b28295bbaea339c85c7400edafdc49df87439fc64ff011e0063d079010265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c080969800000000001f476f6f736520566f746572202d205472756520426c6f636b20576569676874f07a080000000000000000001e0232a083c16aba4362dddec1b3050ffdd6d43f2e3044022063c65263e42be02bd9831b375c1d76a88332f00ed0557ecc1e7d2375ca40070902206797b5932c0bad68444beb5a38daa7cadf536ee2144e0d9777b812284d14374e3045022100b04da6692f75d43229ffd8486c1517e8952d38b4c03dfac38b6b360190a5c33e0220776622e5f09f92a1258b4a011f22181c977b622b8d1bbb2f83b42f4126d00739ff011e0060d079010265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c080969800000000001f476f6f736520566f746572202d205472756520426c6f636b20576569676874e67a080000000000000000001eccc4fce0dc95f9951ee40c09a7ae807746cf51403045022100d4513c3608c2072e38e7a0e3bb8daf2cd5f7cc6fec9a5570dccd1eda696c591902202ecbbf3c9d0757be7b23c8b1cc6481c51600d158756c47fcb6f4a7f4893e31c4304402201fed4858d0806dd32220960900a871dd2f60e1f623af75feef9b1034a9a0a46402205a29b27c63fcc3e1ee1e77ecbbf4dd6e7db09901e7a09b9fd490cd68d62392cb"; + "00000000006fb50300db1a002b324b8b33a85802070000000049d97102000000801d2c040000000000c2eb0b00000000e0000000de56269cae3ab156f6979b94a04c30b82ed7d6f9a97d162583c98215c18c65db03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac3730450221008c59bd2379061ad3539b73284fc0bbb57dbc97efd54f55010ba3f198c04dde7402202e482126b3084c6313c1378d686df92a3e2ef5581323de11e74fe07eeab339f3990000009a0000009a0000009a000000990000009a00000099000000ff011e00006fb50303287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37809698000000000000006d7c4d00000000000000001e46550551e12d2531ea9d2968696b75f68ae7f29530440220714c2627f0e9c3bd6bf13b8b4faa5ec2d677694c27f580e2f9e3875bde9bc36f02201c33faacab9eafd799d9ceecaa153e3b87b4cd04535195261fd366e552652549ff011e00006fb50303287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac3780969800000000000000f1536500000000000000001e46550551e12d2531ea9d2968696b75f68ae7f2953045022100e6039f810684515c0d6b31039040a76c98f3624b6454cb156a0a2137e5f8dba7022001ada19bcca5798e1c7cc8cc39bab5d4019525e3d72a42bd2c4129352b8ead87ff011e00006fb50303287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37809698000000000000002f685900000000000000001e46550551e12d2531ea9d2968696b75f68ae7f2953045022100c2b5ef772b36e468e95ec2e457bfaba7bad0e13b3faf57e229ff5d67a0e017c902202339664595ea5c70ce20e4dd182532f7fa385d86575b0476ff3eda9f9785e1e9ff011e00006fb50303287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac3780969800000000000000105e5f00000000000000001e46550551e12d2531ea9d2968696b75f68ae7f29530450221009ceb56688705e6b12000bde726ca123d84982231d7434f059612ff5f987409c602200d908667877c902e7ba35024951046b883e0bce9103d4717928d94ecc958884aff011e00006fb50303287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37809698000000000000008c864700000000000000001e46550551e12d2531ea9d2968696b75f68ae7f29530440220464beac6d49943ad8afaac4fdc863c9cd7cf3a84f9938c1d7269ed522298f11a02203581bf180de1966f86d914afeb005e1e818c9213514f96a34e1391c2a08514faff011e00006fb50303287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac3780969800000000000000d2496b00000000000000001e46550551e12d2531ea9d2968696b75f68ae7f2953045022100c7b40d7134d909762d18d6bfb7ac1c32be0ee8c047020131f499faea70ca0b2b0220117c0cf026f571f5a85e3ae800a6fd595185076ff38e64c7a4bd14f34e1d4dd1ff011e00006fb50303287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37809698000000000000004e725300000000000000001e46550551e12d2531ea9d2968696b75f68ae7f295304402206a4a8e4e6918fbc15728653b117f51db716aeb04e5ee1de047f80b0476ee4efb02200f486dfaf0def3f3e8636d46ee75a2c07de9714ce4283a25fde9b6218b5e7923"; const block1 = new Block(dummyBlock); const block2 = new Block(Block.deserialize(serialized)); @@ -390,6 +376,8 @@ describe("Models - Block", () => { }); describe("should reorder correctly transactions in deserialization", () => { + configManager.setFromPreset("mainnet"); + const issue = { version: 0, timestamp: 25029544, @@ -444,6 +432,8 @@ describe("Models - Block", () => { const block = new Block(issue); expect(block.data.id).toBe(issue.id); expect(block.transactions[0].id).toBe(issue.transactions[1].id); + + configManager.setFromPreset("devnet"); }); describe("v1 fix", () => { diff --git a/packages/crypto/__tests__/models/delegate.test.ts b/__tests__/unit/crypto/models/delegate.test.ts similarity index 92% rename from packages/crypto/__tests__/models/delegate.test.ts rename to __tests__/unit/crypto/models/delegate.test.ts index c96b8ea776..4709c1a315 100644 --- a/packages/crypto/__tests__/models/delegate.test.ts +++ b/__tests__/unit/crypto/models/delegate.test.ts @@ -1,16 +1,11 @@ import "jest-extended"; -import { generators } from "@arkecosystem/core-test-utils"; -const { generateSecondSignature } = generators; - -import { SATOSHI } from "../../src/constants"; -import { configManager } from "../../src/managers/config"; -import { ITransactionData } from "../../src/models"; -import { Delegate } from "../../src/models/delegate"; -import { Wallet } from "../../src/models/wallet"; -import { INetwork, testnet } from "../../src/networks"; -import { Bignum } from "../../src/utils"; -import { sortTransactions } from "../../src/utils"; +import { Delegate } from "../../../../packages/crypto/src/models/delegate"; +import { INetwork, testnet } from "../../../../packages/crypto/src/networks"; +import { ITransactionData } from "../../../../packages/crypto/src/transactions"; +import { Bignum } from "../../../../packages/crypto/src/utils"; +import { sortTransactions } from "../../../../packages/crypto/src/utils"; +import { TransactionFactory } from "../../../helpers/transaction-factory"; const dummy = { plainPassphrase: "clay harbor enemy utility margin pretty hub comic piece aerobic umbrella acquire", @@ -161,7 +156,9 @@ describe("Models - Delegate", () => { }, reward: new Bignum(0), }; - const transactions = generateSecondSignature("testnet", dummy.plainPassphrase, 1, true); + const transactions = TransactionFactory.secondSignature(dummy.plainPassphrase) + .withPassphrase(dummy.plainPassphrase) + .create(); const expectedBlockData = { generatorPublicKey: dummy.publicKey, timestamp: optionsDefault.timestamp, diff --git a/packages/crypto/__tests__/handlers/transactions/__fixtures__/transaction.ts b/__tests__/unit/crypto/transactions/__fixtures__/transaction.ts similarity index 90% rename from packages/crypto/__tests__/handlers/transactions/__fixtures__/transaction.ts rename to __tests__/unit/crypto/transactions/__fixtures__/transaction.ts index 04930c3a28..bbb7a84156 100644 --- a/packages/crypto/__tests__/handlers/transactions/__fixtures__/transaction.ts +++ b/__tests__/unit/crypto/transactions/__fixtures__/transaction.ts @@ -1,4 +1,4 @@ -import { Bignum } from "../../../../src/utils"; +import { Bignum } from "../../../../../packages/crypto/src/utils"; export const transaction = { version: 1, diff --git a/packages/crypto/__tests__/handlers/transactions/__fixtures__/wallet.ts b/__tests__/unit/crypto/transactions/__fixtures__/wallet.ts similarity index 59% rename from packages/crypto/__tests__/handlers/transactions/__fixtures__/wallet.ts rename to __tests__/unit/crypto/transactions/__fixtures__/wallet.ts index f23cd4d19d..f637cd8689 100644 --- a/packages/crypto/__tests__/handlers/transactions/__fixtures__/wallet.ts +++ b/__tests__/unit/crypto/transactions/__fixtures__/wallet.ts @@ -1,7 +1,8 @@ -import { Bignum } from "../../../../src/utils"; +import { Wallet } from "@arkecosystem/core-database"; +import { Bignum } from "../../../../../packages/crypto/src/utils"; export const wallet = { address: "DTRdbaUW3RQQSL5By4G43JVaeHiqfVp9oh", balance: new Bignum(4527654310), publicKey: "034da006f958beba78ec54443df4a3f52237253f7ae8cbdb17dccf3feaa57f3126", -}; +} as Wallet; diff --git a/packages/crypto/__tests__/deserializers/block.test.ts b/__tests__/unit/crypto/transactions/deserializers/block.test.ts similarity index 66% rename from packages/crypto/__tests__/deserializers/block.test.ts rename to __tests__/unit/crypto/transactions/deserializers/block.test.ts index c9c848d76c..76963a48b9 100644 --- a/packages/crypto/__tests__/deserializers/block.test.ts +++ b/__tests__/unit/crypto/transactions/deserializers/block.test.ts @@ -1,34 +1,25 @@ -import { configManager } from "../../src/managers"; -import { dummyBlock2, dummyBlock3 } from "../fixtures/block"; - -let BlockDeserializer; -let BlockSerializer; +import { configManager } from "../../../../../packages/crypto/src/managers"; +import { BlockDeserializer } from "../../../../../packages/crypto/src/transactions/deserializers"; +import { BlockSerializer } from "../../../../../packages/crypto/src/transactions/serializers"; +import { dummyBlock2, dummyBlock3 } from "../../fixtures/block"; describe("block deserializer", () => { describe("deserialize", () => { it("should get block id from outlook table", () => { const outlookTableBlockId = "123456"; - const getPresetOrig = configManager.getPreset; - jest.spyOn(configManager, "getPreset").mockImplementation(network => { - const preset = getPresetOrig(network); - preset.exceptions.outlookTable = { - [dummyBlock3.id]: outlookTableBlockId, - }; - return preset; - }); - BlockDeserializer = require("../../src/deserializers").BlockDeserializer; - BlockSerializer = require("../../src/serializers").BlockSerializer; + configManager.config.exceptions.outlookTable = { [dummyBlock3.id]: outlookTableBlockId }; const deserialized = BlockDeserializer.deserialize( BlockSerializer.serialize(dummyBlock3).toString("hex"), true, - ); + ).data; expect(deserialized.id).toEqual(outlookTableBlockId); + delete configManager.config.exceptions.outlookTable; }); it("should correctly deserialize a block", () => { - const deserialized = BlockDeserializer.deserialize(dummyBlock2.serializedFull); + const deserialized = BlockDeserializer.deserialize(dummyBlock2.serializedFull).data; const blockFields = [ "id", @@ -59,14 +50,13 @@ describe("block deserializer", () => { "fee", "amount", "recipientId", - "vendorField", "signature", ]; deserialized.transactions.forEach(tx => { - const dummyBlockTx = dummyBlock2.data.transactions.find(dummyTx => dummyTx.id === tx.data.id); + const dummyBlockTx = dummyBlock2.data.transactions.find(dummyTx => dummyTx.id === tx.id); expect(dummyBlockTx).toBeDefined(); transactionFields.forEach(field => { - expect(tx.data[field].toString()).toEqual(dummyBlockTx[field].toString()); + expect(tx[field].toString()).toEqual(dummyBlockTx[field].toString()); }); }); }); diff --git a/__tests__/unit/crypto/transactions/deserializers/transaction.test.ts b/__tests__/unit/crypto/transactions/deserializers/transaction.test.ts new file mode 100644 index 0000000000..d511eac0e8 --- /dev/null +++ b/__tests__/unit/crypto/transactions/deserializers/transaction.test.ts @@ -0,0 +1,456 @@ +import "jest-extended"; + +import ByteBuffer from "bytebuffer"; +import { client } from "../../../../../packages/crypto/src/client"; +import { + TransactionSchemaError, + TransactionVersionError, + UnkownTransactionError, +} from "../../../../../packages/crypto/src/errors"; +import { configManager } from "../../../../../packages/crypto/src/managers"; +import { Transaction } from "../../../../../packages/crypto/src/transactions"; +import { TransactionDeserializer } from "../../../../../packages/crypto/src/transactions/deserializers"; +import { TransactionSerializer } from "../../../../../packages/crypto/src/transactions/serializers"; +import { Bignum } from "../../../../../packages/crypto/src/utils"; + +describe("Transaction serializer / deserializer", () => { + const checkCommonFields = (deserialized: Transaction, expected) => { + const fieldsToCheck = ["version", "network", "type", "timestamp", "senderPublicKey", "fee", "amount"]; + fieldsToCheck.forEach(field => { + expect(deserialized.data[field].toString()).toEqual(expected[field].toString()); + }); + }; + + describe("ser/deserialize - transfer", () => { + const transfer = client + .getBuilder() + .transfer() + .recipientId("D5q7YfEFDky1JJVQQEy4MGyiUhr5cGg47F") + .amount(10000) + .fee(50000000) + .vendorField("yo") + .version(1) + .network(30) + .sign("dummy passphrase") + .getStruct(); + + it("should ser/deserialize giving back original fields", () => { + const serialized = Transaction.fromData(transfer).serialized.toString("hex"); + const deserialized = TransactionDeserializer.deserialize(serialized); + + checkCommonFields(deserialized, transfer); + + expect(deserialized.data.vendorField).toBe(transfer.vendorField); + expect(deserialized.data.recipientId).toBe(transfer.recipientId); + }); + + it("should ser/deserialize giving back original fields - with vendorFieldHex", () => { + delete transfer.vendorField; + const vendorField = "cool vendor field"; + transfer.vendorFieldHex = new Buffer(vendorField).toString("hex"); + + const serialized = Transaction.fromData(transfer).serialized.toString("hex"); + const deserialized = TransactionDeserializer.deserialize(serialized); + + checkCommonFields(deserialized, transfer); + + expect(deserialized.data.vendorField).toBe(vendorField); + expect(deserialized.data.recipientId).toBe(transfer.recipientId); + }); + + it("should ser/deserialize with long vendorfield when vendorFieldLength=255 milestone is active", () => { + configManager.getMilestone().vendorFieldLength = 255; + + const transferWithLongVendorfield = client + .getBuilder() + .transfer() + .recipientId("D5q7YfEFDky1JJVQQEy4MGyiUhr5cGg47F") + .amount(10000) + .fee(50000000) + .vendorField("y".repeat(255)) + .version(1) + .network(30) + .sign("dummy passphrase") + .getStruct(); + + const serialized = Transaction.toBytes(transferWithLongVendorfield); + const deserialized = Transaction.fromBytes(serialized); + + expect(deserialized.verified).toBeTrue(); + expect(deserialized.data.vendorField).toHaveLength(255); + expect(deserialized.data.vendorFieldHex).toHaveLength(510); + expect(deserialized.data.vendorField).toEqual("y".repeat(255)); + + configManager.getMilestone().vendorFieldLength = 64; + }); + + it("should not ser/deserialize long vendorfield when vendorFieldLength=255 milestone is not active", () => { + const transferWithLongVendorfield = client + .getBuilder() + .transfer() + .recipientId("D5q7YfEFDky1JJVQQEy4MGyiUhr5cGg47F") + .amount(10000) + .fee(50000000) + .version(1) + .network(30) + .sign("dummy passphrase") + .getStruct(); + + transferWithLongVendorfield.vendorField = "y".repeat(255); + expect(() => { + const serialized = Transaction.toBytes(transferWithLongVendorfield); + Transaction.fromBytes(serialized); + }).toThrow(TransactionSchemaError); + }); + }); + + describe("ser/deserialize - second signature", () => { + it("should ser/deserialize giving back original fields", () => { + const secondSignature = client + .getBuilder() + .secondSignature() + .signatureAsset("signature") + .fee(50000000) + .version(1) + .network(30) + .sign("dummy passphrase") + .getStruct(); + + const serialized = Transaction.fromData(secondSignature).serialized.toString("hex"); + const deserialized = TransactionDeserializer.deserialize(serialized); + + checkCommonFields(deserialized, secondSignature); + + expect(deserialized.data.asset).toEqual(secondSignature.asset); + }); + }); + + describe("ser/deserialize - delegate registration", () => { + it("should ser/deserialize giving back original fields", () => { + const delegateRegistration = client + .getBuilder() + .delegateRegistration() + .usernameAsset("homer") + .version(1) + .network(30) + .sign("dummy passphrase") + .getStruct(); + + const serialized = Transaction.fromData(delegateRegistration).serialized.toString("hex"); + const deserialized = TransactionDeserializer.deserialize(serialized); + + checkCommonFields(deserialized, delegateRegistration); + + expect(deserialized.data.asset).toEqual(delegateRegistration.asset); + }); + }); + + describe("ser/deserialize - vote", () => { + it("should ser/deserialize giving back original fields", () => { + const vote = client + .getBuilder() + .vote() + .votesAsset(["+02bcfa0951a92e7876db1fb71996a853b57f996972ed059a950d910f7d541706c9"]) + .fee(50000000) + .version(1) + .network(30) + .sign("dummy passphrase") + .getStruct(); + + const serialized = Transaction.fromData(vote).serialized.toString("hex"); + const deserialized = TransactionDeserializer.deserialize(serialized); + + checkCommonFields(deserialized, vote); + + expect(deserialized.data.asset).toEqual(vote.asset); + }); + }); + + describe.skip("ser/deserialize - multi signature", () => { + const multiSignature = client + .getBuilder() + .multiSignature() + .multiSignatureAsset({ + keysgroup: [ + "+0376982a97dadbc65e694743d386084548a65431a82ce935ac9d957b1cffab2784", + "+03793904e0df839809bc89f2839e1ae4f8b1ea97ede6592b7d1e4d0ee194ca2998", + ], + lifetime: 72, + min: 2, + }) + .version(1) + .network(30) + .sign("dummy passphrase") + .multiSignatureSign("multi passphrase 1") + .multiSignatureSign("multi passphrase 2") + .getStruct(); + + it("should ser/deserialize giving back original fields", () => { + const serialized = Transaction.fromData(multiSignature).serialized.toString("hex"); + const deserialized = TransactionDeserializer.deserialize(serialized); + + checkCommonFields(deserialized, multiSignature); + + expect(deserialized.data.asset).toEqual(multiSignature.asset); + }); + + it("should ser/deserialize giving back original fields - v2 keysgroup", () => { + multiSignature.asset.multisignature.keysgroup = [ + "+0376982a97dadbc65e694743d386084548a65431a82ce935ac9d957b1cffab2784", + "+03793904e0df839809bc89f2839e1ae4f8b1ea97ede6592b7d1e4d0ee194ca2998", + ]; + + const serialized = Transaction.fromData(multiSignature).serialized.toString("hex"); + const deserialized = TransactionDeserializer.deserialize(serialized); + + checkCommonFields(deserialized, multiSignature); + + expect(deserialized.data.asset).toEqual(multiSignature.asset); + }); + }); + + describe.skip("ser/deserialize - ipfs", () => { + it("should ser/deserialize giving back original fields", () => { + const ipfs = client + .getBuilder() + .ipfs() + .fee(50000000) + .version(1) + .network(30) + .dag("da304502") + .sign("dummy passphrase") + .getStruct(); + + const serialized = Transaction.fromData(ipfs).serialized.toString("hex"); + const deserialized = TransactionDeserializer.deserialize(serialized); + + checkCommonFields(deserialized, ipfs); + + expect(deserialized.data.asset).toEqual(ipfs.asset); + }); + }); + + describe.skip("ser/deserialize - timelock transfer", () => { + it("should ser/deserialize giving back original fields", () => { + const timelockTransfer = client + .getBuilder() + .timelockTransfer() + .recipientId("D5q7YfEFDky1JJVQQEy4MGyiUhr5cGg47F") + .amount(10000) + .fee(50000000) + .version(1) + .network(30) + .timelock(12, 0x00) + .sign("dummy passphrase") + .getStruct(); + + // expect(timelockTransfer).toEqual({}) + const serialized = Transaction.fromData(timelockTransfer).serialized.toString("hex"); + const deserialized = TransactionDeserializer.deserialize(serialized); + + checkCommonFields(deserialized, timelockTransfer); + + expect(deserialized.data.timelockType).toEqual(timelockTransfer.timelockType); + expect(deserialized.data.timelock).toEqual(timelockTransfer.timelock); + }); + }); + + describe.skip("ser/deserialize - multi payment", () => { + it("should ser/deserialize giving back original fields", () => { + const multiPayment = client + .getBuilder() + .multiPayment() + .fee(50000000) + .version(1) + .network(30) + .addPayment("D5q7YfEFDky1JJVQQEy4MGyiUhr5cGg47F", 1555) + .addPayment("D5q7YfEFDky1JJVQQEy4MGyiUhr5cGg47F", 5000) + .sign("dummy passphrase") + .getStruct(); + + const serialized = Transaction.fromData(multiPayment).serialized.toString("hex"); + const deserialized = TransactionDeserializer.deserialize(serialized); + + checkCommonFields(deserialized, multiPayment); + + expect(deserialized.data.asset).toEqual(multiPayment.asset); + }); + }); + + describe.skip("ser/deserialize - delegate resignation", () => { + it("should ser/deserialize giving back original fields", () => { + const delegateResignation = client + .getBuilder() + .delegateResignation() + .fee(50000000) + .version(1) + .network(30) + .sign("dummy passphrase") + .getStruct(); + + const serialized = Transaction.fromData(delegateResignation).serialized.toString("hex"); + const deserialized = TransactionDeserializer.deserialize(serialized); + + checkCommonFields(deserialized, delegateResignation); + }); + }); + + describe("deserialize - others", () => { + it("should throw if type is not supported", () => { + const serializeWrongType = transaction => { + // copy-paste from transaction serializer, common stuff + const buffer = new ByteBuffer(512, true); + buffer.writeByte(0xff); // fill, to disambiguate from v1 + buffer.writeByte(transaction.version || 0x01); // version + buffer.writeByte(transaction.network); + buffer.writeByte(transaction.type); + buffer.writeUint32(transaction.timestamp); + buffer.append(transaction.senderPublicKey, "hex"); + buffer.writeUint64(+new Bignum(transaction.fee).toFixed()); + buffer.writeByte(0x00); + + return Buffer.from(buffer.flip().toBuffer()); + }; + const transactionWrongType = client + .getBuilder() + .transfer() + .recipientId("D5q7YfEFDky1JJVQQEy4MGyiUhr5cGg47F") + .amount(10000) + .fee(50000000) + .vendorField("yo") + .version(1) + .network(30) + .sign("dummy passphrase") + .getStruct(); + transactionWrongType.type = 55; + + const serialized = serializeWrongType(transactionWrongType).toString("hex"); + expect(() => TransactionDeserializer.deserialize(serialized)).toThrow(UnkownTransactionError); + }); + }); + + describe("serialize - others", () => { + it("should throw if type is not supported", () => { + const transactionWrongType = client + .getBuilder() + .transfer() + .recipientId("D5q7YfEFDky1JJVQQEy4MGyiUhr5cGg47F") + .amount(10000) + .fee(50000000) + .vendorField("yo") + .version(1) + .network(30) + .sign("dummy passphrase") + .getStruct(); + transactionWrongType.type = 55; + + expect(() => Transaction.fromData(transactionWrongType)).toThrow(UnkownTransactionError); + }); + }); + + describe("getBytesV1", () => { + let bytes = null; + + // it('should return Buffer of simply transaction and buffer must be 292 length', () => { + // const transaction = { + // type: 0, + // amount: 1000, + // fee: 2000, + // recipientId: 'AJWRd23HNEhPLkK1ymMnwnDBX2a7QBZqff', + // timestamp: 141738, + // asset: {}, + // senderPublicKey: '5d036a858ce89f844491762eb89e2bfbd50a4a0a0da658e4b2628b25b117ae09', + // signature: '618a54975212ead93df8c881655c625544bce8ed7ccdfe6f08a42eecfb1adebd051307be5014bb051617baf7815d50f62129e70918190361e5d4dd4796541b0a' + // } + + // bytes = crypto.getBytes(transaction) + // expect(bytes).toBeObject() + // expect(bytes.toString('hex') + transaction.signature).toHaveLength(292) + // }) + + it("should return Buffer of simply transaction and buffer must be 202 length", () => { + const transaction = { + type: 0, + amount: 1000, + fee: 2000, + recipientId: "AJWRd23HNEhPLkK1ymMnwnDBX2a7QBZqff", + timestamp: 141738, + asset: {}, + senderPublicKey: "5d036a858ce89f844491762eb89e2bfbd50a4a0a0da658e4b2628b25b117ae09", + signature: + "618a54975212ead93df8c881655c625544bce8ed7ccdfe6f08a42eecfb1adebd051307be5014bb051617baf7815d50f62129e70918190361e5d4dd4796541b0a", + id: "13987348420913138422", + }; + + bytes = TransactionSerializer.getBytes(transaction); + expect(bytes).toBeObject(); + expect(bytes.length).toBe(202); + expect(bytes.toString("hex")).toBe( + "00aa2902005d036a858ce89f844491762eb89e2bfbd50a4a0a0da658e4b2628b25b117ae09171dfc69b54c7fe901e91d5a9ab78388645e2427ea00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e803000000000000d007000000000000618a54975212ead93df8c881655c625544bce8ed7ccdfe6f08a42eecfb1adebd051307be5014bb051617baf7815d50f62129e70918190361e5d4dd4796541b0a", + ); + }); + + // it('should return Buffer of transaction with second signature and buffer must be 420 length', () => { + // const transaction = { + // type: 0, + // amount: 1000, + // fee: 2000, + // recipientId: 'AJWRd23HNEhPLkK1ymMnwnDBX2a7QBZqff', + // timestamp: 141738, + // asset: {}, + // senderPublicKey: '5d036a858ce89f844491762eb89e2bfbd50a4a0a0da658e4b2628b25b117ae09', + // signature: '618a54975212ead93df8c881655c625544bce8ed7ccdfe6f08a42eecfb1adebd051307be5014bb051617baf7815d50f62129e70918190361e5d4dd4796541b0a', + // secondSignature: '618a54975212ead93df8c881655c625544bce8ed7ccdfe6f08a42eecfb1adebd051307be5014bb051617baf7815d50f62129e70918190361e5d4dd4796541b0a' + // } + + // bytes = crypto.getBytes(transaction) + // expect(bytes).toBeObject() + // expect(bytes.toString('hex') + transaction.signature + transaction.secondSignature).toHaveLength(420) + // }) + + it("should return Buffer of transaction with second signature and buffer must be 266 length", () => { + const transaction = { + version: 1, + type: 0, + amount: 1000, + fee: 2000, + recipientId: "AJWRd23HNEhPLkK1ymMnwnDBX2a7QBZqff", + timestamp: 141738, + asset: {}, + senderPublicKey: "5d036a858ce89f844491762eb89e2bfbd50a4a0a0da658e4b2628b25b117ae09", + signature: + "618a54975212ead93df8c881655c625544bce8ed7ccdfe6f08a42eecfb1adebd051307be5014bb051617baf7815d50f62129e70918190361e5d4dd4796541b0a", + secondSignature: + "618a54975212ead93df8c881655c625544bce8ed7ccdfe6f08a42eecfb1adebd051307be5014bb051617baf7815d50f62129e70918190361e5d4dd4796541b0a", + id: "13987348420913138422", + }; + + bytes = TransactionSerializer.getBytes(transaction); + expect(bytes).toBeObject(); + expect(bytes.length).toBe(266); + expect(bytes.toString("hex")).toBe( + "00aa2902005d036a858ce89f844491762eb89e2bfbd50a4a0a0da658e4b2628b25b117ae09171dfc69b54c7fe901e91d5a9ab78388645e2427ea00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e803000000000000d007000000000000618a54975212ead93df8c881655c625544bce8ed7ccdfe6f08a42eecfb1adebd051307be5014bb051617baf7815d50f62129e70918190361e5d4dd4796541b0a618a54975212ead93df8c881655c625544bce8ed7ccdfe6f08a42eecfb1adebd051307be5014bb051617baf7815d50f62129e70918190361e5d4dd4796541b0a", + ); + }); + + it("should throw for unsupported version", () => { + const transaction = { + version: 110, + type: 0, + amount: 1000, + fee: 2000, + recipientId: "AJWRd23HNEhPLkK1ymMnwnDBX2a7QBZqff", + timestamp: 141738, + asset: {}, + senderPublicKey: "5d036a858ce89f844491762eb89e2bfbd50a4a0a0da658e4b2628b25b117ae09", + signature: + "618a54975212ead93df8c881655c625544bce8ed7ccdfe6f08a42eecfb1adebd051307be5014bb051617baf7815d50f62129e70918190361e5d4dd4796541b0a", + secondSignature: + "618a54975212ead93df8c881655c625544bce8ed7ccdfe6f08a42eecfb1adebd051307be5014bb051617baf7815d50f62129e70918190361e5d4dd4796541b0a", + id: "13987348420913138422", + }; + + expect(() => TransactionSerializer.getBytes(transaction)).toThrow(TransactionVersionError); + }); + }); +}); diff --git a/__tests__/unit/crypto/transactions/schemas.test.ts b/__tests__/unit/crypto/transactions/schemas.test.ts new file mode 100644 index 0000000000..4830351e30 --- /dev/null +++ b/__tests__/unit/crypto/transactions/schemas.test.ts @@ -0,0 +1,699 @@ +import { configManager, constants, crypto } from "../../../../packages/crypto/src"; +import { transactionBuilder } from "../../../../packages/crypto/src/builder"; +import { TransactionRegistry } from "../../../../packages/crypto/src/transactions"; +import { TransactionSchema } from "../../../../packages/crypto/src/transactions/types/schemas"; +import { AjvWrapper as Ajv } from "../../../../packages/crypto/src/validation"; + +const { TransactionTypes } = constants; + +let transaction; +let transactionSchema: TransactionSchema; + +describe("Transfer Transaction", () => { + const address = "DTRdbaUW3RQQSL5By4G43JVaeHiqfVp9oh"; + const fee = 1 * constants.ARKTOSHI; + const amount = 10 * constants.ARKTOSHI; + + beforeAll(() => { + transactionSchema = TransactionRegistry.get(TransactionTypes.Transfer).getSchema(); + }); + + beforeEach(() => { + transaction = transactionBuilder.transfer(); + }); + + it("should be valid", () => { + transaction + .recipientId(address) + .amount(amount) + .sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).toBeNull(); + }); + + it("should be valid with correct data", () => { + transaction + .recipientId(address) + .amount(amount) + .fee(fee) + .vendorField("Ahoy") + .sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).toBeNull(); + }); + + it("should be valid with up to 64 bytes in vendor field", () => { + transaction + .recipientId(address) + .amount(amount) + .fee(fee) + .vendorField("a".repeat(64)) + .sign("passphrase"); + let { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).toBeNull(); + + transaction + .recipientId(address) + .amount(amount) + .fee(fee) + .vendorField("⊁".repeat(21)) + .sign("passphrase"); + + error = Ajv.validate(transactionSchema.$id, transaction.getStruct()).error; + expect(error).toBeNull(); + }); + + it("should be invalid with more than 64 bytes in vendor field", () => { + transaction + .recipientId(address) + .amount(amount) + .fee(fee); + + // Bypass vendorfield check by manually assigning a vendorfield > 64 bytes + transaction.data.vendorField = "a".repeat(65); + transaction.sign("passphrase"); + + let { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + + transaction + .recipientId(address) + .amount(amount) + .fee(fee); + + // Bypass vendorfield check by manually assigning a vendorfield > 64 bytes + transaction.vendorField("⊁".repeat(22)); + transaction.sign("passphrase"); + + error = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to no transaction as object", () => { + const { error } = Ajv.validate(transactionSchema.$id, "test"); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to no address", () => { + transaction + .recipientId(null) + .amount(amount) + .sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to invalid address", () => { + transaction + .recipientId(address) + .amount(amount) + .sign("passphrase"); + + const struct = transaction.getStruct(); + struct.recipientId = "woop"; + + const { error } = Ajv.validate(transactionSchema.$id, struct); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to zero amount", () => { + transaction + .recipientId(address) + .amount(0) + .sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to zero fee", () => { + transaction + .recipientId(address) + .amount(1) + .fee(0) + .sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to wrong transaction type", () => { + transaction = transactionBuilder.delegateRegistration(); + transaction.usernameAsset("delegate_name").sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be valid due to missing network byte", () => { + transaction + .recipientId(address) + .amount(1) + .fee(1) + .sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).toBeNull(); + }); + + it("should be valid due to correct network byte", () => { + transaction + .recipientId(address) + .amount(1) + .fee(1) + .network(configManager.get("pubKeyHash")) + .sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).toBeNull(); + }); + + it("should be invalid due to wrong network byte", () => { + transaction + .recipientId(address) + .amount(1) + .fee(1) + .network(1) + .sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be valid after a network change", () => { + configManager.setFromPreset("devnet"); + + let transfer = transaction + .recipientId(address) + .amount(1) + .fee(1) + .network(configManager.get("pubKeyHash")) + .sign("passphrase") + .build(); + + expect(transfer.data.network).toBe(30); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).toBeNull(); + + configManager.setFromPreset("mainnet"); + + transfer = transaction + .recipientId("APnDzjtDb1FthuqcLMeL5XMWb1uD1KeMGi") + .amount(1) + .fee(1) + .network(configManager.get("pubKeyHash")) + .sign("passphrase") + .build(); + + expect(transfer.data.network).toBe(23); + expect(Ajv.validate(transactionSchema.$id, transaction.getStruct()).error).toBeNull(); + + configManager.setFromPreset("devnet"); + }); + + it("should be ok and turn uppercase publicKey to lowercase", () => { + const transfer = transaction + .recipientId(address) + .amount(1) + .fee(1) + .network(configManager.get("pubKeyHash")) + .sign("passphrase") + .build(); + + const { senderPublicKey } = transfer.data; + + transfer.data.senderPublicKey = senderPublicKey.toUpperCase(); + expect(transfer.data.senderPublicKey).not.toBe(senderPublicKey); + + const { value, error } = Ajv.validate(transactionSchema.$id, transfer.data); + expect(error).toBeNull(); + expect(value.senderPublicKey).toBe(senderPublicKey); + }); +}); + +describe("Second Signature Transaction", () => { + beforeAll(() => { + transactionSchema = TransactionRegistry.get(TransactionTypes.SecondSignature).getSchema(); + }); + + beforeEach(() => { + transaction = transactionBuilder.secondSignature(); + }); + + it("should be valid", () => { + transaction.signatureAsset("second passphrase").sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).toBeNull(); + }); + + it("should be valid with correct data", () => { + transaction + .signatureAsset("second passphrase") + .fee(1 * constants.ARKTOSHI) + .sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).toBeNull(); + }); + + it("should be invalid due to no transaction as object", () => { + const { error } = Ajv.validate(transactionSchema.$id, "test"); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to non-zero amount", () => { + transaction + .signatureAsset("second passphrase") + .amount(10 * constants.ARKTOSHI) + .sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to zero fee", () => { + transaction + .signatureAsset("second passphrase") + .fee(0) + .sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to second signature", () => { + transaction + .signatureAsset("second passphrase") + .fee(1) + .sign("passphrase") + .secondSign("second passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to wrong transaction type", () => { + transaction = transactionBuilder.delegateRegistration(); + transaction.usernameAsset("delegate_name").sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); +}); + +describe("Delegate Registration Transaction", () => { + beforeAll(() => { + transactionSchema = TransactionRegistry.get(TransactionTypes.DelegateRegistration).getSchema(); + }); + + beforeEach(() => { + transaction = transactionBuilder.delegateRegistration(); + }); + + it("should be valid", () => { + transaction.usernameAsset("delegate1").sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).toBeNull(); + }); + + it("should be invalid due to no transaction as object", () => { + const { error } = Ajv.validate(transactionSchema.$id, {}); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to non-zero amount", () => { + transaction + .usernameAsset("delegate1") + .amount(10 * constants.ARKTOSHI) + .sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to space in username", () => { + transaction.usernameAsset("test 123").sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to non-alphanumeric in username", () => { + transaction.usernameAsset("£££").sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to username too long", () => { + transaction.usernameAsset("1234567890123456789012345").sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to undefined username", () => { + transaction.usernameAsset("bla").sign("passphrase"); + const struct = transaction.getStruct(); + struct.asset.delegate.username = undefined; + const { error } = Ajv.validate(transactionSchema.$id, struct); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to no username", () => { + transaction.usernameAsset("").sign("passphrase"); + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to capitals in username", () => { + transaction.usernameAsset("I_AM_INVALID").sign("passphrase"); + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to wrong transaction type", () => { + transaction = transactionBuilder.transfer(); + transaction + .recipientId(null) + .amount(10 * constants.ARKTOSHI) + .sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); +}); + +describe("Vote Transaction", () => { + const vote = "+02bcfa0951a92e7876db1fb71996a853b57f996972ed059a950d910f7d541706c9"; + const unvote = "-0326580718fc86ba609799ac95fcd2721af259beb5afa81bfce0ab7d9fe95de991"; + const votes = [vote, "+0310ad026647eed112d1a46145eed58b8c19c67c505a67f1199361a511ce7860c0", unvote]; + const invalidVotes = [ + "02bcfa0951a92e7876db1fb71996a853b57f996972ed059a950d910f7d541706c9", + "0310ad026647eed112d1a46145eed58b8c19c67c505a67f1199361a511ce7860c0", + "0326580718fc86ba609799ac95fcd2721af259beb5afa81bfce0ab7d9fe95de991", + ]; + + beforeAll(() => { + transactionSchema = TransactionRegistry.get(TransactionTypes.Vote).getSchema(); + }); + + beforeEach(() => { + transaction = transactionBuilder.vote(); + }); + + it("should be valid with 1 vote", () => { + transaction.votesAsset([vote]).sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).toBeNull(); + }); + + it("should be valid with 1 unvote", () => { + transaction.votesAsset([unvote]).sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).toBeNull(); + }); + + it("should be invalid due to no transaction as object", () => { + const { error } = Ajv.validate(transactionSchema.$id, "test"); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to non-zero amount", () => { + transaction + .votesAsset([vote]) + .amount(10 * constants.ARKTOSHI) + .sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to zero fee", () => { + transaction + .votesAsset(votes) + .fee(0) + .sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to no votes", () => { + transaction.votesAsset([]).sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to more than 1 vote", () => { + transaction.votesAsset(votes).sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to invalid votes", () => { + transaction.votesAsset(invalidVotes).sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to wrong vote type", () => { + const struct = transaction.sign("passphrase").getStruct(); + struct.asset.votes = vote; + + const { error } = Ajv.validate(transactionSchema.$id, struct); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to wrong transaction type", () => { + const wrongTransaction = transactionBuilder.delegateRegistration(); + wrongTransaction.usernameAsset("delegate_name").sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, wrongTransaction.getStruct()); + expect(error).not.toBeNull(); + }); +}); + +describe.skip("Multi Signature Transaction", () => { + const passphrase = "passphrase 1"; + const publicKey = "+03e8021105a6c202097e97e6c6d650942d913099bf6c9f14a6815df1023dde3b87"; + const passphrases = [passphrase, "passphrase 2", "passphrase 3"]; + const keysGroup = [ + publicKey, + "+03dfdaaa7fd28bc9359874b7e33138f4d0afe9937e152c59b83a99fae7eeb94899", + "+03de72ef9d3ebf1b374f1214f5b8dde823690ab2aa32b4b8b3226cc568aaed1562", + ]; + + let multiSignatureAsset; + + beforeAll(() => { + transactionSchema = TransactionRegistry.get(TransactionTypes.MultiSignature).getSchema(); + }); + + beforeEach(() => { + transaction = transactionBuilder.multiSignature(); + multiSignatureAsset = { + min: 1, + keysgroup: keysGroup, + lifetime: 72, + }; + }); + + const signTransaction = (tx, values) => { + values.map(value => tx.multiSignatureSign(value)); + }; + + it("should be valid with min of 3", () => { + multiSignatureAsset.min = 3; + transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); + signTransaction(transaction, passphrases); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).toBeNull(); + }); + + it("should be valid with 3 public keys", () => { + transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); + signTransaction(transaction, passphrases); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).toBeNull(); + }); + + it("should be valid with lifetime of 10", () => { + multiSignatureAsset.lifetime = 10; + transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); + signTransaction(transaction, passphrases); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).toBeNull(); + }); + + it("should be invalid due to no transaction as object", () => { + const { error } = Ajv.validate(transactionSchema.$id, "test"); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to non-zero amount", () => { + transaction + .multiSignatureAsset(multiSignatureAsset) + .amount(10 * constants.ARKTOSHI) + .sign("passphrase"); + signTransaction(transaction, passphrases); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to zero fee", () => { + transaction + .multiSignatureAsset(multiSignatureAsset) + .fee(0) + .sign("passphrase"); + signTransaction(transaction, passphrases); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to min too low", () => { + multiSignatureAsset.min = 0; + transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); + signTransaction(transaction, passphrases); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to min too high", () => { + multiSignatureAsset.min = multiSignatureAsset.keysgroup.length + 1; + transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); + signTransaction(transaction, passphrases); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to lifetime too low", () => { + multiSignatureAsset.lifetime = 0; + transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); + signTransaction(transaction, passphrases); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to lifetime too high", () => { + multiSignatureAsset.lifetime = 100; + transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); + signTransaction(transaction, passphrases); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to no public keys", () => { + multiSignatureAsset.keysgroup = []; + transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); + signTransaction(transaction, passphrases); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to too many public keys", () => { + const values = []; + multiSignatureAsset.keysgroup = []; + for (let i = 0; i < 20; i++) { + const value = `passphrase ${i}`; + values.push(value); + multiSignatureAsset.keysgroup.push(crypto.getKeys(value).publicKey); + } + transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); + signTransaction(transaction, values); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to duplicate public keys", () => { + multiSignatureAsset.keysgroup = [publicKey, publicKey]; + transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); + signTransaction(transaction, passphrases); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to no signatures", () => { + transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to not enough signatures", () => { + transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); + signTransaction(transaction, passphrases.slice(1)); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to too many signatures", () => { + transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); + signTransaction(transaction, ["wrong passphrase", ...passphrases]); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it('should be invalid due to no "+" for publicKeys', () => { + multiSignatureAsset.keysgroup = keysGroup.map(value => value.slice(1)); + transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); + signTransaction(transaction, passphrases); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it('should be invalid due to having "-" for publicKeys', () => { + multiSignatureAsset.keysgroup = keysGroup.map(value => `-${value.slice(1)}`); + transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); + signTransaction(transaction, passphrases); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to wrong keysgroup type", () => { + multiSignatureAsset.keysgroup = keysGroup.map(value => value.slice(1)); + transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); + signTransaction(transaction, passphrases); + + const struct = transaction.getStruct(); + struct.asset.multisignature = publicKey; + + const { error } = Ajv.validate(transactionSchema.$id, struct); + expect(error).not.toBeNull(); + }); + + it("should be invalid due to wrong transaction type", () => { + transaction = transactionBuilder.delegateRegistration(); + transaction.usernameAsset("delegate_name").sign("passphrase"); + + const { error } = Ajv.validate(transactionSchema.$id, transaction.getStruct()); + expect(error).not.toBeNull(); + }); +}); diff --git a/__tests__/unit/crypto/transactions/transaction.test.ts b/__tests__/unit/crypto/transactions/transaction.test.ts new file mode 100644 index 0000000000..5be521193c --- /dev/null +++ b/__tests__/unit/crypto/transactions/transaction.test.ts @@ -0,0 +1,297 @@ +import "jest-extended"; + +import { transactionBuilder as builder } from "../../../../packages/crypto/src/builder"; +import { crypto } from "../../../../packages/crypto/src/crypto/crypto"; +import { configManager } from "../../../../packages/crypto/src/managers/config"; +import { ITransactionData, Transaction } from "../../../../packages/crypto/src/transactions"; +import { transaction as transactionDataFixture } from "../fixtures/transaction"; + +import deepmerge = require("deepmerge"); +import { + MalformedTransactionBytesError, + TransactionSchemaError, + TransactionTypeError, + TransactionVersionError, + UnkownTransactionError, +} from "../../../../packages/crypto/src/errors"; + +let transactionData: ITransactionData; + +const createRandomTx = type => { + let transaction; + + switch (type) { + case 0: { + // transfer + transaction = builder + .transfer() + .recipientId("DJLxkgm7JMortrGVh1ZrvDH39XALWLa83e") + .amount(1000 * 1e10) + .vendorField(Math.random().toString(36)) + .sign(Math.random().toString(36)) + .secondSign(Math.random().toString(36)) + .build(); + break; + } + + case 1: { + // second signature + transaction = builder + .secondSignature() + .signatureAsset(Math.random().toString(36)) + .sign(Math.random().toString(36)) + .build(); + break; + } + + case 2: { + // delegate registration + transaction = builder + .delegateRegistration() + .usernameAsset("dummydelegate") + .sign(Math.random().toString(36)) + .build(); + break; + } + + case 3: { + // vote registration + transaction = builder + .vote() + .votesAsset(["+036928c98ee53a1f52ed01dd87db10ffe1980eb47cd7c0a7d688321f47b5d7d760"]) + .sign(Math.random().toString(36)) + .build(); + break; + } + + case 4: { + // multisignature registration + const passphrases = [1, 2, 3].map(() => Math.random().toString(36)); + const publicKeys = passphrases.map(passphrase => `+${crypto.getKeys(passphrase).publicKey}`); + const min = Math.min(1, publicKeys.length); + const max = Math.max(1, publicKeys.length); + const minSignatures = Math.floor(Math.random() * (max - min)) + min; + + const transactionBuilder = builder + .multiSignature() + .multiSignatureAsset({ + keysgroup: publicKeys, + min: minSignatures, + lifetime: Math.floor(Math.random() * (72 - 1)) + 1, + }) + .sign(Math.random().toString(36)); + + for (let i = 0; i < minSignatures; i++) { + transactionBuilder.multiSignatureSign(passphrases[i]); + } + + transaction = transactionBuilder.build(); + break; + } + default: { + throw new TransactionTypeError(type); + } + } + + return transaction; +}; + +describe("Models - Transaction", () => { + beforeEach(() => { + configManager.setFromPreset("devnet"); + transactionData = deepmerge({}, transactionDataFixture); + }); + + describe("toBytes / fromBytes", () => { + it("should verify all transactions", () => { + [0, 1, 2, 3] + .map(type => createRandomTx(type)) + .forEach(transaction => { + const ser = Transaction.toBytes(transaction.data); + const newTransaction = Transaction.fromBytes(ser); + + // TODO: Remove both from data when not needed + delete transaction.data.signSignature; + if (transaction.data.recipientId === null) { + delete transaction.data.recipientId; + } + + transaction.data.amount = +transaction.data.amount; + transaction.data.fee = +transaction.data.fee; + + expect(newTransaction.toJson()).toMatchObject(transaction.data); + expect(newTransaction.verified).toBeTrue(); + }); + }); + + it("should create a transaction", () => { + const hex = Transaction.toBytes(transactionData).toString("hex"); + const transaction = Transaction.fromHex(hex); + expect(transaction).toBeInstanceOf(Transaction); + expect(transaction.toJson()).toEqual(transactionDataFixture); + }); + + it("should throw when getting garbage", () => { + expect(() => Transaction.fromBytes(null)).toThrow(MalformedTransactionBytesError); + expect(() => Transaction.fromBytes(Buffer.from("garbage"))).toThrow(MalformedTransactionBytesError); + expect(() => Transaction.fromHex(null)).toThrow(MalformedTransactionBytesError); + expect(() => Transaction.fromHex("affe")).toThrow(MalformedTransactionBytesError); + }); + + it("should throw when getting an unsupported version", () => { + let hex = Transaction.toBytes(transactionData).toString("hex"); + hex = hex.slice(0, 2) + "99" + hex.slice(4); + expect(() => Transaction.fromHex(hex)).toThrow(TransactionVersionError); + }); + }); + + describe("fromBytesUnsafe", () => { + it("should be ok", () => { + const bytes = Transaction.toBytes(transactionData); + const id = transactionData.id; + + const transaction = Transaction.fromBytesUnsafe(bytes, id); + expect(transaction).toBeInstanceOf(Transaction); + expect(transaction.toJson()).toEqual(transactionDataFixture); + }); + }); + + describe("fromData", () => { + it("should match transaction id", () => { + [0, 1, 2, 3] + .map(type => createRandomTx(type)) + .forEach(transaction => { + const originalId = transaction.data.id; + const newTransaction = Transaction.fromData(transaction.data); + expect(newTransaction.data.id).toEqual(originalId); + }); + }); + + it("should throw when getting garbage", () => { + expect(() => Transaction.fromData({} as ITransactionData)).toThrow(UnkownTransactionError); + expect(() => Transaction.fromData({ type: 0 } as ITransactionData)).toThrow(TransactionSchemaError); + }); + }); + + describe("should deserialize correctly some tests transactions", () => { + describe("mainnet", () => { + beforeEach(() => { + configManager.setFromPreset("mainnet"); + }); + + afterEach(() => { + configManager.setFromPreset("devnet"); + }); + + const txs = [ + { + id: "80d75c7b90288246199e4a97ba726bad6639595ef92ad7c2bd14fd31563241ab", + height: 918991, + type: 1, + timestamp: 7410965, + amount: 0, + fee: 500000000, + recipientId: "AP4UQ6j9hAHsxudpXh47RNQi7oF1AEfkAG", + senderPublicKey: "03ca269b2942104b2ad601ccfbe7bd30b14b99cb55210ef7c1a5e25b6669646b99", + signature: + "3045022100d01e0cf0813a722ab5ad92aece2d4d1c3a537422e2ea769182f9172417224e890220437e407db51c4c47393db2e5b1258b2e3ecb707738a5ffdc6e96f08aee7e9c74", + asset: { + signature: { + publicKey: "03c0e7e86dadd316275a31d84a1fdccd00cd26cc059982f95a1b24382c6ec2ceb0", + }, + }, + }, + ]; + + txs.forEach(tx => + it(`txid: ${tx.id}`, () => { + const newtx = Transaction.fromData(tx); + expect(newtx.data.id).toEqual(tx.id); + }), + ); + }); + + describe("devnet", () => { + const txs = [ + { + id: "89f354918b36197269b0e5514f8da66f19829a024f664ccc124bfaabe0266e10", + version: 1, + timestamp: 48068690, + senderPublicKey: "03b7d1da5c1b9f8efd0737d47123eb9cf7eb6d58198ef31bb6f01aa04bc4dda19d", + recipientId: "DHPNjqCaTR9KYtC8nHh7Zt1G86Xj4YiU2V", + type: 1, + amount: "0", + fee: "500000000", + signature: + "3045022100e8e03bdac70e18f220feacba25c1575aa89d1ab61673e54eb2aff38439666d2702207e2d84290d7ef2571f5b2fab7e22a77dec96b1c4187cf9def15be74db98e2700", + asset: { + signature: { + publicKey: "03b7d1da5c1b9f8efd0737d47123eb9cf7eb6d58198ef31bb6f01aa04bc4dda19d", + }, + }, + }, + { + id: "a50af2bb1f043d128480346d0b49f5b3165716d5c630c6b0978dc7aa168e77a8", + version: 1, + timestamp: 48068923, + senderPublicKey: "03173fd793c4bac0d64e9bd74ec5c2055716a7c0266feec9d6d94cb75be097b75d", + recipientId: "DQrj9eh9otRgz2jWdu1K1ASBQqZA6dTkra", + type: 1, + amount: "0", + fee: "500000000", + signature: + "3045022100b263d28a5da58b17c874a5666afab0657f8492266554ad8ff722b00d41e1493d02200c2156dd9b9c1739f1c2099e98b763952bc7ef0423ad9786dcd32f7ffaf4aafc", + asset: { + signature: { + publicKey: "03173fd793c4bac0d64e9bd74ec5c2055716a7c0266feec9d6d94cb75be097b75d", + }, + }, + }, + { + id: "68e34dc1c417cbfb47e5deea142974bc24c8d03df206f168c8b23d6a4decff73", + version: 1, + timestamp: 48068956, + senderPublicKey: "02813ade967f05384e0567841d175294b4102c06c428011646e5ef989212925fcf", + recipientId: "D8PGSYLUC3CxYaXoKjMA2gjV4RaeBpwghZ", + type: 1, + amount: "0", + fee: "500000000", + signature: + "3045022100e593eb501e89941461e247606d088b6e226cc5b5224f89cede532d35f9b16250022034bbdd098493639221e808301e0a99c3790ef9c6d357ac10266c518a2a66066f", + asset: { + signature: { + publicKey: "02813ade967f05384e0567841d175294b4102c06c428011646e5ef989212925fcf", + }, + }, + }, + { + id: "b4b3433be888b4b95b68b83a84a08e40d748b0ad92acf8487072ef01c1de251a", + version: 1, + timestamp: 48069792, + senderPublicKey: "03f9f9dafc06faf4a54be2e45cd7a5523e41f38bb439f6f93cf00a0990e7afc116", + recipientId: "DNuwcwYGTHDdhTPWMTYekhuGM1fFUpW9Jj", + type: 1, + amount: "0", + fee: "500000000", + signature: + "3044022052d1e5be426a79f827a67597fd460237de65e035593144e4e3afb0e82ab40f3802201d6e31892d000e73532bf8659851a3d221205d65ed1c0b8d08ce46b72c7f00ae", + asset: { + signature: { + publicKey: "03f9f9dafc06faf4a54be2e45cd7a5523e41f38bb439f6f93cf00a0990e7afc116", + }, + }, + }, + ]; + txs.forEach(tx => + it(`txid: ${tx.id}`, () => { + const newtx = Transaction.fromData(tx); + expect(newtx.data.id).toEqual(tx.id); + }), + ); + }); + }); + + it("Signatures are verified", () => { + [0, 1, 2, 3].map(type => createRandomTx(type)).forEach(({ data }) => expect(crypto.verify(data)).toBeTrue()); + }); +}); diff --git a/packages/crypto/__tests__/utils/format-satoshi.test.ts b/__tests__/unit/crypto/utils/format-satoshi.test.ts similarity index 75% rename from packages/crypto/__tests__/utils/format-satoshi.test.ts rename to __tests__/unit/crypto/utils/format-satoshi.test.ts index 2633376d9f..bcdb57e916 100644 --- a/packages/crypto/__tests__/utils/format-satoshi.test.ts +++ b/__tests__/unit/crypto/utils/format-satoshi.test.ts @@ -1,7 +1,7 @@ import "jest-extended"; -import { SATOSHI } from "../../src/constants"; -import { Bignum, formatSatoshi } from "../../src/utils"; +import { SATOSHI } from "../../../../packages/crypto/src/constants"; +import { Bignum, formatSatoshi } from "../../../../packages/crypto/src/utils"; describe("Format Satoshi", () => { it("should format satoshis", () => { diff --git a/packages/crypto/__tests__/utils/is-exception.test.ts b/__tests__/unit/crypto/utils/is-exception.test.ts similarity index 76% rename from packages/crypto/__tests__/utils/is-exception.test.ts rename to __tests__/unit/crypto/utils/is-exception.test.ts index 84837007d7..daa2f736a9 100644 --- a/packages/crypto/__tests__/utils/is-exception.test.ts +++ b/__tests__/unit/crypto/utils/is-exception.test.ts @@ -1,8 +1,8 @@ import "jest-extended"; -import { configManager } from "../../src/managers"; -import { IBlockData } from "../../src/models"; -import { isException } from "../../src/utils"; +import { configManager } from "../../../../packages/crypto/src/managers"; +import { IBlockData } from "../../../../packages/crypto/src/models"; +import { isException } from "../../../../packages/crypto/src/utils"; describe("IsException", () => { it("should return true", () => { diff --git a/packages/crypto/__tests__/utils/message.test.ts b/__tests__/unit/crypto/utils/message.test.ts similarity index 93% rename from packages/crypto/__tests__/utils/message.test.ts rename to __tests__/unit/crypto/utils/message.test.ts index 328dfec9fb..855fad9919 100644 --- a/packages/crypto/__tests__/utils/message.test.ts +++ b/__tests__/unit/crypto/utils/message.test.ts @@ -1,6 +1,6 @@ import "jest-extended"; -import { Message } from "../../src/crypto"; +import { Message } from "../../../../packages/crypto/src/crypto"; const fixture = { data: { diff --git a/packages/crypto/__tests__/utils/network-list.ts b/__tests__/unit/crypto/utils/network-list.ts similarity index 77% rename from packages/crypto/__tests__/utils/network-list.ts rename to __tests__/unit/crypto/utils/network-list.ts index 5eac96e291..b936c6cf38 100644 --- a/packages/crypto/__tests__/utils/network-list.ts +++ b/__tests__/unit/crypto/utils/network-list.ts @@ -3,7 +3,7 @@ import "jest-extended"; import { parse } from "path"; import tg from "tiny-glob/sync"; -const entries = tg("../../src/networks/**/*.json", { cwd: __dirname }); +const entries = tg("../../../../packages/crypto/src/networks/**/*.json", { cwd: __dirname }); const NETWORKS = {}; entries.forEach(file => { diff --git a/packages/crypto/__tests__/utils/sort-transactions.test.ts b/__tests__/unit/crypto/utils/sort-transactions.test.ts similarity index 91% rename from packages/crypto/__tests__/utils/sort-transactions.test.ts rename to __tests__/unit/crypto/utils/sort-transactions.test.ts index 0f04a2fe84..05abd2e4c8 100644 --- a/packages/crypto/__tests__/utils/sort-transactions.test.ts +++ b/__tests__/unit/crypto/utils/sort-transactions.test.ts @@ -1,5 +1,5 @@ -import { ITransactionData } from "../../src/models"; -import { sortTransactions } from "../../src/utils"; +import { ITransactionData } from "../../../../packages/crypto/src/transactions"; +import { sortTransactions } from "../../../../packages/crypto/src/utils"; describe("sortTransactions", () => { it("should sort by type", () => { diff --git a/__tests__/unit/crypto/validation/ajv-wrapper.test.ts b/__tests__/unit/crypto/validation/ajv-wrapper.test.ts new file mode 100644 index 0000000000..4e550986f6 --- /dev/null +++ b/__tests__/unit/crypto/validation/ajv-wrapper.test.ts @@ -0,0 +1,229 @@ +import "jest-extended"; + +import ajv = require("ajv"); +import { configManager } from "../../../../packages/crypto/src/managers/config"; +import { TransactionRegistry } from "../../../../packages/crypto/src/transactions/index"; +import { TransactionSchema } from "../../../../packages/crypto/src/transactions/types/schemas"; +import { AjvWrapper } from "../../../../packages/crypto/src/validation"; +import { block2, genesisBlock } from "../../../utils/fixtures/unitnet/blocks"; + +describe("AjvWrapper", () => { + describe("validate", () => { + describe("publicKey", () => { + it("should be ok", () => { + expect( + AjvWrapper.validate( + "publicKey", + "034da006f958beba78ec54443df4a3f52237253f7ae8cbdb17dccf3feaa57f3126", + ).error, + ).toBeNull(); + }); + + it("should not be ok", () => { + expect( + AjvWrapper.validate( + "publicKey", + "Z34da006f958beba78ec54443df4a3f52237253f7ae8cbdb17dccf3feaa57f3126", + ).error, + ).not.toBeNull(); + expect( + AjvWrapper.validate( + "publicKey", + "34da006f958beba78ec54443df4a3f52237253f7ae8cbdb17dccf3feaa57f3126", + ).error, + ).not.toBeNull(); + expect(AjvWrapper.validate("publicKey", "").error).not.toBeNull(); + expect(AjvWrapper.validate("publicKey", 1234).error).not.toBeNull(); + expect(AjvWrapper.validate("publicKey", null).error).not.toBeNull(); + expect(AjvWrapper.validate("publicKey", undefined).error).not.toBeNull(); + }); + }); + + describe("address", () => { + it("should be ok", () => { + expect(AjvWrapper.validate("address", "DTRdbaUW3RQQSL5By4G43JVaeHiqfVp9oh").error).toBeNull(); + }); + + it("should not validate if address is not on the same network", () => { + configManager.setFromPreset("unitnet"); + expect(AjvWrapper.validate("address", "DTRdbaUW3RQQSL5By4G43JVaeHiqfVp9oh").error).not.toBeNull(); + }); + + it("should not be ok", () => { + expect(AjvWrapper.validate("address", "€TRdbaUW3RQQSL5By4G43JVaeHiqfVp9oh").error).not.toBeNull(); + expect(AjvWrapper.validate("address", "DTRdbaUW3RQQSL5By4G43JVaeHiqfVp9").error).not.toBeNull(); + expect( + AjvWrapper.validate("address", "034da006f958beba78ec54443df4a3f52237253f7ae8cbdb17dccf3feaa57f3126") + .error, + ).not.toBeNull(); + expect(AjvWrapper.validate("address", "").error).not.toBeNull(); + expect(AjvWrapper.validate("address", 1234).error).not.toBeNull(); + expect(AjvWrapper.validate("address", null).error).not.toBeNull(); + expect(AjvWrapper.validate("address", undefined).error).not.toBeNull(); + }); + }); + + describe("hex", () => { + it("should be ok", () => { + expect(AjvWrapper.validate("hex", "deadbeef").error).toBeNull(); + }); + + it("should not be ok", () => { + expect(AjvWrapper.validate("hex", "€").error).not.toBeNull(); + expect(AjvWrapper.validate("hex", 1).error).not.toBeNull(); + expect(AjvWrapper.validate("hex", "").error).not.toBeNull(); + expect(AjvWrapper.validate("hex", null).error).not.toBeNull(); + expect(AjvWrapper.validate("hex", undefined).error).not.toBeNull(); + }); + }); + + describe("base58", () => { + it("should be ok", () => { + expect(AjvWrapper.validate("base58", "DTRdbaUW3RQQSL5By4G43JVaeHiqfVp9").error).toBeNull(); + }); + + it("should not be ok", () => { + expect(AjvWrapper.validate("base58", "€").error).not.toBeNull(); + expect(AjvWrapper.validate("base58", 1).error).not.toBeNull(); + expect(AjvWrapper.validate("base58", "").error).not.toBeNull(); + expect(AjvWrapper.validate("base58", null).error).not.toBeNull(); + expect(AjvWrapper.validate("base58", undefined).error).not.toBeNull(); + }); + }); + + describe("alphanumeric", () => { + it("should be ok", () => { + expect(AjvWrapper.validate("alphanumeric", "abcDE1234").error).toBeNull(); + }); + + it("should not be ok", () => { + expect(AjvWrapper.validate("alphanumeric", "+12").error).not.toBeNull(); + expect(AjvWrapper.validate("alphanumeric", ".1").error).not.toBeNull(); + expect(AjvWrapper.validate("alphanumeric", "1.0").error).not.toBeNull(); + expect(AjvWrapper.validate("alphanumeric", "€").error).not.toBeNull(); + expect(AjvWrapper.validate("alphanumeric", 1).error).not.toBeNull(); + expect(AjvWrapper.validate("alphanumeric", "").error).not.toBeNull(); + expect(AjvWrapper.validate("alphanumeric", null).error).not.toBeNull(); + expect(AjvWrapper.validate("alphanumeric", undefined).error).not.toBeNull(); + }); + }); + + describe("transactionId", () => { + it("should be ok", () => { + expect( + AjvWrapper.validate( + "transactionId", + "943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4", + ).error, + ).toBeNull(); + }); + + it("should not be ok", () => { + expect( + AjvWrapper.validate( + "transactionId", + "94c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4", + ).error, + ).not.toBeNull(); + expect( + AjvWrapper.validate( + "transactionId", + "94c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4111", + ).error, + ).not.toBeNull(); + expect( + AjvWrapper.validate( + "transactionId", + "94c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4@@@", + ).error, + ).not.toBeNull(); + expect(AjvWrapper.validate("transactionId", 1).error).not.toBeNull(); + expect(AjvWrapper.validate("transactionId", "").error).not.toBeNull(); + expect(AjvWrapper.validate("transactionId", null).error).not.toBeNull(); + expect(AjvWrapper.validate("transactionId", undefined).error).not.toBeNull(); + }); + }); + + describe("walletVote", () => { + it("should be ok", () => { + expect( + AjvWrapper.validate( + "walletVote", + "+034da006f958beba78ec54443df4a3f52237253f7ae8cbdb17dccf3feaa57f3126", + ).error, + ).toBeNull(); + expect( + AjvWrapper.validate( + "walletVote", + "-034da006f958beba78ec54443df4a3f52237253f7ae8cbdb17dccf3feaa57f3126", + ).error, + ).toBeNull(); + }); + + it("should not be ok", () => { + expect( + AjvWrapper.validate( + "walletVote", + "034da006f958beba78ec54443df4a3f52237253f7ae8cbdb17dccf3feaa57f3126", + ).error, + ).not.toBeNull(); + expect(AjvWrapper.validate("walletVote", "-^sd").error).not.toBeNull(); + expect(AjvWrapper.validate("walletVote", 1234).error).not.toBeNull(); + expect(AjvWrapper.validate("walletVote", "").error).not.toBeNull(); + expect(AjvWrapper.validate("walletVote", null).error).not.toBeNull(); + expect(AjvWrapper.validate("walletVote", undefined).error).not.toBeNull(); + }); + }); + + describe("delegateUsername", () => { + it("should be ok", () => { + expect(AjvWrapper.validate("delegateUsername", "asdf").error).toBeNull(); + expect(AjvWrapper.validate("delegateUsername", "_").error).toBeNull(); + }); + + it("should not be ok", () => { + expect(AjvWrapper.validate("delegateUsername", "AbCdEfG").error).not.toBeNull(); + expect(AjvWrapper.validate("delegateUsername", "longerthantwentycharacterslong").error).not.toBeNull(); + expect(AjvWrapper.validate("delegateUsername", 1234).error).not.toBeNull(); + expect(AjvWrapper.validate("delegateUsername", "").error).not.toBeNull(); + expect(AjvWrapper.validate("delegateUsername", null).error).not.toBeNull(); + expect(AjvWrapper.validate("delegateUsername", undefined).error).not.toBeNull(); + }); + }); + + describe("block", () => { + beforeAll(() => { + TransactionRegistry.get(0); // Make sure registry is loaded, since it adds the "transactions" schema. + configManager.setFromPreset("unitnet"); + }); + + it("should be ok", () => { + expect(AjvWrapper.validate("block", block2).error).toBeNull(); + expect(AjvWrapper.validate("block", genesisBlock).error).toBeNull(); + }); + + it("should not be ok", () => { + block2.numberOfTransactions = 1; + expect(AjvWrapper.validate("block", block2).error).not.toBeNull(); + block2.numberOfTransactions = 11; + expect(AjvWrapper.validate("block", block2).error).not.toBeNull(); + block2.numberOfTransactions = 10; + expect(AjvWrapper.validate("block", block2).error).toBeNull(); + }); + }); + }); + + describe("extend", () => { + it("should extend transaction schema", () => { + const customTransactionSchema = { $id: "custom" } as TransactionSchema; + AjvWrapper.extendTransaction(customTransactionSchema); + expect(AjvWrapper.instance().getSchema("custom")).not.toBeNull(); + }); + }); + + describe("instance", () => { + it("should return the instance", () => { + expect(AjvWrapper.instance()).toBeInstanceOf(ajv); + }); + }); +}); diff --git a/__tests__/unit/crypto/validation/formats.test.ts b/__tests__/unit/crypto/validation/formats.test.ts new file mode 100644 index 0000000000..7880e0e40d --- /dev/null +++ b/__tests__/unit/crypto/validation/formats.test.ts @@ -0,0 +1,73 @@ +import "jest-extended"; + +import { configManager } from "../../../../packages/crypto/src"; +import { AjvWrapper } from "../../../../packages/crypto/src/validation"; + +const ajv = AjvWrapper.instance(); + +describe("format vendorField", () => { + it("should be ok with 64 bytes", () => { + const schema = { type: "string", format: "vendorField" }; + const validate = ajv.compile(schema); + + expect(validate("1234")).toBeTrue(); + expect(validate("a".repeat(64))).toBeTrue(); + expect(validate("a".repeat(65))).toBeFalse(); + expect(validate("⊁".repeat(21))).toBeTrue(); + expect(validate("⊁".repeat(22))).toBeFalse(); + expect(validate({})).toBeFalse(); + expect(validate(null)).toBeFalse(); + expect(validate(undefined)).toBeFalse(); + }); + + it("should not be ok with over 64 bytes without milestone ", () => { + const schema = { type: "string", format: "vendorField" }; + const validate = ajv.compile(schema); + expect(validate("a".repeat(65))).toBeFalse(); + }); + + it("should be ok with up to 255 bytes with milestone ", () => { + configManager.getMilestone().vendorFieldLength = 255; + const schema = { type: "string", format: "vendorField" }; + const validate = ajv.compile(schema); + expect(validate("a".repeat(65))).toBeTrue(); + expect(validate("⊁".repeat(85))).toBeTrue(); + expect(validate("a".repeat(256))).toBeFalse(); + expect(validate("⊁".repeat(86))).toBeFalse(); + + configManager.getMilestone().vendorFieldLength = 64; + }); +}); + +describe("format vendorFieldHex", () => { + it("should be ok with 128 hex", () => { + const schema = { type: "string", format: "vendorFieldHex" }; + const validate = ajv.compile(schema); + + expect(validate("affe".repeat(32))).toBeTrue(); + expect(validate("affe".repeat(33))).toBeFalse(); + expect(validate("⊁".repeat(22))).toBeFalse(); + }); + + it("should be ok with 510 hex when milestone vendorFieldLength=255 is active", () => { + configManager.getMilestone().vendorFieldLength = 255; + const schema = { type: "string", format: "vendorFieldHex" }; + const validate = ajv.compile(schema); + + expect(validate("affe".repeat(127))).toBeTrue(); + expect(validate("affe".repeat(128))).toBeFalse(); + + configManager.getMilestone().vendorFieldLength = 64; + }); + + it("should not be ok with non hex", () => { + const schema = { type: "string", format: "vendorFieldHex" }; + const validate = ajv.compile(schema); + expect(validate("Z")).toBeFalse(); + expect(validate("Zaffe")).toBeFalse(); + expect(validate("⊁")).toBeFalse(); + expect(validate({})).toBeFalse(); + expect(validate(null)).toBeFalse(); + expect(validate(undefined)).toBeFalse(); + }); +}); diff --git a/__tests__/unit/crypto/validation/keywords.test.ts b/__tests__/unit/crypto/validation/keywords.test.ts new file mode 100644 index 0000000000..cd07b7f5b9 --- /dev/null +++ b/__tests__/unit/crypto/validation/keywords.test.ts @@ -0,0 +1,244 @@ +import "jest-extended"; + +import { configManager } from "../../../../packages/crypto/src"; +import { TransactionTypes } from "../../../../packages/crypto/src/constants"; +import { Bignum } from "../../../../packages/crypto/src/utils"; +import { AjvWrapper } from "../../../../packages/crypto/src/validation"; + +const ajv = AjvWrapper.instance(); + +describe("keyword maxBytes", () => { + it("should be ok", () => { + const schema = { type: "string", maxBytes: 64 }; + const validate = ajv.compile(schema); + + expect(validate("1234")).toBeTrue(); + expect(validate("a".repeat(64))).toBeTrue(); + expect(validate("a".repeat(65))).toBeFalse(); + expect(validate("⊁".repeat(21))).toBeTrue(); + expect(validate("⊁".repeat(22))).toBeFalse(); + expect(validate({})).toBeFalse(); + expect(validate(null)).toBeFalse(); + expect(validate(undefined)).toBeFalse(); + }); +}); + +describe("keyword network", () => { + it("should be ok", () => { + const schema = { network: true }; + const validate = ajv.compile(schema); + + expect(validate(30)).toBeTrue(); + expect(validate(23)).toBeFalse(); + expect(validate("a")).toBeFalse(); + + configManager.setFromPreset("mainnet"); + + expect(validate(23)).toBeTrue(); + expect(validate(30)).toBeFalse(); + + configManager.setFromPreset("devnet"); + + expect(validate(30)).toBeTrue(); + expect(validate(23)).toBeFalse(); + expect(validate({})).toBeFalse(); + expect(validate(null)).toBeFalse(); + expect(validate(undefined)).toBeFalse(); + }); +}); + +describe("keyword transactionType", () => { + it("should be ok", () => { + const schema = { transactionType: TransactionTypes.Transfer }; + const validate = ajv.compile(schema); + + expect(validate(0)).toBeTrue(); + expect(validate(TransactionTypes.Vote)).toBeFalse(); + expect(validate(-1)).toBeFalse(); + expect(validate("")).toBeFalse(); + expect(validate("0")).toBeFalse(); + expect(validate(null)).toBeFalse(); + expect(validate(undefined)).toBeFalse(); + }); +}); + +describe("keyword blockId", () => { + it("should be ok", () => { + const schema = { blockId: {} }; + const validate = ajv.compile(schema); + + expect(validate("1")).toBeTrue(); + expect(validate("1234")).toBeTrue(); + expect(validate("15654541800058894516")).toBeTrue(); + expect(validate("156545418000588945160")).toBeFalse(); + + expect(validate("e3b0c44298fc1c14")).toBeTrue(); + expect(validate("e3b0c44298fc1c1")).toBeFalse(); + expect(validate("e3b0c44298fc1c140")).toBeFalse(); + + expect(validate("94c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb40")).toBeTrue(); + expect(validate("94c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4")).toBeFalse(); + expect(validate("94c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb400")).toBeFalse(); + }); + + it("should not be ok", () => { + const schema = { blockId: { hex: true } }; + const validate = ajv.compile(schema); + + expect(validate("nein")).toBeFalse(); + expect(validate({})).toBeFalse(); + expect(validate("")).toBeFalse(); + expect(validate(null)).toBeFalse(); + expect(validate(undefined)).toBeFalse(); + expect(validate(1243)).toBeFalse(); + expect(validate(new Bignum(0))).toBeFalse(); + }); + + it("should be ok (genesis)", () => { + const schema = { + properties: { + height: { type: "number" }, + previousBlock: { blockId: { hex: true, allowNullWhenGenesis: true } }, + }, + }; + + const validate = ajv.compile(schema); + + expect(validate({ height: 1, previousBlock: "" })).toBeTrue(); + expect(validate({ height: 1, previousBlock: null })).toBeTrue(); + expect(validate({ height: 1, previousBlock: 0 })).toBeTrue(); + + expect(validate({ height: 1, previousBlock: "abc" })).toBeFalse(); + expect(validate({ height: 1, previousBlock: {} })).toBeFalse(); + expect(validate({ height: 1, previousBlock: "1234" })).toBeFalse(); + + expect(validate({ height: 2, previousBlock: "" })).toBeFalse(); + expect(validate({ height: 2, previousBlock: null })).toBeFalse(); + expect(validate({ height: 2, previousBlock: 0 })).toBeFalse(); + }); +}); + +describe("keyword bignumber", () => { + it("should be ok if only one possible value is allowed", () => { + const schema = { bignumber: { minimum: 100, maximum: 100 } }; + const validate = ajv.compile(schema); + + expect(validate(100)).toBeTrue(); + expect(validate(99)).toBeFalse(); + expect(validate(101)).toBeFalse(); + }); + + it("should be ok if above or equal minimum", () => { + const schema = { bignumber: { minimum: 20 }, additionalItems: false }; + const validate = ajv.compile(schema); + + expect(validate(25)).toBeTrue(); + expect(validate(20)).toBeTrue(); + expect(validate(19)).toBeFalse(); + }); + + it("should be ok if above or equal maximum", () => { + const schema = { bignumber: { maximum: 20 }, additionalItems: false }; + const validate = ajv.compile(schema); + + expect(validate(20)).toBeTrue(); + expect(validate(Number.MAX_SAFE_INTEGER)).toBeFalse(); + expect(validate(25)).toBeFalse(); + }); + + it("should not be ok for values bigger than the absolute maximum", () => { + const schema = { bignumber: {} }; + const validate = ajv.compile(schema); + + expect(validate(Number.MAX_SAFE_INTEGER)).toBeTrue(); + expect(validate(Number.MAX_SAFE_INTEGER + 1)).toBeFalse(); + expect(validate(String(Number.MAX_SAFE_INTEGER) + "100")).toBeFalse(); + }); + + it("should be ok for number, string and bignumber as input", () => { + const schema = { bignumber: { minimum: 100, maximum: 2000 }, additionalItems: false }; + const validate = ajv.compile(schema); + + [100, 1e2, 1020.0, 500, 2000].forEach(value => { + expect(validate(value)).toBeTrue(); + expect(validate(String(value))).toBeTrue(); + expect(validate(new Bignum(value))).toBeTrue(); + }); + + [1e8, 1999.000001, 1 / 1e8, -100, -500, -2000.1].forEach(value => { + expect(validate(value)).toBeFalse(); + expect(validate(String(value))).toBeFalse(); + expect(validate(new Bignum(value))).toBeFalse(); + }); + }); + + it("should not accept garbage", () => { + const schema = { bignumber: {} }; + const validate = ajv.compile(schema); + + expect(validate(null)).toBeFalse(); + expect(validate(undefined)).toBeFalse(); + expect(validate({})).toBeFalse(); + expect(validate(/d+/)).toBeFalse(); + expect(validate("")).toBeFalse(); + expect(validate("\u0000")).toBeFalse(); + }); + + describe("cast", () => { + it("should cast number to Bignumber", () => { + const schema = { + type: "object", + properties: { + amount: { bignumber: {} }, + }, + }; + + const data = { + amount: 100, + }; + + const validate = ajv.compile(schema); + expect(validate(data)).toBeTrue(); + expect(data.amount).toBeInstanceOf(Bignum); + expect(data.amount).toEqual(new Bignum(100)); + }); + + it("should cast string to Bignumber", () => { + const schema = { + type: "object", + properties: { + amount: { bignumber: {} }, + }, + }; + + const data = { + amount: "100", + }; + + const validate = ajv.compile(schema); + expect(validate(data)).toBeTrue(); + expect(data.amount).toBeInstanceOf(Bignum); + expect(data.amount).toEqual(new Bignum(100)); + }); + }); + + describe("bypassGenesis", () => { + it("should be ok", () => { + const schema = { + type: "object", + properties: { + amount: { bignumber: { minimum: 100, bypassGenesis: true } }, + }, + }; + + const validate = ajv.compile(schema); + expect( + validate({ amount: 0, id: "3e3817fd0c35bc36674f3874c2953fa3e35877cbcdb44a08bdc6083dbd39d572" }), + ).toBeTrue(); + expect( + validate({ amount: 0, id: "affe17fd0c35bc36674f3874c2953fa3e35877cbcdb44a08bdc6083dbd39d572" }), + ).toBeFalse(); + expect(validate({ amount: 0 })).toBeFalse(); + }); + }); +}); diff --git a/__tests__/unit/shared/logger.ts b/__tests__/unit/shared/logger.ts new file mode 100644 index 0000000000..a7c0a4c384 --- /dev/null +++ b/__tests__/unit/shared/logger.ts @@ -0,0 +1,84 @@ +import { Logger } from "@arkecosystem/core-interfaces"; +import * as capcon from "capture-console"; +import "jest-extended"; +import { tmpdir } from "os"; + +export function expectLogger(callback): void { + let logger: Logger.ILogger; + let message: string = ""; + + beforeAll(() => { + process.env.CORE_PATH_LOG = tmpdir(); + + logger = callback().make(); + + capcon.startCapture(process.stdout, stdout => { + message = stdout.toString(); + }); + + capcon.startCapture(process.stderr, stderr => { + message = stderr.toString(); + }); + + // @ts-ignore + capcon.startCapture(console._stdout, stdout => { + message = stdout.toString(); + }); + + // @ts-ignore + capcon.startCapture(console._stderr, stderr => { + message = stderr.toString(); + }); + }); + + afterEach(() => (message = null)); + + describe("Logger", () => { + it("should log a message with the [error] level", () => { + logger.error("error_message"); + + expect(message).toMatch(/error/); + expect(message).toMatch(/error_message/); + }); + + it("should log a message with the [warn] level", () => { + logger.warn("warning_message"); + + expect(message).toMatch(/warn/); + expect(message).toMatch(/warning_message/); + }); + + it("should log a message with the [info] level", () => { + logger.info("info_message"); + + expect(message).toMatch(/info/); + expect(message).toMatch(/info_message/); + }); + + it("should log a message with the [debug] level", () => { + logger.debug("debug_message"); + + expect(message).toMatch(/debug/); + expect(message).toMatch(/debug_message/); + }); + + it("should log a message with the [verbose] level", () => { + logger.verbose("verbose_message"); + + expect(message).toMatch(/verbose/); + expect(message).toMatch(/verbose_message/); + }); + + it("should suppress console output", () => { + logger.suppressConsoleOutput(true); + + logger.info("silent_message"); + expect(message).toBeNull(); + + logger.suppressConsoleOutput(false); + + logger.info("non_silent_message"); + expect(message).toMatch(/non_silent_message/); + }); + }); +} diff --git a/packages/core-test-utils/src/config/index.js b/__tests__/utils/config/index.js similarity index 100% rename from packages/core-test-utils/src/config/index.js rename to __tests__/utils/config/index.js diff --git a/packages/core-test-utils/src/config/testnet/delegates.json b/__tests__/utils/config/testnet/delegates.json similarity index 100% rename from packages/core-test-utils/src/config/testnet/delegates.json rename to __tests__/utils/config/testnet/delegates.json diff --git a/packages/core-test-utils/src/config/testnet/genesisBlock.json b/__tests__/utils/config/testnet/genesisBlock.json similarity index 100% rename from packages/core-test-utils/src/config/testnet/genesisBlock.json rename to __tests__/utils/config/testnet/genesisBlock.json diff --git a/packages/core-test-utils/src/config/testnet/peers.json b/__tests__/utils/config/testnet/peers.json similarity index 100% rename from packages/core-test-utils/src/config/testnet/peers.json rename to __tests__/utils/config/testnet/peers.json diff --git a/packages/core-test-utils/src/config/testnet/plugins.js b/__tests__/utils/config/testnet/plugins.js similarity index 74% rename from packages/core-test-utils/src/config/testnet/plugins.js rename to __tests__/utils/config/testnet/plugins.js index 0c6957d2e2..8c317ba3ee 100644 --- a/packages/core-test-utils/src/config/testnet/plugins.js +++ b/__tests__/utils/config/testnet/plugins.js @@ -1,19 +1,6 @@ module.exports = { "@arkecosystem/core-event-emitter": {}, - "@arkecosystem/core-logger-winston": { - transports: { - console: { - options: { - level: process.env.CORE_LOG_LEVEL || "debug", - }, - }, - dailyRotate: { - options: { - level: process.env.CORE_LOG_LEVEL || "debug", - }, - }, - }, - }, + "@arkecosystem/core-logger-pino": {}, "@arkecosystem/core-database-postgres": { connection: { host: process.env.CORE_DB_HOST || "localhost", @@ -42,9 +29,7 @@ module.exports = { minimumNetworkReach: 5, coldStart: 5, }, - "@arkecosystem/core-blockchain": { - fastRebuild: false, - }, + "@arkecosystem/core-blockchain": {}, "@arkecosystem/core-api": { enabled: !process.env.CORE_API_DISABLED, host: process.env.CORE_API_HOST || "0.0.0.0", @@ -54,17 +39,11 @@ module.exports = { "@arkecosystem/core-webhooks": { enabled: process.env.CORE_WEBHOOKS_ENABLED, server: { - enabled: process.env.CORE_WEBHOOKS_API_ENABLED, host: process.env.CORE_WEBHOOKS_HOST || "0.0.0.0", port: process.env.CORE_WEBHOOKS_PORT || 4004, whitelist: ["127.0.0.1", "::ffff:127.0.0.1"], }, }, - "@arkecosystem/core-graphql": { - enabled: process.env.CORE_GRAPHQL_ENABLED, - host: process.env.CORE_GRAPHQL_HOST || "0.0.0.0", - port: process.env.CORE_GRAPHQL_PORT || 4005, - }, "@arkecosystem/core-forger": { hosts: [`http://127.0.0.1:${process.env.CORE_P2P_PORT || 4000}`], }, diff --git a/packages/core-test-utils/src/config/unitnet/delegates.json b/__tests__/utils/config/unitnet/delegates.json similarity index 100% rename from packages/core-test-utils/src/config/unitnet/delegates.json rename to __tests__/utils/config/unitnet/delegates.json diff --git a/packages/core-test-utils/src/config/unitnet/genesisBlock.json b/__tests__/utils/config/unitnet/genesisBlock.json similarity index 100% rename from packages/core-test-utils/src/config/unitnet/genesisBlock.json rename to __tests__/utils/config/unitnet/genesisBlock.json diff --git a/packages/core-test-utils/src/config/unitnet/peers.json b/__tests__/utils/config/unitnet/peers.json similarity index 100% rename from packages/core-test-utils/src/config/unitnet/peers.json rename to __tests__/utils/config/unitnet/peers.json diff --git a/packages/core-test-utils/src/config/unitnet/plugins.js b/__tests__/utils/config/unitnet/plugins.js similarity index 74% rename from packages/core-test-utils/src/config/unitnet/plugins.js rename to __tests__/utils/config/unitnet/plugins.js index 0c6957d2e2..8c317ba3ee 100644 --- a/packages/core-test-utils/src/config/unitnet/plugins.js +++ b/__tests__/utils/config/unitnet/plugins.js @@ -1,19 +1,6 @@ module.exports = { "@arkecosystem/core-event-emitter": {}, - "@arkecosystem/core-logger-winston": { - transports: { - console: { - options: { - level: process.env.CORE_LOG_LEVEL || "debug", - }, - }, - dailyRotate: { - options: { - level: process.env.CORE_LOG_LEVEL || "debug", - }, - }, - }, - }, + "@arkecosystem/core-logger-pino": {}, "@arkecosystem/core-database-postgres": { connection: { host: process.env.CORE_DB_HOST || "localhost", @@ -42,9 +29,7 @@ module.exports = { minimumNetworkReach: 5, coldStart: 5, }, - "@arkecosystem/core-blockchain": { - fastRebuild: false, - }, + "@arkecosystem/core-blockchain": {}, "@arkecosystem/core-api": { enabled: !process.env.CORE_API_DISABLED, host: process.env.CORE_API_HOST || "0.0.0.0", @@ -54,17 +39,11 @@ module.exports = { "@arkecosystem/core-webhooks": { enabled: process.env.CORE_WEBHOOKS_ENABLED, server: { - enabled: process.env.CORE_WEBHOOKS_API_ENABLED, host: process.env.CORE_WEBHOOKS_HOST || "0.0.0.0", port: process.env.CORE_WEBHOOKS_PORT || 4004, whitelist: ["127.0.0.1", "::ffff:127.0.0.1"], }, }, - "@arkecosystem/core-graphql": { - enabled: process.env.CORE_GRAPHQL_ENABLED, - host: process.env.CORE_GRAPHQL_HOST || "0.0.0.0", - port: process.env.CORE_GRAPHQL_PORT || 4005, - }, "@arkecosystem/core-forger": { hosts: [`http://127.0.0.1:${process.env.CORE_P2P_PORT || 4000}`], }, diff --git a/packages/core-test-utils/src/config/unitnet/wallets.json b/__tests__/utils/config/unitnet/wallets.json similarity index 100% rename from packages/core-test-utils/src/config/unitnet/wallets.json rename to __tests__/utils/config/unitnet/wallets.json diff --git a/packages/core-test-utils/src/config/unitnet/wallets2ndSig.json b/__tests__/utils/config/unitnet/wallets2ndSig.json similarity index 100% rename from packages/core-test-utils/src/config/unitnet/wallets2ndSig.json rename to __tests__/utils/config/unitnet/wallets2ndSig.json diff --git a/packages/core-test-utils/src/fixtures/index.ts b/__tests__/utils/fixtures/index.ts similarity index 100% rename from packages/core-test-utils/src/fixtures/index.ts rename to __tests__/utils/fixtures/index.ts diff --git a/__tests__/utils/fixtures/testnet/block-model.ts b/__tests__/utils/fixtures/testnet/block-model.ts new file mode 100644 index 0000000000..174dba22b9 --- /dev/null +++ b/__tests__/utils/fixtures/testnet/block-model.ts @@ -0,0 +1,6 @@ +import { configManager, models } from "@arkecosystem/crypto"; +import genesisBlockJson from "../../config/testnet/genesisBlock.json"; + +configManager.setFromPreset("testnet"); + +export const genesisBlock = new models.Block(genesisBlockJson); diff --git a/packages/core-test-utils/src/fixtures/testnet/blocks101to155.ts b/__tests__/utils/fixtures/testnet/blocks101to155.ts similarity index 100% rename from packages/core-test-utils/src/fixtures/testnet/blocks101to155.ts rename to __tests__/utils/fixtures/testnet/blocks101to155.ts diff --git a/packages/core-test-utils/src/fixtures/testnet/blocks2to100.ts b/__tests__/utils/fixtures/testnet/blocks2to100.ts similarity index 100% rename from packages/core-test-utils/src/fixtures/testnet/blocks2to100.ts rename to __tests__/utils/fixtures/testnet/blocks2to100.ts diff --git a/packages/core-test-utils/src/fixtures/testnet/delegates.ts b/__tests__/utils/fixtures/testnet/delegates.ts similarity index 100% rename from packages/core-test-utils/src/fixtures/testnet/delegates.ts rename to __tests__/utils/fixtures/testnet/delegates.ts diff --git a/packages/core-test-utils/src/fixtures/testnet/index.ts b/__tests__/utils/fixtures/testnet/index.ts similarity index 100% rename from packages/core-test-utils/src/fixtures/testnet/index.ts rename to __tests__/utils/fixtures/testnet/index.ts diff --git a/packages/core-test-utils/src/fixtures/testnet/passphrases.ts b/__tests__/utils/fixtures/testnet/passphrases.ts similarity index 100% rename from packages/core-test-utils/src/fixtures/testnet/passphrases.ts rename to __tests__/utils/fixtures/testnet/passphrases.ts diff --git a/__tests__/utils/fixtures/unitnet/block-model.ts b/__tests__/utils/fixtures/unitnet/block-model.ts new file mode 100644 index 0000000000..792fa191ad --- /dev/null +++ b/__tests__/utils/fixtures/unitnet/block-model.ts @@ -0,0 +1,6 @@ +import { configManager, models } from "@arkecosystem/crypto"; +import genesisBlockJson from "../../config/unitnet/genesisBlock.json"; + +configManager.setFromPreset("unitnet"); + +export const genesisBlock = new models.Block(genesisBlockJson); diff --git a/packages/core-test-utils/src/fixtures/unitnet/blocks.ts b/__tests__/utils/fixtures/unitnet/blocks.ts similarity index 100% rename from packages/core-test-utils/src/fixtures/unitnet/blocks.ts rename to __tests__/utils/fixtures/unitnet/blocks.ts diff --git a/packages/core-test-utils/src/fixtures/unitnet/delegates.ts b/__tests__/utils/fixtures/unitnet/delegates.ts similarity index 87% rename from packages/core-test-utils/src/fixtures/unitnet/delegates.ts rename to __tests__/utils/fixtures/unitnet/delegates.ts index 9e93ee1775..b758a8e9ce 100644 --- a/packages/core-test-utils/src/fixtures/unitnet/delegates.ts +++ b/__tests__/utils/fixtures/unitnet/delegates.ts @@ -1,11 +1,11 @@ -import { client, crypto } from "@arkecosystem/crypto"; +import { configManager, crypto } from "@arkecosystem/crypto"; /** * Get the unitnet genesis delegates information * @return {Array} array of objects like { secret, publicKey, address, balance } */ -client.getConfigManager().setFromPreset("unitnet"); +configManager.setFromPreset("unitnet"); import { secrets } from "../../config/unitnet/delegates.json"; import { transactions as genesisTransactions } from "../../config/unitnet/genesisBlock.json"; diff --git a/packages/core-test-utils/src/fixtures/unitnet/index.ts b/__tests__/utils/fixtures/unitnet/index.ts similarity index 100% rename from packages/core-test-utils/src/fixtures/unitnet/index.ts rename to __tests__/utils/fixtures/unitnet/index.ts diff --git a/packages/core-test-utils/src/fixtures/unitnet/wallets.ts b/__tests__/utils/fixtures/unitnet/wallets.ts similarity index 100% rename from packages/core-test-utils/src/fixtures/unitnet/wallets.ts rename to __tests__/utils/fixtures/unitnet/wallets.ts diff --git a/__tests__/utils/generators/index.ts b/__tests__/utils/generators/index.ts new file mode 100644 index 0000000000..9c2861619e --- /dev/null +++ b/__tests__/utils/generators/index.ts @@ -0,0 +1 @@ +export * from "./wallets"; diff --git a/packages/core-test-utils/src/generators/wallets.ts b/__tests__/utils/generators/wallets.ts similarity index 100% rename from packages/core-test-utils/src/generators/wallets.ts rename to __tests__/utils/generators/wallets.ts diff --git a/packages/core-test-utils/src/helpers/api.ts b/__tests__/utils/helpers/api.ts similarity index 95% rename from packages/core-test-utils/src/helpers/api.ts rename to __tests__/utils/helpers/api.ts index 08720ce440..0f54e8bd19 100644 --- a/packages/core-test-utils/src/helpers/api.ts +++ b/__tests__/utils/helpers/api.ts @@ -7,7 +7,7 @@ export class ApiHelpers { .map(([key, val]) => `${key}=${val}`) .join("&"); - // Injecting the request into Hapi server instead of using axios + // Injecting the request into Hapi server const injectOptions = { method, url: ["GET", "DELETE"].includes(method) ? `${url}?${getParams}` : url, diff --git a/packages/core-test-utils/src/helpers/blockchain.ts b/__tests__/utils/helpers/blockchain.ts similarity index 86% rename from packages/core-test-utils/src/helpers/blockchain.ts rename to __tests__/utils/helpers/blockchain.ts index 649d3c91bd..3ec35f0afc 100644 --- a/packages/core-test-utils/src/helpers/blockchain.ts +++ b/__tests__/utils/helpers/blockchain.ts @@ -1,5 +1,6 @@ import { app } from "@arkecosystem/core-container"; import { Blockchain, TransactionPool } from "@arkecosystem/core-interfaces"; + export const resetBlockchain = async () => { // Resets everything so that it can be used in beforeAll to start clean a test suite // Now resets: blocks (remove blocks other than genesis), transaction pool @@ -12,6 +13,6 @@ export const resetBlockchain = async () => { await blockchain.removeBlocks(height - 1); } - const transactionPool = app.resolvePlugin("transactionPool"); + const transactionPool = app.resolvePlugin("transaction-pool"); transactionPool.flush(); }; diff --git a/packages/core-test-utils/src/helpers/container.ts b/__tests__/utils/helpers/container.ts similarity index 87% rename from packages/core-test-utils/src/helpers/container.ts rename to __tests__/utils/helpers/container.ts index 3768aba6b2..377690b10a 100644 --- a/packages/core-test-utils/src/helpers/container.ts +++ b/__tests__/utils/helpers/container.ts @@ -2,13 +2,13 @@ import { app } from "@arkecosystem/core-container"; import { Container } from "@arkecosystem/core-interfaces"; import "@arkecosystem/core-jest-matchers"; import { asValue } from "awilix"; -import isString from "lodash/isString"; +import isString from "lodash.isstring"; import * as path from "path"; export async function setUpContainer(options: any): Promise { options.network = options.network || "testnet"; - process.env.CORE_PATH_DATA = options.data || "~/.core"; + process.env.CORE_PATH_DATA = options.data || `${process.env.HOME}/.core`; process.env.CORE_PATH_CONFIG = options.config ? options.config : path.resolve(__dirname, `../config/${options.network}`); @@ -16,7 +16,7 @@ export async function setUpContainer(options: any): Promise { + return new models.Block(dataEmpty); +}; + +exports['fromData (150)'] = () => { + return new models.Block(dataFull); +}; + +exports['fromHex (0)'] = () => { + return new models.Block(serializedEmpty); +}; + +exports['fromHex (150)'] = () => { + return new models.Block(serializedFull); +}; + diff --git a/benchmark/crypto/hash-algorithms.js b/benchmark/crypto/hash-algorithms.js new file mode 100644 index 0000000000..a137369d85 --- /dev/null +++ b/benchmark/crypto/hash-algorithms.js @@ -0,0 +1,49 @@ +const { + HashAlgorithms, + Transaction +} = require('@arkecosystem/crypto') +const createHash = require("create-hash"); +const nodeSha256 = (bytes) => createHash("sha256").update(bytes).digest() + +const data = require('../helpers').getJSONFixture('transaction/deserialized/0'); +const transactionBytes = Transaction.toBytes(data); + +exports['bcrypto.sha256'] = () => { + HashAlgorithms.sha256(transactionBytes); +}; + +exports['node.sha256'] = () => { + nodeSha256(transactionBytes); +}; + +exports['bcrypto.sha1'] = () => { + HashAlgorithms.sha1(transactionBytes); +}; + +exports['node.sha1'] = () => { + createHash("sha1").update(transactionBytes).digest(); +}; + +exports['bcrypto.ripemd160'] = () => { + HashAlgorithms.ripemd160(transactionBytes); +}; + +exports['node.ripemd160'] = () => { + createHash("ripemd160").update(transactionBytes).digest(); +}; + +exports['bcrypto.hash160'] = () => { + HashAlgorithms.hash160(transactionBytes); +}; + +exports['node.hash160'] = () => { + createHash("ripemd160").update(nodeSha256(transactionBytes)).digest(); +}; + +exports['bcrypto.hash256'] = () => { + HashAlgorithms.hash256(transactionBytes); +}; + +exports['node.hash256'] = () => { + nodeSha256(nodeSha256(transactionBytes)); +}; \ No newline at end of file diff --git a/benchmark/index.js b/benchmark/index.js index bf217a53e4..ee8d959502 100644 --- a/benchmark/index.js +++ b/benchmark/index.js @@ -1,12 +1,19 @@ const { benchmarker } = require('@faustbrian/benchmarker'); +const { configManager } = require("@arkecosystem/crypto"); + +configManager.setFromPreset("mainnet"); benchmarker('core', [ + { name: 'new Block()', scenarios: require('./block/create') }, { name: 'Block.serialize (0 transactions)', scenarios: require('./block/serialize') }, { name: 'Block.serialize (150 transactions)', scenarios: require('./block/serializeFull') }, - { name: 'Block.deserialize (0 transactions)', scenarios: require('./block/deserialize/0') }, { name: 'Block.deserialize (150 transactions)', scenarios: require('./block/deserialize/150') }, + { name: 'new Transaction (Type 0)', scenarios: require('./transaction/create/0') }, { name: 'Transaction.serialize (Type 0)', scenarios: require('./transaction/serialize/0') }, { name: 'Transaction.deserialize (Type 0)', scenarios: require('./transaction/deserialize/0') }, + + { name: 'HashAlgorithms', scenarios: require('./crypto/hash-algorithms') }, + ], { hideSummary: true }); diff --git a/benchmark/transaction/create/0.js b/benchmark/transaction/create/0.js new file mode 100644 index 0000000000..15cc78a2e0 --- /dev/null +++ b/benchmark/transaction/create/0.js @@ -0,0 +1,23 @@ +const { + Transaction +} = require('@arkecosystem/crypto') + +const data = require('../../helpers').getJSONFixture('transaction/deserialized/0'); +const serializedHex = require('../../helpers').getFixture('transaction/serialized/0.txt'); +const serializedBytes = Buffer.from(serializedHex, "hex"); + +exports['fromData'] = () => { + return Transaction.fromData(data); +}; + +exports['fromHex'] = () => { + return Transaction.fromHex(serializedHex); +}; + +exports['fromBytes'] = () => { + return Transaction.fromBytes(serializedBytes); +}; + +exports['fromBytesUnsafe'] = () => { + return Transaction.fromBytesUnsafe(serializedBytes, data.id); +}; diff --git a/benchmark/transaction/deserialize/methods.js b/benchmark/transaction/deserialize/methods.js index d01249d1b3..19f0920c22 100644 --- a/benchmark/transaction/deserialize/methods.js +++ b/benchmark/transaction/deserialize/methods.js @@ -1,7 +1,7 @@ const { - models + TransactionDeserializer } = require('@arkecosystem/crypto') exports.deserialize = data => { - return models.Transaction.deserialize(data) + return TransactionDeserializer.deserialize(data) } diff --git a/benchmark/transaction/serialize/0.js b/benchmark/transaction/serialize/0.js index 761e1c1b31..bf78c8638d 100644 --- a/benchmark/transaction/serialize/0.js +++ b/benchmark/transaction/serialize/0.js @@ -1,9 +1,9 @@ const { - models + Transaction } = require('@arkecosystem/crypto') const data = require('../../helpers').getJSONFixture('transaction/deserialized/0'); exports['core'] = () => { - return models.Transaction.serialize(data); + return Transaction.toBytes(data); }; diff --git a/deprecated/core-snapshots-cli/bin/run b/deprecated/core-snapshots-cli/bin/run deleted file mode 100755 index 30b14e1773..0000000000 --- a/deprecated/core-snapshots-cli/bin/run +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env node - -require('@oclif/command').run() -.then(require('@oclif/command/flush')) -.catch(require('@oclif/errors/handle')) diff --git a/deprecated/core-snapshots-cli/bin/run.cmd b/deprecated/core-snapshots-cli/bin/run.cmd deleted file mode 100644 index 968fc30758..0000000000 --- a/deprecated/core-snapshots-cli/bin/run.cmd +++ /dev/null @@ -1,3 +0,0 @@ -@echo off - -node "%~dp0\run" %* diff --git a/deprecated/core-snapshots-cli/package.json b/deprecated/core-snapshots-cli/package.json deleted file mode 100644 index 4fe44adfba..0000000000 --- a/deprecated/core-snapshots-cli/package.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "name": "@arkecosystem/core-snapshots-cli", - "description": "Provides live cli interface to the core-snapshots module for ARK Core", - "version": "2.2.0", - "contributors": [ - "Kristjan Košič " - ], - "license": "MIT", - "main": "dist/index.js", - "files": [ - "/bin", - "/dist", - "/oclif.manifest.json" - ], - "bin": { - "snapshot": "./bin/run" - }, - "scripts": { - "snapshot": "./bin/run", - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", - "prepublishOnly": "yarn build", - "pretest": "yarn lint && yarn build", - "prepack": "oclif-dev manifest && npm shrinkwrap", - "postpack": "rm -f oclif.manifest.json", - "compile": "../../node_modules/typescript/bin/tsc", - "build": "yarn clean && yarn compile", - "build:watch": "yarn clean && yarn compile -w", - "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "debug": "node --inspect-brk ./dist/index.js", - "dump": "yarn snapshot dump", - "dump:mainnet": "yarn snapshot dump --network mainnet", - "dump:devnet": "yarn snapshot dump --network devnet", - "dump:testnet": "yarn snapshot dump --network testnet", - "restore": "yarn snapshot restore", - "restore:mainnet": "yarn snapshot restore --network mainnet", - "restore:devnet": "yarn snapshot restore --network devnet", - "restore:testnet": "yarn snapshot restore --network testnet", - "verify": "yarn snapshot verify", - "verify:mainnet": "yarn snapshot verify --network mainnet", - "verify:devnet": "yarn snapshot verify --network devnet", - "verify:testnet": "yarn snapshot verify --network testnet", - "rollback": "yarn snapshot rollback", - "rollback:mainnet": "yarn snapshot rollback --network mainnet", - "rollback:devnet": "yarn snapshot rollback --network devnet", - "rollback:testnet": "yarn snapshot rollback --network testnet", - "truncate": "yarn snapshot truncate", - "truncate:mainnet": "yarn snapshot truncate --network mainnet", - "truncate:devnet": "yarn snapshot truncate --network devnet", - "truncate:testnet": "yarn snapshot truncate --network testnet", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" - }, - "dependencies": { - "@arkecosystem/core-container": "^2.2.0-beta.7", - "@arkecosystem/core-interfaces": "^2.2.0-beta.7", - "@arkecosystem/core-snapshots": "^2.2.0-beta.7", - "@oclif/command": "^1.5.10", - "@oclif/config": "^1.12.6", - "@oclif/plugin-help": "^2.1.6", - "@oclif/plugin-not-found": "^1.2.2", - "@types/boom": "^7.2.1", - "@types/cli-progress": "^1.8.0", - "@types/commander": "^2.12.2", - "@types/fs-extra": "^5.0.5", - "cli-progress": "^2.1.1", - "fs-extra": "^7.0.1" - }, - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=10.x" - }, - "jest": { - "preset": "../../jest-preset.json" - }, - "oclif": { - "commands": "./dist/commands", - "bin": "snapshot", - "plugins": [ - "@oclif/plugin-help", - "@oclif/plugin-not-found" - ] - } -} diff --git a/deprecated/core-snapshots-cli/src/commands/command.ts b/deprecated/core-snapshots-cli/src/commands/command.ts deleted file mode 100644 index c4f02e2d44..0000000000 --- a/deprecated/core-snapshots-cli/src/commands/command.ts +++ /dev/null @@ -1,25 +0,0 @@ -import Command, { flags } from "@oclif/command"; - -export abstract class BaseCommand extends Command { - public static flags = { - data: flags.string({ - description: "data directory", - }), - config: flags.string({ - description: "network config", - }), - token: flags.string({ - description: "token name", - default: "ark", - }), - network: flags.string({ - description: "token network", - }), - skipCompression: flags.boolean({ - description: "skip gzip compression", - }), - trace: flags.boolean({ - description: "dumps generated queries and settings to console", - }), - }; -} diff --git a/deprecated/core-snapshots-cli/src/commands/dump.ts b/deprecated/core-snapshots-cli/src/commands/dump.ts deleted file mode 100644 index a06ac275fb..0000000000 --- a/deprecated/core-snapshots-cli/src/commands/dump.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { app } from "@arkecosystem/core-container"; -import { Logger } from "@arkecosystem/core-interfaces"; -import { SnapshotManager } from "@arkecosystem/core-snapshots"; -import { flags } from "@oclif/command"; -import fs from "fs-extra"; -import { setUpLite } from "../utils"; -import { BaseCommand } from "./command"; - -export class DumpCommand extends BaseCommand { - public static description: string = "create a full snapshot of the database"; - - public static flags = { - ...BaseCommand.flags, - blocks: flags.string({ - description: "blocks to append to, correlates to folder name", - }), - start: flags.integer({ - description: "start network height to export", - default: -1, - }), - end: flags.integer({ - description: "end network height to export", - default: -1, - }), - codec: flags.string({ - description: "codec name, default is msg-lite binary", - }), - }; - - public async run(): Promise { - // tslint:disable-next-line:no-shadowed-variable - const { flags } = this.parse(DumpCommand); - - await setUpLite(flags); - - await app.resolvePlugin("snapshots").exportData(flags); - } -} diff --git a/deprecated/core-snapshots-cli/src/commands/restore.ts b/deprecated/core-snapshots-cli/src/commands/restore.ts deleted file mode 100644 index c3c04e858f..0000000000 --- a/deprecated/core-snapshots-cli/src/commands/restore.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { app } from "@arkecosystem/core-container"; -import { EventEmitter } from "@arkecosystem/core-interfaces"; -import { SnapshotManager } from "@arkecosystem/core-snapshots"; -import { flags } from "@oclif/command"; -import _cliProgress from "cli-progress"; -import { setUpLite } from "../utils"; -import { BaseCommand } from "./command"; - -export class RestoreCommand extends BaseCommand { - public static description: string = "import data from specified snapshot"; - - public static flags = { - ...BaseCommand.flags, - blocks: flags.string({ - description: "blocks to import, corelates to folder name", - required: true, - }), - codec: flags.string({ - description: "codec name, default is msg-lite binary", - }), - truncate: flags.boolean({ - description: "empty all tables before running import", - }), - skipRestartRound: flags.boolean({ - description: "skip revert to current round", - }), - signatureVerify: flags.boolean({ - description: "signature verification", - }), - }; - - public async run(): Promise { - // tslint:disable-next-line:no-shadowed-variable - const { flags } = this.parse(RestoreCommand); - - await setUpLite(flags); - - const emitter = app.resolvePlugin("event-emitter"); - - const progressBar = new _cliProgress.Bar( - { - format: "{bar} {percentage}% | ETA: {eta}s | {value}/{total} | Duration: {duration}s", - }, - _cliProgress.Presets.shades_classic, - ); - - emitter.on("start", data => { - progressBar.start(data.count, 1); - }); - - emitter.on("progress", data => { - progressBar.update(data.value); - }); - - emitter.on("complete", data => { - progressBar.stop(); - }); - - await app.resolvePlugin("snapshots").importData(flags); - } -} diff --git a/deprecated/core-snapshots-cli/src/commands/rollback.ts b/deprecated/core-snapshots-cli/src/commands/rollback.ts deleted file mode 100644 index dfa70112f9..0000000000 --- a/deprecated/core-snapshots-cli/src/commands/rollback.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { app } from "@arkecosystem/core-container"; -import { Logger } from "@arkecosystem/core-interfaces"; -import { SnapshotManager } from "@arkecosystem/core-snapshots"; -import { flags } from "@oclif/command"; -import { setUpLite } from "../utils"; -import { BaseCommand } from "./command"; - -export class RollbackCommand extends BaseCommand { - public static description: string = "rollback chain to specified height"; - - public static flags = { - ...BaseCommand.flags, - height: flags.integer({ - description: "block network height number to rollback", - default: -1, - }), - }; - - public async run(): Promise { - // tslint:disable-next-line:no-shadowed-variable - const { flags } = this.parse(RollbackCommand); - - await setUpLite(flags); - - const logger = app.resolvePlugin("logger"); - - if (flags.height === -1) { - logger.warn("Rollback height is not specified. Rolling back to last completed round."); - } - - logger.info(`Starting the process of blockchain rollback to block height of ${flags.height.toLocaleString()}`); - - await app.resolvePlugin("snapshots").rollbackChain(flags.height); - } -} diff --git a/deprecated/core-snapshots-cli/src/commands/truncate.ts b/deprecated/core-snapshots-cli/src/commands/truncate.ts deleted file mode 100644 index 1d6a8fa2d3..0000000000 --- a/deprecated/core-snapshots-cli/src/commands/truncate.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { app } from "@arkecosystem/core-container"; -import { SnapshotManager } from "@arkecosystem/core-snapshots"; -import { setUpLite } from "../utils"; -import { BaseCommand } from "./command"; - -export class TruncateCommand extends BaseCommand { - public static description: string = "truncate blockchain database"; - - public async run(): Promise { - // tslint:disable-next-line:no-shadowed-variable - const { flags } = this.parse(TruncateCommand); - - await setUpLite(flags); - - await app.resolvePlugin("snapshots").truncateChain(); - } -} diff --git a/deprecated/core-snapshots-cli/src/commands/verify.ts b/deprecated/core-snapshots-cli/src/commands/verify.ts deleted file mode 100644 index 23f2303422..0000000000 --- a/deprecated/core-snapshots-cli/src/commands/verify.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { app } from "@arkecosystem/core-container"; -import { Logger } from "@arkecosystem/core-interfaces"; -import { SnapshotManager } from "@arkecosystem/core-snapshots"; -import { flags } from "@oclif/command"; -import fs from "fs-extra"; -import { setUpLite } from "../utils"; -import { BaseCommand } from "./command"; - -export class VerifyCommand extends BaseCommand { - public static description: string = "check validity of specified snapshot"; - - public static flags = { - ...BaseCommand.flags, - blocks: flags.string({ - description: "blocks to verify, corelates to folder name", - }), - codec: flags.string({ - description: "codec name, default is msg-lite binary", - }), - signatureVerify: flags.boolean({ - description: "signature verification", - }), - }; - - public async run(): Promise { - // tslint:disable-next-line:no-shadowed-variable - const { flags } = this.parse(VerifyCommand); - - await setUpLite(flags); - - await app.resolvePlugin("snapshots").verifyData(flags); - } -} diff --git a/deprecated/core-snapshots-cli/src/index.ts b/deprecated/core-snapshots-cli/src/index.ts deleted file mode 100644 index 8bdb76f9a0..0000000000 --- a/deprecated/core-snapshots-cli/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { run } from "@oclif/command"; diff --git a/deprecated/core-snapshots-cli/src/utils.ts b/deprecated/core-snapshots-cli/src/utils.ts deleted file mode 100644 index 779bcdfc6f..0000000000 --- a/deprecated/core-snapshots-cli/src/utils.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { app } from "@arkecosystem/core-container"; - -export const setUpLite = async options => { - process.env.CORE_SKIP_BLOCKCHAIN = "true"; - - await app.setUp("2.0.0", options, { - include: [ - "@arkecosystem/core-logger", - "@arkecosystem/core-logger-winston", - "@arkecosystem/core-event-emitter", - "@arkecosystem/core-snapshots", - ], - }); - - return app; -}; - -export const tearDown = async () => app.tearDown(); diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile index 7f569c32d2..dd2558f019 100644 --- a/docker/production/Dockerfile +++ b/docker/production/Dockerfile @@ -4,13 +4,13 @@ WORKDIR /home/node/core ADD docker/production/entrypoint.sh /entrypoint.sh -COPY ./ /home/node/core +ARG core_channel=latest RUN apk add --no-cache --virtual .build-deps make gcc g++ python git \ && apk add --no-cache bash sudo git openntpd openssl \ && npm i pm2 -g --loglevel notice \ - && yarn global add @arkecosystem/core \ - && yarn cache clean \ + && su node -c "yarn global add @arkecosystem/core@${core_channel}" \ + && su node -c "yarn cache clean" \ && apk del .build-deps \ && echo 'node ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers diff --git a/docker/production/LICENSE b/docker/production/LICENSE index d6dd75272f..ecb1fa8e07 100644 --- a/docker/production/LICENSE +++ b/docker/production/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) Ark Ecosystem +Copyright (c) ARK Ecosystem Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/docker/production/README.md b/docker/production/README.md index c6e28c2bc2..e9f4db8d9b 100644 --- a/docker/production/README.md +++ b/docker/production/README.md @@ -1,4 +1,4 @@ -# Ark Core Docker +# Persona Core Docker

@@ -6,7 +6,7 @@ ## Introduction -Official Production ready ARK Core images available now at [Docker Hub](https://hub.docker.com/r/arkecosystem/core). +Official Production ready Persona Core images available now at [Docker Hub](https://hub.docker.com/r/arkecosystem/core). ## Documentation @@ -18,7 +18,7 @@ Official Production ready ARK Core images available now at [Docker Hub](https:// - API v1 : https://docs.ark.io/api/public/v1/ - API v2 : https://docs.ark.io/api/public/v2/ -## ARK Core Relay +## Persona Core Relay Run Relay only node using [Docker Compose](https://docs.docker.com/compose/) @@ -189,4 +189,4 @@ _If you prefer to use custom DB Name, DB User and DB Password simply adjust vari docker-compose up -d ``` -### _ARK Core docker image allows you to run a `forger`. However it requires some additional steps that can be found by visiting our [Documentation page](https://docs.ark.io/guidebook/core/docker.html)._ +### _Persona Core docker image allows you to run a `forger`. However it requires some additional steps that can be found by visiting our [Documentation page](https://docs.ark.io/guidebook/core/docker.html)._ diff --git a/docker/production/ark-core-docker.png b/docker/production/ark-core-docker.png index bf45a7f655..b54d095b4d 100644 Binary files a/docker/production/ark-core-docker.png and b/docker/production/ark-core-docker.png differ diff --git a/docker/production/devnet/docker-compose-build.yml b/docker/production/devnet/docker-compose-build.yml index f3fba47443..5796a86785 100644 --- a/docker/production/devnet/docker-compose-build.yml +++ b/docker/production/devnet/docker-compose-build.yml @@ -36,7 +36,6 @@ services: - ~/.config/ark-core:/home/node/.config/ark-core - ~/.local/share/ark-core:/home/node/.local/share/ark-core - ~/.local/state/ark-core:/home/node/.local/state/ark-core - - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro - ./enc:/run/secrets networks: diff --git a/docker/production/devnet/docker-compose.yml b/docker/production/devnet/docker-compose.yml index 04d68c4122..cf906889ce 100644 --- a/docker/production/devnet/docker-compose.yml +++ b/docker/production/devnet/docker-compose.yml @@ -33,7 +33,6 @@ services: - ~/.config/ark-core:/home/node/.config/ark-core - ~/.local/share/ark-core:/home/node/.local/share/ark-core - ~/.local/state/ark-core:/home/node/.local/state/ark-core - - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro - ./enc:/run/secrets networks: diff --git a/docker/production/entrypoint.sh b/docker/production/entrypoint.sh index db3b24fd11..a1d27dfb1a 100755 --- a/docker/production/entrypoint.sh +++ b/docker/production/entrypoint.sh @@ -4,6 +4,7 @@ sudo /usr/sbin/ntpd -s sudo rm -rf /home/node/.config/ark-core/* sudo rm -rf /home/node/.local/state/ark-core/* sudo chown node:node -R /home/node +sudo ln -s /home/node/.yarn/bin/ark /usr/bin/ark ark config:publish --network=$NETWORK sudo rm -f /home/node/.config/ark-core/$NETWORK/.env diff --git a/docker/production/mainnet/docker-compose-build.yml b/docker/production/mainnet/docker-compose-build.yml index a184308630..eec1ff2df4 100644 --- a/docker/production/mainnet/docker-compose-build.yml +++ b/docker/production/mainnet/docker-compose-build.yml @@ -36,7 +36,6 @@ services: - ~/.config/ark-core:/home/node/.config/ark-core - ~/.local/share/ark-core:/home/node/.local/share/ark-core - ~/.local/state/ark-core:/home/node/.local/state/ark-core - - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro - ./enc:/run/secrets networks: diff --git a/docker/production/mainnet/docker-compose.yml b/docker/production/mainnet/docker-compose.yml index d308f961e3..e109aacaa5 100644 --- a/docker/production/mainnet/docker-compose.yml +++ b/docker/production/mainnet/docker-compose.yml @@ -33,7 +33,6 @@ services: - ~/.config/ark-core:/home/node/.config/ark-core - ~/.local/share/ark-core:/home/node/.local/share/ark-core - ~/.local/state/ark-core:/home/node/.local/state/ark-core - - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro - ./enc:/run/secrets networks: diff --git a/install.sh b/install.sh deleted file mode 100644 index 5d78d8dab3..0000000000 --- a/install.sh +++ /dev/null @@ -1,256 +0,0 @@ -#!/usr/bin/env bash - -# Typography -red=$(tput setaf 1) -green=$(tput setaf 2) -yellow=$(tput setaf 3) -lila=$(tput setaf 4) -pink=$(tput setaf 5) -blue=$(tput setaf 6) -white=$(tput setaf 7) -black=$(tput setaf 8) - -bold=$(tput bold) -reset=$(tput sgr0) - -heading () -{ - echo " ${lila}==>${reset}${bold} $1${reset}" -} - -success () -{ - echo " ${green}==>${reset}${bold} $1${reset}" -} - -info () -{ - echo " ${blue}==>${reset}${bold} $1${reset}" -} - -warning () -{ - echo " ${yellow}==>${reset}${bold} $1${reset}" -} - -error () -{ - echo " ${red}==>${reset}${bold} $1${reset}" -} - -# Detect pkg type -DEB=$(which apt-get) -RPM=$(which yum) - -# Detect SystemV / SystemD -SYS=$([[ -L "/sbin/init" ]] && echo 'SystemD' || echo 'SystemV') - -if [[ ! -z $DEB ]]; then - success "Running install for Debian derivate" -elif [[ ! -z $RPM ]]; then - success "Running install for RedHat derivate" -else - heading "Not supported system" - exit 1; -fi - -if [[ $(locale -a | grep ^en_US.UTF-8) ]] || [[ $(locale -a | grep ^en_US.utf8) ]]; then - if ! $(grep -E "(en_US.UTF-8)" "$HOME/.bashrc"); then - # Setting the bashrc locale - echo "export LC_ALL=en_US.UTF-8" >> "$HOME/.bashrc" - echo "export LANG=en_US.UTF-8" >> "$HOME/.bashrc" - echo "export LANGUAGE=en_US.UTF-8" >> "$HOME/.bashrc" - - # Setting the current shell locale - export LC_ALL="en_US.UTF-8" - export LANG="en_US.UTF-8" - export LANGUAGE="en_US.UTF-8" - fi -else - # Install en_US.UTF-8 Locale - if [[ ! -z $DEB ]]; then - sudo locale-gen en_US.UTF-8 - sudo update-locale LANG=en_US.UTF-8 - elif [[ ! -z $RPM ]]; then - sudo localedef -c -i en_US -f UTF-8 en_US.UTF-8 - fi - - # Setting the current shell locale - export LC_ALL="en_US.UTF-8" - export LANG="en_US.UTF-8" - export LANGUAGE="en_US.UTF-8" - - # Setting the bashrc locale - echo "export LC_ALL=en_US.UTF-8" >> "$HOME/.bashrc" - echo "export LANG=en_US.UTF-8" >> "$HOME/.bashrc" - echo "export LANGUAGE=en_US.UTF-8" >> "$HOME/.bashrc" -fi - -heading "Installing system dependencies..." - -if [[ ! -z $DEB ]]; then - sudo apt-get update - sudo apt-get install -y git curl apt-transport-https update-notifier -elif [[ ! -z $RPM ]]; then - sudo yum update -y - sudo yum install git curl epel-release -y -fi - -success "Installed system dependencies!" - -heading "Installing node.js & npm..." - -sudo rm -rf /usr/local/{lib/node{,/.npm,_modules},bin,share/man}/{npm*,node*,man1/node*} -sudo rm -rf ~/{.npm,.forever,.node*,.cache,.nvm} - -if [[ ! -z $DEB ]]; then - sudo wget --quiet -O - https://deb.nodesource.com/gpgkey/nodesource.gpg.key | sudo apt-key add - - (echo "deb https://deb.nodesource.com/node_10.x $(lsb_release -s -c) main" | sudo tee /etc/apt/sources.list.d/nodesource.list) - sudo apt-get update - sudo apt-get install nodejs -y -elif [[ ! -z $RPM ]]; then - sudo yum install gcc-c++ make -y - curl -sL https://rpm.nodesource.com/setup_10.x | sudo -E bash - > /dev/null 2>&1 -fi - -success "Installed node.js & npm!" - -heading "Installing Yarn..." - -if [[ ! -z $DEB ]]; then - curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - - (echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list) - - sudo apt-get update - sudo apt-get install -y yarn -elif [[ ! -z $RPM ]]; then - curl -sL https://dl.yarnpkg.com/rpm/yarn.repo | sudo tee /etc/yum.repos.d/yarn.repo - sudo yum install yarn -y -fi - -success "Installed Yarn!" - -heading "Installing PM2..." - -sudo yarn global add pm2 -pm2 install pm2-logrotate -pm2 set pm2-logrotate:max_size 500M -pm2 set pm2-logrotate:compress true -pm2 set pm2-logrotate:retain 7 - -success "Installed PM2!" - -heading "Installing program dependencies..." - -if [[ ! -z $DEB ]]; then - sudo apt-get install build-essential libcairo2-dev pkg-config libtool autoconf automake python libpq-dev jq -y -elif [[ ! -z $RPM ]]; then - sudo yum groupinstall "Development Tools" -y -q - sudo yum install postgresql-devel jq -y -q -fi - -success "Installed program dependencies!" - -heading "Installing PostgreSQL..." - -if [[ ! -z $DEB ]]; then - sudo apt-get update - sudo apt-get install postgresql postgresql-contrib -y -elif [[ ! -z $RPM ]]; then - sudo yum install postgresql-server postgresql-contrib -y - - if [[ "$SYS" == "SystemV" ]]; then - sudo service postgresql initdb - sudo service postgresql start - else - sudo postgresql-setup initdb - sudo systemctl start postgresql - fi -fi - -success "Installed PostgreSQL!" - -heading "Installing NTP..." - -sudo timedatectl set-ntp off > /dev/null 2>&1 # disable the default systemd timesyncd service - -if [[ ! -z $DEB ]]; then - sudo apt-get install ntp -yyq -elif [[ ! -z $RPM ]]; then - sudo yum install ntp -y -q -fi - -sudo ntpd -gq - -success "Installed NTP!" - -heading "Installing system updates..." - -if [[ ! -z $DEB ]]; then - sudo apt-get update - sudo apt-get upgrade -yqq - sudo apt-get dist-upgrade -yq - sudo apt-get autoremove -yyq - sudo apt-get autoclean -yq -elif [[ ! -z $RPM ]]; then - sudo yum update - sudo yum clean -fi - -success "Installed system updates!" - -heading "Installing Ark Core..." - -yarn global add @arkecosystem/core -echo 'export PATH=$(yarn global bin):$PATH' >> ~/.bashrc -export PATH=$(yarn global bin):$PATH -ark config:publish - -success "Installed Ark Core!" - -# setup postgres username, password and database -read -p "Would you like to configure the database? [y/N]: " choice - -if [[ "$choice" =~ ^(yes|y|Y) ]]; then - read -p "Enter the database username: " databaseUsername - read -p "Enter the database password: " databasePassword - read -p "Enter the database name: " databaseName - - ark env:set CORE_DB_USERNAME $databaseUsername - ark env:set CORE_DB_PASSWORD $databasePassword - ark env:set CORE_DB_DATABASE $databaseName - - userExists=$(sudo -i -u postgres psql -c "SELECT * FROM pg_user WHERE usename = '${databaseUsername}'" | grep -c "1 row") - databaseExists=$(sudo -i -u postgres psql -tAc "SELECT 1 FROM pg_database WHERE datname = '${databaseName}'") - - if [[ $userExists == 1 ]]; then - read -p "The database user ${databaseUsername} already exists, do you want to overwrite it? [y/N]: " choice - - if [[ "$choice" =~ ^(yes|y|Y) ]]; then - if [[ $databaseExists == 1 ]]; then - sudo -i -u postgres psql -c "ALTER DATABASE ${databaseName} OWNER TO postgres;" - fi - sudo -i -u postgres psql -c "DROP USER ${databaseUsername}" - sudo -i -u postgres psql -c "CREATE USER ${databaseUsername} WITH PASSWORD '${databasePassword}' CREATEDB;" - elif [[ "$choice" =~ ^(no|n|N) ]]; then - continue; - fi - else - sudo -i -u postgres psql -c "CREATE USER ${databaseUsername} WITH PASSWORD '${databasePassword}' CREATEDB;" - fi - - if [[ $databaseExists == 1 ]]; then - read -p "The database ${databaseName} already exists, do you want to overwrite it? [y/N]: " choice - - if [[ "$choice" =~ ^(yes|y|Y) ]]; then - sudo -i -u postgres psql -c "DROP DATABASE ${databaseName};" - sudo -i -u postgres psql -c "CREATE DATABASE ${databaseName} WITH OWNER ${databaseUsername};" - elif [[ "$choice" =~ ^(no|n|N) ]]; then - sudo -i -u postgres psql -c "ALTER DATABASE ${databaseName} OWNER TO ${databaseUsername};" - fi - else - sudo -i -u postgres psql -c "CREATE DATABASE ${databaseName} WITH OWNER ${databaseUsername};" - fi -fi - -exec "$BASH" diff --git a/jest-preset.json b/jest-preset.json deleted file mode 100644 index 8e1d472cbb..0000000000 --- a/jest-preset.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "testEnvironment": "node", - "bail": true, - "verbose": true, - "transform": { - "^.+\\.tsx?$": "ts-jest" - }, - "testMatch": ["**/*.test.ts"], - "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"], - "collectCoverage": false, - "coverageDirectory": "/.coverage", - "collectCoverageFrom": ["src/**/*.ts", "!**/node_modules/**"], - "watchman": false, - "setupTestFrameworkScriptFile": "jest-extended" -} diff --git a/lerna.json b/lerna.json index fb66475ebd..8d2adf7681 100644 --- a/lerna.json +++ b/lerna.json @@ -3,5 +3,5 @@ "npmClient": "yarn", "packages": ["packages/*", "plugins/*"], "useWorkspaces": true, - "version": "2.2.1" + "version": "2.3.15" } diff --git a/package.json b/package.json index 1f9a760529..52068a76a7 100644 --- a/package.json +++ b/package.json @@ -1,78 +1,97 @@ { "private": true, "name": "core", - "description": "The packages that make up the Ark Core", + "description": "The packages that make up the Persona Core", "scripts": { "lerna": "./node_modules/lerna/cli.js", - "setup": "yarn && yarn clean && yarn bootstrap && yarn build", + "setup": "yarn && yarn bootstrap && yarn build", + "setup:clean": "yarn && yarn clean && yarn bootstrap && yarn build", "bootstrap": "yarn lerna bootstrap", "clean": "yarn lerna clean --yes", "build": "yarn lerna run build", - "publish:alpha": "yarn lerna run publish:alpha", - "publish:beta": "yarn lerna run publish:beta", - "publish:rc": "yarn lerna run publish:rc", - "publish:latest": "yarn lerna run publish:latest", - "lint": "yarn lerna run lint", + "lint": "./node_modules/tslint/bin/tslint -c ./tslint.json './packages/**/*/src/**/*.ts' --fix", "format": "yarn lint && yarn prettier", "prettier": "prettier --write \"./*.{ts,js,json,md}\" \"./packages/**/*.{ts,js,json,md}\"", + "lint:tests": "./node_modules/tslint/bin/tslint -c ./tslint.json '__tests__/**/*.ts' --fix", + "format:tests": "yarn lint:tests && yarn prettier:tests", + "prettier:tests": "prettier --write \"./__tests__/**/*.{ts,js,json,md}\"", "test": "cross-env CORE_ENV=test jest --runInBand --forceExit", "test:coverage": "cross-env CORE_ENV=test jest --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$' --runInBand --forceExit", + "test:debug": "cross-env CORE_ENV=test node --inspect-brk ./node_modules/.bin/jest --runInBand", + "test:watch": "cross-env CORE_ENV=test jest --runInBand --watch", + "test:watch:all": "cross-env CORE_ENV=test jest --runInBand --watchAll", + "test:unit": "cross-env CORE_ENV=test jest ./__tests__/unit", + "test:unit:coverage": "cross-env CORE_ENV=test jest ./__tests__/unit --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$'", + "test:unit:debug": "cross-env CORE_ENV=test node --inspect-brk ./node_modules/.bin/jest ./__tests__/unit", + "test:unit:watch": "cross-env CORE_ENV=test jest ./__tests__/unit --watch", + "test:unit:watch:all": "cross-env CORE_ENV=test jest ./__tests__/unit --watchAll", + "test:integration": "cross-env CORE_ENV=test jest ./__tests__/integration --runInBand --forceExit", + "test:integration:coverage": "cross-env CORE_ENV=test jest ./__tests__/integration --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$' --runInBand --forceExit", + "test:integration:debug": "cross-env CORE_ENV=test node --inspect-brk ./node_modules/.bin/jest ./__tests__/integration --runInBand", + "test:integration:watch": "cross-env CORE_ENV=test jest ./__tests__/integration --runInBand --watch", + "test:integration:watch:all": "cross-env CORE_ENV=test jest ./__tests__/integration --runInBand --watchAll", + "test:functional": "cross-env CORE_ENV=test jest ./__tests__/functional --runInBand --forceExit", + "test:functional:coverage": "cross-env CORE_ENV=test jest ./__tests__/functional --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$' --runInBand --forceExit", + "test:functional:debug": "cross-env CORE_ENV=test node --inspect-brk ./node_modules/.bin/jest ./__tests__/functional --runInBand", + "test:functional:watch": "cross-env CORE_ENV=test jest ./__tests__/functional --runInBand --watch", + "test:functional:watch:all": "cross-env CORE_ENV=test jest ./__tests__/functional --runInBand --watchAll", "snyk": "./node_modules/.bin/snyk protect", + "publish:alpha": "cross-env-shell ./scripts/publish/alpha.sh", + "publish:beta": "cross-env-shell ./scripts/publish/beta.sh", + "publish:rc": "cross-env-shell ./scripts/publish/rc.sh", + "publish:next": "cross-env-shell ./scripts/publish/next.sh", + "publish:latest": "cross-env-shell ./scripts/publish/latest.sh", "upgrade": "cross-env-shell ./scripts/upgrade.sh", "version": "cross-env-shell ./scripts/version.sh", - "updates": "yarn lerna run updates", + "deps": "cross-env-shell ./scripts/deps.sh", "docker": "node ./scripts/docker/generate-docker.js", "bench": "node benchmark/index.js" }, "devDependencies": { - "@babel/core": "^7.2.2", - "@babel/preset-env": "^7.2.0", + "@babel/core": "^7.3.4", + "@babel/preset-env": "^7.3.4", "@faustbrian/benchmarker": "^0.1.2", - "@sindresorhus/tsconfig": "^0.1.1", - "@types/babel__core": "^7.0.4", - "@types/body-parser": "^1.17.0", - "@types/express": "^4.16.0", - "@types/jest": "^23.3.10", - "@types/js-yaml": "^3.11.4", - "@types/node": "^10.12.17", - "@types/prettier": "^1.15.2", + "@sindresorhus/tsconfig": "^0.2.1", + "@types/babel__core": "^7.1.0", + "@types/jest": "^24.0.9", + "@types/js-yaml": "^3.12.0", + "@types/node": "^11.10.5", + "@types/prettier": "^1.16.1", "@types/pretty-ms": "^4.0.0", "@types/request-promise": "^4.1.42", "@types/rimraf": "^2.0.2", "@types/uuid": "^3.4.4", - "@types/webpack": "^4.4.23", + "@types/webpack": "^4.4.25", "@types/webpack-merge": "^4.1.3", "@types/webpack-node-externals": "^1.6.3", - "axios": "^0.18.0", - "babel-loader": "^8.0.4", - "body-parser": "^1.18.3", - "codecov": "^3.1.0", + "@oclif/dev-cli": "^1.21.3", + "babel-loader": "^8.0.5", + "codecov": "^3.2.0", "cross-env": "^5.2.0", "del-cli": "^1.1.0", - "docdash": "^1.0.1", - "express": "^4.16.4", - "husky": "^1.3.0", - "jest": "^23.6.0", - "jest-extended": "^0.11.0", - "js-yaml": "^3.12.0", - "lerna": "^3.6.0", - "lint-staged": "^8.1.0", - "npm-check-updates": "^2.15.0", - "prettier": "^1.15.3", - "prompts": "^2.0.1", + "docdash": "^1.0.3", + "husky": "^1.3.1", + "jest": "^24.3.1", + "jest-extended": "^0.11.1", + "js-yaml": "^3.12.2", + "lerna": "^3.13.1", + "lint-staged": "^8.1.5", + "nock": "^10.0.6", + "npm-check-updates": "^3.0.2", + "prettier": "^1.16.4", + "prompts": "^2.0.3", "regenerator-runtime": "^0.13.1", - "request-promise": "^4.2.2", - "rimraf": "^2.6.2", - "snyk": "^1.118.0", - "ts-jest": "^23.10.5", - "tslint": "^5.12.0", - "tslint-config-prettier": "^1.17.0", - "typedoc": "^0.13.0", - "typescript": "^3.2.2", + "request-promise": "^4.2.4", + "rimraf": "^2.6.3", + "snyk": "^1.136.1", + "ts-jest": "^24.0.0", + "tslint": "^5.13.1", + "tslint-config-prettier": "^1.18.0", + "typescript": "^3.3.3333", "uuid": "^3.3.2", - "webpack": "^4.27.1", - "webpack-cli": "^3.1.2", - "webpack-merge": "^4.1.5", + "webpack": "^4.29.6", + "webpack-cli": "^3.2.3", + "webpack-merge": "^4.2.1", "webpack-node-externals": "^1.7.2" }, "workspaces": [ @@ -85,10 +104,32 @@ } }, "jest": { - "preset": "./jest-preset.json", + "testEnvironment": "node", + "bail": true, + "verbose": true, + "transform": { + "^.+\\.tsx?$": "ts-jest" + }, + "testMatch": [ + "**/*.test.ts" + ], + "moduleFileExtensions": [ + "ts", + "tsx", + "js", + "jsx", + "json", + "node" + ], + "collectCoverage": false, + "coverageDirectory": "/.coverage", "collectCoverageFrom": [ "packages/**/src/**/*.ts", "!**/node_modules/**" + ], + "watchman": false, + "setupFilesAfterEnv": [ + "jest-extended" ] } } diff --git a/packages/core-api/.gitattributes b/packages/core-api/.gitattributes index 60cc52db63..63f6a5b970 100644 --- a/packages/core-api/.gitattributes +++ b/packages/core-api/.gitattributes @@ -2,10 +2,6 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore -/__tests__ export-ignore -/docs export-ignore /README.md export-ignore diff --git a/packages/core-api/README.md b/packages/core-api/README.md index 39b72d5bd4..bea06a3c92 100644 --- a/packages/core-api/README.md +++ b/packages/core-api/README.md @@ -1,12 +1,12 @@ -# Ark Core - Public API +# Persona Core - Public API

- +

## Documentation -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core-api.html). +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/optional/core-api.html). ## Security @@ -14,11 +14,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [Brian Faust](https://github.com/faustbrian) -- [Joshua Noack](https://github.com/supaiku0) -- [Kristjan Košič](https://github.com/kristjank) -- [All Contributors](../../../../contributors) +This project exists thanks to all the people who [contribute](../../../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-api/__tests__/v2/handlers/delegates.test.ts b/packages/core-api/__tests__/v2/handlers/delegates.test.ts deleted file mode 100644 index 8a7cb3f070..0000000000 --- a/packages/core-api/__tests__/v2/handlers/delegates.test.ts +++ /dev/null @@ -1,232 +0,0 @@ -import "@arkecosystem/core-test-utils"; -import { calculateRanks, setUp, tearDown } from "../../__support__/setup"; -import { utils } from "../utils"; - -import { blocks2to100 } from "../../../../core-test-utils/src/fixtures/testnet/blocks2to100"; - -import { Bignum, models } from "@arkecosystem/crypto"; -const { Block } = models; - -import { app } from "@arkecosystem/core-container"; -import { Database } from "@arkecosystem/core-interfaces"; - -const delegate = { - username: "genesis_9", - address: "AG8kwwk4TsYfA2HdwaWBVAJQBj6VhdcpMo", - publicKey: "0377f81a18d25d77b100cb17e829a72259f08334d064f6c887298917a04df8f647", -}; - -beforeAll(async () => { - await setUp(); - await calculateRanks(); -}); - -afterAll(async () => { - await tearDown(); -}); - -describe("API 2.0 - Delegates", () => { - describe("GET /delegates", () => { - describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( - "using the %s header", - (header, request) => { - it("should GET all the delegates", async () => { - const response = await utils[request]("GET", "delegates"); - expect(response).toBeSuccessfulResponse(); - expect(response.data.data).toBeArray(); - - response.data.data.forEach(utils.expectDelegate); - expect(response.data.data.sort((a, b) => a.rank < b.rank)).toEqual(response.data.data); - }); - - it("should GET all the delegates sorted by votes,asc", async () => { - const wm = app.resolvePlugin("database").walletManager; - const wallet = wm.findByUsername("genesis_51"); - wallet.voteBalance = new Bignum(1); - wm.reindex(wallet); - - const response = await utils[request]("GET", "delegates", { orderBy: "votes:asc" }); - expect(response).toBeSuccessfulResponse(); - expect(response.data.data).toBeArray(); - - expect(response.data.data[0].username).toBe(wallet.username); - expect(response.data.data[0].votes).toBe(+wallet.voteBalance.toFixed()); - }); - - it("should GET all the delegates sorted by votes,desc", async () => { - const wm = app.resolvePlugin("database").walletManager; - const wallet = wm.findByUsername("genesis_1"); - wallet.voteBalance = new Bignum(12500000000000000); - wm.reindex(wallet); - - const response = await utils[request]("GET", "delegates", { orderBy: "votes:desc" }); - expect(response).toBeSuccessfulResponse(); - expect(response.data.data).toBeArray(); - - expect(response.data.data[0].username).toBe(wallet.username); - expect(response.data.data[0].votes).toBe(+wallet.voteBalance.toFixed()); - }); - }, - ); - - describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( - "using the %s header", - (header, request) => { - it("should GET all the delegates ordered by descending rank", async () => { - const response = await utils[request]("GET", "delegates", { orderBy: "rank:desc" }); - expect(response).toBeSuccessfulResponse(); - expect(response.data.data).toBeArray(); - - response.data.data.forEach(utils.expectDelegate); - expect(response.data.data.sort((a, b) => a.rank > b.rank)).toEqual(response.data.data); - }); - }, - ); - - describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( - "using the %s header", - (header, request) => { - it("should GET all the delegates ordered by descending productivity", async () => { - const response = await utils[request]("GET", "delegates", { orderBy: "productivity:desc" }); - expect(response).toBeSuccessfulResponse(); - expect(response.data.data).toBeArray(); - - response.data.data.forEach(utils.expectDelegate); - expect( - response.data.data.sort((a, b) => a.production.productivity > b.production.productivity), - ).toEqual(response.data.data); - }); - }, - ); - - describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( - "using the %s header", - (header, request) => { - it("should GET all the delegates ordered by descending approval", async () => { - const response = await utils[request]("GET", "delegates", { orderBy: "approval:desc" }); - expect(response).toBeSuccessfulResponse(); - expect(response.data.data).toBeArray(); - - response.data.data.forEach(utils.expectDelegate); - expect(response.data.data.sort((a, b) => a.production.approval > b.production.approval)).toEqual( - response.data.data, - ); - }); - }, - ); - }); - - describe("GET /delegates/:id", () => { - describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( - "using the %s header", - (header, request) => { - it("should GET a delegate by the given username", async () => { - const response = await utils[request]("GET", `delegates/${delegate.username}`); - expect(response).toBeSuccessfulResponse(); - expect(response.data.data).toBeObject(); - - utils.expectDelegate(response.data.data, delegate); - }); - }, - ); - - describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( - "using the %s header", - (header, request) => { - it("should GET a delegate by the given address", async () => { - const response = await utils[request]("GET", `delegates/${delegate.address}`); - expect(response).toBeSuccessfulResponse(); - expect(response.data.data).toBeObject(); - - utils.expectDelegate(response.data.data, delegate); - }); - }, - ); - - describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( - "using the %s header", - (header, request) => { - it("should GET a delegate by the given public key", async () => { - const response = await utils[request]("GET", `delegates/${delegate.publicKey}`); - expect(response).toBeSuccessfulResponse(); - expect(response.data.data).toBeObject(); - - utils.expectDelegate(response.data.data, delegate); - }); - }, - ); - }); - - describe("POST /delegates/search", () => { - describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( - "using the %s header", - (header, request) => { - it("should POST a search for delegates with a username that matches the given string", async () => { - const response = await utils[request]("POST", "delegates/search", { - username: delegate.username, - }); - expect(response).toBeSuccessfulResponse(); - expect(response.data.data).toBeArray(); - - expect(response.data.data).toHaveLength(1); - - utils.expectDelegate(response.data.data[0], delegate); - }); - }, - ); - }); - - describe("GET /delegates/:id/blocks", () => { - describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( - "using the %s header", - (header, request) => { - it("should GET all blocks for a delegate by the given identifier", async () => { - // save a new block so that we can make the request with generatorPublicKey - const block2 = new Block(blocks2to100[0]); - const databaseService = app.resolvePlugin("database"); - await databaseService.saveBlock(block2); - - const response = await utils[request]( - "GET", - `delegates/${blocks2to100[0].generatorPublicKey}/blocks`, - ); - expect(response).toBeSuccessfulResponse(); - expect(response.data.data).toBeArray(); - response.data.data.forEach(utils.expectBlock); - - await databaseService.deleteBlock(block2); // reset to genesis block - }); - }, - ); - }); - - describe("GET /delegates/:id/voters", () => { - describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( - "using the %s header", - (header, request) => { - it("should GET all voters (wallets) for a delegate by the given identifier", async () => { - const response = await utils[request]("GET", `delegates/${delegate.publicKey}/voters`); - expect(response).toBeSuccessfulResponse(); - expect(response.data.data).toBeArray(); - - response.data.data.forEach(utils.expectWallet); - expect(response.data.data.sort((a, b) => a.balance > b.balance)).toEqual(response.data.data); - }); - }, - ); - - describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( - "using the %s header", - (header, request) => { - it("should GET all voters (wallets) for a delegate by the given identifier ordered by 'balance:asc'", async () => { - const response = await utils[request]("GET", `delegates/${delegate.publicKey}/voters`); - expect(response).toBeSuccessfulResponse(); - expect(response.data.data).toBeArray(); - - response.data.data.forEach(utils.expectWallet); - expect(response.data.data.sort((a, b) => a.balance < b.balance)).toEqual(response.data.data); - }); - }, - ); - }); -}); diff --git a/packages/core-api/__tests__/v2/handlers/peers.test.ts b/packages/core-api/__tests__/v2/handlers/peers.test.ts deleted file mode 100644 index 30c3af41bd..0000000000 --- a/packages/core-api/__tests__/v2/handlers/peers.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { app } from "@arkecosystem/core-container"; -import { Peer } from "@arkecosystem/core-p2p/src/peer"; -import "@arkecosystem/core-test-utils"; -import { setUp, tearDown } from "../../__support__/setup"; -import { utils } from "../utils"; - -const mockAddress = "1.0.0.99"; -const mockPort = 4002; - -beforeAll(async () => { - await setUp(); - - const peerMock = new Peer(mockAddress, mockPort); - peerMock.setStatus("OK"); - - const monitor = app.resolvePlugin("p2p"); - monitor.peers = {}; - monitor.peers[peerMock.ip] = peerMock; -}); - -afterAll(async () => { - const monitor = app.resolvePlugin("p2p"); - monitor.peers = {}; - - await tearDown(); -}); - -describe("API 2.0 - Peers", () => { - describe("GET /peers", () => { - describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( - "using the %s header", - (_, request) => { - it("should GET all the peers", async () => { - const response = await utils[request]("GET", "peers"); - expect(response).toBeSuccessfulResponse(); - expect(response.data.data).toBeArray(); - expect(response.data.data[0]).toBeObject(); - }); - }, - ); - }); - - describe("GET /peers/:ip", () => { - describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( - "using the %s header", - (_, request) => { - it("should GET a peer by the given ip", async () => { - const response = await utils[request]("GET", `peers/${mockAddress}`); - expect(response).toBeSuccessfulResponse(); - expect(response.data.data).toBeObject(); - expect(response.data.data.ip).toBe(mockAddress); - expect(response.data.data.port).toBe(mockPort); - }); - }, - ); - }); -}); diff --git a/packages/core-api/package.json b/packages/core-api/package.json index 72146a11e5..9169a17c01 100644 --- a/packages/core-api/package.json +++ b/packages/core-api/package.json @@ -1,7 +1,7 @@ { "name": "@arkecosystem/core-api", - "description": "Public API for Ark Core", - "version": "2.2.1", + "description": "Public API for ARK Core", + "version": "2.3.15", "contributors": [ "Kristjan Košič ", "Brian Faust " @@ -13,38 +13,26 @@ "dist" ], "scripts": { - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", "prepublishOnly": "yarn build", "pretest": "bash ../../scripts/pre-test.sh", "compile": "../../node_modules/typescript/bin/tsc", "build": "yarn clean && yarn compile", "build:watch": "yarn clean && yarn compile -w", - "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "test": "cross-env CORE_ENV=test jest --runInBand --forceExit", - "test:coverage": "cross-env CORE_ENV=test jest --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$' --runInBand --forceExit", - "test:debug": "cross-env CORE_ENV=test node --inspect-brk ../../node_modules/.bin/jest --runInBand", - "test:watch": "cross-env CORE_ENV=test jest --runInBand --watch", - "test:watch:all": "cross-env CORE_ENV=test jest --runInBand --watchAll", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" + "clean": "del dist" }, "dependencies": { - "@arkecosystem/core-container": "^2.2.1", - "@arkecosystem/core-http-utils": "^2.2.1", - "@arkecosystem/core-interfaces": "^2.2.1", - "@arkecosystem/core-transaction-pool": "^2.2.1", - "@arkecosystem/core-utils": "^2.2.1", - "@arkecosystem/crypto": "^2.2.1", - "@arkecosystem/utils": "^0.2.4", + "@arkecosystem/core-container": "^2.3.15", + "@arkecosystem/core-http-utils": "^2.3.15", + "@arkecosystem/core-interfaces": "^2.3.15", + "@arkecosystem/core-transaction-pool": "^2.3.15", + "@arkecosystem/core-utils": "^2.3.15", + "@arkecosystem/crypto": "^2.3.15", + "@arkecosystem/utils": "^0.3.0", + "@faustbrian/dato": "^0.2.0", "@faustbrian/hapi-version": "^0.2.11", - "ajv": "^6.9.1", + "ajv": "^6.10.0", "boom": "^7.3.0", "bs58check": "^2.1.2", - "dayjs-ext": "^2.2.0", "delay": "^4.1.0", "hapi-pagination": "https://github.com/faustbrian/hapi-pagination", "hapi-rate-limit": "^3.1.1", @@ -52,25 +40,22 @@ "joi": "^14.3.1", "lodash.orderby": "^4.6.0", "lodash.partition": "^4.6.0", - "lodash.snakecase": "^4.1.1" + "lodash.snakecase": "^4.1.1", + "semver": "^5.6.0" }, "devDependencies": { - "@arkecosystem/core-test-utils": "^2.2.1", "@types/boom": "^7.2.1", "@types/ip": "^1.1.0", - "@types/joi": "^14.3.1", - "@types/lodash.orderby": "^4.6.4", - "@types/lodash.partition": "^4.6.4", - "@types/lodash.snakecase": "^4.1.4", - "axios": "^0.18.0" + "@types/joi": "^14.3.2", + "@types/lodash.orderby": "^4.6.6", + "@types/lodash.partition": "^4.6.6", + "@types/lodash.snakecase": "^4.1.6", + "@types/semver": "^5.5.0" }, "publishConfig": { "access": "public" }, "engines": { "node": ">=10.x" - }, - "jest": { - "preset": "../../jest-preset.json" } } diff --git a/packages/core-api/src/formats.ts b/packages/core-api/src/formats.ts new file mode 100644 index 0000000000..03da6890e4 --- /dev/null +++ b/packages/core-api/src/formats.ts @@ -0,0 +1,77 @@ +import { app } from "@arkecosystem/core-container"; +import { Ajv } from "ajv"; +import * as bs58check from "bs58check"; +import * as ipAddress from "ip"; + +export const registerFormats = (ajv: Ajv) => { + const config = app.getConfig(); + + ajv.addFormat("address", { + type: "string", + validate: value => { + try { + return bs58check.decode(value)[0] === config.get("network.pubKeyHash"); + } catch (e) { + return false; + } + }, + }); + + ajv.addFormat("csv", { + type: "string", + validate: value => { + try { + const a = value.split(","); + + return a.length > 0 && a.length <= 1000; + } catch (e) { + return false; + } + }, + }); + + ajv.addFormat("hex", { + type: "string", + validate: value => value.match(/^[0-9a-f]+$/i) !== null && value.length % 2 === 0, + }); + + ajv.addFormat("ip", { + type: "string", + validate: value => ipAddress.isV4Format(value) || ipAddress.isV6Format(value), + }); + + ajv.addFormat("parsedInt", { + type: "string", + validate: (value: any) => { + if (isNaN(value) || parseInt(value, 10) !== value || isNaN(parseInt(value, 10))) { + return false; + } + + value = parseInt(value, 10); + + return true; + }, + }); + + ajv.addFormat("publicKey", { + type: "string", + validate: value => { + try { + return Buffer.from(value, "hex").length === 33; + } catch (e) { + return false; + } + }, + }); + + ajv.addFormat("signature", { + type: "string", + validate: value => { + try { + return Buffer.from(value, "hex").length < 73; + } catch (e) { + return false; + } + }, + }); +}; diff --git a/packages/core-api/src/plugin.ts b/packages/core-api/src/plugin.ts index be469c15b2..52aaa651e3 100644 --- a/packages/core-api/src/plugin.ts +++ b/packages/core-api/src/plugin.ts @@ -8,7 +8,7 @@ export const plugin: Container.PluginDescriptor = { alias: "api", async register(container: Container.IContainer, options) { if (!options.enabled) { - container.resolvePlugin("logger").info("Public API is disabled :grey_exclamation:"); + container.resolvePlugin("logger").info("Public API is disabled"); return false; } diff --git a/packages/core-api/src/plugins/validation/formats/address.ts b/packages/core-api/src/plugins/validation/formats/address.ts deleted file mode 100644 index 7e4ecc56d7..0000000000 --- a/packages/core-api/src/plugins/validation/formats/address.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { app } from "@arkecosystem/core-container"; -import * as bs58check from "bs58check"; - -export function registerAddressFormat(ajv) { - const config = app.getConfig(); - - ajv.addFormat("address", { - type: "string", - validate: value => { - try { - return bs58check.decode(value)[0] === config.get("network.pubKeyHash"); - } catch (e) { - return false; - } - }, - }); -} diff --git a/packages/core-api/src/plugins/validation/formats/csv.ts b/packages/core-api/src/plugins/validation/formats/csv.ts deleted file mode 100644 index f3a2af591f..0000000000 --- a/packages/core-api/src/plugins/validation/formats/csv.ts +++ /dev/null @@ -1,14 +0,0 @@ -export function registerCsvFormat(ajv) { - ajv.addFormat("csv", { - type: "string", - validate: value => { - try { - const a = value.split(","); - - return a.length > 0 && a.length <= 1000; - } catch (e) { - return false; - } - }, - }); -} diff --git a/packages/core-api/src/plugins/validation/formats/hex.ts b/packages/core-api/src/plugins/validation/formats/hex.ts deleted file mode 100644 index 8356d6702b..0000000000 --- a/packages/core-api/src/plugins/validation/formats/hex.ts +++ /dev/null @@ -1,6 +0,0 @@ -export function registerHexFormat(ajv) { - ajv.addFormat("hex", { - type: "string", - validate: value => value.match(/^[0-9a-f]+$/i) !== null && value.length % 2 === 0, - }); -} diff --git a/packages/core-api/src/plugins/validation/formats/ip.ts b/packages/core-api/src/plugins/validation/formats/ip.ts deleted file mode 100644 index 2d0639c83e..0000000000 --- a/packages/core-api/src/plugins/validation/formats/ip.ts +++ /dev/null @@ -1,8 +0,0 @@ -import * as ip from "ip"; - -export function registerIpFormat(ajv) { - ajv.addFormat("ip", { - type: "string", - validate: value => ip.isV4Format(value) || ip.isV6Format(value), - }); -} diff --git a/packages/core-api/src/plugins/validation/formats/parseInt.ts b/packages/core-api/src/plugins/validation/formats/parseInt.ts deleted file mode 100644 index 8e8da2ff7f..0000000000 --- a/packages/core-api/src/plugins/validation/formats/parseInt.ts +++ /dev/null @@ -1,14 +0,0 @@ -export function registerParseIntFormat(ajv) { - ajv.addFormat("parsedInt", { - type: "string", - validate: value => { - if (isNaN(value) || parseInt(value, 10) !== value || isNaN(parseInt(value, 10))) { - return false; - } - - value = parseInt(value, 10); - - return true; - }, - }); -} diff --git a/packages/core-api/src/plugins/validation/formats/publicKey.ts b/packages/core-api/src/plugins/validation/formats/publicKey.ts deleted file mode 100644 index df19146296..0000000000 --- a/packages/core-api/src/plugins/validation/formats/publicKey.ts +++ /dev/null @@ -1,12 +0,0 @@ -export function registerPublicKeyFormat(ajv) { - ajv.addFormat("publicKey", { - type: "string", - validate: value => { - try { - return Buffer.from(value, "hex").length === 33; - } catch (e) { - return false; - } - }, - }); -} diff --git a/packages/core-api/src/plugins/validation/formats/signature.ts b/packages/core-api/src/plugins/validation/formats/signature.ts deleted file mode 100644 index 1f96dac2d4..0000000000 --- a/packages/core-api/src/plugins/validation/formats/signature.ts +++ /dev/null @@ -1,12 +0,0 @@ -export function registerSignatureFormat(ajv) { - ajv.addFormat("signature", { - type: "string", - validate: value => { - try { - return Buffer.from(value, "hex").length < 73; - } catch (e) { - return false; - } - }, - }); -} diff --git a/packages/core-api/src/plugins/validation/formats/vendorField.ts b/packages/core-api/src/plugins/validation/formats/vendorField.ts deleted file mode 100644 index 4b634941c0..0000000000 --- a/packages/core-api/src/plugins/validation/formats/vendorField.ts +++ /dev/null @@ -1,12 +0,0 @@ -export function registerVendorFieldFormat(ajv) { - ajv.addFormat("vendorField", { - type: "string", - validate: value => { - try { - return Buffer.from(value).length < 65; - } catch (e) { - return false; - } - }, - }); -} diff --git a/packages/core-api/src/plugins/validation/index.ts b/packages/core-api/src/plugins/validation/index.ts deleted file mode 100644 index 66d75776ae..0000000000 --- a/packages/core-api/src/plugins/validation/index.ts +++ /dev/null @@ -1,81 +0,0 @@ -import AJV from "ajv"; -import Boom from "boom"; -import * as fs from "fs"; -import Hapi from "hapi"; -import * as path from "path"; - -// SOF: IMPORT CUSTOM AJV FORMATS -import { registerAddressFormat } from "./formats/address"; -import { registerCsvFormat } from "./formats/csv"; -import { registerHexFormat } from "./formats/hex"; -import { registerIpFormat } from "./formats/ip"; -import { registerParseIntFormat } from "./formats/parseInt"; -import { registerPublicKeyFormat } from "./formats/publicKey"; -import { registerSignatureFormat } from "./formats/signature"; -import { registerVendorFieldFormat } from "./formats/vendorField"; -// EOF: IMPORT CUSTOM AJV FORMATS - -const PLUGIN_NAME = "hapi-ajv"; - -const register = async (server: Hapi.Server, options: object): Promise => { - const ajv = new AJV(); - registerCsvFormat(ajv); - registerAddressFormat(ajv); - registerHexFormat(ajv); - registerIpFormat(ajv); - registerParseIntFormat(ajv); - registerPublicKeyFormat(ajv); - registerSignatureFormat(ajv); - registerVendorFieldFormat(ajv); - - const validate = (schema, data) => { - return ajv.validate(schema, data) ? null : ajv.errors; - }; - - const createErrorResponse = (request, h, errors) => { - if (request.pre.apiVersion === 1) { - return h - .response({ - path: errors[0].dataPath, - error: errors[0].message, - success: false, - }) - .takeover(); - } - - return Boom.badData(errors); - }; - - server.ext({ - type: "onPreHandler", - method: (request, h) => { - const config = request.route.settings.plugins[PLUGIN_NAME] || {}; - - let errors; - - if (config.payloadSchema) { - errors = validate(config.payloadSchema, request.payload); - - if (errors) { - return createErrorResponse(request, h, errors); - } - } - - if (config.querySchema) { - errors = validate(config.querySchema, request.query); - - if (errors) { - return createErrorResponse(request, h, errors); - } - } - - return h.continue; - }, - }); -}; - -export = { - register, - name: PLUGIN_NAME, - version: "1.0.0", -}; diff --git a/packages/core-api/src/repositories/blocks.ts b/packages/core-api/src/repositories/blocks.ts index 9d312fbf50..52f4586ff4 100644 --- a/packages/core-api/src/repositories/blocks.ts +++ b/packages/core-api/src/repositories/blocks.ts @@ -2,6 +2,7 @@ import { IRepository } from "../interfaces"; import { Repository } from "./repository"; import { buildFilterQuery } from "./utils/build-filter-query"; +// TODO: Deprecate this with v1 code export class BlockRepository extends Repository implements IRepository { constructor() { super(); @@ -66,7 +67,7 @@ export class BlockRepository extends Repository implements IRepository { /** * Get the last block for the given generator. - * TODO is this right? + * TODO is this right? - This is unused anyway * @param {String} generatorPublicKey * @return {Object} */ diff --git a/packages/core-api/src/repositories/repository.ts b/packages/core-api/src/repositories/repository.ts index 9dae5dcf47..c313bf41c3 100644 --- a/packages/core-api/src/repositories/repository.ts +++ b/packages/core-api/src/repositories/repository.ts @@ -1,12 +1,13 @@ import { app } from "@arkecosystem/core-container"; import { Database, TransactionPool } from "@arkecosystem/core-interfaces"; -import snakeCase from "lodash/snakeCase"; +import snakeCase from "lodash.snakecase"; import { IRepository } from "../interfaces"; +// TODO: Deprecate this with v1 export abstract class Repository implements IRepository { public databaseService = app.resolvePlugin("database"); public cache = this.databaseService.cache; - public transactionPool = app.resolvePlugin("transactionPool"); + public transactionPool = app.resolvePlugin("transaction-pool"); public model = this.getModel(); public query = this.model.query(); public columns: string[] = []; diff --git a/packages/core-api/src/repositories/transactions.ts b/packages/core-api/src/repositories/transactions.ts index 59a0a6dbc4..c46360f6ca 100644 --- a/packages/core-api/src/repositories/transactions.ts +++ b/packages/core-api/src/repositories/transactions.ts @@ -1,11 +1,12 @@ import { constants, slots } from "@arkecosystem/crypto"; -import dayjs from "dayjs-ext"; -import partition from "lodash/partition"; -import snakeCase from "lodash/snakeCase"; +import { dato } from "@faustbrian/dato"; +import partition from "lodash.partition"; +import snakeCase from "lodash.snakecase"; import { IRepository } from "../interfaces"; import { Repository } from "./repository"; import { buildFilterQuery } from "./utils/build-filter-query"; +// TODO: Deprecate this with v1 export class TransactionsRepository extends Repository implements IRepository { constructor() { super(); @@ -68,7 +69,7 @@ export class TransactionsRepository extends Repository implements IRepository { */ public async findAllLegacy(parameters: any = {}): Promise { const selectQuery = this.query - .select(this.query.block_id, this.query.serialized, this.query.timestamp) + .select(this.query.id, this.query.block_id, this.query.serialized, this.query.timestamp) .from(this.query); if (parameters.senderId) { @@ -114,7 +115,7 @@ export class TransactionsRepository extends Repository implements IRepository { */ public async findAllByWallet(wallet, parameters: any = {}): Promise { const selectQuery = this.query - .select(this.query.block_id, this.query.serialized, this.query.timestamp) + .select(this.query.id, this.query.block_id, this.query.serialized, this.query.timestamp) .from(this.query); const applyConditions = queries => { @@ -201,7 +202,7 @@ export class TransactionsRepository extends Repository implements IRepository { */ public async findById(id): Promise { const query = this.query - .select(this.query.block_id, this.query.serialized, this.query.timestamp) + .select(this.query.id, this.query.block_id, this.query.serialized, this.query.timestamp) .from(this.query) .where(this.query.id.equals(id)); @@ -218,7 +219,7 @@ export class TransactionsRepository extends Repository implements IRepository { */ public async findByTypeAndId(type, id): Promise { const query = this.query - .select(this.query.block_id, this.query.serialized, this.query.timestamp) + .select(this.query.id, this.query.block_id, this.query.serialized, this.query.timestamp) .from(this.query) .where(this.query.id.equals(id).and(this.query.type.equals(type))); @@ -234,7 +235,7 @@ export class TransactionsRepository extends Repository implements IRepository { */ public async findByIds(ids): Promise { const query = this.query - .select(this.query.block_id, this.query.serialized, this.query.timestamp) + .select(this.query.id, this.query.block_id, this.query.serialized, this.query.timestamp) .from(this.query) .where(this.query.id.in(ids)); @@ -247,7 +248,7 @@ export class TransactionsRepository extends Repository implements IRepository { */ public async findWithVendorField(): Promise { const query = this.query - .select(this.query.block_id, this.query.serialized, this.query.timestamp) + .select(this.query.id, this.query.block_id, this.query.serialized, this.query.timestamp) .from(this.query) .where(this.query.vendor_field_hex.isNotNull()); @@ -273,9 +274,9 @@ export class TransactionsRepository extends Repository implements IRepository { .where( this.query.timestamp.gte( slots.getTime( - dayjs() - .subtract(30, "day") - .valueOf(), + dato() + .addDays(30) + .toMilliseconds(), ), ), ) diff --git a/packages/core-api/src/repositories/utils/build-filter-query.ts b/packages/core-api/src/repositories/utils/build-filter-query.ts index dd2e26f7fa..8e286294ac 100644 --- a/packages/core-api/src/repositories/utils/build-filter-query.ts +++ b/packages/core-api/src/repositories/utils/build-filter-query.ts @@ -28,6 +28,7 @@ export function buildFilterQuery(parameters, filters) { } if (parameters[elem].hasOwnProperty("from") || parameters[elem].hasOwnProperty("to")) { + // 'where' is declared to be an array, yet 'elem' is a string. Why are we using a string as a numerical index? where[elem] = {}; if (parameters[elem].hasOwnProperty("from")) { diff --git a/packages/core-api/src/server.ts b/packages/core-api/src/server.ts index 124a53de3b..e93f639051 100644 --- a/packages/core-api/src/server.ts +++ b/packages/core-api/src/server.ts @@ -2,6 +2,7 @@ import { app } from "@arkecosystem/core-container"; import { createSecureServer, createServer, mountServer, plugins } from "@arkecosystem/core-http-utils"; import { Logger } from "@arkecosystem/core-interfaces"; import Hapi from "hapi"; +import { registerFormats } from "./formats"; export class Server { private logger = app.resolvePlugin("logger"); @@ -105,7 +106,10 @@ export class Server { }); await server.register({ - plugin: require("./plugins/validation"), + plugin: plugins.hapiAjv, + options: { + registerFormats, + }, }); await server.register({ diff --git a/packages/core-api/src/services/cache.ts b/packages/core-api/src/services/cache.ts index f0f354bee2..2091ea6a3c 100644 --- a/packages/core-api/src/services/cache.ts +++ b/packages/core-api/src/services/cache.ts @@ -1,6 +1,6 @@ import { app } from "@arkecosystem/core-container"; -import { createHash } from "crypto"; -import Hapi from "hapi"; +import { HashAlgorithms } from "@arkecosystem/crypto"; +import Hapi, { ServerMethod } from "hapi"; export class ServerCache { public static make(server: Hapi.Server): ServerCache { @@ -9,7 +9,7 @@ export class ServerCache { private constructor(readonly server: Hapi.Server) {} - public method(name: string, method: any, expiresIn: number, argsCallback?: any): this { + public method(name: string, method: ServerMethod, expiresIn: number, argsCallback?: any): this { let options = {}; // @ts-ignore @@ -30,9 +30,7 @@ export class ServerCache { } private generateCacheKey(value: object): string { - return createHash("sha256") - .update(JSON.stringify(value)) - .digest("hex"); + return HashAlgorithms.sha256(JSON.stringify(value)).toString("hex"); } private getCacheTimeout(): number | boolean { diff --git a/packages/core-api/src/versions/1/delegates/controller.ts b/packages/core-api/src/versions/1/delegates/controller.ts index 4ee2ce26f1..95fe402d1c 100644 --- a/packages/core-api/src/versions/1/delegates/controller.ts +++ b/packages/core-api/src/versions/1/delegates/controller.ts @@ -1,3 +1,4 @@ +import { roundCalculator } from "@arkecosystem/core-utils"; import { slots } from "@arkecosystem/crypto"; import Boom from "boom"; import Hapi from "hapi"; @@ -95,15 +96,15 @@ export class DelegatesController extends Controller { const delegatesCount = this.config.getMilestone(lastBlock).activeDelegates; const currentSlot = slots.getSlotNumber(lastBlock.data.timestamp); - let activeDelegates = await this.databaseService.getActiveDelegates(lastBlock.data.height); - activeDelegates = activeDelegates.map(delegate => delegate.publicKey); - + const roundInfo = roundCalculator.calculateRound(lastBlock.data.height); + const activeDelegates = await this.databaseService.getActiveDelegates(roundInfo); const nextForgers = []; + for (let i = 1; i <= delegatesCount && i <= limit; i++) { const delegate = activeDelegates[(currentSlot + i) % delegatesCount]; if (delegate) { - nextForgers.push(delegate); + nextForgers.push(delegate.publicKey); } } diff --git a/packages/core-api/src/versions/1/delegates/schema.ts b/packages/core-api/src/versions/1/delegates/schema.ts index 2cdab0866d..5b42a449c6 100644 --- a/packages/core-api/src/versions/1/delegates/schema.ts +++ b/packages/core-api/src/versions/1/delegates/schema.ts @@ -1,5 +1,6 @@ import { app } from "@arkecosystem/core-container"; import { Blockchain } from "@arkecosystem/core-interfaces"; +import { roundCalculator } from "@arkecosystem/core-utils"; const lastBlock = app.resolvePlugin("blockchain").getLastBlock(); @@ -63,7 +64,7 @@ export const getDelegates: object = { limit: { type: "integer", minimum: 1, - maximum: lastBlock ? app.getConfig().getMilestone(lastBlock.data.height).activeDelegates : 51, + maximum: lastBlock ? roundCalculator.calculateRound(lastBlock.data.height).maxDelegates : 51, }, offset: { type: "integer", diff --git a/packages/core-api/src/versions/1/delegates/transformer.ts b/packages/core-api/src/versions/1/delegates/transformer.ts index 84cff93572..584cbbea2f 100644 --- a/packages/core-api/src/versions/1/delegates/transformer.ts +++ b/packages/core-api/src/versions/1/delegates/transformer.ts @@ -7,10 +7,8 @@ export function transformDelegateLegacy(model) { publicKey: model.publicKey, vote: `${model.voteBalance}`, producedblocks: model.producedBlocks, - missedblocks: model.missedBlocks, forged: model.forged, rate: model.rate, approval: delegateCalculator.calculateApproval(model), - productivity: delegateCalculator.calculateProductivity(model), }; } diff --git a/packages/core-api/src/versions/1/peers/controller.ts b/packages/core-api/src/versions/1/peers/controller.ts index ac40e3fc0d..53248f21de 100644 --- a/packages/core-api/src/versions/1/peers/controller.ts +++ b/packages/core-api/src/versions/1/peers/controller.ts @@ -5,13 +5,7 @@ import Hapi from "hapi"; import { Controller } from "../shared/controller"; export class PeersController extends Controller { - protected p2p: P2P.IMonitor; - - public constructor() { - super(); - - this.p2p = app.resolvePlugin("p2p"); - } + protected readonly p2p: P2P.IMonitor = app.resolvePlugin("p2p"); public async index(request: Hapi.Request, h: Hapi.ResponseToolkit) { try { diff --git a/packages/core-api/src/versions/1/peers/transformer.ts b/packages/core-api/src/versions/1/peers/transformer.ts index 3571e2f861..956e85244d 100644 --- a/packages/core-api/src/versions/1/peers/transformer.ts +++ b/packages/core-api/src/versions/1/peers/transformer.ts @@ -3,7 +3,7 @@ import { app } from "@arkecosystem/core-container"; export function transformPeerLegacy(model) { const config = app.getConfig(); - const peer: any = { + return { ip: model.ip, port: model.port, version: model.version, @@ -12,10 +12,4 @@ export function transformPeerLegacy(model) { os: model.os, delay: model.delay, }; - - if (config.get("network.name") !== "mainnet") { - peer.hashid = model.hashid; - } - - return peer; } diff --git a/packages/core-api/src/versions/1/shared/controller.ts b/packages/core-api/src/versions/1/shared/controller.ts index 7e43933440..a2b5a947a5 100644 --- a/packages/core-api/src/versions/1/shared/controller.ts +++ b/packages/core-api/src/versions/1/shared/controller.ts @@ -4,10 +4,10 @@ import Hapi from "hapi"; import { paginate, respondWith, respondWithCache, toCollection, toResource } from "../utils"; export class Controller { - protected config = app.getConfig(); - protected blockchain = app.resolvePlugin("blockchain"); - protected databaseService = app.resolvePlugin("database"); - protected logger = app.resolvePlugin("logger"); + protected readonly config = app.getConfig(); + protected readonly blockchain = app.resolvePlugin("blockchain"); + protected readonly databaseService = app.resolvePlugin("database"); + protected readonly logger = app.resolvePlugin("logger"); protected paginate(request: Hapi.Request): any { return paginate(request); diff --git a/packages/core-api/src/versions/1/shared/transformers/ports.ts b/packages/core-api/src/versions/1/shared/transformers/ports.ts index 579a94b156..51adcfbdae 100644 --- a/packages/core-api/src/versions/1/shared/transformers/ports.ts +++ b/packages/core-api/src/versions/1/shared/transformers/ports.ts @@ -3,7 +3,6 @@ export function transformPortsLegacy(config: any) { const keys = [ "@arkecosystem/core-p2p", "@arkecosystem/core-api", - "@arkecosystem/core-graphql", "@arkecosystem/core-json-rpc", "@arkecosystem/core-webhooks", ]; diff --git a/packages/core-api/src/versions/1/transactions/controller.ts b/packages/core-api/src/versions/1/transactions/controller.ts index 5ee1f41502..7af5e618de 100644 --- a/packages/core-api/src/versions/1/transactions/controller.ts +++ b/packages/core-api/src/versions/1/transactions/controller.ts @@ -5,11 +5,7 @@ import Hapi from "hapi"; import { Controller } from "../shared/controller"; export class TransactionsController extends Controller { - protected transactionPool = app.resolvePlugin("transactionPool"); - - public constructor() { - super(); - } + protected readonly transactionPool = app.resolvePlugin("transaction-pool"); public async index(request: Hapi.Request, h: Hapi.ResponseToolkit) { try { diff --git a/packages/core-api/src/versions/1/transactions/transformer.ts b/packages/core-api/src/versions/1/transactions/transformer.ts index 6785d8b343..733a6cff93 100644 --- a/packages/core-api/src/versions/1/transactions/transformer.ts +++ b/packages/core-api/src/versions/1/transactions/transformer.ts @@ -1,23 +1,23 @@ import { app } from "@arkecosystem/core-container"; -import { Blockchain } from "@arkecosystem/core-interfaces"; -import { bignumify } from "@arkecosystem/core-utils"; -import { crypto, models } from "@arkecosystem/crypto"; +import { Blockchain, Database } from "@arkecosystem/core-interfaces"; +import { Transaction } from "@arkecosystem/crypto"; export function transformTransactionLegacy(model) { - const config = app.getConfig(); const blockchain = app.resolvePlugin("blockchain"); + const databaseService = app.resolvePlugin("database"); - const data: any = new models.Transaction(model.serialized.toString("hex")); + const { data } = Transaction.fromBytesUnsafe(model.serialized, model.id); + const senderId = databaseService.walletManager.findByPublicKey(data.senderPublicKey).address; return { id: data.id, blockid: model.blockId, type: data.type, timestamp: model.timestamp || data.timestamp, - amount: +bignumify(data.amount).toFixed(), - fee: +bignumify(data.fee).toFixed(), + amount: +data.amount, + fee: +data.fee, recipientId: data.recipientId, - senderId: crypto.getAddress(data.senderPublicKey, config.get("network.pubKeyHash")), + senderId, senderPublicKey: data.senderPublicKey, vendorField: data.vendorField, signature: data.signature, diff --git a/packages/core-api/src/versions/2/blocks/methods.ts b/packages/core-api/src/versions/2/blocks/methods.ts index a12628c4d8..cf5d092007 100644 --- a/packages/core-api/src/versions/2/blocks/methods.ts +++ b/packages/core-api/src/versions/2/blocks/methods.ts @@ -1,8 +1,13 @@ +import { app } from "@arkecosystem/core-container"; +import { Database } from "@arkecosystem/core-interfaces"; import Boom from "boom"; -import { blocksRepository, transactionsRepository } from "../../../repositories"; import { ServerCache } from "../../../services"; import { paginate, respondWithResource, toPagination } from "../utils"; +const databaseService = app.resolvePlugin("database"); +const blocksRepository = databaseService.blocksBusinessRepository; +const transactionsRepository = databaseService.transactionsBusinessRepository; + const index = async request => { const blocks = await blocksRepository.findAll({ ...request.query, @@ -13,7 +18,7 @@ const index = async request => { }; const show = async request => { - const block = await blocksRepository.findById(request.params.id); + const block = await blocksRepository.findByIdOrHeight(request.params.id); if (!block) { return Boom.notFound("Block not found"); diff --git a/packages/core-api/src/versions/2/blocks/schema.ts b/packages/core-api/src/versions/2/blocks/schema.ts index d8f70e1098..221b16ab0d 100644 --- a/packages/core-api/src/versions/2/blocks/schema.ts +++ b/packages/core-api/src/versions/2/blocks/schema.ts @@ -1,4 +1,5 @@ import * as Joi from "joi"; +import { blockId } from "../shared/schemas/block-id"; import { pagination } from "../shared/schemas/pagination"; export const index: object = { @@ -6,14 +7,14 @@ export const index: object = { ...pagination, ...{ orderBy: Joi.string(), - id: Joi.string().regex(/^[0-9]+$/, "numbers"), + id: blockId, version: Joi.number() .integer() .min(0), timestamp: Joi.number() .integer() .min(0), - previousBlock: Joi.string().regex(/^[0-9]+$/, "numbers"), + previousBlock: blockId, height: Joi.number() .integer() .positive(), @@ -43,7 +44,7 @@ export const index: object = { export const show: object = { params: { - id: Joi.string().regex(/^[0-9]+$/, "numbers"), + id: blockId, }, }; @@ -58,7 +59,7 @@ export const transactions: object = { id: Joi.string() .hex() .length(66), - blockId: Joi.string().regex(/^[0-9]+$/, "numbers"), + blockId, type: Joi.number() .integer() .min(0), @@ -91,11 +92,11 @@ export const transactions: object = { export const search: object = { query: pagination, payload: { - id: Joi.string().regex(/^[0-9]+$/, "numbers"), + id: blockId, version: Joi.number() .integer() .min(0), - previousBlock: Joi.string().regex(/^[0-9]+$/, "numbers"), + previousBlock: blockId, payloadHash: Joi.string().hex(), generatorPublicKey: Joi.string() .hex() diff --git a/packages/core-api/src/versions/2/delegates/controller.ts b/packages/core-api/src/versions/2/delegates/controller.ts index c36d413476..a2678a0168 100644 --- a/packages/core-api/src/versions/2/delegates/controller.ts +++ b/packages/core-api/src/versions/2/delegates/controller.ts @@ -57,15 +57,4 @@ export class DelegatesController extends Controller { return Boom.badImplementation(error); } } - - public async voterBalances(request: Hapi.Request, h: Hapi.ResponseToolkit) { - try { - // @ts-ignore - const data = await request.server.methods.v2.delegates.voterBalances(request); - - return super.respondWithCache(data, h); - } catch (error) { - return Boom.badImplementation(error); - } - } } diff --git a/packages/core-api/src/versions/2/delegates/methods.ts b/packages/core-api/src/versions/2/delegates/methods.ts index a6abebffda..b9865498d3 100644 --- a/packages/core-api/src/versions/2/delegates/methods.ts +++ b/packages/core-api/src/versions/2/delegates/methods.ts @@ -1,6 +1,5 @@ import { app } from "@arkecosystem/core-container"; import { Database } from "@arkecosystem/core-interfaces"; -import { orderBy } from "@arkecosystem/utils"; import Boom from "boom"; import { blocksRepository } from "../../../repositories"; import { ServerCache } from "../../../services"; @@ -58,29 +57,12 @@ const voters = async request => { const wallets = await databaseService.wallets.findAllByVote(delegate.publicKey, { ...request.query, - ...paginate(request) + ...paginate(request), }); return toPagination(request, wallets, "wallet"); }; -const voterBalances = async request => { - const delegate = await databaseService.delegates.findById(request.params.id); - - if (!delegate) { - return Boom.notFound("Delegate not found"); - } - - const wallets = await databaseService.wallets.all().filter(wallet => wallet.vote === delegate.publicKey); - - const data = {}; - orderBy(wallets, ["balance"], ["desc"]).forEach(wallet => { - data[wallet.address] = +wallet.balance.toFixed(); - }); - - return { data }; -}; - export function registerMethods(server) { ServerCache.make(server) .method("v2.delegates.index", index, 8, request => ({ @@ -100,6 +82,5 @@ export function registerMethods(server) { .method("v2.delegates.voters", voters, 8, request => ({ ...{ id: request.params.id }, ...paginate(request), - })) - .method("v2.delegates.voterBalances", voterBalances, 8, request => ({ id: request.params.id })); + })); } diff --git a/packages/core-api/src/versions/2/delegates/routes.ts b/packages/core-api/src/versions/2/delegates/routes.ts index b1c7705e7c..3e81159410 100644 --- a/packages/core-api/src/versions/2/delegates/routes.ts +++ b/packages/core-api/src/versions/2/delegates/routes.ts @@ -42,15 +42,6 @@ export function registerRoutes(server: Hapi.Server): void { }, }); - server.route({ - method: "GET", - path: "/delegates/{id}/voters/balances", - handler: controller.voterBalances, - options: { - validate: Schema.voterBalances, - }, - }); - server.route({ method: "POST", path: "/delegates/search", diff --git a/packages/core-api/src/versions/2/delegates/schema.ts b/packages/core-api/src/versions/2/delegates/schema.ts index 8ebb84cf26..c32e130e47 100644 --- a/packages/core-api/src/versions/2/delegates/schema.ts +++ b/packages/core-api/src/versions/2/delegates/schema.ts @@ -1,6 +1,10 @@ +import { app } from "@arkecosystem/core-container"; import * as Joi from "joi"; +import { blockId } from "../shared/schemas/block-id"; import { pagination } from "../shared/schemas/pagination"; +const config = app.getConfig(); + const schemaIdentifier = Joi.string() .regex(/^[a-zA-Z0-9!@$&_.]+$/) .min(1) @@ -11,6 +15,26 @@ const schemaUsername = Joi.string() .min(1) .max(20); +const schemaIntegerBetween = Joi.object().keys({ + from: Joi.number() + .integer() + .min(0), + to: Joi.number() + .integer() + .min(0), +}); + +const schemaPercentage = Joi.object().keys({ + from: Joi.number() + .precision(2) + .min(0) + .max(100), + to: Joi.number() + .precision(2) + .min(0) + .max(100), +}); + export const index: object = { query: { ...pagination, @@ -38,9 +62,6 @@ export const index: object = { producedBlocks: Joi.number() .integer() .min(0), - missedBlocks: Joi.number() - .integer() - .min(0), }, }, }; @@ -52,9 +73,31 @@ export const show: object = { }; export const search: object = { - query: pagination, + query: { + ...pagination, + ...{ + orderBy: Joi.string(), + }, + }, payload: { + address: Joi.string() + .alphanum() + .length(34), + publicKey: Joi.string() + .hex() + .length(66), username: schemaUsername, + usernames: Joi.array() + .unique() + .min(1) + .max(config.getMilestone().activeDelegates) + .items(schemaUsername), + approval: schemaPercentage, + forgedFees: schemaIntegerBetween, + forgedRewards: schemaIntegerBetween, + forgedTotal: schemaIntegerBetween, + producedBlocks: schemaIntegerBetween, + voteBalance: schemaIntegerBetween, }, }; @@ -66,14 +109,14 @@ export const blocks: object = { ...pagination, ...{ orderBy: Joi.string(), - id: Joi.string().regex(/^[0-9]+$/, "numbers"), + id: blockId, version: Joi.number() .integer() .min(0), timestamp: Joi.number() .integer() .min(0), - previousBlock: Joi.string().regex(/^[0-9]+$/, "numbers"), + previousBlock: blockId, height: Joi.number() .integer() .positive(), @@ -131,15 +174,6 @@ export const voters: object = { producedBlocks: Joi.number() .integer() .min(0), - missedBlocks: Joi.number() - .integer() - .min(0), }, }, }; - -export const voterBalances: object = { - params: { - id: schemaIdentifier, - }, -}; diff --git a/packages/core-api/src/versions/2/delegates/transformer.ts b/packages/core-api/src/versions/2/delegates/transformer.ts index 6fdb040209..b0956cf12b 100644 --- a/packages/core-api/src/versions/2/delegates/transformer.ts +++ b/packages/core-api/src/versions/2/delegates/transformer.ts @@ -9,16 +9,14 @@ export function transformDelegate(delegate) { rank: delegate.rate, blocks: { produced: delegate.producedBlocks, - missed: delegate.missedBlocks, }, production: { approval: delegateCalculator.calculateApproval(delegate), - productivity: delegateCalculator.calculateProductivity(delegate), }, forged: { fees: +delegate.forgedFees.toFixed(), rewards: +delegate.forgedRewards.toFixed(), - total: +delegate.forgedFees.plus(delegate.forgedRewards).toFixed(), + total: delegateCalculator.calculateForgedTotal(delegate), }, }; diff --git a/packages/core-api/src/versions/2/node/controller.ts b/packages/core-api/src/versions/2/node/controller.ts index 4be8f9169c..64847a1324 100644 --- a/packages/core-api/src/versions/2/node/controller.ts +++ b/packages/core-api/src/versions/2/node/controller.ts @@ -1,7 +1,7 @@ import { app } from "@arkecosystem/core-container"; +import { Database } from "@arkecosystem/core-interfaces"; import Boom from "boom"; import Hapi from "hapi"; -import { transactionsRepository } from "../../../repositories"; import { Controller } from "../shared/controller"; export class NodeController extends Controller { @@ -42,14 +42,18 @@ export class NodeController extends Controller { public async configuration(request: Hapi.Request, h: Hapi.ResponseToolkit) { try { - const feeStatisticsData = await transactionsRepository.getFeeStatistics(); + const transactionsBusinessRepository = app.resolvePlugin("database") + .transactionsBusinessRepository; + const feeStatisticsData = await transactionsBusinessRepository.getFeeStatistics(); const network = this.config.get("network"); - const dynamicFees = app.resolveOptions("transactionPool").dynamicFees; + const dynamicFees = app.resolveOptions("transaction-pool").dynamicFees; return { data: { nethash: network.nethash, + slip44: network.slip44, + wif: network.wif, token: network.client.token, symbol: network.client.symbol, explorer: network.client.explorer, @@ -58,7 +62,7 @@ export class NodeController extends Controller { constants: this.config.getMilestone(this.blockchain.getLastHeight()), feeStatistics: super.toCollection(request, feeStatisticsData, "fee-statistics"), transactionPool: { - maxTransactionAge: app.resolveOptions("transactionPool").maxTransactionAge, + maxTransactionAge: app.resolveOptions("transaction-pool").maxTransactionAge, dynamicFees: dynamicFees.enabled ? dynamicFees : { enabled: false }, }, }, diff --git a/packages/core-api/src/versions/2/peers/controller.ts b/packages/core-api/src/versions/2/peers/controller.ts index ddf74a28c5..4e7be9f88f 100644 --- a/packages/core-api/src/versions/2/peers/controller.ts +++ b/packages/core-api/src/versions/2/peers/controller.ts @@ -2,6 +2,7 @@ import { app } from "@arkecosystem/core-container"; import { P2P } from "@arkecosystem/core-interfaces"; import Boom from "boom"; import Hapi from "hapi"; +import semver from "semver"; import { Controller } from "../shared/controller"; export class PeersController extends Controller { @@ -38,12 +39,19 @@ export class PeersController extends Controller { // @ts-ignore const order = request.query.orderBy.split(":"); - if (["port", "status", "os", "version"].includes(order[0])) { + if (["port", "status", "os"].includes(order[0])) { result = order[1].toUpperCase() === "ASC" ? result.sort((a, b) => a[order[0]] - b[order[0]]) : result.sort((a, b) => a[order[0]] + b[order[0]]); } + + if (order[0] === "version") { + result = + order[1].toUpperCase() === "ASC" + ? result.sort((a, b) => semver.compare(a[order[0]], b[order[0]])) + : result.sort((a, b) => semver.rcompare(a[order[0]], b[order[0]])); + } } return super.toPagination(request, { rows: result, count: allPeers.length }, "peer"); diff --git a/packages/core-api/src/versions/2/peers/transformer.ts b/packages/core-api/src/versions/2/peers/transformer.ts index b9837f021e..e3e4ff1283 100644 --- a/packages/core-api/src/versions/2/peers/transformer.ts +++ b/packages/core-api/src/versions/2/peers/transformer.ts @@ -3,7 +3,7 @@ import { app } from "@arkecosystem/core-container"; export function transformPeer(model) { const config = app.getConfig(); - const peer: any = { + return { ip: model.ip, port: +model.port, version: model.version, @@ -12,10 +12,4 @@ export function transformPeer(model) { os: model.os, latency: model.delay, }; - - if (config.get("network.name") !== "mainnet") { - peer.hashid = model.hashid || "unknown"; - } - - return peer; } diff --git a/packages/core-api/src/versions/2/shared/controller.ts b/packages/core-api/src/versions/2/shared/controller.ts index 659a8474d2..660e6c006d 100644 --- a/packages/core-api/src/versions/2/shared/controller.ts +++ b/packages/core-api/src/versions/2/shared/controller.ts @@ -12,9 +12,9 @@ import { } from "../utils"; export class Controller { - protected config = app.getConfig(); - protected blockchain = app.resolvePlugin("blockchain"); - protected databaseService = app.resolvePlugin("database"); + protected readonly config = app.getConfig(); + protected readonly blockchain = app.resolvePlugin("blockchain"); + protected readonly databaseService = app.resolvePlugin("database"); protected paginate(request: Hapi.Request): any { return paginate(request); diff --git a/packages/core-api/src/versions/2/shared/schemas/block-id.ts b/packages/core-api/src/versions/2/shared/schemas/block-id.ts new file mode 100644 index 0000000000..731799ad1f --- /dev/null +++ b/packages/core-api/src/versions/2/shared/schemas/block-id.ts @@ -0,0 +1,11 @@ +import * as Joi from "joi"; + +export const blockId = Joi.alternatives().try( + Joi.string() + .min(1) + .max(20) + .regex(/^[0-9]+$/, "decimal non-negative integer"), + Joi.string() + .length(64) + .hex(), +); diff --git a/packages/core-api/src/versions/2/shared/transformers/ports.ts b/packages/core-api/src/versions/2/shared/transformers/ports.ts index 3193f00719..0c1b99b198 100644 --- a/packages/core-api/src/versions/2/shared/transformers/ports.ts +++ b/packages/core-api/src/versions/2/shared/transformers/ports.ts @@ -3,7 +3,6 @@ export function transformPorts(config: any) { const keys = [ "@arkecosystem/core-p2p", "@arkecosystem/core-api", - "@arkecosystem/core-graphql", "@arkecosystem/core-json-rpc", "@arkecosystem/core-webhooks", ]; diff --git a/packages/core-api/src/versions/2/transactions/controller.ts b/packages/core-api/src/versions/2/transactions/controller.ts index 43af05af45..007637b7c0 100644 --- a/packages/core-api/src/versions/2/transactions/controller.ts +++ b/packages/core-api/src/versions/2/transactions/controller.ts @@ -1,18 +1,13 @@ import { app } from "@arkecosystem/core-container"; -import { P2P } from "@arkecosystem/core-interfaces"; +import { P2P, TransactionPool } from "@arkecosystem/core-interfaces"; +import { TransactionGuard } from "@arkecosystem/core-transaction-pool"; +import { constants } from "@arkecosystem/crypto"; import Boom from "boom"; import Hapi from "hapi"; import { Controller } from "../shared/controller"; -import { TransactionGuard, TransactionPool } from "@arkecosystem/core-transaction-pool"; -import { constants } from "@arkecosystem/crypto"; - export class TransactionsController extends Controller { - private transactionPool = app.resolvePlugin("transactionPool"); - - public constructor() { - super(); - } + private readonly transactionPool = app.resolvePlugin("transaction-pool"); public async index(request: Hapi.Request, h: Hapi.ResponseToolkit) { try { @@ -72,16 +67,16 @@ export class TransactionsController extends Controller { const pagination = super.paginate(request); - let transactions = this.transactionPool.getTransactions(pagination.offset, pagination.limit, 0); - transactions = transactions.map(transaction => ({ - serialized: transaction, + const transactions = this.transactionPool.getTransactions(pagination.offset, pagination.limit); + const data = transactions.map(transaction => ({ + serialized: transaction.toString("hex"), })); return super.toPagination( request, { count: this.transactionPool.getPoolSize(), - rows: transactions, + rows: data, }, "transaction", ); @@ -96,15 +91,14 @@ export class TransactionsController extends Controller { return Boom.serverUnavailable("Transaction pool is disabled."); } - let transaction = this.transactionPool.getTransaction(request.params.id); - + const transaction = this.transactionPool.getTransaction(request.params.id); if (!transaction) { return Boom.notFound("Transaction not found"); } - transaction = { serialized: transaction.serialized }; + const data = { id: transaction.id, serialized: transaction.serialized.toString("hex") }; - return super.respondWithResource(request, transaction, "transaction"); + return super.respondWithResource(request, data, "transaction"); } catch (error) { return Boom.badImplementation(error); } diff --git a/packages/core-api/src/versions/2/transactions/methods.ts b/packages/core-api/src/versions/2/transactions/methods.ts index 04707c22d6..763f2e090f 100644 --- a/packages/core-api/src/versions/2/transactions/methods.ts +++ b/packages/core-api/src/versions/2/transactions/methods.ts @@ -1,8 +1,11 @@ +import { app } from "@arkecosystem/core-container"; +import { Database } from "@arkecosystem/core-interfaces"; import Boom from "boom"; -import { transactionsRepository } from "../../../repositories"; import { ServerCache } from "../../../services"; import { paginate, respondWithResource, toPagination } from "../utils"; +const transactionsRepository = app.resolvePlugin("database").transactionsBusinessRepository; + const index = async request => { const transactions = await transactionsRepository.findAll({ ...request.query, diff --git a/packages/core-api/src/versions/2/transactions/routes.ts b/packages/core-api/src/versions/2/transactions/routes.ts index b4a9e47663..7e95cc5614 100644 --- a/packages/core-api/src/versions/2/transactions/routes.ts +++ b/packages/core-api/src/versions/2/transactions/routes.ts @@ -20,11 +20,13 @@ export function registerRoutes(server: Hapi.Server): void { path: "/transactions", handler: controller.store, options: { - validate: Schema.store, plugins: { pagination: { enabled: false, }, + "hapi-ajv": { + payloadSchema: Schema.store, + }, }, }, }); diff --git a/packages/core-api/src/versions/2/transactions/schema.ts b/packages/core-api/src/versions/2/transactions/schema.ts index aeacd37f26..f88e830f03 100644 --- a/packages/core-api/src/versions/2/transactions/schema.ts +++ b/packages/core-api/src/versions/2/transactions/schema.ts @@ -1,60 +1,76 @@ import { app } from "@arkecosystem/core-container"; -import { Joi } from "@arkecosystem/crypto"; +import joi from "joi"; +import { blockId } from "../shared/schemas/block-id"; import { pagination } from "../shared/schemas/pagination"; export const index: object = { query: { ...pagination, ...{ - orderBy: Joi.string(), - id: Joi.string() + orderBy: joi.string(), + id: joi + .string() .hex() .length(64), - blockId: Joi.string().regex(/^[0-9]+$/, "numbers"), - type: Joi.number() + blockId, + type: joi + .number() .integer() .min(0), - version: Joi.number() + version: joi + .number() .integer() .positive(), - senderPublicKey: Joi.string() + senderPublicKey: joi + .string() .hex() .length(66), - senderId: Joi.string() + senderId: joi + .string() .alphanum() .length(34), - recipientId: Joi.string() + recipientId: joi + .string() .alphanum() .length(34), - ownerId: Joi.string() + ownerId: joi + .string() .alphanum() .length(34), - timestamp: Joi.number() + timestamp: joi + .number() .integer() .min(0), - amount: Joi.number() + amount: joi + .number() .integer() .min(0), - fee: Joi.number() + fee: joi + .number() .integer() .min(0), - vendorFieldHex: Joi.string().hex(), + vendorFieldHex: joi.string().hex(), }, }, }; export const store: object = { - payload: { - transactions: Joi.transactionArray() - .min(1) - .max(app.resolveOptions("transactionPool").maxTransactionsPerRequest) - .options({ stripUnknown: true }), + type: "object", + required: ["transactions"], + additionalProperties: false, + properties: { + transactions: { + $ref: "transactions", + minItems: 1, + maxItems: app.resolveOptions("transaction-pool").maxTransactionsPerRequest, + }, }, }; export const show: object = { params: { - id: Joi.string() + id: joi + .string() .hex() .length(64), }, @@ -66,63 +82,76 @@ export const unconfirmed: object = { export const showUnconfirmed: object = { params: { - id: Joi.string() + id: joi + .string() .hex() .length(64), }, }; -const address: object = Joi.string() +const address: object = joi + .string() .alphanum() .length(34); export const search: object = { query: pagination, payload: { - orderBy: Joi.string(), - id: Joi.string() + orderBy: joi.string(), + id: joi + .string() .hex() .length(64), - blockId: Joi.string().regex(/^[0-9]+$/, "numbers"), - type: Joi.number() + blockId, + type: joi + .number() .integer() .min(0), - version: Joi.number() + version: joi + .number() .integer() .positive(), - senderPublicKey: Joi.string() + senderPublicKey: joi + .string() .hex() .length(66), senderId: address, recipientId: address, ownerId: address, - addresses: Joi.array() + addresses: joi + .array() .unique() .min(1) .max(50) .items(address), - vendorFieldHex: Joi.string().hex(), - timestamp: Joi.object().keys({ - from: Joi.number() + vendorFieldHex: joi.string().hex(), + timestamp: joi.object().keys({ + from: joi + .number() .integer() .min(0), - to: Joi.number() + to: joi + .number() .integer() .min(0), }), - amount: Joi.object().keys({ - from: Joi.number() + amount: joi.object().keys({ + from: joi + .number() .integer() .min(0), - to: Joi.number() + to: joi + .number() .integer() .min(0), }), - fee: Joi.object().keys({ - from: Joi.number() + fee: joi.object().keys({ + from: joi + .number() .integer() .min(0), - to: Joi.number() + to: joi + .number() .integer() .min(0), }), diff --git a/packages/core-api/src/versions/2/transactions/transformer.ts b/packages/core-api/src/versions/2/transactions/transformer.ts index 1b8d48c745..d0b973c16a 100644 --- a/packages/core-api/src/versions/2/transactions/transformer.ts +++ b/packages/core-api/src/versions/2/transactions/transformer.ts @@ -1,13 +1,15 @@ import { app } from "@arkecosystem/core-container"; -import { Blockchain } from "@arkecosystem/core-interfaces"; -import { bignumify, formatTimestamp } from "@arkecosystem/core-utils"; -import { crypto, models } from "@arkecosystem/crypto"; +import { Blockchain, Database } from "@arkecosystem/core-interfaces"; +import { formatTimestamp } from "@arkecosystem/core-utils"; +import { Transaction } from "@arkecosystem/crypto"; export function transformTransaction(model) { - const config = app.getConfig(); const blockchain = app.resolvePlugin("blockchain"); + const databaseService = app.resolvePlugin("database"); + + const { data } = Transaction.fromBytesUnsafe(model.serialized, model.id); + const sender = databaseService.walletManager.findByPublicKey(data.senderPublicKey).address; - const data: any = new models.Transaction(model.serialized.toString("hex")); const lastBlock = blockchain.getLastBlock(); return { @@ -15,9 +17,9 @@ export function transformTransaction(model) { blockId: model.blockId, version: data.version, type: data.type, - amount: +bignumify(data.amount).toFixed(), - fee: +bignumify(data.fee).toFixed(), - sender: crypto.getAddress(data.senderPublicKey, config.get("network.pubKeyHash")), + amount: +data.amount, + fee: +data.fee, + sender, recipient: data.recipientId, signature: data.signature, signSignature: data.signSignature, diff --git a/packages/core-api/src/versions/2/votes/schema.ts b/packages/core-api/src/versions/2/votes/schema.ts index 1f9acde79d..840ff8cb37 100644 --- a/packages/core-api/src/versions/2/votes/schema.ts +++ b/packages/core-api/src/versions/2/votes/schema.ts @@ -1,4 +1,5 @@ import * as Joi from "joi"; +import { blockId } from "../shared/schemas/block-id"; import { pagination } from "../shared/schemas/pagination"; export const index: object = { @@ -9,7 +10,7 @@ export const index: object = { id: Joi.string() .hex() .length(64), - blockId: Joi.string().regex(/^[0-9]+$/, "numbers"), + blockId, version: Joi.number() .integer() .positive(), diff --git a/packages/core-api/src/versions/2/wallets/controller.ts b/packages/core-api/src/versions/2/wallets/controller.ts index 07855fffb6..b5ee067471 100644 --- a/packages/core-api/src/versions/2/wallets/controller.ts +++ b/packages/core-api/src/versions/2/wallets/controller.ts @@ -1,4 +1,3 @@ -import { app } from "@arkecosystem/core-container"; import Boom from "boom"; import Hapi from "hapi"; import { Controller } from "../shared/controller"; diff --git a/packages/core-api/src/versions/2/wallets/methods.ts b/packages/core-api/src/versions/2/wallets/methods.ts index c354ff87f5..d9ccaf3904 100644 --- a/packages/core-api/src/versions/2/wallets/methods.ts +++ b/packages/core-api/src/versions/2/wallets/methods.ts @@ -122,7 +122,7 @@ export function registerMethods(server) { ...paginate(request), })) .method("v2.wallets.top", top, 30, request => paginate(request)) - .method("v2.wallets.show", show, 30, request => ({ id: request.params.id })) + .method("v2.wallets.show", show, 8, request => ({ id: request.params.id })) .method("v2.wallets.transactions", transactions, 30, request => ({ ...{ id: request.params.id }, ...request.query, diff --git a/packages/core-api/src/versions/2/wallets/schema.ts b/packages/core-api/src/versions/2/wallets/schema.ts index 7e9bd48747..75213f3693 100644 --- a/packages/core-api/src/versions/2/wallets/schema.ts +++ b/packages/core-api/src/versions/2/wallets/schema.ts @@ -1,4 +1,5 @@ import * as Joi from "joi"; +import { blockId } from "../shared/schemas/block-id"; import { pagination } from "../shared/schemas/pagination"; export const index: object = { @@ -26,9 +27,6 @@ export const index: object = { producedBlocks: Joi.number() .integer() .min(0), - missedBlocks: Joi.number() - .integer() - .min(0), }, }, }; @@ -50,7 +48,7 @@ export const transactions: object = { id: Joi.string() .hex() .length(64), - blockId: Joi.string().regex(/^[0-9]+$/, "numbers"), + blockId, type: Joi.number() .integer() .min(0), @@ -94,7 +92,7 @@ export const transactionsSent: object = { id: Joi.string() .hex() .length(64), - blockId: Joi.string().regex(/^[0-9]+$/, "numbers"), + blockId, type: Joi.number() .integer() .min(0), @@ -132,7 +130,7 @@ export const transactionsReceived: object = { id: Joi.string() .hex() .length(64), - blockId: Joi.string().regex(/^[0-9]+$/, "numbers"), + blockId, type: Joi.number() .integer() .min(0), @@ -174,9 +172,13 @@ const address: object = Joi.string() .length(34); export const search: object = { - query: pagination, + query: { + ...pagination, + ...{ + orderBy: Joi.string(), + }, + }, payload: { - orderBy: Joi.string(), address, addresses: Joi.array() .unique() @@ -196,9 +198,6 @@ export const search: object = { producedBlocks: Joi.number() .integer() .min(0), - missedBlocks: Joi.number() - .integer() - .min(0), balance: Joi.object().keys({ from: Joi.number().integer(), to: Joi.number().integer(), diff --git a/packages/core-blockchain/.gitattributes b/packages/core-blockchain/.gitattributes index 60cc52db63..63f6a5b970 100644 --- a/packages/core-blockchain/.gitattributes +++ b/packages/core-blockchain/.gitattributes @@ -2,10 +2,6 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore -/__tests__ export-ignore -/docs export-ignore /README.md export-ignore diff --git a/packages/core-blockchain/README.md b/packages/core-blockchain/README.md index 7f9231f70f..3571e5ec0f 100644 --- a/packages/core-blockchain/README.md +++ b/packages/core-blockchain/README.md @@ -1,12 +1,12 @@ -# Ark Core - Blockchain +# Persona Core - Blockchain

- +

## Documentation -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core-blockchain.html). +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/required/core-blockchain.html). ## Security @@ -14,12 +14,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [Brian Faust](https://github.com/faustbrian) -- [François-Xavier Thoorens](https://github.com/fix) -- [Joshua Noack](https://github.com/supaiku0) -- [Kristjan Košič](https://github.com/kristjank) -- [All Contributors](../../../../contributors) +This project exists thanks to all the people who [contribute](../../../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-blockchain/__tests__/blockchain-networkStart.test.ts b/packages/core-blockchain/__tests__/blockchain-networkStart.test.ts deleted file mode 100644 index e42d267b7a..0000000000 --- a/packages/core-blockchain/__tests__/blockchain-networkStart.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -/* tslint:disable:max-line-length */ -import "@arkecosystem/core-test-utils"; -import { asValue } from "awilix"; -import { Blockchain } from "../src/blockchain"; -import { defaults } from "../src/defaults"; -import { setUp, tearDown } from "./__support__/setup"; - -let container; -let blockchain: Blockchain; - -describe("constructor - networkStart", () => { - let logger; - beforeAll(async () => { - container = await setUp(); - - logger = container.resolvePlugin("logger"); - }); - afterAll(async () => { - await tearDown(); - - jest.restoreAllMocks(); - }); - - it("should output log messages if launched in networkStart mode", async () => { - const loggerWarn = jest.spyOn(logger, "warn"); - const loggerInfo = jest.spyOn(logger, "info"); - - await __start(true); - - expect(loggerWarn).toHaveBeenCalledWith( - "Ark Core is launched in Genesis Start mode. This is usually for starting the first node on the blockchain. Unless you know what you are doing, this is likely wrong. :warning:", - ); - expect(loggerInfo).toHaveBeenCalledWith("Starting Ark Core for a new world, welcome aboard :rocket:"); - }); - - describe("dispatch", () => { - it("should be ok", () => { - const nextState = blockchain.dispatch("START"); - - expect(blockchain.state.blockchain).toEqual(nextState); - }); - - it("should log an error if no action is found", () => { - const stateMachine = require("../src/state-machine").stateMachine; - const loggerError = jest.spyOn(logger, "error"); - - // @ts-ignore - jest.spyOn(stateMachine, "transition").mockReturnValueOnce({ - actions: ["yooo"], - }); - - blockchain.dispatch("STOP"); - expect(loggerError).toHaveBeenCalledWith("No action 'yooo' found :interrobang:"); - }); - }); -}); - -async function __start(networkStart) { - process.env.CORE_SKIP_BLOCKCHAIN = "false"; - process.env.CORE_ENV = "false"; - - const plugin = require("../src").plugin; - - blockchain = await plugin.register(container, { - networkStart, - ...defaults, - }); - - await container.register( - "blockchain", - asValue({ - name: "blockchain", - version: "0.1.0", - plugin: blockchain, - options: {}, - }), - ); -} diff --git a/packages/core-blockchain/__tests__/blockchain.test.ts b/packages/core-blockchain/__tests__/blockchain.test.ts deleted file mode 100644 index 442fe432f6..0000000000 --- a/packages/core-blockchain/__tests__/blockchain.test.ts +++ /dev/null @@ -1,822 +0,0 @@ -/* tslint:disable:max-line-length */ -import "@arkecosystem/core-test-utils"; - -import { blocks101to155 } from "@arkecosystem/core-test-utils/src/fixtures/testnet/blocks101to155"; -import { blocks2to100 } from "@arkecosystem/core-test-utils/src/fixtures/testnet/blocks2to100"; -import { delegates } from "@arkecosystem/core-test-utils/src/fixtures/testnet/delegates"; -import { Bignum, crypto, models, slots, sortTransactions, transactionBuilder } from "@arkecosystem/crypto"; -import { asValue } from "awilix"; -import { createHash } from "crypto"; -import delay from "delay"; -import { Blockchain } from "../src/blockchain"; -import { defaults } from "../src/defaults"; -import { setUp, tearDown } from "./__support__/setup"; - -const { Block, Wallet } = models; - -let genesisBlock; -let configManager; -let container; -let blockchain: Blockchain; -let loggerDebugBackup; - -describe("Blockchain", () => { - let logger; - beforeAll(async () => { - container = await setUp(); - - // Backup logger.debug function as we are going to mock it in the test suite - logger = container.resolvePlugin("logger"); - loggerDebugBackup = logger.debug; - - // Create the genesis block after the setup has finished or else it uses a potentially - // wrong network config. - genesisBlock = new Block(require("@arkecosystem/core-test-utils/src/config/testnet/genesisBlock.json")); - - configManager = container.getConfig(); - - // Workaround: Add genesis transactions to the exceptions list, because they have a fee of 0 - // and otherwise don't pass validation. - configManager.set("exceptions.transactions", genesisBlock.transactions.map(tx => tx.id)); - - // Manually register the blockchain and start it - await __start(false); - }); - - afterAll(async () => { - configManager.set("exceptions.transactions", []); - - await __resetToHeight1(); - - // Manually stop the blockchain - await blockchain.stop(); - - await tearDown(); - }); - - afterEach(async () => { - // Restore original logger.debug function - logger.debug = loggerDebugBackup; - - await __resetToHeight1(); - await __addBlocks(5); - await __resetBlocksInCurrentRound(); - }); - - describe("dispatch", () => { - it("should be ok", () => { - const nextState = blockchain.dispatch("START"); - - expect(blockchain.state.blockchain).toEqual(nextState); - }); - }); - - describe("start", () => { - it("should be ok", async () => { - process.env.CORE_SKIP_BLOCKCHAIN = "false"; - - const started = await blockchain.start(true); - - expect(started).toBeTrue(); - }); - }); - - describe("checkNetwork", () => { - it("should throw an exception", () => { - expect(() => blockchain.checkNetwork()).toThrow("Method [checkNetwork] not implemented!"); - }); - }); - - describe("updateNetworkStatus", () => { - it("should call p2p updateNetworkStatus", async () => { - const p2pUpdateNetworkStatus = jest.spyOn(blockchain.p2p, "updateNetworkStatus"); - - await blockchain.updateNetworkStatus(); - - expect(p2pUpdateNetworkStatus).toHaveBeenCalled(); - }); - }); - - describe("rebuild", () => { - it("should throw an exception", () => { - expect(() => blockchain.rebuild()).toThrow("Method [rebuild] not implemented!"); - }); - }); - - describe("postTransactions", () => { - it("should be ok", async () => { - const transactionsWithoutType2 = genesisBlock.transactions.filter(tx => tx.type !== 2); - - blockchain.transactionPool.flush(); - await blockchain.postTransactions(transactionsWithoutType2); - const transactions = blockchain.transactionPool.getTransactions(0, 200); - - expect(transactions.length).toBe(transactionsWithoutType2.length); - - expect(transactions).toEqual(transactionsWithoutType2.map(transaction => transaction.serialized)); - - blockchain.transactionPool.flush(); - }); - }); - - describe("enQueueBlocks", () => { - it("should just return if blocks provided are an empty array", async () => { - const processQueuePush = jest.spyOn(blockchain.processQueue, "push"); - - blockchain.enqueueBlocks([]); - expect(processQueuePush).not.toHaveBeenCalled(); - }); - - it("should enqueue the blocks provided", async () => { - const processQueuePush = jest.spyOn(blockchain.processQueue, "push"); - - const blocksToEnqueue = [blocks101to155[54]]; - blockchain.enqueueBlocks(blocksToEnqueue); - expect(processQueuePush).toHaveBeenCalledWith(blocksToEnqueue); - }); - }); - - describe("rollbackCurrentRound", () => { - it("should rollback", async () => { - await __addBlocks(155); - await blockchain.rollbackCurrentRound(); - expect(blockchain.getLastBlock().data.height).toBe(153); - }); - - it("shouldnt rollback more if previous round is round 2", async () => { - await __addBlocks(140); - await blockchain.rollbackCurrentRound(); - expect(blockchain.getLastBlock().data.height).toBe(102); - - await blockchain.rollbackCurrentRound(); - expect(blockchain.getLastBlock().data.height).toBe(102); - }); - }); - - describe("removeBlocks", () => { - it("should remove blocks", async () => { - const lastBlockHeight = blockchain.getLastBlock().data.height; - - await blockchain.removeBlocks(2); - expect(blockchain.getLastBlock().data.height).toBe(lastBlockHeight - 2); - }); - - it("should remove (current height - 1) blocks if we provide a greater value", async () => { - await __resetToHeight1(); - - await blockchain.removeBlocks(9999); - expect(blockchain.getLastBlock().data.height).toBe(1); - }); - }); - - describe("removeTopBlocks", () => { - it("should remove top blocks", async () => { - const dbLastBlockBefore = await blockchain.database.getLastBlock(); - const lastBlockHeight = dbLastBlockBefore.data.height; - - await blockchain.removeTopBlocks(2); - const dbLastBlockAfter = await blockchain.database.getLastBlock(); - - expect(dbLastBlockAfter.data.height).toBe(lastBlockHeight - 2); - }); - }); - - describe("rebuildBlock", () => { - it("should rebuild with a known block", async () => { - const mockCallback = jest.fn(() => true); - const lastBlock = blockchain.getLastBlock(); - - await blockchain.rebuildBlock(lastBlock, mockCallback); - await delay(200); - - expect(mockCallback.mock.calls.length).toBe(1); - }); - - it("should rebuild with a new chained block", async () => { - const mockCallback = jest.fn(() => true); - const lastBlock = blockchain.getLastBlock(); - - await blockchain.removeBlocks(1); // remove 1 block so that we can add it then as a chained block - - expect(blockchain.getLastBlock()).not.toEqual(lastBlock); - - await blockchain.rebuildBlock(lastBlock, mockCallback); - await delay(200); - - expect(mockCallback.mock.calls.length).toBe(1); - expect(blockchain.getLastBlock()).toEqual(lastBlock); - }); - - it("should disregard block with height == last height but different id", async () => { - const mockCallback = jest.fn(() => true); - const lastBlock = blockchain.getLastBlock(); - const lastBlockCopy = new Block(lastBlock.data); - lastBlockCopy.data.id = "123456"; - - const loggerInfo = jest.spyOn(logger, "info"); - - await blockchain.rebuildBlock(lastBlockCopy, mockCallback); - await delay(200); - - expect(mockCallback.mock.calls.length).toBe(1); - expect(loggerInfo).toHaveBeenCalledWith( - `Block ${lastBlockCopy.data.height.toLocaleString()} disregarded because on a fork :knife_fork_plate:`, - ); - expect(blockchain.getLastBlock().data.id).toBe(lastBlock.data.id); - }); - - it("should disregard block with height > last height + 1", async () => { - const mockCallback = jest.fn(() => true); - const lastBlock = blockchain.getLastBlock(); - const lastBlockCopy = new Block(lastBlock.data); - lastBlockCopy.data.height += 2; - - await blockchain.rebuildBlock(lastBlockCopy, mockCallback); - await delay(200); - - expect(mockCallback.mock.calls.length).toBe(1); - expect(blockchain.getLastBlock().data.id).toBe(lastBlock.data.id); - expect(blockchain.state.lastDownloadedBlock).toBe(lastBlock); - }); - - it("should disregard block not verified", async () => { - const mockCallback = jest.fn(() => true); - const lastBlock = blockchain.getLastBlock(); - const lastBlockCopy = new Block(lastBlock.data); - lastBlockCopy.verification.verified = false; - - const loggerWarn = jest.spyOn(logger, "warn"); - - await blockchain.rebuildBlock(lastBlockCopy, mockCallback); - await delay(200); - - expect(mockCallback.mock.calls.length).toBe(1); - expect(loggerWarn).toHaveBeenCalledWith( - `Block ${lastBlockCopy.data.height.toLocaleString()} disregarded because verification failed :scroll:`, - ); - expect(blockchain.getLastBlock().data.id).toBe(lastBlock.data.id); - }); - - it("should commitQueuedQueries if block height % 20 000 == 0", async () => { - const mockCallback = jest.fn(() => true); - const lastBlock = blockchain.getLastBlock(); - const lastBlockHeight = lastBlock.data.height; - const nextBlock = new Block(blocks2to100[lastBlock.data.height - 1]); - lastBlock.data.height = 19999; - nextBlock.data.height = 20000; - - const commitQueuedQueries = jest - .spyOn(blockchain.database, "commitQueuedQueries") - // @ts-ignore - .mockReturnValueOnce(true); - // @ts-ignore - jest.spyOn(blockchain.database, "enqueueSaveBlock").mockReturnValueOnce(true); - - await blockchain.rebuildBlock(nextBlock, mockCallback); - await delay(200); - - expect(mockCallback.mock.calls.length).toBe(1); - expect(commitQueuedQueries).toHaveBeenCalled(); - expect(blockchain.getLastBlock().data.id).toBe(nextBlock.data.id); - - // reset to "stable" state - lastBlock.data.height = lastBlockHeight; - blockchain.state.setLastBlock(lastBlock); - }); - }); - - describe("processBlock", () => { - it("should process a new chained block", async () => { - const mockCallback = jest.fn(() => true); - const lastBlock = blockchain.state.getLastBlock(); - - await blockchain.removeBlocks(1); // remove 1 block so that we can add it then as a chained block - - expect(blockchain.getLastBlock()).not.toEqual(lastBlock); - - await blockchain.processBlock(lastBlock, mockCallback); - await delay(200); - - expect(mockCallback.mock.calls.length).toBe(1); - expect(blockchain.getLastBlock()).toEqual(lastBlock); - }); - - it("should process a valid block already known", async () => { - const mockCallback = jest.fn(() => true); - const lastBlock = blockchain.getLastBlock(); - - await blockchain.processBlock(lastBlock, mockCallback); - await delay(200); - - expect(mockCallback.mock.calls.length).toBe(1); - expect(blockchain.getLastBlock()).toEqual(lastBlock); - }); - - it("should broadcast a block if (slots.getSlotNumber() * blocktime <= block.data.timestamp)", async () => { - const mockCallback = jest.fn(() => true); - const lastBlock = blockchain.getLastBlock(); - lastBlock.data.timestamp = - slots.getSlotNumber() * configManager.getMilestone(lastBlock.data.height).blocktime; - - const broadcastBlock = jest.spyOn(blockchain.p2p, "broadcastBlock"); - - await blockchain.processBlock(lastBlock, mockCallback); - await delay(200); - - expect(mockCallback.mock.calls.length).toBe(1); - expect(broadcastBlock).toHaveBeenCalled(); - }); - }); - - describe("acceptChainedBlock", () => { - it.skip("should process a new chained block", async () => { - const lastBlock = blockchain.getLastBlock(); - - await blockchain.removeBlocks(1); // remove 1 block so that we can add it then as a chained block - - expect(await blockchain.database.getLastBlock()).not.toEqual(lastBlock); - - // await blockchain.acceptChainedBlock(lastBlock); - - expect(await blockchain.database.getLastBlock()).toEqual(lastBlock); - - // manually set lastBlock because acceptChainedBlock doesn't do it - blockchain.state.setLastBlock(lastBlock); - }); - }); - - describe("manageUnchainedBlock", () => { - it.skip("should process a new unchained block", async () => { - const mockLoggerDebug = jest.fn(message => true); - logger.debug = mockLoggerDebug; - - const lastBlock = blockchain.getLastBlock(); - await blockchain.removeBlocks(2); // remove 2 blocks so that we can have _lastBlock_ as an unchained block - // await blockchain.manageUnchainedBlock(lastBlock); - - expect(mockLoggerDebug).toHaveBeenCalled(); - - const debugMessage = `Blockchain not ready to accept new block at height ${lastBlock.data.height.toLocaleString()}. Last block: ${( - lastBlock.data.height - 2 - ).toLocaleString()} :warning:`; - expect(mockLoggerDebug).toHaveBeenCalledWith(debugMessage); - - expect(blockchain.getLastBlock().data.height).toBe(lastBlock.data.height - 2); - }); - }); - - describe("rollback", () => { - beforeEach(async () => { - await __resetToHeight1(); - await __addBlocks(155); - }); - - const getNextForger = async () => { - const lastBlock = blockchain.state.getLastBlock(); - const activeDelegates = await blockchain.database.getActiveDelegates(lastBlock.data.height); - const nextSlot = slots.getSlotNumber(lastBlock.data.timestamp) + 1; - return activeDelegates[nextSlot % activeDelegates.length]; - }; - - const createBlock = (generatorKeys: any, transactions: models.Transaction[]) => { - const transactionData = { - amount: Bignum.ZERO, - fee: Bignum.ZERO, - sha256: createHash("sha256"), - }; - - const sortedTransactions = sortTransactions(transactions); - sortedTransactions.forEach(transaction => { - transactionData.amount = transactionData.amount.plus(transaction.amount); - transactionData.fee = transactionData.fee.plus(transaction.fee); - transactionData.sha256.update(Buffer.from(transaction.id, "hex")); - }); - - const lastBlock = blockchain.state.getLastBlock(); - const data = { - timestamp: slots.getSlotTime(slots.getSlotNumber(lastBlock.data.timestamp) + 1), - version: 0, - previousBlock: lastBlock.data.id, - previousBlockHex: lastBlock.data.idHex, - height: lastBlock.data.height + 1, - numberOfTransactions: sortedTransactions.length, - totalAmount: transactionData.amount, - totalFee: transactionData.fee, - reward: Bignum.ZERO, - payloadLength: 32 * sortedTransactions.length, - payloadHash: transactionData.sha256.digest().toString("hex"), - transactions: sortedTransactions, - }; - - return Block.create(data, crypto.getKeys(generatorKeys.secret)); - }; - - it("should restore vote balances after a rollback", async () => { - const mockCallback = jest.fn(() => true); - - // Create key pair for new voter - const keyPair = crypto.getKeys("secret"); - const recipient = crypto.getAddress(keyPair.publicKey); - - let nextForger = await getNextForger(); - const initialVoteBalance = nextForger.voteBalance; - - // First send funds to new voter wallet - const forgerKeys = delegates.find(wallet => wallet.publicKey === nextForger.publicKey); - const transfer = transactionBuilder - .transfer() - .recipientId(recipient) - .amount(125) - .sign(forgerKeys.passphrase) - .build(); - - const transferBlock = createBlock(forgerKeys, [transfer]); - await blockchain.processBlock(transferBlock, mockCallback); - - const wallet = blockchain.database.walletManager.findByPublicKey(keyPair.publicKey); - const walletForger = blockchain.database.walletManager.findByPublicKey(forgerKeys.publicKey); - - // New wallet received funds and vote balance of delegate has been reduced by the same amount, - // since it forged it's own transaction the fees for the transaction have been recovered. - expect(wallet.balance).toEqual(new Bignum(transfer.amount)); - expect(walletForger.voteBalance).toEqual(new Bignum(initialVoteBalance).minus(transfer.amount)); - - // Now vote with newly created wallet for previous forger. - const vote = transactionBuilder - .vote() - .fee(1) - .votesAsset([`+${forgerKeys.publicKey}`]) - .sign("secret") - .build(); - - nextForger = await getNextForger(); - let nextForgerWallet = delegates.find(wallet => wallet.publicKey === nextForger.publicKey); - - const voteBlock = createBlock(nextForgerWallet, [vote]); - await blockchain.processBlock(voteBlock, mockCallback); - - // Wallet paid a fee of 1 and the vote has been placed. - expect(wallet.balance).toEqual(new Bignum(124)); - expect(wallet.vote).toEqual(forgerKeys.publicKey); - - // Vote balance of delegate now equals initial vote balance minus 1 for the vote fee - // since it was forged by a different delegate. - expect(walletForger.voteBalance).toEqual(new Bignum(initialVoteBalance).minus(vote.fee)); - - // Now unvote again - const unvote = transactionBuilder - .vote() - .fee(1) - .votesAsset([`-${forgerKeys.publicKey}`]) - .sign("secret") - .build(); - - nextForger = await getNextForger(); - nextForgerWallet = delegates.find(wallet => wallet.publicKey === nextForger.publicKey); - - const unvoteBlock = createBlock(nextForgerWallet, [unvote]); - await blockchain.processBlock(unvoteBlock, mockCallback); - - // Wallet paid a fee of 1 and no longer voted a delegate - expect(wallet.balance).toEqual(new Bignum(123)); - expect(wallet.vote).toBeNull(); - - // Vote balance of delegate now equals initial vote balance minus the amount sent to the voter wallet. - expect(walletForger.voteBalance).toEqual(new Bignum(initialVoteBalance).minus(transfer.amount)); - - // Now rewind 3 blocks back to the initial state - await blockchain.removeBlocks(3); - - // Wallet is now a cold wallet and the initial vote balance has been restored. - expect(wallet.balance).toEqual(Bignum.ZERO); - expect(walletForger.voteBalance).toEqual(new Bignum(initialVoteBalance)); - }); - }); - - describe("getUnconfirmedTransactions", () => { - it("should get unconfirmed transactions", async () => { - const transactionsWithoutType2 = genesisBlock.transactions.filter(tx => tx.type !== 2); - - blockchain.transactionPool.flush(); - await blockchain.postTransactions(transactionsWithoutType2); - const unconfirmedTransactions = blockchain.getUnconfirmedTransactions(200); - - expect(unconfirmedTransactions.transactions.length).toBe(transactionsWithoutType2.length); - - expect(unconfirmedTransactions.transactions).toEqual( - transactionsWithoutType2.map(transaction => transaction.serialized), - ); - - blockchain.transactionPool.flush(); - }); - - it("should return object with count == -1 if getTransactionsForForging returned a falsy value", async () => { - jest.spyOn(blockchain.transactionPool, "getTransactionsForForging").mockReturnValueOnce(null); - - const unconfirmedTransactions = blockchain.getUnconfirmedTransactions(200); - expect(unconfirmedTransactions.count).toBe(-1); - }); - }); - - describe("getLastBlock", () => { - it("should be ok", () => { - blockchain.state.setLastBlock(genesisBlock); - - expect(blockchain.getLastBlock()).toEqual(genesisBlock); - }); - }); - - describe("handleIncomingBlock", () => { - it("should be ok", () => { - const dispatch = blockchain.dispatch; - const enqueueBlocks = blockchain.enqueueBlocks; - blockchain.dispatch = jest.fn(() => true); - blockchain.enqueueBlocks = jest.fn(() => true); - - const block = { - height: 100, - timestamp: slots.getEpochTime(), - }; - - blockchain.handleIncomingBlock(block); - - expect(blockchain.dispatch).toHaveBeenCalled(); - expect(blockchain.enqueueBlocks).toHaveBeenCalled(); - - blockchain.dispatch = dispatch; - blockchain.enqueueBlocks = enqueueBlocks; - }); - - it("should not handle block from future slot", () => { - const dispatch = blockchain.dispatch; - const enqueueBlocks = blockchain.enqueueBlocks; - blockchain.dispatch = jest.fn(() => true); - blockchain.enqueueBlocks = jest.fn(() => true); - - const block = { - height: 100, - timestamp: slots.getSlotTime(slots.getNextSlot()), - }; - - blockchain.handleIncomingBlock(block); - - expect(blockchain.dispatch).not.toHaveBeenCalled(); - expect(blockchain.enqueueBlocks).not.toHaveBeenCalled(); - - blockchain.dispatch = dispatch; - blockchain.enqueueBlocks = enqueueBlocks; - }); - - it("should disregard block when blockchain is not ready", async () => { - blockchain.state.started = false; - const loggerInfo = jest.spyOn(logger, "info"); - - const mockGetSlotNumber = jest - .spyOn(slots, "getSlotNumber") - .mockReturnValueOnce(1) - .mockReturnValueOnce(1); - - await blockchain.handleIncomingBlock(blocks101to155[54]); - - expect(loggerInfo).toHaveBeenCalledWith("Block disregarded because blockchain is not ready :exclamation:"); - blockchain.state.started = true; - - mockGetSlotNumber.mockRestore(); - }); - }); - - describe("forceWakeup", () => { - it("should dispatch WAKEUP", () => { - expect(() => blockchain.forceWakeup()).toDispatch(blockchain, "WAKEUP"); - }); - }); - - describe("forkBlock", () => { - it("should dispatch FORK and set state.forkedBlock", () => { - const forkedBlock = new Block(blocks2to100[11]); - expect(() => blockchain.forkBlock(forkedBlock)).toDispatch(blockchain, "FORK"); - expect(blockchain.state.forkedBlock).toBe(forkedBlock); - - blockchain.state.forkedBlock = null; // reset - }); - }); - - describe("isSynced", () => { - describe("with a block param", () => { - it("should be ok", () => { - expect( - blockchain.isSynced({ - data: { - timestamp: slots.getTime(), - height: genesisBlock.height, - }, - } as models.IBlock), - ).toBeTrue(); - }); - }); - - describe("without a block param", () => { - it("should use the last block", () => { - jest.spyOn(blockchain.p2p, "hasPeers").mockReturnValueOnce(true); - const getLastBlock = jest.spyOn(blockchain, "getLastBlock").mockReturnValueOnce({ - // @ts-ignore - data: { - timestamp: slots.getTime(), - height: genesisBlock.height, - }, - }); - expect(blockchain.isSynced()).toBeTrue(); - expect(getLastBlock).toHaveBeenCalled(); - }); - }); - }); - - describe("isRebuildSynced", () => { - describe("with a block param", () => { - it("should be ok", () => { - jest.spyOn(blockchain.p2p, "hasPeers").mockReturnValueOnce(true); - expect( - blockchain.isRebuildSynced({ - data: { - timestamp: slots.getTime() - 3600 * 24 * 6, - height: blocks101to155[52].height, - }, - } as models.IBlock), - ).toBeTrue(); - }); - }); - - describe("without a block param", () => { - it("should use the last block", () => { - jest.spyOn(blockchain.p2p, "hasPeers").mockReturnValueOnce(true); - const getLastBlock = jest.spyOn(blockchain, "getLastBlock").mockReturnValueOnce({ - // @ts-ignore - data: { - timestamp: slots.getTime(), - height: genesisBlock.height, - }, - }); - expect(blockchain.isRebuildSynced()).toBeTrue(); - expect(getLastBlock).toHaveBeenCalled(); - }); - }); - - it("should return true when there is no peer", () => { - jest.spyOn(blockchain.p2p, "hasPeers").mockReturnValueOnce(false); - - expect(blockchain.isRebuildSynced()).toBeTrue(); - }); - }); - - describe("getBlockPing", () => { - it("should return state.blockPing", () => { - const blockPing = { - count: 1, - first: new Date().getTime(), - last: new Date().getTime(), - block: {}, - }; - blockchain.state.blockPing = blockPing; - - expect(blockchain.getBlockPing()).toBe(blockPing); - }); - }); - - describe("pingBlock", () => { - it("should call state.pingBlock", () => { - blockchain.state.blockPing = null; - - // returns false if no state.blockPing - expect(blockchain.pingBlock(blocks2to100[3])).toBeFalse(); - }); - }); - - describe("pushPingBlock", () => { - it("should call state.pushPingBlock", () => { - blockchain.state.blockPing = null; - - blockchain.pushPingBlock(blocks2to100[3]); - expect(blockchain.state.blockPing).toBeObject(); - expect(blockchain.state.blockPing.block).toBe(blocks2to100[3]); - }); - }); - - describe("getEvents", () => { - it("should return the events", () => { - expect(blockchain.getEvents()).toEqual([ - "block.applied", - "block.forged", - "block.reverted", - "delegate.registered", - "delegate.resigned", - "forger.failed", - "forger.missing", - "forger.started", - "peer.added", - "peer.removed", - "round.created", - "state:started", - "transaction.applied", - "transaction.expired", - "transaction.forged", - "transaction.reverted", - "wallet.saved", - "wallet.created.cold", - ]); - }); - }); - - describe("__registerQueue", () => { - it("should be ok", () => { - blockchain.__registerQueue(); - - expect(blockchain).toHaveProperty("queue"); - expect(blockchain).toHaveProperty("processQueue"); - expect(blockchain).toHaveProperty("rebuildQueue"); - }); - }); - - describe("stop on emit shutdown", () => { - it("should trigger the stop method when receiving 'shutdown' event", async () => { - const emitter = container.resolvePlugin("event-emitter"); - - // @ts-ignore - const stop = jest.spyOn(blockchain, "stop").mockReturnValue(true); - - emitter.emit("shutdown"); - - await delay(200); - - expect(stop).toHaveBeenCalled(); - }); - }); -}); - -async function __start(networkStart) { - process.env.CORE_SKIP_BLOCKCHAIN = "false"; - process.env.CORE_SKIP_PEER_STATE_VERIFICATION = "true"; - process.env.CORE_ENV = "false"; - - const plugin = require("../src").plugin; - - blockchain = await plugin.register(container, { - networkStart, - ...defaults, - }); - - await container.register( - "blockchain", - asValue({ - name: "blockchain", - version: "0.1.0", - plugin: blockchain, - options: {}, - }), - ); - - if (networkStart) { - return; - } - - await __resetToHeight1(); - - await blockchain.start(); - await __addBlocks(5); -} - -async function __resetBlocksInCurrentRound() { - await blockchain.database.loadBlocksFromCurrentRound(); -} - -async function __resetToHeight1() { - const lastBlock = await blockchain.database.getLastBlock(); - if (lastBlock) { - // Make sure the wallet manager has been fed or else revertRound - // cannot determine the previous delegates. This is only necessary, because - // the database is not dropped after the unit tests are done. - await blockchain.database.buildWallets(lastBlock.data.height); - - // Index the genesis wallet or else revert block at height 1 fails - const generator = crypto.getAddress(genesisBlock.data.generatorPublicKey); - const genesis = new Wallet(generator); - genesis.publicKey = genesisBlock.data.generatorPublicKey; - genesis.username = "genesis"; - blockchain.database.walletManager.reindex(genesis); - - blockchain.state.clear(); - - blockchain.state.setLastBlock(lastBlock); - await __resetBlocksInCurrentRound(); - await blockchain.removeBlocks(lastBlock.data.height - 1); - } -} - -async function __addBlocks(untilHeight) { - const allBlocks = [...blocks2to100, ...blocks101to155]; - const lastHeight = blockchain.getLastHeight(); - - for (let height = lastHeight + 1; height < untilHeight && height < 155; height++) { - const blockToProcess = new Block(allBlocks[height - 2]); - await blockchain.processBlock(blockToProcess, () => null); - } -} diff --git a/packages/core-blockchain/__tests__/machines/actions/rebuild-from-network.test.ts b/packages/core-blockchain/__tests__/machines/actions/rebuild-from-network.test.ts deleted file mode 100644 index 1821a7389b..0000000000 --- a/packages/core-blockchain/__tests__/machines/actions/rebuild-from-network.test.ts +++ /dev/null @@ -1,155 +0,0 @@ -import "@arkecosystem/core-test-utils/"; - -import { blockchainMachine as machine } from "../../../src/machines/blockchain"; - -describe("Blockchain machine > Rebuilding", () => { - it("should start with the `rebuilding` state", () => { - expect(machine.states.rebuild).toHaveProperty("initial", "rebuilding"); - }); - - describe("state `rebuilding`", () => { - it("should execute the `checkLastDownloadedBlockSynced` action when is entered", () => { - expect(machine).toExecuteOnEntry({ - state: "rebuild.rebuilding", - actions: ["checkLastDownloadedBlockSynced"], - }); - }); - - it("should transition to `waitingFinished` on `SYNCED`", () => { - expect(machine).toTransition({ - from: "rebuild.rebuilding", - on: "SYNCED", - to: "rebuild.waitingFinished", - }); - }); - - it("should transition to `revertBlocks` on `NOTSYNCED`", () => { - expect(machine).toTransition({ - from: "rebuild.rebuilding", - on: "NOTSYNCED", - to: "rebuild.rebuildBlocks", - }); - }); - - it("should transition to `rebuildPaused` on `PAUSED`", () => { - expect(machine).toTransition({ - from: "rebuild.rebuilding", - on: "PAUSED", - to: "rebuild.rebuildPaused", - }); - }); - }); - - describe("state `idle`", () => { - it("should transition to `rebuildBlocks` on `DOWNLOADED`", () => { - expect(machine).toTransition({ - from: "rebuild.idle", - on: "DOWNLOADED", - to: "rebuild.rebuildBlocks", - }); - }); - }); - - describe("state `rebuildBlocks`", () => { - it("should execute the `rebuildBlocks` action when is entered", () => { - expect(machine).toExecuteOnEntry({ - state: "rebuild.rebuildBlocks", - actions: ["rebuildBlocks"], - }); - }); - - it("should transition to `rebuilding` on `DOWNLOADED`", () => { - expect(machine).toTransition({ - from: "rebuild.rebuildBlocks", - on: "DOWNLOADED", - to: "rebuild.rebuilding", - }); - }); - - it("should transition to `rebuilding` on `NOBLOCK`", () => { - expect(machine).toTransition({ - from: "rebuild.rebuildBlocks", - on: "NOBLOCK", - to: "rebuild.rebuilding", - }); - }); - }); - - describe("state `waitingFinished`", () => { - it("should transition to `rebuildFinished` on `REBUILDFINISHED`", () => { - expect(machine).toTransition({ - from: "rebuild.waitingFinished", - on: "REBUILDFINISHED", - to: "rebuild.rebuildFinished", - }); - }); - }); - - describe("state `processFinished`", () => { - it("should execute the `checkRebuildBlockSynced` action when is entered", () => { - expect(machine).toExecuteOnEntry({ - state: "rebuild.processFinished", - actions: ["checkRebuildBlockSynced"], - }); - }); - - it("should transition to `processFinished` on `SYNCED`", () => { - expect(machine).toTransition({ - from: "rebuild.processFinished", - on: "SYNCED", - to: "rebuild.end", - }); - }); - - it("should transition to `processFinished` on `NOTSYNCED`", () => { - expect(machine).toTransition({ - from: "rebuild.processFinished", - on: "NOTSYNCED", - to: "rebuild.rebuildBlocks", - }); - }); - }); - - describe("state `rebuildPaused`", () => { - it("should execute the `downloadPaused` action when is entered", () => { - expect(machine).toExecuteOnEntry({ - state: "rebuild.rebuildPaused", - actions: ["downloadPaused"], - }); - }); - - it("should transition to `processFinished` on `REBUILDFINISHED`", () => { - expect(machine).toTransition({ - from: "rebuild.rebuildPaused", - on: "REBUILDFINISHED", - to: "rebuild.processFinished", - }); - }); - }); - - describe("state `rebuildFinished`", () => { - it("should execute the `rebuildFinished` action when is entered", () => { - expect(machine).toExecuteOnEntry({ - state: "rebuild.rebuildFinished", - actions: ["rebuildFinished"], - }); - }); - - it("should transition to `processFinished` on `PROCESSFINISHED`", () => { - expect(machine).toTransition({ - from: "rebuild.rebuildFinished", - on: "PROCESSFINISHED", - to: "rebuild.processFinished", - }); - }); - }); - - describe("state `end`", () => { - it("should execute the `rebuildingComplete` action when is entered", () => { - expect(machine).toExecuteOnEntry({ - state: "rebuild.end", - actions: ["rebuildingComplete"], - }); - }); - }); -}); diff --git a/packages/core-blockchain/__tests__/processor/handlers/exception-handler.test.ts b/packages/core-blockchain/__tests__/processor/handlers/exception-handler.test.ts deleted file mode 100644 index 1b0984edf7..0000000000 --- a/packages/core-blockchain/__tests__/processor/handlers/exception-handler.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import "@arkecosystem/core-test-utils"; -import { ExceptionHandler } from "../../../src/processor/handlers"; - -import { models } from "@arkecosystem/crypto"; -import { blocks2to100 } from "../../../../core-test-utils/src/fixtures/testnet/blocks2to100"; -import { Blockchain } from "../../../src/blockchain"; -import { BlockProcessorResult } from "../../../src/processor"; -import { setUpFull, tearDownFull } from "../../__support__/setup"; - -const { Block } = models; -let app; -let blockchain: Blockchain; - -beforeAll(async () => { - app = await setUpFull(); - blockchain = app.resolvePlugin("blockchain"); -}); - -afterAll(async () => { - await tearDownFull(); -}); - -describe("Exception handler", () => { - describe("execute", () => { - it("should reject if block has already been forged", async () => { - const handler = new ExceptionHandler(blockchain, new Block(blocks2to100[0])); - - // @ts-ignore - jest.spyOn(blockchain.database, "getBlock").mockReturnValueOnce(true); - - expect(await handler.execute()).toBe(BlockProcessorResult.Rejected); - }); - - it("should accept if block has not already been forged", async () => { - const handler = new ExceptionHandler(blockchain, new Block(blocks2to100[0])); - - expect(await handler.execute()).toBe(BlockProcessorResult.Accepted); - }); - }); -}); diff --git a/packages/core-blockchain/__tests__/queue/interface.test.ts b/packages/core-blockchain/__tests__/queue/interface.test.ts deleted file mode 100644 index 9ed581b1f6..0000000000 --- a/packages/core-blockchain/__tests__/queue/interface.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import "@arkecosystem/core-test-utils"; -import async from "async"; -import { asValue } from "awilix"; -import delay from "delay"; -import { Blockchain } from "../../src/blockchain"; -import { QueueInterface } from "../../src/queue/interface"; -import { setUp, tearDown } from "../__support__/setup"; - -let fakeQueue; -let container; -let blockchain: Blockchain; - -class FakeQueue extends QueueInterface { - /** - * Create an instance of the process queue. - */ - constructor(readonly blockchainInstance: Blockchain, readonly event: string) { - super(blockchainInstance, event); - - this.queue = async.queue(async (item: any, cb) => { - await delay(1000); - return cb(); - }, 1); - } -} - -beforeAll(async () => { - container = await setUp(); - - process.env.CORE_SKIP_BLOCKCHAIN = "true"; - - // Manually register the blockchain - const plugin = require("../../src").plugin; - - blockchain = await plugin.register(container, { - networkStart: false, - }); - - await container.register( - "blockchain", - asValue({ - name: "blockchain", - version: "0.1.0", - plugin: blockchain, - options: {}, - }), - ); -}); - -afterAll(async () => { - await tearDown(); -}); - -beforeEach(async () => { - process.env.CORE_SKIP_BLOCKCHAIN = "false"; - - fakeQueue = new FakeQueue(blockchain, "fake"); -}); - -describe("FakeQueue", () => { - it("should remove successfully an item from the queue", async () => { - const cb = jest.fn(); - fakeQueue.push(cb); - - expect(fakeQueue.queue.length()).toBe(1); - - fakeQueue.remove(obj => true); // removes everything, see async queue doc - - expect(fakeQueue.queue.length()).toBe(0); - }); -}); diff --git a/packages/core-blockchain/__tests__/queue/process.test.ts b/packages/core-blockchain/__tests__/queue/process.test.ts deleted file mode 100644 index a49780b630..0000000000 --- a/packages/core-blockchain/__tests__/queue/process.test.ts +++ /dev/null @@ -1,74 +0,0 @@ -import "@arkecosystem/core-test-utils"; -import { asValue } from "awilix"; -import delay from "delay"; -import { blocks2to100 } from "../../../core-test-utils/src/fixtures/testnet/blocks2to100"; -import { Blockchain } from "../../src/blockchain"; -import { setUp, tearDown } from "../__support__/setup"; - -let processQueue; -let container; -let blockchain: Blockchain; - -beforeAll(async () => { - container = await setUp(); - - process.env.CORE_SKIP_BLOCKCHAIN = "true"; - - // Manually register the blockchain - const plugin = require("../../src").plugin; - - blockchain = await plugin.register(container, { - networkStart: false, - }); - - await container.register( - "blockchain", - asValue({ - name: "blockchain", - version: "0.1.0", - plugin: blockchain, - options: {}, - }), - ); -}); - -afterAll(async () => { - jest.restoreAllMocks(); - await tearDown(); -}); - -beforeEach(async () => { - process.env.CORE_SKIP_BLOCKCHAIN = "false"; - jest.restoreAllMocks(); - - const ProcessQueue = require("../../src/queue").ProcessQueue; - processQueue = new ProcessQueue(blockchain, "processEvent"); -}); - -describe("ProcessQueue", () => { - it("should call blockchain processBlock when pushing a block to the queue", async () => { - // @ts-ignore - const processBlock = jest.spyOn(blockchain, "processBlock").mockReturnValue(true); - - const cb = jest.fn(); - processQueue.push(blocks2to100[3], cb); - - await delay(200); - expect(processBlock).toHaveBeenCalled(); - }); - - it("should log error and call callback when blockchain processBlock throws", async () => { - const processBlock = jest.spyOn(blockchain, "processBlock").mockImplementation(() => { - throw new Error("wooo"); - }); - - const loggerError = jest.spyOn(container.resolvePlugin("logger"), "error"); - - const cb = jest.fn(); - processQueue.push(blocks2to100[3], cb); - - await delay(200); - expect(processBlock).toHaveBeenCalled(); - expect(loggerError).toHaveBeenCalledWith(`Failed to process block in ProcessQueue: ${blocks2to100[3].height}`); - }); -}); diff --git a/packages/core-blockchain/__tests__/queue/rebuild.test.ts b/packages/core-blockchain/__tests__/queue/rebuild.test.ts deleted file mode 100644 index 4a0486e93a..0000000000 --- a/packages/core-blockchain/__tests__/queue/rebuild.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -import "@arkecosystem/core-test-utils"; -import { asValue } from "awilix"; -import delay from "delay"; -import { blocks2to100 } from "../../../core-test-utils/src/fixtures/testnet/blocks2to100"; -import { Blockchain } from "../../src/blockchain"; -import { setUp, tearDown } from "../__support__/setup"; - -let rebuildQueue; -let container; -let blockchain: Blockchain; - -beforeAll(async () => { - container = await setUp(); - - process.env.CORE_SKIP_BLOCKCHAIN = "true"; - - // Manually register the blockchain - const plugin = require("../../src").plugin; - - blockchain = await plugin.register(container, { - networkStart: false, - }); - - await container.register( - "blockchain", - asValue({ - name: "blockchain", - version: "0.1.0", - plugin: blockchain, - options: {}, - }), - ); -}); - -afterAll(async () => { - jest.restoreAllMocks(); - await tearDown(); -}); - -beforeEach(async () => { - process.env.CORE_SKIP_BLOCKCHAIN = "false"; - jest.restoreAllMocks(); - - const RebuildQueue = require("../../src/queue").RebuildQueue; - rebuildQueue = new RebuildQueue(blockchain, "processEvent"); -}); - -describe("RebuildQueue", () => { - it("should call blockchain rebuildBlock when pushing a block to the queue", async () => { - // @ts-ignore - const rebuildBlock = jest.spyOn(blockchain, "rebuildBlock").mockReturnValue(true); - - const cb = jest.fn(); - rebuildQueue.push(blocks2to100[3], cb); - - await delay(200); - expect(rebuildBlock).toHaveBeenCalled(); - }); - - it.skip("should just call callback if queue is paused when pushing a block to the queue", async () => { - // should call callback, but doesn't seem so... TODO - // @ts-ignore - const rebuildBlock = jest.spyOn(blockchain, "rebuildBlock").mockReturnValue(true); - - const cb = jest.fn(() => { - throw new Error("uuuui"); - }); - rebuildQueue.queue.paused = true; - rebuildQueue.queue.push(blocks2to100[3], cb); - - await delay(200); - expect(rebuildBlock).not.toHaveBeenCalled(); - expect(cb).toHaveBeenCalled(); - }); - - it("should log error and call callback when blockchain rebuildBlock throws", async () => { - const rebuildBlock = jest.spyOn(blockchain, "rebuildBlock").mockImplementation(() => { - throw new Error("wooo"); - }); - - const loggerError = jest.spyOn(container.resolvePlugin("logger"), "error"); - - const cb = jest.fn(() => true); - rebuildQueue.push(blocks2to100[3], cb); - - await delay(200); - expect(rebuildBlock).toHaveBeenCalled(); - expect(loggerError).toHaveBeenCalledWith(`Failed to rebuild block in RebuildQueue: ${blocks2to100[3].height}`); - }); -}); diff --git a/packages/core-blockchain/package.json b/packages/core-blockchain/package.json index ba1c9bea11..d447afec9e 100644 --- a/packages/core-blockchain/package.json +++ b/packages/core-blockchain/package.json @@ -1,7 +1,7 @@ { "name": "@arkecosystem/core-blockchain", - "description": "Blockchain Manager for Ark Core", - "version": "2.2.1", + "description": "Blockchain Manager for ARK Core", + "version": "2.3.15", "contributors": [ "François-Xavier Thoorens ", "Kristjan Košič ", @@ -14,56 +14,38 @@ "dist" ], "scripts": { - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", "prepublishOnly": "yarn build", "pretest": "bash ../../scripts/pre-test.sh", "compile": "../../node_modules/typescript/bin/tsc", "build": "yarn clean && yarn compile", "build:watch": "yarn clean && yarn compile -w", - "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "test": "cross-env CORE_ENV=test jest --runInBand --forceExit", - "test:coverage": "cross-env CORE_ENV=test jest --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$' --runInBand --forceExit", - "test:debug": "cross-env CORE_ENV=test node --inspect-brk ../../node_modules/.bin/jest --runInBand", - "test:watch": "cross-env CORE_ENV=test jest --runInBand --watch", - "test:watch:all": "cross-env CORE_ENV=test jest --runInBand --watchAll", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" + "clean": "del dist" }, "dependencies": { - "@arkecosystem/core-container": "^2.2.1", - "@arkecosystem/core-interfaces": "^2.2.1", - "@arkecosystem/core-utils": "^2.2.1", - "@arkecosystem/crypto": "^2.2.1", + "@arkecosystem/core-container": "^2.3.15", + "@arkecosystem/core-interfaces": "^2.3.15", + "@arkecosystem/core-utils": "^2.3.15", + "@arkecosystem/crypto": "^2.3.15", "async": "^2.6.2", - "awilix": "^4.2.0", + "awilix": "^4.2.1", "delay": "^4.1.0", "immutable": "^4.0.0-rc.12", "lodash.get": "^4.4.2", "pluralize": "^7.0.0", "pretty-ms": "^4.0.0", - "xstate": "^4.3.1" + "xstate": "^4.3.3" }, "devDependencies": { - "@arkecosystem/core-p2p": "^2.2.1", - "@arkecosystem/core-test-utils": "^2.2.1", - "@types/async": "^2.4.0", - "@types/lodash.get": "^4.4.4", + "@arkecosystem/core-p2p": "^2.3.15", + "@types/async": "^2.4.1", + "@types/lodash.get": "^4.4.6", "@types/pluralize": "^0.0.29", - "@types/pretty-ms": "^4.0.0", - "axios": "^0.18.0", - "axios-mock-adapter": "^1.16.0" + "@types/pretty-ms": "^4.0.0" }, "publishConfig": { "access": "public" }, "engines": { "node": ">=10.x" - }, - "jest": { - "preset": "../../jest-preset.json" } } diff --git a/packages/core-blockchain/src/blockchain.ts b/packages/core-blockchain/src/blockchain.ts index a5590644ad..8900459879 100644 --- a/packages/core-blockchain/src/blockchain.ts +++ b/packages/core-blockchain/src/blockchain.ts @@ -8,15 +8,14 @@ import { P2P, TransactionPool, } from "@arkecosystem/core-interfaces"; -import { models, slots } from "@arkecosystem/crypto"; +import { models, slots, Transaction } from "@arkecosystem/crypto"; +import async from "async"; import delay from "delay"; import pluralize from "pluralize"; import { BlockProcessor, BlockProcessorResult } from "./processor"; -import { ProcessQueue, Queue, RebuildQueue } from "./queue"; import { stateMachine } from "./state-machine"; import { StateStorage } from "./state-storage"; -import { isBlockChained } from "./utils"; const logger = app.resolvePlugin("logger"); const config = app.getConfig(); @@ -36,7 +35,7 @@ export class Blockchain implements blockchain.IBlockchain { * Get the network (p2p) interface. * @return {P2PInterface} */ - get p2p() { + get p2p(): P2P.IMonitor { return app.resolvePlugin("p2p"); } @@ -44,24 +43,22 @@ export class Blockchain implements blockchain.IBlockchain { * Get the transaction handler. * @return {TransactionPool} */ - get transactionPool() { - return app.resolvePlugin("transactionPool"); + get transactionPool(): TransactionPool.IConnection { + return app.resolvePlugin("transaction-pool"); } /** * Get the database connection. * @return {ConnectionInterface} */ - get database() { + get database(): Database.IDatabaseService { return app.resolvePlugin("database"); } public isStopped: boolean; public options: any; - public processQueue: ProcessQueue; - public rebuildQueue: RebuildQueue; + public queue: async.AsyncQueue; private actions: any; - private queue: Queue; private blockProcessor: BlockProcessor; /** @@ -69,21 +66,31 @@ export class Blockchain implements blockchain.IBlockchain { * @param {Object} options * @return {void} */ - constructor(options) { + constructor(options: { networkStart?: boolean }) { // flag to force a network start this.state.networkStart = !!options.networkStart; if (this.state.networkStart) { logger.warn( - "Ark Core is launched in Genesis Start mode. This is usually for starting the first node on the blockchain. Unless you know what you are doing, this is likely wrong. :warning:", + "Persona Core is launched in Genesis Start mode. This is usually for starting the first node on the blockchain. Unless you know what you are doing, this is likely wrong. :warning:", ); - logger.info("Starting Ark Core for a new world, welcome aboard :rocket:"); + logger.info("Starting Persona Core for a new world, welcome aboard :rocket:"); } this.actions = stateMachine.actionMap(this); this.blockProcessor = new BlockProcessor(this); - this.__registerQueue(); + this.queue = async.queue((block: models.IBlockData, cb) => { + try { + return this.processBlock(new models.Block(block), cb); + } catch (error) { + logger.error(`Failed to process block in queue: ${block.height.toLocaleString()}`); + logger.error(error.stack); + return cb(); + } + }, 1); + + this.queue.drain = () => this.dispatch("PROCESSFINISHED"); } /** @@ -91,7 +98,7 @@ export class Blockchain implements blockchain.IBlockchain { * @param {String} event * @return {void} */ - public dispatch(event) { + public dispatch(event): void { const nextState = stateMachine.transition(this.state.blockchain, event); if (nextState.actions.length > 0) { @@ -100,6 +107,12 @@ export class Blockchain implements blockchain.IBlockchain { nextState.value, )} -> actions: [${nextState.actions.map(a => a.type).join(", ")}]`, ); + } else { + logger.debug( + `event '${event}': ${JSON.stringify(this.state.blockchain.value)} -> ${JSON.stringify( + nextState.value, + )}`, + ); } this.state.blockchain = nextState; @@ -110,7 +123,7 @@ export class Blockchain implements blockchain.IBlockchain { if (action) { setTimeout(() => action.call(this, event), 0); } else { - logger.error(`No action '${actionKey}' found :interrobang:`); + logger.error(`No action '${actionKey}' found`); } }); @@ -121,7 +134,7 @@ export class Blockchain implements blockchain.IBlockchain { * Start the blockchain and wait for it to be ready. * @return {void} */ - public async start(skipStartedCheck = false) { + public async start(skipStartedCheck = false): Promise { logger.info("Starting Blockchain Manager :chains:"); this.dispatch("START"); @@ -142,7 +155,7 @@ export class Blockchain implements blockchain.IBlockchain { return true; } - public async stop() { + public async stop(): Promise { if (!this.isStopped) { logger.info("Stopping Blockchain Manager :chains:"); @@ -151,18 +164,14 @@ export class Blockchain implements blockchain.IBlockchain { this.dispatch("STOP"); - this.queue.destroy(); + this.queue.kill(); } } - public checkNetwork() { - throw new Error("Method [checkNetwork] not implemented!"); - } - /** * Set wakeup timeout to check the network for new blocks. */ - public setWakeUp() { + public setWakeUp(): void { this.state.wakeUpTimeout = setTimeout(() => { this.state.wakeUpTimeout = null; return this.dispatch("WAKEUP"); @@ -172,7 +181,7 @@ export class Blockchain implements blockchain.IBlockchain { /** * Reset the wakeup timeout. */ - public resetWakeUp() { + public resetWakeUp(): void { this.state.clearWakeUpTimeout(); this.setWakeUp(); } @@ -181,24 +190,15 @@ export class Blockchain implements blockchain.IBlockchain { * Update network status. * @return {void} */ - public async updateNetworkStatus() { - return this.p2p.updateNetworkStatus(); - } - - /** - * Rebuild N blocks in the blockchain. - * @param {Number} nblocks - * @return {void} - */ - public rebuild(nblocks?: number) { - throw new Error("Method [rebuild] not implemented!"); + public async updateNetworkStatus(): Promise { + await this.p2p.updateNetworkStatus(); } /** * Reset the state of the blockchain. * @return {void} */ - public resetState() { + public resetState(): void { this.clearAndStopQueue(); this.state.reset(); } @@ -207,20 +207,26 @@ export class Blockchain implements blockchain.IBlockchain { * Clear and stop the queue. * @return {void} */ - public clearAndStopQueue() { + public clearAndStopQueue(): void { this.state.lastDownloadedBlock = this.getLastBlock(); this.queue.pause(); - this.queue.clear(); + this.clearQueue(); } /** - * Hand the given transactions to the transaction handler. - * @param {Array} transactions + * Clear the queue. * @return {void} */ - public async postTransactions(transactions) { - logger.info(`Received ${transactions.length} new ${pluralize("transaction", transactions.length)} :moneybag:`); + public clearQueue(): void { + this.queue.remove(() => true); + } + + /** + * Hand the given transactions to the transaction handler. + */ + public async postTransactions(transactions: Transaction[]): Promise { + logger.info(`Received ${transactions.length} new ${pluralize("transaction", transactions.length)}`); await this.transactionPool.addTransactions(transactions); } @@ -230,7 +236,7 @@ export class Blockchain implements blockchain.IBlockchain { * @param {Block} block * @return {void} */ - public handleIncomingBlock(block) { + public handleIncomingBlock(block): void { logger.info( `Received new block at height ${block.height.toLocaleString()} with ${pluralize( "transaction", @@ -249,78 +255,33 @@ export class Blockchain implements blockchain.IBlockchain { if (this.state.started) { this.dispatch("NEWBLOCK"); this.enqueueBlocks([block]); + + emitter.emit("block.received", block); } else { - logger.info(`Block disregarded because blockchain is not ready :exclamation:`); + logger.info(`Block disregarded because blockchain is not ready`); + + emitter.emit("block.disregarded", block); } } /** * Enqueue blocks in process queue and set last downloaded block to last item in list. */ - public enqueueBlocks(blocks: any[]) { + public enqueueBlocks(blocks: any[]): void { if (blocks.length === 0) { return; } - this.processQueue.push(blocks); + this.queue.push(blocks); this.state.lastDownloadedBlock = new Block(blocks.slice(-1)[0]); } - /** - * Rollback all blocks up to the previous round. - * @return {void} - */ - public async rollbackCurrentRound() { - const height = this.state.getLastBlock().data.height; - const maxDelegates = config.getMilestone(height).activeDelegates; - const previousRound = Math.floor((height - 1) / maxDelegates); - - if (previousRound < 2) { - return; - } - - const newHeight = previousRound * maxDelegates; - // If the current chain height is H and we will be removing blocks [N, H], - // then blocksToRemove[] will contain blocks [N - 1, H - 1]. - const blocksToRemove = await this.database.getBlocks(newHeight, height - newHeight); - const deleteLastBlock = async () => { - const lastBlock = this.state.getLastBlock(); - await this.database.enqueueDeleteBlock(lastBlock); - - const newLastBlock = new Block(blocksToRemove.pop()); - - this.state.setLastBlock(newLastBlock); - this.state.lastDownloadedBlock = newLastBlock; - }; - - logger.info(`Removing ${pluralize("block", height - newHeight, true)} to reset current round :warning:`); - - let count = 0; - const max = this.state.getLastBlock().data.height - newHeight; - - while (this.state.getLastBlock().data.height >= newHeight + 1) { - const removalBlockId = this.state.getLastBlock().data.id; - const removalBlockHeight = this.state.getLastBlock().data.height.toLocaleString(); - - logger.info(`Removing block ${count++} of ${max} - ID: ${removalBlockId}, height: ${removalBlockHeight}`); - - await deleteLastBlock(); - } - - // Commit delete blocks - await this.database.commitQueuedQueries(); - - logger.info(`Removed ${count} ${pluralize("block", max, true)}`); - - await this.database.deleteRound(previousRound + 1); - } - /** * Remove N number of blocks. * @param {Number} nblocks * @return {void} */ - public async removeBlocks(nblocks) { + public async removeBlocks(nblocks: number): Promise { this.clearAndStopQueue(); // If the current chain height is H and we will be removing blocks [N, H], @@ -381,7 +342,7 @@ export class Blockchain implements blockchain.IBlockchain { * @param {Number} count * @return {void} */ - public async removeTopBlocks(count) { + public async removeTopBlocks(count: number): Promise { const blocks = await this.database.getTopBlocks(count); logger.info( @@ -403,60 +364,16 @@ export class Blockchain implements blockchain.IBlockchain { await this.database.loadBlocksFromCurrentRound(); } - /** - * Hande a block during a rebuild. - * NOTE: We should be sure this is fail safe (ie callback() is being called only ONCE) - * @param {Block} block - * @param {Function} callback - * @return {Object} - */ - public async rebuildBlock(block, callback) { - const lastBlock = this.state.getLastBlock(); - - if (block.verification.verified) { - if (isBlockChained(lastBlock, block)) { - // save block on database - this.database.enqueueSaveBlock(block); - - // committing to db every 20,000 blocks - if (block.data.height % 20000 === 0) { - await this.database.commitQueuedQueries(); - } - - this.state.setLastBlock(block); - - return callback(); - } - if (block.data.height > lastBlock.data.height + 1) { - this.state.lastDownloadedBlock = lastBlock; - return callback(); - } - if ( - block.data.height < lastBlock.data.height || - (block.data.height === lastBlock.data.height && block.data.id === lastBlock.data.id) - ) { - this.state.lastDownloadedBlock = lastBlock; - return callback(); - } - this.state.lastDownloadedBlock = lastBlock; - logger.info(`Block ${block.data.height.toLocaleString()} disregarded because on a fork :knife_fork_plate:`); - return callback(); - } - logger.warn(`Block ${block.data.height.toLocaleString()} disregarded because verification failed :scroll:`); - logger.warn(JSON.stringify(block.verification, null, 4)); - return callback(); - } - /** * Process the given block. */ - public async processBlock(block: models.Block, callback) { + public async processBlock(block: models.Block, callback): Promise { const result = await this.blockProcessor.process(block); if (result === BlockProcessorResult.Accepted || result === BlockProcessorResult.DiscardedButCanBeBroadcasted) { // broadcast only current block const blocktime = config.getMilestone(block.data.height).blocktime; - if (slots.getSlotNumber() * blocktime <= block.data.timestamp) { + if (this.state.started && slots.getSlotNumber() * blocktime <= block.data.timestamp) { this.p2p.broadcastBlock(block); } } @@ -467,7 +384,7 @@ export class Blockchain implements blockchain.IBlockchain { /** * Reset the last downloaded block to last chained block. */ - public resetLastDownloadedBlock() { + public resetLastDownloadedBlock(): void { this.state.lastDownloadedBlock = this.getLastBlock(); } @@ -475,7 +392,7 @@ export class Blockchain implements blockchain.IBlockchain { * Called by forger to wake up and sync with the network. * It clears the wakeUpTimeout if set. */ - public forceWakeup() { + public forceWakeup(): void { this.state.clearWakeUpTimeout(); this.dispatch("WAKEUP"); } @@ -499,7 +416,7 @@ export class Blockchain implements blockchain.IBlockchain { * @param {Boolean} forForging * @return {Object} */ - public getUnconfirmedTransactions(blockSize) { + public getUnconfirmedTransactions(blockSize: number): { transactions: string[]; poolSize: number; count: number } { const transactions = this.transactionPool.getTransactionsForForging(blockSize); return { @@ -522,24 +439,6 @@ export class Blockchain implements blockchain.IBlockchain { return slots.getTime() - block.data.timestamp < 3 * config.getMilestone(block.data.height).blocktime; } - /** - * Determine if the blockchain is synced after a rebuild. - */ - public isRebuildSynced(block?: models.IBlock): boolean { - if (!this.p2p.hasPeers()) { - return true; - } - - block = block || this.getLastBlock(); - - const remaining = slots.getTime() - block.data.timestamp; - logger.info(`Remaining block timestamp ${remaining} :hourglass:`); - - // stop fast rebuild 7 days before the last network block - return slots.getTime() - block.data.timestamp < 3600 * 24 * 7; - // return slots.getTime() - block.data.timestamp < 100 * config.getMilestone(block.data.height).blocktime - } - /** * Get the last block of the blockchain. */ @@ -564,7 +463,7 @@ export class Blockchain implements blockchain.IBlockchain { /** * Get the block ping. */ - public getBlockPing() { + public getBlockPing(): number { return this.state.blockPing; } @@ -578,48 +477,7 @@ export class Blockchain implements blockchain.IBlockchain { /** * Push ping block. */ - public pushPingBlock(block: models.IBlockData) { + public pushPingBlock(block: models.IBlockData): void { this.state.pushPingBlock(block); } - - /** - * Get the list of events that are available. - * @return {Array} - */ - public getEvents() { - return [ - "block.applied", - "block.forged", - "block.reverted", - "delegate.registered", - "delegate.resigned", - "forger.failed", - "forger.missing", - "forger.started", - "peer.added", - "peer.removed", - "round.created", - "state:started", - "transaction.applied", - "transaction.expired", - "transaction.forged", - "transaction.reverted", - "wallet.saved", - "wallet.created.cold", - ]; - } - - /** - * Register the block queue. - * @return {void} - */ - public __registerQueue() { - this.queue = new Queue(this, { - process: "PROCESSFINISHED", - rebuild: "REBUILDFINISHED", - }); - - this.processQueue = this.queue.process; - this.rebuildQueue = this.queue.rebuild; - } } diff --git a/packages/core-blockchain/src/defaults.ts b/packages/core-blockchain/src/defaults.ts index 2838157dd8..25d7dca523 100644 --- a/packages/core-blockchain/src/defaults.ts +++ b/packages/core-blockchain/src/defaults.ts @@ -1,5 +1,4 @@ export const defaults = { - fastRebuild: false, databaseRollback: { maxBlockRewind: 10000, steps: 1000, diff --git a/packages/core-blockchain/src/machines/actions/fork.ts b/packages/core-blockchain/src/machines/actions/fork.ts index 0b205c99e4..05c093d9a9 100644 --- a/packages/core-blockchain/src/machines/actions/fork.ts +++ b/packages/core-blockchain/src/machines/actions/fork.ts @@ -4,18 +4,11 @@ export const fork = { analysing: { onEntry: ["analyseFork"], on: { - REBUILD: "revertBlocks", NOFORK: "exit", }, }, network: { - onEntry: ["checkNetwork"], - /* these transitions are not used yet (TODO?) - on: { - SUCCESS: 'blockchain', - FAILURE: 'reset' - } - */ + onEntry: ["checkNetwork"], // TODO: implement }, revertBlocks: {}, exit: { @@ -23,39 +16,3 @@ export const fork = { }, }, }; - -// const fork = { -// initial: 'network', -// states: { -// network: { -// onEntry: ['checkNetwork'], -// on: { -// SUCCESS: 'blockchain', -// FAILURE: 'reset' -// } -// }, -// blockchain: { -// onEntry: ['removeBlocks'], -// on: { -// SUCCESS: 'wallets', -// FAILURE: 'reset' -// } -// }, -// wallets: { -// onEntry: ['rebuildWallets'], -// on: { -// SUCCESS: 'success', -// FAILURE: 'reset' -// } -// }, -// reset: { -// onEntry: ['resetNode'], -// on: { -// RESET: 'success', -// FAILURE: 'reset' -// } -// }, -// success: { -// } -// } -// } diff --git a/packages/core-blockchain/src/machines/actions/rebuild-from-network.ts b/packages/core-blockchain/src/machines/actions/rebuild-from-network.ts deleted file mode 100644 index 70ca9e2a7e..0000000000 --- a/packages/core-blockchain/src/machines/actions/rebuild-from-network.ts +++ /dev/null @@ -1,52 +0,0 @@ -export const rebuildFromNetwork = { - initial: "rebuilding", - states: { - rebuilding: { - onEntry: ["checkLastDownloadedBlockSynced"], - on: { - SYNCED: "waitingFinished", - NOTSYNCED: "rebuildBlocks", - PAUSED: "rebuildPaused", - }, - }, - idle: { - on: { - DOWNLOADED: "rebuildBlocks", - }, - }, - rebuildBlocks: { - onEntry: ["rebuildBlocks"], - on: { - DOWNLOADED: "rebuilding", - NOBLOCK: "rebuilding", - }, - }, - waitingFinished: { - on: { - REBUILDFINISHED: "rebuildFinished", - }, - }, - rebuildFinished: { - onEntry: ["rebuildFinished"], - on: { - PROCESSFINISHED: "processFinished", - }, - }, - rebuildPaused: { - onEntry: ["downloadPaused"], - on: { - REBUILDFINISHED: "processFinished", - }, - }, - processFinished: { - onEntry: ["checkRebuildBlockSynced"], - on: { - SYNCED: "end", - NOTSYNCED: "rebuildBlocks", - }, - }, - end: { - onEntry: ["rebuildingComplete"], - }, - }, -}; diff --git a/packages/core-blockchain/src/machines/actions/sync-with-network.ts b/packages/core-blockchain/src/machines/actions/sync-with-network.ts index f8683ad344..f2ff9ea82e 100644 --- a/packages/core-blockchain/src/machines/actions/sync-with-network.ts +++ b/packages/core-blockchain/src/machines/actions/sync-with-network.ts @@ -20,6 +20,7 @@ export const syncWithNetwork = { on: { DOWNLOADED: "syncing", NOBLOCK: "syncing", + PROCESSFINISHED: "downloadFinished", }, }, downloadFinished: { diff --git a/packages/core-blockchain/src/machines/blockchain.ts b/packages/core-blockchain/src/machines/blockchain.ts index 779bfb5b5d..c9b2ea737d 100644 --- a/packages/core-blockchain/src/machines/blockchain.ts +++ b/packages/core-blockchain/src/machines/blockchain.ts @@ -1,6 +1,5 @@ import { Machine } from "xstate"; import { fork } from "./actions/fork"; -import { rebuildFromNetwork } from "./actions/rebuild-from-network"; import { syncWithNetwork } from "./actions/sync-with-network"; export const blockchainMachine: any = Machine({ @@ -16,7 +15,6 @@ export const blockchainMachine: any = Machine({ init: { onEntry: ["init"], on: { - REBUILD: "rebuild", NETWORKSTART: "idle", STARTED: "syncWithNetwork", ROLLBACK: "rollback", @@ -24,15 +22,6 @@ export const blockchainMachine: any = Machine({ STOP: "stopped", }, }, - rebuild: { - on: { - REBUILDCOMPLETE: "syncWithNetwork", - FORK: "fork", - TEST: "syncWithNetwork", - STOP: "stopped", - }, - ...rebuildFromNetwork, - }, syncWithNetwork: { on: { TEST: "idle", diff --git a/packages/core-blockchain/src/plugin.ts b/packages/core-blockchain/src/plugin.ts index 57771cefc7..76bcc97b7f 100644 --- a/packages/core-blockchain/src/plugin.ts +++ b/packages/core-blockchain/src/plugin.ts @@ -13,7 +13,7 @@ export const plugin: Container.PluginDescriptor = { pkg: require("../package.json"), defaults, alias: "blockchain", - async register(container: Container.IContainer, options) { + async register(container: Container.IContainer, options: Container.IPluginOptions) { const blockchain = new Blockchain(options); config.init(options); diff --git a/packages/core-blockchain/src/processor/block-processor.ts b/packages/core-blockchain/src/processor/block-processor.ts index 29d51782db..3009be231d 100644 --- a/packages/core-blockchain/src/processor/block-processor.ts +++ b/packages/core-blockchain/src/processor/block-processor.ts @@ -71,7 +71,7 @@ export class BlockProcessor { this.logger.warn( `Block ${block.data.height.toLocaleString()} (${ block.data.id - }) disregarded because verification failed :scroll:`, + }) disregarded because verification failed`, ); this.logger.warn(JSON.stringify(block.verification, null, 4)); return false; @@ -90,7 +90,7 @@ export class BlockProcessor { ); if (forgedIds.length > 0) { this.logger.warn( - `Block ${block.data.height.toLocaleString()} disregarded, because it contains already forged transactions :scroll:`, + `Block ${block.data.height.toLocaleString()} disregarded, because it contains already forged transactions`, ); this.logger.debug(`${JSON.stringify(forgedIds, null, 4)}`); return true; diff --git a/packages/core-blockchain/src/processor/handlers/accept-block-handler.ts b/packages/core-blockchain/src/processor/handlers/accept-block-handler.ts index 91f9472a82..fbde23e8d8 100644 --- a/packages/core-blockchain/src/processor/handlers/accept-block-handler.ts +++ b/packages/core-blockchain/src/processor/handlers/accept-block-handler.ts @@ -11,7 +11,7 @@ export class AcceptBlockHandler extends BlockHandler { // Check if we recovered from a fork if (state.forkedBlock && state.forkedBlock.data.height === this.block.data.height) { - this.logger.info("Successfully recovered from fork :star2:"); + this.logger.info("Successfully recovered from fork"); state.forkedBlock = null; } @@ -40,11 +40,10 @@ export class AcceptBlockHandler extends BlockHandler { return BlockProcessorResult.Accepted; } catch (error) { - this.logger.error(`Refused new block ${JSON.stringify(this.block.data)}`); + this.logger.warn(`Refused new block ${JSON.stringify(this.block.data)}`); this.logger.debug(error.stack); this.blockchain.transactionPool.purgeBlock(this.block); - this.blockchain.forkBlock(this.block); return super.execute(); } diff --git a/packages/core-blockchain/src/processor/handlers/block-handler.ts b/packages/core-blockchain/src/processor/handlers/block-handler.ts index 5a5ffd0494..7bf9b70f15 100644 --- a/packages/core-blockchain/src/processor/handlers/block-handler.ts +++ b/packages/core-blockchain/src/processor/handlers/block-handler.ts @@ -5,11 +5,9 @@ import { Blockchain } from "../../blockchain"; import { BlockProcessorResult } from "../block-processor"; export abstract class BlockHandler { - protected logger: Logger.ILogger; + protected readonly logger: Logger.ILogger = app.resolvePlugin("logger"); - public constructor(protected blockchain: Blockchain, protected block: models.Block) { - this.logger = app.resolvePlugin("logger"); - } + public constructor(protected readonly blockchain: Blockchain, protected readonly block: models.Block) {} public async execute(): Promise { this.blockchain.resetLastDownloadedBlock(); diff --git a/packages/core-blockchain/src/processor/handlers/exception-handler.ts b/packages/core-blockchain/src/processor/handlers/exception-handler.ts index 52f05e8bec..50328146db 100644 --- a/packages/core-blockchain/src/processor/handlers/exception-handler.ts +++ b/packages/core-blockchain/src/processor/handlers/exception-handler.ts @@ -11,9 +11,7 @@ export class ExceptionHandler extends BlockHandler { return super.execute(); } - this.logger.warn( - `Block ${this.block.data.height.toLocaleString()} (${this.block.data.id}) forcibly accepted. :exclamation:`, - ); + this.logger.warn(`Block ${this.block.data.height.toLocaleString()} (${this.block.data.id}) forcibly accepted.`); return new AcceptBlockHandler(this.blockchain, this.block).execute(); } diff --git a/packages/core-blockchain/src/processor/handlers/unchained-handler.ts b/packages/core-blockchain/src/processor/handlers/unchained-handler.ts index e41dddf48b..1420c28045 100644 --- a/packages/core-blockchain/src/processor/handlers/unchained-handler.ts +++ b/packages/core-blockchain/src/processor/handlers/unchained-handler.ts @@ -1,6 +1,7 @@ // tslint:disable:max-classes-per-file import { app } from "@arkecosystem/core-container"; +import { roundCalculator } from "@arkecosystem/core-utils"; import { models } from "@arkecosystem/crypto"; import { Blockchain } from "../../blockchain"; import { BlockProcessorResult } from "../block-processor"; @@ -51,8 +52,8 @@ export class UnchainedHandler extends BlockHandler { public static notReadyCounter = new BlockNotReadyCounter(); public constructor( - protected blockchain: Blockchain, - protected block: models.Block, + protected readonly blockchain: Blockchain, + protected readonly block: models.Block, private isValidGenerator: boolean, ) { super(blockchain, block); @@ -61,13 +62,14 @@ export class UnchainedHandler extends BlockHandler { public async execute(): Promise { super.execute(); - this.blockchain.processQueue.clear(); + this.blockchain.clearQueue(); const status = this.checkUnchainedBlock(); switch (status) { case UnchainedBlockStatus.DoubleForging: { const database = app.resolvePlugin("database"); - const delegates = await database.getActiveDelegates(this.block.data.height); + const roundInfo = roundCalculator.calculateRound(this.block.data.height); + const delegates = await database.getActiveDelegates(roundInfo); if (delegates.some(delegate => delegate.publicKey === this.block.data.generatorPublicKey)) { this.blockchain.forkBlock(this.block); } @@ -95,15 +97,15 @@ export class UnchainedHandler extends BlockHandler { const lastBlock = this.blockchain.getLastBlock(); if (this.block.data.height > lastBlock.data.height + 1) { this.logger.debug( - `Blockchain not ready to accept new block at height ${this.block.data.height.toLocaleString()}. Last block: ${lastBlock.data.height.toLocaleString()} :warning:`, + `Blockchain not ready to accept new block at height ${this.block.data.height.toLocaleString()}. Last block: ${lastBlock.data.height.toLocaleString()}`, ); // Also remove all remaining queued blocks. Since blocks are downloaded in batches, // it is very likely that all blocks will be disregarded at this point anyway. // NOTE: This isn't really elegant, but still better than spamming the log with // useless `not ready to accept` messages. - if (this.blockchain.processQueue.length() > 0) { - this.logger.debug(`Discarded ${this.blockchain.processQueue.length()} downloaded blocks.`); + if (this.blockchain.queue.length() > 0) { + this.logger.debug(`Discarded ${this.blockchain.queue.length()} downloaded blocks.`); } // If we consecutively fail to accept the same block, our chain is likely forked. In this @@ -121,12 +123,12 @@ export class UnchainedHandler extends BlockHandler { return UnchainedBlockStatus.ExceededNotReadyToAcceptNewHeightMaxAttempts; } else if (this.block.data.height < lastBlock.data.height) { this.logger.debug( - `Block ${this.block.data.height.toLocaleString()} disregarded because already in blockchain :warning:`, + `Block ${this.block.data.height.toLocaleString()} disregarded because already in blockchain`, ); return UnchainedBlockStatus.AlreadyInBlockchain; } else if (this.block.data.height === lastBlock.data.height && this.block.data.id === lastBlock.data.id) { - this.logger.debug(`Block ${this.block.data.height.toLocaleString()} just received :chains:`); + this.logger.debug(`Block ${this.block.data.height.toLocaleString()} just received`); return UnchainedBlockStatus.EqualToLastBlock; } else if (this.block.data.timestamp < lastBlock.data.timestamp) { this.logger.debug( @@ -135,14 +137,14 @@ export class UnchainedHandler extends BlockHandler { return UnchainedBlockStatus.InvalidTimestamp; } else { if (this.isValidGenerator) { - this.logger.warn(`Detect double forging by ${this.block.data.generatorPublicKey} :chains:`); + this.logger.warn(`Detect double forging by ${this.block.data.generatorPublicKey}`); return UnchainedBlockStatus.DoubleForging; } this.logger.info( `Forked block disregarded because it is not allowed to be forged. Caused by delegate: ${ this.block.data.generatorPublicKey - } :bangbang:`, + }`, ); return UnchainedBlockStatus.GeneratorMismatch; diff --git a/packages/core-blockchain/src/queue/index.ts b/packages/core-blockchain/src/queue/index.ts deleted file mode 100644 index 031522c6c0..0000000000 --- a/packages/core-blockchain/src/queue/index.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { ProcessQueue } from "./process"; -import { RebuildQueue } from "./rebuild"; - -export { ProcessQueue }; -export { RebuildQueue }; - -export class Queue { - public process: ProcessQueue; - public rebuild: RebuildQueue; - - /** - * Create an instance of the queue. - * @param {Blockchain} blockchain - * @param {Object} events - * @return {void} - */ - constructor(blockchain, events) { - this.process = new ProcessQueue(blockchain, events.process); - this.rebuild = new RebuildQueue(blockchain, events.rebuild); - } - - /** - * Pause all queues. - * @return {void} - */ - public pause() { - this.rebuild.pause(); - this.process.pause(); - } - - /** - * Flush all queues. - * @return {void} - */ - public clear() { - this.rebuild.clear(); - this.process.clear(); - } - - /** - * Resume all queues. - * @return {void} - */ - public resume() { - this.rebuild.resume(); - this.process.resume(); - } - - public destroy() { - this.rebuild.destroy(); - this.process.destroy(); - } -} diff --git a/packages/core-blockchain/src/queue/interface.ts b/packages/core-blockchain/src/queue/interface.ts deleted file mode 100644 index 45b863c9a4..0000000000 --- a/packages/core-blockchain/src/queue/interface.ts +++ /dev/null @@ -1,71 +0,0 @@ -import async from "async"; -import { Blockchain } from "../blockchain"; - -export abstract class QueueInterface { - protected queue: any; - - /** - * Create an instance of the process queue. - */ - constructor(readonly blockchain: Blockchain, readonly event: string) {} - - /** - * Drain the queue. - */ - public drain() { - this.queue.drain = () => this.blockchain.dispatch(this.event); - } - - /** - * Pause the queue. - * @return {void} - */ - public pause() { - return this.queue.pause(); - } - - /** - * Flush the queue. - * @return {void} - */ - public clear() { - return this.queue.remove(() => true); - } - - /** - * Resume the queue. - * @return {void} - */ - public resume() { - return this.queue.resume(); - } - - /** - * Remove the item from the queue. - * @return {void} - */ - public remove(item) { - return this.queue.remove(item); - } - - /** - * Push the item to the queue. - * @param {Function} callback - * @return {void} - */ - public push(callback) { - return this.queue.push(callback); - } - - /** - * Get the length of the queue. - * @return {void} - */ - public length() { - return this.queue.length(); - } - - public destroy() { - return this.queue.kill(); - } -} diff --git a/packages/core-blockchain/src/queue/process.ts b/packages/core-blockchain/src/queue/process.ts deleted file mode 100644 index bd4afc25ba..0000000000 --- a/packages/core-blockchain/src/queue/process.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { app } from "@arkecosystem/core-container"; -import { Logger } from "@arkecosystem/core-interfaces"; -import { models } from "@arkecosystem/crypto"; -import async from "async"; -import { Blockchain } from "../blockchain"; -import { QueueInterface } from "./interface"; - -const logger = app.resolvePlugin("logger"); - -export class ProcessQueue extends QueueInterface { - /** - * Create an instance of the process queue. - */ - constructor(readonly blockchain: Blockchain, readonly event: string) { - super(blockchain, event); - - this.queue = async.queue((block: models.IBlockData, cb) => { - try { - return blockchain.processBlock(new models.Block(block), cb); - } catch (error) { - logger.error(`Failed to process block in ProcessQueue: ${block.height.toLocaleString()}`); - logger.error(error.stack); - return cb(); - } - }, 1); - - this.drain(); - } -} diff --git a/packages/core-blockchain/src/queue/rebuild.ts b/packages/core-blockchain/src/queue/rebuild.ts deleted file mode 100644 index 786d3dc4bf..0000000000 --- a/packages/core-blockchain/src/queue/rebuild.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { app } from "@arkecosystem/core-container"; -import { Logger } from "@arkecosystem/core-interfaces"; -import { models } from "@arkecosystem/crypto"; -import async from "async"; -import { Blockchain } from "../blockchain"; -import { QueueInterface } from "./interface"; - -const logger = app.resolvePlugin("logger"); - -export class RebuildQueue extends QueueInterface { - /** - * Create an instance of the process queue. - */ - constructor(readonly blockchain: Blockchain, readonly event: string) { - super(blockchain, event); - - this.queue = async.queue((block: models.IBlockData, cb) => { - if (this.queue.paused) { - return cb(); - } - try { - return blockchain.rebuildBlock(new models.Block(block), cb); - } catch (error) { - logger.error(`Failed to rebuild block in RebuildQueue: ${block.height.toLocaleString()}`); - return cb(); - } - }, 1); - - this.drain(); - } -} diff --git a/packages/core-blockchain/src/state-machine.ts b/packages/core-blockchain/src/state-machine.ts index 8a56c0a16e..2b1b99f2d7 100644 --- a/packages/core-blockchain/src/state-machine.ts +++ b/packages/core-blockchain/src/state-machine.ts @@ -4,13 +4,13 @@ import { app } from "@arkecosystem/core-container"; import { EventEmitter, Logger } from "@arkecosystem/core-interfaces"; import { roundCalculator } from "@arkecosystem/core-utils"; -import { isException, models, slots } from "@arkecosystem/crypto"; +import { isException, models } from "@arkecosystem/crypto"; import pluralize from "pluralize"; import { config as localConfig } from "./config"; import { blockchainMachine } from "./machines/blockchain"; import { stateStorage } from "./state-storage"; -import { isBlockChained, tickSyncTracker } from "./utils"; +import { isBlockChained } from "./utils"; import { Blockchain } from "./blockchain"; @@ -47,31 +47,23 @@ blockchainMachine.actionMap = (blockchain: Blockchain) => ({ return blockchain.dispatch(blockchain.isSynced() ? "SYNCED" : "NOTSYNCED"); }, - checkRebuildBlockSynced() { - return blockchain.dispatch(blockchain.isRebuildSynced() ? "SYNCED" : "NOTSYNCED"); - }, - async checkLastDownloadedBlockSynced() { let event = "NOTSYNCED"; - logger.debug( - `Queued blocks (rebuild: ${blockchain.rebuildQueue.length()} process: ${blockchain.processQueue.length()})`, - ); + logger.debug(`Queued blocks (process: ${blockchain.queue.length()})`); - if (blockchain.rebuildQueue.length() > 10000 || blockchain.processQueue.length() > 10000) { + if (blockchain.queue.length() > 10000) { event = "PAUSED"; } // tried to download but no luck after 5 tries (looks like network missing blocks) - if (stateStorage.noBlockCounter > 5 && blockchain.processQueue.length() === 0) { - logger.info( - "Tried to sync 5 times to different nodes, looks like the network is missing blocks :umbrella:", - ); + if (stateStorage.noBlockCounter > 5 && blockchain.queue.length() === 0) { + logger.info("Tried to sync 5 times to different nodes, looks like the network is missing blocks"); stateStorage.noBlockCounter = 0; event = "NETWORKHALTED"; if (stateStorage.p2pUpdateCounter + 1 > 3) { - logger.info("Network keeps missing blocks. :umbrella:"); + logger.info("Network keeps missing blocks."); const networkStatus = await blockchain.p2p.checkNetworkHealth(); if (networkStatus.forked) { @@ -104,55 +96,31 @@ blockchainMachine.actionMap = (blockchain: Blockchain) => ({ }, downloadFinished() { - logger.info("Block download finished :rocket:"); + logger.info("Block download finished"); if (stateStorage.networkStart) { // next time we will use normal behaviour stateStorage.networkStart = false; blockchain.dispatch("SYNCFINISHED"); - } else if (blockchain.rebuildQueue.length() === 0) { + } else if (blockchain.queue.length() === 0) { blockchain.dispatch("PROCESSFINISHED"); } }, - async rebuildFinished() { - try { - logger.info("Blockchain rebuild finished :chains:"); - - stateStorage.rebuild = false; - - await blockchain.database.commitQueuedQueries(); - await blockchain.rollbackCurrentRound(); - await blockchain.database.buildWallets(stateStorage.getLastBlock().data.height); - await blockchain.database.saveWallets(true); - await blockchain.transactionPool.buildWallets(); - - return blockchain.dispatch("PROCESSFINISHED"); - } catch (error) { - logger.error(error.stack); - return blockchain.dispatch("FAILURE"); - } - }, - - downloadPaused: () => logger.info("Blockchain download paused :clock1030:"), + downloadPaused: () => logger.info("Blockchain download paused"), syncingComplete() { - logger.info("Blockchain 100% in sync :100:"); + logger.info("Blockchain 100% in sync"); blockchain.dispatch("SYNCFINISHED"); }, - rebuildingComplete() { - logger.info("Blockchain rebuild complete :unicorn_face:"); - blockchain.dispatch("REBUILDCOMPLETE"); - }, - stopped() { - logger.info("The blockchain has been stopped :guitar:"); + logger.info("The blockchain has been stopped"); }, exitApp() { - app.forceExit("Failed to startup blockchain. Exiting Ark Core! :rotating_light:"); + app.forceExit("Failed to startup blockchain. Exiting Persona Core! :rotating_light:"); }, async init() { @@ -160,14 +128,12 @@ blockchainMachine.actionMap = (blockchain: Blockchain) => ({ let block = await blockchain.database.getLastBlock(); if (!block) { - logger.warn("No block found in database :hushed:"); + logger.warn("No block found in database"); block = new Block(config.get("genesisBlock")); if (block.data.payloadHash !== config.get("network.nethash")) { - logger.error( - "FATAL: The genesis block payload hash is different from configured the nethash :rotating_light:", - ); + logger.error("FATAL: The genesis block payload hash is different from configured the nethash"); return blockchain.dispatch("FAILURE"); } @@ -176,19 +142,19 @@ blockchainMachine.actionMap = (blockchain: Blockchain) => ({ } if (!blockchain.database.restoredDatabaseIntegrity) { - logger.info("Verifying database integrity :hourglass_flowing_sand:"); + logger.info("Verifying database integrity"); const blockchainAudit = await blockchain.database.verifyBlockchain(); if (!blockchainAudit.valid) { - logger.error("FATAL: The database is corrupted :fire:"); + logger.error("FATAL: The database is corrupted"); logger.error(JSON.stringify(blockchainAudit.errors, null, 4)); return blockchain.dispatch("ROLLBACK"); } - logger.info("Verified database integrity :smile_cat:"); + logger.info("Verified database integrity"); } else { - logger.info("Skipping database integrity check after successful database recovery :smile_cat:"); + logger.info("Skipping database integrity check after successful database recovery"); } // only genesis block? special case of first round needs to be dealt with @@ -199,72 +165,51 @@ blockchainMachine.actionMap = (blockchain: Blockchain) => ({ /** ******************************* * state machine data init * ******************************* */ - const constants = config.getMilestone(block.data.height); stateStorage.setLastBlock(block); stateStorage.lastDownloadedBlock = block; + // NOTE: if the node is shutdown between round, the round has already been applied + if (roundCalculator.isNewRound(block.data.height + 1)) { + const { round } = roundCalculator.calculateRound(block.data.height + 1); + + logger.info( + `New round ${round.toLocaleString()} detected. Cleaning calculated data before restarting!`, + ); + + await blockchain.database.deleteRound(round); + } + if (stateStorage.networkStart) { - await blockchain.database.buildWallets(block.data.height); - await blockchain.database.saveWallets(true); + await blockchain.database.buildWallets(); await blockchain.database.applyRound(block.data.height); await blockchain.transactionPool.buildWallets(); return blockchain.dispatch("STARTED"); } - stateStorage.rebuild = - slots.getTime() - block.data.timestamp > (constants.activeDelegates + 1) * constants.blocktime; - // no fast rebuild if in last week - stateStorage.fastRebuild = - slots.getTime() - block.data.timestamp > 3600 * 24 * 7 && !!localConfig.get("fastRebuild"); - if (process.env.NODE_ENV === "test") { - logger.verbose("TEST SUITE DETECTED! SYNCING WALLETS AND STARTING IMMEDIATELY. :bangbang:"); + logger.verbose("TEST SUITE DETECTED! SYNCING WALLETS AND STARTING IMMEDIATELY."); stateStorage.setLastBlock(new Block(config.get("genesisBlock"))); - await blockchain.database.buildWallets(block.data.height); + await blockchain.database.buildWallets(); return blockchain.dispatch("STARTED"); } - logger.info(`Fast rebuild: ${stateStorage.fastRebuild}`); logger.info(`Last block in database: ${block.data.height.toLocaleString()}`); - if (stateStorage.fastRebuild) { - return blockchain.dispatch("REBUILD"); - } - - // removing blocks up to the last round to compute active delegate list later if needed - const activeDelegates = await blockchain.database.getActiveDelegates(block.data.height); - - if (!activeDelegates) { - await blockchain.rollbackCurrentRound(); - } - /** ******************************* * database init * ******************************* */ - // SPV rebuild - const verifiedWalletsIntegrity = await blockchain.database.buildWallets(block.data.height); + // Integrity Verification + const verifiedWalletsIntegrity = await blockchain.database.buildWallets(); if (!verifiedWalletsIntegrity && block.data.height > 1) { logger.warn( - "Rebuilding wallets table because of some inconsistencies. Most likely due to an unfortunate shutdown. :hammer:", - ); - await blockchain.database.saveWallets(true); - } - - // NOTE: if the node is shutdown between round, the round has already been applied - if (roundCalculator.isNewRound(block.data.height + 1)) { - const { round } = roundCalculator.calculateRound(block.data.height + 1); - - logger.info( - `New round ${round.toLocaleString()} detected. Cleaning calculated data before restarting!`, + "Rebuilding wallets table because of some inconsistencies. Most likely due to an unfortunate shutdown.", ); - - await blockchain.database.deleteRound(round); } - await blockchain.database.applyRound(block.data.height); + await blockchain.database.restoreCurrentRound(block.data.height); await blockchain.transactionPool.buildWallets(); return blockchain.dispatch("STARTED"); @@ -275,42 +220,6 @@ blockchainMachine.actionMap = (blockchain: Blockchain) => ({ } }, - async rebuildBlocks() { - const lastBlock = stateStorage.lastDownloadedBlock || stateStorage.getLastBlock(); - const blocks = await blockchain.p2p.downloadBlocks(lastBlock.data.height); - - tickSyncTracker(blocks.length, lastBlock.data.height); - - if (!blocks || blocks.length === 0) { - logger.info("No new blocks found on this peer"); - - blockchain.dispatch("NOBLOCK"); - } else { - logger.info( - `Downloaded ${blocks.length} new ${pluralize( - "block", - blocks.length, - )} accounting for a total of ${pluralize( - "transaction", - blocks.reduce((sum, b) => sum + b.numberOfTransactions, 0), - true, - )}`, - ); - - if (blocks.length && blocks[0].previousBlock === lastBlock.data.id) { - stateStorage.lastDownloadedBlock = { data: blocks.slice(-1)[0] }; - blockchain.rebuildQueue.push(blocks); - blockchain.dispatch("DOWNLOADED"); - } else { - logger.warn(`Downloaded block not accepted: ${JSON.stringify(blocks[0])}`); - logger.warn(`Last block: ${JSON.stringify(lastBlock.data)}`); - - // disregard the whole block list - blockchain.dispatch("NOBLOCK"); - } - } - }, - async downloadBlocks() { const lastDownloadedBlock = stateStorage.lastDownloadedBlock || stateStorage.getLastBlock(); const blocks = await blockchain.p2p.downloadBlocks(lastDownloadedBlock.data.height); @@ -342,18 +251,24 @@ blockchainMachine.actionMap = (blockchain: Blockchain) => ({ stateStorage.noBlockCounter = 0; stateStorage.p2pUpdateCounter = 0; - blockchain.enqueueBlocks(blocks); - blockchain.dispatch("DOWNLOADED"); + try { + blockchain.enqueueBlocks(blocks); + blockchain.dispatch("DOWNLOADED"); + } catch (error) { + logger.warn(`Failed to enqueue downloaded block.`); + blockchain.dispatch("NOBLOCK"); + return; + } } else { if (empty) { logger.info("No new block found on this peer"); } else { logger.warn(`Downloaded block not accepted: ${JSON.stringify(blocks[0])}`); logger.warn(`Last downloaded block: ${JSON.stringify(lastDownloadedBlock.data)}`); - blockchain.processQueue.clear(); + blockchain.clearQueue(); } - if (blockchain.processQueue.length() === 0) { + if (blockchain.queue.length() === 0) { stateStorage.noBlockCounter++; stateStorage.lastDownloadedBlock = stateStorage.getLastBlock(); } @@ -363,11 +278,11 @@ blockchainMachine.actionMap = (blockchain: Blockchain) => ({ }, async analyseFork() { - logger.info("Analysing fork :mag:"); + logger.info("Analysing fork"); }, async startForkRecovery() { - logger.info("Starting fork recovery :fork_and_knife:"); + logger.info("Starting fork recovery"); blockchain.clearAndStopQueue(); await blockchain.database.commitQueuedQueries(); @@ -377,7 +292,7 @@ blockchainMachine.actionMap = (blockchain: Blockchain) => ({ await blockchain.removeBlocks(stateStorage.numberOfBlocksToRollback || random); stateStorage.numberOfBlocksToRollback = null; - logger.info(`Removed ${pluralize("block", random, true)} :wastebasket:`); + logger.info(`Removed ${pluralize("block", random, true)}`); await blockchain.transactionPool.buildWallets(); await blockchain.p2p.refreshPeersAfterFork(); @@ -386,7 +301,7 @@ blockchainMachine.actionMap = (blockchain: Blockchain) => ({ }, async rollbackDatabase() { - logger.info("Trying to restore database integrity :fire_engine:"); + logger.info("Trying to restore database integrity"); const { maxBlockRewind, steps } = localConfig.get("databaseRollback"); let blockchainAudit; @@ -402,7 +317,7 @@ blockchainMachine.actionMap = (blockchain: Blockchain) => ({ if (!blockchainAudit.valid) { // TODO: multiple attempts? rewind further? restore snapshot? - logger.error("FATAL: Failed to restore database integrity :skull: :skull: :skull:"); + logger.error("FATAL: Failed to restore database integrity"); logger.error(JSON.stringify(blockchainAudit.errors, null, 4)); blockchain.dispatch("FAILURE"); return; @@ -412,12 +327,11 @@ blockchainMachine.actionMap = (blockchain: Blockchain) => ({ const lastBlock = await blockchain.database.getLastBlock(); logger.info( - `Database integrity verified again after rollback to height ${lastBlock.data.height.toLocaleString()} :green_heart:`, + `Database integrity verified again after rollback to height ${lastBlock.data.height.toLocaleString()}`, ); blockchain.dispatch("SUCCESS"); }, }); -const stateMachine = blockchainMachine; -export { stateMachine }; +export const stateMachine = blockchainMachine; diff --git a/packages/core-blockchain/src/state-storage.ts b/packages/core-blockchain/src/state-storage.ts index d218c511f4..2f6f3189aa 100644 --- a/packages/core-blockchain/src/state-storage.ts +++ b/packages/core-blockchain/src/state-storage.ts @@ -2,7 +2,7 @@ import { app } from "@arkecosystem/core-container"; import { Blockchain, Logger } from "@arkecosystem/core-interfaces"; -import { configManager, models } from "@arkecosystem/crypto"; +import { configManager, ITransactionData, models, TransactionRegistry } from "@arkecosystem/crypto"; import assert from "assert"; import immutable from "immutable"; import { config } from "./config"; @@ -31,8 +31,6 @@ export class StateStorage implements Blockchain.IStateStorage { public blockPing: any; public started: boolean; public forkedBlock: models.Block | null; - public rebuild: boolean; - public fastRebuild: boolean; public wakeUpTimeout: any; public noBlockCounter: number; public p2pUpdateCounter: number; @@ -52,8 +50,6 @@ export class StateStorage implements Blockchain.IStateStorage { this.blockPing = null; this.started = false; this.forkedBlock = null; - this.rebuild = true; - this.fastRebuild = false; this.wakeUpTimeout = null; this.noBlockCounter = 0; this.p2pUpdateCounter = 0; @@ -100,6 +96,7 @@ export class StateStorage implements Blockchain.IStateStorage { _lastBlocks = _lastBlocks.set(block.data.height, block); configManager.setHeight(block.data.height); + TransactionRegistry.updateStaticFees(block.data.height); // Delete oldest block if size exceeds the maximum if (_lastBlocks.size > config.get("state.maxLastBlocks")) { @@ -163,8 +160,8 @@ export class StateStorage implements Blockchain.IStateStorage { * Cache the ids of the given transactions. */ public cacheTransactions( - transactions: models.ITransactionData[], - ): { added: models.ITransactionData[]; notAdded: models.ITransactionData[] } { + transactions: ITransactionData[], + ): { added: ITransactionData[]; notAdded: ITransactionData[] } { const notAdded = []; const added = transactions.filter(tx => { if (_cachedTransactionIds.has(tx.id)) { diff --git a/packages/core-blockchain/src/utils/validate-generator.ts b/packages/core-blockchain/src/utils/validate-generator.ts index f517b58948..6918c0655b 100644 --- a/packages/core-blockchain/src/utils/validate-generator.ts +++ b/packages/core-blockchain/src/utils/validate-generator.ts @@ -1,12 +1,14 @@ import { app } from "@arkecosystem/core-container"; import { Logger } from "@arkecosystem/core-interfaces"; +import { roundCalculator } from "@arkecosystem/core-utils"; import { models, slots } from "@arkecosystem/crypto"; export const validateGenerator = async (block: models.Block): Promise => { const database = app.resolvePlugin("database"); const logger = app.resolvePlugin("logger"); - const delegates = await database.getActiveDelegates(block.data.height); + const roundInfo = roundCalculator.calculateRound(block.data.height); + const delegates = await database.getActiveDelegates(roundInfo); const slot = slots.getSlotNumber(block.data.timestamp); const forgingDelegate = delegates[slot % delegates.length]; @@ -16,7 +18,7 @@ export const validateGenerator = async (block: models.Block): Promise = logger.debug( `Could not decide if delegate ${generatorUsername} (${ block.data.generatorPublicKey - }) is allowed to forge block ${block.data.height.toLocaleString()} :grey_question:`, + }) is allowed to forge block ${block.data.height.toLocaleString()}`, ); } else if (forgingDelegate.publicKey !== block.data.generatorPublicKey) { const forgingUsername = database.walletManager.findByPublicKey(forgingDelegate.publicKey).username; @@ -24,7 +26,7 @@ export const validateGenerator = async (block: models.Block): Promise = logger.warn( `Delegate ${generatorUsername} (${ block.data.generatorPublicKey - }) not allowed to forge, should be ${forgingUsername} (${forgingDelegate.publicKey}) :-1:`, + }) not allowed to forge, should be ${forgingUsername} (${forgingDelegate.publicKey})`, ); return false; @@ -33,7 +35,7 @@ export const validateGenerator = async (block: models.Block): Promise = logger.debug( `Delegate ${generatorUsername} (${ block.data.generatorPublicKey - }) allowed to forge block ${block.data.height.toLocaleString()} :+1:`, + }) allowed to forge block ${block.data.height.toLocaleString()}`, ); return true; diff --git a/packages/core-container/.gitattributes b/packages/core-container/.gitattributes index 60cc52db63..63f6a5b970 100644 --- a/packages/core-container/.gitattributes +++ b/packages/core-container/.gitattributes @@ -2,10 +2,6 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore -/__tests__ export-ignore -/docs export-ignore /README.md export-ignore diff --git a/packages/core-container/README.md b/packages/core-container/README.md index 91d6c0666e..ce79318eba 100644 --- a/packages/core-container/README.md +++ b/packages/core-container/README.md @@ -1,12 +1,12 @@ -# Ark Core - Container +# Persona Core - Container

- +

## Documentation -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core-container.html). +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/required/core-container.html). ## Security @@ -14,10 +14,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [Brian Faust](https://github.com/faustbrian) -- [Joshua Noack](https://github.com/supaiku0) -- [All Contributors](../../../../contributors) +This project exists thanks to all the people who [contribute](../../../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-container/package.json b/packages/core-container/package.json index d0a15c6ca4..572af0519b 100644 --- a/packages/core-container/package.json +++ b/packages/core-container/package.json @@ -1,7 +1,7 @@ { "name": "@arkecosystem/core-container", - "description": "Container for Ark Core", - "version": "2.2.1", + "description": "Container for ARK Core", + "version": "2.3.15", "contributors": [ "Brian Faust " ], @@ -12,35 +12,22 @@ "dist" ], "scripts": { - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", "prepublishOnly": "yarn build", - "pretest": "yarn lint && yarn build", "compile": "../../node_modules/typescript/bin/tsc", "build": "yarn clean && yarn compile", "build:watch": "yarn clean && yarn compile -w", - "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "test": "cross-env CORE_ENV=test jest --runInBand --forceExit", - "test:coverage": "cross-env CORE_ENV=test jest --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$' --runInBand --forceExit", - "test:debug": "cross-env CORE_ENV=test node --inspect-brk ../../node_modules/.bin/jest --runInBand", - "test:watch": "cross-env CORE_ENV=test jest --runInBand --watch", - "test:watch:all": "cross-env CORE_ENV=test jest --runInBand --watchAll", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" + "clean": "del dist" }, "dependencies": { - "@arkecosystem/core-interfaces": "^2.2.1", - "@arkecosystem/crypto": "^2.2.1", - "awilix": "^4.2.0", - "axios": "^0.18.0", + "@arkecosystem/core-interfaces": "^2.3.15", + "@arkecosystem/crypto": "^2.3.15", + "awilix": "^4.2.1", "delay": "^4.1.0", - "env-paths": "^2.0.0", + "env-paths": "^2.1.0", "envfile": "^3.0.0", "expand-home-dir": "^0.0.3", "fs-extra": "^7.0.1", + "got": "^9.6.0", "hoek": "^6.1.2", "joi": "^14.3.1", "lodash.get": "^4.4.2", @@ -49,15 +36,13 @@ "semver": "^5.6.0" }, "devDependencies": { - "@types/env-paths": "^1.0.2", "@types/fs-extra": "^5.0.5", "@types/hoek": "^4.1.3", - "@types/joi": "^14.3.1", - "@types/lodash.get": "^4.4.4", - "@types/lodash.isstring": "^4.0.4", - "@types/lodash.set": "^4.3.4", + "@types/joi": "^14.3.2", + "@types/lodash.get": "^4.4.6", + "@types/lodash.isstring": "^4.0.6", + "@types/lodash.set": "^4.3.6", "@types/semver": "^5.5.0", - "axios-mock-adapter": "^1.16.0", "jest-mock-process": "^1.1.0" }, "publishConfig": { @@ -65,8 +50,5 @@ }, "engines": { "node": ">=10.x" - }, - "jest": { - "preset": "../../jest-preset.json" } } diff --git a/packages/core-container/src/config/index.ts b/packages/core-container/src/config/index.ts index 9dc7c96854..2c185f6f41 100644 --- a/packages/core-container/src/config/index.ts +++ b/packages/core-container/src/config/index.ts @@ -1,10 +1,10 @@ -import { configManager as crypto, HashAlgorithms } from "@arkecosystem/crypto"; -import get from "lodash/get"; -import set from "lodash/set"; +import { configManager as crypto } from "@arkecosystem/crypto"; +import get from "lodash.get"; +import set from "lodash.set"; import { fileLoader } from "./loaders"; import { Network } from "./network"; -class Config { +export class Config { private config: Record; public async setUp(opts) { diff --git a/packages/core-container/src/config/loaders/file-loader.ts b/packages/core-container/src/config/loaders/file-loader.ts index 53bb2794d6..ba36254f2b 100644 --- a/packages/core-container/src/config/loaders/file-loader.ts +++ b/packages/core-container/src/config/loaders/file-loader.ts @@ -1,5 +1,5 @@ -import axios from "axios"; import { existsSync, readdirSync, writeFileSync } from "fs-extra"; +import got from "got"; import Joi from "joi"; import { basename, extname, resolve } from "path"; import { schemaConfig } from "../schema"; @@ -73,35 +73,43 @@ class FileLoader { /** * Build the peer list either from a local file, remote file or object. - * @param {String} configFile - * @return {void} */ private async buildPeers(configFile: any): Promise { + let fetchedList: Array<{ ip: string; port: number }>; + if (configFile.sources) { for (const source of configFile.sources) { // Local File... if (source.startsWith("/")) { - configFile.list = require(source); - - writeFileSync(configFile, JSON.stringify(configFile, null, 2)); - + fetchedList = require(source); break; } // URL... try { - const response = await axios.get(source); - - configFile.list = response.data; - - writeFileSync(configFile, JSON.stringify(configFile, null, 2)); - + const { body } = await got.get(source); + fetchedList = JSON.parse(body); break; } catch (error) { // } } } + + if (fetchedList) { + if (!configFile.list) { + configFile.list = []; + } + + fetchedList.forEach(peer => { + if (!configFile.list.some(seed => seed.ip === peer.ip && seed.port === peer.port)) { + configFile.list.push(peer); + } + }); + + const path = `${resolve(process.env.CORE_PATH_CONFIG)}/peers.json`; + writeFileSync(path, JSON.stringify(configFile, null, 2)); + } } } diff --git a/packages/core-container/src/config/network.ts b/packages/core-container/src/config/network.ts index 843fbd5a38..64d27cde48 100644 --- a/packages/core-container/src/config/network.ts +++ b/packages/core-container/src/config/network.ts @@ -1,7 +1,5 @@ import { NetworkManager } from "@arkecosystem/crypto"; -import expandHomeDir from "expand-home-dir"; import Joi from "joi"; -import { resolve } from "path"; import { schemaNetwork } from "./schema"; export class Network { @@ -9,7 +7,7 @@ export class Network { * Expose information about the for the operating network to the environment. * @return {void} */ - public static setUp(opts: any) { + public static setUp(opts: Record) { const config = NetworkManager.findByName(opts.network); const { error } = Joi.validate(config, schemaNetwork); diff --git a/packages/core-container/src/config/schema.ts b/packages/core-container/src/config/schema.ts index e9df8f6e30..077d5777d9 100644 --- a/packages/core-container/src/config/schema.ts +++ b/packages/core-container/src/config/schema.ts @@ -28,6 +28,7 @@ export const schemaNetwork = Joi.object({ nethash: Joi.string() .hex() .required(), + slip44: Joi.number().positive(), wif: Joi.number() .positive() .required(), diff --git a/packages/core-container/src/container.ts b/packages/core-container/src/container.ts index 058ec4bbe0..ba3b24b9f1 100644 --- a/packages/core-container/src/container.ts +++ b/packages/core-container/src/container.ts @@ -1,51 +1,26 @@ import { Container as container, EventEmitter, Logger } from "@arkecosystem/core-interfaces"; import { createContainer, Resolver } from "awilix"; -import { execSync } from "child_process"; import delay from "delay"; -import { existsSync } from "fs"; -import { join } from "path"; import semver from "semver"; import { configManager } from "./config"; import { Environment } from "./environment"; import { PluginRegistrar } from "./registrars/plugin"; export class Container implements container.IContainer { - public options: any; - public exitEvents: any; /** * May be used by CLI programs to suppress the shutdown messages. */ public silentShutdown = false; - public hashid: string; - public plugins: any; + public options: Record; + public plugins: PluginRegistrar; public shuttingDown: boolean; public version: string; public isReady: boolean = false; - public variables: any; + public variables: Record; public config: any; - private container = createContainer(); - /** - * Create a new container instance. - * @constructor - */ - constructor() { - this.hashid = "unknown"; - - /** - * The git commit hash of the repository. Used during development to - * easily idenfity nodes based on their commit hash and version. - */ - try { - if (existsSync(join(__dirname, "../../..", ".git"))) { - this.hashid = execSync("git rev-parse --short=8 HEAD") - .toString() - .trim(); - } - } catch (e) { - this.hashid = "unknown"; - } - } + private name: string; + private readonly container = createContainer(); /** * Set up the app. @@ -54,7 +29,7 @@ export class Container implements container.IContainer { * @param {Object} options * @return {void} */ - public async setUp(version: string, variables: any, options: any = {}) { + public async setUp(version: string, variables: Record, options: Record = {}) { // Register any exit signal handling this.registerExitHandler(["SIGINT", "exit"]); @@ -64,6 +39,8 @@ export class Container implements container.IContainer { this.setVersion(version); + this.name = `${this.variables.token}-${this.variables.suffix}`; + // Register the environment variables const environment = new Environment(variables); environment.setUp(); @@ -190,7 +167,6 @@ export class Container implements container.IContainer { this.shuttingDown = true; const logger = this.resolvePlugin("logger"); - logger.error(":boom: Container force shutdown :boom:"); logger.error(message); if (error) { @@ -200,19 +176,11 @@ export class Container implements container.IContainer { process.exit(exitCode); } - /** - * Get the application git commit hash. - * @throws {String} - */ - public getHashid() { - return this.hashid; - } - /** * Get the application version. * @throws {String} */ - public getVersion() { + public getVersion(): string { return this.version; } @@ -221,7 +189,7 @@ export class Container implements container.IContainer { * @param {String} version * @return {void} */ - public setVersion(version) { + public setVersion(version: string) { if (!semver.valid(version)) { this.forceExit( // tslint:disable-next-line:max-line-length @@ -232,6 +200,10 @@ export class Container implements container.IContainer { this.version = version; } + public getName(): string { + return this.name; + } + /** * Handle any exit signals. * @return {void} @@ -247,7 +219,7 @@ export class Container implements container.IContainer { const logger = this.resolvePlugin("logger"); if (logger) { logger.suppressConsoleOutput(this.silentShutdown); - logger.info("Core is trying to gracefully shut down to avoid data corruption :pizza:"); + logger.info("Core is trying to gracefully shut down to avoid data corruption"); } try { @@ -265,9 +237,6 @@ export class Container implements container.IContainer { // Wait for event to be emitted and give time to finish await delay(1000); - - // Save dirty wallets - await database.saveWallets(false); } } catch (error) { // tslint:disable-next-line:no-console diff --git a/packages/core-container/src/environment.ts b/packages/core-container/src/environment.ts index 730e2e94c1..a7fc6573fd 100644 --- a/packages/core-container/src/environment.ts +++ b/packages/core-container/src/environment.ts @@ -10,7 +10,7 @@ export class Environment { * @param {Object} variables * @return {void} */ - constructor(readonly variables: any) {} + constructor(private readonly variables: Record) {} /** * Set up the environment variables. diff --git a/packages/core-container/src/index.ts b/packages/core-container/src/index.ts index 6e9d336d6a..0f5e26d3b1 100644 --- a/packages/core-container/src/index.ts +++ b/packages/core-container/src/index.ts @@ -1,5 +1,4 @@ import { Container as container } from "@arkecosystem/core-interfaces"; import { Container } from "./container"; -const app: container.IContainer = new Container(); -export { app }; +export const app: container.IContainer = new Container(); diff --git a/packages/core-container/src/registrars/plugin.ts b/packages/core-container/src/registrars/plugin.ts index ead7be3d1d..735b878407 100644 --- a/packages/core-container/src/registrars/plugin.ts +++ b/packages/core-container/src/registrars/plugin.ts @@ -1,27 +1,18 @@ +import { Container } from "@arkecosystem/core-interfaces"; import { asValue } from "awilix"; -import expandHomeDir from "expand-home-dir"; -import { existsSync } from "fs"; import Hoek from "hoek"; -import isString from "lodash/isString"; -import { dirname, resolve } from "path"; +import isString from "lodash.isstring"; import semver from "semver"; export class PluginRegistrar { private container: any; private plugins: any; - private resolvedPlugins: any; private options: any; private deregister: any; - /** - * Create a new plugin manager instance. - * @param {IContainer} container - * @param {Object} options - */ - constructor(container, options: any = {}) { + constructor(container: Container.IContainer, options: Record = {}) { this.container = container; this.plugins = container.config.get("plugins"); - this.resolvedPlugins = []; this.options = this.__castOptions(options); this.deregister = []; } diff --git a/packages/core-database-postgres/.gitattributes b/packages/core-database-postgres/.gitattributes index 60cc52db63..63f6a5b970 100644 --- a/packages/core-database-postgres/.gitattributes +++ b/packages/core-database-postgres/.gitattributes @@ -2,10 +2,6 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore -/__tests__ export-ignore -/docs export-ignore /README.md export-ignore diff --git a/packages/core-database-postgres/README.md b/packages/core-database-postgres/README.md index 9a38b12edf..46aca000a8 100644 --- a/packages/core-database-postgres/README.md +++ b/packages/core-database-postgres/README.md @@ -1,7 +1,7 @@ -# Ark Core - Database - Postgres +# Persona Core - Database - Postgres

- +

## Documentation @@ -14,10 +14,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [Brian Faust](https://github.com/faustbrian) -- [Joshua Noack](https://github.com/supaiku0) -- [All Contributors](../../../../contributors) +This project exists thanks to all the people who [contribute](../../../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-database-postgres/__tests__/.gitkeep b/packages/core-database-postgres/__tests__/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/core-database-postgres/package.json b/packages/core-database-postgres/package.json index eb29ca9ca3..3155c51a65 100644 --- a/packages/core-database-postgres/package.json +++ b/packages/core-database-postgres/package.json @@ -1,7 +1,7 @@ { "name": "@arkecosystem/core-database-postgres", - "description": "PostgreSQL integration for Ark Core", - "version": "2.2.1", + "description": "PostgreSQL integration for ARK Core", + "version": "2.3.15", "contributors": [ "Brian Faust " ], @@ -12,42 +12,35 @@ "dist" ], "scripts": { - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", "prepublishOnly": "yarn build", "pretest": "bash ../../scripts/pre-test.sh", "compile": "../../node_modules/typescript/bin/tsc", "build": "yarn clean && yarn copy && yarn compile", "build:watch": "yarn clean && yarn copy && yarn compile -w", "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "test": "cross-env CORE_ENV=test jest --runInBand --forceExit", - "test:coverage": "cross-env CORE_ENV=test jest --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$' --runInBand --forceExit", - "test:debug": "cross-env CORE_ENV=test node --inspect-brk ../../node_modules/.bin/jest --runInBand", - "test:watch": "cross-env CORE_ENV=test jest --runInBand --watch", - "test:watch:all": "cross-env CORE_ENV=test jest --runInBand --watchAll", - "copy": "cd src/ && cpy './**/*.sql' --parents ../dist/ && cd ../", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" + "copy": "cd src/ && cpy './**/*.sql' --parents ../dist/ && cd ../" }, "dependencies": { - "@arkecosystem/core-container": "^2.2.1", - "@arkecosystem/core-database": "^2.2.1", - "@arkecosystem/core-interfaces": "^2.2.1", - "@arkecosystem/core-utils": "^2.2.1", - "@arkecosystem/crypto": "^2.2.1", + "@arkecosystem/core-container": "^2.3.15", + "@arkecosystem/core-database": "^2.3.15", + "@arkecosystem/core-interfaces": "^2.3.15", + "@arkecosystem/core-utils": "^2.3.15", + "@arkecosystem/crypto": "^2.3.15", + "@arkecosystem/utils": "^0.3.0", + "@faustbrian/dato": "^0.2.0", + "@types/bluebird": "^3.5.26", + "@types/lodash.chunk": "^4.2.6", + "@types/pluralize": "^0.0.29", "bluebird": "^3.5.3", "cpy-cli": "^2.0.0", "lodash.chunk": "^4.2.0", - "pg-promise": "^8.5.5", + "pg-promise": "^8.6.3", "pluralize": "^7.0.0", "sql": "^0.78.0" }, "devDependencies": { - "@types/bluebird": "^3.5.25", - "@types/lodash.chunk": "^4.2.4", + "@types/bluebird": "^3.5.26", + "@types/lodash.chunk": "^4.2.6", "@types/pluralize": "^0.0.29" }, "publishConfig": { @@ -55,8 +48,5 @@ }, "engines": { "node": ">=10.x" - }, - "jest": { - "preset": "../../jest-preset.json" } } diff --git a/packages/core-database-postgres/src/index.ts b/packages/core-database-postgres/src/index.ts index b4d0f3e18c..51ab7feb34 100644 --- a/packages/core-database-postgres/src/index.ts +++ b/packages/core-database-postgres/src/index.ts @@ -1,6 +1,6 @@ export * from "./postgres-connection"; export * from "./migrations"; -export * from "./spv"; +export * from "./integrity-verifier"; export * from "./models"; export * from "./repositories"; export * from "./plugin"; diff --git a/packages/core-database-postgres/src/spv.ts b/packages/core-database-postgres/src/integrity-verifier.ts similarity index 50% rename from packages/core-database-postgres/src/spv.ts rename to packages/core-database-postgres/src/integrity-verifier.ts index fdb1998618..9a8ddb4a7a 100644 --- a/packages/core-database-postgres/src/spv.ts +++ b/packages/core-database-postgres/src/integrity-verifier.ts @@ -1,5 +1,5 @@ -import { Bignum, models } from "@arkecosystem/crypto"; -const { Transaction } = models; +import { Bignum } from "@arkecosystem/crypto"; +import { sortBy } from "@arkecosystem/utils"; import { app } from "@arkecosystem/core-container"; import { Database, Logger } from "@arkecosystem/core-interfaces"; @@ -11,52 +11,50 @@ const config = app.getConfig(); const genesisWallets = config.get("genesisBlock.transactions").map(tx => tx.senderId); -export class SPV { - constructor(private query: QueryExecutor, private walletManager: Database.IWalletManager) {} +export class IntegrityVerifier { + constructor(private readonly query: QueryExecutor, private readonly walletManager: Database.IWalletManager) {} /** - * Perform the SPV (Simple Payment Verification). - * @param {Number} height - * @return {void} + * Perform the State & Integrity Verification. + * @return {Boolean} */ - public async build(height) { + public async run() { + logger.info("Integrity Verification - Step 1 of 8: Received Transactions"); + await this.buildReceivedTransactions(); - logger.info("SPV Step 1 of 8: Received Transactions"); - await this.__buildReceivedTransactions(); + logger.info("Integrity Verification - Step 2 of 8: Block Rewards"); + await this.buildBlockRewards(); - logger.info("SPV Step 2 of 8: Block Rewards"); - await this.__buildBlockRewards(); + logger.info("Integrity Verification - Step 3 of 8: Last Forged Blocks"); + await this.buildLastForgedBlocks(); - logger.info("SPV Step 3 of 8: Last Forged Blocks"); - await this.__buildLastForgedBlocks(); + logger.info("Integrity Verification - Step 4 of 8: Sent Transactions"); + await this.buildSentTransactions(); - logger.info("SPV Step 4 of 8: Sent Transactions"); - await this.__buildSentTransactions(); + logger.info("Integrity Verification - Step 5 of 8: Second Signatures"); + await this.buildSecondSignatures(); - logger.info("SPV Step 5 of 8: Second Signatures"); - await this.__buildSecondSignatures(); + logger.info("Integrity Verification - Step 6 of 8: Votes"); + await this.buildVotes(); - logger.info("SPV Step 6 of 8: Votes"); - await this.__buildVotes(); + logger.info("Integrity Verification - Step 7 of 8: Delegates"); + await this.buildDelegates(); - logger.info("SPV Step 7 of 8: Delegates"); - await this.__buildDelegates(); + logger.info("Integrity Verification - Step 8 of 8: MultiSignatures"); + await this.buildMultisignatures(); - logger.info("SPV Step 8 of 8: MultiSignatures"); - await this.__buildMultisignatures(); - - logger.info(`SPV rebuild finished, wallets in memory: ${Object.keys(this.walletManager.allByAddress()).length}`); + logger.info(`Integrity verified! Wallets in memory: ${Object.keys(this.walletManager.allByAddress()).length}`); logger.info(`Number of registered delegates: ${Object.keys(this.walletManager.allByUsername()).length}`); - return this.__verifyWalletsConsistency(); + return this.verifyWalletsConsistency(); } /** * Load and apply received transactions to wallets. * @return {void} */ - public async __buildReceivedTransactions() { - const transactions = await this.query.many(queries.spv.receivedTransactions); + private async buildReceivedTransactions() { + const transactions = await this.query.many(queries.integrityVerifier.receivedTransactions); for (const transaction of transactions) { const wallet = this.walletManager.findByAddress(transaction.recipientId); @@ -71,8 +69,8 @@ export class SPV { * Load and apply block rewards to wallets. * @return {void} */ - public async __buildBlockRewards() { - const blocks = await this.query.many(queries.spv.blockRewards); + private async buildBlockRewards() { + const blocks = await this.query.many(queries.integrityVerifier.blockRewards); for (const block of blocks) { const wallet = this.walletManager.findByPublicKey(block.generatorPublicKey); @@ -84,8 +82,8 @@ export class SPV { * Load and apply last forged blocks to wallets. * @return {void} */ - public async __buildLastForgedBlocks() { - const blocks = await this.query.many(queries.spv.lastForgedBlocks); + private async buildLastForgedBlocks() { + const blocks = await this.query.many(queries.integrityVerifier.lastForgedBlocks); for (const block of blocks) { const wallet = this.walletManager.findByPublicKey(block.generatorPublicKey); @@ -97,8 +95,8 @@ export class SPV { * Load and apply sent transactions to wallets. * @return {void} */ - public async __buildSentTransactions() { - const transactions = await this.query.many(queries.spv.sentTransactions); + private async buildSentTransactions() { + const transactions = await this.query.many(queries.integrityVerifier.sentTransactions); for (const transaction of transactions) { const wallet = this.walletManager.findByPublicKey(transaction.senderPublicKey); @@ -114,7 +112,7 @@ export class SPV { * Used to determine if a wallet is a Genesis wallet. * @return {Boolean} */ - public isGenesis(wallet) { + private isGenesis(wallet) { return genesisWallets.includes(wallet.address); } @@ -122,14 +120,12 @@ export class SPV { * Load and apply second signature transactions to wallets. * @return {void} */ - public async __buildSecondSignatures() { - const transactions = await this.query.manyOrNone(queries.spv.secondSignatures); + private async buildSecondSignatures() { + const transactions = await this.query.manyOrNone(queries.integrityVerifier.secondSignatures); for (const transaction of transactions) { const wallet = this.walletManager.findByPublicKey(transaction.senderPublicKey); - wallet.secondPublicKey = Transaction.deserialize( - transaction.serialized.toString("hex"), - ).asset.signature.publicKey; + wallet.secondPublicKey = transaction.asset.signature.publicKey; } } @@ -137,14 +133,14 @@ export class SPV { * Load and apply votes to wallets. * @return {void} */ - public async __buildVotes() { - const transactions = await this.query.manyOrNone(queries.spv.votes); + private async buildVotes() { + const transactions = await this.query.manyOrNone(queries.integrityVerifier.votes); for (const transaction of transactions) { const wallet = this.walletManager.findByPublicKey(transaction.senderPublicKey); if (!wallet.voted) { - const vote = Transaction.deserialize(transaction.serialized.toString("hex")).asset.votes[0]; + const vote = transaction.asset.votes[0]; if (vote.startsWith("+")) { wallet.vote = vote.slice(1); @@ -164,18 +160,18 @@ export class SPV { * Load and apply delegate usernames to wallets. * @return {void} */ - public async __buildDelegates() { + private async buildDelegates() { // Register... - const transactions = await this.query.manyOrNone(queries.spv.delegates); + const transactions = await this.query.manyOrNone(queries.integrityVerifier.delegates); transactions.forEach(transaction => { const wallet = this.walletManager.findByPublicKey(transaction.senderPublicKey); - wallet.username = Transaction.deserialize(transaction.serialized.toString("hex")).asset.delegate.username; + wallet.username = transaction.asset.delegate.username; this.walletManager.reindex(wallet); }); // Forged Blocks... - const forgedBlocks = await this.query.manyOrNone(queries.spv.delegatesForgedBlocks); + const forgedBlocks = await this.query.manyOrNone(queries.integrityVerifier.delegatesForgedBlocks); forgedBlocks.forEach(block => { const wallet = this.walletManager.findByPublicKey(block.generatorPublicKey); wallet.forgedFees = wallet.forgedFees.plus(block.totalFees); @@ -183,32 +179,23 @@ export class SPV { wallet.producedBlocks = +block.totalProduced; }); - // NOTE: This is highly NOT reliable, however the number of missed blocks - // is NOT used for the consensus - const delegates = await this.query.manyOrNone(queries.spv.delegatesRanks); - delegates.forEach((delegate, i) => { - const wallet = this.walletManager.findByPublicKey(delegate.publicKey); - wallet.missedBlocks = +delegate.missedBlocks; - // TODO: unknown property 'rate' being access on Wallet class - (wallet as any).rate = i + 1; - this.walletManager.reindex(wallet); - }); + const delegateWallets = this.walletManager.allByUsername(); + + this.walletManager.buildDelegateRanking(delegateWallets); } /** * Load and apply multisignatures to wallets. * @return {void} */ - public async __buildMultisignatures() { - const transactions = await this.query.manyOrNone(queries.spv.multiSignatures); + private async buildMultisignatures() { + const transactions = await this.query.manyOrNone(queries.integrityVerifier.multiSignatures); for (const transaction of transactions) { const wallet = this.walletManager.findByPublicKey(transaction.senderPublicKey); if (!wallet.multisignature) { - wallet.multisignature = Transaction.deserialize( - transaction.serialized.toString("hex"), - ).asset.multisignature; + wallet.multisignature = transaction.asset.multisignature; } } } @@ -219,43 +206,21 @@ export class SPV { * NOTE: This is faster than rebuilding the entire table from scratch each time. * @returns {Boolean} */ - public async __verifyWalletsConsistency() { - const dbWallets = await this.query.manyOrNone(queries.wallets.all); - const inMemoryWallets = this.walletManager.allByPublicKey(); - + private async verifyWalletsConsistency() { let detectedInconsistency = false; - if (dbWallets.length !== inMemoryWallets.length) { - detectedInconsistency = true; - } else { - for (const dbWallet of dbWallets) { - if (dbWallet.balance < 0 && !this.isGenesis(dbWallet)) { - detectedInconsistency = true; - logger.warn(`Wallet '${dbWallet.address}' has a negative balance of '${dbWallet.balance}'`); - break; - } - if (dbWallet.voteBalance < 0) { - detectedInconsistency = true; - logger.warn(`Wallet ${dbWallet.address} has a negative vote balance of '${dbWallet.voteBalance}'`); - break; - } - - const inMemoryWallet = this.walletManager.findByPublicKey(dbWallet.publicKey); - - if ( - !inMemoryWallet.balance.isEqualTo(dbWallet.balance) || - !inMemoryWallet.voteBalance.isEqualTo(dbWallet.voteBalance) || - dbWallet.username !== inMemoryWallet.username - ) { - detectedInconsistency = true; - break; - } + for (const wallet of this.walletManager.allByAddress()) { + if (wallet.balance.isLessThan(0) && !this.isGenesis(wallet)) { + detectedInconsistency = true; + logger.warn(`Wallet '${wallet.address}' has a negative balance of '${wallet.balance}'`); + break; } - } - // Remove dirty flags when no inconsistency has been found - if (!detectedInconsistency) { - this.walletManager.clear(); + if (wallet.voteBalance.isLessThan(0)) { + detectedInconsistency = true; + logger.warn(`Wallet ${wallet.address} has a negative vote balance of '${wallet.voteBalance}'`); + break; + } } return !detectedInconsistency; diff --git a/packages/core-database-postgres/src/migrations/20190307000000-drop-wallets-table.sql b/packages/core-database-postgres/src/migrations/20190307000000-drop-wallets-table.sql new file mode 100644 index 0000000000..bc21aedbef --- /dev/null +++ b/packages/core-database-postgres/src/migrations/20190307000000-drop-wallets-table.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS "wallets"; diff --git a/packages/core-database-postgres/src/migrations/20190313000000-add-asset-column-to-transactions-table.sql b/packages/core-database-postgres/src/migrations/20190313000000-add-asset-column-to-transactions-table.sql new file mode 100644 index 0000000000..b5d4ea66fa --- /dev/null +++ b/packages/core-database-postgres/src/migrations/20190313000000-add-asset-column-to-transactions-table.sql @@ -0,0 +1,2 @@ +ALTER TABLE transactions DROP COLUMN IF EXISTS asset; +ALTER TABLE transactions ADD COLUMN asset JSONB; diff --git a/packages/core-database-postgres/src/migrations/index.ts b/packages/core-database-postgres/src/migrations/index.ts index d0e51a118e..6a57a2759c 100644 --- a/packages/core-database-postgres/src/migrations/index.ts +++ b/packages/core-database-postgres/src/migrations/index.ts @@ -11,4 +11,6 @@ export const migrations = [ loadQueryFile(__dirname, "./20181204200000-add-timestamp-index-to-blocks-table.sql"), loadQueryFile(__dirname, "./20181204300000-add-sender_public_key-index-to-transactions-table.sql"), loadQueryFile(__dirname, "./20181204400000-add-recipient_id-index-to-transactions-table.sql"), + loadQueryFile(__dirname, "./20190307000000-drop-wallets-table.sql"), + loadQueryFile(__dirname, "./20190313000000-add-asset-column-to-transactions-table.sql"), ]; diff --git a/packages/core-database-postgres/src/models/block.ts b/packages/core-database-postgres/src/models/block.ts index f537ab8b4e..c889c38daf 100644 --- a/packages/core-database-postgres/src/models/block.ts +++ b/packages/core-database-postgres/src/models/block.ts @@ -1,72 +1,89 @@ +import { Database } from "@arkecosystem/core-interfaces"; import { bignumify } from "@arkecosystem/core-utils"; import { Model } from "./model"; export class Block extends Model { - /** - * The table associated with the model. - * @return {String} - */ - public getTable() { - return "blocks"; - } + constructor(pgp) { + super(pgp); - /** - * The read-only structure with query-formatting columns. - * @return {Object} - */ - public getColumnSet() { - return this.createColumnSet([ + this.columnsDescriptor = [ { name: "id", + supportedOperators: [Database.SearchOperator.OP_EQ, Database.SearchOperator.OP_IN], }, { name: "version", + supportedOperators: [Database.SearchOperator.OP_EQ], }, { name: "timestamp", + supportedOperators: [Database.SearchOperator.OP_LTE, Database.SearchOperator.OP_GTE], }, { name: "previous_block", prop: "previousBlock", def: null, + supportedOperators: [Database.SearchOperator.OP_EQ], }, { name: "height", + supportedOperators: [ + Database.SearchOperator.OP_EQ, + Database.SearchOperator.OP_IN, + Database.SearchOperator.OP_LTE, + Database.SearchOperator.OP_GTE, + ], }, { name: "number_of_transactions", prop: "numberOfTransactions", + supportedOperators: [Database.SearchOperator.OP_LTE, Database.SearchOperator.OP_GTE], }, { name: "total_amount", prop: "totalAmount", init: col => bignumify(col.value).toFixed(), + supportedOperators: [Database.SearchOperator.OP_LTE, Database.SearchOperator.OP_GTE], }, { name: "total_fee", prop: "totalFee", init: col => bignumify(col.value).toFixed(), + supportedOperators: [Database.SearchOperator.OP_LTE, Database.SearchOperator.OP_GTE], }, { name: "reward", init: col => bignumify(col.value).toFixed(), + supportedOperators: [Database.SearchOperator.OP_LTE, Database.SearchOperator.OP_GTE], }, { name: "payload_length", prop: "payloadLength", + supportedOperators: [Database.SearchOperator.OP_LTE, Database.SearchOperator.OP_GTE], }, { name: "payload_hash", prop: "payloadHash", + supportedOperators: [Database.SearchOperator.OP_EQ], }, { name: "generator_public_key", prop: "generatorPublicKey", + supportedOperators: [Database.SearchOperator.OP_EQ, Database.SearchOperator.OP_IN], }, { name: "block_signature", prop: "blockSignature", + supportedOperators: [Database.SearchOperator.OP_EQ], }, - ]); + ]; + } + + /** + * The table associated with the model. + * @return {String} + */ + public getTable() { + return "blocks"; } } diff --git a/packages/core-database-postgres/src/models/index.ts b/packages/core-database-postgres/src/models/index.ts index 5fdb934052..62d8bb4408 100644 --- a/packages/core-database-postgres/src/models/index.ts +++ b/packages/core-database-postgres/src/models/index.ts @@ -3,4 +3,3 @@ export * from "./block"; export * from "./migration"; export * from "./round"; export * from "./transaction"; -export * from "./wallet"; diff --git a/packages/core-database-postgres/src/models/migration.ts b/packages/core-database-postgres/src/models/migration.ts index cf56eb259c..56b56c717b 100644 --- a/packages/core-database-postgres/src/models/migration.ts +++ b/packages/core-database-postgres/src/models/migration.ts @@ -1,6 +1,16 @@ import { Model } from "./model"; export class Migration extends Model { + constructor(pgp) { + super(pgp); + + this.columnsDescriptor = [ + { + name: "name", + }, + ]; + } + /** * The table associated with the model. * @return {String} @@ -8,16 +18,4 @@ export class Migration extends Model { public getTable() { return "migrations"; } - - /** - * The read-only structure with query-formatting columns. - * @return {Object} - */ - public getColumnSet() { - return this.createColumnSet([ - { - name: "name", - }, - ]); - } } diff --git a/packages/core-database-postgres/src/models/model.ts b/packages/core-database-postgres/src/models/model.ts index 6d401f9dca..b9ca4daa54 100644 --- a/packages/core-database-postgres/src/models/model.ts +++ b/packages/core-database-postgres/src/models/model.ts @@ -1,11 +1,24 @@ +import { Database } from "@arkecosystem/core-interfaces"; +import { IMain } from "pg-promise"; import sql from "sql"; -export abstract class Model { +interface ColumnDescriptor { + name: string; + supportedOperators?: Database.SearchOperator[]; + prop?: string; + init?: any; + def?: any; +} + +export abstract class Model implements Database.IModel { + protected columnsDescriptor: ColumnDescriptor[]; + protected columnSet: any; + /** * Create a new model instance. * @param {Object} pgp */ - constructor(public pgp) {} + protected constructor(protected readonly pgp: IMain) {} /** * Get table name for model. @@ -17,7 +30,37 @@ export abstract class Model { * Get table column names for model. * @return {String[]} */ - public abstract getColumnSet(): any; + public getColumnSet() { + if (!this.columnSet) { + this.columnSet = this.createColumnSet( + this.columnsDescriptor.map(col => { + const colDef: any = { + name: col.name, + }; + ["prop", "init", "def"].forEach(prop => { + if (col.hasOwnProperty(prop)) { + colDef[prop] = col[prop]; + } + }); + return colDef; + }), + ); + } + return this.columnSet; + } + + public getSearchableFields(): Database.SearchableField[] { + return this.columnsDescriptor.map(col => { + return { + fieldName: col.prop || col.name, + supportedOperators: col.supportedOperators, + }; + }); + } + + public getName(): string { + return this.constructor.name; + } /** * Return the model & table definition. @@ -40,7 +83,7 @@ export abstract class Model { * @return {ColumnSet} * @param columns */ - public createColumnSet(columns) { + private createColumnSet(columns) { return new this.pgp.helpers.ColumnSet(columns, { table: { table: this.getTable(), diff --git a/packages/core-database-postgres/src/models/round.ts b/packages/core-database-postgres/src/models/round.ts index 2ccd7a87bd..20cf894493 100644 --- a/packages/core-database-postgres/src/models/round.ts +++ b/packages/core-database-postgres/src/models/round.ts @@ -1,33 +1,42 @@ +import { Database } from "@arkecosystem/core-interfaces"; import { bignumify } from "@arkecosystem/core-utils"; import { Model } from "./model"; export class Round extends Model { - /** - * The table associated with the model. - * @return {String} - */ - public getTable() { - return "rounds"; - } - - /** - * The read-only structure with query-formatting columns. - * @return {Object} - */ - public getColumnSet() { - return this.createColumnSet([ + constructor(pgp) { + super(pgp); + this.columnsDescriptor = [ { name: "public_key", prop: "publicKey", + supportedOperators: [Database.SearchOperator.OP_EQ], }, { name: "balance", prop: "voteBalance", init: col => bignumify(col.value).toFixed(), + supportedOperators: [ + Database.SearchOperator.OP_EQ, + Database.SearchOperator.OP_LTE, + Database.SearchOperator.OP_GTE, + ], }, { name: "round", + supportedOperators: [ + Database.SearchOperator.OP_EQ, + Database.SearchOperator.OP_LTE, + Database.SearchOperator.OP_GTE, + ], }, - ]); + ]; + } + + /** + * The table associated with the model. + * @return {String} + */ + public getTable() { + return "rounds"; } } diff --git a/packages/core-database-postgres/src/models/transaction.ts b/packages/core-database-postgres/src/models/transaction.ts index e79870cfd4..3fd7583c04 100644 --- a/packages/core-database-postgres/src/models/transaction.ts +++ b/packages/core-database-postgres/src/models/transaction.ts @@ -1,64 +1,95 @@ +import { Database } from "@arkecosystem/core-interfaces"; import { bignumify } from "@arkecosystem/core-utils"; import { Model } from "./model"; export class Transaction extends Model { - /** - * The table associated with the model. - * @return {String} - */ - public getTable() { - return "transactions"; - } + constructor(pgp) { + super(pgp); - /** - * The read-only structure with query-formatting columns. - * @return {Object} - */ - public getColumnSet() { - return this.createColumnSet([ + this.columnsDescriptor = [ { name: "id", + supportedOperators: [Database.SearchOperator.OP_EQ, Database.SearchOperator.OP_IN], }, { name: "version", + supportedOperators: [Database.SearchOperator.OP_EQ, Database.SearchOperator.OP_IN], }, { name: "block_id", prop: "blockId", + supportedOperators: [Database.SearchOperator.OP_EQ, Database.SearchOperator.OP_IN], }, { name: "sequence", + supportedOperators: [Database.SearchOperator.OP_EQ], }, { name: "timestamp", + supportedOperators: [ + Database.SearchOperator.OP_LTE, + Database.SearchOperator.OP_GTE, + Database.SearchOperator.OP_EQ, + ], }, { name: "sender_public_key", prop: "senderPublicKey", + supportedOperators: [Database.SearchOperator.OP_EQ, Database.SearchOperator.OP_IN], }, { name: "recipient_id", prop: "recipientId", + supportedOperators: [Database.SearchOperator.OP_EQ, Database.SearchOperator.OP_IN], + def: null, }, { name: "type", + supportedOperators: [Database.SearchOperator.OP_EQ, Database.SearchOperator.OP_IN], }, { name: "vendor_field_hex", prop: "vendorFieldHex", + supportedOperators: [Database.SearchOperator.OP_LIKE], + def: null, }, { name: "amount", init: col => bignumify(col.value).toFixed(), + supportedOperators: [ + Database.SearchOperator.OP_LTE, + Database.SearchOperator.OP_GTE, + Database.SearchOperator.OP_EQ, + ], }, { name: "fee", init: col => bignumify(col.value).toFixed(), + supportedOperators: [ + Database.SearchOperator.OP_LTE, + Database.SearchOperator.OP_GTE, + Database.SearchOperator.OP_EQ, + ], }, { name: "serialized", - init: col => Buffer.from(col.value, "hex"), + supportedOperators: [Database.SearchOperator.OP_EQ], }, - ]); + { + name: "asset", + init: col => { + return col.value; + }, + supportedOperators: [Database.SearchOperator.OP_EQ], + }, + ]; + } + + /** + * The table associated with the model. + * @return {String} + */ + public getTable() { + return "transactions"; } } diff --git a/packages/core-database-postgres/src/models/wallet.ts b/packages/core-database-postgres/src/models/wallet.ts deleted file mode 100644 index 52c9c043dd..0000000000 --- a/packages/core-database-postgres/src/models/wallet.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { bignumify } from "@arkecosystem/core-utils"; -import { Model } from "./model"; - -export class Wallet extends Model { - /** - * The table associated with the model. - * @return {String} - */ - public getTable() { - return "wallets"; - } - - /** - * The read-only structure with query-formatting columns. - * @return {Object} - */ - public getColumnSet() { - return this.createColumnSet([ - { - name: "address", - }, - { - name: "public_key", - prop: "publicKey", - }, - { - name: "second_public_key", - prop: "secondPublicKey", - }, - { - name: "vote", - }, - { - name: "username", - }, - { - name: "balance", - init: col => bignumify(col.value).toFixed(), - }, - { - name: "vote_balance", - prop: "voteBalance", - init: col => (col.value ? bignumify(col.value).toFixed() : null), - }, - { - name: "produced_blocks", - prop: "producedBlocks", - }, - { - name: "missed_blocks", - prop: "missedBlocks", - }, - ]); - } -} diff --git a/packages/core-database-postgres/src/plugin.ts b/packages/core-database-postgres/src/plugin.ts index 2e1c39fd57..f455005c30 100644 --- a/packages/core-database-postgres/src/plugin.ts +++ b/packages/core-database-postgres/src/plugin.ts @@ -1,5 +1,4 @@ -import { - DatabaseManager, databaseServiceFactory, WalletManager} from "@arkecosystem/core-database"; +import { ConnectionManager, databaseServiceFactory, WalletManager } from "@arkecosystem/core-database"; import { Container, Database, Logger } from "@arkecosystem/core-interfaces"; import { defaults } from "./defaults"; import { PostgresConnection } from "./postgres-connection"; @@ -14,11 +13,10 @@ export const plugin: Container.PluginDescriptor = { const walletManager = new WalletManager(); - const databaseManager = container.resolvePlugin("databaseManager"); + const connectionManager = container.resolvePlugin("database-manager"); + const connection = await connectionManager.createConnection(new PostgresConnection(options, walletManager)); - const connection = await databaseManager.makeConnection(new PostgresConnection(options, walletManager)); - - return await databaseServiceFactory(options, walletManager, connection); + return databaseServiceFactory(options, walletManager, connection); }, async deregister(container: Container.IContainer, options) { container.resolvePlugin("logger").info("Closing Database Connection"); diff --git a/packages/core-database-postgres/src/postgres-connection.ts b/packages/core-database-postgres/src/postgres-connection.ts index 442a19eb98..63defa5efc 100644 --- a/packages/core-database-postgres/src/postgres-connection.ts +++ b/packages/core-database-postgres/src/postgres-connection.ts @@ -1,21 +1,19 @@ import { app } from "@arkecosystem/core-container"; import { Database, EventEmitter, Logger } from "@arkecosystem/core-interfaces"; import { roundCalculator } from "@arkecosystem/core-utils"; -import { models } from "@arkecosystem/crypto"; -import fs from "fs"; -import chunk from "lodash/chunk"; +import { configManager, models, Transaction } from "@arkecosystem/crypto"; +import chunk from "lodash.chunk"; import path from "path"; -import pgPromise from "pg-promise"; +import pgPromise, { IMain } from "pg-promise"; +import { IntegrityVerifier } from "./integrity-verifier"; import { migrations } from "./migrations"; import { Model } from "./models"; import { repositories } from "./repositories"; import { MigrationsRepository } from "./repositories/migrations"; -import { SPV } from "./spv"; import { QueryExecutor } from "./sql/query-executor"; import { camelizeColumns } from "./utils"; -export class PostgresConnection implements Database.IDatabaseConnection { - +export class PostgresConnection implements Database.IConnection { public logger = app.resolvePlugin("logger"); public models: { [key: string]: Model } = {}; public query: QueryExecutor; @@ -24,62 +22,46 @@ export class PostgresConnection implements Database.IDatabaseConnection { public roundsRepository: Database.IRoundsRepository; public transactionsRepository: Database.ITransactionsRepository; public walletsRepository: Database.IWalletsRepository; - public pgp: any; + public pgp: IMain; private emitter = app.resolvePlugin("event-emitter"); - private migrationsRepository : MigrationsRepository; + private migrationsRepository: MigrationsRepository; private cache: Map; private queuedQueries: any[]; + public constructor(readonly options: any, private walletManager: Database.IWalletManager) {} - public constructor(readonly options: any, private walletManager: Database.IWalletManager) { - - } - - - public async buildWallets(height: number) { - const spvPath = `${process.env.CORE_PATH_CACHE}/spv.json`; - - if (fs.existsSync(spvPath)) { - (fs as any).removeSync(spvPath); - - this.logger.info("Ark Core ended unexpectedly - resuming from where we left off :runner:"); - - return true; - } - - try { - const spv = new SPV(this.query, this.walletManager); - return await spv.build(height); - } catch (error) { - this.logger.error(error.stack); + public async make(): Promise { + if (this.db) { + throw new Error("Database connection already initialised"); } - return false; - } - - public async commitQueuedQueries() { - if (!this.queuedQueries || this.queuedQueries.length === 0) { - return; - } + this.logger.debug("Connecting to database"); - this.logger.debug("Committing database transactions."); + this.queuedQueries = null; + this.cache = new Map(); try { - await this.db.tx(t => t.batch(this.queuedQueries)); - } catch (error) { - this.logger.error(error); + await this.connect(); + this.exposeRepositories(); + await this.registerQueryExecutor(); + await this.runMigrations(); + await this.registerModels(); + this.logger.debug("Connected to database."); + this.emitter.emit(Database.DatabaseEvents.POST_CONNECT); - throw error; - } finally { - this.queuedQueries = null; + return this; + } catch (error) { + app.forceExit("Unable to connect to the database!", error); } - } - public async connect() { + return null; + } + public async connect(): Promise { this.emitter.emit(Database.DatabaseEvents.PRE_CONNECT); + const initialization = { - receive(data, result, e) { + receive(data) { camelizeColumns(pgp, data); }, extend(object) { @@ -95,19 +77,7 @@ export class PostgresConnection implements Database.IDatabaseConnection { this.db = this.pgp(this.options.connection); } - public async deleteBlock(block: models.Block) { - try { - const queries = [this.transactionsRepository.deleteByBlockId(block.data.id), this.blocksRepository.delete(block.data.id)]; - - await this.db.tx(t => t.batch(queries)); - } catch (error) { - this.logger.error(error.stack); - - throw error; - } - } - - public async disconnect() { + public async disconnect(): Promise { this.logger.debug("Disconnecting from database"); this.emitter.emit(Database.DatabaseEvents.PRE_DISCONNECT); @@ -124,63 +94,81 @@ export class PostgresConnection implements Database.IDatabaseConnection { this.logger.debug("Disconnected from database"); } - public enqueueDeleteBlock(block: models.Block): any { - const queries = [this.transactionsRepository.deleteByBlockId(block.data.id), this.blocksRepository.delete(block.data.id)]; + public async buildWallets(): Promise { + try { + const result = await new IntegrityVerifier(this.query, this.walletManager).run(); + + return result; + } catch (error) { + this.logger.error(error.stack); + app.forceExit("Failed to build wallets. This indicates a problem with the database."); + } - this.enqueueQueries(queries); + return false; } - public enqueueDeleteRound(height: number): any { - const { round, nextRound, maxDelegates } = roundCalculator.calculateRound(height); - - if (nextRound === round + 1 && height >= maxDelegates) { - this.enqueueQueries([this.roundsRepository.delete(nextRound)]); + public async commitQueuedQueries(): Promise { + if (!this.queuedQueries || this.queuedQueries.length === 0) { + return; } - } - public enqueueSaveBlock(block: models.Block): any { - const queries = [this.blocksRepository.insert(block.data)]; + this.logger.debug("Committing database transactions."); - if (block.transactions.length > 0) { - queries.push(this.transactionsRepository.insert(block.transactions)); - } + try { + await this.db.tx(t => t.batch(this.queuedQueries)); + } catch (error) { + this.logger.error(error); - this.enqueueQueries(queries); + throw error; + } finally { + this.queuedQueries = null; + } } - public async make(): Promise { - if (this.db) { - throw new Error("Database connection already initialised"); - } + public async deleteBlock(block: models.Block): Promise { + try { + await this.db.tx(t => + t.batch([ + this.transactionsRepository.deleteByBlockId(block.data.id), + this.blocksRepository.delete(block.data.id), + ]), + ); + } catch (error) { + this.logger.error(error.stack); - this.logger.debug("Connecting to database"); + throw error; + } + } - this.queuedQueries = null; - this.cache = new Map(); + public enqueueDeleteBlock(block: models.Block): void { + this.enqueueQueries([ + this.transactionsRepository.deleteByBlockId(block.data.id), + this.blocksRepository.delete(block.data.id), + ]); + } - try { - await this.connect(); - this.exposeRepositories(); - await this.registerQueryExecutor(); - await this.runMigrations(); - await this.registerModels(); - this.logger.debug("Connected to database."); - this.emitter.emit(Database.DatabaseEvents.POST_CONNECT); + public enqueueDeleteRound(height: number): void { + const { round, nextRound, maxDelegates } = roundCalculator.calculateRound(height); - return this; - } catch (error) { - app.forceExit("Unable to connect to the database!", error); + if (nextRound === round + 1 && height >= maxDelegates) { + this.enqueueQueries([this.roundsRepository.delete(nextRound)]); } - - return null; } - public async saveBlock(block: models.Block) { + public async saveBlock(block: models.Block): Promise { try { const queries = [this.blocksRepository.insert(block.data)]; if (block.transactions.length > 0) { - queries.push(this.transactionsRepository.insert(block.transactions)); + queries.push( + this.transactionsRepository.insert( + block.transactions.map(tx => ({ + ...tx.data, + timestamp: tx.timestamp, + serialized: tx.serialized, + })), + ), + ); } await this.db.tx(t => t.batch(queries)); @@ -189,61 +177,90 @@ export class PostgresConnection implements Database.IDatabaseConnection { } } - public async saveWallets(wallets: any[], force?: boolean) { - if (force) { - // all wallets to be updated, performance is better without upsert - await this.walletsRepository.truncate(); - - try { - const chunks = chunk(wallets, 5000).map(c => this.walletsRepository.insert(c)); // this 5000 figure should be configurable... - await this.db.tx(t => t.batch(chunks)); - } catch (error) { - this.logger.error(error.stack); - } - } else { - // NOTE: The list of delegates is calculated in-memory against the WalletManager, - // so it is safe to perform the costly UPSERT non-blocking during round change only: - // 'await saveWallets(false)' -> 'saveWallets(false)' - try { - const queries = wallets.map(wallet => this.walletsRepository.updateOrCreate(wallet)); - await this.db.tx(t => t.batch(queries)); - } catch (error) { - this.logger.error(error.stack); - } - } - } - /** * Run all migrations. * @return {void} */ - - private async runMigrations() { + private async runMigrations(): Promise { for (const migration of migrations) { const { name } = path.parse(migration.file); if (name === "20180304100000-create-migrations-table") { await this.query.none(migration); + } else if (name === "20190313000000-add-asset-column-to-transactions-table") { + await this.migrateTransactionsTableToAssetColumn(name, migration); } else { const row = await this.migrationsRepository.findByName(name); - if (row === null) { this.logger.debug(`Migrating ${name}`); - await this.query.none(migration); - await this.migrationsRepository.insert({ name }); } } } } + /** + * Migrate transactions table to asset column. + */ + private async migrateTransactionsTableToAssetColumn(name: string, migration: pgPromise.QueryFile): Promise { + const row = await this.migrationsRepository.findByName(name); + + // Also run migration if the asset column is present, but missing values. E.g. + // after restoring a snapshot without assets even though the database has already been migrated. + let runMigration = row === null; + if (!runMigration) { + const { missingAsset } = await this.db.one( + `SELECT EXISTS (SELECT id FROM transactions WHERE type > 0 AND asset IS NULL) as "missingAsset"`, + ); + if (missingAsset) { + await this.db.none(`DELETE FROM migrations WHERE name = '${name}'`); + runMigration = true; + } + } + + if (!runMigration) { + return; + } + this.logger.warn(`Migrating transactions table. This may take a while.`); + + await this.query.none(migration); + + const all = await this.db.manyOrNone("SELECT id, serialized FROM transactions WHERE type > 0"); + const { transactionIdFixTable } = configManager.get("exceptions"); + + for (const batch of chunk(all, 20000)) { + await this.db.task(task => { + const transactions = []; + batch.forEach((tx: { serialized: Buffer; id: string }) => { + const transaction = Transaction.fromBytesUnsafe(tx.serialized, tx.id); + if (transaction.data.asset) { + let transactionId = transaction.id; + + // If the transaction is a broken v1 transaction use the broken id for the query. + if (transactionIdFixTable && transactionIdFixTable[transactionId]) { + transactionId = transactionIdFixTable[transactionId]; + } + + const query = + this.pgp.helpers.update({ asset: transaction.data.asset }, ["asset"], "transactions") + + ` WHERE id = '${transactionId}'`; + transactions.push(task.none(query)); + } + }); + + return task.batch(transactions); + }); + } + + await this.migrationsRepository.insert({ name }); + } /** * Register all models. * @return {void} */ - private async registerModels() { + private async registerModels(): Promise { for (const [key, Value] of Object.entries(require("./models"))) { this.models[key.toLowerCase()] = new (Value as any)(this.pgp); } @@ -253,11 +270,11 @@ export class PostgresConnection implements Database.IDatabaseConnection { * Register the query builder. * @return {void} */ - private registerQueryExecutor() { + private registerQueryExecutor(): void { this.query = new QueryExecutor(this); } - private enqueueQueries(queries) { + private enqueueQueries(queries): void { if (!this.queuedQueries) { this.queuedQueries = []; } @@ -265,7 +282,7 @@ export class PostgresConnection implements Database.IDatabaseConnection { (this.queuedQueries as any).push(...queries); } - private exposeRepositories() { + private exposeRepositories(): void { this.blocksRepository = this.db.blocks; this.transactionsRepository = this.db.transactions; this.roundsRepository = this.db.rounds; diff --git a/packages/core-database-postgres/src/queries/blocks/find-by-height.sql b/packages/core-database-postgres/src/queries/blocks/find-by-height.sql index 9b87fadb46..25ad1738ce 100644 --- a/packages/core-database-postgres/src/queries/blocks/find-by-height.sql +++ b/packages/core-database-postgres/src/queries/blocks/find-by-height.sql @@ -1,3 +1,3 @@ SELECT * FROM blocks -WHERE height IN (${heights:list}) +WHERE height = ${height} diff --git a/packages/core-database-postgres/src/queries/blocks/find-by-heights.sql b/packages/core-database-postgres/src/queries/blocks/find-by-heights.sql new file mode 100644 index 0000000000..9b87fadb46 --- /dev/null +++ b/packages/core-database-postgres/src/queries/blocks/find-by-heights.sql @@ -0,0 +1,3 @@ +SELECT * +FROM blocks +WHERE height IN (${heights:list}) diff --git a/packages/core-database-postgres/src/queries/index.ts b/packages/core-database-postgres/src/queries/index.ts index 39a1933aa8..d1d6373270 100644 --- a/packages/core-database-postgres/src/queries/index.ts +++ b/packages/core-database-postgres/src/queries/index.ts @@ -7,6 +7,7 @@ export const queries = { delete: loadQueryFile(__dirname, "./blocks/delete.sql"), findById: loadQueryFile(__dirname, "./blocks/find-by-id.sql"), findByHeight: loadQueryFile(__dirname, "./blocks/find-by-height.sql"), + findByHeights: loadQueryFile(__dirname, "./blocks/find-by-heights.sql"), headers: loadQueryFile(__dirname, "./blocks/headers.sql"), heightRange: loadQueryFile(__dirname, "./blocks/height-range.sql"), latest: loadQueryFile(__dirname, "./blocks/latest.sql"), @@ -22,17 +23,16 @@ export const queries = { delete: loadQueryFile(__dirname, "./rounds/delete.sql"), find: loadQueryFile(__dirname, "./rounds/find.sql"), }, - spv: { - blockRewards: loadQueryFile(__dirname, "./spv/block-rewards.sql"), - delegates: loadQueryFile(__dirname, "./spv/delegates.sql"), - delegatesForgedBlocks: loadQueryFile(__dirname, "./spv/delegates-forged-blocks.sql"), - delegatesRanks: loadQueryFile(__dirname, "./spv/delegates-ranks.sql"), - lastForgedBlocks: loadQueryFile(__dirname, "./spv/last-forged-blocks.sql"), - multiSignatures: loadQueryFile(__dirname, "./spv/multi-signatures.sql"), - receivedTransactions: loadQueryFile(__dirname, "./spv/received-transactions.sql"), - secondSignatures: loadQueryFile(__dirname, "./spv/second-signatures.sql"), - sentTransactions: loadQueryFile(__dirname, "./spv/sent-transactions.sql"), - votes: loadQueryFile(__dirname, "./spv/votes.sql"), + integrityVerifier: { + blockRewards: loadQueryFile(__dirname, "./integrity-verifier/block-rewards.sql"), + delegates: loadQueryFile(__dirname, "./integrity-verifier/delegates.sql"), + delegatesForgedBlocks: loadQueryFile(__dirname, "./integrity-verifier/delegates-forged-blocks.sql"), + lastForgedBlocks: loadQueryFile(__dirname, "./integrity-verifier/last-forged-blocks.sql"), + multiSignatures: loadQueryFile(__dirname, "./integrity-verifier/multi-signatures.sql"), + receivedTransactions: loadQueryFile(__dirname, "./integrity-verifier/received-transactions.sql"), + secondSignatures: loadQueryFile(__dirname, "./integrity-verifier/second-signatures.sql"), + sentTransactions: loadQueryFile(__dirname, "./integrity-verifier/sent-transactions.sql"), + votes: loadQueryFile(__dirname, "./integrity-verifier/votes.sql"), }, transactions: { findByBlock: loadQueryFile(__dirname, "./transactions/find-by-block.sql"), @@ -43,10 +43,4 @@ export const queries = { findById: loadQueryFile(__dirname, "./transactions/find-by-id.sql"), deleteByBlock: loadQueryFile(__dirname, "./transactions/delete-by-block.sql"), }, - wallets: { - all: loadQueryFile(__dirname, "./wallets/all.sql"), - findByAddress: loadQueryFile(__dirname, "./wallets/find-by-address.sql"), - findNegativeBalances: loadQueryFile(__dirname, "./wallets/find-negative-balances.sql"), - findNegativeVoteBalances: loadQueryFile(__dirname, "./wallets/find-negative-vote-balances.sql"), - }, }; diff --git a/packages/core-database-postgres/src/queries/spv/block-rewards.sql b/packages/core-database-postgres/src/queries/integrity-verifier/block-rewards.sql similarity index 100% rename from packages/core-database-postgres/src/queries/spv/block-rewards.sql rename to packages/core-database-postgres/src/queries/integrity-verifier/block-rewards.sql diff --git a/packages/core-database-postgres/src/queries/spv/delegates-forged-blocks.sql b/packages/core-database-postgres/src/queries/integrity-verifier/delegates-forged-blocks.sql similarity index 100% rename from packages/core-database-postgres/src/queries/spv/delegates-forged-blocks.sql rename to packages/core-database-postgres/src/queries/integrity-verifier/delegates-forged-blocks.sql diff --git a/packages/core-database-postgres/src/queries/spv/delegates.sql b/packages/core-database-postgres/src/queries/integrity-verifier/delegates.sql similarity index 76% rename from packages/core-database-postgres/src/queries/spv/delegates.sql rename to packages/core-database-postgres/src/queries/integrity-verifier/delegates.sql index 9ea51abd44..b07917eafc 100644 --- a/packages/core-database-postgres/src/queries/spv/delegates.sql +++ b/packages/core-database-postgres/src/queries/integrity-verifier/delegates.sql @@ -1,4 +1,4 @@ SELECT sender_public_key, - serialized + asset FROM transactions WHERE TYPE = 2 diff --git a/packages/core-database-postgres/src/queries/spv/last-forged-blocks.sql b/packages/core-database-postgres/src/queries/integrity-verifier/last-forged-blocks.sql similarity index 100% rename from packages/core-database-postgres/src/queries/spv/last-forged-blocks.sql rename to packages/core-database-postgres/src/queries/integrity-verifier/last-forged-blocks.sql diff --git a/packages/core-database-postgres/src/queries/spv/multi-signatures.sql b/packages/core-database-postgres/src/queries/integrity-verifier/multi-signatures.sql similarity index 84% rename from packages/core-database-postgres/src/queries/spv/multi-signatures.sql rename to packages/core-database-postgres/src/queries/integrity-verifier/multi-signatures.sql index ad6f6c03e1..e0cef8b576 100644 --- a/packages/core-database-postgres/src/queries/spv/multi-signatures.sql +++ b/packages/core-database-postgres/src/queries/integrity-verifier/multi-signatures.sql @@ -1,5 +1,5 @@ SELECT sender_public_key, - serialized + asset FROM transactions WHERE TYPE = 4 ORDER BY (timestamp + sequence) DESC diff --git a/packages/core-database-postgres/src/queries/spv/received-transactions.sql b/packages/core-database-postgres/src/queries/integrity-verifier/received-transactions.sql similarity index 100% rename from packages/core-database-postgres/src/queries/spv/received-transactions.sql rename to packages/core-database-postgres/src/queries/integrity-verifier/received-transactions.sql diff --git a/packages/core-database-postgres/src/queries/spv/second-signatures.sql b/packages/core-database-postgres/src/queries/integrity-verifier/second-signatures.sql similarity index 76% rename from packages/core-database-postgres/src/queries/spv/second-signatures.sql rename to packages/core-database-postgres/src/queries/integrity-verifier/second-signatures.sql index ad929ad146..6c68ed4377 100644 --- a/packages/core-database-postgres/src/queries/spv/second-signatures.sql +++ b/packages/core-database-postgres/src/queries/integrity-verifier/second-signatures.sql @@ -1,4 +1,4 @@ SELECT sender_public_key, - serialized + asset FROM transactions WHERE TYPE = 1 diff --git a/packages/core-database-postgres/src/queries/spv/sent-transactions.sql b/packages/core-database-postgres/src/queries/integrity-verifier/sent-transactions.sql similarity index 100% rename from packages/core-database-postgres/src/queries/spv/sent-transactions.sql rename to packages/core-database-postgres/src/queries/integrity-verifier/sent-transactions.sql diff --git a/packages/core-database-postgres/src/queries/spv/votes.sql b/packages/core-database-postgres/src/queries/integrity-verifier/votes.sql similarity index 84% rename from packages/core-database-postgres/src/queries/spv/votes.sql rename to packages/core-database-postgres/src/queries/integrity-verifier/votes.sql index 63e71e02c3..40501c268e 100644 --- a/packages/core-database-postgres/src/queries/spv/votes.sql +++ b/packages/core-database-postgres/src/queries/integrity-verifier/votes.sql @@ -1,5 +1,5 @@ SELECT sender_public_key, - serialized + asset FROM transactions WHERE TYPE = 3 ORDER BY timestamp DESC, sequence ASC diff --git a/packages/core-database-postgres/src/queries/spv/delegates-ranks.sql b/packages/core-database-postgres/src/queries/spv/delegates-ranks.sql deleted file mode 100644 index ea16718904..0000000000 --- a/packages/core-database-postgres/src/queries/spv/delegates-ranks.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT public_key, - vote_balance, - missed_blocks -FROM wallets -WHERE username IS NOT NULL -ORDER BY vote_balance DESC, - public_key ASC diff --git a/packages/core-database-postgres/src/queries/transactions/find-by-block.sql b/packages/core-database-postgres/src/queries/transactions/find-by-block.sql index 922708b6b4..89aaf3aad9 100644 --- a/packages/core-database-postgres/src/queries/transactions/find-by-block.sql +++ b/packages/core-database-postgres/src/queries/transactions/find-by-block.sql @@ -1,3 +1,3 @@ -SELECT serialized +SELECT id, serialized FROM transactions WHERE block_id = ${id} diff --git a/packages/core-database-postgres/src/queries/transactions/latest-by-block.sql b/packages/core-database-postgres/src/queries/transactions/latest-by-block.sql index acf776d38d..9262f554e3 100644 --- a/packages/core-database-postgres/src/queries/transactions/latest-by-block.sql +++ b/packages/core-database-postgres/src/queries/transactions/latest-by-block.sql @@ -1,4 +1,4 @@ -SELECT serialized +SELECT id, serialized FROM transactions WHERE block_id = ${id} ORDER BY sequence ASC diff --git a/packages/core-database-postgres/src/queries/transactions/latest-by-blocks.sql b/packages/core-database-postgres/src/queries/transactions/latest-by-blocks.sql index 594234ab7c..716aca288b 100644 --- a/packages/core-database-postgres/src/queries/transactions/latest-by-blocks.sql +++ b/packages/core-database-postgres/src/queries/transactions/latest-by-blocks.sql @@ -1,4 +1,5 @@ -SELECT block_id, +SELECT id, + block_id, serialized FROM transactions WHERE block_id IN (${ids:list}) diff --git a/packages/core-database-postgres/src/queries/wallets/all.sql b/packages/core-database-postgres/src/queries/wallets/all.sql deleted file mode 100644 index 5435ffc400..0000000000 --- a/packages/core-database-postgres/src/queries/wallets/all.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT * -FROM wallets diff --git a/packages/core-database-postgres/src/queries/wallets/find-by-address.sql b/packages/core-database-postgres/src/queries/wallets/find-by-address.sql deleted file mode 100644 index 98fe3cdfef..0000000000 --- a/packages/core-database-postgres/src/queries/wallets/find-by-address.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT * -FROM wallets -WHERE address = ${address} diff --git a/packages/core-database-postgres/src/queries/wallets/find-negative-balances.sql b/packages/core-database-postgres/src/queries/wallets/find-negative-balances.sql deleted file mode 100644 index 83ebbad129..0000000000 --- a/packages/core-database-postgres/src/queries/wallets/find-negative-balances.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT COUNT (DISTINCT "address") AS "count" -FROM wallets -WHERE balance < 0; diff --git a/packages/core-database-postgres/src/queries/wallets/find-negative-vote-balances.sql b/packages/core-database-postgres/src/queries/wallets/find-negative-vote-balances.sql deleted file mode 100644 index af79cbad33..0000000000 --- a/packages/core-database-postgres/src/queries/wallets/find-negative-vote-balances.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT COUNT (DISTINCT "address") AS "count" -FROM wallets -WHERE vote_balance < 0; diff --git a/packages/core-database-postgres/src/repositories/blocks.ts b/packages/core-database-postgres/src/repositories/blocks.ts index 78b6670d3a..bf503d5272 100644 --- a/packages/core-database-postgres/src/repositories/blocks.ts +++ b/packages/core-database-postgres/src/repositories/blocks.ts @@ -15,13 +15,36 @@ export class BlocksRepository extends Repository implements Database.IBlocksRepo return this.db.oneOrNone(sql.findById, { id }); } + /** + * Find many blocks by their IDs. + * @param {String[]} ids + */ + public async findByIds(ids: string[]) { + return this.findMany( + this.query + .select() + .from(this.query) + .where(this.query.id.in(ids)) + .group(this.query.id), + ); + } + + /** + * Get a block at the given height. + * @param {Number} height the height of the blocks to retrieve + * @return {Promise} + */ + public async findByHeight(height: number) { + return this.db.oneOrNone(sql.findByHeight, { height }); + } + /** * Get all of the blocks at the given heights. * @param {Array} heights the heights of the blocks to retrieve * @return {Promise} */ - public async findByHeight(heights) { - return this.db.manyOrNone(sql.findByHeight, { heights }); + public async findByHeights(heights: number[]) { + return this.db.manyOrNone(sql.findByHeights, { heights }); } /** @@ -110,4 +133,46 @@ export class BlocksRepository extends Repository implements Database.IBlocksRepo public getModel() { return new Block(this.pgp); } + + /* TODO: Remove with v1 */ + public async findAll(params: Database.SearchParameters) { + const selectQuery = this.query.select().from(this.query); + // Blocks repo atm, doesn't search using any custom parameters + const parameterList = params.parameters.filter(o => o.operator !== Database.SearchOperator.OP_CUSTOM); + if (parameterList.length) { + const first = parameterList.shift(); + /* Notice the difference between 'findAll' and 'search' is that the former assumes eq for all params passed in */ + if (first) { + selectQuery.where(this.query[this.propToColumnName(first.field)].equals(first.value)); + for (const param of parameterList) { + selectQuery.and(this.query[this.propToColumnName(param.field)].equals(param.value)); + } + } + } + + return this.findManyWithCount(selectQuery, params.paginate, params.orderBy); + } + + public async search(params: Database.SearchParameters) { + // TODO: we're selecting all the columns right now. Add support for choosing specific columns, when it proves useful. + const selectQuery = this.query.select().from(this.query); + // Blocks repo atm, doesn't search using any custom parameters + const parameterList = params.parameters.filter(o => o.operator !== Database.SearchOperator.OP_CUSTOM); + if (parameterList.length) { + let first; + do { + first = parameterList.shift(); + // ignore params whose operator is unknown + } while (!first.operator && parameterList.length); + + if (first) { + selectQuery.where(this.query[this.propToColumnName(first.field)][first.operator](first.value)); + for (const param of parameterList) { + selectQuery.and(this.query[this.propToColumnName(param.field)][param.operator](param.value)); + } + } + } + + return this.findManyWithCount(selectQuery, params.paginate, params.orderBy); + } } diff --git a/packages/core-database-postgres/src/repositories/index.ts b/packages/core-database-postgres/src/repositories/index.ts index 5dae791f43..43b7c2c080 100644 --- a/packages/core-database-postgres/src/repositories/index.ts +++ b/packages/core-database-postgres/src/repositories/index.ts @@ -2,12 +2,10 @@ import { BlocksRepository } from "./blocks"; import { MigrationsRepository } from "./migrations"; import { RoundsRepository } from "./rounds"; import { TransactionsRepository } from "./transactions"; -import { WalletsRepository } from "./wallets"; export const repositories = { blocks: BlocksRepository, migrations: MigrationsRepository, rounds: RoundsRepository, transactions: TransactionsRepository, - wallets: WalletsRepository, }; diff --git a/packages/core-database-postgres/src/repositories/repository.ts b/packages/core-database-postgres/src/repositories/repository.ts index 63de711e58..0a9a7d6b9a 100644 --- a/packages/core-database-postgres/src/repositories/repository.ts +++ b/packages/core-database-postgres/src/repositories/repository.ts @@ -1,16 +1,19 @@ import { Database } from "@arkecosystem/core-interfaces"; +import { IMain } from "pg-promise"; import { Model } from "../models"; export abstract class Repository implements Database.IRepository { protected model: Model; + protected query; /** * Create a new repository instance. * @param {Object} db * @param {Object} pgp */ - constructor(public db, public pgp) { + constructor(protected readonly db, protected readonly pgp: IMain) { this.model = this.getModel(); + this.query = this.model.query(); } /** @@ -41,7 +44,7 @@ export abstract class Repository implements Database.IRepository { * @return {Promise} */ public async insert(items) { - return this.db.none(this.__insertQuery(items)); + return this.db.none(this.insertQuery(items)); } /** @@ -50,7 +53,7 @@ export abstract class Repository implements Database.IRepository { * @return {Promise} */ public async update(items) { - return this.db.none(this.__updateQuery(items)); + return this.db.none(this.updateQuery(items)); } /** @@ -58,7 +61,7 @@ export abstract class Repository implements Database.IRepository { * @param {Array|Object} data * @return {String} */ - public __insertQuery(data) { + protected insertQuery(data) { return this.pgp.helpers.insert(data, this.model.getColumnSet()); } @@ -67,7 +70,70 @@ export abstract class Repository implements Database.IRepository { * @param {Array|Object} data * @return {String} */ - public __updateQuery(data) { + protected updateQuery(data) { return this.pgp.helpers.update(data, this.model.getColumnSet()); } + + protected propToColumnName(prop: string): string { + if (prop) { + const columnSet = this.model.getColumnSet(); + const columnDef = columnSet.columns.find(col => col.prop === prop || col.name === prop); + return columnDef ? columnDef.name : null; + } + return prop; + } + + protected async find(query): Promise { + return this.db.oneOrNone(query.toQuery()); + } + + protected async findMany(query): Promise { + return this.db.manyOrNone(query.toQuery()); + } + + protected async findManyWithCount( + selectQuery, + paginate?: Database.SearchPaginate, + orderBy?: Database.SearchOrderBy[], + ): Promise { + if (!!orderBy) { + orderBy.forEach(o => selectQuery.order(this.query[o.field][o.direction])); + } + + if (!paginate || (!paginate.limit && !paginate.offset)) { + // tslint:disable-next-line:no-shadowed-variable + const rows = await this.findMany(selectQuery); + + return { rows, count: rows.length }; + } + + selectQuery.offset(paginate.offset).limit(paginate.limit); + + const rows = await this.findMany(selectQuery); + + if (rows.length < paginate.limit) { + return { rows, count: paginate.limit + rows.length }; + } + + // Get the last rows=... from something that looks like (1 column, few rows): + // + // QUERY PLAN + // ------------------------------------------------------------------ + // Limit (cost=15.34..15.59 rows=100 width=622) + // -> Sort (cost=15.34..15.64 rows=120 width=622) + // Sort Key: "timestamp" DESC + // -> Seq Scan on transactions (cost=0.00..11.20 rows=120 width=622) + + let count = 0; + const explainedQuery = await this.db.manyOrNone(`EXPLAIN ${selectQuery.toString()}`); + for (const row of explainedQuery) { + const line: any = Object.values(row)[0]; + const match = line.match(/rows=([0-9]+)/); + if (match !== null) { + count = Number(match[1]); + } + } + + return { rows, count: Math.max(count, rows.length) }; + } } diff --git a/packages/core-database-postgres/src/repositories/rounds.ts b/packages/core-database-postgres/src/repositories/rounds.ts index 06602185de..f8508b5c45 100644 --- a/packages/core-database-postgres/src/repositories/rounds.ts +++ b/packages/core-database-postgres/src/repositories/rounds.ts @@ -5,13 +5,13 @@ import { Repository } from "./repository"; const { rounds: sql } = queries; -export class RoundsRepository extends Repository implements Database.IRoundsRepository { +export class RoundsRepository extends Repository implements Database.IRoundsRepository { /** * Find a round by its ID. * @param {Number} round * @return {Promise} */ - public async findById(round) { + public async findById(round: number): Promise { return this.db.manyOrNone(sql.find, { round }); } @@ -20,7 +20,7 @@ export class RoundsRepository extends Repository implements Database.IRoundsRep * @param {Number} round * @return {Promise} */ - public async delete(round) { + public async delete(round): Promise { return this.db.none(sql.delete, { round }); } @@ -28,7 +28,7 @@ export class RoundsRepository extends Repository implements Database.IRoundsRep * Get the model related to this repository. * @return {Round} */ - public getModel() { + public getModel(): Round { return new Round(this.pgp); } } diff --git a/packages/core-database-postgres/src/repositories/transactions.ts b/packages/core-database-postgres/src/repositories/transactions.ts index 843f71b241..08ba627ce0 100644 --- a/packages/core-database-postgres/src/repositories/transactions.ts +++ b/packages/core-database-postgres/src/repositories/transactions.ts @@ -1,4 +1,7 @@ import { Database } from "@arkecosystem/core-interfaces"; +import { slots } from "@arkecosystem/crypto"; +import { dato } from "@faustbrian/dato"; +import partition from "lodash.partition"; import { Transaction } from "../models"; import { queries } from "../queries"; import { Repository } from "./repository"; @@ -75,4 +78,144 @@ export class TransactionsRepository extends Repository implements Database.ITran public getModel() { return new Transaction(this.pgp); } + + public getFeeStatistics(minFeeBroadcast: number): Promise { + const query = this.query + .select( + this.query.type, + this.query.fee.min("minFee"), + this.query.fee.max("maxFee"), + this.query.fee.avg("avgFee"), + this.query.timestamp.max("timestamp"), + ) + .from(this.query) + // Should make this '30' figure configurable + .where( + this.query.timestamp.gte( + slots.getTime( + dato() + .subDays(30) + .toMilliseconds(), + ), + ), + ) + .and(this.query.fee.gte(minFeeBroadcast)) + .group(this.query.type) + .order('"timestamp" DESC'); + + return this.findMany(query); + } + + public async findAllByWallet(wallet: any, paginate?: Database.SearchPaginate, orderBy?: Database.SearchOrderBy[]) { + return this.findManyWithCount( + this.query + .select() + .from(this.query) + .where(this.query.sender_public_key.equals(wallet.publicKey)) + .or(this.query.recipient_id.equals(wallet.address)), + paginate, + orderBy, + ); + } + + public async findWithVendorField() { + const selectQuery = this.query + .select() + .from(this.query) + .where(this.query.vendor_field_hex.isNotNull()); + + return this.findMany(selectQuery); + } + + // TODO: Remove with v1 + public async findAll(parameters: Database.SearchParameters) { + if (!parameters.paginate) { + parameters.paginate = { + limit: 100, + offset: 0, + }; + } + const selectQuery = this.query.select().from(this.query); + const params = parameters.parameters; + if (params.length) { + const [customOps, otherOps] = partition( + params, + param => param.operator === Database.SearchOperator.OP_CUSTOM, + ); + + const hasNonCustomOps = otherOps.length > 0; + + const first = otherOps.shift(); + if (first) { + selectQuery.where(this.query[this.propToColumnName(first.field)].equals(first.value)); + for (const op of otherOps) { + selectQuery.and(this.query[this.propToColumnName(op.field)].equals(op.value)); + } + } + + customOps.forEach(o => { + if (o.field === "ownerWallet") { + const wallet = o.value as Database.IWallet; + if (hasNonCustomOps) { + selectQuery.and( + this.query.sender_public_key + .equals(wallet.publicKey) + .or(this.query.recipient_id.equals(wallet.address)), + ); + } else { + selectQuery + .where(this.query.sender_public_key.equals(wallet.publicKey)) + .or(this.query.recipient_id.equals(wallet.address)); + } + } + }); + } + return this.findManyWithCount(selectQuery, parameters.paginate, parameters.orderBy); + } + + public async search(parameters: Database.SearchParameters) { + if (!parameters.paginate) { + parameters.paginate = { + limit: 100, + offset: 0, + }; + } + const selectQuery = this.query.select().from(this.query); + const params = parameters.parameters; + if (params.length) { + // 'search' doesn't support custom-op 'ownerId' like 'findAll' can + const ops = params.filter(value => value.operator !== Database.SearchOperator.OP_CUSTOM); + + const [participants, rest] = partition(ops, op => + ["sender_public_key", "recipient_id"].includes(this.propToColumnName(op.field)), + ); + + if (participants.length > 0) { + const [first, last] = participants; + selectQuery.where(this.query[this.propToColumnName(first.field)][first.operator](first.value)); + + if (last) { + const usesInOperator = participants.every( + condition => condition.operator === Database.SearchOperator.OP_IN, + ); + if (usesInOperator) { + selectQuery.or(this.query[this.propToColumnName(last.field)][last.operator](last.value)); + } else { + // This search is 1 `senderPublicKey` and 1 `recipientId` + selectQuery.and(this.query[this.propToColumnName(last.field)][last.operator](last.value)); + } + } + } else if (rest.length) { + const first = rest.shift(); + selectQuery.where(this.query[this.propToColumnName(first.field)][first.operator](first.value)); + } + + for (const condition of rest) { + selectQuery.and( + this.query[this.propToColumnName(condition.field)][condition.operator](condition.value), + ); + } + } + return this.findManyWithCount(selectQuery, parameters.paginate, parameters.orderBy); + } } diff --git a/packages/core-database-postgres/src/repositories/wallets.ts b/packages/core-database-postgres/src/repositories/wallets.ts deleted file mode 100644 index a95d4eba19..0000000000 --- a/packages/core-database-postgres/src/repositories/wallets.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Database } from "@arkecosystem/core-interfaces"; -import { Wallet } from "../models"; -import { queries } from "../queries"; -import { Repository } from "./repository"; - -const { wallets: sql } = queries; - -export class WalletsRepository extends Repository implements Database.IWalletsRepository { - /** - * Get all of the wallets from the database. - * @return {Promise} - */ - public async all() { - return this.db.manyOrNone(sql.all); - } - - /** - * Find a wallet by its address. - * @param {String} address - * @return {Promise} - */ - public async findByAddress(address) { - return this.db.oneOrNone(sql.findByAddress, { address }); - } - - /** - * Get the count of wallets that have a negative balance. - * @return {Promise} - */ - public async tallyWithNegativeBalance() { - return this.db.oneOrNone(sql.findNegativeBalances); - } - - /** - * Get the count of wallets that have a negative vote balance. - * @return {Promise} - */ - public async tallyWithNegativeVoteBalance() { - return this.db.oneOrNone(sql.findNegativeVoteBalances); - } - - /** - * Create or update a record matching the attributes, and fill it with values. - * @param {Object} wallet - * @return {Promise} - */ - public async updateOrCreate(wallet) { - const query = `${this.__insertQuery(wallet)} ON CONFLICT(address) DO UPDATE SET ${this.pgp.helpers.sets( - wallet, - this.model.getColumnSet(), - )}`; - - return this.db.none(query); - } - - /** - * Get the model related to this repository. - * @return {Object} - */ - public getModel() { - return new Wallet(this.pgp); - } -} diff --git a/packages/core-database/.gitattributes b/packages/core-database/.gitattributes index 60cc52db63..63f6a5b970 100644 --- a/packages/core-database/.gitattributes +++ b/packages/core-database/.gitattributes @@ -2,10 +2,6 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore -/__tests__ export-ignore -/docs export-ignore /README.md export-ignore diff --git a/packages/core-database/README.md b/packages/core-database/README.md index 92f27b92dc..62ea66abe1 100644 --- a/packages/core-database/README.md +++ b/packages/core-database/README.md @@ -1,12 +1,12 @@ -# Ark Core - Database - Interface +# Persona Core - Database - Interface

- +

## Documentation -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core-database.html). +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/required/core-database.html). ## Security @@ -14,13 +14,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [Brian Faust](https://github.com/faustbrian) -- [Erwann Gentric](https://github.com/air1one) -- [François-Xavier Thoorens](https://github.com/fix) -- [Joshua Noack](https://github.com/supaiku0) -- [Kristjan Košič](https://github.com/kristjank) -- [All Contributors](../../../../contributors) +This project exists thanks to all the people who [contribute](../../../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-database/__tests__/__support__/setup.ts b/packages/core-database/__tests__/__support__/setup.ts deleted file mode 100644 index faba7ee97d..0000000000 --- a/packages/core-database/__tests__/__support__/setup.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { app } from "@arkecosystem/core-container"; -import "@arkecosystem/core-test-utils"; -import { setUpContainer } from "@arkecosystem/core-test-utils/src/helpers/container"; - -export const setUp = async () => { - jest.setTimeout(60000); - - process.env.CORE_SKIP_BLOCKCHAIN = "true"; - - return await setUpContainer({ - exit: "@arkecosystem/core-blockchain", - exclude: [ - "@arkecosystem/core-p2p", - "@arkecosystem/core-transaction-pool", - "@arkecosystem/core-database-postgres", - ], - }); -}; - -export const tearDown = async () => { - await app.tearDown(); -}; diff --git a/packages/core-database/package.json b/packages/core-database/package.json index b12a25970e..ef63d78c77 100644 --- a/packages/core-database/package.json +++ b/packages/core-database/package.json @@ -1,7 +1,7 @@ { "name": "@arkecosystem/core-database", - "description": "Database Interface for Ark Core", - "version": "2.2.1", + "description": "Database Interface for ARK Core", + "version": "2.3.15", "contributors": [ "François-Xavier Thoorens ", "Kristjan Košič ", @@ -14,41 +14,30 @@ "dist" ], "scripts": { - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", "prepublishOnly": "yarn build", "pretest": "bash ../../scripts/pre-test.sh", "compile": "../../node_modules/typescript/bin/tsc", "build": "yarn clean && yarn compile", "build:watch": "yarn clean && yarn compile -w", - "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "test": "cross-env CORE_ENV=test jest --runInBand --forceExit", - "test:coverage": "cross-env CORE_ENV=test jest --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$' --runInBand --forceExit", - "test:debug": "cross-env CORE_ENV=test node --inspect-brk ../../node_modules/.bin/jest --runInBand", - "test:watch": "cross-env CORE_ENV=test jest --runInBand --watch", - "test:watch:all": "cross-env CORE_ENV=test jest --runInBand --watchAll", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" + "clean": "del dist" }, "dependencies": { - "@arkecosystem/core-container": "^2.2.1", - "@arkecosystem/core-interfaces": "^2.2.1", - "@arkecosystem/core-utils": "^2.2.1", - "@arkecosystem/crypto": "^2.2.1", - "@arkecosystem/utils": "^0.2.4", + "@arkecosystem/core-container": "^2.3.15", + "@arkecosystem/core-event-emitter": "^2.3.15", + "@arkecosystem/core-interfaces": "^2.3.15", + "@arkecosystem/core-transactions": "^2.3.15", + "@arkecosystem/core-utils": "^2.3.15", + "@arkecosystem/crypto": "^2.3.15", + "@arkecosystem/utils": "^0.3.0", "lodash.clonedeep": "^4.5.0", "lodash.compact": "^3.0.1", "lodash.uniq": "^4.5.0", "pluralize": "^7.0.0" }, "devDependencies": { - "@arkecosystem/core-test-utils": "^2.2.1", - "@types/lodash.clonedeep": "^4.5.4", - "@types/lodash.compact": "^3.0.4", - "@types/lodash.uniq": "^4.5.4", + "@types/lodash.clonedeep": "^4.5.6", + "@types/lodash.compact": "^3.0.6", + "@types/lodash.uniq": "^4.5.6", "@types/pluralize": "^0.0.29" }, "publishConfig": { @@ -56,8 +45,5 @@ }, "engines": { "node": ">=10.x" - }, - "jest": { - "preset": "../../jest-preset.json" } } diff --git a/packages/core-database/src/database-service-factory.ts b/packages/core-database/src/database-service-factory.ts index 483c3c85b8..492d0a1bd2 100644 --- a/packages/core-database/src/database-service-factory.ts +++ b/packages/core-database/src/database-service-factory.ts @@ -1,22 +1,25 @@ import { Database } from "@arkecosystem/core-interfaces"; import { DatabaseService } from "./database-service"; -import { DelegatesRepository } from "./repositories/delegates"; -import { WalletsRepository } from "./repositories/wallets"; +import { BlocksBusinessRepository } from "./repositories/blocks-business-repository"; +import { DelegatesBusinessRepository } from "./repositories/delegates-business-repository"; +import { TransactionsBusinessRepository } from "./repositories/transactions-business-repository"; +import { WalletsBusinessRepository } from "./repositories/wallets-business-repository"; -// Allow extenders of core-database to provide, optionally, a IWalletManager concrete in addition to a IDatabaseConnection, but keep the business repos common +// Allow extenders of core-database to provide, optionally, a IWalletManager concrete in addition to a IConnection, but keep the business repos common export const databaseServiceFactory = async ( - opts: any, + opts: Record, walletManager: Database.IWalletManager, - connection: Database.IDatabaseConnection, + connection: Database.IConnection, ): Promise => { let databaseService: DatabaseService; databaseService = new DatabaseService( opts, connection, walletManager, - // @ts-ignore - new WalletsRepository(() => databaseService), - new DelegatesRepository(() => databaseService), + new WalletsBusinessRepository(() => databaseService), + new DelegatesBusinessRepository(() => databaseService), + new TransactionsBusinessRepository(() => databaseService), + new BlocksBusinessRepository(() => databaseService), ); await databaseService.init(); return databaseService; diff --git a/packages/core-database/src/database-service.ts b/packages/core-database/src/database-service.ts index 7c9e327a22..372d75192f 100644 --- a/packages/core-database/src/database-service.ts +++ b/packages/core-database/src/database-service.ts @@ -1,18 +1,15 @@ import { app } from "@arkecosystem/core-container"; -import { Blockchain, Database, EventEmitter, Logger } from "@arkecosystem/core-interfaces"; +import { ApplicationEvents } from "@arkecosystem/core-event-emitter"; +import { Blockchain, Database, EventEmitter, Logger, Shared } from "@arkecosystem/core-interfaces"; +import { TransactionHandlerRegistry } from "@arkecosystem/core-transactions"; import { roundCalculator } from "@arkecosystem/core-utils"; -import { Bignum, constants, crypto as arkCrypto, models } from "@arkecosystem/crypto"; +import { Bignum, configManager, crypto, HashAlgorithms, models, Transaction } from "@arkecosystem/crypto"; import assert from "assert"; -import crypto from "crypto"; -import cloneDeep from "lodash/cloneDeep"; -import pluralize from "pluralize"; -import { WalletManager } from "./wallet-manager"; -const { Block, Transaction } = models; -const { TransactionTypes } = constants; +const { Block } = models; export class DatabaseService implements Database.IDatabaseService { - public connection: Database.IDatabaseConnection; + public connection: Database.IConnection; public walletManager: Database.IWalletManager; public logger = app.resolvePlugin("logger"); public emitter = app.resolvePlugin("event-emitter"); @@ -20,33 +17,51 @@ export class DatabaseService implements Database.IDatabaseService { public options: any; public wallets: Database.IWalletsBusinessRepository; public delegates: Database.IDelegatesBusinessRepository; - public blocksInCurrentRound: any[] = null; + public blocksBusinessRepository: Database.IBlocksBusinessRepository; + public transactionsBusinessRepository: Database.ITransactionsBusinessRepository; + public blocksInCurrentRound: models.Block[] = null; public stateStarted: boolean = false; public restoredDatabaseIntegrity: boolean = false; - public forgingDelegates: any[] = null; + public forgingDelegates: Database.IDelegateWallet[] = null; public cache: Map = new Map(); - private spvFinished: boolean; constructor( options: any, - connection: Database.IDatabaseConnection, + connection: Database.IConnection, walletManager: Database.IWalletManager, walletsBusinessRepository: Database.IWalletsBusinessRepository, delegatesBusinessRepository: Database.IDelegatesBusinessRepository, + transactionsBusinessRepository: Database.ITransactionsBusinessRepository, + blocksBusinessRepository: Database.IBlocksBusinessRepository, ) { this.connection = connection; this.walletManager = walletManager; this.options = options; this.wallets = walletsBusinessRepository; this.delegates = delegatesBusinessRepository; + this.blocksBusinessRepository = blocksBusinessRepository; + this.transactionsBusinessRepository = transactionsBusinessRepository; this.registerListeners(); } - public async init() { + public async init(): Promise { await this.loadBlocksFromCurrentRound(); } + public async restoreCurrentRound(height: number): Promise { + await this.initializeActiveDelegates(height); + await this.applyRound(height); + } + + public async reset(): Promise { + await this.connection.blocksRepository.truncate(); + await this.connection.roundsRepository.truncate(); + await this.connection.transactionsRepository.truncate(); + + await this.saveBlock(new Block(configManager.get("genesisBlock"))); + } + public async applyBlock(block: models.Block) { this.walletManager.applyBlock(block); @@ -62,24 +77,26 @@ export class DatabaseService implements Database.IDatabaseService { public async applyRound(height: number) { const nextHeight = height === 1 ? 1 : height + 1; - const maxDelegates = this.config.getMilestone(nextHeight).activeDelegates; - - if (nextHeight % maxDelegates === 1) { - const round = Math.floor((nextHeight - 1) / maxDelegates) + 1; + if (roundCalculator.isNewRound(nextHeight)) { + const roundInfo = roundCalculator.calculateRound(nextHeight); + const { round } = roundInfo; if ( + nextHeight === 1 || !this.forgingDelegates || this.forgingDelegates.length === 0 || - (this.forgingDelegates.length && this.forgingDelegates[0].round !== round) + this.forgingDelegates[0].round !== round ) { - this.logger.info(`Starting Round ${round.toLocaleString()} :dove_of_peace:`); + this.logger.info(`Starting Round ${roundInfo.round.toLocaleString()}`); try { - this.updateDelegateStats(this.forgingDelegates); - await this.saveWallets(false); // save only modified wallets during the last round - const delegates = this.walletManager.loadActiveDelegateList(maxDelegates, nextHeight); // get active delegate list from in-memory wallet manager - await this.saveRound(delegates); // save next round delegate list non-blocking - this.forgingDelegates = await this.getActiveDelegates(nextHeight, delegates); // generate the new active delegates list + if (nextHeight > 1) { + this.updateDelegateStats(this.forgingDelegates); + } + + const delegates = this.walletManager.loadActiveDelegateList(roundInfo); + await this.saveRound(delegates); + await this.setForgingDelegatesOfRound(roundInfo, delegates); this.blocksInCurrentRound.length = 0; this.emitter.emit("round.applied"); @@ -91,22 +108,22 @@ export class DatabaseService implements Database.IDatabaseService { } else { this.logger.warn( // tslint:disable-next-line:max-line-length - `Round ${round.toLocaleString()} has already been applied. This should happen only if you are a forger. :warning:`, + `Round ${round.toLocaleString()} has already been applied. This should happen only if you are a forger.`, ); } } } - public async buildWallets(height: number): Promise { + public async buildWallets(): Promise { this.walletManager.reset(); try { - const success = await this.connection.buildWallets(height); - this.spvFinished = true; - return success; + const result = await this.connection.buildWallets(); + return result; } catch (e) { this.logger.error(e.stack); } + return false; } @@ -130,28 +147,24 @@ export class DatabaseService implements Database.IDatabaseService { this.connection.enqueueDeleteRound(height); } - public enqueueSaveBlock(block: models.Block) { - this.connection.enqueueSaveBlock(block); - } - - public async getActiveDelegates(height: number, delegates?: any[]) { - const maxDelegates = this.config.getMilestone(height).activeDelegates; - const round = Math.floor((height - 1) / maxDelegates) + 1; - + public async getActiveDelegates( + roundInfo: Shared.IRoundInfo, + delegates?: Database.IDelegateWallet[], + ): Promise { + const { round } = roundInfo; if (this.forgingDelegates && this.forgingDelegates.length && this.forgingDelegates[0].round === round) { return this.forgingDelegates; } // When called during applyRound we already know the delegates, so we don't have to query the database. if (!delegates || delegates.length === 0) { - delegates = await this.connection.roundsRepository.findById(round); + delegates = ((await this.connection.roundsRepository.findById( + round, + )) as unknown) as Database.IDelegateWallet[]; } const seedSource = round.toString(); - let currentSeed = crypto - .createHash("sha256") - .update(seedSource, "utf8") - .digest(); + let currentSeed = HashAlgorithms.sha256(seedSource); for (let i = 0, delCount = delegates.length; i < delCount; i++) { for (let x = 0; x < 4 && i < delCount; i++, x++) { @@ -160,23 +173,21 @@ export class DatabaseService implements Database.IDatabaseService { delegates[newIndex] = delegates[i]; delegates[i] = b; } - currentSeed = crypto - .createHash("sha256") - .update(currentSeed) - .digest(); + currentSeed = HashAlgorithms.sha256(currentSeed); } - this.forgingDelegates = delegates.map(delegate => { + const forgingDelegates = delegates.map(delegate => { delegate.round = +delegate.round; + delegate.username = this.walletManager.findByPublicKey(delegate.publicKey).username; return delegate; }); - return this.forgingDelegates; + return forgingDelegates; } public async getBlock(id: string) { // TODO: caching the last 1000 blocks, in combination with `saveBlock` could help to optimise - const block = await this.connection.blocksRepository.findById(id); + const block: models.IBlockData = await this.connection.blocksRepository.findById(id); if (!block) { return null; @@ -184,7 +195,7 @@ export class DatabaseService implements Database.IDatabaseService { const transactions = await this.connection.transactionsRepository.findByBlockId(block.id); - block.transactions = transactions.map(({ serialized }) => Transaction.deserialize(serialized.toString("hex"))); + block.transactions = transactions.map(({ serialized, id }) => Transaction.fromBytesUnsafe(serialized, id).data); return new Block(block); } @@ -252,19 +263,20 @@ export class DatabaseService implements Database.IDatabaseService { } } - const heightsToGetFromDB = Object.keys(toGetFromDB); + const heightsToGetFromDB = Object.keys(toGetFromDB).map(height => +height); if (heightsToGetFromDB.length > 0) { - for (const blockFromDB of await this.connection.blocksRepository.findByHeight(heightsToGetFromDB)) { - const h = blockFromDB.height; - const i = toGetFromDB[h]; - blocks[i] = blockFromDB; + const blocksByHeights = await this.connection.blocksRepository.findByHeights(heightsToGetFromDB); + + for (const blockFromDB of blocksByHeights) { + const index = toGetFromDB[blockFromDB.height]; + blocks[index] = blockFromDB; } } return blocks; } - public async getBlocksForRound(round?: number) { + public async getBlocksForRound(roundInfo?: Shared.IRoundInfo) { let lastBlock; if (app.has("state")) { lastBlock = app.resolve("state").getLastBlock(); @@ -276,15 +288,14 @@ export class DatabaseService implements Database.IDatabaseService { return []; } - let height = +lastBlock.data.height; - if (!round) { - round = roundCalculator.calculateRound(height).round; + const height = +lastBlock.data.height; + if (!roundInfo) { + roundInfo = roundCalculator.calculateRound(height); } - const maxDelegates = this.config.getMilestone(height).activeDelegates; - height = round * maxDelegates + 1; + const { maxDelegates, roundHeight } = roundInfo; - const blocks = await this.getBlocks(height - maxDelegates, maxDelegates); + const blocks = await this.getBlocks(roundHeight, maxDelegates); return blocks.map(b => new Block(b)); } @@ -306,12 +317,12 @@ export class DatabaseService implements Database.IDatabaseService { const transactions = await this.connection.transactionsRepository.latestByBlock(block.id); - block.transactions = transactions.map(({ serialized }) => Transaction.deserialize(serialized.toString("hex"))); + block.transactions = transactions.map(({ serialized, id }) => Transaction.fromBytesUnsafe(serialized, id).data); return new Block(block); } - public async getCommonBlocks(ids: string[]) { + public async getCommonBlocks(ids: string[]): Promise { const state = app.resolve("state"); let commonBlocks = state.getCommonBlocks(ids); if (commonBlocks.length < ids.length) { @@ -361,7 +372,7 @@ export class DatabaseService implements Database.IDatabaseService { let transactions = await this.connection.transactionsRepository.latestByBlocks(ids); transactions = transactions.map(tx => { - const data = Transaction.deserialize(tx.serialized.toString("hex")); + const { data } = Transaction.fromBytesUnsafe(tx.serialized, tx.id); data.blockId = tx.blockId; return data; }); @@ -383,13 +394,16 @@ export class DatabaseService implements Database.IDatabaseService { } public async revertRound(height: number) { - const { round, nextRound, maxDelegates } = roundCalculator.calculateRound(height); + const roundInfo = roundCalculator.calculateRound(height); + const { round, nextRound, maxDelegates } = roundInfo; if (nextRound === round + 1 && height >= maxDelegates) { - this.logger.info(`Back to previous round: ${round.toLocaleString()} :back:`); + this.logger.info(`Back to previous round: ${round.toLocaleString()}`); + + this.blocksInCurrentRound = await this.getBlocksForRound(roundInfo); - const delegates = await this.calcPreviousActiveDelegates(round); - this.forgingDelegates = await this.getActiveDelegates(height, delegates); + const delegates = await this.calcPreviousActiveDelegates(roundInfo, this.blocksInCurrentRound); + await this.setForgingDelegatesOfRound(roundInfo, delegates); await this.deleteRound(nextRound); } @@ -399,7 +413,7 @@ export class DatabaseService implements Database.IDatabaseService { await this.connection.saveBlock(block); } - public async saveRound(activeDelegates: any[]) { + public async saveRound(activeDelegates: Database.IDelegateWallet[]) { this.logger.info(`Saving round ${activeDelegates[0].round.toLocaleString()}`); await this.connection.roundsRepository.insert(activeDelegates); @@ -407,30 +421,15 @@ export class DatabaseService implements Database.IDatabaseService { this.emitter.emit("round.created", activeDelegates); } - public async saveWallets(force: boolean) { - const wallets = this.walletManager - .allByPublicKey() - .filter(wallet => wallet.publicKey && (force || wallet.dirty)); - - // Remove dirty flags first to not save all dirty wallets in the exit handler - // when called during a force insert right after SPV. - this.walletManager.clear(); - - await this.connection.saveWallets(wallets, force); - - this.logger.info(`${wallets.length} modified ${pluralize("wallet", wallets.length)} committed to database`); - - this.emitter.emit("wallet.saved", wallets.length); - - // NOTE: commented out as more use cases to be taken care of - // this.walletManager.purgeEmptyNonDelegates() - } - - public updateDelegateStats(delegates: any[]): void { + public updateDelegateStats(delegates: Database.IDelegateWallet[]): void { if (!delegates || !this.blocksInCurrentRound) { return; } + if (this.blocksInCurrentRound.length === 1 && this.blocksInCurrentRound[0].data.height === 1) { + return; + } + this.logger.debug("Updating delegate statistics"); try { @@ -441,13 +440,7 @@ export class DatabaseService implements Database.IDatabaseService { const wallet = this.walletManager.findByPublicKey(delegate.publicKey); if (producedBlocks.length === 0) { - wallet.missedBlocks++; - this.logger.debug( - `Delegate ${wallet.username} (${wallet.publicKey}) just missed a block. Total: ${ - wallet.missedBlocks - }`, - ); - wallet.dirty = true; + this.logger.debug(`Delegate ${wallet.username} (${wallet.publicKey}) just missed a block.`); this.emitter.emit("forger.missing", { delegate: wallet, }); @@ -513,10 +506,11 @@ export class DatabaseService implements Database.IDatabaseService { }; } - public async verifyTransaction(transaction: models.Transaction) { - const senderId = arkCrypto.getAddress(transaction.data.senderPublicKey, this.config.get("network.pubKeyHash")); + public async verifyTransaction(transaction: Transaction): Promise { + const senderId = crypto.getAddress(transaction.data.senderPublicKey, this.config.get("network.pubKeyHash")); const sender = this.walletManager.findByAddress(senderId); // should exist + const transactionHandler = TransactionHandlerRegistry.get(transaction.type); if (!sender.publicKey) { sender.publicKey = transaction.data.senderPublicKey; @@ -525,64 +519,66 @@ export class DatabaseService implements Database.IDatabaseService { const dbTransaction = await this.getTransaction(transaction.data.id); - return sender.canApply(transaction.data, []) && !dbTransaction; + try { + return transactionHandler.canBeApplied(transaction, sender) && !dbTransaction; + } catch { + return false; + } } - private async calcPreviousActiveDelegates(round: number) { - // TODO: cache the blocks of the last X rounds - this.blocksInCurrentRound = await this.getBlocksForRound(round); + private async initializeActiveDelegates(height: number): Promise { + this.forgingDelegates = null; - // Create temp wallet manager from all delegates - const tempWalletManager = new WalletManager(); - tempWalletManager.index(cloneDeep(this.walletManager.allByUsername())); + const roundInfo = roundCalculator.calculateRound(height); + const delegates = await this.calcPreviousActiveDelegates(roundInfo); + await this.setForgingDelegatesOfRound(roundInfo, delegates); + } + + private async setForgingDelegatesOfRound( + roundInfo: Shared.IRoundInfo, + delegates?: Database.IDelegateWallet[], + ): Promise { + const activeDelegates = await this.getActiveDelegates(roundInfo, delegates); + this.forgingDelegates = activeDelegates; + } + + private async calcPreviousActiveDelegates( + roundInfo: Shared.IRoundInfo, + blocks?: models.Block[], + ): Promise { + blocks = blocks || (await this.getBlocksForRound(roundInfo)); + + const tempWalletManager = this.walletManager.cloneDelegateWallets(); // Revert all blocks in reverse order + const index = blocks.length - 1; let height = 0; - for (let i = this.blocksInCurrentRound.length - 1; i >= 0; i--) { - tempWalletManager.revertBlock(this.blocksInCurrentRound[i]); - height = this.blocksInCurrentRound[i].data.height; - } + for (let i = index; i >= 0; i--) { + height = blocks[i].data.height; + if (height === 1) { + break; + } - // The first round has no active delegates - if (height === 1) { - return []; + tempWalletManager.revertBlock(blocks[i]); } - // Assert that the height is the beginning of a round. - const { maxDelegates } = roundCalculator.calculateRound(height); - assert(height > 1 && height % maxDelegates === 1); - // Now retrieve the active delegate list from the temporary wallet manager. - return tempWalletManager.loadActiveDelegateList(maxDelegates, height); + return tempWalletManager.loadActiveDelegateList(roundInfo); } - private emitTransactionEvents(transaction) { + private emitTransactionEvents(transaction: Transaction): void { this.emitter.emit("transaction.applied", transaction.data); - if (transaction.type === TransactionTypes.DelegateRegistration) { - this.emitter.emit("delegate.registered", transaction.data); - } - - if (transaction.type === TransactionTypes.DelegateResignation) { - this.emitter.emit("delegate.resigned", transaction.data); - } - - if (transaction.type === TransactionTypes.Vote) { - const vote = transaction.asset.votes[0]; - - this.emitter.emit(vote.startsWith("+") ? "wallet.vote" : "wallet.unvote", { - delegate: vote, - transaction: transaction.data, - }); - } + const handler = TransactionHandlerRegistry.get(transaction.type); + handler.emitEvents(transaction, this.emitter); } - private registerListeners() { - this.emitter.on("state:started", () => { + private registerListeners(): void { + this.emitter.on(ApplicationEvents.StateStarted, () => { this.stateStarted = true; }); - this.emitter.on("wallet.created.cold", async coldWallet => { + this.emitter.on(ApplicationEvents.WalletColdCreated, async coldWallet => { try { const wallet = await this.connection.walletsRepository.findByAddress(coldWallet.address); @@ -599,12 +595,5 @@ export class DatabaseService implements Database.IDatabaseService { this.logger.error(err); } }); - - this.emitter.once("shutdown", async () => { - if (!this.spvFinished) { - // Prevent dirty wallets to be saved when SPV didn't finish - this.walletManager.clear(); - } - }); } } diff --git a/packages/core-database/src/factory.ts b/packages/core-database/src/factory.ts new file mode 100644 index 0000000000..2f1035632b --- /dev/null +++ b/packages/core-database/src/factory.ts @@ -0,0 +1,7 @@ +import { Database } from "@arkecosystem/core-interfaces"; + +export class ConnectionFactory { + public async make(connection: Database.IConnection): Promise { + return connection.make(); + } +} diff --git a/packages/core-database/src/index.ts b/packages/core-database/src/index.ts index 51340494aa..01183dd37a 100644 --- a/packages/core-database/src/index.ts +++ b/packages/core-database/src/index.ts @@ -1,6 +1,7 @@ export * from "./manager"; export * from "./database-service-factory"; export * from "./wallet-manager"; -export * from "./repositories/delegates"; -export * from "./repositories/wallets"; +export * from "./wallet"; +export * from "./repositories/delegates-business-repository"; +export * from "./repositories/wallets-business-repository"; export * from "./plugin"; diff --git a/packages/core-database/src/manager.ts b/packages/core-database/src/manager.ts index 41721bcd62..c1a7592108 100644 --- a/packages/core-database/src/manager.ts +++ b/packages/core-database/src/manager.ts @@ -1,33 +1,23 @@ import { Database } from "@arkecosystem/core-interfaces"; +import { ConnectionFactory } from "./factory"; -export class DatabaseManager { - public connections: { [key: string]: Database.IDatabaseConnection }; +export class ConnectionManager { + private readonly factory: ConnectionFactory = new ConnectionFactory(); + private readonly connections: Map = new Map< + string, + Database.IConnection + >(); - /** - * Create a new database manager instance. - * @constructor - */ - constructor() { - this.connections = {}; + public connection(name = "default"): Database.IConnection { + return this.connections.get(name); } - /** - * Get a database connection instance. - * @param {String} name - * @return {DatabaseConnection} - */ - public connection(name = "default"): Database.IDatabaseConnection { - return this.connections[name]; - } + public async createConnection( + connection: Database.IConnection, + name = "default", + ): Promise { + this.connections.set(name, await this.factory.make(connection)); - /** - * Make the database connection instance. - * @param {DatabaseConnection} connection - * @param {String} name - * @return {void} - */ - public async makeConnection(connection: Database.IDatabaseConnection, name = "default"): Promise { - this.connections[name] = await connection.make(); return this.connection(name); } } diff --git a/packages/core-database/src/plugin.ts b/packages/core-database/src/plugin.ts index b4083c984e..92404eb258 100644 --- a/packages/core-database/src/plugin.ts +++ b/packages/core-database/src/plugin.ts @@ -1,12 +1,12 @@ import { Container, Logger } from "@arkecosystem/core-interfaces"; -import { DatabaseManager } from "./manager"; +import { ConnectionManager } from "./manager"; export const plugin: Container.PluginDescriptor = { pkg: require("../package.json"), - alias: "databaseManager", + alias: "database-manager", async register(container: Container.IContainer, options) { container.resolvePlugin("logger").info("Starting Database Manager"); - return new DatabaseManager(); + return new ConnectionManager(); }, }; diff --git a/packages/core-database/src/repositories/blocks-business-repository.ts b/packages/core-database/src/repositories/blocks-business-repository.ts new file mode 100644 index 0000000000..06af50e730 --- /dev/null +++ b/packages/core-database/src/repositories/blocks-business-repository.ts @@ -0,0 +1,55 @@ +import { Database } from "@arkecosystem/core-interfaces"; +import { SearchParameterConverter } from "./utils/search-parameter-converter"; + +export class BlocksBusinessRepository implements Database.IBlocksBusinessRepository { + constructor(private databaseServiceProvider: () => Database.IDatabaseService) {} + + /* TODO: Remove with v1 */ + public async findAll(params: Database.IParameters) { + return this.databaseServiceProvider().connection.blocksRepository.findAll(this.parseSearchParams(params)); + } + + public async findAllByGenerator(generatorPublicKey: string, paginate: Database.SearchPaginate) { + return this.findAll({ generatorPublicKey, ...paginate }); + } + + public async findLastByPublicKey(generatorPublicKey: string) { + // we order by height,desc by default + return this.findAll({ generatorPublicKey }); + } + + public async findByHeight(height: number) { + return this.databaseServiceProvider().connection.blocksRepository.findByHeight(height); + } + + public async findById(id: string) { + return this.databaseServiceProvider().connection.blocksRepository.findById(id); + } + + public async findByIdOrHeight(idOrHeight) { + try { + const block = await this.findByHeight(idOrHeight); + + return block || this.findById(idOrHeight); + } catch (error) { + return this.findById(idOrHeight); + } + } + + public async search(params: Database.IParameters) { + return this.databaseServiceProvider().connection.blocksRepository.search(this.parseSearchParams(params)); + } + + private parseSearchParams(params: Database.IParameters): Database.SearchParameters { + const blocksRepository = this.databaseServiceProvider().connection.blocksRepository; + const searchParameters = new SearchParameterConverter(blocksRepository.getModel()).convert(params); + if (!searchParameters.orderBy.length) { + // default order-by + searchParameters.orderBy.push({ + field: "height", + direction: "desc", + }); + } + return searchParameters; + } +} diff --git a/packages/core-database/src/repositories/delegates-business-repository.ts b/packages/core-database/src/repositories/delegates-business-repository.ts new file mode 100644 index 0000000000..7eea55645e --- /dev/null +++ b/packages/core-database/src/repositories/delegates-business-repository.ts @@ -0,0 +1,156 @@ +import { Database } from "@arkecosystem/core-interfaces"; +import { delegateCalculator, hasSomeProperty } from "@arkecosystem/core-utils"; +import filterRows from "./utils/filter-rows"; +import limitRows from "./utils/limit-rows"; +import { sortEntries } from "./utils/sort-entries"; + +type CallbackFunctionVariadicVoidReturn = (...args: any[]) => void; + +export class DelegatesBusinessRepository implements Database.IDelegatesBusinessRepository { + /** + * Create a new delegate repository instance. + * @param databaseServiceProvider + */ + public constructor(private databaseServiceProvider: () => Database.IDatabaseService) {} + + /** + * Get all local delegates. + * @param {Object} params + * @return {Object} + */ + public getLocalDelegates(params: Database.IParameters = {}) { + let delegates = this.databaseServiceProvider().walletManager.allByUsername(); + + const manipulators = { + approval: delegateCalculator.calculateApproval, + forgedTotal: delegateCalculator.calculateForgedTotal, + }; + + if (hasSomeProperty(params, Object.keys(manipulators))) { + delegates = delegates.map(delegate => { + for (const [prop, method] of Object.entries(manipulators)) { + if (params.hasOwnProperty(prop)) { + delegate[prop] = method(delegate); + } + } + + return delegate; + }); + } + + return delegates; + } + + /** + * Find all delegates. + * @param {Object} params + * @return {Object} + */ + public findAll(params: Database.IParameters = {}) { + this.applyOrder(params); + + const delegates = sortEntries(params, this.getLocalDelegates(), ["rate", "asc"]); + + return { + rows: limitRows(delegates, params), + count: delegates.length, + }; + } + + /** + * Search all delegates. + * TODO Search by last block + * @param {Object} [params] + * @param {Number} [params.limit] - Limit the number of results + * @param {Number} [params.offset] - Skip some results + * @param {String} [params.orderBy] - Order of the results + * @param {String} [params.address] - Search by address + * @param {String} [params.publicKey] - Search by publicKey + * @param {String} [params.username] - Search by username + * @param {Array} [params.usernames] - Search by usernames + * @param {Object} [params.approval] - Search by approval + * @param {Number} [params.approval.from] - Search by approval (minimum) + * @param {Number} [params.approval.to] - Search by approval (maximum) + * @param {Object} [params.forgedFees] - Search by forgedFees + * @param {Number} [params.forgedFees.from] - Search by forgedFees (minimum) + * @param {Number} [params.forgedFees.to] - Search by forgedFees (maximum) + * @param {Object} [params.forgedRewards] - Search by forgedRewards + * @param {Number} [params.forgedRewards.from] - Search by forgedRewards (minimum) + * @param {Number} [params.forgedRewards.to] - Search by forgedRewards (maximum) + * @param {Object} [params.forgedTotal] - Search by forgedTotal + * @param {Number} [params.forgedTotal.from] - Search by forgedTotal (minimum) + * @param {Number} [params.forgedTotal.to] - Search by forgedTotal (maximum) + * @param {Object} [params.producedBlocks] - Search by producedBlocks + * @param {Number} [params.producedBlocks.from] - Search by producedBlocks (minimum) + * @param {Number} [params.producedBlocks.to] - Search by producedBlocks (maximum) + * @param {Object} [params.voteBalance] - Search by voteBalance + * @param {Number} [params.voteBalance.from] - Search by voteBalance (minimum) + * @param {Number} [params.voteBalance.to] - Search by voteBalance (maximum) + */ + public search(params: Database.IParameters) { + const query: any = { + exact: ["address", "publicKey"], + like: ["username"], + between: ["approval", "forgedFees", "forgedRewards", "forgedTotal", "producedBlocks", "voteBalance"], + }; + + if (params.usernames) { + if (!params.username) { + params.username = params.usernames; + query.like.shift(); + query.in = ["username"]; + } + delete params.usernames; + } + + this.applyOrder(params); + + let delegates = filterRows(this.getLocalDelegates(params), params, query); + delegates = sortEntries(params, delegates, ["rate", "asc"]); + + return { + rows: limitRows(delegates, params), + count: delegates.length, + }; + } + + /** + * Find a delegate. + * @param {String} id + * @return {Object} + */ + public findById(id) { + return this.getLocalDelegates().find(a => a.address === id || a.publicKey === id || a.username === id); + } + + private applyOrder(params): [CallbackFunctionVariadicVoidReturn | string, string] { + const assignOrder = (params, value) => (params.orderBy = value); + + if (!params.orderBy) { + return assignOrder(params, ["rate", "asc"]); + } + + const orderByMapped = params.orderBy.split(":").map(p => p.toLowerCase()); + + if (orderByMapped.length !== 2 || ["desc", "asc"].includes(orderByMapped[1]) !== true) { + return assignOrder(params, ["rate", "asc"]); + } + + return assignOrder(params, [this.manipulateIteratee(orderByMapped[0]), orderByMapped[1]]); + } + + private manipulateIteratee(iteratee): any { + switch (iteratee) { + case "approval": + return delegateCalculator.calculateApproval; + case "forgedTotal": + return delegateCalculator.calculateForgedTotal; + case "rank": + return "rate"; + case "votes": + return "voteBalance"; + default: + return iteratee; + } + } +} diff --git a/packages/core-database/src/repositories/delegates.ts b/packages/core-database/src/repositories/delegates.ts deleted file mode 100644 index 3c4ea779d3..0000000000 --- a/packages/core-database/src/repositories/delegates.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { Database } from "@arkecosystem/core-interfaces"; -import { delegateCalculator } from "@arkecosystem/core-utils"; -import { orderBy } from "@arkecosystem/utils"; -import limitRows from "./utils/limit-rows"; -import { sortEntries } from "./utils/sort-entries"; - -export class DelegatesRepository implements Database.IDelegatesBusinessRepository { - /** - * Create a new delegate repository instance. - * @param databaseServiceProvider - */ - public constructor(private databaseServiceProvider: () => Database.IDatabaseService) {} - - /** - * Get all local delegates. - */ - public getLocalDelegates() { - // TODO: What's the diff between this and just calling 'allByUsername' - return this.databaseServiceProvider() - .walletManager.allByAddress() - .filter(wallet => !!wallet.username); - } - - /** - * Find all delegates. - * @param {Object} params - * @return {Object} - */ - public findAll(params: Database.IParameters = {}) { - this.applyOrder(params); - - const delegates = sortEntries(params, this.getLocalDelegates(), ["rate", "asc"]); - - return { - rows: limitRows(delegates, params), - count: delegates.length, - }; - } - - /** - * Search all delegates. - * TODO Currently it searches by username only - * @param {Object} [params] - * @param {String} [params.username] - Search by username - */ - public search(params: Database.IParameters) { - let delegates = this.getLocalDelegates(); - if (params.hasOwnProperty("username")) { - delegates = delegates.filter(delegate => delegate.username.indexOf(params.username as string) > -1); - } - - if (params.orderBy) { - const orderByField = params.orderBy.split(":")[0]; - const orderByDirection = params.orderBy.split(":")[1] || "desc"; - - delegates = delegates.sort((a, b) => { - if (orderByDirection === "desc" && a[orderByField] < b[orderByField]) { - return -1; - } - - if (orderByDirection === "asc" && a[orderByField] > b[orderByField]) { - return 1; - } - - return 0; - }); - } - - return { - rows: limitRows(delegates, params), - count: delegates.length, - }; - } - - /** - * Find a delegate. - * @param {String} id - * @return {Object} - */ - public findById(id) { - return this.getLocalDelegates().find(a => a.address === id || a.publicKey === id || a.username === id); - } - - /** - * Find all active delegates at height. - * @param {Number} height - * @return {Array} - */ - public async getActiveAtHeight(height: number) { - const delegates = await this.databaseServiceProvider().getActiveDelegates(height); - - return delegates.map(delegate => { - const wallet = this.databaseServiceProvider().wallets.findById(delegate.publicKey); - - return { - username: wallet.username, - approval: delegateCalculator.calculateApproval(delegate, height), - productivity: delegateCalculator.calculateProductivity(wallet), - }; - }); - } - - private applyOrder(params): string { - const assignOrder = (params, value) => (params.orderBy = value.join(":")); - - if (!params.orderBy) { - return assignOrder(params, ["rate", "asc"]); - } - - const orderByMapped = params.orderBy.split(":").map(p => p.toLowerCase()); - - if (orderByMapped.length !== 2 || ["desc", "asc"].includes(orderByMapped[1]) !== true) { - return assignOrder(params, ["rate", "asc"]); - } - - return assignOrder(params, [this.manipulateIteratee(orderByMapped[0]), orderByMapped[1]]); - } - - private manipulateIteratee(iteratee): any { - switch (iteratee) { - case "votes": - return "voteBalance"; - case "rank": - return "rate"; - case "productivity": - return delegateCalculator.calculateProductivity; - case "approval": - return delegateCalculator.calculateApproval; - default: - return iteratee; - } - } -} diff --git a/packages/core-database/src/repositories/transactions-business-repository.ts b/packages/core-database/src/repositories/transactions-business-repository.ts new file mode 100644 index 0000000000..f8e98af79c --- /dev/null +++ b/packages/core-database/src/repositories/transactions-business-repository.ts @@ -0,0 +1,213 @@ +import { app } from "@arkecosystem/core-container"; +import { Database } from "@arkecosystem/core-interfaces"; +import { constants } from "@arkecosystem/crypto"; +import { SearchParameterConverter } from "./utils/search-parameter-converter"; + +export class TransactionsBusinessRepository implements Database.ITransactionsBusinessRepository { + constructor(private databaseServiceProvider: () => Database.IDatabaseService) {} + + public async allVotesBySender(senderPublicKey: any, parameters: any) { + return this.findAll({ + ...{ senderPublicKey, type: constants.TransactionTypes.Vote }, + ...parameters, + }); + } + + // TODO: Remove with v1 + public async findAll(params: any, sequenceOrder: "asc" | "desc" = "desc") { + try { + const result = await this.databaseServiceProvider().connection.transactionsRepository.findAll( + this.parseSearchParameters(params, sequenceOrder), + ); + result.rows = await this.mapBlocksToTransactions(result.rows); + + return result; + } catch (e) { + return { rows: [], count: 0 }; + } + } + + public async findAllByBlock(blockId: any, parameters: any = {}) { + return this.findAll({ blockId, ...parameters }, "asc"); + } + + public async findAllByRecipient(recipientId: any, parameters: any = {}) { + return this.findAll({ recipientId, ...parameters }); + } + + public async findAllBySender(senderPublicKey: any, parameters: any = {}) { + return this.findAll({ senderPublicKey, ...parameters }); + } + + public async findAllByType(type: any, parameters: any = {}) { + return this.findAll({ type, ...parameters }); + } + + public async findAllByWallet(wallet: any, parameters: any) { + const transactionsRepository = this.databaseServiceProvider().connection.transactionsRepository; + const searchParameters = new SearchParameterConverter(transactionsRepository.getModel()).convert(parameters); + const result = await transactionsRepository.findAllByWallet( + wallet, + searchParameters.paginate, + searchParameters.orderBy, + ); + return this.mapBlocksToTransactions(result.rows); + } + + public async findAllLegacy(parameters: any) { + throw new Error("This is deprecated in v2"); + } + + public async findById(id: string) { + const rows = await this.databaseServiceProvider().connection.transactionsRepository.findById(id); + return (await this.mapBlocksToTransactions(rows))[0]; + } + + public async findByTypeAndId(type: any, id: string) { + const results = await this.findAll({ type, id }); + return results.rows.length ? results.rows[0] : null; + } + + public async findWithVendorField() { + const rows = await this.databaseServiceProvider().connection.transactionsRepository.findWithVendorField(); + return this.mapBlocksToTransactions(rows); + } + + public async getFeeStatistics() { + return this.databaseServiceProvider().connection.transactionsRepository.getFeeStatistics( + app.resolveOptions("transaction-pool").dynamicFees.minFeeBroadcast, + ); + } + + public async search(params: any) { + try { + const result = await this.databaseServiceProvider().connection.transactionsRepository.search( + this.parseSearchParameters(params), + ); + result.rows = await this.mapBlocksToTransactions(result.rows); + + return result; + } catch (e) { + return { rows: [], count: 0 }; + } + } + + private getPublicKeyFromAddress(senderId: string): string { + const walletManager = this.databaseServiceProvider().walletManager; + return walletManager.exists(senderId) ? walletManager.findByAddress(senderId).publicKey : null; + } + + private async mapBlocksToTransactions(rows) { + if (!Array.isArray(rows)) { + rows = rows ? [rows] : []; + } + + // 1. get heights from cache + const missingFromCache = []; + + for (let i = 0; i < rows.length; i++) { + const cachedBlock = this.getCachedBlock(rows[i].blockId); + + if (cachedBlock) { + rows[i].block = cachedBlock; + } else { + missingFromCache.push({ + index: i, + blockId: rows[i].blockId, + }); + } + } + + // 2. get uncached blocks from database + if (missingFromCache.length) { + const blocksRepository = this.databaseServiceProvider().connection.blocksRepository; + const result = await blocksRepository.findByIds(missingFromCache.map(d => d.blockId)); + + for (const missing of missingFromCache) { + const block = result.find(item => item.id === missing.blockId); + if (block) { + rows[missing.index].block = block; + this.cacheBlock(block); + } + } + } + + return rows; + } + + private getCachedBlock(blockId: string): any { + // TODO: Improve caching mechanism. Would be great if we have the caching directly linked to the data-layer repos. + // Such that when you try to fetch a block, it'll transparently check the cache first, before querying db. + const height = this.databaseServiceProvider().cache.get(`heights:${blockId}`); + return height ? { height, id: blockId } : null; + } + + /** + * Stores the height of the block on the cache + * @param {Object} block + * @param {String} block.id + * @param {Number} block.height + */ + private cacheBlock({ id, height }): void { + this.databaseServiceProvider().cache.set(`heights:${id}`, height); + } + + private parseSearchParameters(params: any, sequenceOrder: "asc" | "desc" = "desc"): Database.SearchParameters { + const databaseService = this.databaseServiceProvider(); + + if (params.senderId) { + const senderPublicKey = this.getPublicKeyFromAddress(params.senderId); + + if (!senderPublicKey) { + throw new Error(`Invalid senderId:${params.senderId}`); + } + delete params.senderId; + params.senderPublicKey = senderPublicKey; + } + + if (params.addresses) { + if (!params.recipientId) { + params.recipientId = params.addresses; + } + if (!params.senderPublicKey) { + params.senderPublicKey = params.addresses.map(address => { + return this.getPublicKeyFromAddress(address); + }); + } + + delete params.addresses; + } + + // TODO: supported by 'findAll' but was replaced by 'addresses' in 'search' so remove this when removing v1 code + if (params.ownerId) { + // custom OP here + params.ownerWallet = databaseService.walletManager.findByAddress(params.ownerId); + delete params.ownerId; + } + + const searchParameters = new SearchParameterConverter( + databaseService.connection.transactionsRepository.getModel(), + ).convert(params); + if (!searchParameters.paginate) { + searchParameters.paginate = { + offset: 0, + limit: 100, + }; + } + + if (!searchParameters.orderBy.length) { + // default order-by + searchParameters.orderBy.push({ + field: "timestamp", + direction: "desc", + }); + } + + searchParameters.orderBy.push({ + field: "sequence", + direction: sequenceOrder, + }); + + return searchParameters; + } +} diff --git a/packages/core-database/src/repositories/utils/filter-rows.ts b/packages/core-database/src/repositories/utils/filter-rows.ts index 0cf7962b95..022648a796 100644 --- a/packages/core-database/src/repositories/utils/filter-rows.ts +++ b/packages/core-database/src/repositories/utils/filter-rows.ts @@ -15,6 +15,14 @@ export = (rows: T[], params, filters) => } } + if (filters.hasOwnProperty("like")) { + for (const elem of filters.like) { + if (params[elem] && !item[elem].includes(params[elem])) { + return false; + } + } + } + if (filters.hasOwnProperty("between")) { for (const elem of filters.between) { if (!params[elem]) { diff --git a/packages/core-database/src/repositories/utils/search-parameter-converter.ts b/packages/core-database/src/repositories/utils/search-parameter-converter.ts new file mode 100644 index 0000000000..fb9bc29ef6 --- /dev/null +++ b/packages/core-database/src/repositories/utils/search-parameter-converter.ts @@ -0,0 +1,150 @@ +import { Database } from "@arkecosystem/core-interfaces"; +import snakeCase from "lodash.snakecase"; + +export class SearchParameterConverter implements Database.ISearchParameterConverter { + constructor(private databaseModel: Database.IModel) {} + + public convert(params: Database.IParameters, orderBy?: any, paginate?: any): Database.SearchParameters { + const searchParameters: Database.SearchParameters = { + orderBy: [], + paginate: null, + parameters: [], + }; + + if (!params || !Object.keys(params).length) { + return searchParameters; + } + + // paginate and orderBy can be embedded in the other search params. + if (!paginate && (params.hasOwnProperty("limit") || params.hasOwnProperty("offset"))) { + this.parsePaginate(searchParameters, params); + } else { + this.parsePaginate(searchParameters, paginate); + } + if (!orderBy && params.hasOwnProperty("orderBy")) { + this.parseOrderBy(searchParameters, params.orderBy); + } else { + this.parseOrderBy(searchParameters, orderBy); + } + + this.parseSearchParameters(searchParameters, params); + + return searchParameters; + } + + private parsePaginate(searchParameters: Database.SearchParameters, paginate?: any) { + if (paginate) { + searchParameters.paginate = { + limit: Number.isInteger(paginate.limit) ? paginate.limit : 100, + offset: Number.isInteger(paginate.offset) && +paginate.offset > 0 ? paginate.offset : 0, + }; + } + } + + private parseOrderBy(searchParameters: Database.SearchParameters, orderBy?: any) { + if (orderBy && typeof orderBy === "string") { + const fieldDirection = orderBy.split(":").map(o => o.toLowerCase()); + if (fieldDirection.length === 2 && (fieldDirection[1] === "asc" || fieldDirection[1] === "desc")) { + searchParameters.orderBy.push({ + field: snakeCase(fieldDirection[0]), + direction: fieldDirection[1] as "asc" | "desc", + }); + } + } + } + + private parseSearchParameters(searchParameters: Database.SearchParameters, params: any) { + const searchableFields = this.databaseModel.getSearchableFields(); + const mapByFieldName = searchableFields.reduce((p, c) => { + const map = {}; + map[c.fieldName] = c; + return Object.assign(map, p); + }, {}); + /* + orderBy, limit and offset are parsed earlier. + page, pagination are added automatically by hapi-pagination + */ + Object.keys(params) + .filter(value => !["orderBy", "limit", "offset", "page", "pagination"].includes(value)) + .forEach(fieldName => { + const fieldDescriptor = mapByFieldName[fieldName] as Database.SearchableField; + + /* null op means that the business repo doesn't know how to categorize what to do w/ with this field so + let the repo layer decide how it will handle querying this field + i.e Transactions repo, when parameters contains 'ownerId', some extra logic is done. + */ + if (!fieldDescriptor) { + searchParameters.parameters.push({ + field: fieldName, + operator: Database.SearchOperator.OP_CUSTOM, + value: params[fieldName], + }); + return; + } + + if (fieldDescriptor.supportedOperators.includes(Database.SearchOperator.OP_LIKE)) { + searchParameters.parameters.push({ + field: fieldName, + operator: Database.SearchOperator.OP_LIKE, + value: `%${params[fieldName]}%`, + }); + return; + } + + // 'between' + if ( + fieldDescriptor.supportedOperators.includes(Database.SearchOperator.OP_GTE) || + fieldDescriptor.supportedOperators.includes(Database.SearchOperator.OP_LTE) + ) { + // check if we have 'to' & 'from', if not, default to OP_EQ + if (!params[fieldName].hasOwnProperty("from") && !params[fieldName].hasOwnProperty("to")) { + searchParameters.parameters.push({ + field: fieldName, + operator: Database.SearchOperator.OP_EQ, + value: params[fieldName], + }); + return; + } else { + if (params[fieldName].hasOwnProperty("from")) { + searchParameters.parameters.push({ + field: fieldName, + operator: Database.SearchOperator.OP_GTE, + value: params[fieldName].from, + }); + } + if (params[fieldName].hasOwnProperty("to")) { + searchParameters.parameters.push({ + field: fieldName, + operator: Database.SearchOperator.OP_LTE, + value: params[fieldName].to, + }); + } + return; + } + } + + // If we support 'IN', then the value must be an array(of values) + if ( + fieldDescriptor.supportedOperators.includes(Database.SearchOperator.OP_IN) && + Array.isArray(params[fieldName]) + ) { + searchParameters.parameters.push({ + field: fieldName, + operator: Database.SearchOperator.OP_IN, + value: params[fieldName], + }); + return; + } + + // if the field supports EQ, then ignore any others. + if (fieldDescriptor.supportedOperators.includes(Database.SearchOperator.OP_EQ)) { + searchParameters.parameters.push({ + field: fieldName, + operator: Database.SearchOperator.OP_EQ, + value: params[fieldName], + }); + return; + } + }); + } +} diff --git a/packages/core-database/src/repositories/utils/sort-entries.ts b/packages/core-database/src/repositories/utils/sort-entries.ts index 1e8a31188d..457c0ccadf 100644 --- a/packages/core-database/src/repositories/utils/sort-entries.ts +++ b/packages/core-database/src/repositories/utils/sort-entries.ts @@ -2,7 +2,7 @@ import { Database } from "@arkecosystem/core-interfaces"; import { orderBy } from "@arkecosystem/utils"; export function sortEntries(params: Database.IParameters, entries: any[], defaultValue) { - const [iteratee, order] = params.orderBy ? params.orderBy.split(":") : defaultValue; + const [iteratee, order] = params.orderBy ? params.orderBy : defaultValue; if (["balance", "voteBalance"].includes(iteratee)) { return Object.values(entries).sort((a: any, b: any) => { diff --git a/packages/core-database/src/repositories/wallets.ts b/packages/core-database/src/repositories/wallets-business-repository.ts similarity index 79% rename from packages/core-database/src/repositories/wallets.ts rename to packages/core-database/src/repositories/wallets-business-repository.ts index 30ff0af0fd..2d858f60c6 100644 --- a/packages/core-database/src/repositories/wallets.ts +++ b/packages/core-database/src/repositories/wallets-business-repository.ts @@ -1,13 +1,12 @@ import { Database } from "@arkecosystem/core-interfaces"; -import { orderBy } from "@arkecosystem/utils"; import filterRows from "./utils/filter-rows"; import limitRows from "./utils/limit-rows"; import { sortEntries } from "./utils/sort-entries"; -export class WalletsRepository implements Database.IWalletsBusinessRepository { +export class WalletsBusinessRepository implements Database.IWalletsBusinessRepository { /** * Create a new wallet repository instance. - * @param {DatabaseConnection} databaseService + * @param databaseServiceProvider */ public constructor(private databaseServiceProvider: () => Database.IDatabaseService) {} @@ -25,6 +24,8 @@ export class WalletsRepository implements Database.IWalletsBusinessRepository { * @return {Object} */ public findAll(params: Database.IParameters = {}) { + this.applyOrder(params); + const wallets = sortEntries(params, this.all(), ["rate", "asc"]); return { @@ -40,6 +41,8 @@ export class WalletsRepository implements Database.IWalletsBusinessRepository { * @return {Object} */ public findAllByVote(publicKey: string, params: Database.IParameters = {}) { + this.applyOrder(params); + const wallets = this.all().filter(wallet => wallet.vote === publicKey); return { @@ -66,6 +69,8 @@ export class WalletsRepository implements Database.IWalletsBusinessRepository { * Find all wallets sorted by balance. */ public top(params: Database.IParameters = {}) { + this.applyOrder(params); + const wallets = sortEntries(params, this.all(), ["balance", "desc"]); return { @@ -79,7 +84,7 @@ export class WalletsRepository implements Database.IWalletsBusinessRepository { * @param {Object} [params] * @param {Number} [params.limit] - Limit the number of results * @param {Number} [params.offset] - Skip some results - * @param {Array} [params.orderBy] - Order of the results + * @param {String} [params.orderBy] - Order of the results * @param {String} [params.address] - Search by address * @param {Array} [params.addresses] - Search by several addresses * @param {String} [params.publicKey] - Search by publicKey @@ -110,11 +115,30 @@ export class WalletsRepository implements Database.IWalletsBusinessRepository { delete params.addresses; } - const wallets = filterRows(this.all(), params, query); + this.applyOrder(params); + + let wallets = filterRows(this.all(), params, query); + wallets = sortEntries(params, wallets, ["balance", "desc"]); return { rows: limitRows(wallets, params), count: wallets.length, }; } + + private applyOrder(params): [string, string] { + const assignOrder = (params, value) => (params.orderBy = value); + + if (!params.orderBy) { + return assignOrder(params, ["balance", "desc"]); + } + + const orderByMapped = params.orderBy.split(":").map(p => p.toLowerCase()); + + if (orderByMapped.length !== 2 || ["desc", "asc"].includes(orderByMapped[1]) !== true) { + return assignOrder(params, ["balance", "desc"]); + } + + return assignOrder(params, orderByMapped); + } } diff --git a/packages/core-database/src/wallet-manager.ts b/packages/core-database/src/wallet-manager.ts index e80124b15e..e4609157d4 100644 --- a/packages/core-database/src/wallet-manager.ts +++ b/packages/core-database/src/wallet-manager.ts @@ -1,38 +1,47 @@ import { app } from "@arkecosystem/core-container"; -import { Database, Logger } from "@arkecosystem/core-interfaces"; +import { Database, Logger, Shared } from "@arkecosystem/core-interfaces"; +import { TransactionHandlerRegistry } from "@arkecosystem/core-transactions"; import { roundCalculator } from "@arkecosystem/core-utils"; -import { Bignum, constants, crypto, formatSatoshi, isException, models } from "@arkecosystem/crypto"; +import { + Bignum, + constants, + crypto, + formatSatoshi, + isException, + ITransactionData, + models, + Transaction, +} from "@arkecosystem/crypto"; +import cloneDeep from "lodash.clonedeep"; import pluralize from "pluralize"; +import { Wallet } from "./wallet"; -const { Wallet } = models; const { TransactionTypes } = constants; export class WalletManager implements Database.IWalletManager { public logger = app.resolvePlugin("logger"); public config = app.getConfig(); - public networkId: number; - public byAddress: { [key: string]: any }; - public byPublicKey: { [key: string]: any }; - public byUsername: { [key: string]: any }; + public byAddress: { [key: string]: Wallet }; + public byPublicKey: { [key: string]: Wallet }; + public byUsername: { [key: string]: Wallet }; /** * Create a new wallet manager instance. * @constructor */ constructor() { - this.networkId = this.config ? this.config.get("network.pubKeyHash") : 0x17; this.reset(); } - public allByAddress(): models.Wallet[] { + public allByAddress(): Wallet[] { return Object.values(this.byAddress); } /** * Get all wallets by publicKey. */ - public allByPublicKey(): models.Wallet[] { + public allByPublicKey(): Wallet[] { return Object.values(this.byPublicKey); } @@ -40,15 +49,15 @@ export class WalletManager implements Database.IWalletManager { * Get all wallets by username. * @return {Array} */ - public allByUsername(): models.Wallet[] { + public allByUsername(): Wallet[] { return Object.values(this.byUsername); } /** * Find a wallet by the given address. */ - public findByAddress(address: string): models.Wallet { - if (!this.byAddress[address]) { + public findByAddress(address: string): Wallet { + if (address && !this.byAddress[address]) { this.byAddress[address] = new Wallet(address); } @@ -57,14 +66,15 @@ export class WalletManager implements Database.IWalletManager { /** * Checks if wallet exits in wallet manager - * @param {String} key can be publicKey or address of wallet + * @param {String} addressOrPublicKey + * @return {boolean} */ - public exists(key: string) { - if (this.byPublicKey[key]) { + public exists(addressOrPublicKey: string): boolean { + if (this.byPublicKey[addressOrPublicKey]) { return true; } - return !!this.byAddress[key]; + return !!this.byAddress[addressOrPublicKey]; } /** @@ -72,9 +82,9 @@ export class WalletManager implements Database.IWalletManager { * @param {String} publicKey * @return {Wallet} */ - public findByPublicKey(publicKey: string): models.Wallet { - if (!this.byPublicKey[publicKey]) { - const address = crypto.getAddress(publicKey, this.networkId); + public findByPublicKey(publicKey: string): Wallet { + if (publicKey && !this.byPublicKey[publicKey]) { + const address = crypto.getAddress(publicKey); const wallet = this.findByAddress(address); wallet.publicKey = publicKey; @@ -89,7 +99,7 @@ export class WalletManager implements Database.IWalletManager { * @param {String} username * @return {Wallet} */ - public findByUsername(username: string): models.Wallet { + public findByUsername(username: string): Wallet { return this.byUsername[username]; } @@ -98,8 +108,10 @@ export class WalletManager implements Database.IWalletManager { * @param {String} address * @param {Wallet} wallet */ - public setByAddress(address, wallet) { - this.byAddress[address] = wallet; + public setByAddress(address: string, wallet: Wallet): void { + if (address && wallet) { + this.byAddress[address] = wallet; + } } /** @@ -107,8 +119,10 @@ export class WalletManager implements Database.IWalletManager { * @param {String} publicKey * @param {Wallet} wallet */ - public setByPublicKey(publicKey, wallet) { - this.byPublicKey[publicKey] = wallet; + public setByPublicKey(publicKey: string, wallet: Wallet): void { + if (publicKey && wallet) { + this.byPublicKey[publicKey] = wallet; + } } /** @@ -116,15 +130,17 @@ export class WalletManager implements Database.IWalletManager { * @param {String} username * @param {Wallet} wallet */ - public setByUsername(username, wallet) { - this.byUsername[username] = wallet; + public setByUsername(username: string, wallet: Wallet): void { + if (username && wallet) { + this.byUsername[username] = wallet; + } } /** * Remove wallet by address. * @param {String} address */ - public forgetByAddress(address) { + public forgetByAddress(address: string): void { delete this.byAddress[address]; } @@ -132,7 +148,7 @@ export class WalletManager implements Database.IWalletManager { * Remove wallet by publicKey. * @param {String} publicKey */ - public forgetByPublicKey(publicKey) { + public forgetByPublicKey(publicKey: string): void { delete this.byPublicKey[publicKey]; } @@ -140,7 +156,7 @@ export class WalletManager implements Database.IWalletManager { * Remove wallet by username. * @param {String} username */ - public forgetByUsername(username) { + public forgetByUsername(username: string): void { delete this.byUsername[username]; } @@ -149,7 +165,7 @@ export class WalletManager implements Database.IWalletManager { * @param {Array} wallets * @return {void} */ - public index(wallets) { + public index(wallets: Wallet[]): void { for (const wallet of wallets) { this.reindex(wallet); } @@ -160,7 +176,7 @@ export class WalletManager implements Database.IWalletManager { * @param {Wallet} wallet * @return {void} */ - public reindex(wallet: models.Wallet) { + public reindex(wallet: Wallet): void { if (wallet.address) { this.byAddress[wallet.address] = wallet; } @@ -174,10 +190,10 @@ export class WalletManager implements Database.IWalletManager { } } - public clear() { - Object.values(this.byAddress).forEach(wallet => { - wallet.dirty = false; - }); + public cloneDelegateWallets(): WalletManager { + const walletManager = new WalletManager(); + walletManager.index(cloneDeep(this.allByUsername())); + return walletManager; } /** @@ -186,12 +202,8 @@ export class WalletManager implements Database.IWalletManager { * @param height * @return {Array} */ - public loadActiveDelegateList(maxDelegates: number, height?: number): any[] { - if (height > 1 && height % maxDelegates !== 1) { - throw new Error("Trying to build delegates outside of round change"); - } - - const { round } = roundCalculator.calculateRound(height, maxDelegates); + public loadActiveDelegateList(roundInfo: Shared.IRoundInfo): Database.IDelegateWallet[] { + const { maxDelegates } = roundInfo; const delegatesWallets = this.allByUsername(); if (delegatesWallets.length < maxDelegates) { @@ -202,64 +214,19 @@ export class WalletManager implements Database.IWalletManager { ); } - const equalVotesMap = new Map(); - - const delegates = delegatesWallets - .sort((a, b) => { - const diff = b.voteBalance.comparedTo(a.voteBalance); - - if (diff === 0) { - if (!equalVotesMap.has(a.voteBalance.toFixed())) { - equalVotesMap.set(a.voteBalance.toFixed(), new Set()); - } - - const set = equalVotesMap.get(a.voteBalance.toFixed()); - set.add(a); - set.add(b); - - if (a.publicKey === b.publicKey) { - throw new Error( - `The balance and public key of both delegates are identical! Delegate "${ - a.username - }" appears twice in the list.`, - ); - } - - return a.publicKey.localeCompare(b.publicKey, "en"); - } - - return diff; - }) - .map((delegate, i) => { - const rate = i + 1; - this.byUsername[delegate.username].rate = rate; - return { ...{ round }, ...delegate, rate }; - }) - .slice(0, maxDelegates); - - for (const [voteBalance, set] of equalVotesMap.entries()) { - const values: any[] = Array.from(set.values()); - if (delegates.includes(values[0])) { - const mapped = values.map(v => `${v.username} (${v.publicKey})`); - this.logger.warn( - `Delegates ${JSON.stringify(mapped, null, 4)} have a matching vote balance of ${formatSatoshi( - voteBalance, - )}`, - ); - } - } + const delegates = this.buildDelegateRanking(delegatesWallets, roundInfo); this.logger.debug(`Loaded ${delegates.length} active ${pluralize("delegate", delegates.length)}`); - return delegates; + return delegates as Database.IDelegateWallet[]; } /** * Build vote balances of all delegates. - * NOTE: Only called during SPV. + * NOTE: Only called during integrity verification on boot. * @return {void} */ - public buildVoteBalances() { + public buildVoteBalances(): void { Object.values(this.byPublicKey).forEach(voter => { if (voter.vote) { const delegate = this.byPublicKey[voter.vote]; @@ -272,7 +239,7 @@ export class WalletManager implements Database.IWalletManager { * Remove non-delegate wallets that have zero (0) balance from memory. * @return {void} */ - public purgeEmptyNonDelegates() { + public purgeEmptyNonDelegates(): void { Object.values(this.byPublicKey).forEach(wallet => { if (this.canBePurged(wallet)) { delete this.byPublicKey[wallet.publicKey]; @@ -286,13 +253,13 @@ export class WalletManager implements Database.IWalletManager { * @param {Block} block * @return {void} */ - public applyBlock(block: models.Block) { + public applyBlock(block: models.Block): void { const generatorPublicKey = block.data.generatorPublicKey; let delegate = this.byPublicKey[block.data.generatorPublicKey]; if (!delegate) { - const generator = crypto.getAddress(generatorPublicKey, this.networkId); + const generator = crypto.getAddress(generatorPublicKey); if (block.data.height === 1) { delegate = new Wallet(generator); @@ -303,7 +270,7 @@ export class WalletManager implements Database.IWalletManager { this.logger.debug(`Delegate by address: ${this.byAddress[generator]}`); if (this.byAddress[generator]) { - this.logger.info("This look like a bug, please report :bug:"); + this.logger.info("This look like a bug, please report"); } throw new Error(`Could not find delegate with publicKey ${generatorPublicKey}`); @@ -347,13 +314,11 @@ export class WalletManager implements Database.IWalletManager { * @param {Block} block * @return {void} */ - public revertBlock(block: models.Block) { + public revertBlock(block: models.Block): void { const delegate = this.byPublicKey[block.data.generatorPublicKey]; if (!delegate) { - app.forceExit( - `Failed to lookup generator '${block.data.generatorPublicKey}' of block '${block.data.id}'. :skull:`, - ); + app.forceExit(`Failed to lookup generator '${block.data.generatorPublicKey}' of block '${block.data.id}'.`); } const revertedTransactions = []; @@ -387,121 +352,60 @@ export class WalletManager implements Database.IWalletManager { /** * Apply the given transaction to a delegate. - * @param {Transaction} transaction - * @return {Transaction} */ - public applyTransaction(transaction: models.Transaction) { + public applyTransaction(transaction: Transaction): void { const { data } = transaction; - const { type, asset, recipientId, senderPublicKey } = data; + const { type, recipientId, senderPublicKey } = data; + const transactionHandler = TransactionHandlerRegistry.get(transaction.type); const sender = this.findByPublicKey(senderPublicKey); const recipient = this.findByAddress(recipientId); const errors = []; - // specific verifications / adjustments depending on transaction type - if (type === TransactionTypes.DelegateRegistration && this.byUsername[asset.delegate.username.toLowerCase()]) { - this.logger.error( - `Can't apply transaction ${ - data.id - }: delegate name '${asset.delegate.username.toLowerCase()}' already taken.`, - ); - throw new Error(`Can't apply transaction ${data.id}: delegate name already taken.`); - - // NOTE: We use the vote public key, because vote transactions - // have the same sender and recipient - } else if (type === TransactionTypes.Vote && !this.isDelegate(asset.votes[0].slice(1))) { - this.logger.error(`Can't apply vote transaction ${data.id}: delegate ${asset.votes[0]} does not exist.`); - throw new Error(`Can't apply transaction ${data.id}: delegate ${asset.votes[0]} does not exist.`); - } else if (type === TransactionTypes.SecondSignature) { + // TODO: can/should be removed? + if (type === TransactionTypes.SecondSignature) { data.recipientId = ""; } // handle exceptions / verify that we can apply the transaction to the sender if (isException(data)) { this.logger.warn(`Transaction ${data.id} forcibly applied because it has been added as an exception.`); - } else if (!sender.canApply(data, errors)) { - this.logger.error( - `Can't apply transaction id:${data.id} from sender:${sender.address} due to ${JSON.stringify(errors)}`, - ); - this.logger.debug(`Audit: ${JSON.stringify(sender.auditApply(data), null, 2)}`); - throw new Error(`Can't apply transaction ${data.id}`); + } else { + try { + transactionHandler.canBeApplied(transaction, sender, this); + } catch (error) { + this.logger.error( + `Can't apply transaction id:${data.id} from sender:${sender.address} due to ${error.message}`, + ); + this.logger.debug(`Audit: ${JSON.stringify(sender.auditApply(data), null, 2)}`); + throw new Error(`Can't apply transaction ${data.id}`); + } } - sender.applyTransactionToSender(data); + transactionHandler.applyToSender(transaction, sender); if (type === TransactionTypes.DelegateRegistration) { this.reindex(sender); } + // TODO: make more generic if (recipient && type === TransactionTypes.Transfer) { - recipient.applyTransactionToRecipient(data); + transactionHandler.applyToRecipient(transaction, recipient); } - this._updateVoteBalances(sender, recipient, data); - - return transaction; - } - - /** - * Updates the vote balances of the respective delegates of sender and recipient. - * If the transaction is not a vote... - * 1. fee + amount is removed from the sender's delegate vote balance - * 2. amount is added to the recipient's delegate vote balance - * - * in case of a vote... - * 1. the full sender balance is added to the sender's delegate vote balance - * - * If revert is set to true, the operations are reversed (plus -> minus, minus -> plus). - * @param {Wallet} sender - * @param {Wallet} recipient - * @param {Transaction} transaction - * @param {Boolean} revert - * @return {Transaction} - */ - public _updateVoteBalances(sender, recipient, transaction, revert = false) { - // TODO: multipayment? - if (transaction.type !== TransactionTypes.Vote) { - // Update vote balance of the sender's delegate - if (sender.vote) { - const delegate = this.findByPublicKey(sender.vote); - const total = transaction.amount.plus(transaction.fee); - delegate.voteBalance = revert ? delegate.voteBalance.plus(total) : delegate.voteBalance.minus(total); - } - - // Update vote balance of recipient's delegate - if (recipient && recipient.vote) { - const delegate = this.findByPublicKey(recipient.vote); - delegate.voteBalance = revert - ? delegate.voteBalance.minus(transaction.amount) - : delegate.voteBalance.plus(transaction.amount); - } - } else { - const vote = transaction.asset.votes[0]; - const delegate = this.findByPublicKey(vote.substr(1)); - - if (vote.startsWith("+")) { - delegate.voteBalance = revert - ? delegate.voteBalance.minus(sender.balance.minus(transaction.fee)) - : delegate.voteBalance.plus(sender.balance); - } else { - delegate.voteBalance = revert - ? delegate.voteBalance.plus(sender.balance) - : delegate.voteBalance.minus(sender.balance.plus(transaction.fee)); - } - } + this.updateVoteBalances(sender, recipient, data); } /** * Remove the given transaction from a delegate. - * @param {Transaction} transaction - * @return {Transaction} */ - public revertTransaction(transaction: models.Transaction) { + public revertTransaction(transaction: Transaction): void { const { type, data } = transaction; + const transactionHandler = TransactionHandlerRegistry.get(transaction.type); const sender = this.findByPublicKey(data.senderPublicKey); // Should exist const recipient = this.byAddress[data.recipientId]; - sender.revertTransactionForSender(data); + transactionHandler.revertForSender(transaction, sender); // removing the wallet from the delegates index if (type === TransactionTypes.DelegateRegistration) { @@ -509,20 +413,18 @@ export class WalletManager implements Database.IWalletManager { } if (recipient && type === TransactionTypes.Transfer) { - recipient.revertTransactionForRecipient(data); + transactionHandler.revertForRecipient(transaction, recipient); } // Revert vote balance updates - this._updateVoteBalances(sender, recipient, data, true); - - return data; + this.updateVoteBalances(sender, recipient, data, true); } /** * Checks if a given publicKey is a registered delegate * @param {String} publicKey */ - public isDelegate(publicKey: string) { + public isDelegate(publicKey: string): boolean { const delegateWallet = this.byPublicKey[publicKey]; if (delegateWallet && delegateWallet.username) { @@ -537,7 +439,7 @@ export class WalletManager implements Database.IWalletManager { * @param {Object} wallet * @return {Boolean} */ - public canBePurged(wallet) { + public canBePurged(wallet): boolean { return wallet.balance.isZero() && !wallet.secondPublicKey && !wallet.multisignature && !wallet.username; } @@ -545,9 +447,109 @@ export class WalletManager implements Database.IWalletManager { * Reset the wallets index. * @return {void} */ - public reset() { + public reset(): void { this.byAddress = {}; this.byPublicKey = {}; this.byUsername = {}; } + + public buildDelegateRanking( + delegates: Database.IWallet[], + roundInfo?: Shared.IRoundInfo, + ): Database.IDelegateWallet[] { + const equalVotesMap = new Map(); + let delegateWallets = delegates + .sort((a, b) => { + const diff = b.voteBalance.comparedTo(a.voteBalance); + + if (diff === 0) { + if (!equalVotesMap.has(a.voteBalance.toFixed())) { + equalVotesMap.set(a.voteBalance.toFixed(), new Set()); + } + + const set = equalVotesMap.get(a.voteBalance.toFixed()); + set.add(a); + set.add(b); + + if (a.publicKey === b.publicKey) { + throw new Error( + `The balance and public key of both delegates are identical! Delegate "${ + a.username + }" appears twice in the list.`, + ); + } + + return a.publicKey.localeCompare(b.publicKey, "en"); + } + + return diff; + }) + .map((delegate, i) => { + const rate = i + 1; + this.byUsername[delegate.username].rate = rate; + return { round: roundInfo ? roundInfo.round : 0, ...delegate, rate }; + }); + + if (roundInfo) { + delegateWallets = delegateWallets.slice(0, roundInfo.maxDelegates); + + for (const [voteBalance, set] of equalVotesMap.entries()) { + const values: any[] = Array.from(set.values()); + if (delegateWallets.includes(values[0])) { + const mapped = values.map(v => `${v.username} (${v.publicKey})`); + this.logger.warn( + `Delegates ${JSON.stringify(mapped, null, 4)} have a matching vote balance of ${formatSatoshi( + voteBalance, + )}`, + ); + } + } + } + + return delegateWallets; + } + + /** + * Updates the vote balances of the respective delegates of sender and recipient. + * If the transaction is not a vote... + * 1. fee + amount is removed from the sender's delegate vote balance + * 2. amount is added to the recipient's delegate vote balance + * + * in case of a vote... + * 1. the full sender balance is added to the sender's delegate vote balance + * + * If revert is set to true, the operations are reversed (plus -> minus, minus -> plus). + */ + private updateVoteBalances(sender: Wallet, recipient: Wallet, transaction: ITransactionData, revert = false): void { + // TODO: multipayment? + if (transaction.type !== TransactionTypes.Vote) { + // Update vote balance of the sender's delegate + if (sender.vote) { + const delegate = this.findByPublicKey(sender.vote); + const total = (transaction.amount as Bignum).plus(transaction.fee); + delegate.voteBalance = revert ? delegate.voteBalance.plus(total) : delegate.voteBalance.minus(total); + } + + // Update vote balance of recipient's delegate + if (recipient && recipient.vote) { + const delegate = this.findByPublicKey(recipient.vote); + delegate.voteBalance = revert + ? delegate.voteBalance.minus(transaction.amount) + : delegate.voteBalance.plus(transaction.amount); + } + } else { + const vote = transaction.asset.votes[0]; + const delegate = this.findByPublicKey(vote.substr(1)); + + if (vote.startsWith("+")) { + delegate.voteBalance = revert + ? delegate.voteBalance.minus(sender.balance.minus(transaction.fee)) + : delegate.voteBalance.plus(sender.balance); + } else { + delegate.voteBalance = revert + ? delegate.voteBalance.plus(sender.balance) + : delegate.voteBalance.minus(sender.balance.plus(transaction.fee)); + } + } + } } diff --git a/packages/crypto/src/models/wallet.ts b/packages/core-database/src/wallet.ts similarity index 74% rename from packages/crypto/src/models/wallet.ts rename to packages/core-database/src/wallet.ts index c5681d7625..5f89dde15d 100644 --- a/packages/crypto/src/models/wallet.ts +++ b/packages/core-database/src/wallet.ts @@ -1,32 +1,17 @@ -import { TransactionTypes } from "../constants"; -import { crypto } from "../crypto/crypto"; -import { transactionHandler } from "../handlers/transactions"; -import { Bignum, formatSatoshi } from "../utils"; -import { IBlockData } from "./block"; -import { IMultiSignatureAsset, ITransactionData } from "./transaction"; - -/** - * TODO copy some parts to ArkDocs - * @classdesc This class holds the wallet data, verifies it and applies the - * transaction and blocks to it - * - * Wallet attributes that are stored on the db: - * - address - * - publicKey - * - secondPublicKey - * - balance - * - vote - * - username (name, if the wallet is a delegate) - * - voteBalance (number of votes if the wallet is a delegate) - * - producedBlocks - * - missedBlocks - * - * This other attributes are not stored on the db: - * - multisignature - * - lastBlock (last block applied or `null``) - * - dirty - */ -export class Wallet { +import { Database } from "@arkecosystem/core-interfaces"; +import { + Bignum, + constants, + crypto, + formatSatoshi, + IMultiSignatureAsset, + ITransactionData, + models, +} from "@arkecosystem/crypto"; + +const { TransactionTypes } = constants; + +export class Wallet implements Database.IWallet { public address: string; public publicKey: string | null; public secondPublicKey: string | null; @@ -39,9 +24,9 @@ export class Wallet { public multisignature?: IMultiSignatureAsset; public dirty: boolean; public producedBlocks: number; - public missedBlocks: number; public forgedFees: Bignum; public forgedRewards: Bignum; + public rate?: number; constructor(address: string) { this.address = address; @@ -54,54 +39,15 @@ export class Wallet { this.lastBlock = null; this.voteBalance = Bignum.ZERO; this.multisignature = null; - this.dirty = true; this.producedBlocks = 0; - this.missedBlocks = 0; this.forgedFees = Bignum.ZERO; this.forgedRewards = Bignum.ZERO; } - /** - * Check if can apply a transaction to the wallet. - */ - public canApply(transaction: ITransactionData, errors: any[]): boolean { - return transactionHandler.canApply(this, transaction, errors); - } - - /** - * Associate this wallet as the sender of a transaction. - */ - public applyTransactionToSender(transaction: ITransactionData): void { - return transactionHandler.applyTransactionToSender(this, transaction); - } - - /** - * Remove this wallet as the sender of a transaction. - */ - public revertTransactionForSender(transaction: ITransactionData): void { - return transactionHandler.revertTransactionForSender(this, transaction); - } - - /** - * Add transaction balance to this wallet. - */ - public applyTransactionToRecipient(transaction: ITransactionData): void { - return transactionHandler.applyTransactionToRecipient(this, transaction); - } - - /** - * Remove transaction balance from this wallet. - */ - public revertTransactionForRecipient(transaction: ITransactionData): void { - return transactionHandler.revertTransactionForRecipient(this, transaction); - } - /** * Add block data to this wallet. */ - public applyBlock(block: IBlockData): boolean { - this.dirty = true; - + public applyBlock(block: models.IBlockData): boolean { if ( block.generatorPublicKey === this.publicKey || crypto.getAddress(block.generatorPublicKey) === this.address @@ -122,15 +68,13 @@ export class Wallet { /** * Remove block data from this wallet. */ - public revertBlock(block: IBlockData): boolean { + public revertBlock(block: models.IBlockData): boolean { if ( block.generatorPublicKey === this.publicKey || crypto.getAddress(block.generatorPublicKey) === this.address ) { - this.dirty = true; this.balance = this.balance.minus(block.reward).minus(block.totalFee); - // update stats this.forgedFees = this.forgedFees.minus(block.totalFee); this.forgedRewards = this.forgedRewards.minus(block.reward); this.producedBlocks--; @@ -282,7 +226,7 @@ export class Wallet { * Verify the wallet. */ private verify(transaction: ITransactionData, signature: string, publicKey: string): boolean { - const hash = crypto.getHash(transaction, true, true); + const hash = crypto.getHash(transaction, { excludeSignature: true, excludeSecondSignature: true }); return crypto.verifyHash(hash, signature, publicKey); } } diff --git a/packages/core-debugger-cli/.gitignore b/packages/core-debugger-cli/.gitignore deleted file mode 100644 index 5dc9198e84..0000000000 --- a/packages/core-debugger-cli/.gitignore +++ /dev/null @@ -1 +0,0 @@ -test-wallets \ No newline at end of file diff --git a/packages/core-debugger-cli/__tests__/commands/identity.test.ts b/packages/core-debugger-cli/__tests__/commands/identity.test.ts deleted file mode 100644 index 2fff72ab91..0000000000 --- a/packages/core-debugger-cli/__tests__/commands/identity.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import "jest-extended"; - -import { IdentityCommand } from "../../src/commands/identity"; - -describe("Commands - Identity", async () => { - const fixtureIdentities = require("../__fixtures__/identities.json"); - - it("should return identities from passphrase", async () => { - const expected = { - passphrase: "this is a top secret passphrase", - publicKey: "034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192", - privateKey: "d8839c2432bfd0a67ef10a804ba991eabba19f154a3d707917681d45822a5712", - address: "D61mfSggzbvQgTUe6JhYKH2doHaqJ3Dyib", - }; - - expect(await IdentityCommand.run(["--data", fixtureIdentities.passphrase, "--type", "passphrase"])).toEqual( - expected, - ); - }); - - it("should return identities from privateKey", async () => { - const expected = { - publicKey: "034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192", - privateKey: "d8839c2432bfd0a67ef10a804ba991eabba19f154a3d707917681d45822a5712", - address: "D61mfSggzbvQgTUe6JhYKH2doHaqJ3Dyib", - }; - - expect(await IdentityCommand.run(["--data", fixtureIdentities.privateKey, "--type", "privateKey"])).toEqual( - expected, - ); - }); - - it("should return identities from publicKey", async () => { - const expected = { - publicKey: "034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192", - address: "D61mfSggzbvQgTUe6JhYKH2doHaqJ3Dyib", - }; - - expect(await IdentityCommand.run(["--data", fixtureIdentities.publicKey, "--type", "publicKey"])).toEqual( - expected, - ); - }); -}); diff --git a/packages/core-debugger-cli/__tests__/commands/serialize.test.ts b/packages/core-debugger-cli/__tests__/commands/serialize.test.ts deleted file mode 100644 index a2ddb006bb..0000000000 --- a/packages/core-debugger-cli/__tests__/commands/serialize.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import "jest-extended"; - -import { SerializeCommand } from "../../src/commands/serialize"; - -describe("Commands - Serialize", () => { - const fixtureBlock = require("../__fixtures__/block.json"); - const fixtureTransaction = require("../__fixtures__/transaction.json"); - - it("should serialize a block (not-full)", async () => { - expect(await SerializeCommand.run(["--data", JSON.stringify(fixtureBlock.data), "--type", "block"])).toEqual( - fixtureBlock.serialized, - ); - }); - - it("should serialize a block (full)", async () => { - expect( - await SerializeCommand.run(["--data", JSON.stringify(fixtureBlock.data), "--type", "block", "--full"]), - ).toEqual(fixtureBlock.serializedFull); - }); - - it("should serialize a transaction", async () => { - expect( - await SerializeCommand.run(["--data", JSON.stringify(fixtureTransaction.data), "--type", "transaction"]), - ).toEqual(fixtureTransaction.serialized); - }); -}); diff --git a/packages/core-debugger-cli/__tests__/commands/verify.test.ts b/packages/core-debugger-cli/__tests__/commands/verify.test.ts deleted file mode 100644 index 8827d94bc0..0000000000 --- a/packages/core-debugger-cli/__tests__/commands/verify.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import "jest-extended"; - -import { VerifyCommand } from "../../src/commands/verify"; - -describe("Commands - Verify", () => { - const fixtureBlock = require("../__fixtures__/block.json"); - const fixtureTransaction = require("../__fixtures__/transaction.json"); - - it("should verify a block", async () => { - expect(await VerifyCommand.run(["--data", fixtureBlock.serializedFull, "--type", "block"])).toBeTrue(); - }); - - it("should verify a transaction", async () => { - expect(await VerifyCommand.run(["--data", fixtureTransaction.serialized, "--type", "transaction"])).toBeTrue(); - }); -}); diff --git a/packages/core-debugger-cli/__tests__/utils.test.ts b/packages/core-debugger-cli/__tests__/utils.test.ts deleted file mode 100644 index b07503a359..0000000000 --- a/packages/core-debugger-cli/__tests__/utils.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { readSync } from "clipboardy"; -import "jest-extended"; - -import { copyToClipboard, handleOutput } from "../src/utils"; - -const dummyData = { hello: "world" }; - -describe("Utils", () => { - describe("copyToClipboard", () => { - it("should contain the copied data", () => { - copyToClipboard(dummyData); - - expect(JSON.parse(readSync())).toEqual(dummyData); - }); - }); - - describe("handleOutput", () => { - it("should copy the data", () => { - handleOutput({ copy: true }, dummyData); - - expect(JSON.parse(readSync())).toEqual(dummyData); - }); - - it("should log the data", () => { - const method = jest.spyOn(global.console, "log"); - - handleOutput({ log: true }, dummyData); - - expect(method).toHaveBeenCalledWith(dummyData); - }); - - it("should return the data", () => { - expect(handleOutput({}, dummyData)).toEqual(dummyData); - }); - }); -}); diff --git a/packages/core-debugger-cli/bin/run b/packages/core-debugger-cli/bin/run deleted file mode 100755 index 30b14e1773..0000000000 --- a/packages/core-debugger-cli/bin/run +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env node - -require('@oclif/command').run() -.then(require('@oclif/command/flush')) -.catch(require('@oclif/errors/handle')) diff --git a/packages/core-debugger-cli/bin/run.cmd b/packages/core-debugger-cli/bin/run.cmd deleted file mode 100644 index 968fc30758..0000000000 --- a/packages/core-debugger-cli/bin/run.cmd +++ /dev/null @@ -1,3 +0,0 @@ -@echo off - -node "%~dp0\run" %* diff --git a/packages/core-debugger-cli/package.json b/packages/core-debugger-cli/package.json deleted file mode 100644 index a33e4cf65d..0000000000 --- a/packages/core-debugger-cli/package.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "name": "@arkecosystem/core-debugger-cli", - "description": "Debugger CLI for Ark Core", - "version": "2.2.1", - "contributors": [ - "Brian Faust " - ], - "license": "MIT", - "main": "dist/index.js", - "files": [ - "/bin", - "/dist", - "/oclif.manifest.json" - ], - "bin": { - "debugger": "./bin/run" - }, - "scripts": { - "debugger": "./bin/run", - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", - "prepublishOnly": "yarn build", - "pretest": "yarn lint && yarn build", - "prepack": "oclif-dev manifest && npm shrinkwrap", - "postpack": "rm -f oclif.manifest.json", - "compile": "../../node_modules/typescript/bin/tsc", - "build": "yarn clean && yarn compile", - "build:watch": "yarn clean && yarn compile -w", - "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "test": "cross-env CORE_ENV=test jest --runInBand --forceExit", - "test:coverage": "cross-env CORE_ENV=test jest --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$' --runInBand --forceExit", - "test:debug": "cross-env CORE_ENV=test node --inspect-brk ../../node_modules/.bin/jest --runInBand", - "test:watch": "cross-env CORE_ENV=test jest --runInBand --watch", - "test:watch:all": "cross-env CORE_ENV=test jest --runInBand --watchAll", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" - }, - "dependencies": { - "@arkecosystem/crypto": "^2.2.1", - "@oclif/command": "^1.5.10", - "@oclif/config": "^1.12.6", - "@oclif/plugin-help": "^2.1.6", - "@oclif/plugin-not-found": "^1.2.2", - "clipboardy": "^1.2.3" - }, - "devDependencies": { - "@types/clipboardy": "^1.1.0" - }, - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=10.x" - }, - "jest": { - "preset": "../../jest-preset.json" - }, - "oclif": { - "commands": "./dist/commands", - "bin": "debugger", - "plugins": [ - "@oclif/plugin-help", - "@oclif/plugin-not-found" - ] - } -} diff --git a/packages/core-debugger-cli/src/commands/command.ts b/packages/core-debugger-cli/src/commands/command.ts deleted file mode 100644 index 4ab327459a..0000000000 --- a/packages/core-debugger-cli/src/commands/command.ts +++ /dev/null @@ -1,12 +0,0 @@ -import Command, { flags } from "@oclif/command"; - -export abstract class BaseCommand extends Command { - public static flags = { - log: flags.string({ - description: "log the data to the console", - }), - copy: flags.string({ - description: "copy the data to the clipboard", - }), - }; -} diff --git a/packages/core-debugger-cli/src/commands/deserialize.ts b/packages/core-debugger-cli/src/commands/deserialize.ts deleted file mode 100644 index 553fe65abb..0000000000 --- a/packages/core-debugger-cli/src/commands/deserialize.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { models } from "@arkecosystem/crypto"; -import { flags } from "@oclif/command"; -import { handleOutput } from "../utils"; -import { BaseCommand } from "./command"; - -export class DeserializeCommand extends BaseCommand { - public static description: string = "Deserialize the given HEX"; - - public static flags = { - ...BaseCommand.flags, - data: flags.string({ - description: "the HEX blob to deserialize", - required: true, - default: "transaction", - }), - type: flags.string({ - description: "transaction or block", - required: true, - }), - }; - - public async run(): Promise { - // tslint:disable-next-line:no-shadowed-variable - const { flags } = this.parse(DeserializeCommand); - - const deserialized = - flags.type === "transaction" ? new models.Transaction(flags.data) : new models.Block(flags.data); - - return handleOutput(flags, JSON.stringify(deserialized, null, 4)); - } -} diff --git a/packages/core-debugger-cli/src/index.ts b/packages/core-debugger-cli/src/index.ts deleted file mode 100644 index 8bdb76f9a0..0000000000 --- a/packages/core-debugger-cli/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { run } from "@oclif/command"; diff --git a/packages/core-debugger-cli/src/utils.ts b/packages/core-debugger-cli/src/utils.ts deleted file mode 100644 index 2fbd8ef2ac..0000000000 --- a/packages/core-debugger-cli/src/utils.ts +++ /dev/null @@ -1,18 +0,0 @@ -import clipboardy from "clipboardy"; - -export function copyToClipboard(data) { - clipboardy.writeSync(JSON.stringify(data)); -} - -export function handleOutput(opts, data) { - if (opts.copy) { - return copyToClipboard(data); - } - - if (opts.log) { - // tslint:disable-next-line:no-console - return console.log(data); - } - - return data; -} diff --git a/packages/core-elasticsearch/.gitattributes b/packages/core-elasticsearch/.gitattributes index 60cc52db63..63f6a5b970 100644 --- a/packages/core-elasticsearch/.gitattributes +++ b/packages/core-elasticsearch/.gitattributes @@ -2,10 +2,6 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore -/__tests__ export-ignore -/docs export-ignore /README.md export-ignore diff --git a/packages/core-elasticsearch/README.md b/packages/core-elasticsearch/README.md index 7d91695924..6f2cc475e9 100644 --- a/packages/core-elasticsearch/README.md +++ b/packages/core-elasticsearch/README.md @@ -1,12 +1,12 @@ -# Ark Core - Elasticsearch +# Persona Core - Elasticsearch

- +

## Documentation -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core-elasticsearch.html). +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/optional/core-elasticsearch.html). ## Security @@ -14,9 +14,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [Brian Faust](https://github.com/faustbrian) -- [All Contributors](../../../../contributors) +This project exists thanks to all the people who [contribute](../../../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-elasticsearch/package.json b/packages/core-elasticsearch/package.json index a1f77ccd39..5cd95b22b9 100644 --- a/packages/core-elasticsearch/package.json +++ b/packages/core-elasticsearch/package.json @@ -1,7 +1,7 @@ { "name": "@arkecosystem/core-elasticsearch", - "description": "A powerful Elasticsearch integration for Ark Core", - "version": "2.2.1", + "description": "A powerful Elasticsearch integration for ARK Core", + "version": "2.3.15", "contributors": [ "Brian Faust " ], @@ -11,41 +11,32 @@ "dist" ], "scripts": { - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", "prepublishOnly": "yarn build", - "pretest": "yarn lint && yarn build", "compile": "../../node_modules/typescript/bin/tsc", "build": "yarn clean && yarn compile", "build:watch": "yarn clean && yarn compile -w", - "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" + "clean": "del dist" }, "dependencies": { - "@arkecosystem/core-container": "^2.2.1", - "@arkecosystem/core-http-utils": "^2.2.1", - "@arkecosystem/core-interfaces": "^2.2.1", - "@arkecosystem/crypto": "^2.2.1", + "@arkecosystem/core-container": "^2.3.15", + "@arkecosystem/core-event-emitter": "^2.3.15", + "@arkecosystem/core-http-utils": "^2.3.15", + "@arkecosystem/core-interfaces": "^2.3.15", + "@arkecosystem/crypto": "^2.3.15", + "boom": "^7.3.0", "elasticsearch": "^15.4.1", "fs-extra": "^7.0.1", "joi": "^14.3.1" }, "devDependencies": { - "@types/elasticsearch": "^5.0.30", + "@types/elasticsearch": "^5.0.31", "@types/fs-extra": "^5.0.5", - "@types/joi": "^14.3.1" + "@types/joi": "^14.3.2" }, "publishConfig": { "access": "public" }, "engines": { "node": ">=10.x" - }, - "jest": { - "preset": "../../jest-preset.json" } } diff --git a/packages/core-elasticsearch/src/index.ts b/packages/core-elasticsearch/src/index.ts index 98cf4c4b8a..754fc9991f 100644 --- a/packages/core-elasticsearch/src/index.ts +++ b/packages/core-elasticsearch/src/index.ts @@ -8,7 +8,15 @@ export const plugin: Container.PluginDescriptor = { pkg: require("../package.json"), defaults, alias: "elasticsearch", - async register(_, options) { + async register(container: Container.IContainer, options: Container.IPluginOptions) { + if ( + typeof options.client !== "object" || + Array.isArray(options.client) || + typeof options.chunkSize !== "number" + ) { + throw new Error("Elasticsearch plugin config invalid"); + } + await client.setUp(options.client); await watchIndices(options.chunkSize); diff --git a/packages/core-elasticsearch/src/indices/blocks.ts b/packages/core-elasticsearch/src/indices/blocks.ts index 6d9fe3a8ca..c51abace01 100644 --- a/packages/core-elasticsearch/src/indices/blocks.ts +++ b/packages/core-elasticsearch/src/indices/blocks.ts @@ -1,4 +1,3 @@ -import { client } from "../client"; import { storage } from "../storage"; import { first, last } from "../utils"; import { Index } from "./base"; diff --git a/packages/core-elasticsearch/src/indices/rounds.ts b/packages/core-elasticsearch/src/indices/rounds.ts index b4152b44b5..f9629753d2 100644 --- a/packages/core-elasticsearch/src/indices/rounds.ts +++ b/packages/core-elasticsearch/src/indices/rounds.ts @@ -1,4 +1,4 @@ -import { client } from "../client"; +import { ApplicationEvents } from "@arkecosystem/core-event-emitter"; import { storage } from "../storage"; import { first, last } from "../utils"; import { Index } from "./base"; @@ -37,6 +37,6 @@ export class Rounds extends Index { } public listen() { - this.emitter.on("round.created", () => this.index()); + this.emitter.on(ApplicationEvents.RoundCreated, () => this.index()); } } diff --git a/packages/core-elasticsearch/src/indices/transactions.ts b/packages/core-elasticsearch/src/indices/transactions.ts index e2eec0718c..f0fb5d13d8 100644 --- a/packages/core-elasticsearch/src/indices/transactions.ts +++ b/packages/core-elasticsearch/src/indices/transactions.ts @@ -1,5 +1,4 @@ -import { models } from "@arkecosystem/crypto"; -import { client } from "../client"; +import { Transaction } from "@arkecosystem/crypto"; import { storage } from "../storage"; import { first, last } from "../utils"; import { Index } from "./base"; @@ -12,7 +11,7 @@ export class Transactions extends Index { const modelQuery = this.createQuery(); const query = modelQuery - .select(modelQuery.block_id, modelQuery.serialized) + .select(modelQuery.id, modelQuery.block_id, modelQuery.serialized) .from(modelQuery) .where(modelQuery.timestamp.gte(storage.get("lastTransaction"))) .order(modelQuery.timestamp.asc) @@ -22,10 +21,10 @@ export class Transactions extends Index { if (rows.length) { rows = rows.map(row => { - const transaction: any = new models.Transaction(row.serialized.toString("hex")); - transaction.blockId = row.blockId; + const { data } = Transaction.fromBytesUnsafe(row.serialized, row.id); + data.blockId = row.blockId; - return transaction; + return data; }); const timestamps = rows.map(row => row.data.timestamp); diff --git a/packages/core-elasticsearch/src/indices/wallets.ts b/packages/core-elasticsearch/src/indices/wallets.ts index 2ccbd95151..b274c4e9bb 100644 --- a/packages/core-elasticsearch/src/indices/wallets.ts +++ b/packages/core-elasticsearch/src/indices/wallets.ts @@ -1,4 +1,4 @@ -import { client } from "../client"; +import { ApplicationEvents } from "@arkecosystem/core-event-emitter"; import { Index } from "./base"; export class Wallets extends Index { @@ -33,6 +33,6 @@ export class Wallets extends Index { } public listen() { - this.emitter.on("round.applied", () => this.index()); + this.emitter.on(ApplicationEvents.RoundApplied, () => this.index()); } } diff --git a/packages/core-graphql/.gitattributes b/packages/core-error-tracker-airbrake/.gitattributes similarity index 66% rename from packages/core-graphql/.gitattributes rename to packages/core-error-tracker-airbrake/.gitattributes index 60cc52db63..63f6a5b970 100644 --- a/packages/core-graphql/.gitattributes +++ b/packages/core-error-tracker-airbrake/.gitattributes @@ -2,10 +2,6 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore -/__tests__ export-ignore -/docs export-ignore /README.md export-ignore diff --git a/packages/core-error-tracker-airbrake/README.md b/packages/core-error-tracker-airbrake/README.md new file mode 100644 index 0000000000..e533e320e7 --- /dev/null +++ b/packages/core-error-tracker-airbrake/README.md @@ -0,0 +1,21 @@ +# Persona Core - Error Tracker - Airbrake + +

+ +

+ +## Documentation + +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/optional/core-error-tracker-airbrake.html). + +## Security + +If you discover a security vulnerability within this package, please send an e-mail to security@ark.io. All security vulnerabilities will be promptly addressed. + +## Credits + +This project exists thanks to all the people who [contribute](../../../../contributors). + +## License + +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-error-tracker-airbrake/package.json b/packages/core-error-tracker-airbrake/package.json new file mode 100644 index 0000000000..7e182d7692 --- /dev/null +++ b/packages/core-error-tracker-airbrake/package.json @@ -0,0 +1,30 @@ +{ + "name": "@arkecosystem/core-error-tracker-airbrake", + "description": "Airbrake error tracker integration for ARK Core.", + "version": "2.3.15", + "contributors": [ + "Brian Faust " + ], + "license": "MIT", + "main": "dist/index.js", + "files": [ + "dist" + ], + "scripts": { + "prepublishOnly": "yarn build", + "compile": "../../node_modules/typescript/bin/tsc", + "build": "yarn clean && yarn compile", + "build:watch": "yarn clean && yarn compile -w", + "clean": "del dist" + }, + "dependencies": { + "@arkecosystem/core-container": "^2.3.15", + "airbrake-js": "^1.6.6" + }, + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=10.x" + } +} diff --git a/packages/core-error-tracker-airbrake/src/defaults.ts b/packages/core-error-tracker-airbrake/src/defaults.ts new file mode 100644 index 0000000000..06ce508a97 --- /dev/null +++ b/packages/core-error-tracker-airbrake/src/defaults.ts @@ -0,0 +1,4 @@ +export const defaults = { + projectId: process.env.CORE_ERROR_TRACKER_AIRBRAKE_PROJECT_ID, + projectKey: process.env.CORE_ERROR_TRACKER_AIRBRAKE_PROJECT_KEY, +}; diff --git a/packages/core-error-tracker-airbrake/src/index.ts b/packages/core-error-tracker-airbrake/src/index.ts new file mode 100644 index 0000000000..0d7446e074 --- /dev/null +++ b/packages/core-error-tracker-airbrake/src/index.ts @@ -0,0 +1,12 @@ +import { Container } from "@arkecosystem/core-interfaces"; +import AirBrake from "airbrake-js"; +import { defaults } from "./defaults"; + +export const plugin: Container.PluginDescriptor = { + pkg: require("../package.json"), + defaults, + alias: "error-tracker", + async register(container: Container.IContainer, options) { + return new AirBrake(options); + }, +}; diff --git a/deprecated/core-snapshots-cli/tsconfig.json b/packages/core-error-tracker-airbrake/tsconfig.json similarity index 100% rename from deprecated/core-snapshots-cli/tsconfig.json rename to packages/core-error-tracker-airbrake/tsconfig.json diff --git a/packages/core-error-tracker-bugsnag/.gitattributes b/packages/core-error-tracker-bugsnag/.gitattributes index 60cc52db63..63f6a5b970 100644 --- a/packages/core-error-tracker-bugsnag/.gitattributes +++ b/packages/core-error-tracker-bugsnag/.gitattributes @@ -2,10 +2,6 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore -/__tests__ export-ignore -/docs export-ignore /README.md export-ignore diff --git a/packages/core-error-tracker-bugsnag/README.md b/packages/core-error-tracker-bugsnag/README.md index fda421356b..9077542dbb 100644 --- a/packages/core-error-tracker-bugsnag/README.md +++ b/packages/core-error-tracker-bugsnag/README.md @@ -1,12 +1,12 @@ -# Ark Core - Error Tracker - Bugsnag +# Persona Core - Error Tracker - Bugsnag

- +

## Documentation -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core-error-tracker-bugsnag.html). +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/optional/core-error-tracker-bugsnag.html). ## Security @@ -14,9 +14,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [Brian Faust](https://github.com/faustbrian) -- [All Contributors](../../../../contributors) +This project exists thanks to all the people who [contribute](../../../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-error-tracker-bugsnag/package.json b/packages/core-error-tracker-bugsnag/package.json index 21facb106d..259abdfde9 100644 --- a/packages/core-error-tracker-bugsnag/package.json +++ b/packages/core-error-tracker-bugsnag/package.json @@ -1,7 +1,7 @@ { "name": "@arkecosystem/core-error-tracker-bugsnag", - "description": "Bugsnag error tracker integration for Ark Core.", - "version": "2.2.1", + "description": "Bugsnag error tracker integration for ARK Core.", + "version": "2.3.15", "contributors": [ "Brian Faust " ], @@ -11,31 +11,20 @@ "dist" ], "scripts": { - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", "prepublishOnly": "yarn build", - "pretest": "yarn lint && yarn build", "compile": "../../node_modules/typescript/bin/tsc", "build": "yarn clean && yarn compile", "build:watch": "yarn clean && yarn compile -w", - "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" + "clean": "del dist" }, "dependencies": { - "@arkecosystem/core-container": "^2.2.1", - "@bugsnag/js": "^5.2.0" + "@arkecosystem/core-container": "^2.3.15", + "@bugsnag/js": "^6.0.0" }, "publishConfig": { "access": "public" }, "engines": { "node": ">=10.x" - }, - "jest": { - "preset": "../../jest-preset.json" } } diff --git a/packages/core-error-tracker-bugsnag/src/index.ts b/packages/core-error-tracker-bugsnag/src/index.ts index 09c6304902..6485a07820 100644 --- a/packages/core-error-tracker-bugsnag/src/index.ts +++ b/packages/core-error-tracker-bugsnag/src/index.ts @@ -1,12 +1,15 @@ import { Container } from "@arkecosystem/core-interfaces"; -import bugsnag from "@bugsnag/js"; +import bugsnag, { Bugsnag } from "@bugsnag/js"; import { defaults } from "./defaults"; export const plugin: Container.PluginDescriptor = { pkg: require("../package.json"), defaults, alias: "error-tracker", - async register(container: Container.IContainer, options) { - return bugsnag(options); + async register(container: Container.IContainer, options: Container.IPluginOptions) { + if (!options.apiKey || typeof options.apiKey !== "string") { + throw new Error("Bugsnag plugin config invalid"); + } + return bugsnag(options as Bugsnag.IConfig); }, }; diff --git a/packages/core-debugger-cli/.gitattributes b/packages/core-error-tracker-raygun/.gitattributes similarity index 66% rename from packages/core-debugger-cli/.gitattributes rename to packages/core-error-tracker-raygun/.gitattributes index 60cc52db63..63f6a5b970 100644 --- a/packages/core-debugger-cli/.gitattributes +++ b/packages/core-error-tracker-raygun/.gitattributes @@ -2,10 +2,6 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore -/__tests__ export-ignore -/docs export-ignore /README.md export-ignore diff --git a/packages/core-error-tracker-raygun/README.md b/packages/core-error-tracker-raygun/README.md new file mode 100644 index 0000000000..528c8d4a5c --- /dev/null +++ b/packages/core-error-tracker-raygun/README.md @@ -0,0 +1,21 @@ +# Persona Core - Error Tracker - Raygun + +

+ +

+ +## Documentation + +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/optional/core-error-tracker-raygun.html). + +## Security + +If you discover a security vulnerability within this package, please send an e-mail to security@ark.io. All security vulnerabilities will be promptly addressed. + +## Credits + +This project exists thanks to all the people who [contribute](../../../../contributors). + +## License + +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-error-tracker-raygun/package.json b/packages/core-error-tracker-raygun/package.json new file mode 100644 index 0000000000..8e2b40f4d5 --- /dev/null +++ b/packages/core-error-tracker-raygun/package.json @@ -0,0 +1,30 @@ +{ + "name": "@arkecosystem/core-error-tracker-raygun", + "description": "Raygun error tracker integration for ARK Core.", + "version": "2.3.15", + "contributors": [ + "Brian Faust " + ], + "license": "MIT", + "main": "dist/index.js", + "files": [ + "dist" + ], + "scripts": { + "prepublishOnly": "yarn build", + "compile": "../../node_modules/typescript/bin/tsc", + "build": "yarn clean && yarn compile", + "build:watch": "yarn clean && yarn compile -w", + "clean": "del dist" + }, + "dependencies": { + "@arkecosystem/core-container": "^2.3.15", + "raygun": "^0.10.1" + }, + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=10.x" + } +} diff --git a/packages/core-error-tracker-raygun/src/defaults.ts b/packages/core-error-tracker-raygun/src/defaults.ts new file mode 100644 index 0000000000..c9cb436f5e --- /dev/null +++ b/packages/core-error-tracker-raygun/src/defaults.ts @@ -0,0 +1,3 @@ +export const defaults = { + apiKey: process.env.CORE_ERROR_TRACKER_RAYGUN_API_KEY, +}; diff --git a/packages/core-error-tracker-raygun/src/index.ts b/packages/core-error-tracker-raygun/src/index.ts new file mode 100644 index 0000000000..d920d86f83 --- /dev/null +++ b/packages/core-error-tracker-raygun/src/index.ts @@ -0,0 +1,12 @@ +import { Container } from "@arkecosystem/core-interfaces"; +import raygun from "raygun"; +import { defaults } from "./defaults"; + +export const plugin: Container.PluginDescriptor = { + pkg: require("../package.json"), + defaults, + alias: "error-tracker", + async register(container: Container.IContainer, options) { + return new raygun.Client().init(options); + }, +}; diff --git a/packages/core-debugger-cli/tsconfig.json b/packages/core-error-tracker-raygun/tsconfig.json similarity index 100% rename from packages/core-debugger-cli/tsconfig.json rename to packages/core-error-tracker-raygun/tsconfig.json diff --git a/packages/core-test-utils/.gitattributes b/packages/core-error-tracker-rollbar/.gitattributes similarity index 66% rename from packages/core-test-utils/.gitattributes rename to packages/core-error-tracker-rollbar/.gitattributes index 60cc52db63..63f6a5b970 100644 --- a/packages/core-test-utils/.gitattributes +++ b/packages/core-error-tracker-rollbar/.gitattributes @@ -2,10 +2,6 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore -/__tests__ export-ignore -/docs export-ignore /README.md export-ignore diff --git a/packages/core-error-tracker-rollbar/README.md b/packages/core-error-tracker-rollbar/README.md new file mode 100644 index 0000000000..1a8d9ee44a --- /dev/null +++ b/packages/core-error-tracker-rollbar/README.md @@ -0,0 +1,21 @@ +# Persona Core - Error Tracker - rollbar + +

+ +

+ +## Documentation + +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/optional/core-error-tracker-rollbar.html). + +## Security + +If you discover a security vulnerability within this package, please send an e-mail to security@ark.io. All security vulnerabilities will be promptly addressed. + +## Credits + +This project exists thanks to all the people who [contribute](../../../../contributors). + +## License + +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-error-tracker-rollbar/package.json b/packages/core-error-tracker-rollbar/package.json new file mode 100644 index 0000000000..359b4463a4 --- /dev/null +++ b/packages/core-error-tracker-rollbar/package.json @@ -0,0 +1,30 @@ +{ + "name": "@arkecosystem/core-error-tracker-rollbar", + "description": "Rollbar error tracker integration for ARK Core.", + "version": "2.3.15", + "contributors": [ + "Brian Faust " + ], + "license": "MIT", + "main": "dist/index.js", + "files": [ + "dist" + ], + "scripts": { + "prepublishOnly": "yarn build", + "compile": "../../node_modules/typescript/bin/tsc", + "build": "yarn clean && yarn compile", + "build:watch": "yarn clean && yarn compile -w", + "clean": "del dist" + }, + "dependencies": { + "@arkecosystem/core-container": "^2.3.15", + "rollbar": "^2.5.4" + }, + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=10.x" + } +} diff --git a/packages/core-error-tracker-rollbar/src/defaults.ts b/packages/core-error-tracker-rollbar/src/defaults.ts new file mode 100644 index 0000000000..6c9407f7bf --- /dev/null +++ b/packages/core-error-tracker-rollbar/src/defaults.ts @@ -0,0 +1,5 @@ +export const defaults = { + accessToken: process.env.CORE_ERROR_TRACKER_ROLLBAR_ACCESS_TOKEN, + captureUncaught: true, + captureUnhandledRejections: true, +}; diff --git a/packages/core-error-tracker-rollbar/src/index.ts b/packages/core-error-tracker-rollbar/src/index.ts new file mode 100644 index 0000000000..d433ff121b --- /dev/null +++ b/packages/core-error-tracker-rollbar/src/index.ts @@ -0,0 +1,12 @@ +import { Container } from "@arkecosystem/core-interfaces"; +import Rollbar from "rollbar"; +import { defaults } from "./defaults"; + +export const plugin: Container.PluginDescriptor = { + pkg: require("../package.json"), + defaults, + alias: "error-tracker", + async register(container: Container.IContainer, options) { + return new Rollbar(options); + }, +}; diff --git a/packages/core-graphql/tsconfig.json b/packages/core-error-tracker-rollbar/tsconfig.json similarity index 100% rename from packages/core-graphql/tsconfig.json rename to packages/core-error-tracker-rollbar/tsconfig.json diff --git a/packages/core-error-tracker-sentry/.gitattributes b/packages/core-error-tracker-sentry/.gitattributes index 60cc52db63..63f6a5b970 100644 --- a/packages/core-error-tracker-sentry/.gitattributes +++ b/packages/core-error-tracker-sentry/.gitattributes @@ -2,10 +2,6 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore -/__tests__ export-ignore -/docs export-ignore /README.md export-ignore diff --git a/packages/core-error-tracker-sentry/README.md b/packages/core-error-tracker-sentry/README.md index ffb13a95e8..8dc8231065 100644 --- a/packages/core-error-tracker-sentry/README.md +++ b/packages/core-error-tracker-sentry/README.md @@ -1,12 +1,12 @@ -# Ark Core - Error Tracker - Sentry +# Persona Core - Error Tracker - Sentry

- +

## Documentation -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core-error-tracker-sentry.html). +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/optional/core-error-tracker-sentry.html). ## Security @@ -14,9 +14,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [Brian Faust](https://github.com/faustbrian) -- [All Contributors](../../../../contributors) +This project exists thanks to all the people who [contribute](../../../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-error-tracker-sentry/package.json b/packages/core-error-tracker-sentry/package.json index 7420fd30f2..70d3b1b560 100644 --- a/packages/core-error-tracker-sentry/package.json +++ b/packages/core-error-tracker-sentry/package.json @@ -1,7 +1,7 @@ { "name": "@arkecosystem/core-error-tracker-sentry", - "description": "Sentry error tracker integration for Ark Core.", - "version": "2.2.1", + "description": "Sentry error tracker integration for ARK Core.", + "version": "2.3.15", "contributors": [ "Brian Faust " ], @@ -11,31 +11,20 @@ "dist" ], "scripts": { - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", "prepublishOnly": "yarn build", - "pretest": "yarn lint && yarn build", "compile": "../../node_modules/typescript/bin/tsc", "build": "yarn clean && yarn compile", "build:watch": "yarn clean && yarn compile -w", - "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" + "clean": "del dist" }, "dependencies": { - "@arkecosystem/core-container": "^2.2.1", - "@sentry/node": "^4.5.4" + "@arkecosystem/core-container": "^2.3.15", + "@sentry/node": "^4.6.4" }, "publishConfig": { "access": "public" }, "engines": { "node": ">=10.x" - }, - "jest": { - "preset": "../../jest-preset.json" } } diff --git a/packages/core-event-emitter/.gitattributes b/packages/core-event-emitter/.gitattributes index 60cc52db63..63f6a5b970 100644 --- a/packages/core-event-emitter/.gitattributes +++ b/packages/core-event-emitter/.gitattributes @@ -2,10 +2,6 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore -/__tests__ export-ignore -/docs export-ignore /README.md export-ignore diff --git a/packages/core-event-emitter/README.md b/packages/core-event-emitter/README.md index 06f6190ead..083821617f 100644 --- a/packages/core-event-emitter/README.md +++ b/packages/core-event-emitter/README.md @@ -1,12 +1,12 @@ -# Ark Core - Event Emitter +# Persona Core - Event Emitter

- +

## Documentation -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core-event-emitter.html). +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/required/core-event-emitter.html). ## Security @@ -14,9 +14,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [Brian Faust](https://github.com/faustbrian) -- [All Contributors](../../../../contributors) +This project exists thanks to all the people who [contribute](../../../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-event-emitter/package.json b/packages/core-event-emitter/package.json index d6b628b2f9..2d3ef72bc8 100644 --- a/packages/core-event-emitter/package.json +++ b/packages/core-event-emitter/package.json @@ -1,7 +1,7 @@ { "name": "@arkecosystem/core-event-emitter", - "description": "Event Manager for Ark Core", - "version": "2.2.1", + "description": "Event Manager for ARK Core", + "version": "2.3.15", "contributors": [ "Brian Faust " ], @@ -11,35 +11,16 @@ "dist" ], "scripts": { - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", "prepublishOnly": "yarn build", - "pretest": "yarn lint && yarn build", "compile": "../../node_modules/typescript/bin/tsc", "build": "yarn clean && yarn compile", "build:watch": "yarn clean && yarn compile -w", - "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "test": "cross-env CORE_ENV=test jest --runInBand --forceExit", - "test:coverage": "cross-env CORE_ENV=test jest --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$' --runInBand --forceExit", - "test:debug": "cross-env CORE_ENV=test node --inspect-brk ../../node_modules/.bin/jest --runInBand", - "test:watch": "cross-env CORE_ENV=test jest --runInBand --watch", - "test:watch:all": "cross-env CORE_ENV=test jest --runInBand --watchAll", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" - }, - "dependencies": { - "eventemitter3": "^3.1.0" + "clean": "del dist" }, "publishConfig": { "access": "public" }, "engines": { "node": ">=10.x" - }, - "jest": { - "preset": "../../jest-preset.json" } } diff --git a/packages/core-event-emitter/src/emitter.ts b/packages/core-event-emitter/src/emitter.ts new file mode 100644 index 0000000000..c3573f3f59 --- /dev/null +++ b/packages/core-event-emitter/src/emitter.ts @@ -0,0 +1,36 @@ +import { EventEmitter as NativeEmitter } from "events"; + +export class EventEmitter { + private readonly emitter: NativeEmitter = new NativeEmitter(); + + public emit(event: string | symbol, args: any): boolean { + return this.emitter.emit(event, args); + } + + public on(event: string | symbol, listener: (...args: any) => void): void { + this.ensureListenerCount(event, maxListeners => maxListeners + 1); + + this.emitter.on(event, listener); + } + + public once(event: string | symbol, listener: (...args: any) => void): void { + this.ensureListenerCount(event, maxListeners => maxListeners + 1); + + this.emitter.once(event, listener); + } + + public off(event: string | symbol, listener: (...args: any) => void): void { + this.emitter.off(event, listener); + + this.ensureListenerCount(event, maxListeners => maxListeners - 1); + } + + private ensureListenerCount(event: string | symbol, count: (maxListeners: number) => number): void { + const maxListeners = this.emitter.getMaxListeners(); + const listenerCount = this.emitter.listenerCount(event); + + if (listenerCount >= maxListeners) { + this.emitter.setMaxListeners(count(maxListeners)); + } + } +} diff --git a/packages/core-event-emitter/src/index.ts b/packages/core-event-emitter/src/index.ts index ec20d8dd61..cf0fcb7790 100644 --- a/packages/core-event-emitter/src/index.ts +++ b/packages/core-event-emitter/src/index.ts @@ -1,4 +1,4 @@ -import EventEmitter from "eventemitter3"; +import { EventEmitter } from "./emitter"; export const plugin = { pkg: require("../package.json"), @@ -7,3 +7,30 @@ export const plugin = { return new EventEmitter(); }, }; + +export enum ApplicationEvents { + BlockApplied = "block.applied", + BlockDisregarded = "block.disregarded", + BlockForged = "block.forged", + BlockReceived = "block.received", + BlockReverted = "block.reverted", + DelegateRegistered = "delegate.registered", + DelegateResigned = "delegate.resigned", + ForgerFailed = "forger.failed", + ForgerMissing = "forger.missing", + ForgerStarted = "forger.started", + PeerAdded = "peer.added", + PeerRemoved = "peer.removed", + RoundApplied = "round.applied", + RoundCreated = "round.created", + StateStarted = "state.started", + TransactionApplied = "transaction.applied", + TransactionExpired = "transaction.expired", + TransactionForged = "transaction.forged", + TransactionPoolAdded = "transaction.pool.added", + TransactionPoolRejected = "transaction.pool.rejected", + TransactionPoolRemoved = "transaction.pool.removed", + TransactionReverted = "transaction.reverted", + WalletColdCreated = "wallet.created.cold", + WalletSaved = "wallet.saved", +} diff --git a/packages/core-forger/.gitattributes b/packages/core-forger/.gitattributes index 60cc52db63..63f6a5b970 100644 --- a/packages/core-forger/.gitattributes +++ b/packages/core-forger/.gitattributes @@ -2,10 +2,6 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore -/__tests__ export-ignore -/docs export-ignore /README.md export-ignore diff --git a/packages/core-forger/README.md b/packages/core-forger/README.md index e6031668bf..7675d05264 100644 --- a/packages/core-forger/README.md +++ b/packages/core-forger/README.md @@ -1,12 +1,12 @@ -# Ark Core - Forger +# Persona Core - Forger

- +

## Documentation -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core-forger.html). +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/required/core-forger.html). ## Security @@ -14,13 +14,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [Brian Faust](https://github.com/faustbrian) -- [Erwann Gentric](https://github.com/air1one) -- [François-Xavier Thoorens](https://github.com/fix) -- [Joshua Noack](https://github.com/supaiku0) -- [Kristjan Košič](https://github.com/kristjank) -- [All Contributors](../../../../contributors) +This project exists thanks to all the people who [contribute](../../../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-forger/__tests__/__support__/setup.ts b/packages/core-forger/__tests__/__support__/setup.ts deleted file mode 100644 index 37ef936c75..0000000000 --- a/packages/core-forger/__tests__/__support__/setup.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { app } from "@arkecosystem/core-container"; -import { setUpContainer } from "@arkecosystem/core-test-utils/src/helpers/container"; - -export const setUp = async () => { - return setUpContainer({ - exit: "@arkecosystem/core-p2p", - }); -}; - -export const tearDown = async () => { - return app.tearDown(); -}; diff --git a/packages/core-forger/__tests__/client.test.ts b/packages/core-forger/__tests__/client.test.ts deleted file mode 100644 index f31c2c580a..0000000000 --- a/packages/core-forger/__tests__/client.test.ts +++ /dev/null @@ -1,150 +0,0 @@ -import "jest-extended"; - -import { NetworkState, NetworkStateStatus } from "@arkecosystem/core-p2p"; -import axios from "axios"; -import MockAdapter from "axios-mock-adapter"; -import { Client } from "../src/client"; -import { sampleBlock } from "./__fixtures__/block"; -import { setUp, tearDown } from "./__support__/setup"; - -const mockAxios = new MockAdapter(axios); - -jest.setTimeout(30000); - -const host = `http://127.0.0.1:4000`; - -let client: Client; - -beforeAll(async () => { - await setUp(); -}); - -afterAll(async () => { - await tearDown(); - mockAxios.restore(); -}); - -beforeEach(() => { - client = new Client(host); - - mockAxios.onGet(`${host}/peer/status`).reply(200); -}); - -afterEach(() => { - mockAxios.reset(); -}); - -describe("Client", () => { - describe("constructor", () => { - it("accepts 1 or more hosts as parameter", () => { - expect(new Client(host).hosts).toEqual([host]); - - const hosts = [host, "http://localhost:4000"]; - - expect(new Client(hosts).hosts).toEqual(hosts); - }); - }); - - describe("broadcast", () => { - describe("when the host is available", () => { - it("should be truthy if broadcasts", async () => { - mockAxios.onPost(`${host}/internal/blocks`).reply(c => { - expect(JSON.parse(c.data).block).toMatchObject( - expect.objectContaining({ - id: sampleBlock.data.id, - }), - ); - return [200, true]; - }); - - await client.__chooseHost(); - - const wasBroadcasted = await client.broadcast(sampleBlock.toJson()); - expect(wasBroadcasted).toBeTruthy(); - }); - }); - }); - - describe("getRound", () => { - describe("when the host is available", () => { - it("should be ok", async () => { - const expectedResponse = { foo: "bar" }; - mockAxios.onGet(`${host}/internal/rounds/current`).reply(200, { data: expectedResponse }); - - const response = await client.getRound(); - - expect(response).toEqual(expectedResponse); - }); - }); - }); - - describe("getTransactions", () => { - describe("when the host is available", () => { - it("should be ok", async () => { - const expectedResponse = { foo: "bar" }; - mockAxios.onGet(`${host}/internal/transactions/forging`).reply(200, { data: expectedResponse }); - - await client.__chooseHost(); - const response = await client.getTransactions(); - - expect(response).toEqual(expectedResponse); - }); - }); - }); - - describe("getNetworkState", () => { - describe("when the host is available", () => { - it("should be ok", async () => { - const expectedResponse = new NetworkState(NetworkStateStatus.Test); - mockAxios.onGet(`${host}/internal/network/state`).reply(200, { data: expectedResponse }); - - await client.__chooseHost(); - const response = await client.getNetworkState(); - - expect(response).toEqual(expectedResponse); - }); - }); - }); - - describe("syncCheck", () => { - it("should induce network sync", async () => { - jest.spyOn(axios, "get"); - mockAxios.onGet(`${host}/internal/blockchain/sync`).reply(200); - - await client.syncCheck(); - - expect(axios.get).toHaveBeenCalledWith(`${host}/internal/blockchain/sync`, expect.any(Object)); - }); - }); - - describe("getUsernames", () => { - it("should fetch usernames", async () => { - jest.spyOn(axios, "get"); - const expectedResponse = { foo: "bar" }; - mockAxios.onGet(`${host}/internal/utils/usernames`).reply(200, { data: expectedResponse }); - - const response = await client.getUsernames(); - - expect(response).toEqual(expectedResponse); - }); - }); - - describe("emitEvent", () => { - it("should emit events", async () => { - jest.spyOn(axios, "post"); - mockAxios.onPost(`${host}/internal/utils/events`).reply(c => { - expect(JSON.parse(c.data)).toMatchObject({ event: "foo", body: "bar" }); - return [200]; - }); - - await client.__chooseHost(); - await client.emitEvent("foo", "bar"); - - expect(axios.post).toHaveBeenCalledWith( - `${host}/internal/utils/events`, - { event: "foo", body: "bar" }, - expect.any(Object), - ); - }); - }); -}); diff --git a/packages/core-forger/package.json b/packages/core-forger/package.json index 2607613e7f..c592599aa9 100644 --- a/packages/core-forger/package.json +++ b/packages/core-forger/package.json @@ -1,7 +1,7 @@ { "name": "@arkecosystem/core-forger", - "description": "Forger for Ark Core", - "version": "2.2.1", + "description": "Forger for ARK Core", + "version": "2.3.15", "contributors": [ "François-Xavier Thoorens ", "Kristjan Košič ", @@ -13,52 +13,32 @@ "dist" ], "scripts": { - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", "prepublishOnly": "yarn build", "pretest": "bash ../../scripts/pre-test.sh", "compile": "../../node_modules/typescript/bin/tsc", "build": "yarn clean && yarn compile", "build:watch": "yarn clean && yarn compile -w", - "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "test": "cross-env CORE_ENV=test jest --runInBand --forceExit", - "test:coverage": "cross-env CORE_ENV=test jest --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$' --runInBand --forceExit", - "test:debug": "cross-env CORE_ENV=test node --inspect-brk ../../node_modules/.bin/jest --runInBand", - "test:watch": "cross-env CORE_ENV=test jest --runInBand --watch", - "test:watch:all": "cross-env CORE_ENV=test jest --runInBand --watchAll", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" + "clean": "del dist" }, "dependencies": { - "@arkecosystem/core-container": "^2.2.1", - "@arkecosystem/core-interfaces": "^2.2.1", - "@arkecosystem/core-p2p": "^2.2.1", - "@arkecosystem/crypto": "^2.2.1", - "axios": "^0.18.0", - "delay": "^4.1.0", + "@arkecosystem/core-container": "^2.3.15", + "@arkecosystem/core-interfaces": "^2.3.15", + "@arkecosystem/core-p2p": "^2.3.15", + "@arkecosystem/core-utils": "^2.3.15", + "@arkecosystem/crypto": "^2.3.15", "lodash.isempty": "^4.4.0", - "lodash.sample": "^4.2.1", "lodash.uniq": "^4.5.0", "pluralize": "^7.0.0" }, "devDependencies": { - "@arkecosystem/core-test-utils": "^2.2.1", - "@types/lodash.isempty": "^4.4.4", - "@types/lodash.sample": "^4.2.4", - "@types/lodash.uniq": "^4.5.4", - "@types/pluralize": "^0.0.29", - "axios-mock-adapter": "^1.16.0" + "@types/lodash.isempty": "^4.4.6", + "@types/lodash.uniq": "^4.5.6", + "@types/pluralize": "^0.0.29" }, "publishConfig": { "access": "public" }, "engines": { "node": ">=10.x" - }, - "jest": { - "preset": "../../jest-preset.json" } } diff --git a/packages/core-forger/src/client.ts b/packages/core-forger/src/client.ts index 7c247baf10..09451d3c06 100644 --- a/packages/core-forger/src/client.ts +++ b/packages/core-forger/src/client.ts @@ -1,15 +1,22 @@ import { app } from "@arkecosystem/core-container"; import { Logger } from "@arkecosystem/core-interfaces"; -import { NetworkState, NetworkStateStatus } from "@arkecosystem/core-p2p"; -import axios from "axios"; -import delay from "delay"; -import sample from "lodash/sample"; +import { ICurrentRound, IForgingTransactions, IResponse, NetworkState } from "@arkecosystem/core-p2p"; +import { httpie, IHttpieResponse } from "@arkecosystem/core-utils"; +import { ITransactionData, models } from "@arkecosystem/crypto"; import { URL } from "url"; +import { HostNoResponseError, RelayCommunicationError } from "./errors"; export class Client { public hosts: string[]; - private host: any; - private headers: any; + private host: string; + private headers: { + version: string; + port: number; + nethash: string; + "x-auth": "forger"; + "Content-Type": "application/json"; + }; + private logger: Logger.ILogger; /** @@ -28,7 +35,7 @@ export class Client { this.headers = { version: app.getVersion(), - port, + port: +port, nethash: app.getConfig().get("network.nethash"), "x-auth": "forger", "Content-Type": "application/json", @@ -37,103 +44,54 @@ export class Client { /** * Send the given block to the relay. - * @param {(Block|Object)} block - * @return {Object} */ - public async broadcast(block) { + public async broadcast(block: models.IBlockData): Promise> { this.logger.debug( `Broadcasting forged block id:${block.id} at height:${block.height.toLocaleString()} with ${ block.numberOfTransactions - } transactions to ${this.host} :package:`, + } transactions to ${this.host}`, ); - return this.__post(`${this.host}/internal/blocks`, { block }); + return this.post(`${this.host}/internal/blocks`, { block }); } /** * Sends the WAKEUP signal to the to relay hosts to check if synced and sync if necesarry */ - public async syncCheck() { - await this.__chooseHost(); - + public async syncCheck(): Promise { this.logger.debug(`Sending wake-up check to relay node ${this.host}`); - - try { - await this.__get(`${this.host}/internal/blockchain/sync`); - } catch (error) { - this.logger.error(`Could not sync check: ${error.message}`); - } + await this.get(`${this.host}/internal/blockchain/sync`); } /** * Get the current round. - * @return {Object} */ - public async getRound() { - try { - await this.__chooseHost(); - - const response = await this.__get(`${this.host}/internal/rounds/current`); - - return response.data.data; - } catch (e) { - return {}; - } + public async getRound(): Promise { + await this.selectHost(); + const response = await this.get>(`${this.host}/internal/rounds/current`); + return response.body.data; } /** * Get the current network quorum. - * @return {NetworkState} */ public async getNetworkState(): Promise { - try { - const response = await this.__get(`${this.host}/internal/network/state`, 4000); - const { data } = response.data; - - return NetworkState.parse(data); - } catch (e) { - this.logger.error(`Could not retrieve network state: ${this.host}/internal/network/state: ${e.message}`); - return new NetworkState(NetworkStateStatus.Unknown); - } + const response = await this.get>(`${this.host}/internal/network/state`, 4000); + return NetworkState.parse(response.body.data); } /** * Get all transactions that are ready to be forged. - * @return {Object} */ - public async getTransactions() { - try { - const response = await this.__get(`${this.host}/internal/transactions/forging`); - - return response.data.data; - } catch (e) { - return {}; - } - } - - /** - * Get a list of all active delegate usernames. - * @return {Object} - */ - public async getUsernames(wait = 0) { - await this.__chooseHost(wait); - - try { - const response = await this.__get(`${this.host}/internal/utils/usernames`); - - return response.data.data; - } catch (e) { - return {}; - } + public async getTransactions(): Promise { + const response = await this.get>(`${this.host}/internal/transactions/forging`); + return response.body.data; } /** * Emit the given event and payload to the local host. - * @param {String} event - * @param {Object} body - * @return {Object} */ - public async emitEvent(event, body) { + public async emitEvent(event: string, body: string | models.IBlockData | ITransactionData): Promise { // NOTE: Events need to be emitted to the localhost. If you need to trigger // actions on a remote host based on events you should be using webhooks // that get triggered by the events you wish to react to. @@ -143,43 +101,47 @@ export class Client { const host = this.hosts.find(item => allowedHosts.some(allowedHost => item.includes(allowedHost))); if (!host) { - return this.logger.error("Was unable to find any local hosts."); + this.logger.error("emitEvent: unable to find any local hosts."); + return; } - try { - await this.__post(`${host}/internal/utils/events`, { event, body }); - } catch (error) { - this.logger.error(`Failed to emit "${event}" to "${host}"`); - } + await this.post(`${host}/internal/utils/events`, { event, body }); } /** * Chose a responsive host. - * @return {void} */ - public async __chooseHost(wait = 0) { - const host = sample(this.hosts); - - try { - await this.__get(`${host}/peer/status`); - - this.host = host; - } catch (error) { - this.logger.debug(`${host} didn't respond to the forger. Trying another host :sparkler:`); - - if (wait > 0) { - await delay(wait); + public async selectHost(): Promise { + let queriedHosts = 0; + for (const host of this.hosts) { + try { + await this.get(`${host}/peer/status`); + this.host = host; + } catch (error) { + if (queriedHosts === this.hosts.length - 1) { + throw new HostNoResponseError(host); + } else { + this.logger.warn(`Failed to get response from ${host}. Trying another host.`); + } + } finally { + queriedHosts++; } - - await this.__chooseHost(wait); } } - public async __get(url, timeout: number = 2000) { - return axios.get(url, { headers: this.headers, timeout }); + private async get(url, timeout: number = 2000): Promise> { + try { + return httpie.get(url, { headers: this.headers, timeout }); + } catch (error) { + throw new RelayCommunicationError(url, error.message); + } } - public async __post(url, body) { - return axios.post(url, body, { headers: this.headers, timeout: 2000 }); + private async post(url, body): Promise> { + try { + return httpie.post(url, { body, headers: this.headers, timeout: 2000 }); + } catch (error) { + throw new RelayCommunicationError(url, error.message); + } } } diff --git a/packages/core-forger/src/errors.ts b/packages/core-forger/src/errors.ts new file mode 100644 index 0000000000..db2c4b2a6c --- /dev/null +++ b/packages/core-forger/src/errors.ts @@ -0,0 +1,31 @@ +// tslint:disable:max-classes-per-file + +export class ForgerError extends Error { + constructor(message: string) { + super(message); + + Object.defineProperty(this, "message", { + enumerable: false, + value: message, + }); + + Object.defineProperty(this, "name", { + enumerable: false, + value: this.constructor.name, + }); + + Error.captureStackTrace(this, this.constructor); + } +} + +export class RelayCommunicationError extends ForgerError { + constructor(endpoint: string, message: string) { + super(`Request to ${endpoint} failed, because of '${message}'.`); + } +} + +export class HostNoResponseError extends ForgerError { + constructor(host: string) { + super(`${host} didn't respond. Trying again later.`); + } +} diff --git a/packages/core-forger/src/index.ts b/packages/core-forger/src/index.ts index a2f186d220..319b548318 100644 --- a/packages/core-forger/src/index.ts +++ b/packages/core-forger/src/index.ts @@ -9,11 +9,11 @@ export const plugin: Container.PluginDescriptor = { alias: "forger", async register(container: Container.IContainer, options) { const forgerManager = new ForgerManager(options); - const forgers = await forgerManager.loadDelegates(options.bip38, options.password); + const forgers = await forgerManager.loadDelegates(options.bip38 as string, options.password as string); const logger = container.resolvePlugin("logger"); if (!forgers) { - logger.info("Forger is disabled :grey_exclamation:"); + logger.info("Forger is disabled"); return false; } diff --git a/packages/core-forger/src/manager.ts b/packages/core-forger/src/manager.ts index 97eca0aa28..efe33c4f47 100644 --- a/packages/core-forger/src/manager.ts +++ b/packages/core-forger/src/manager.ts @@ -1,25 +1,28 @@ import { app } from "@arkecosystem/core-container"; import { Logger } from "@arkecosystem/core-interfaces"; -import { NetworkStateStatus } from "@arkecosystem/core-p2p"; -import { models, slots } from "@arkecosystem/crypto"; -import delay from "delay"; -import isEmpty from "lodash/isEmpty"; -import uniq from "lodash/uniq"; +import { ICurrentRound, NetworkState, NetworkStateStatus } from "@arkecosystem/core-p2p"; +import { configManager, ITransactionData, models, networks, slots, Transaction } from "@arkecosystem/crypto"; +import isEmpty from "lodash.isempty"; +import uniq from "lodash.uniq"; import pluralize from "pluralize"; import { Client } from "./client"; +import { HostNoResponseError } from "./errors"; -const { Delegate, Transaction } = models; +const { Delegate } = models; export class ForgerManager { private logger = app.resolvePlugin("logger"); private config = app.getConfig(); - private secrets: any; - private network: any; - private client: any; - private delegates: any; - private usernames: any; - private isStopped: any; + + private secrets: string[]; + private network: networks.INetwork; + private client: Client; + private delegates: models.Delegate[]; + private usernames: { [key: string]: string }; + private isStopped: boolean; + private round: ICurrentRound; + private initialized: boolean; /** * Create a new forger manager instance. @@ -33,14 +36,11 @@ export class ForgerManager { /** * Load all delegates that forge. - * @param {String} bip38 - * @param {String} password - * @return {Array} */ - public async loadDelegates(bip38, password) { + public async loadDelegates(bip38: string, password: string): Promise { if (!bip38 && (!this.secrets || !this.secrets.length || !Array.isArray(this.secrets))) { this.logger.warn('No delegate found! Please check your "delegates.json" file and try again.'); - return; + return null; } this.secrets = uniq(this.secrets.map(secret => secret.trim())); @@ -52,135 +52,119 @@ export class ForgerManager { this.delegates.push(new Delegate(bip38, this.network, password)); } - await this.__loadUsernames(2000); - - const delegates = this.delegates.map( - delegate => `${this.usernames[delegate.publicKey]} (${delegate.publicKey})`, - ); - - this.logger.debug(`Loaded ${pluralize("delegate", delegates.length, true)}: ${delegates.join(", ")}`); + try { + await this.loadRound(); + } catch (error) { + this.logger.warn("Waiting for a responsive host."); + } return this.delegates; } /** * Start forging on the given node. - * @return {Object} */ - public async startForging() { - const slot = slots.getSlotNumber(); - - while (slots.getSlotNumber() === slot) { - await delay(100); - } - - return this.__monitor(null); + public async startForging(): Promise { + return this.checkLater(slots.getTimeInMsUntilNextSlot()); } /** * Stop forging on the given node. - * @return {void} */ - public async stop() { + public async stop(): Promise { this.isStopped = true; } /** * Monitor the node for any actions that trigger forging. - * @param {Object} round - * @return {Function} */ - public async __monitor(round): Promise { + public async __monitor(): Promise { try { if (this.isStopped) { - return false; + return; } - await this.__loadUsernames(); - - round = await this.client.getRound(); - if (!round.canForge) { - // this.logger.debug('Block already forged in current slot') - // technically it is possible to compute doing shennanigan with arkjs.slots lib + await this.loadRound(); - await delay(200); // basically looping until we lock at beginning of next slot - - return this.__monitor(round); + if (!this.round.canForge) { + // basically looping until we lock at beginning of next slot + return this.checkLater(200); } - const delegate = this.__isDelegateActivated(round.currentForger.publicKey); + const delegate = this.getDelegateByPublicKey(this.round.currentForger.publicKey); if (!delegate) { // this.logger.debug(`Current forging delegate ${ // round.currentForger.publicKey // } is not configured on this node.`) - if (this.__isDelegateActivated(round.nextForger.publicKey)) { - const username = this.usernames[round.nextForger.publicKey]; + if (this.getDelegateByPublicKey(this.round.nextForger.publicKey)) { + const username = this.usernames[this.round.nextForger.publicKey]; this.logger.info( - `Next forging delegate ${username} (${round.nextForger.publicKey}) is active on this node.`, + `Next forging delegate ${username} (${ + this.round.nextForger.publicKey + }) is active on this node.`, ); await this.client.syncCheck(); } - await delay(slots.getTimeInMsUntilNextSlot()); // we will check at next slot - - return this.__monitor(round); + return this.checkLater(slots.getTimeInMsUntilNextSlot()); } const networkState = await this.client.getNetworkState(); - - if (this.__parseNetworkState(networkState, delegate)) { - await this.__forgeNewBlock(delegate, round); + if (networkState.nodeHeight !== this.round.lastBlock.height) { + this.logger.warn( + `The NetworkState height (${networkState.nodeHeight}) and round height (${ + this.round.lastBlock.height + }) are out of sync. This indicates delayed blocks on the network.`, + ); } - await delay(slots.getTimeInMsUntilNextSlot()); // we will check at next slot + if (this.parseNetworkState(networkState, delegate)) { + await this.forgeNewBlock(delegate, this.round, networkState); + } - return this.__monitor(round); + return this.checkLater(slots.getTimeInMsUntilNextSlot()); } catch (error) { - // README: The Blockchain is not ready, monitor until it is instead of crashing. - if (error.response && error.response.status === 503) { - this.logger.warn(`Blockchain not ready - ${error.response.status} ${error.response.statusText}`); + if (error instanceof HostNoResponseError) { + this.logger.warn(error.message); + } else { + this.logger.error(JSON.stringify(error.stack)); + this.logger.error(`Forging failed: ${error.message}`); - await delay(2000); - - return this.__monitor(round); - } - - // README: The Blockchain is ready but an action still failed. - this.logger.error(`Forging failed: ${error.message} :bangbang:`); + if (!isEmpty(this.round)) { + this.logger.info( + `Round: ${this.round.current.toLocaleString()}, height: ${this.round.lastBlock.height.toLocaleString()}`, + ); + } - if (!isEmpty(round)) { - this.logger.info( - `Round: ${round.current.toLocaleString()}, height: ${round.lastBlock.height.toLocaleString()}`, - ); + this.client.emitEvent("forger.failed", error.message); } - await delay(2000); // no idea when this will be ok, so waiting 2s before checking again - - this.client.emitEvent("forger.failed", error.message); - - return this.__monitor(round); + // no idea when this will be ok, so waiting 2s before checking again + return this.checkLater(2000); } } /** * Creates new block by the delegate and sends it to relay node for verification and broadcast - * @param {Object} delegate - * @param {Object} round */ - public async __forgeNewBlock(delegate, round) { - // TODO: Disabled for now as this could cause a delay in forging that - // results in missing a block which we want to avoid. - // - // We should either use a very radical timeout like 500ms or look - // into another solution for broadcasting this specific event. - // - // this.client.emitEvent('forger.started', delegate.publicKey) + public async forgeNewBlock(delegate: models.Delegate, round, networkState: NetworkState) { + const transactions = await this.getTransactionsForForging(); - const transactions = await this.__getTransactionsForForging(); + const previousBlock = { + id: networkState.lastBlockId, + idHex: null, + height: networkState.nodeHeight, + }; + + if (configManager.getMilestone(networkState.nodeHeight).block.idFullSha256) { + previousBlock.idHex = previousBlock.id; + } else { + previousBlock.idHex = models.Block.toBytesHex(previousBlock.id); + } const blockOptions = { - previousBlock: round.lastBlock, + previousBlock, timestamp: round.timestamp, reward: round.reward, }; @@ -188,22 +172,21 @@ export class ForgerManager { const block = await delegate.forge(transactions, blockOptions); const username = this.usernames[delegate.publicKey]; - this.logger.info(`Forged new block ${block.data.id} by delegate ${username} (${delegate.publicKey}) :trident:`); + this.logger.info(`Forged new block ${block.data.id} by delegate ${username} (${delegate.publicKey})`); await this.client.broadcast(block.toJson()); this.client.emitEvent("block.forged", block.data); - transactions.forEach(transaction => this.client.emitEvent("transaction.forged", transaction.data)); + transactions.forEach(transaction => this.client.emitEvent("transaction.forged", transaction)); } /** * Gets the unconfirmed transactions from the relay nodes transaction pool */ - public async __getTransactionsForForging() { + public async getTransactionsForForging(): Promise { const response = await this.client.getTransactions(); - const transactions = response.transactions - ? response.transactions.map(serializedTx => new Transaction(serializedTx)) + ? response.transactions.map(serializedTx => Transaction.fromHex(serializedTx).data) : []; if (isEmpty(response)) { @@ -212,28 +195,17 @@ export class ForgerManager { this.logger.debug( `Received ${pluralize("transaction", transactions.length, true)} from the pool containing ${ response.poolSize - } :money_with_wings:`, + }`, ); } return transactions; } - /** - * Checks if delegate public key is in the loaded (active) delegates list - * @param {Object} PublicKey - * @return {Object} - */ - public __isDelegateActivated(queryPublicKey) { - return this.delegates.find(delegate => delegate.publicKey === queryPublicKey); - } - /** * Parses the given network state and decides if forging is allowed. - * @param {Object} networkState internal response - * @param {Booolean} isAllowedToForge */ - public __parseNetworkState(networkState, currentForger) { + public parseNetworkState(networkState: NetworkState, delegate: models.Delegate): boolean { if (networkState.status === NetworkStateStatus.Unknown) { this.logger.info("Failed to get network state from client. Will not forge."); return false; @@ -260,10 +232,10 @@ export class ForgerManager { ); for (const overHeightBlockHeader of overHeightBlockHeaders) { - if (overHeightBlockHeader.generatorPublicKey === currentForger.publicKey) { - const username = this.usernames[currentForger.publicKey]; + if (overHeightBlockHeader.generatorPublicKey === delegate.publicKey) { + const username = this.usernames[delegate.publicKey]; this.logger.warn( - `Possible double forging delegate: ${username} (${currentForger.publicKey}) - Block: ${ + `Possible double forging delegate: ${username} (${delegate.publicKey}) - Block: ${ overHeightBlockHeader.id }. Will not forge.`, ); @@ -283,10 +255,52 @@ export class ForgerManager { } /** - * Get a list of all active delegate usernames. - * @return {Object} + * Checks if delegate public key is in the loaded (active) delegates list */ - public async __loadUsernames(wait = 0) { - this.usernames = await this.client.getUsernames(wait); + private getDelegateByPublicKey(publicKey: string): models.Delegate | null { + return this.delegates.find(delegate => delegate.publicKey === publicKey); + } + + private async loadRound(): Promise { + this.round = await this.client.getRound(); + + this.usernames = this.round.delegates.reduce( + (acc, delegate) => Object.assign(acc, { [delegate.publicKey]: delegate.username }), + {}, + ); + + if (!this.initialized) { + this.printLoadedDelegates(); + } + + this.initialized = true; + } + + private checkLater(timeout: number): void { + setTimeout(() => this.__monitor(), timeout); + } + + private printLoadedDelegates() { + const activeDelegates = this.delegates.filter(delegate => this.usernames.hasOwnProperty(delegate.publicKey)); + + if (activeDelegates.length > 0) { + this.logger.debug( + `Loaded ${pluralize("active delegate", activeDelegates.length, true)}: ${activeDelegates + .map(({ publicKey }) => `${this.usernames[publicKey]} (${publicKey})`) + .join(", ")}`, + ); + } + + if (this.delegates.length > activeDelegates.length) { + const inactiveDelegates = this.delegates + .filter(delegate => !activeDelegates.includes(delegate)) + .map(delegate => delegate.publicKey); + + this.logger.debug( + `Loaded ${pluralize("inactive delegate", inactiveDelegates.length, true)}: ${inactiveDelegates.join( + ", ", + )}`, + ); + } } } diff --git a/packages/core-graphql/README.md b/packages/core-graphql/README.md deleted file mode 100644 index 7815d61650..0000000000 --- a/packages/core-graphql/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Ark Core - GraphQL - -

- -

- -## Documentation - -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core-graphql.html). - -## Security - -If you discover a security vulnerability within this package, please send an e-mail to security@ark.io. All security vulnerabilities will be promptly addressed. - -## Credits - -- [Brian Faust](https://github.com/faustbrian) -- [Joshua Noack](https://github.com/supaiku0) -- [Lúcio Rubens](https://github.com/luciorubeens) -- [All Contributors](../../../../contributors) - -## License - -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) diff --git a/packages/core-graphql/__tests__/__support__/setup.ts b/packages/core-graphql/__tests__/__support__/setup.ts deleted file mode 100644 index 326ac4d09f..0000000000 --- a/packages/core-graphql/__tests__/__support__/setup.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { app } from "@arkecosystem/core-container"; -import { registerWithContainer, setUpContainer } from "../../../core-test-utils/src/helpers/container"; - -jest.setTimeout(60000); - -const options = { - enabled: true, - host: "0.0.0.0", - port: 4005, -}; - -export const setUp = async () => { - process.env.CORE_GRAPHQL_ENABLED = "true"; - - await setUpContainer({ - exclude: ["@arkecosystem/core-api", "@arkecosystem/core-forger", "@arkecosystem/core-graphql"], - }); - - const { plugin } = require("../../src"); - await registerWithContainer(plugin, options); - - return app; -}; - -export const tearDown = async () => { - await app.tearDown(); - - const { plugin } = require("../../src"); - await plugin.deregister(app, options); -}; diff --git a/packages/core-graphql/__tests__/__support__/utils.ts b/packages/core-graphql/__tests__/__support__/utils.ts deleted file mode 100644 index c9685529ec..0000000000 --- a/packages/core-graphql/__tests__/__support__/utils.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { app } from "@arkecosystem/core-container"; -import { ApiHelpers } from "../../../core-test-utils/src/helpers/api"; - -class Helpers { - public async request(query) { - const url = "http://localhost:4005/graphql"; - const server = app.resolvePlugin("graphql"); - - return ApiHelpers.request(server, "POST", url, {}, { query }); - } -} - -/** - * @type {Helpers} - */ -export const utils = new Helpers(); diff --git a/packages/core-graphql/__tests__/api/address.test.ts b/packages/core-graphql/__tests__/api/address.test.ts deleted file mode 100644 index 0a3335b69d..0000000000 --- a/packages/core-graphql/__tests__/api/address.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import "@arkecosystem/core-test-utils"; - -import { setUp, tearDown } from "../__support__/setup"; -import { utils } from "../__support__/utils"; - -beforeAll(async () => { - await setUp(); -}); - -afterAll(() => { - tearDown(); -}); - -describe("GraphQL API { address }", () => { - describe("GraphQL resolver for Address", () => { - it("should get wallter for a correctly formatted Address", async () => { - const query = '{ wallet(address: "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn") { producedBlocks } }'; - const response = await utils.request(query); - - expect(response).toBeSuccessfulResponse(); - - const data = response.data.data; - expect(data).toBeObject(); - expect(data.wallet).toBeObject(); - - expect(data.wallet.producedBlocks).toBe(0); - }); - it("should return an error for an incorrectly formatted Address", async () => { - const query = '{ wallet(address: "bad address") { producedBlocks } }'; - const response = await utils.request(query); - - expect(response).not.toBeSuccessfulResponse(); - - const data = response.data.data; - expect(data).toBeFalsy(); - expect(response.data.errors[0]).toBeObject(); - expect(response.data.errors[0].message).not.toBeNull(); - }); - }); -}); diff --git a/packages/core-graphql/__tests__/api/block.test.ts b/packages/core-graphql/__tests__/api/block.test.ts deleted file mode 100644 index 0ee445bf95..0000000000 --- a/packages/core-graphql/__tests__/api/block.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import "@arkecosystem/core-test-utils"; -import genesisBlock from "../../../core-test-utils/src/config/testnet/genesisBlock.json"; - -import { setUp, tearDown } from "../__support__/setup"; -import { utils } from "../__support__/utils"; - -beforeAll(async () => { - await setUp(); -}); - -afterAll(() => { - tearDown(); -}); - -describe("GraphQL API { block }", () => { - describe("GraphQL queries for Block", () => { - it("should get a block by its id", async () => { - const query = `{ block(id:"${genesisBlock.id}") { id } }`; - const response = await utils.request(query); - - expect(response).toBeSuccessfulResponse(); - - const data = response.data.data; - expect(data).toBeObject(); - expect(data.block).toBeObject(); - expect(data.block.id).toBe(genesisBlock.id); - }); - }); -}); diff --git a/packages/core-graphql/__tests__/api/blocks.test.ts b/packages/core-graphql/__tests__/api/blocks.test.ts deleted file mode 100644 index 1403cddc4a..0000000000 --- a/packages/core-graphql/__tests__/api/blocks.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import "@arkecosystem/core-test-utils"; -import genesisBlock from "../../../core-test-utils/src/config/testnet/genesisBlock.json"; - -import { setUp, tearDown } from "../__support__/setup"; -import { utils } from "../__support__/utils"; - -beforeAll(async () => { - await setUp(); -}); - -afterAll(() => { - tearDown(); -}); - -describe("GraphQL API { blocks }", () => { - describe("GraphQL queries for Blocks - filter by generatorPublicKey", () => { - it("should get blocks by generatorPublicKey", async () => { - const query = `{ blocks(filter: { generatorPublicKey: "${genesisBlock.generatorPublicKey}" }) { id } }`; - const response = await utils.request(query); - - expect(response).toBeSuccessfulResponse(); - - const data = response.data.data; - expect(data).toBeObject(); - expect(data.blocks).toEqual([{ id: genesisBlock.id }]); - }); - }); - - describe("GraphQL queries for Blocks - testing relationships", () => { - it("should verify that relationships are valid", async () => { - const query = "{ blocks(limit: 1) { generator { address } } }"; - const response = await utils.request(query); - - expect(response).toBeSuccessfulResponse(); - - const data = response.data.data; - expect(data).toBeObject(); - expect(data.blocks[0].generator.address).toEqual("AP6kAVdX1zQ3S8mfDnnHx9GaAohEqQUins"); - }); - }); - - describe("GraphQL queries for Blocks - testing api errors", () => { - it("should not be a successful query", async () => { - const query = "{ blocks(filter: { vers } ) { id } }"; - const response = await utils.request(query); - - expect(response).not.toBeSuccessfulResponse(); - - const error = response.data.errors; - expect(error).toBeArray(); - expect(response.status).toEqual(400); - }); - }); -}); diff --git a/packages/core-graphql/__tests__/api/transaction.test.ts b/packages/core-graphql/__tests__/api/transaction.test.ts deleted file mode 100644 index 131d8f6150..0000000000 --- a/packages/core-graphql/__tests__/api/transaction.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import "@arkecosystem/core-test-utils"; -import genesisBlock from "../../../core-test-utils/src/config/testnet/genesisBlock.json"; - -import { setUp, tearDown } from "../__support__/setup"; -import { utils } from "../__support__/utils"; - -beforeAll(async () => { - await setUp(); -}); - -afterAll( async () => { - await tearDown(); -}); - -describe("GraphQL API { transaction }", () => { - describe("GraphQL queries for Transaction", () => { - it("should get a transaction by its id", async () => { - const query = `{ transaction(id:"${genesisBlock.transactions[0].id}") { id } }`; - const response = await utils.request(query); - - await expect(response).toBeSuccessfulResponse(); - - const data = response.data.data; - expect(data).toBeObject(); - expect(data.transaction).toBeObject(); - expect(data.transaction.id).toBe(genesisBlock.transactions[0].id); - }); - }); -}); diff --git a/packages/core-graphql/__tests__/api/transactions.test.ts b/packages/core-graphql/__tests__/api/transactions.test.ts deleted file mode 100644 index 6ed78078fc..0000000000 --- a/packages/core-graphql/__tests__/api/transactions.test.ts +++ /dev/null @@ -1,174 +0,0 @@ -import "@arkecosystem/core-test-utils"; -import genesisBlock from "../../../core-test-utils/src/config/testnet/genesisBlock.json"; - -import { setUp, tearDown } from "../__support__/setup"; -import { utils } from "../__support__/utils"; - -beforeAll(async () => { - await setUp(); -}); - -afterAll(() => { - tearDown(); -}); - -describe("GraphQL API { transactions }", () => { - describe("GraphQL queries for Transactions - all", () => { - it("should get 100 transactions", async () => { - const query = "{ transactions { id } }"; - const response = await utils.request(query); - - expect(response).toBeSuccessfulResponse(); - - const data = response.data.data; - expect(data).toBeObject(); - expect(data.transactions.length).toBe(100); - }); - }); - - describe("GraphQL queries for Transactions - orderBy", () => { - it("should get 100 transactionsin ascending order of their id", async () => { - const query = '{ transactions(orderBy: { field: "id", direction: ASC }) { id } }'; - const response = await utils.request(query); - - expect(response).toBeSuccessfulResponse(); - - const data = response.data.data; - expect(data).toBeObject(); - expect(data.transactions.length).toBe(100); - expect(data.transactions.sort((a, b) => (+a <= +b ? -1 : 0))).toEqual(data.transactions); - }); - }); - - describe("GraphQL queries for Transactions - filter by fee", () => { - it("should get all transactions with fee = 0", async () => { - const query = "{ transactions(filter: { fee: 0 }) { id } }"; - const response = await utils.request(query); - - expect(response).toBeSuccessfulResponse(); - - const data = response.data.data; - expect(data).toBeObject(); - expect(data.transactions.length).toBe(100); // because of default limit = 100 - }); - - it("should get no transaction with fee = 987", async () => { - const query = "{ transactions(filter: { fee: 987 }) { id } }"; - const response = await utils.request(query); - - expect(response).toBeSuccessfulResponse(); - - const data = response.data.data; - expect(data).toBeObject(); - expect(data.transactions.length).toBe(0); - }); - }); - - describe("GraphQL queries for Transactions - filter by blockId", () => { - it("should get transactions for given blockId", async () => { - const query = `{ transactions(filter: { blockId: "${genesisBlock.id}" }) { id } }`; - const response = await utils.request(query); - - expect(response).toBeSuccessfulResponse(); - - const data = response.data.data; - expect(data).toBeObject(); - - const genesisBlockTransactionIds = genesisBlock.transactions.map(transaction => transaction.id); - data.transactions.forEach(transaction => { - expect(genesisBlockTransactionIds).toContain(transaction.id); - }); - }); - }); - - describe("GraphQL queries for Transactions - filter by senderPublicKey", () => { - it("should get transactions for given senderPublicKey", async () => { - const query = `{ transactions(filter: { senderPublicKey: "${ - genesisBlock.transactions[0].senderPublicKey - }" }) { id } }`; - const response = await utils.request(query); - - expect(response).toBeSuccessfulResponse(); - - const data = response.data.data; - expect(data).toBeObject(); - // tslint:disable-next-line:max-line-length - expect(data.transactions.length).toEqual(51); // number of outgoing transactions for the 0th transaction's sender address - - const genesisBlockTransactionIds = genesisBlock.transactions.map(transaction => transaction.id); - - data.transactions.forEach(transaction => { - expect(genesisBlockTransactionIds).toContain(transaction.id); - }); - }); - }); - - describe("GraphQL queries for Transactions - filter by recipientId", () => { - it("should get transactions for given recipientId", async () => { - const query = '{ transactions(filter: { recipientId: "AHXtmB84sTZ9Zd35h9Y1vfFvPE2Xzqj8ri" }) { id } }'; - const response = await utils.request(query); - - expect(response).toBeSuccessfulResponse(); - - const data = response.data.data; - expect(data).toBeObject(); - - expect(data.transactions.length).toBe(2); - }); - }); - - describe("GraphQL queries for Transactions - filter by type", () => { - it("should get transactions for given type", async () => { - const query = "{ transactions(filter: { type: TRANSFER } ) { type } }"; - const response = await utils.request(query); - expect(response).toBeSuccessfulResponse(); - - const data = response.data.data; - expect(data).toBeObject(); - - data.transactions.forEach(tx => { - expect(tx.type).toBe(Number(0)); - }); - }); - }); - - describe("GraphQL queries for Transactions - using orderBy, limit", () => { - it("should get 5 transactions in order of ASCending address", async () => { - const query = '{ transactions(orderBy: { field: "id", direction: ASC }, limit: 5 ) { id } }'; - const response = await utils.request(query); - expect(response).toBeSuccessfulResponse(); - - const data = response.data.data; - expect(data).toBeObject(); - expect(data.transactions.length).toBe(5); - - expect(parseInt(data.transactions[0].id, 16)).toBeLessThan(parseInt(data.transactions[1].id, 16)); - }); - }); - - describe("GraphQL queries for Transactions - testing relationships", () => { - it("should verify that relationships are valid", async () => { - const query = "{ transactions(limit: 1) { recipient { address } } }"; - const response = await utils.request(query); - - expect(response).toBeSuccessfulResponse(); - - const data = response.data.data; - expect(data).toBeObject(); - expect(data.transactions[0].recipient.address).not.toBeNull(); - }); - }); - - describe("GraphQL queries for Transactions - testing api errors", () => { - it("should not be a successful query", async () => { - const query = "{ transaction(filter: { vers } ) { id } }"; - const response = await utils.request(query); - - expect(response).not.toBeSuccessfulResponse(); - - const error = response.data.errors; - expect(error).toBeArray(); - expect(response.status).toEqual(400); - }); - }); -}); diff --git a/packages/core-graphql/__tests__/api/wallet.test.ts b/packages/core-graphql/__tests__/api/wallet.test.ts deleted file mode 100644 index f0a12e6ff2..0000000000 --- a/packages/core-graphql/__tests__/api/wallet.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import "@arkecosystem/core-test-utils"; -import genesisBlock from "../../../core-test-utils/src/config/testnet/genesisBlock.json"; - -import { setUp, tearDown } from "../__support__/setup"; -import { utils } from "../__support__/utils"; - -beforeAll(async () => { - await setUp(); -}); - -afterAll(() => { - tearDown(); -}); - -describe("GraphQL API { wallet }", () => { - describe("GraphQL queries for Wallet", () => { - it("should get a wallet by address", async () => { - const query = `{ wallet(address:"${genesisBlock.transactions[0].senderId}") { address } }`; - const response = await utils.request(query); - - expect(response).toBeSuccessfulResponse(); - - const data = response.data.data; - expect(data).toBeObject(); - expect(data.wallet).toBeObject(); - expect(data.wallet.address).toBe(genesisBlock.transactions[0].senderId); - }); - }); -}); diff --git a/packages/core-graphql/__tests__/api/wallets.test.ts b/packages/core-graphql/__tests__/api/wallets.test.ts deleted file mode 100644 index cb1dc40d3a..0000000000 --- a/packages/core-graphql/__tests__/api/wallets.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -/* tslint:disable:max-line-length */ - -import "@arkecosystem/core-test-utils"; - -import { setUp, tearDown } from "../__support__/setup"; -import { utils } from "../__support__/utils"; - -beforeAll(async () => { - await setUp(); -}); - -afterAll(() => { - tearDown(); -}); - -describe("GraphQL API { wallets }", () => { - describe("GraphQL queries for Wallets - get all", () => { - it("should get all wallets", async () => { - const query = "{ wallets { address } }"; - const response = await utils.request(query); - - expect(response).toBeSuccessfulResponse(); - - const data = response.data.data; - expect(data).toBeObject(); - expect(data.wallets.length).toBe(53); - // TODO why 53 ? From genesis block I can count 52, but there is an additional "AP6kAVdX1zQ3S8mfDnnHx9GaAohEqQUins" wallet. What did I miss ? - }); - }); - - describe("GraphQL queries for Wallets - filter by vote", () => { - it("should get all wallets with specific vote", async () => { - const query = - '{ wallets(filter: { vote: "036f612457adc81041662e664ca4ae64f844b412065f2b7d2f9f7d305e59c908cd" }) { address } }'; - const response = await utils.request(query); - - expect(response).toBeSuccessfulResponse(); - - const data = response.data.data; - expect(data).toBeObject(); - expect(data.wallets.length).toBe(1); - }); - - it("should get no wallet with unknown vote", async () => { - const query = '{ wallets(filter: { vote: "unknownPublicKey" }) { address } }'; - const response = await utils.request(query); - - expect(response).toBeSuccessfulResponse(); - - const data = response.data.data; - expect(data).toBeObject(); - expect(data.wallets.length).toBe(0); - }); - }); - - describe("GraphQL queries for Wallets - using orderBy, limit", () => { - it("should get 5 wallets in order of ASCending address", async () => { - const query = '{ wallets(orderBy: { field: "address", direction: ASC }, limit: 5 ) { address } }'; - const response = await utils.request(query); - - expect(response).toBeSuccessfulResponse(); - - const data = response.data.data; - expect(data).toBeObject(); - expect(data.wallets.length).toBe(5); - expect(data.wallets.sort((a, b) => (+a <= +b ? -1 : 0))).toEqual(data.wallets); - }); - }); - - describe("GraphQL queries for Wallets - testing relationships", () => { - it("should verify that relationships are valid", async () => { - const query = "{ wallets(limit: 1) { transactions { id } } }"; - const response = await utils.request(query); - - expect(response).toBeSuccessfulResponse(); - - expect(response.data.errors[0]).toBeObject(); // relationships doesn't function well (unimplemented) - }); - }); - - describe("GraphQL queries for Wallets - testing api errors", () => { - it("should not be a successful query", async () => { - const query = "{ wallets(filter: { vers } ) { address } }"; - const response = await utils.request(query); - - expect(response).not.toBeSuccessfulResponse(); - - const error = response.data.errors; - expect(error).toBeArray(); - expect(response.status).toEqual(400); - }); - }); -}); diff --git a/packages/core-graphql/package.json b/packages/core-graphql/package.json deleted file mode 100644 index 31ed30c4a0..0000000000 --- a/packages/core-graphql/package.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "name": "@arkecosystem/core-graphql", - "description": "GraphQL Integration for Ark Core", - "version": "2.2.1", - "contributors": [ - "Lúcio Rubens " - ], - "license": "MIT", - "main": "dist/index.js", - "files": [ - "dist" - ], - "scripts": { - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", - "prepublishOnly": "yarn build", - "pretest": "yarn lint && yarn build", - "compile": "../../node_modules/typescript/bin/tsc", - "build": "yarn clean && yarn compile", - "build:watch": "yarn clean && yarn compile -w", - "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "test": "cross-env CORE_ENV=test jest --runInBand --forceExit", - "test:coverage": "cross-env CORE_ENV=test jest --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$' --runInBand --forceExit", - "test:debug": "cross-env CORE_ENV=test node --inspect-brk ../../node_modules/.bin/jest --runInBand", - "test:watch": "cross-env CORE_ENV=test jest --runInBand --watch", - "test:watch:all": "cross-env CORE_ENV=test jest --runInBand --watchAll", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" - }, - "dependencies": { - "@arkecosystem/core-container": "^2.2.1", - "@arkecosystem/core-http-utils": "^2.2.1", - "@arkecosystem/core-interfaces": "^2.2.1", - "@arkecosystem/crypto": "^2.2.1", - "apollo-server-hapi": "^2.4.0", - "dayjs-ext": "^2.2.0", - "graphql-tools-types": "^1.2.1" - }, - "devDependencies": { - "@arkecosystem/core-test-utils": "^2.2.1" - }, - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=10.x" - }, - "jest": { - "preset": "../../jest-preset.json" - } -} diff --git a/packages/core-graphql/src/apollo-server.ts b/packages/core-graphql/src/apollo-server.ts deleted file mode 100644 index 0e76dfab3b..0000000000 --- a/packages/core-graphql/src/apollo-server.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ApolloServer } from "apollo-server-hapi"; -import { typeDefs } from "./defs"; -import { resolvers } from "./resolvers"; - -/** - * Schema used by the Apollo GraphQL plugin for the hapi.js server. - */ -export const apolloServer = new ApolloServer({ - typeDefs, - resolvers, -}); diff --git a/packages/core-graphql/src/defaults.ts b/packages/core-graphql/src/defaults.ts deleted file mode 100644 index a6ee749195..0000000000 --- a/packages/core-graphql/src/defaults.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const defaults = { - enabled: false, - host: process.env.CORE_GRAPHQL_HOST || "0.0.0.0", - port: process.env.CORE_GRAPHQL_PORT || 4005, - path: "/graphql", -}; diff --git a/packages/core-graphql/src/defs/index.ts b/packages/core-graphql/src/defs/index.ts deleted file mode 100644 index de6af74419..0000000000 --- a/packages/core-graphql/src/defs/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { inputs } from "./inputs"; -import { root } from "./root"; -import { types } from "./types"; - -export const typeDefs = ` - ${inputs} - ${root} - ${types} -`; diff --git a/packages/core-graphql/src/defs/inputs.ts b/packages/core-graphql/src/defs/inputs.ts deleted file mode 100644 index d6e087213c..0000000000 --- a/packages/core-graphql/src/defs/inputs.ts +++ /dev/null @@ -1,44 +0,0 @@ -export const inputs = ` - scalar JSON - scalar Limit - scalar Offset - scalar Address - - enum OrderDirection { - ASC - DESC - } - - enum TransactionType { - TRANSFER, - SECOND_SIGNATURE, - DELEGATE, - VOTE, - MULTI_SIGNATURE, - IPFS, - TIMELOCK_TRANSFER, - MULTI_PAYMENT, - DELEGATE_RESIGNATION - } - - input TransactionFilter { - fee: Float - blockId: String - senderPublicKey: String - recipientId: String - type: TransactionType - } - - input BlockFilter { - generatorPublicKey: String - } - - input WalletFilter { - vote: String - } - - input OrderByInput { - field: String - direction: OrderDirection - } -`; diff --git a/packages/core-graphql/src/defs/root.ts b/packages/core-graphql/src/defs/root.ts deleted file mode 100644 index 25b2243ec1..0000000000 --- a/packages/core-graphql/src/defs/root.ts +++ /dev/null @@ -1,14 +0,0 @@ -export const root = ` - type Query { - block(id: String): Block - blocks(limit: Limit, offset: Offset, orderBy: OrderByInput, filter: BlockFilter): [Block] - transaction(id: String): Transaction - transactions(limit: Limit, orderBy: OrderByInput, filter: TransactionFilter): [Transaction] - wallet(address: Address, publicKey: String, username: String): Wallet - wallets(limit: Limit, orderBy: OrderByInput, filter: WalletFilter): [Wallet] - } - - schema { - query: Query - } -`; diff --git a/packages/core-graphql/src/defs/types.ts b/packages/core-graphql/src/defs/types.ts deleted file mode 100644 index 22db2aa611..0000000000 --- a/packages/core-graphql/src/defs/types.ts +++ /dev/null @@ -1,49 +0,0 @@ -export const types = ` - type Block { - id: String - version: Int! - timestamp: Int! - previousBlock: String - height: Int! - numberOfTransactions: Int! - totalAmount: Float - totalFee: Float - reward: Float - payloadLength: Int! - payloadHash: String - generatorPublicKey: String - blockSignature: String - transactions(limit: Limit, offset: Offset, orderBy: OrderByInput, filter: TransactionFilter): [Transaction] - generator: Wallet - } - - type Transaction { - id: String - version: Int! - timestamp: Int! - senderPublicKey: String - recipientId: Address - type: Int! - vendorField: String - amount: Float - fee: Float - signature: String - block: Block - recipient: Wallet - sender: Wallet - } - - type Wallet { - address: Address - publicKey: String - secondPublicKey: String - vote: String - username: String - balance: Float - voteBalance: Float - producedBlocks: Float - missedBlocks: Float - transactions(limit: Limit, offset: Offset, orderBy: OrderByInput): [Transaction] - blocks(limit: Limit, offset: Offset, orderBy: OrderByInput): [Block] - } -`; diff --git a/packages/core-graphql/src/helpers/format-orderBy.ts b/packages/core-graphql/src/helpers/format-orderBy.ts deleted file mode 100644 index 61b32556e9..0000000000 --- a/packages/core-graphql/src/helpers/format-orderBy.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Logic used by our orderBy input - * @param {Object} parameter - * @param {String} defaultValue - * @return {String} - */ -export function formatOrderBy(parameter, defaultValue) { - let order; - - if (parameter) { - order = `${parameter.field}:${parameter.direction.toLowerCase()}`; - } - - return order || defaultValue; -} diff --git a/packages/core-graphql/src/helpers/index.ts b/packages/core-graphql/src/helpers/index.ts deleted file mode 100644 index d667ee29c0..0000000000 --- a/packages/core-graphql/src/helpers/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { formatOrderBy } from "./format-orderBy"; -import { unserializeTransactions } from "./unserialize-transactions"; - -export { formatOrderBy, unserializeTransactions }; diff --git a/packages/core-graphql/src/helpers/unserialize-transactions.ts b/packages/core-graphql/src/helpers/unserialize-transactions.ts deleted file mode 100644 index 545edc2f43..0000000000 --- a/packages/core-graphql/src/helpers/unserialize-transactions.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { models } from "@arkecosystem/crypto"; -const { Transaction } = models; - -/** - * Deserialize multiple transactions - */ -export async function unserializeTransactions(data) { - const deserialize = buffer => { - const serialized = Buffer.from(buffer).toString("hex"); - return Transaction.deserialize(serialized); - }; - - if (Array.isArray(data)) { - return data.reduce((total, value, key) => { - total.push(deserialize(value.serialized)); - - return total; - }, []); - } - return deserialize(data); -} diff --git a/packages/core-graphql/src/index.ts b/packages/core-graphql/src/index.ts deleted file mode 100644 index 867988e21d..0000000000 --- a/packages/core-graphql/src/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Container, Logger } from "@arkecosystem/core-interfaces"; -import { defaults } from "./defaults"; -import { startServer } from "./server"; - -/** - * The struct used by the plugin manager. - * @type {Object} - */ -export const plugin: Container.PluginDescriptor = { - pkg: require("../package.json"), - defaults, - alias: "graphql", - async register(container: Container.IContainer, options) { - if (!options.enabled) { - container.resolvePlugin("logger").info("GraphQL API is disabled :grey_exclamation:"); - return; - } - - return startServer(options); - }, - async deregister(container: Container.IContainer, options) { - if (options.enabled) { - container.resolvePlugin("logger").info("Stopping GraphQL API"); - - return container.resolvePlugin("graphql").stop(); - } - }, -}; diff --git a/packages/core-graphql/src/repositories/blocks.ts b/packages/core-graphql/src/repositories/blocks.ts deleted file mode 100644 index f1e12c66a2..0000000000 --- a/packages/core-graphql/src/repositories/blocks.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { Repository } from "./repository"; -import { buildFilterQuery } from "./utils/filter-query"; - -class BlocksRepository extends Repository { - /** - * Get all blocks for the given parameters. - * @param {Object} parameters - * @return {Object} - */ - public async findAll(parameters: any = {}) { - const selectQuery = this.query.select().from(this.query); - const countQuery = this._makeEstimateQuery(); - - const applyConditions = queries => { - const conditions = Object.entries(this._formatConditions(parameters)); - - if (conditions.length) { - const first = conditions.shift(); - - for (const item of queries) { - item.where(this.query[first[0]].equals(first[1])); - - for (const condition of conditions) { - item.and(this.query[condition[0]].equals(condition[1])); - } - } - } - }; - - applyConditions([selectQuery, countQuery]); - - return this._findManyWithCount(selectQuery, countQuery, { - limit: parameters.limit || 100, - offset: parameters.offset || 0, - orderBy: this.__orderBy(parameters), - }); - } - - /** - * Get all blocks for the given generator. - * @param {String} generatorPublicKey - * @param {Object} paginator - * @return {Object} - */ - public async findAllByGenerator(generatorPublicKey, paginator) { - return this.findAll({ ...{ generatorPublicKey }, ...paginator }); - } - - /** - * Get a block. - * @param {Number} id - * @return {Object} - */ - public async findById(id) { - const query = this.query - .select() - .from(this.query) - .where(this.query.id.equals(id)); - - return this._find(query); - } - - /** - * Get the last block for the given generator. - * TODO is this right? - * @param {String} generatorPublicKey - * @return {Object} - */ - public async findLastByPublicKey(generatorPublicKey) { - const query = this.query - .select(this.query.id, this.query.timestamp) - .from(this.query) - .where(this.query.generator_public_key.equals(generatorPublicKey)) - .order(this.query.height.desc); - - return this._find(query); - } - - /** - * Search all blocks. - * @param {Object} parameters - * @return {Object} - */ - public async search(parameters) { - const selectQuery = this.query.select().from(this.query); - const countQuery = this._makeEstimateQuery(); - - const applyConditions = queries => { - const conditions = buildFilterQuery(this._formatConditions(parameters), { - exact: ["id", "version", "previous_block", "payload_hash", "generator_public_key", "block_signature"], - between: [ - "timestamp", - "height", - "number_of_transactions", - "total_amount", - "total_fee", - "reward", - "payload_length", - ], - }); - - if (conditions.length) { - const first = conditions.shift(); - - for (const item of queries) { - item.where(this.query[first.column][first.method](first.value)); - - for (const condition of conditions) { - item.and(this.query[condition.column][condition.method](condition.value)); - } - } - } - }; - - applyConditions([selectQuery, countQuery]); - - return this._findManyWithCount(selectQuery, countQuery, { - limit: parameters.limit, - offset: parameters.offset, - orderBy: this.__orderBy(parameters), - }); - } - - public getModel() { - return (this.databaseService.connection as any).models.block; - } - - public __orderBy(parameters) { - return parameters.orderBy ? parameters.orderBy.split(":").map(p => p.toLowerCase()) : ["height", "desc"]; - } -} - -export const blockRepository = new BlocksRepository(); diff --git a/packages/core-graphql/src/repositories/index.ts b/packages/core-graphql/src/repositories/index.ts deleted file mode 100644 index d747df1177..0000000000 --- a/packages/core-graphql/src/repositories/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { blockRepository } from "./blocks"; -import { transactionRepository } from "./transactions"; - -export { blockRepository, transactionRepository }; diff --git a/packages/core-graphql/src/repositories/repository.ts b/packages/core-graphql/src/repositories/repository.ts deleted file mode 100644 index 21499a3fd0..0000000000 --- a/packages/core-graphql/src/repositories/repository.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { app } from "@arkecosystem/core-container"; -import { Database, TransactionPool } from "@arkecosystem/core-interfaces"; - -export abstract class Repository { - public databaseService = app.resolvePlugin("database"); - public transactionPool = app.resolvePlugin("transactionPool"); - public cache = this.databaseService.cache; - public model = this.getModel(); - public query = this.model.query(); - - public abstract getModel(): any; - - public async _find(query) { - return (this.databaseService.connection as any).query.oneOrNone(query.toQuery()); - } - - public async _findMany(query) { - return (this.databaseService.connection as any).query.manyOrNone(query.toQuery()); - } - - public async _findManyWithCount(selectQuery, countQuery, { limit, offset, orderBy }) { - const { count } = await this._find(countQuery); - - selectQuery - .order(this.query[orderBy[0]][orderBy[1]]) - .offset(offset) - .limit(limit); - - limit = 100; - offset = 0; - const rows = await this._findMany(selectQuery); - return { - rows, - count: +count, - }; - } - - public _makeCountQuery() { - return this.query.select("count(*) AS count").from(this.query); - } - - public _makeEstimateQuery() { - return this.query.select("count(*) AS count").from(`${this.model.getTable()} TABLESAMPLE SYSTEM (100)`); - } - - public _formatConditions(parameters) { - const columns = this.model.getColumnSet().columns.map(column => ({ - name: column.name, - prop: column.prop || column.name, - })); - - const columnNames = columns.map(column => column.name); - const columnProps = columns.map(column => column.prop); - - const filter = args => args.filter(arg => columnNames.includes(arg) || columnProps.includes(arg)); - - return filter(Object.keys(parameters)).reduce((items, item) => { - const columnName = columns.find(column => column.prop === item).name; - - items[columnName] = parameters[item]; - - return items; - }, {}); - } -} diff --git a/packages/core-graphql/src/repositories/transactions.ts b/packages/core-graphql/src/repositories/transactions.ts deleted file mode 100644 index 602eeb0c3f..0000000000 --- a/packages/core-graphql/src/repositories/transactions.ts +++ /dev/null @@ -1,439 +0,0 @@ -import { constants, slots } from "@arkecosystem/crypto"; -import dayjs from "dayjs-ext"; - -import { Repository } from "./repository"; -import { buildFilterQuery } from "./utils/filter-query"; - -const { TransactionTypes } = constants; - -class TransactionsRepository extends Repository { - /** - * Get all transactions. - * @param {Object} params - * @return {Object} - */ - public async findAll(parameters: any = {}) { - const selectQuery = this.query.select().from(this.query); - const countQuery = this._makeEstimateQuery(); - - if (parameters.senderId) { - const senderPublicKey = this.__publicKeyFromSenderId(parameters.senderId); - - if (!senderPublicKey) { - return { rows: [], count: 0 }; - } - - parameters.senderPublicKey = senderPublicKey; - } - - if (parameters.type) { - parameters.type = TransactionTypes[parameters.type]; - } - - const applyConditions = queries => { - const conditions = Object.entries(this._formatConditions(parameters)); - - if (conditions.length) { - const first = conditions.shift(); - - for (const item of queries) { - item.where(this.query[first[0]].equals(first[1])); - - for (const condition of conditions) { - item.and(this.query[condition[0]].equals(condition[1])); - } - } - } - }; - - applyConditions([selectQuery, countQuery]); - - const results = await this._findManyWithCount(selectQuery, countQuery, { - limit: parameters.limit || 100, - offset: parameters.offset || 0, - orderBy: this.__orderBy(parameters), - }); - - results.rows = await this.__mapBlocksToTransactions(results.rows); - return results; - } - - /** - * Get all transactions (LEGACY, for V1 only). - * @param {Object} params - * @return {Object} - */ - public async findAllLegacy(parameters: any = {}) { - const selectQuery = this.query.select(this.query.block_id, this.query.serialized).from(this.query); - const countQuery = this._makeEstimateQuery(); - - if (parameters.senderId) { - parameters.senderPublicKey = this.__publicKeyFromSenderId(parameters.senderId); - } - - const applyConditions = queries => { - const conditions = Object.entries(this._formatConditions(parameters)); - - if (conditions.length) { - const first = conditions.shift(); - - for (const item of queries) { - item.where(this.query[first[0]].equals(first[1])); - - for (const [key, value] of conditions) { - item.or(this.query[key].equals(value)); - } - } - } - }; - - applyConditions([selectQuery, countQuery]); - - const results = await this._findManyWithCount(selectQuery, countQuery, { - limit: parameters.limit, - offset: parameters.offset, - orderBy: this.__orderBy(parameters), - }); - - results.rows = await this.__mapBlocksToTransactions(results.rows); - - return results; - } - - /** - * Get all transactions for the given Wallet object. - * @param {Wallet} wallet - * @param {Object} parameters - * @return {Object} - */ - public async findAllByWallet(wallet, parameters: any = {}) { - const selectQuery = this.query.select(this.query.block_id, this.query.serialized).from(this.query); - const countQuery = this._makeEstimateQuery(); - - const applyConditions = queries => { - for (const item of queries) { - item.where(this.query.sender_public_key.equals(wallet.publicKey)).or( - this.query.recipient_id.equals(wallet.address), - ); - } - }; - - applyConditions([selectQuery, countQuery]); - - const results = await this._findManyWithCount(selectQuery, countQuery, { - limit: parameters.limit, - offset: parameters.offset, - orderBy: this.__orderBy(parameters), - }); - - results.rows = await this.__mapBlocksToTransactions(results.rows); - - return results; - } - - /** - * Get all transactions for the given sender public key. - * @param {String} senderPublicKey - * @param {Object} parameters - * @return {Object} - */ - public async findAllBySender(senderPublicKey, parameters = {}) { - return this.findAll({ ...{ senderPublicKey }, ...parameters }); - } - - /** - * Get all transactions for the given recipient address. - * @param {String} recipientId - * @param {Object} parameters - * @return {Object} - */ - public async findAllByRecipient(recipientId, parameters = {}) { - return this.findAll({ ...{ recipientId }, ...parameters }); - } - - /** - * Get all vote transactions for the given sender public key. - * TODO rename to findAllVotesBySender or not? - * @param {String} senderPublicKey - * @param {Object} parameters - * @return {Object} - */ - public async allVotesBySender(senderPublicKey, parameters = {}) { - return this.findAll({ - ...{ senderPublicKey, type: TransactionTypes.Vote }, - ...parameters, - }); - } - - /** - * Get all transactions for the given block. - * @param {Number} blockId - * @param {Object} parameters - * @return {Object} - */ - public async findAllByBlock(blockId, parameters = {}) { - return this.findAll({ ...{ blockId }, ...parameters }); - } - - /** - * Get all transactions for the given type. - * @param {Number} type - * @param {Object} parameters - * @return {Object} - */ - public async findAllByType(type, parameters = {}) { - return this.findAll({ ...{ type }, ...parameters }); - } - - /** - * Get a transaction. - * @param {Number} id - * @return {Object} - */ - public async findById(id) { - const query = this.query - .select(this.query.block_id, this.query.serialized) - .from(this.query) - .where(this.query.id.equals(id)); - - const transaction = await this._find(query); - - return this.__mapBlocksToTransactions(transaction); - } - - /** - * Get a transactions for the given type and id. - * @param {Number} type - * @param {Number} id - * @return {Object} - */ - public async findByTypeAndId(type, id) { - const query = this.query - .select(this.query.block_id, this.query.serialized) - .from(this.query) - .where(this.query.id.equals(id).and(this.query.type.equals(type))); - - const transaction = await this._find(query); - - return this.__mapBlocksToTransactions(transaction); - } - - /** - * Get transactions for the given ids. - * @param {Array} ids - * @return {Object} - */ - public async findByIds(ids) { - const query = this.query - .select(this.query.block_id, this.query.serialized) - .from(this.query) - .where(this.query.id.in(ids)); - - return this._findMany(query); - } - - /** - * Get all transactions that have a vendor field. - * @return {Object} - */ - public async findWithVendorField() { - const query = this.query - .select(this.query.block_id, this.query.serialized) - .from(this.query) - .where(this.query.vendor_field_hex.isNotNull()); - - const rows = await this._findMany(query); - - return this.__mapBlocksToTransactions(rows); - } - - /** - * Calculates min, max and average fee statistics based on transactions table - * @return {Object} - */ - public async getFeeStatistics() { - const query = this.query - .select( - this.query.type, - this.query.fee.min("minFee"), - this.query.fee.max("maxFee"), - this.query.fee.avg("avgFee"), - this.query.timestamp.max("timestamp"), - ) - .from(this.query) - .where( - this.query.timestamp.gte( - slots.getTime( - dayjs() - .subtract(30, "day") - .valueOf(), - ), - ), - ) - .and(this.query.fee.gte(this.transactionPool.options.dynamicFees.minFeeBroadcast)) - .group(this.query.type) - .order('"timestamp" DESC'); - - return this._findMany(query); - } - - /** - * Search all transactions. - * - * @param {Object} params - * @return {Object} - */ - public async search(parameters) { - const selectQuery = this.query.select().from(this.query); - const countQuery = this._makeEstimateQuery(); - - if (parameters.senderId) { - const senderPublicKey = this.__publicKeyFromSenderId(parameters.senderId); - - if (senderPublicKey) { - parameters.senderPublicKey = senderPublicKey; - } - } - - const applyConditions = queries => { - const conditions = buildFilterQuery(this._formatConditions(parameters), { - exact: ["id", "block_id", "type", "version", "sender_public_key", "recipient_id"], - between: ["timestamp", "amount", "fee"], - wildcard: ["vendor_field_hex"], - }); - - if (conditions.length) { - const first = conditions.shift(); - - for (const item of queries) { - item.where(this.query[first.column][first.method](first.value)); - - for (const condition of conditions) { - item.and(this.query[condition.column][condition.method](condition.value)); - } - } - } - }; - - applyConditions([selectQuery, countQuery]); - - const results = await this._findManyWithCount(selectQuery, countQuery, { - limit: parameters.limit, - offset: parameters.offset, - orderBy: this.__orderBy(parameters), - }); - - results.rows = await this.__mapBlocksToTransactions(results.rows); - - return results; - } - - public getModel() { - return (this.databaseService.connection as any).models.transaction; - } - - /** - * [__mapBlocksToTransactions description] - * @param {Array|Object} data - * @return {Object} - */ - public async __mapBlocksToTransactions(data) { - const blockQuery = (this.databaseService.connection as any).models.block.query(); - - // Array... - if (Array.isArray(data)) { - // 1. get heights from cache - const missingFromCache = []; - - for (let i = 0; i < data.length; i++) { - const cachedBlock = this.__getBlockCache(data[i].blockId); - - if (cachedBlock) { - data[i].block = cachedBlock; - } else { - missingFromCache.push({ - index: i, - blockId: data[i].blockId, - }); - } - } - - // 2. get missing heights from database - if (missingFromCache.length) { - const query = blockQuery - .select(blockQuery.id, blockQuery.height) - .from(blockQuery) - .where(blockQuery.id.in(missingFromCache.map(d => d.blockId))) - .group(blockQuery.id); - - const blocks = await this._findMany(query); - - for (const missing of missingFromCache) { - const block = blocks.find(item => item.id === missing.blockId); - if (block) { - data[missing.index].block = block; - this.__setBlockCache(block); - } - } - } - - return data; - } - - // Object... - if (data) { - const cachedBlock = this.__getBlockCache(data.blockId); - - if (cachedBlock) { - data.block = cachedBlock; - } else { - const query = blockQuery - .select(blockQuery.id, blockQuery.height) - .from(blockQuery) - .where(blockQuery.id.equals(data.blockId)); - - data.block = await this._find(query); - - this.__setBlockCache(data.block); - } - } - - return data; - } - - /** - * Tries to retrieve the height of the block from the cache - * @param {String} blockId - * @return {Object|null} - */ - public __getBlockCache(blockId) { - const height = this.cache.get(`heights:${blockId}`); - - return height ? { height, id: blockId } : null; - } - - /** - * Stores the height of the block on the cache - * @param {Object} block - * @param {String} block.id - * @param {Number} block.height - */ - public __setBlockCache({ id, height }) { - this.cache.set(`heights:${id}`, height); - } - - /** - * Retrieves the publicKey of the address from the WalletManager in-memory data - * @param {String} senderId - * @return {String} - */ - public __publicKeyFromSenderId(senderId) { - return this.databaseService.walletManager.findByAddress(senderId).publicKey; - } - - public __orderBy(parameters) { - return parameters.orderBy ? parameters.orderBy.split(":").map(p => p.toLowerCase()) : ["timestamp", "desc"]; - } -} - -export const transactionRepository = new TransactionsRepository(); diff --git a/packages/core-graphql/src/repositories/utils/filter-query.ts b/packages/core-graphql/src/repositories/utils/filter-query.ts deleted file mode 100644 index 3c41c8cd19..0000000000 --- a/packages/core-graphql/src/repositories/utils/filter-query.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Create a "where" object for a sql query. - * @param {Object} parameters - * @param {Object} filters - * @return {Object} - */ -export function buildFilterQuery(parameters, filters) { - const where = []; - - if (filters.exact) { - for (const elem of filters.exact) { - if (typeof parameters[elem] !== "undefined") { - where.push({ - column: elem, - method: "equals", - value: parameters[elem], - }); - } - } - } - - if (filters.between) { - for (const elem of filters.between) { - if (!parameters[elem]) { - continue; - } - - if (!parameters[elem].from && !parameters[elem].to) { - where.push({ - column: elem, - method: "equals", - value: parameters[elem], - }); - } - - if (parameters[elem].from || parameters[elem].to) { - where[elem] = {}; - - if (parameters[elem].from) { - where.push({ - column: elem, - method: "gte", - value: parameters[elem].from, - }); - } - - if (parameters[elem].to) { - where.push({ - column: elem, - method: "lte", - value: parameters[elem].to, - }); - } - } - } - } - - if (filters.wildcard) { - for (const elem of filters.wildcard) { - if (parameters[elem]) { - where.push({ - column: elem, - method: "like", - value: `%${parameters[elem]}%`, - }); - } - } - } - - return where; -} diff --git a/packages/core-graphql/src/resolvers/index.ts b/packages/core-graphql/src/resolvers/index.ts deleted file mode 100644 index 91626b41ea..0000000000 --- a/packages/core-graphql/src/resolvers/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -import GraphQLTypes from "graphql-tools-types"; -import * as queries from "./queries"; -import { Block } from "./relationship/block"; -import { Transaction } from "./relationship/transaction"; -import { Wallet } from "./relationship/wallet"; - -/** - * Resolvers used by the executed schema when encountering a - * scalar or type. - * - * All of our scalars are based on graphql-tools-types which helps us with - * query standardization. - * - * We introduce relationships and queries for our own types, - * these hold the data processing responsibilities of the complete - * GraphQL query flow. - */ - -export const resolvers = { - JSON: GraphQLTypes.JSON({ name: "Json" }), - Limit: GraphQLTypes.Int({ name: "Limit", min: 1, max: 100 }), - Offset: GraphQLTypes.Int({ name: "Offset", min: 0 }), - Address: GraphQLTypes.String({ - name: "Address", - regex: /^[AaDd]{1}[0-9a-zA-Z]{33}/, - }), - Query: queries, - Block, - Transaction, - Wallet, -}; diff --git a/packages/core-graphql/src/resolvers/queries/block/block.ts b/packages/core-graphql/src/resolvers/queries/block/block.ts deleted file mode 100644 index d6e1fcbc0f..0000000000 --- a/packages/core-graphql/src/resolvers/queries/block/block.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { app } from "@arkecosystem/core-container"; -import { Database } from "@arkecosystem/core-interfaces"; - -/** - * Get a single block from the database - * @return {Block} - */ -export async function block(_, { id }) { - return app.resolvePlugin("database").connection.blocksRepository.findById(id); -} diff --git a/packages/core-graphql/src/resolvers/queries/block/blocks.ts b/packages/core-graphql/src/resolvers/queries/block/blocks.ts deleted file mode 100644 index 1b4db4aff3..0000000000 --- a/packages/core-graphql/src/resolvers/queries/block/blocks.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { formatOrderBy } from "../../../helpers"; -import { blockRepository } from "../../../repositories"; - -/** - * Get multiple blocks from the database - * @return {Block[]} - */ -export async function blocks(_, args: any) { - const { orderBy, filter } = args; - - const order = formatOrderBy(orderBy, "height:desc"); - - const result = await blockRepository.findAll({ ...filter, orderBy: order }); - - return result ? result.rows : []; -} diff --git a/packages/core-graphql/src/resolvers/queries/index.ts b/packages/core-graphql/src/resolvers/queries/index.ts deleted file mode 100644 index e89c5b4cfb..0000000000 --- a/packages/core-graphql/src/resolvers/queries/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { block } from "./block/block"; -import { blocks } from "./block/blocks"; -import { transaction } from "./transaction/transaction"; -import { transactions } from "./transaction/transactions"; -import { wallet } from "./wallet/wallet"; -import { wallets } from "./wallet/wallets"; - -export { block, blocks, transaction, transactions, wallet, wallets }; diff --git a/packages/core-graphql/src/resolvers/queries/transaction/transaction.ts b/packages/core-graphql/src/resolvers/queries/transaction/transaction.ts deleted file mode 100644 index e2c3b8d8c3..0000000000 --- a/packages/core-graphql/src/resolvers/queries/transaction/transaction.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { app } from "@arkecosystem/core-container"; -import { Database } from "@arkecosystem/core-interfaces"; - -/** - * Get a single transaction from the database - * @return {Transaction} - */ -export async function transaction(_, { id }) { - return app.resolvePlugin("database").connection.transactionsRepository.findById(id); -} diff --git a/packages/core-graphql/src/resolvers/queries/transaction/transactions.ts b/packages/core-graphql/src/resolvers/queries/transaction/transactions.ts deleted file mode 100644 index 9c6b993b2b..0000000000 --- a/packages/core-graphql/src/resolvers/queries/transaction/transactions.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { formatOrderBy } from "../../../helpers"; -import { transactionRepository } from "../../../repositories"; - -/** - * Get multiple transactions from the database - * @return {Transaction[]} - */ -export async function transactions(_, args: any) { - const { orderBy, filter, limit } = args; - const order = formatOrderBy(orderBy, "timestamp:desc"); - const result = await transactionRepository.findAll({ ...filter, orderBy: order, limit }); - return result ? result.rows : []; -} diff --git a/packages/core-graphql/src/resolvers/queries/wallet/wallet.ts b/packages/core-graphql/src/resolvers/queries/wallet/wallet.ts deleted file mode 100644 index 81feab2dd7..0000000000 --- a/packages/core-graphql/src/resolvers/queries/wallet/wallet.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { app } from "@arkecosystem/core-container"; -import { Database } from "@arkecosystem/core-interfaces"; - -const databaseService = app.resolvePlugin("database"); - -/** - * Get a single wallet from the database - * @return {Wallet} - */ -export async function wallet(_, args: any) { - const param = args.address || args.publicKey || args.username; - return databaseService.wallets.findById(param); -} diff --git a/packages/core-graphql/src/resolvers/queries/wallet/wallets.ts b/packages/core-graphql/src/resolvers/queries/wallet/wallets.ts deleted file mode 100644 index 0ae2f10353..0000000000 --- a/packages/core-graphql/src/resolvers/queries/wallet/wallets.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { app } from "@arkecosystem/core-container"; -import { Database } from "@arkecosystem/core-interfaces"; -import { formatOrderBy } from "../../../helpers"; - -const databaseService = app.resolvePlugin("database"); - -/** - * Get multiple wallets from the database - * @return {Wallet[]} - */ -export async function wallets(_, args: any) { - const { orderBy, filter, ...params } = args; - - const order = formatOrderBy(orderBy, "height:desc"); - const result = - filter && filter.vote - ? await databaseService.wallets.findAllByVote(filter.vote, { - orderBy: order, - ...params, - }) - : await databaseService.wallets.findAll({ orderBy: order, ...params }); - - return result ? result.rows : []; -} diff --git a/packages/core-graphql/src/resolvers/relationship/block.ts b/packages/core-graphql/src/resolvers/relationship/block.ts deleted file mode 100644 index 7457dfd2ba..0000000000 --- a/packages/core-graphql/src/resolvers/relationship/block.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { app } from "@arkecosystem/core-container"; -import { Database } from "@arkecosystem/core-interfaces"; -import { formatOrderBy, unserializeTransactions } from "../../helpers"; - -const databaseService = app.resolvePlugin("database"); - -/** - * Useful and common database operations with block data. - */ -export const Block = { - /** - * Get the transactions for a given block - * @param {Block}: block - * @param {Object}: args - * @return {Transaction[]} - */ - async transactions(block, args) { - const { orderBy, filter, ...params } = args; - - /* .findAll() method never existed on the TransactionRepository in core-database-postgres. This code would've blown chunks - const result = await database.connection.transactionsRepository.findAll( - { - ...filter, - orderBy: formatOrderBy(orderBy, "timestamp:DESC"), - ...params, - }, - false, - );*/ - const result = null; - const rows = result ? result.rows : []; - - return unserializeTransactions(rows); - }, - - /** - * Get the generator wallet for a given block - * @param {Block} block - * @return {Wallet} - */ - generator(block) { - return databaseService.wallets.findById(block.generatorPublicKey); - }, -}; diff --git a/packages/core-graphql/src/resolvers/relationship/transaction.ts b/packages/core-graphql/src/resolvers/relationship/transaction.ts deleted file mode 100644 index 5999c67b99..0000000000 --- a/packages/core-graphql/src/resolvers/relationship/transaction.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { app } from "@arkecosystem/core-container"; -import { Database } from "@arkecosystem/core-interfaces"; - -const databaseService = app.resolvePlugin("database"); - -/** - * Useful and common database operations with transaction data. - */ -export const Transaction = { - /** - * Get the block of a transaction - * @param {Transaction} transaction - * @return {Block} - */ - block: transaction => databaseService.connection.blocksRepository.findById(transaction.blockId), - - /** - * Get the recipient of a transaction - * @param {Transaction} transaction - * @return {Wallet} - */ - recipient: transaction => (transaction.recipientId ? databaseService.wallets.findById(transaction.recipientId) : []), - - /** - * Get the sender of a transaction - * @param {Transaction} transaction - * @return {Wallet} - */ - sender: transaction => (transaction.senderPublicKey ? databaseService.wallets.findById(transaction.senderPublicKey) : []), -}; diff --git a/packages/core-graphql/src/resolvers/relationship/wallet.ts b/packages/core-graphql/src/resolvers/relationship/wallet.ts deleted file mode 100644 index b0216a4722..0000000000 --- a/packages/core-graphql/src/resolvers/relationship/wallet.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { app } from "@arkecosystem/core-container"; -import { Database } from "@arkecosystem/core-interfaces"; -import { formatOrderBy, unserializeTransactions } from "../../helpers"; - -const databaseService = app.resolvePlugin("database"); - -/** - * Useful and common database operations with wallet data. - */ -export const Wallet = { - /* - * Get the transactions for a given wallet. - * @param {Wallet} wallet - * @param {Object} args - * @return {Transaction[]} - */ - async transactions(wallet, args) { - const { orderBy, filter, ...params } = args; - - const walletOr = (databaseService.connection as any).createCondition("OR", [ - { - senderPublicKey: wallet.publicKey, - }, - { - recipientId: wallet.address, - }, - ]); - - /* TODO .findAll() method never existed on the TransactionRepository in core-database-postgres. This code would've blown chunks - const result = await databaseService.connection.transactionsRepository.findAll( - { - ...filter, - orderBy: formatOrderBy(orderBy, "timestamp:DESC"), - ...walletOr, - ...params, - }, - false, - );*/ - const result = null; - const rows = result ? result.rows : []; - - return unserializeTransactions(rows); - }, - - /* - * Get the blocks generated for a given wallet. - * @param {Wallet} wallet - * @param {Object} args - * @return {Block[]} - */ - blocks(wallet, args) { - const { orderBy, ...params } = args; - - params.generatorPublickKey = wallet.publicKey; - - - /* TODO: .findAll() method never existed on the TransactionRepository in core-database-postgres. This code would've blown chunks - const result = databaseService.connection.blocksRepository.findAll( - { - orderBy: formatOrderBy(orderBy, "height:DESC"), - ...params, - }, - false, - );*/ - const result = null; - const rows = result ? result.rows : []; - return rows; - }, -}; diff --git a/packages/core-graphql/src/server.ts b/packages/core-graphql/src/server.ts deleted file mode 100644 index 59cdba2f97..0000000000 --- a/packages/core-graphql/src/server.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { createServer, mountServer } from "@arkecosystem/core-http-utils"; -import { apolloServer } from "./apollo-server"; - -export async function startServer(config) { - const app = await createServer({ - host: config.host, - port: config.port, - }); - - await apolloServer.applyMiddleware({ - app, - path: config.path, - }); - - await apolloServer.installSubscriptionHandlers(app.listener); - - return mountServer("GraphQL", app); -} diff --git a/packages/core-http-utils/.gitattributes b/packages/core-http-utils/.gitattributes index 60cc52db63..63f6a5b970 100644 --- a/packages/core-http-utils/.gitattributes +++ b/packages/core-http-utils/.gitattributes @@ -2,10 +2,6 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore -/__tests__ export-ignore -/docs export-ignore /README.md export-ignore diff --git a/packages/core-http-utils/README.md b/packages/core-http-utils/README.md index 7295c0d50a..25fa192539 100644 --- a/packages/core-http-utils/README.md +++ b/packages/core-http-utils/README.md @@ -1,12 +1,12 @@ -# Ark Core - HTTP Utilities +# Persona Core - HTTP Utilities

- +

## Documentation -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core-http-utils.html). +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/required/core-http-utils.html). ## Security @@ -14,9 +14,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [Brian Faust](https://github.com/faustbrian) -- [All Contributors](../../../../contributors) +This project exists thanks to all the people who [contribute](../../../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-http-utils/package.json b/packages/core-http-utils/package.json index 22d3737802..05ca02f7b7 100644 --- a/packages/core-http-utils/package.json +++ b/packages/core-http-utils/package.json @@ -1,7 +1,7 @@ { "name": "@arkecosystem/core-http-utils", - "description": "Http Utilities for Ark Core", - "version": "2.2.1", + "description": "Http Utilities for ARK Core", + "version": "2.3.15", "contributors": [ "Brian Faust " ], @@ -11,27 +11,16 @@ "dist" ], "scripts": { - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", "prepublishOnly": "yarn build", - "pretest": "yarn lint && yarn build", "compile": "../../node_modules/typescript/bin/tsc", "build": "yarn clean && yarn compile", "build:watch": "yarn clean && yarn compile -w", - "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "test": "cross-env CORE_ENV=test jest --runInBand --forceExit", - "test:coverage": "cross-env CORE_ENV=test jest --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$' --runInBand --forceExit", - "test:debug": "cross-env CORE_ENV=test node --inspect-brk ../../node_modules/.bin/jest --runInBand", - "test:watch": "cross-env CORE_ENV=test jest --runInBand --watch", - "test:watch:all": "cross-env CORE_ENV=test jest --runInBand --watchAll", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" + "clean": "del dist" }, "dependencies": { - "@arkecosystem/core-container": "^2.2.1", + "@arkecosystem/core-container": "^2.3.15", + "@arkecosystem/core-utils": "^2.3.15", + "@arkecosystem/crypto": "^2.3.15", "boom": "^7.3.0", "expand-home-dir": "^0.0.3", "good": "^8.1.2", @@ -39,26 +28,19 @@ "good-squeeze": "^5.1.0", "hapi": "^18.1.0", "hapi-trailing-slash": "^3.0.1", - "inert": "^5.1.2", - "lout": "^11.1.0", - "micromatch": "^3.1.10", - "vision": "^5.4.4" + "nanomatch": "^1.2.13" }, "devDependencies": { "@types/boom": "^7.2.1", - "@types/hapi": "^18.0.0", + "@types/hapi": "^18.0.1", "@types/inert": "^5.1.2", "@types/micromatch": "^3.1.0", - "@types/vision": "^5.3.5", - "axios": "^0.18.0" + "@types/vision": "^5.3.6" }, "publishConfig": { "access": "public" }, "engines": { "node": ">=10.x" - }, - "jest": { - "preset": "../../jest-preset.json" } } diff --git a/packages/core-http-utils/src/plugins/hapi-ajv.ts b/packages/core-http-utils/src/plugins/hapi-ajv.ts new file mode 100644 index 0000000000..9450ca796f --- /dev/null +++ b/packages/core-http-utils/src/plugins/hapi-ajv.ts @@ -0,0 +1,62 @@ +import { AjvWrapper } from "@arkecosystem/crypto"; +import Boom from "boom"; +import Hapi from "hapi"; + +const name = "hapi-ajv"; + +export const hapiAjv = { + name, + version: "1.0.0", + register: async (server: Hapi.Server, options: any): Promise => { + const ajv = AjvWrapper.instance(); + + if (options.registerFormats) { + options.registerFormats(ajv); + } + + const validate = (schema, data) => { + return ajv.validate(schema, data) ? null : ajv.errors; + }; + + const createErrorResponse = (request, h, errors) => { + if (request.pre.apiVersion === 1) { + return h + .response({ + path: errors[0].dataPath, + error: errors[0].message, + success: false, + }) + .takeover(); + } + + return Boom.badData(errors); + }; + + server.ext({ + type: "onPreHandler", + method: (request, h) => { + const config = request.route.settings.plugins[name] || {}; + + let errors; + + if (config.payloadSchema) { + errors = validate(config.payloadSchema, request.payload); + + if (errors) { + return createErrorResponse(request, h, errors[0].message); + } + } + + if (config.querySchema) { + errors = validate(config.querySchema, request.query); + + if (errors) { + return createErrorResponse(request, h, errors); + } + } + + return h.continue; + }, + }); + }, +}; diff --git a/packages/core-http-utils/src/plugins/index.ts b/packages/core-http-utils/src/plugins/index.ts index 51ec0fcb1c..25e58f29c1 100644 --- a/packages/core-http-utils/src/plugins/index.ts +++ b/packages/core-http-utils/src/plugins/index.ts @@ -1,6 +1,5 @@ -import { contentType } from "./content-type"; -import { corsHeaders } from "./cors-headers"; -import { transactionPayload } from "./transaction-payload"; -import { whitelist } from "./whitelist"; - -export { contentType, corsHeaders, transactionPayload, whitelist }; +export { hapiAjv } from "./hapi-ajv"; +export { contentType } from "./content-type"; +export { corsHeaders } from "./cors-headers"; +export { transactionPayload } from "./transaction-payload"; +export { whitelist } from "./whitelist"; diff --git a/packages/core-http-utils/src/plugins/transaction-payload.ts b/packages/core-http-utils/src/plugins/transaction-payload.ts index 04db3aa482..85067b3d13 100644 --- a/packages/core-http-utils/src/plugins/transaction-payload.ts +++ b/packages/core-http-utils/src/plugins/transaction-payload.ts @@ -18,7 +18,7 @@ export const transactionPayload = { return h.continue; } - const transactionPool = app.resolveOptions("transactionPool"); + const transactionPool = app.resolveOptions("transaction-pool"); if (!transactionPool) { return h.continue; diff --git a/packages/core-http-utils/src/plugins/whitelist.ts b/packages/core-http-utils/src/plugins/whitelist.ts index 9bed9351aa..372c8f160a 100644 --- a/packages/core-http-utils/src/plugins/whitelist.ts +++ b/packages/core-http-utils/src/plugins/whitelist.ts @@ -1,6 +1,6 @@ import { app } from "@arkecosystem/core-container"; import Boom from "boom"; -import mm from "micromatch"; +import nm from "nanomatch"; export const whitelist = { name: "whitelist", @@ -14,7 +14,7 @@ export const whitelist = { if (Array.isArray(options.whitelist)) { for (const ip of options.whitelist) { try { - if (mm.isMatch(remoteAddress, ip)) { + if (nm.isMatch(remoteAddress, ip)) { return h.continue; } } catch { @@ -24,7 +24,7 @@ export const whitelist = { } app.resolvePlugin("logger").warn( - `${remoteAddress} tried to access the ${options.name} without being whitelisted :warning:`, + `${remoteAddress} tried to access the ${options.name} without being whitelisted`, ); return Boom.forbidden(); diff --git a/packages/core-http-utils/src/server/create-secure.ts b/packages/core-http-utils/src/server/create-secure.ts index ed0cb474e4..d391e0b919 100644 --- a/packages/core-http-utils/src/server/create-secure.ts +++ b/packages/core-http-utils/src/server/create-secure.ts @@ -2,7 +2,7 @@ import expandHomeDir from "expand-home-dir"; import { readFileSync } from "fs"; import { createServer } from "./create"; -export async function createSecureServer(options, callback, secure) { +export async function createSecureServer(options, callback, secure, plugins?: any[]) { options.host = secure.host; options.port = secure.port; options.tls = { @@ -10,5 +10,5 @@ export async function createSecureServer(options, callback, secure) { cert: readFileSync(expandHomeDir(secure.cert)), }; - return createServer(options, callback); + return createServer(options, callback, plugins); } diff --git a/packages/core-http-utils/src/server/create.ts b/packages/core-http-utils/src/server/create.ts index dcc767b108..a37ef3e64c 100644 --- a/packages/core-http-utils/src/server/create.ts +++ b/packages/core-http-utils/src/server/create.ts @@ -1,10 +1,14 @@ import Hapi from "hapi"; import { monitorServer } from "./monitor"; -export async function createServer(options, callback: any = null) { +export async function createServer(options, callback: any = null, plugins?: any[]) { const server = new Hapi.Server(options); - await server.register([require("vision"), require("inert"), require("lout")]); + if (Array.isArray(plugins)) { + for (const plugin of plugins) { + await server.register(plugin); + } + } await server.register({ plugin: require("hapi-trailing-slash"), diff --git a/packages/core-interfaces/package.json b/packages/core-interfaces/package.json index 26d4b635ba..b71f33f111 100644 --- a/packages/core-interfaces/package.json +++ b/packages/core-interfaces/package.json @@ -1,7 +1,7 @@ { "name": "@arkecosystem/core-interfaces", - "description": "Interface types for essential Ark core modules", - "version": "2.2.1", + "description": "Interface types for essential ARK Core modules", + "version": "2.3.15", "contributors": [ "François-Xavier Thoorens ", "Kristjan Košič ", @@ -15,24 +15,17 @@ "dist" ], "scripts": { - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", "prepublishOnly": "yarn build", "pretest": "bash ../../scripts/pre-test.sh", "compile": "../../node_modules/typescript/bin/tsc", "build": "yarn clean && yarn compile", "build:watch": "yarn clean && yarn compile -w", - "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' --fix", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" + "clean": "del dist" }, "dependencies": { - "@arkecosystem/crypto": "^2.2.1", - "awilix": "^4.2.0", - "eventemitter3": "^3.1.0" + "@arkecosystem/crypto": "^2.3.15", + "@faustbrian/dato": "^0.2.0", + "awilix": "^4.2.1" }, "publishConfig": { "access": "public" diff --git a/packages/core-interfaces/src/core-blockchain/blockchain.ts b/packages/core-interfaces/src/core-blockchain/blockchain.ts index 17607f938d..f421b6876a 100644 --- a/packages/core-interfaces/src/core-blockchain/blockchain.ts +++ b/packages/core-interfaces/src/core-blockchain/blockchain.ts @@ -1,6 +1,7 @@ -import { models } from "@arkecosystem/crypto"; +import { models, Transaction } from "@arkecosystem/crypto"; +import { IDatabaseService } from "../core-database"; import { IMonitor } from "../core-p2p"; -import { ITransactionPool } from "../core-transaction-pool"; +import { IConnection } from "../core-transaction-pool"; import { IStateStorage } from "./state-storage"; export interface IBlockchain { @@ -17,17 +18,17 @@ export interface IBlockchain { /** * Get the transaction handler. - * @return {ITransactionPool} + * @return {IConnection} */ - readonly transactionPool: ITransactionPool; + readonly transactionPool: IConnection; /** * Get the database connection. * @return {ConnectionInterface} */ - readonly database: any; + readonly database: IDatabaseService; - dispatch(event: any): any; + dispatch(event: string): void; /** * Start the blockchain and wait for it to be ready. @@ -37,21 +38,12 @@ export interface IBlockchain { stop(): Promise; - checkNetwork(): void; - /** * Update network status. * @return {void} */ updateNetworkStatus(): Promise; - /** - * Rebuild N blocks in the blockchain. - * @param {Number} nblocks - * @return {void} - */ - rebuild(nblocks?: number): void; - /** * Reset the state of the blockchain. * @return {void} @@ -69,7 +61,7 @@ export interface IBlockchain { * @param {Array} transactions * @return {void} */ - postTransactions(transactions: models.Transaction[]): Promise; + postTransactions(transactions: Transaction[]): Promise; /** * Push a block to the process queue. @@ -78,12 +70,6 @@ export interface IBlockchain { */ handleIncomingBlock(block: models.Block): void; - /** - * Rollback all blocks up to the previous round. - * @return {void} - */ - rollbackCurrentRound(): Promise; - /** * Remove N number of blocks. * @param {Number} nblocks @@ -99,15 +85,6 @@ export interface IBlockchain { */ removeTopBlocks(count: any): Promise; - /** - * Hande a block during a rebuild. - * NOTE: We should be sure this is fail safe (ie callback() is being called only ONCE) - * @param {Block} block - * @param {Function} callback - * @return {Object} - */ - rebuildBlock(block: models.Block, callback: any): Promise; - /** * Process the given block. * NOTE: We should be sure this is fail safe (ie callback() is being called only ONCE) @@ -140,10 +117,10 @@ export interface IBlockchain { * @return {Object} */ getUnconfirmedTransactions( - blockSize: any, + blockSize: number, ): { - transactions: any[]; - poolSize: any; + transactions: string[]; + poolSize: number; count: number; }; @@ -154,13 +131,6 @@ export interface IBlockchain { */ isSynced(block?: models.Block): boolean; - /** - * Determine if the blockchain is synced after a rebuild. - * @param {Block} block - * @return {Boolean} - */ - isRebuildSynced(block?: models.Block): boolean; - /** * Get the last block of the blockchain. * @return {Object} @@ -196,10 +166,4 @@ export interface IBlockchain { * @return {Object} */ pushPingBlock(block: models.IBlockData): void; - - /** - * Get the list of events that are available. - * @return {Array} - */ - getEvents(): string[]; } diff --git a/packages/core-interfaces/src/core-blockchain/state-storage.ts b/packages/core-interfaces/src/core-blockchain/state-storage.ts index e7c908f317..c586d23f14 100644 --- a/packages/core-interfaces/src/core-blockchain/state-storage.ts +++ b/packages/core-interfaces/src/core-blockchain/state-storage.ts @@ -1,4 +1,4 @@ -import { models } from "@arkecosystem/crypto"; +import { ITransactionData, models } from "@arkecosystem/crypto"; export interface IStateStorage { reset(): void; @@ -52,9 +52,7 @@ export interface IStateStorage { /** * Cache the ids of the given transactions. */ - cacheTransactions( - transactions: models.ITransactionData[], - ): { [key in "added" | "notAdded"]: models.ITransactionData[] }; + cacheTransactions(transactions: ITransactionData[]): { [key in "added" | "notAdded"]: ITransactionData[] }; /** * Remove the given transaction ids from the cache. diff --git a/packages/core-interfaces/src/core-container/container.ts b/packages/core-interfaces/src/core-container/container.ts index 4490a16c16..b8384763a6 100644 --- a/packages/core-interfaces/src/core-container/container.ts +++ b/packages/core-interfaces/src/core-container/container.ts @@ -5,18 +5,26 @@ export interface PluginDescriptor { pkg: any; defaults?: any; extends?: string; - register(container: IContainer, options?: any): Promise; + register(container: IContainer, options?: IPluginOptions): Promise; deregister?(container: IContainer, options?: any): Promise; } +type PluginOptionValue = string | number | boolean | object; + +export interface IPluginOptions { + [key: string]: PluginOptionValue | PluginOptionValue[]; +} + export interface PluginConfig { name: string; version: string; - options: { [key: string]: any }; + options: IPluginOptions; plugin: T; } export interface IContainer { + config: any; + silentShutdown: boolean; isReady: boolean; @@ -76,12 +84,6 @@ export interface IContainer { */ exit(exitCode: number, message: string, error?: Error): void; - /** - * Get the application git commit hash. - * @throws {String} - */ - getHashid(): string; - /** * Get the application version. * @throws {String} @@ -94,4 +96,6 @@ export interface IContainer { * @return {void} */ setVersion(version: any): void; + + getName(): string; } diff --git a/packages/core-interfaces/src/core-database/business-repository/blocks-business-repository.ts b/packages/core-interfaces/src/core-database/business-repository/blocks-business-repository.ts new file mode 100644 index 0000000000..45442bf81c --- /dev/null +++ b/packages/core-interfaces/src/core-database/business-repository/blocks-business-repository.ts @@ -0,0 +1,18 @@ +import { SearchPaginate } from "../search"; +import { IParameters } from "./parameters"; + +export interface IBlocksBusinessRepository { + search(params: IParameters): Promise; + + findAll(params: IParameters): Promise; + + findById(id: string): Promise; + + findByHeight(height: number): Promise; + + findByIdOrHeight(idOrHeight): Promise; + + findAllByGenerator(generatorPublicKey: string, paginate: SearchPaginate); + + findLastByPublicKey(generatorPublicKey: string): Promise; +} diff --git a/packages/core-interfaces/src/core-database/business-repository/delegates-business-repository.ts b/packages/core-interfaces/src/core-database/business-repository/delegates-business-repository.ts index 48e65c523c..664e974f7c 100644 --- a/packages/core-interfaces/src/core-database/business-repository/delegates-business-repository.ts +++ b/packages/core-interfaces/src/core-database/business-repository/delegates-business-repository.ts @@ -1,15 +1,12 @@ -import { models } from "@arkecosystem/crypto"; +import { IWallet } from "../wallet-manager"; import { IParameters } from "./parameters"; export interface IDelegatesBusinessRepository { + getLocalDelegates(): IWallet[]; - getLocalDelegates(): models.Wallet[]; + findAll(params?: IParameters): { count: number; rows: IWallet[] }; - findAll(params?: IParameters): { count: number, rows: models.Wallet[] } + search(params: T): { count: number; rows: IWallet[] }; - search(params: T): { count: number, rows: models.Wallet[] } - - findById(id: string): models.Wallet; - - getActiveAtHeight(height: number): Promise> + findById(id: string): IWallet; } diff --git a/packages/core-interfaces/src/core-database/business-repository/index.ts b/packages/core-interfaces/src/core-database/business-repository/index.ts index a30abaafdc..57962aa358 100644 --- a/packages/core-interfaces/src/core-database/business-repository/index.ts +++ b/packages/core-interfaces/src/core-database/business-repository/index.ts @@ -1,3 +1,5 @@ export * from "./wallets-business-repository"; -export * from "./delegates-business-repository" +export * from "./delegates-business-repository"; export * from "./parameters"; +export * from "./blocks-business-repository"; +export * from "./transactions-business-repository"; diff --git a/packages/core-interfaces/src/core-database/business-repository/parameters.ts b/packages/core-interfaces/src/core-database/business-repository/parameters.ts index 27df1df680..c74625fa67 100644 --- a/packages/core-interfaces/src/core-database/business-repository/parameters.ts +++ b/packages/core-interfaces/src/core-database/business-repository/parameters.ts @@ -1,6 +1,6 @@ export interface IParameters { offset?: number; limit?: number; - orderBy?: string, - [key: string]: object | number | string | boolean + orderBy?: string; + [key: string]: object | number | string | boolean; } diff --git a/packages/core-interfaces/src/core-database/business-repository/transactions-business-repository.ts b/packages/core-interfaces/src/core-database/business-repository/transactions-business-repository.ts new file mode 100644 index 0000000000..c6067f4dde --- /dev/null +++ b/packages/core-interfaces/src/core-database/business-repository/transactions-business-repository.ts @@ -0,0 +1,29 @@ +import { IParameters } from "./parameters"; + +export interface ITransactionsBusinessRepository { + findAll(params: IParameters, sequenceOrder?: "asc" | "desc"): Promise; + + findAllLegacy(parameters: IParameters): Promise; + + findWithVendorField(): Promise; + + findAllByWallet(wallet, parameters?: IParameters): Promise; + + findAllBySender(senderPublicKey, parameters?: IParameters): Promise; + + findAllByRecipient(recipientId, parameters?: IParameters): Promise; + + allVotesBySender(senderPublicKey, parameters?: IParameters): Promise; + + findAllByBlock(blockId, parameters?: IParameters): Promise; + + findAllByType(type, parameters?: IParameters): Promise; + + findById(id: string): Promise; + + findByTypeAndId(type: any, id: string): Promise; + + getFeeStatistics(): Promise; + + search(params: IParameters): Promise; +} diff --git a/packages/core-interfaces/src/core-database/business-repository/wallets-business-repository.ts b/packages/core-interfaces/src/core-database/business-repository/wallets-business-repository.ts index ad5f6f05e8..0c14c12f17 100644 --- a/packages/core-interfaces/src/core-database/business-repository/wallets-business-repository.ts +++ b/packages/core-interfaces/src/core-database/business-repository/wallets-business-repository.ts @@ -1,20 +1,18 @@ -import { models } from "@arkecosystem/crypto"; +import { IWallet } from "../wallet-manager"; import { IParameters } from "./parameters"; export interface IWalletsBusinessRepository { + all(): IWallet[]; - all(): models.Wallet[]; + findAll(params?: IParameters): { count: number; rows: IWallet[] }; - findAll(params?: IParameters): { count: number, rows: models.Wallet[] } + findAllByVote(publicKey: string, params?: IParameters): { count: number; rows: IWallet[] }; - findAllByVote(publicKey: string, params?: IParameters): { count: number, rows: models.Wallet[] }; - - findById(id: string): models.Wallet; + findById(id: string): IWallet; count(): number; - top(params?: IParameters): { count: number, rows: models.Wallet[] } - - search(params: T): { count: number, rows: models.Wallet[] } + top(params?: IParameters): { count: number; rows: IWallet[] }; + search(params: T): { count: number; rows: IWallet[] }; } diff --git a/packages/core-interfaces/src/core-database/database-connection.ts b/packages/core-interfaces/src/core-database/database-connection.ts index 7ce5688307..7aaab75366 100644 --- a/packages/core-interfaces/src/core-database/database-connection.ts +++ b/packages/core-interfaces/src/core-database/database-connection.ts @@ -5,37 +5,29 @@ import { IWalletsRepository } from "./database-repository"; import { models } from "@arkecosystem/crypto"; -export interface IDatabaseConnection { - +export interface IConnection { options: any; - blocksRepository : IBlocksRepository; + blocksRepository: IBlocksRepository; walletsRepository: IWalletsRepository; roundsRepository: IRoundsRepository; transactionsRepository: ITransactionsRepository; - make(): Promise; + make(): Promise; connect(): Promise; disconnect(): Promise; - buildWallets(height: number) : Promise; - - /* We have these methods on the connection since they rely on transactions, which is a DB specific detail - Keep DB specifics away from the service layer - */ - saveWallets(wallets: any[], force?: boolean) : Promise; - - saveBlock(block: models.Block): Promise; + buildWallets(): Promise; - deleteBlock(block: models.Block): Promise; + saveBlock(block: models.Block): Promise; - enqueueDeleteBlock(block: models.Block); + deleteBlock(block: models.Block): Promise; - enqueueDeleteRound(height: number); + enqueueDeleteBlock(block: models.Block): void; - enqueueSaveBlock(block: models.Block); + enqueueDeleteRound(height: number): void; - commitQueuedQueries(); + commitQueuedQueries(): Promise; } diff --git a/packages/core-interfaces/src/core-database/database-model/database-model.ts b/packages/core-interfaces/src/core-database/database-model/database-model.ts new file mode 100644 index 0000000000..b65b8675c3 --- /dev/null +++ b/packages/core-interfaces/src/core-database/database-model/database-model.ts @@ -0,0 +1,18 @@ +import { SearchOperator } from "../search"; + +export interface SearchableField { + fieldName: string; + supportedOperators: SearchOperator[]; +} +export interface IModel { + getName(): string; + + getTable(): string; + + query(): any; + + getColumnSet(): any; + + /* A list of fields on this model that can be queried, and each search-operator they support */ + getSearchableFields(): SearchableField[]; +} diff --git a/packages/core-interfaces/src/core-database/database-model/index.ts b/packages/core-interfaces/src/core-database/database-model/index.ts new file mode 100644 index 0000000000..2f8dd1522c --- /dev/null +++ b/packages/core-interfaces/src/core-database/database-model/index.ts @@ -0,0 +1 @@ +export * from "./database-model"; diff --git a/packages/core-interfaces/src/core-database/database-repository/blocks-repository.ts b/packages/core-interfaces/src/core-database/database-repository/blocks-repository.ts index e7b28ba809..c20212ebc4 100644 --- a/packages/core-interfaces/src/core-database/database-repository/blocks-repository.ts +++ b/packages/core-interfaces/src/core-database/database-repository/blocks-repository.ts @@ -1,4 +1,5 @@ import { Bignum } from "@arkecosystem/crypto"; +import { SearchParameters } from "../search"; import { IRepository } from "./repository"; export interface IBlocksRepository extends IRepository { @@ -7,12 +8,25 @@ export interface IBlocksRepository extends IRepository { */ findById(id: string): Promise; + /** + * Find many blocks by their IDs. + * @param {String[]} ids + */ + findByIds(id: string[]): Promise; + + /** + * Get a block at the given height. + * @param {Number} height the height of the blocks to retrieve + * @return {Promise} + */ + findByHeight(height: number): Promise; + /** * Get all of the blocks at the given heights. * @param {Array} heights the heights of the blocks to retrieve * @return {Promise} */ - findByHeight(heights): Promise; + findByHeights(heights: number[]): Promise; /** * Count the number of records in the database. @@ -59,4 +73,9 @@ export interface IBlocksRepository extends IRepository { * Delete the block from the database. */ delete(id: string): Promise; + + /* TODO: Remove with V1 */ + findAll(params: SearchParameters): Promise; + + search(params: SearchParameters): Promise; } diff --git a/packages/core-interfaces/src/core-database/database-repository/repository.ts b/packages/core-interfaces/src/core-database/database-repository/repository.ts index 20dbcfcc82..acc13db8fe 100644 --- a/packages/core-interfaces/src/core-database/database-repository/repository.ts +++ b/packages/core-interfaces/src/core-database/database-repository/repository.ts @@ -1,11 +1,13 @@ +import { IModel } from "../database-model"; + export interface IRepository { + getModel(): IModel; - estimate() : Promise; + estimate(): Promise; truncate(): Promise; - insert(item: any | any[]) : Promise; - - update(item: any | any[]) : Promise; + insert(item: any | any[]): Promise; + update(item: any | any[]): Promise; } diff --git a/packages/core-interfaces/src/core-database/database-repository/rounds-repository.ts b/packages/core-interfaces/src/core-database/database-repository/rounds-repository.ts index 468ca091c1..a1c4983c01 100644 --- a/packages/core-interfaces/src/core-database/database-repository/rounds-repository.ts +++ b/packages/core-interfaces/src/core-database/database-repository/rounds-repository.ts @@ -1,13 +1,20 @@ import { IRepository } from "./repository"; +export interface IRound { + id: number; + publicKey: string; + balance: string; + round: number; +} + export interface IRoundsRepository extends IRepository { /** * Find a round by its ID. */ - findById(id: number): Promise; + findById(id: number): Promise; /** * Delete the round from the database. */ - delete(id: number): Promise; + delete(id: number): Promise; } diff --git a/packages/core-interfaces/src/core-database/database-repository/transactions-repository.ts b/packages/core-interfaces/src/core-database/database-repository/transactions-repository.ts index 7df0d8c633..4a5cf3c557 100644 --- a/packages/core-interfaces/src/core-database/database-repository/transactions-repository.ts +++ b/packages/core-interfaces/src/core-database/database-repository/transactions-repository.ts @@ -1,8 +1,8 @@ import { Bignum } from "@arkecosystem/crypto"; +import { SearchOrderBy, SearchPaginate, SearchParameters } from "../search"; import { IRepository } from "./repository"; export interface ITransactionsRepository extends IRepository { - /** * Find a transactions by its ID. */ @@ -32,14 +32,24 @@ export interface ITransactionsRepository extends IRepository { * Get statistics about all transactions from the database. */ statistics(): Promise<{ - count: number, - totalFee: Bignum, - totalAmount: Bignum + count: number; + totalFee: Bignum; + totalAmount: Bignum; }>; + getFeeStatistics(minFeeBroadcast: number): Promise; + /** * Delete transactions with blockId */ deleteByBlockId(blockId: string): Promise; + findAllByWallet(wallet: any, paginate?: SearchPaginate, orderBy?: SearchOrderBy[]): Promise; + + findWithVendorField(): Promise; + + /* TODO: Remove with v1 */ + findAll(parameters: SearchParameters): Promise; + + search(parameters: SearchParameters): Promise; } diff --git a/packages/core-interfaces/src/core-database/database-repository/wallets-repository.ts b/packages/core-interfaces/src/core-database/database-repository/wallets-repository.ts index b0046fa8b7..9652288266 100644 --- a/packages/core-interfaces/src/core-database/database-repository/wallets-repository.ts +++ b/packages/core-interfaces/src/core-database/database-repository/wallets-repository.ts @@ -9,7 +9,7 @@ export interface IWalletsRepository extends IRepository { /** * Find a wallet by its address. */ - findByAddress(address: string): Promise + findByAddress(address: string): Promise; /** * Get the count of wallets that have a negative balance. diff --git a/packages/core-interfaces/src/core-database/database-service.ts b/packages/core-interfaces/src/core-database/database-service.ts index b27c5af602..f8a87de350 100644 --- a/packages/core-interfaces/src/core-database/database-service.ts +++ b/packages/core-interfaces/src/core-database/database-service.ts @@ -1,8 +1,14 @@ -import { models } from "@arkecosystem/crypto"; +import { models, Transaction } from "@arkecosystem/crypto"; import { EventEmitter, Logger } from "../index"; -import { IDelegatesBusinessRepository, IWalletsBusinessRepository } from "./business-repository"; -import { IDatabaseConnection } from "./database-connection"; -import { IWalletManager } from "./wallet-manager"; +import { IRoundInfo } from "../shared"; +import { + IBlocksBusinessRepository, + IDelegatesBusinessRepository, + ITransactionsBusinessRepository, + IWalletsBusinessRepository, +} from "./business-repository"; +import { IConnection } from "./database-connection"; +import { IDelegateWallet, IWalletManager } from "./wallet-manager"; export interface IDatabaseService { walletManager: IWalletManager; @@ -11,7 +17,11 @@ export interface IDatabaseService { delegates: IDelegatesBusinessRepository; - connection: IDatabaseConnection; + blocksBusinessRepository: IBlocksBusinessRepository; + + transactionsBusinessRepository: ITransactionsBusinessRepository; + + connection: IConnection; logger: Logger.ILogger; @@ -27,18 +37,16 @@ export interface IDatabaseService { verifyBlockchain(): Promise<{ valid: boolean; errors: any[] }>; - getActiveDelegates(height: number, delegates?: any[]): Promise; + getActiveDelegates(roundInfo: IRoundInfo, delegates?: IDelegateWallet[]): Promise; - buildWallets(height: number): Promise; + restoreCurrentRound(height: number): Promise; - saveWallets(force: boolean): Promise; + buildWallets(): Promise; saveBlock(block: models.Block): Promise; // TODO: These methods are exposing database terminology on the business layer, not a fan... - enqueueSaveBlock(block: models.Block): void; - enqueueDeleteBlock(block: models.Block): void; enqueueDeleteRound(height: number): void; @@ -73,13 +81,13 @@ export interface IDatabaseService { */ getBlocksByHeight(heights: number[]): Promise; - getTopBlocks(count): Promise; + getTopBlocks(count: number): Promise; getRecentBlockIds(): Promise; - saveRound(activeDelegates: object[]): Promise; + saveRound(activeDelegates: IDelegateWallet[]): Promise; - deleteRound(round: any): Promise; + deleteRound(round: number): Promise; getTransaction(id: string): Promise; @@ -87,11 +95,13 @@ export interface IDatabaseService { init(): Promise; + reset(): Promise; + loadBlocksFromCurrentRound(): Promise; loadTransactionsForBlocks(blocks): Promise; - updateDelegateStats(delegates: any[]): void; + updateDelegateStats(delegates: IDelegateWallet[]): void; applyRound(height: number): Promise; @@ -101,9 +111,9 @@ export interface IDatabaseService { revertBlock(block: models.Block): Promise; - verifyTransaction(transaction: models.Transaction): Promise; + verifyTransaction(transaction: Transaction): Promise; - getBlocksForRound(round?: number): Promise; + getBlocksForRound(roundInfo?: IRoundInfo): Promise; - getCommonBlocks(ids: string[]): Promise; + getCommonBlocks(ids: string[]): Promise; } diff --git a/packages/core-interfaces/src/core-database/event-types.ts b/packages/core-interfaces/src/core-database/event-types.ts index 4f16f870a4..2f8a68bdbd 100644 --- a/packages/core-interfaces/src/core-database/event-types.ts +++ b/packages/core-interfaces/src/core-database/event-types.ts @@ -1,6 +1,6 @@ export enum DatabaseEvents { - PRE_CONNECT = "database.pre_connect", - POST_CONNECT = "database.post_connect", - PRE_DISCONNECT = "database.pre_disconnect", - POST_DISCONNECT = "databse.post_disconnect" + PRE_CONNECT = "database.preConnect", + POST_CONNECT = "database.postConnect", + PRE_DISCONNECT = "database.preDisconnect", + POST_DISCONNECT = "database.postDisconnect", } diff --git a/packages/core-interfaces/src/core-database/index.ts b/packages/core-interfaces/src/core-database/index.ts index fd44d022fe..9b558604fb 100644 --- a/packages/core-interfaces/src/core-database/index.ts +++ b/packages/core-interfaces/src/core-database/index.ts @@ -4,3 +4,5 @@ export * from "./database-connection"; export * from "./wallet-manager"; export * from "./database-service"; export * from "./event-types"; +export * from "./search"; +export * from "./database-model"; diff --git a/packages/core-interfaces/src/core-database/search/index.ts b/packages/core-interfaces/src/core-database/search/index.ts new file mode 100644 index 0000000000..0bcdf6d7ec --- /dev/null +++ b/packages/core-interfaces/src/core-database/search/index.ts @@ -0,0 +1,2 @@ +export * from "./search-parameter-converter"; +export * from "./search-parameters"; diff --git a/packages/core-interfaces/src/core-database/search/search-parameter-converter.ts b/packages/core-interfaces/src/core-database/search/search-parameter-converter.ts new file mode 100644 index 0000000000..954dde093b --- /dev/null +++ b/packages/core-interfaces/src/core-database/search/search-parameter-converter.ts @@ -0,0 +1,6 @@ +import { IParameters } from "../business-repository"; +import { SearchParameters } from "./search-parameters"; + +export interface ISearchParameterConverter { + convert(params: IParameters, orderBy?: any, paginate?: any): SearchParameters; +} diff --git a/packages/core-interfaces/src/core-database/search/search-parameters.ts b/packages/core-interfaces/src/core-database/search/search-parameters.ts new file mode 100644 index 0000000000..34d6552e2d --- /dev/null +++ b/packages/core-interfaces/src/core-database/search/search-parameters.ts @@ -0,0 +1,31 @@ +export enum SearchOperator { + OP_EQ = "equals", + OP_IN = "in", + OP_GTE = "gte", + OP_LTE = "lte", + OP_LIKE = "like", + // placeholder. For parameters that require custom(not a 1-to-1 field to column mapping) filtering logic on the data-layer repo + OP_CUSTOM = "custom_operator", +} + +export interface SearchParameter { + field: string; + value: any; + operator: SearchOperator; +} + +export interface SearchOrderBy { + field: string; + direction: "asc" | "desc"; +} + +export interface SearchPaginate { + offset?: number; + limit?: number; +} + +export interface SearchParameters { + parameters: SearchParameter[]; + orderBy?: SearchOrderBy[]; + paginate?: SearchPaginate; +} diff --git a/packages/core-interfaces/src/core-database/wallet-manager.ts b/packages/core-interfaces/src/core-database/wallet-manager.ts index 565089b195..40b1f7b50b 100644 --- a/packages/core-interfaces/src/core-database/wallet-manager.ts +++ b/packages/core-interfaces/src/core-database/wallet-manager.ts @@ -1,61 +1,85 @@ -import { models } from "@arkecosystem/crypto"; -import { Logger } from "../index"; +import { Bignum, IMultiSignatureAsset, ITransactionData, models, Transaction } from "@arkecosystem/crypto"; +import { Logger, Shared } from "../index"; +import { IRoundInfo } from "../shared"; + +export interface IWallet { + address: string; + publicKey: string | null; + secondPublicKey: string | null; + balance: Bignum; + vote: string; + voted: boolean; + username: string | null; + lastBlock: any; + voteBalance: Bignum; + multisignature?: IMultiSignatureAsset; + dirty: boolean; + producedBlocks: number; + forgedFees: Bignum; + forgedRewards: Bignum; + rate?: number; + + verifySignatures(transaction: ITransactionData, multisignature: IMultiSignatureAsset): boolean; +} -export interface IWalletManager { +export type IDelegateWallet = IWallet & { rate: number; round: number }; +export interface IWalletManager { logger: Logger.ILogger; config: any; reset(): void; - allByAddress(): models.Wallet[]; + allByAddress(): IWallet[]; - allByPublicKey(): models.Wallet[]; + allByPublicKey(): IWallet[]; - allByUsername(): models.Wallet[]; + allByUsername(): IWallet[]; - findByAddress(address: string): models.Wallet; + findByAddress(address: string): IWallet; exists(addressOrPublicKey: string): boolean; - findByPublicKey(publicKey: string): models.Wallet; + findByPublicKey(publicKey: string): IWallet; - findByUsername(username: string): models.Wallet; + findByUsername(username: string): IWallet; - index(wallets: models.Wallet[]): void; + index(wallets: IWallet[]): void; - reindex(wallet: models.Wallet): void; + reindex(wallet: IWallet): void; - clear(): void; + cloneDelegateWallets(): IWalletManager; - loadActiveDelegateList(maxDelegateCount: number, height?: number): any[]; + loadActiveDelegateList(roundInfo: IRoundInfo): IDelegateWallet[]; buildVoteBalances(): void; + buildDelegateRanking(delegates: IWallet[], roundInfo?: Shared.IRoundInfo): IDelegateWallet[]; + applyBlock(block: models.Block): void; revertBlock(block: models.Block): void; - applyTransaction(transaction: models.Transaction): models.Transaction; + applyTransaction(transaction: Transaction): void; - revertTransaction(transaction: models.Transaction): any; + revertTransaction(transaction: Transaction): void; isDelegate(publicKey: string): boolean; - canBePurged(wallet: models.Wallet): boolean; + canBePurged(wallet: IWallet): boolean; forgetByAddress(address: string): void; - forgetByPublicKey( publicKey: string): void; + forgetByPublicKey(publicKey: string): void; forgetByUsername(username: string): void; - setByAddress(address: string, wallet: models.Wallet): void; + setByAddress(address: string, wallet: IWallet): void; - setByPublicKey(publicKey: string, wallet: models.Wallet): void; + setByPublicKey(publicKey: string, wallet: IWallet): void; - setByUsername(username: string, wallet: models.Wallet): void; + setByUsername(username: string, wallet: IWallet): void; purgeEmptyNonDelegates(): void; } diff --git a/packages/core-interfaces/src/core-event-emitter/index.ts b/packages/core-interfaces/src/core-event-emitter/index.ts index 478f9bc498..685127b974 100644 --- a/packages/core-interfaces/src/core-event-emitter/index.ts +++ b/packages/core-interfaces/src/core-event-emitter/index.ts @@ -1,2 +1 @@ -import EventEmitter from "eventemitter3"; -export { EventEmitter }; +export { EventEmitter } from "events"; diff --git a/packages/core-interfaces/src/core-logger/logger.ts b/packages/core-interfaces/src/core-logger/logger.ts index c01cbd5a8d..82ef3fa549 100644 --- a/packages/core-interfaces/src/core-logger/logger.ts +++ b/packages/core-interfaces/src/core-logger/logger.ts @@ -7,38 +7,38 @@ export interface ILogger { /** * Log an error message. - * @param {String} message + * @param {*} message * @return {void} */ - error(message: string): void; + error(message: any): void; /** * Log a warning message. - * @param {String} message + * @param {*} message * @return {void} */ - warn(message: string): void; + warn(message: any): void; /** * Log an info message. - * @param {String} message + * @param {*} message * @return {void} */ - info(message: string): void; + info(message: any): void; /** * Log a debug message. - * @param {String} message + * @param {*} message * @return {void} */ - debug(message: string): void; + debug(message: any): void; /** * Log a verbose message. - * @param {String} message + * @param {*} message * @return {void} */ - verbose(message: string): void; + verbose(message: any): void; /** * Suppress console output. diff --git a/packages/core-interfaces/src/core-p2p/monitor.ts b/packages/core-interfaces/src/core-p2p/monitor.ts index 5e412874eb..aa5bc29644 100644 --- a/packages/core-interfaces/src/core-p2p/monitor.ts +++ b/packages/core-interfaces/src/core-p2p/monitor.ts @@ -1,3 +1,12 @@ +import { Dato } from "@faustbrian/dato"; + +export interface ISuspension { + peer: any; + reason: string; + until: Dato; + nextSuspensionReminder?: Dato; +} + export interface INetworkStatus { forked: boolean; blocksToRollback?: number; @@ -122,8 +131,8 @@ export interface IMonitor { checkNetworkHealth(): Promise; /** - * Dump the list of active peers. + * Cache the list of active peers. * @return {void} */ - dumpPeers(): void; + cachePeers(): void; } diff --git a/packages/core-interfaces/src/core-p2p/peer.ts b/packages/core-interfaces/src/core-p2p/peer.ts index 48071a4321..2338625a89 100644 --- a/packages/core-interfaces/src/core-p2p/peer.ts +++ b/packages/core-interfaces/src/core-p2p/peer.ts @@ -1,28 +1,28 @@ -import { models } from "@arkecosystem/crypto"; +import { models, Transaction } from "@arkecosystem/crypto"; export interface IPeer { - setHeaders(headers: any): void; + setHeaders(headers: Record): void; /** * Set the given status for the peer. * @param {String} value * @return {void} */ - setStatus(value: any): void; + setStatus(value: string | number): void; /** * Get information to broadcast. * @return {Object} */ toBroadcastInfo(): { - ip: any; + ip: string; port: number; - nethash: any; - version: any; - os: any; - status: any; - height: any; - delay: any; + nethash: string; + version: string; + os: string; + status: string | number; + height: number; + delay: number; }; /** @@ -37,7 +37,7 @@ export interface IPeer { * @param {Transaction[]} transactions * @return {(Object|undefined)} */ - postTransactions(transactions: models.Transaction[]): Promise; + postTransactions(transactions: Transaction[]): Promise; /** * Download blocks from peer. diff --git a/packages/core-interfaces/src/core-transaction-pool/transaction-pool.ts b/packages/core-interfaces/src/core-transaction-pool/connection.ts similarity index 80% rename from packages/core-interfaces/src/core-transaction-pool/transaction-pool.ts rename to packages/core-interfaces/src/core-transaction-pool/connection.ts index 072e1b2eaf..7e21705396 100644 --- a/packages/core-interfaces/src/core-transaction-pool/transaction-pool.ts +++ b/packages/core-interfaces/src/core-transaction-pool/connection.ts @@ -1,18 +1,21 @@ -import dayjs from "dayjs-ext"; +import { Dato } from "@faustbrian/dato"; -import { constants, models } from "@arkecosystem/crypto"; +import { constants, ITransactionData, models, Transaction } from "@arkecosystem/crypto"; -export interface AddTransactionResponseDTO { +export interface IAddTransactionResponse { success: boolean; } -export interface AddTransactionErrorDTO extends AddTransactionResponseDTO { - transaction: models.Transaction; +export interface IAddTransactionErrorResponse extends IAddTransactionResponse { + transaction: Transaction; type: string; message: string; + success: boolean; } -export interface ITransactionPool { +export interface IConnection { options: any; + loggedAllowedSenders: string[]; + walletManager: any; make(): Promise; @@ -48,23 +51,23 @@ export interface ITransactionPool { * } */ addTransactions( - transactions: models.Transaction[], + transactions: Transaction[], ): { - added: models.Transaction[]; - notAdded: AddTransactionErrorDTO[]; + added: Transaction[]; + notAdded: IAddTransactionErrorResponse[]; }; /** * Add a transaction to the pool. */ - addTransaction(transaction: models.Transaction): AddTransactionResponseDTO; + addTransaction(transaction: Transaction): IAddTransactionResponse; /** * Remove a transaction from the pool by transaction object. * @param {Transaction} transaction * @return {void} */ - removeTransaction(transaction: models.Transaction): void; + removeTransaction(transaction: Transaction): void; /** * Remove a transaction from the pool by id. @@ -74,18 +77,18 @@ export interface ITransactionPool { /** * Get all transactions that are ready to be forged. */ - getTransactionsForForging(blockSize: number): models.Transaction[]; + getTransactionsForForging(blockSize: number): string[]; /** * Get a transaction by transaction id. */ - getTransaction(id: string): models.Transaction; + getTransaction(id: string): Transaction; /** * Get all transactions within the specified range [start, start + size), ordered by fee. * @return {(Array|void)} array of serialized transaction hex strings */ - getTransactions(start: number, size: number, maxBytes?: number): string[]; + getTransactions(start: number, size: number, maxBytes?: number): Buffer[]; /** * Get all transactions within the specified range [start, start + size). @@ -99,7 +102,12 @@ export interface ITransactionPool { * insertion time, if fees equal (earliest transaction first). * @return {Array} array of transaction[property] */ - getTransactionsData(start: number, size: number, property: string, maxBytes?: number): any[]; + getTransactionsData(start: number, size: number, property: string, maxBytes?: number): string[] | Buffer[]; + + /** + * Get all transactions of a given type from the pool. + */ + getTransactionsByType(type: any): any; /** * Remove all transactions from the transaction pool belonging to specific sender. @@ -109,7 +117,7 @@ export interface ITransactionPool { /** * Check whether sender of transaction has exceeded max transactions in queue. */ - hasExceededMaxTransactions(transaction: models.Transaction): boolean; + hasExceededMaxTransactions(transaction: ITransactionData): boolean; /** * Flush the pool (delete all transactions from it). @@ -130,7 +138,7 @@ export interface ITransactionPool { /** * Blocks sender for a specified time */ - blockSender(senderPublicKey: string): dayjs.Dayjs; + blockSender(senderPublicKey: string): Dato; /** * Processes recently accepted block by the blockchain. diff --git a/packages/core-interfaces/src/core-transaction-pool/guard.ts b/packages/core-interfaces/src/core-transaction-pool/guard.ts new file mode 100644 index 0000000000..866933aeef --- /dev/null +++ b/packages/core-interfaces/src/core-transaction-pool/guard.ts @@ -0,0 +1,25 @@ +import { ITransactionData, Transaction } from "@arkecosystem/crypto"; +import { IConnection } from "./connection"; + +export interface ITransactionErrorResponse { + type: string; + message: string; +} + +export interface IValidationResult { + accept: string[]; + broadcast: string[]; + invalid: string[]; + excess: string[]; + errors: { [key: string]: ITransactionErrorResponse[] } | null; +} + +export interface IGuard { + pool: IConnection; + transactions: ITransactionData[]; + + validate(transactions: ITransactionData[]): Promise; + pushError(transaction: ITransactionData, type: string, message: string); + + getBroadcastTransactions(): Transaction[]; +} diff --git a/packages/core-interfaces/src/core-transaction-pool/index.ts b/packages/core-interfaces/src/core-transaction-pool/index.ts index e11bb11e00..22de865caa 100644 --- a/packages/core-interfaces/src/core-transaction-pool/index.ts +++ b/packages/core-interfaces/src/core-transaction-pool/index.ts @@ -1,2 +1,2 @@ -export * from "./transaction-pool"; -export * from "./transaction-guard"; +export * from "./connection"; +export * from "./guard"; diff --git a/packages/core-interfaces/src/core-transaction-pool/transaction-guard.ts b/packages/core-interfaces/src/core-transaction-pool/transaction-guard.ts deleted file mode 100644 index 35f03e2b52..0000000000 --- a/packages/core-interfaces/src/core-transaction-pool/transaction-guard.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { models } from "@arkecosystem/crypto"; - -export interface TransactionErrorDTO { - type: string; - message: string; -} - -export interface ValidationResultDTO { - accept: string[]; - broadcast: string[]; - invalid: string[]; - excess: string[]; - errors: { [key: string]: TransactionErrorDTO[] } | null; -} - -export interface ITransactionGuard { - validate(transactions: models.Transaction[]): Promise; - - getBroadcastTransactions(): models.Transaction[]; -} diff --git a/packages/core-interfaces/src/shared/config.ts b/packages/core-interfaces/src/shared/config.ts index 1563710e3d..6892bd8112 100644 --- a/packages/core-interfaces/src/shared/config.ts +++ b/packages/core-interfaces/src/shared/config.ts @@ -1,18 +1,18 @@ -import get from "lodash/get"; -import set from "lodash/set"; +import get from "lodash.get"; +import set from "lodash.set"; export class Config { - private config: any; + private config: Record; - public init(options: any): void { + public init(options: Record): void { this.config = options; } - public get(key: string, defaultValue: any = null): any { + public get(key: string, defaultValue: T = null): T { return get(this.config, key, defaultValue); } - public set(key: string, value: any): void { + public set(key: string, value: T): void { set(this.config, key, value); } } diff --git a/packages/core-interfaces/src/shared/index.ts b/packages/core-interfaces/src/shared/index.ts index 5c62e04f5e..887a910260 100644 --- a/packages/core-interfaces/src/shared/index.ts +++ b/packages/core-interfaces/src/shared/index.ts @@ -1 +1,2 @@ export * from "./config"; +export * from "./interfaces"; diff --git a/packages/core-interfaces/src/shared/interfaces.ts b/packages/core-interfaces/src/shared/interfaces.ts new file mode 100644 index 0000000000..fb51100e95 --- /dev/null +++ b/packages/core-interfaces/src/shared/interfaces.ts @@ -0,0 +1,6 @@ +export interface IRoundInfo { + round: number; + nextRound: number; + maxDelegates: number; + roundHeight: number; +} diff --git a/packages/core-jest-matchers/.gitattributes b/packages/core-jest-matchers/.gitattributes index 60cc52db63..63f6a5b970 100644 --- a/packages/core-jest-matchers/.gitattributes +++ b/packages/core-jest-matchers/.gitattributes @@ -2,10 +2,6 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore -/__tests__ export-ignore -/docs export-ignore /README.md export-ignore diff --git a/packages/core-jest-matchers/README.md b/packages/core-jest-matchers/README.md index 24cba42650..748fa7bc44 100644 --- a/packages/core-jest-matchers/README.md +++ b/packages/core-jest-matchers/README.md @@ -1,7 +1,7 @@ -# Ark Core - Jest Matchers +# Persona Core - Jest Matchers

- +

## Documentation @@ -14,11 +14,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [Brian Faust](https://github.com/faustbrian) -- [Erwann Gentric](https://github.com/air1one) -- [Joshua Noack](https://github.com/supaiku0) -- [All Contributors](../../../../contributors) +This project exists thanks to all the people who [contribute](../../../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-jest-matchers/package.json b/packages/core-jest-matchers/package.json index 954761adb4..c8d782d362 100644 --- a/packages/core-jest-matchers/package.json +++ b/packages/core-jest-matchers/package.json @@ -1,7 +1,7 @@ { "name": "@arkecosystem/core-jest-matchers", - "description": "Jest matchers for Ark Core", - "version": "2.2.1", + "description": "Jest matchers for ARK Core", + "version": "2.3.15", "contributors": [ "Brian Faust ", "Erwann Gentric ", @@ -13,49 +13,34 @@ "dist" ], "scripts": { - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", "prepublishOnly": "yarn build", - "pretest": "yarn lint && yarn build", "compile": "../../node_modules/typescript/bin/tsc", "build": "yarn clean && yarn compile", "build:watch": "yarn clean && yarn compile -w", - "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "test": "cross-env CORE_ENV=test jest --runInBand --forceExit", - "test:coverage": "cross-env CORE_ENV=test jest --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$' --runInBand --forceExit", - "test:debug": "cross-env CORE_ENV=test node --inspect-brk ../../node_modules/.bin/jest --runInBand", - "test:watch": "cross-env CORE_ENV=test jest --runInBand --watch", - "test:watch:all": "cross-env CORE_ENV=test jest --runInBand --watchAll", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" + "clean": "del dist" }, "dependencies": { - "@arkecosystem/core-container": "^2.2.1", - "@arkecosystem/crypto": "^2.2.1", - "@arkecosystem/utils": "^0.2.4", + "@arkecosystem/core-container": "^2.3.15", + "@arkecosystem/crypto": "^2.3.15", + "@arkecosystem/utils": "^0.3.0", "bip39": "^2.5.0", + "jest-extended": "^0.11.1", "lodash.get": "^4.4.2", "lodash.isequal": "^4.5.0", "lodash.sortby": "^4.7.0", "superheroes": "^2.0.0", - "xstate": "^4.3.1" + "xstate": "^4.3.3" }, "devDependencies": { - "@types/bip39": "^2.4.1", - "@types/lodash.get": "^4.4.4", - "@types/lodash.isequal": "^4.5.3", - "@types/lodash.sortby": "^4.7.4" + "@types/bip39": "^2.4.2", + "@types/lodash.get": "^4.4.6", + "@types/lodash.isequal": "^4.5.5", + "@types/lodash.sortby": "^4.7.6" }, "publishConfig": { "access": "public" }, "engines": { "node": ">=10.x" - }, - "jest": { - "preset": "../../jest-preset.json" } } diff --git a/packages/core-jest-matchers/src/api/block.ts b/packages/core-jest-matchers/src/api/block.ts index a2636a2465..f75bd4bb84 100644 --- a/packages/core-jest-matchers/src/api/block.ts +++ b/packages/core-jest-matchers/src/api/block.ts @@ -1,5 +1,5 @@ import { sortBy } from "@arkecosystem/utils"; -import isEqual from "lodash/isEqual"; +import isEqual from "lodash.isequal"; export {}; diff --git a/packages/core-jest-matchers/src/api/peer.ts b/packages/core-jest-matchers/src/api/peer.ts index 7604782819..ea978d3a36 100644 --- a/packages/core-jest-matchers/src/api/peer.ts +++ b/packages/core-jest-matchers/src/api/peer.ts @@ -1,5 +1,5 @@ import { sortBy } from "@arkecosystem/utils"; -import isEqual from "lodash/isEqual"; +import isEqual from "lodash.isequal"; export {}; diff --git a/packages/core-jest-matchers/src/api/transaction.ts b/packages/core-jest-matchers/src/api/transaction.ts index 6d2ca52f35..94abb27bc6 100644 --- a/packages/core-jest-matchers/src/api/transaction.ts +++ b/packages/core-jest-matchers/src/api/transaction.ts @@ -1,5 +1,5 @@ import { sortBy } from "@arkecosystem/utils"; -import isEqual from "lodash/isEqual"; +import isEqual from "lodash.isequal"; export {}; diff --git a/packages/core-jest-matchers/src/blockchain/execute-on-entry.ts b/packages/core-jest-matchers/src/blockchain/execute-on-entry.ts index c014289242..413c8aa5de 100644 --- a/packages/core-jest-matchers/src/blockchain/execute-on-entry.ts +++ b/packages/core-jest-matchers/src/blockchain/execute-on-entry.ts @@ -1,5 +1,5 @@ -import get from "lodash/get"; -import isEqual from "lodash/isEqual"; +import get from "lodash.get"; +import isEqual from "lodash.isequal"; export {}; diff --git a/packages/core-jest-matchers/src/models/delegate.ts b/packages/core-jest-matchers/src/models/delegate.ts index 97fe8d574e..1ba66e4248 100644 --- a/packages/core-jest-matchers/src/models/delegate.ts +++ b/packages/core-jest-matchers/src/models/delegate.ts @@ -1,5 +1,5 @@ import { sortBy } from "@arkecosystem/utils"; -import isEqual from "lodash/isEqual"; +import isEqual from "lodash.isequal"; export {}; diff --git a/packages/core-jest-matchers/src/models/transaction.ts b/packages/core-jest-matchers/src/models/transaction.ts index a3cd5cc965..41136a9b8a 100644 --- a/packages/core-jest-matchers/src/models/transaction.ts +++ b/packages/core-jest-matchers/src/models/transaction.ts @@ -1,5 +1,5 @@ import { sortBy } from "@arkecosystem/utils"; -import isEqual from "lodash/isEqual"; +import isEqual from "lodash.isequal"; export {}; diff --git a/packages/core-jest-matchers/src/models/wallet.ts b/packages/core-jest-matchers/src/models/wallet.ts index 3d1c4d7513..81e4edc63d 100644 --- a/packages/core-jest-matchers/src/models/wallet.ts +++ b/packages/core-jest-matchers/src/models/wallet.ts @@ -1,5 +1,5 @@ import { sortBy } from "@arkecosystem/utils"; -import isEqual from "lodash/isEqual"; +import isEqual from "lodash.isequal"; export {}; diff --git a/packages/core-json-rpc/.gitattributes b/packages/core-json-rpc/.gitattributes index 60cc52db63..63f6a5b970 100644 --- a/packages/core-json-rpc/.gitattributes +++ b/packages/core-json-rpc/.gitattributes @@ -2,10 +2,6 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore -/__tests__ export-ignore -/docs export-ignore /README.md export-ignore diff --git a/packages/core-json-rpc/README.md b/packages/core-json-rpc/README.md index d61b9e34b7..9126045194 100644 --- a/packages/core-json-rpc/README.md +++ b/packages/core-json-rpc/README.md @@ -1,12 +1,12 @@ -# Ark Core - JSON-RPC +# Persona Core - JSON-RPC

- +

## Documentation -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core-json-rpc.html). +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/optional/core-json-rpc.html). ## Security @@ -14,10 +14,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [Brian Faust](https://github.com/faustbrian) -- [François-Xavier Thoorens](https://github.com/fix) -- [All Contributors](../../../../contributors) +This project exists thanks to all the people who [contribute](../../../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-json-rpc/__tests__/__support__/request.ts b/packages/core-json-rpc/__tests__/__support__/request.ts deleted file mode 100644 index a1bd1f6f37..0000000000 --- a/packages/core-json-rpc/__tests__/__support__/request.ts +++ /dev/null @@ -1,18 +0,0 @@ -import axios from "axios"; -import uuid from "uuid/v4"; - -export async function sendRequest(method, params: any = {}) { - const id = uuid(); - const response = await axios.post("http://localhost:8080/", { - jsonrpc: "2.0", - id, - method, - params, - }); - - await expect(response.status).toBe(200); - await expect(response.data.jsonrpc).toBe("2.0"); - await expect(response.data.id).toBe(id); - - return response; -} diff --git a/packages/core-json-rpc/package.json b/packages/core-json-rpc/package.json index 11ee225fd7..48d068ab84 100644 --- a/packages/core-json-rpc/package.json +++ b/packages/core-json-rpc/package.json @@ -1,7 +1,7 @@ { "name": "@arkecosystem/core-json-rpc", - "description": "A JSON-RPC 2.0 Specification compliant server to interact with the Ark Blockchain.", - "version": "2.2.1", + "description": "A JSON-RPC 2.0 Specification compliant server to interact with the ARK Blockchain.", + "version": "2.3.15", "contributors": [ "François-Xavier Thoorens ", "Brian Faust " @@ -12,32 +12,20 @@ "dist" ], "scripts": { - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", "prepublishOnly": "yarn build", "pretest": "bash ../../scripts/pre-test.sh", "compile": "../../node_modules/typescript/bin/tsc", "build": "yarn clean && yarn compile", "build:watch": "yarn clean && yarn compile -w", - "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "test": "cross-env CORE_ENV=test jest --runInBand --forceExit", - "test:coverage": "cross-env CORE_ENV=test jest --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$' --runInBand --forceExit", - "test:debug": "cross-env CORE_ENV=test node --inspect-brk ../../node_modules/.bin/jest --runInBand", - "test:watch": "cross-env CORE_ENV=test jest --runInBand --watch", - "test:watch:all": "cross-env CORE_ENV=test jest --runInBand --watchAll", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" + "clean": "del dist" }, "dependencies": { - "@arkecosystem/core-container": "^2.2.1", - "@arkecosystem/core-http-utils": "^2.2.1", - "@arkecosystem/core-interfaces": "^2.2.1", - "@arkecosystem/crypto": "^2.2.1", + "@arkecosystem/core-container": "^2.3.15", + "@arkecosystem/core-http-utils": "^2.3.15", + "@arkecosystem/core-interfaces": "^2.3.15", + "@arkecosystem/core-utils": "^2.3.15", + "@arkecosystem/crypto": "^2.3.15", "@keyv/sqlite": "^2.0.0", - "axios": "^0.18.0", "bip39": "^2.5.0", "boom": "^7.3.0", "is-reachable": "^3.0.0", @@ -48,26 +36,21 @@ "wif": "^2.0.6" }, "devDependencies": { - "@arkecosystem/core-p2p": "^2.2.1", - "@arkecosystem/core-test-utils": "^2.2.1", - "@types/bip39": "^2.4.1", + "@arkecosystem/core-p2p": "^2.3.15", + "@types/bip39": "^2.4.2", "@types/boom": "^7.2.1", "@types/is-reachable": "^3.0.0", - "@types/joi": "^14.3.1", + "@types/joi": "^14.3.2", "@types/keyv": "^3.1.0", "@types/keyv__sqlite": "^2.0.0", - "@types/lodash.get": "^4.4.4", + "@types/lodash.get": "^4.4.6", "@types/uuid": "^3.4.4", - "@types/wif": "^2.0.1", - "axios-mock-adapter": "^1.16.0" + "@types/wif": "^2.0.1" }, "publishConfig": { "access": "public" }, "engines": { "node": ">=10.x" - }, - "jest": { - "preset": "../../jest-preset.json" } } diff --git a/packages/core-json-rpc/src/index.ts b/packages/core-json-rpc/src/index.ts index 384b06471b..71303fff87 100644 --- a/packages/core-json-rpc/src/index.ts +++ b/packages/core-json-rpc/src/index.ts @@ -12,7 +12,7 @@ export const plugin: Container.PluginDescriptor = { const logger = container.resolvePlugin("logger"); if (!options.enabled) { - logger.info("JSON-RPC Server is disabled :grey_exclamation:"); + logger.info("JSON-RPC Server is disabled"); return; } diff --git a/packages/core-json-rpc/src/server/index.ts b/packages/core-json-rpc/src/server/index.ts index fc1a9b5715..ef5b906cae 100755 --- a/packages/core-json-rpc/src/server/index.ts +++ b/packages/core-json-rpc/src/server/index.ts @@ -7,7 +7,7 @@ import { Processor } from "./services/processor"; export async function startServer(options) { if (options.allowRemote) { app.resolvePlugin("logger").warn( - "JSON-RPC server allows remote connections, this is a potential security risk :warning:", + "JSON-RPC server allows remote connections, this is a potential security risk", ); } @@ -29,6 +29,7 @@ export async function startServer(options) { }); } + // @ts-ignore registerMethods(server); server.route({ diff --git a/packages/core-json-rpc/src/server/methods/blocks/latest.ts b/packages/core-json-rpc/src/server/methods/blocks/latest.ts index 7e5fdfd332..b13b4b6e13 100644 --- a/packages/core-json-rpc/src/server/methods/blocks/latest.ts +++ b/packages/core-json-rpc/src/server/methods/blocks/latest.ts @@ -3,8 +3,8 @@ import { network } from "../../services/network"; export const blockLatest = { name: "blocks.latest", - async method(params) { - const response = await network.sendRequest("blocks?orderBy=height:desc&limit=1"); + async method() { + const response = await network.sendRequest("blocks", { orderBy: "height:desc", limit: 1 }); return response ? response.data[0] : Boom.notFound(`Latest block could not be found.`); }, diff --git a/packages/core-json-rpc/src/server/methods/wallets/transactions.ts b/packages/core-json-rpc/src/server/methods/wallets/transactions.ts index ddfee4965b..645efec677 100644 --- a/packages/core-json-rpc/src/server/methods/wallets/transactions.ts +++ b/packages/core-json-rpc/src/server/methods/wallets/transactions.ts @@ -6,12 +6,12 @@ export const walletTransactions = { name: "wallets.transactions", async method(params) { const response = await network.sendRequest("transactions", { - offset: params.offset, + offset: params.offset || 0, orderBy: "timestamp:desc", ownerId: params.address, }); - if (!response) { + if (!response.data || !response.data.length) { return Boom.notFound(`Wallet ${params.address} could not be found.`); } diff --git a/packages/core-json-rpc/src/server/services/database.ts b/packages/core-json-rpc/src/server/services/database.ts index aac4224fb9..e734dbd5cc 100644 --- a/packages/core-json-rpc/src/server/services/database.ts +++ b/packages/core-json-rpc/src/server/services/database.ts @@ -1,7 +1,7 @@ import Keyv from "keyv"; class Database { - public database: any; + public database: Keyv; public init(options) { this.database = new Keyv(options); diff --git a/packages/core-json-rpc/src/server/services/network.ts b/packages/core-json-rpc/src/server/services/network.ts index d8789900f5..da30d6a3b7 100644 --- a/packages/core-json-rpc/src/server/services/network.ts +++ b/packages/core-json-rpc/src/server/services/network.ts @@ -1,42 +1,35 @@ import { app } from "@arkecosystem/core-container"; import { Logger, P2P } from "@arkecosystem/core-interfaces"; +import { httpie } from "@arkecosystem/core-utils"; import { configManager } from "@arkecosystem/crypto"; -import axios from "axios"; import isReachable from "is-reachable"; -import sample from "lodash/sample"; +import sample from "lodash.sample"; class Network { - public logger: Logger.ILogger; - public p2p: P2P.IMonitor; - public config: any; - public network: any; - public client: any; - public peers: any; - public server: any; + private peers: any; + private server: any; - public async init() { - this.logger = app.resolvePlugin("logger"); - this.config = app.getConfig(); - this.p2p = app.resolvePlugin("p2p"); + private readonly network: any = configManager.all(); + private readonly logger: Logger.ILogger = app.resolvePlugin("logger"); + private readonly p2p: P2P.IMonitor = app.resolvePlugin("p2p"); - this.network = configManager.all(); + private readonly requestOpts: Record = { + headers: { + Accept: "application/vnd.core-api.v2+json", + "Content-Type": "application/json", + }, + timeout: 3000, + }; + public async init() { this.loadRemotePeers(); - - this.client = axios.create({ - headers: { - Accept: "application/vnd.core-api.v2+json", - "Content-Type": "application/json", - }, - timeout: 3000, - }); } public setServer() { this.server = this.getRandomPeer(); } - public async sendRequest(url, params = {}) { + public async sendRequest(url, query = {}) { if (!this.server) { this.setServer(); } @@ -47,17 +40,20 @@ class Network { try { this.logger.info(`Sending request on "${this.network.name}" to "${uri}"`); - const response = await this.client.get(uri, { params }); + const response = await httpie.get(uri, { query, ...this.requestOpts }); - return response.data; + return response.body; } catch (error) { this.logger.error(error.message); } } public async broadcast(transaction) { - return this.client.post(`http://${this.server.ip}:${this.server.port}/api/transactions`, { - transactions: [transaction], + return httpie.post(`http://${this.server.ip}:${this.server.port}/api/transactions`, { + body: { + transactions: [transaction], + }, + ...this.requestOpts.headers, }); } @@ -71,9 +67,9 @@ class Network { try { const peerPort = app.resolveOptions("p2p").port; - const response = await axios.get(`http://${this.server.ip}:${peerPort}/config`); + const response = await httpie.get(`http://${this.server.ip}:${peerPort}/config`); - const plugin = response.data.data.plugins["@arkecosystem/core-api"]; + const plugin = response.body.data.plugins["@arkecosystem/core-api"]; if (!plugin.enabled) { const index = this.peers.findIndex(peer => peer.ip === this.server.ip); @@ -101,7 +97,7 @@ class Network { private loadRemotePeers() { this.peers = this.network.name === "testnet" - ? [{ ip: "127.0.0.1", port: app.resolveOptions("api").port }] + ? [{ ip: "localhost", port: app.resolveOptions("api").port }] : this.p2p.getPeers(); if (!this.peers.length) { diff --git a/packages/core-json-rpc/src/server/services/processor.ts b/packages/core-json-rpc/src/server/services/processor.ts index 75eda130b0..3ccae294da 100644 --- a/packages/core-json-rpc/src/server/services/processor.ts +++ b/packages/core-json-rpc/src/server/services/processor.ts @@ -1,5 +1,5 @@ import Joi from "joi"; -import get from "lodash/get"; +import get from "lodash.get"; import { network } from "./network"; export class Processor { diff --git a/packages/core-json-rpc/src/server/utils/bip38-keys.ts b/packages/core-json-rpc/src/server/utils/bip38-keys.ts index 97d9028dfe..54b15a5f78 100644 --- a/packages/core-json-rpc/src/server/utils/bip38-keys.ts +++ b/packages/core-json-rpc/src/server/utils/bip38-keys.ts @@ -1,4 +1,4 @@ -import { configManager, crypto, HashAlgorithms } from "@arkecosystem/crypto"; +import { HashAlgorithms } from "@arkecosystem/crypto"; import { database } from "../services/database"; import { decryptWIF } from "./decrypt-wif"; diff --git a/packages/core-logger-pino/.gitattributes b/packages/core-logger-pino/.gitattributes new file mode 100644 index 0000000000..63f6a5b970 --- /dev/null +++ b/packages/core-logger-pino/.gitattributes @@ -0,0 +1,7 @@ +# Path-based git attributes +# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html + +# Ignore all test and documentation with "export-ignore". +/.gitattributes export-ignore +/.gitignore export-ignore +/README.md export-ignore diff --git a/packages/core-debugger-cli/README.md b/packages/core-logger-pino/README.md similarity index 52% rename from packages/core-debugger-cli/README.md rename to packages/core-logger-pino/README.md index bc6530e60b..5e4491b2d4 100644 --- a/packages/core-debugger-cli/README.md +++ b/packages/core-logger-pino/README.md @@ -1,12 +1,12 @@ -# Ark Core - Debugger CLI +# Persona Core - Debugger CLI

- +

## Documentation -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core-debugger-cli.html). +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core-logger-pino.html). ## Security @@ -14,10 +14,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [Brian Faust](https://github.com/faustbrian) -- [Joshua Noack](https://github.com/supaiku0) -- [All Contributors](../../../../contributors) +This project exists thanks to all the people who [contribute](../../../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-logger-pino/package.json b/packages/core-logger-pino/package.json new file mode 100644 index 0000000000..d6f452bec7 --- /dev/null +++ b/packages/core-logger-pino/package.json @@ -0,0 +1,44 @@ +{ + "name": "@arkecosystem/core-logger-pino", + "description": "Pino integration for ARK Core", + "version": "2.3.15", + "contributors": [ + "Brian Faust " + ], + "license": "MIT", + "main": "dist/index", + "types": "dist/index", + "files": [ + "dist" + ], + "scripts": { + "prepublishOnly": "yarn build", + "compile": "../../node_modules/typescript/bin/tsc", + "build": "yarn clean && yarn compile", + "build:watch": "yarn clean && yarn compile -w", + "clean": "del dist" + }, + "dependencies": { + "@arkecosystem/core-container": "^2.3.15", + "@arkecosystem/core-interfaces": "^2.3.15", + "@arkecosystem/core-logger": "^2.3.15", + "pino": "^5.11.1", + "pino-pretty": "^2.5.0", + "pump": "^3.0.0", + "readable-stream": "^3.2.0", + "rotating-file-stream": "^1.4.0", + "split2": "^3.1.1" + }, + "devDependencies": { + "@types/pino": "^5.8.6", + "@types/pump": "^1.0.1", + "@types/readable-stream": "^2.3.1", + "@types/split2": "^2.1.6" + }, + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=10.x" + } +} diff --git a/packages/core-logger-pino/src/defaults.ts b/packages/core-logger-pino/src/defaults.ts new file mode 100644 index 0000000000..ecd190ee95 --- /dev/null +++ b/packages/core-logger-pino/src/defaults.ts @@ -0,0 +1,9 @@ +export const defaults = { + levels: { + console: process.env.CORE_LOG_LEVEL || "debug", + file: process.env.CORE_LOG_LEVEL_FILE || "trace", + }, + fileRotator: { + interval: "1d", + }, +}; diff --git a/packages/core-logger-pino/src/driver.ts b/packages/core-logger-pino/src/driver.ts new file mode 100644 index 0000000000..cc6f58ff4b --- /dev/null +++ b/packages/core-logger-pino/src/driver.ts @@ -0,0 +1,98 @@ +import { app } from "@arkecosystem/core-container"; +import { Logger } from "@arkecosystem/core-interfaces"; +import { AbstractLogger } from "@arkecosystem/core-logger"; +import { WriteStream } from "fs"; +import pino, { PrettyOptions } from "pino"; +import PinoPretty from "pino-pretty"; +import pump from "pump"; +import { Transform } from "readable-stream"; +import rfs from "rotating-file-stream"; +import split from "split2"; +import { PassThrough } from "stream"; + +export class PinoLogger extends AbstractLogger { + protected logger: pino.Logger; + private fileStream: WriteStream; + + public make(): Logger.ILogger { + const stream = new PassThrough(); + this.logger = pino( + { + base: null, + safe: true, + level: "trace", + }, + stream, + ); + + this.fileStream = this.getFileStream(); + + const consoleTransport = this.createPrettyTransport(this.options.levels.console, { colorize: true }); + const fileTransport = this.createPrettyTransport(this.options.levels.file, { colorize: false }); + + pump(stream, split(), consoleTransport, process.stdout); + pump(stream, split(), fileTransport, this.fileStream); + + return this; + } + + protected getLevels(): Record { + return { + verbose: "trace", + }; + } + + private createPrettyTransport(level: string, prettyOptions?: PrettyOptions): Transform { + const pinoPretty = PinoPretty({ + ...{ + levelFirst: false, + translateTime: "yyyy-mm-dd HH:MM:ss.l", + }, + ...prettyOptions, + }); + + const levelValue = this.logger.levels.values[level]; + + return new Transform({ + transform(chunk, enc, cb) { + try { + const json = JSON.parse(chunk); + if (json.level >= levelValue) { + const line = pinoPretty(json); + if (line !== undefined) { + return cb(null, line); + } + } + } catch (ex) { + // + } + + return cb(); + }, + }); + } + + private getFileStream(): WriteStream { + const createFileName = (time: Date, index: number) => { + if (!time) { + return `${app.getName()}-current.log`; + } + + let filename = time.toISOString().slice(0, 10); + if (index > 1) { + filename += `.${index}`; + } + + return `${app.getName()}-${filename}.log.gz`; + }; + + return rfs(createFileName, { + path: process.env.CORE_PATH_LOG, + initialRotation: true, + interval: this.options.fileRotator ? this.options.fileRotator.interval : "1d", + maxSize: "100M", + maxFiles: 10, + compress: "gzip", + }); + } +} diff --git a/packages/core-logger-pino/src/index.ts b/packages/core-logger-pino/src/index.ts new file mode 100644 index 0000000000..064795df6c --- /dev/null +++ b/packages/core-logger-pino/src/index.ts @@ -0,0 +1,2 @@ +export * from "./driver"; +export * from "./plugin"; diff --git a/packages/core-logger-pino/src/plugin.ts b/packages/core-logger-pino/src/plugin.ts new file mode 100644 index 0000000000..73a41f9b23 --- /dev/null +++ b/packages/core-logger-pino/src/plugin.ts @@ -0,0 +1,14 @@ +import { Container } from "@arkecosystem/core-interfaces"; +import { LoggerManager } from "@arkecosystem/core-logger"; +import { defaults } from "./defaults"; +import { PinoLogger } from "./driver"; + +export const plugin: Container.PluginDescriptor = { + pkg: require("../package.json"), + defaults, + alias: "logger", + extends: "@arkecosystem/core-logger", + async register(container: Container.IContainer, options) { + return container.resolvePlugin("log-manager").createDriver(new PinoLogger(options)); + }, +}; diff --git a/packages/core-test-utils/tsconfig.json b/packages/core-logger-pino/tsconfig.json similarity index 100% rename from packages/core-test-utils/tsconfig.json rename to packages/core-logger-pino/tsconfig.json diff --git a/packages/core-logger-signale/.gitattributes b/packages/core-logger-signale/.gitattributes new file mode 100644 index 0000000000..63f6a5b970 --- /dev/null +++ b/packages/core-logger-signale/.gitattributes @@ -0,0 +1,7 @@ +# Path-based git attributes +# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html + +# Ignore all test and documentation with "export-ignore". +/.gitattributes export-ignore +/.gitignore export-ignore +/README.md export-ignore diff --git a/deprecated/core-snapshots-cli/README.md b/packages/core-logger-signale/README.md similarity index 51% rename from deprecated/core-snapshots-cli/README.md rename to packages/core-logger-signale/README.md index d41925bdf6..36f51774d5 100644 --- a/deprecated/core-snapshots-cli/README.md +++ b/packages/core-logger-signale/README.md @@ -1,12 +1,12 @@ -# Ark Core - Snapshots CLI +# Persona Core - Signale Logger

- +

## Documentation -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core-snapshots-cli.html). +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core-logger-signale.html). ## Security @@ -14,10 +14,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [Joshua Noack](https://github.com/supaiku0) -- [Kristjan Košič](https://github.com/kristjank) -- [All Contributors](../../../../contributors) +This project exists thanks to all the people who [contribute](../../../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-logger-signale/package.json b/packages/core-logger-signale/package.json new file mode 100644 index 0000000000..9bc44f9604 --- /dev/null +++ b/packages/core-logger-signale/package.json @@ -0,0 +1,32 @@ +{ + "name": "@arkecosystem/core-logger-signale", + "description": "Signale integration for ARK Core", + "version": "2.3.15", + "contributors": [ + "Brian Faust " + ], + "license": "MIT", + "main": "dist/index", + "types": "dist/index", + "files": [ + "dist" + ], + "scripts": { + "prepublishOnly": "yarn build", + "compile": "../../node_modules/typescript/bin/tsc", + "build": "yarn clean && yarn compile", + "build:watch": "yarn clean && yarn compile -w", + "clean": "del dist" + }, + "dependencies": { + "@arkecosystem/core-interfaces": "^2.3.15", + "@arkecosystem/core-logger": "^2.3.15", + "signale": "^1.4.0" + }, + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=10.x" + } +} diff --git a/packages/core-logger-signale/src/defaults.ts b/packages/core-logger-signale/src/defaults.ts new file mode 100644 index 0000000000..c4fc2a3b79 --- /dev/null +++ b/packages/core-logger-signale/src/defaults.ts @@ -0,0 +1,3 @@ +export const defaults = { + logLevel: process.env.CORE_LOG_LEVEL || "info", +}; diff --git a/packages/core-logger-signale/src/driver.ts b/packages/core-logger-signale/src/driver.ts new file mode 100644 index 0000000000..1c73789062 --- /dev/null +++ b/packages/core-logger-signale/src/driver.ts @@ -0,0 +1,19 @@ +import { Logger } from "@arkecosystem/core-interfaces"; +import { AbstractLogger } from "@arkecosystem/core-logger"; +import { Signale } from "signale"; + +export class SignaleLogger extends AbstractLogger { + protected logger: Signale; + + public make(): Logger.ILogger { + this.logger = new Signale(this.options); + + return this; + } + + protected getLevels(): Record { + return { + verbose: "note", + }; + } +} diff --git a/packages/core-logger-signale/src/index.ts b/packages/core-logger-signale/src/index.ts new file mode 100644 index 0000000000..064795df6c --- /dev/null +++ b/packages/core-logger-signale/src/index.ts @@ -0,0 +1,2 @@ +export * from "./driver"; +export * from "./plugin"; diff --git a/packages/core-logger-signale/src/plugin.ts b/packages/core-logger-signale/src/plugin.ts new file mode 100644 index 0000000000..1eb9755e0a --- /dev/null +++ b/packages/core-logger-signale/src/plugin.ts @@ -0,0 +1,14 @@ +import { Container } from "@arkecosystem/core-interfaces"; +import { LoggerManager } from "@arkecosystem/core-logger"; +import { defaults } from "./defaults"; +import { SignaleLogger } from "./driver"; + +export const plugin: Container.PluginDescriptor = { + pkg: require("../package.json"), + defaults, + alias: "logger", + extends: "@arkecosystem/core-logger", + async register(container: Container.IContainer, options) { + return container.resolvePlugin("log-manager").createDriver(new SignaleLogger(options)); + }, +}; diff --git a/packages/core-logger-signale/tsconfig.json b/packages/core-logger-signale/tsconfig.json new file mode 100644 index 0000000000..0b089c5fa8 --- /dev/null +++ b/packages/core-logger-signale/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": ["src/**/**.ts"] +} diff --git a/packages/core-logger-winston/.gitattributes b/packages/core-logger-winston/.gitattributes index 60cc52db63..63f6a5b970 100644 --- a/packages/core-logger-winston/.gitattributes +++ b/packages/core-logger-winston/.gitattributes @@ -2,10 +2,6 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore -/__tests__ export-ignore -/docs export-ignore /README.md export-ignore diff --git a/packages/core-logger-winston/README.md b/packages/core-logger-winston/README.md index eb4b8770e8..885df347ca 100644 --- a/packages/core-logger-winston/README.md +++ b/packages/core-logger-winston/README.md @@ -1,12 +1,12 @@ -# Ark Core - Winston Logger +# Persona Core - Winston Logger

- +

## Documentation -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core-logger-winston.html). +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/optional/core-logger-winston.html). ## Security @@ -14,10 +14,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [Brian Faust](https://github.com/faustbrian) -- [François-Xavier Thoorens](https://github.com/fix) -- [All Contributors](../../../../contributors) +This project exists thanks to all the people who [contribute](../../../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-logger-winston/__tests__/logger.test.ts b/packages/core-logger-winston/__tests__/logger.test.ts deleted file mode 100644 index ae1e64e0ab..0000000000 --- a/packages/core-logger-winston/__tests__/logger.test.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { AbstractLogger } from "@arkecosystem/core-logger"; -import * as capcon from "capture-console"; -import "jest-extended"; -import * as winston from "winston"; -import { WinstonLogger } from "../src"; - -let logger: AbstractLogger; -let message; - -beforeAll(() => { - const driver = new WinstonLogger({ - transports: [ - { - constructor: "Console", - package: "winston/lib/winston/transports/console", - options: { - level: "debug", - }, - }, - { - constructor: "File", - options: { filename: "tmp.log", level: "silly" }, - }, - ], - }); - - logger = driver.make(); - - capcon.startCapture(process.stdout, stdout => { - message += stdout; - }); -}); - -describe("Logger", () => { - describe("error", () => { - it("should log a message", () => { - logger.error("error_message"); - - expect(message).toMatch(/error/); - expect(message).toMatch(/error_message/); - message = null; - }); - }); - - describe("warn", () => { - it("should log a message", () => { - logger.warn("warning_message"); - - expect(message).toMatch(/warn/); - expect(message).toMatch(/warning_message/); - message = null; - }); - }); - - describe("info", () => { - it("should log a message", () => { - logger.info("info_message"); - - expect(message).toMatch(/info/); - expect(message).toMatch(/info_message/); - message = null; - }); - }); - - describe("debug", () => { - it("should log a message", () => { - logger.debug("debug_message"); - - expect(message).toMatch(/debug/); - expect(message).toMatch(/debug_message/); - message = null; - }); - }); - - describe("verbose", () => { - it("should log a message", () => { - logger.verbose("verbose_message"); - - expect(message).toMatch(/verbose/); - expect(message).toMatch(/verbose_message/); - message = null; - }); - }); - - describe("suppressConsoleOutput", () => { - it("should suppress console output", () => { - logger.suppressConsoleOutput(true); - - logger.info("silent_message"); - expect(message).toBeNull(); - - logger.suppressConsoleOutput(false); - - logger.info("non_silent_message"); - expect(message).toMatch(/non_silent_message/); - - message = null; - }); - }); -}); diff --git a/packages/core-logger-winston/package.json b/packages/core-logger-winston/package.json index 5e34ee23d2..4aefd47c28 100644 --- a/packages/core-logger-winston/package.json +++ b/packages/core-logger-winston/package.json @@ -1,7 +1,7 @@ { "name": "@arkecosystem/core-logger-winston", - "description": "Winston Logger for Ark Core", - "version": "2.2.1", + "description": "Winston Logger for ARK Core", + "version": "2.3.15", "contributors": [ "François-Xavier Thoorens ", "Brian Faust " @@ -13,49 +13,26 @@ "dist" ], "scripts": { - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", "prepublishOnly": "yarn build", - "pretest": "yarn lint && yarn build", "compile": "../../node_modules/typescript/bin/tsc", "build": "yarn clean && yarn compile", "build:watch": "yarn clean && yarn compile -w", - "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "test": "cross-env CORE_ENV=test jest --runInBand --forceExit", - "test:coverage": "cross-env CORE_ENV=test jest --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$' --runInBand --forceExit", - "test:debug": "cross-env CORE_ENV=test node --inspect-brk ../../node_modules/.bin/jest --runInBand", - "test:watch": "cross-env CORE_ENV=test jest --runInBand --watch", - "test:watch:all": "cross-env CORE_ENV=test jest --runInBand --watchAll", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" + "clean": "del dist" }, "dependencies": { - "@arkecosystem/core-interfaces": "^2.2.1", - "@arkecosystem/core-logger": "^2.2.1", + "@arkecosystem/core-interfaces": "^2.3.15", + "@arkecosystem/core-logger": "^2.3.15", "chalk": "^2.4.2", "colors": "^1.3.3", "dayjs-ext": "^2.2.0", - "lodash.isempty": "^4.4.0", - "node-emoji": "^1.8.1", "winston": "^3.2.1", - "winston-daily-rotate-file": "^3.6.0" - }, - "devDependencies": { - "@types/capture-console": "^1.0.0", - "@types/lodash.isempty": "^4.4.4", - "@types/node-emoji": "^1.8.1", - "capture-console": "^1.0.1" + "winston-daily-rotate-file": "^3.6.0", + "winston-transport": "^4.3.0" }, "publishConfig": { "access": "public" }, "engines": { "node": ">=10.x" - }, - "jest": { - "preset": "../../jest-preset.json" } } diff --git a/packages/core-logger-winston/src/driver.ts b/packages/core-logger-winston/src/driver.ts index 724770cb13..0d93866649 100644 --- a/packages/core-logger-winston/src/driver.ts +++ b/packages/core-logger-winston/src/driver.ts @@ -1,20 +1,13 @@ +import { Logger } from "@arkecosystem/core-interfaces"; import { AbstractLogger } from "@arkecosystem/core-logger"; import "colors"; -import isEmpty from "lodash/isEmpty"; -import { inspect } from "util"; import * as winston from "winston"; +import { ITransport, ITransportStream } from "./interfaces"; export class WinstonLogger extends AbstractLogger { - public logger: any; + protected logger: winston.Logger; - constructor(readonly options) { - super(options); - } - - /** - * Make the logger instance. - */ - public make() { + public make(): Logger.ILogger { this.logger = winston.createLogger(); this.registerTransports(); @@ -22,98 +15,25 @@ export class WinstonLogger extends AbstractLogger { return this; } - /** - * Log an error message. - * @param {*} message - * @return {void} - */ - public error(message: any): void { - this.createLog("error", message); - } - - /** - * Log a warning message. - * @param {*} message - * @return {void} - */ - public warn(message: any): void { - this.createLog("warn", message); - } - - /** - * Log an info message. - * @param {*} message - * @return {void} - */ - public info(message: any): void { - this.createLog("info", message); - } - - /** - * Log a debug message. - * @param {*} message - * @return {void} - */ - public debug(message: any): void { - this.createLog("debug", message); - } - - /** - * Log a verbose message. - * @param {*} message - * @return {void} - */ - public verbose(message: any): void { - this.createLog("verbose", message); - } - - /** - * Suppress console output. - * @param {Boolean} - * @return {void} - */ public suppressConsoleOutput(suppress: boolean): void { - const consoleTransport = this.logger.transports.find(t => t.name === "console"); + const consoleTransport = this.logger.transports.find( + (transport: ITransportStream) => transport.name === "console", + ); if (consoleTransport) { consoleTransport.silent = suppress; } } - /** - * Register all transports. - * @return {void} - */ private registerTransports(): void { - for (const transport of Object.values(this.options.transports)) { - // @ts-ignore + const transports: ITransport[] = Object.values(this.options.transports); + + for (const transport of transports) { if (transport.package) { - // @ts-ignore require(transport.package); } - this.logger.add( - // @ts-ignore - new winston.transports[transport.constructor](transport.options), - ); + this.logger.add(new winston.transports[transport.constructor](transport.options)); } } - - /** - * Log a message with the given method. - * @param {String} method - * @param {*} message - * @return {void} - */ - private createLog(method: string, message: any): void { - if (isEmpty(message)) { - return; - } - - if (typeof message !== "string") { - message = inspect(message, { depth: 1 }); - } - - this.logger[method](message); - } } diff --git a/packages/core-logger-winston/src/formatter.ts b/packages/core-logger-winston/src/formatter.ts index 9249d5e04c..59793debb5 100644 --- a/packages/core-logger-winston/src/formatter.ts +++ b/packages/core-logger-winston/src/formatter.ts @@ -1,6 +1,5 @@ import chalk from "chalk"; import dayjs from "dayjs-ext"; -import emoji from "node-emoji"; import { format } from "winston"; const { colorize, combine, timestamp, printf } = format; @@ -14,7 +13,7 @@ const formatter = (colorOutput: boolean = true) => const infoLevel = info[Symbol.for("level")]; let level = infoLevel.toUpperCase(); - let message = emoji.emojify(info.message) || JSON.stringify(info.meta); + let message = info.message || JSON.stringify(info.meta); if (colorOutput) { level = { diff --git a/packages/core-logger-winston/src/interfaces.ts b/packages/core-logger-winston/src/interfaces.ts new file mode 100644 index 0000000000..1285a12f9a --- /dev/null +++ b/packages/core-logger-winston/src/interfaces.ts @@ -0,0 +1,11 @@ +import TransportStream from "winston-transport"; + +export interface ITransportStream extends TransportStream { + name: string; +} + +export interface ITransport { + package?: string; + constructor: string; + options: Record; +} diff --git a/packages/core-logger-winston/src/plugin.ts b/packages/core-logger-winston/src/plugin.ts index 8388ab84fb..00a474210b 100644 --- a/packages/core-logger-winston/src/plugin.ts +++ b/packages/core-logger-winston/src/plugin.ts @@ -1,5 +1,5 @@ import { Container } from "@arkecosystem/core-interfaces"; -import { LogManager } from "@arkecosystem/core-logger"; +import { LoggerManager } from "@arkecosystem/core-logger"; import { defaults } from "./defaults"; import { WinstonLogger } from "./driver"; @@ -9,25 +9,6 @@ export const plugin: Container.PluginDescriptor = { alias: "logger", extends: "@arkecosystem/core-logger", async register(container: Container.IContainer, options) { - const logManager: LogManager = container.resolvePlugin("logManager"); - await logManager.makeDriver(new WinstonLogger(options)); - - const driver = logManager.driver(); - driver.debug(`Data Directory => ${process.env.CORE_PATH_DATA}`); - driver.debug(`Config Directory => ${process.env.CORE_PATH_CONFIG}`); - - if (process.env.CORE_PATH_CACHE) { - driver.debug(`Cache Directory => ${process.env.CORE_PATH_CACHE}`); - } - - if (process.env.CORE_PATH_LOG) { - driver.debug(`Log Directory => ${process.env.CORE_PATH_LOG}`); - } - - if (process.env.CORE_PATH_TEMP) { - driver.debug(`Temp Directory => ${process.env.CORE_PATH_TEMP}`); - } - - return driver; + return container.resolvePlugin("log-manager").createDriver(new WinstonLogger(options)); }, }; diff --git a/packages/core-logger/.gitattributes b/packages/core-logger/.gitattributes index 60cc52db63..63f6a5b970 100644 --- a/packages/core-logger/.gitattributes +++ b/packages/core-logger/.gitattributes @@ -2,10 +2,6 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore -/__tests__ export-ignore -/docs export-ignore /README.md export-ignore diff --git a/packages/core-logger/README.md b/packages/core-logger/README.md index 05e30ceccd..42fdd7739d 100644 --- a/packages/core-logger/README.md +++ b/packages/core-logger/README.md @@ -1,12 +1,12 @@ -# Ark Core - Logger - Interface +# Persona Core - Logger - Interface

- +

## Documentation -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core-logger.html). +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/required/core-logger.html). ## Security @@ -14,9 +14,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [Brian Faust](https://github.com/faustbrian) -- [All Contributors](../../../../contributors) +This project exists thanks to all the people who [contribute](../../../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-logger/package.json b/packages/core-logger/package.json index 81afa02e87..96bc60588b 100644 --- a/packages/core-logger/package.json +++ b/packages/core-logger/package.json @@ -1,7 +1,7 @@ { "name": "@arkecosystem/core-logger", - "description": "Logger Manager for Ark Core", - "version": "2.2.1", + "description": "Logger Manager for ARK Core", + "version": "2.3.15", "contributors": [ "Brian Faust " ], @@ -12,35 +12,26 @@ "dist" ], "scripts": { - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", "prepublishOnly": "yarn build", - "pretest": "yarn lint && yarn build", "compile": "../../node_modules/typescript/bin/tsc", "build": "yarn clean && yarn compile", "build:watch": "yarn clean && yarn compile -w", - "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "test": "cross-env CORE_ENV=test jest --runInBand --forceExit", - "test:coverage": "cross-env CORE_ENV=test jest --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$' --runInBand --forceExit", - "test:debug": "cross-env CORE_ENV=test node --inspect-brk ../../node_modules/.bin/jest --runInBand", - "test:watch": "cross-env CORE_ENV=test jest --runInBand --watch", - "test:watch:all": "cross-env CORE_ENV=test jest --runInBand --watchAll", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" + "clean": "del dist" }, "dependencies": { - "@arkecosystem/core-interfaces": "^2.2.1" + "@arkecosystem/core-container": "^2.3.15", + "@arkecosystem/core-interfaces": "^2.3.15", + "lodash.isempty": "^4.4.0" + }, + "devDependencies": { + "@types/capture-console": "^1.0.0", + "@types/lodash.isempty": "^4.4.6", + "capture-console": "^1.0.1" }, "publishConfig": { "access": "public" }, "engines": { "node": ">=10.x" - }, - "jest": { - "preset": "../../jest-preset.json" } } diff --git a/packages/core-logger/src/factory.ts b/packages/core-logger/src/factory.ts new file mode 100644 index 0000000000..955ff9a421 --- /dev/null +++ b/packages/core-logger/src/factory.ts @@ -0,0 +1,25 @@ +import { app } from "@arkecosystem/core-container"; +import { Logger } from "@arkecosystem/core-interfaces"; + +export class LoggerFactory { + public make(driver: Logger.ILogger): Logger.ILogger { + const instance: Logger.ILogger = driver.make(); + + driver.debug(`${app.getName()} ${app.getVersion()}`); + this.logPaths(instance); + + return instance; + } + + private logPaths(driver: Logger.ILogger): void { + for (const [key, value] of Object.entries({ + Data: process.env.CORE_PATH_DATA, + Config: process.env.CORE_PATH_CONFIG, + Cache: process.env.CORE_PATH_CACHE, + Log: process.env.CORE_PATH_LOG, + Temp: process.env.CORE_PATH_TEMP, + })) { + driver.debug(`${key} Directory: ${value}`); + } + } +} diff --git a/packages/core-logger/src/logger.ts b/packages/core-logger/src/logger.ts index 884c10be91..5c0914fb73 100644 --- a/packages/core-logger/src/logger.ts +++ b/packages/core-logger/src/logger.ts @@ -1,57 +1,68 @@ import { Logger } from "@arkecosystem/core-interfaces"; +import isEmpty from "lodash.isempty"; +import { inspect } from "util"; export abstract class AbstractLogger implements Logger.ILogger { - /** - * Create a new logger instance. - * @param {Object} options - */ - constructor(protected options: any) {} - - /** - * Make the logger instance. - * @return {Object} - */ + protected logger: any; + protected silentConsole: boolean = false; + + protected readonly defaultLevels: Record = { + error: "error", + warn: "warn", + info: "info", + debug: "debug", + verbose: "verbose", + }; + + constructor(protected readonly options: any) {} + public abstract make(): Logger.ILogger; - /** - * Log an error message. - * @param {*} message - * @return {void} - */ - public abstract error(message: any): void; - - /** - * Log a warning message. - * @param {*} message - * @return {void} - */ - public abstract warn(message: any): void; - - /** - * Log an info message. - * @param {*} message - * @return {void} - */ - public abstract info(message: any): void; - - /** - * Log a debug message. - * @param {*} message - * @return {void} - */ - public abstract debug(message: any): void; - - /** - * Log a verbose message. - * @param {*} message - * @return {void} - */ - public abstract verbose(message: any): void; - - /** - * Suppress console output. - * @param {Boolean} - * @return {void} - */ - public abstract suppressConsoleOutput(suppress: boolean): void; + public error(message: any): void { + this.createLog(this.getLevel("error"), message); + } + + public warn(message: any): void { + this.createLog(this.getLevel("warn"), message); + } + + public info(message: any): void { + this.createLog(this.getLevel("info"), message); + } + + public debug(message: any): void { + this.createLog(this.getLevel("debug"), message); + } + + public verbose(message: any): void { + this.createLog(this.getLevel("verbose"), message); + } + + public suppressConsoleOutput(suppress: boolean): void { + this.silentConsole = suppress; + } + + protected getLevel(level: string): string { + return { ...this.defaultLevels, ...this.getLevels() }[level]; + } + + protected getLevels(): Record { + return this.defaultLevels; + } + + private createLog(method: string, message: any): void { + if (this.silentConsole) { + return; + } + + if (isEmpty(message)) { + return; + } + + if (typeof message !== "string") { + message = inspect(message, { depth: 1 }); + } + + this.logger[method](message); + } } diff --git a/packages/core-logger/src/manager.ts b/packages/core-logger/src/manager.ts index b9d59e4383..bfea18dce6 100644 --- a/packages/core-logger/src/manager.ts +++ b/packages/core-logger/src/manager.ts @@ -1,31 +1,25 @@ import { Logger } from "@arkecosystem/core-interfaces"; +import { LoggerFactory } from "./factory"; -export class LogManager { - private drivers: Map; +export class LoggerManager { + private readonly factory: LoggerFactory = new LoggerFactory(); + private readonly drivers: Map = new Map(); - /** - * Create a new manager instance. - */ - constructor() { - this.drivers = new Map(); - } - - /** - * Get a logger instance. - * @param {String} name - * @return {AbstractLogger} - */ public driver(name: string = "default"): Logger.ILogger { return this.drivers.get(name); } - /** - * Make the logger instance. - * @param {AbstractLogger} driver - * @param {String} name - * @return {void} - */ - public async makeDriver(driver: Logger.ILogger, name: string = "default"): Promise { - this.drivers.set(name, await driver.make()); + public createDriver(driver: Logger.ILogger, name: string = "default"): Logger.ILogger { + this.drivers.set(name, this.factory.make(driver)); + + return this.driver(); + } + + public getDrivers(): Map { + return this.drivers; + } + + public getFactory(): LoggerFactory { + return this.factory; } } diff --git a/packages/core-logger/src/plugin.ts b/packages/core-logger/src/plugin.ts index c7d7eb8ebd..1a080070ca 100644 --- a/packages/core-logger/src/plugin.ts +++ b/packages/core-logger/src/plugin.ts @@ -1,9 +1,9 @@ -import { LogManager } from "./manager"; +import { LoggerManager } from "./manager"; export const plugin = { pkg: require("../package.json"), - alias: "logManager", + alias: "log-manager", async register() { - return new LogManager(); + return new LoggerManager(); }, }; diff --git a/packages/core-new-relic/.gitattributes b/packages/core-new-relic/.gitattributes new file mode 100644 index 0000000000..63f6a5b970 --- /dev/null +++ b/packages/core-new-relic/.gitattributes @@ -0,0 +1,7 @@ +# Path-based git attributes +# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html + +# Ignore all test and documentation with "export-ignore". +/.gitattributes export-ignore +/.gitignore export-ignore +/README.md export-ignore diff --git a/packages/core-new-relic/README.md b/packages/core-new-relic/README.md new file mode 100644 index 0000000000..3d0d4a799e --- /dev/null +++ b/packages/core-new-relic/README.md @@ -0,0 +1,21 @@ +# Persona Core - New Relic + +

+ +

+ +## Documentation + +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/optional/core-new-relic.html). + +## Security + +If you discover a security vulnerability within this package, please send an e-mail to security@ark.io. All security vulnerabilities will be promptly addressed. + +## Credits + +This project exists thanks to all the people who [contribute](../../../../contributors). + +## License + +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-new-relic/package.json b/packages/core-new-relic/package.json new file mode 100644 index 0000000000..f81b6e001c --- /dev/null +++ b/packages/core-new-relic/package.json @@ -0,0 +1,30 @@ +{ + "name": "@arkecosystem/core-new-relic", + "description": "New Relic integration for ARK Core.", + "version": "2.3.15", + "contributors": [ + "Brian Faust " + ], + "license": "MIT", + "main": "dist/index.js", + "files": [ + "dist" + ], + "scripts": { + "prepublishOnly": "yarn build", + "compile": "../../node_modules/typescript/bin/tsc", + "build": "yarn clean && yarn compile", + "build:watch": "yarn clean && yarn compile -w", + "clean": "del dist" + }, + "dependencies": { + "@arkecosystem/core-container": "^2.3.15", + "newrelic": "^5.6.1" + }, + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=10.x <11.0.0" + } +} diff --git a/packages/core-new-relic/src/index.ts b/packages/core-new-relic/src/index.ts new file mode 100644 index 0000000000..8423cfa6a4 --- /dev/null +++ b/packages/core-new-relic/src/index.ts @@ -0,0 +1,10 @@ +import { Container } from "@arkecosystem/core-interfaces"; +import newrelic from "newrelic"; + +export const plugin: Container.PluginDescriptor = { + pkg: require("../package.json"), + alias: "error-tracker", + async register(container: Container.IContainer, options) { + return newrelic; + }, +}; diff --git a/packages/core-new-relic/tsconfig.json b/packages/core-new-relic/tsconfig.json new file mode 100644 index 0000000000..0b089c5fa8 --- /dev/null +++ b/packages/core-new-relic/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": ["src/**/**.ts"] +} diff --git a/packages/core-p2p/.gitattributes b/packages/core-p2p/.gitattributes index 60cc52db63..63f6a5b970 100644 --- a/packages/core-p2p/.gitattributes +++ b/packages/core-p2p/.gitattributes @@ -2,10 +2,6 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore -/__tests__ export-ignore -/docs export-ignore /README.md export-ignore diff --git a/packages/core-p2p/README.md b/packages/core-p2p/README.md index b8819d9bae..b577ca13aa 100644 --- a/packages/core-p2p/README.md +++ b/packages/core-p2p/README.md @@ -1,12 +1,12 @@ -# Ark Core - P2P API +# Persona Core - P2P API

- +

## Documentation -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core-p2p.html). +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/required/core-p2p.html). ## Security @@ -14,14 +14,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [Alex Barnsley](https://github.com/alexbarnsley) -- [Brian Faust](https://github.com/faustbrian) -- [Erwann Gentric](https://github.com/air1one) -- [François-Xavier Thoorens](https://github.com/fix) -- [Joshua Noack](https://github.com/supaiku0) -- [Kristjan Košič](https://github.com/kristjank) -- [All Contributors](../../../../contributors) +This project exists thanks to all the people who [contribute](../../../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-p2p/__mocks__/sntp.ts b/packages/core-p2p/__mocks__/sntp.ts deleted file mode 100644 index 6dc687fbc8..0000000000 --- a/packages/core-p2p/__mocks__/sntp.ts +++ /dev/null @@ -1,12 +0,0 @@ -const Sntp: any = jest.genMockFromModule("sntp"); - -const sntpTime = Sntp.time; -Sntp.time = options => { - if (options.host === "notime.unknown.not") { - // we actually want to call the real Sntp time() because we want it to fail - return sntpTime(options); - } - return { t: 111 }; -}; - -export = Sntp; diff --git a/packages/core-p2p/__tests__/monitor.test.ts b/packages/core-p2p/__tests__/monitor.test.ts deleted file mode 100644 index 9b7c9f1484..0000000000 --- a/packages/core-p2p/__tests__/monitor.test.ts +++ /dev/null @@ -1,191 +0,0 @@ -/* tslint:disable:max-line-length */ -import { models } from "@arkecosystem/crypto"; -import axios from "axios"; -import MockAdapter from "axios-mock-adapter"; -import { defaults } from "../src/defaults"; -import { Peer } from "../src/peer"; -import { setUp, tearDown } from "./__support__/setup"; -const { Block } = models; - -const axiosMock = new MockAdapter(axios); - -let genesisBlock; -let peerMock: Peer; -let monitor; - -beforeAll(async () => { - await setUp(); - monitor = require("../src/monitor").monitor; - - // Create the genesis block after the setup has finished or else it uses a potentially - // wrong network config. - genesisBlock = new Block(require("@arkecosystem/core-test-utils/src/config/testnet/genesisBlock.json")); -}); - -afterAll(async () => { - await tearDown(); -}); - -beforeEach(async () => { - monitor.config = defaults; - - const initialPeersMock = {}; - ["1.0.0.0", "1.0.0.1", "1.0.0.2", "1.0.0.3", "1.0.0.4"].forEach(ip => { - const initialPeer = new Peer(ip, 4000); - initialPeersMock[ip] = Object.assign(initialPeer, initialPeer.headers, { - ban: 0, - verification: { forked: false }, - }); - }); - - monitor.peers = initialPeersMock; - - peerMock = new Peer("1.0.0.99", 4000); // this peer is just here to be picked up by tests below (not added to initial peers) - Object.assign(peerMock, peerMock.headers, { status: 200 }); - peerMock.nethash = "d9acd04bde4234a81addb8482333b4ac906bed7be5a9970ce8ada428bd083192"; - - axiosMock.reset(); // important: resets any existing mocking behavior -}); - -describe("Monitor", () => { - describe("cleanPeers", () => { - it("should be ok", async () => { - const previousLength = Object.keys(monitor.peers).length; - - await monitor.cleanPeers(true); - - expect(Object.keys(monitor.peers).length).toBeLessThan(previousLength); - }); - }); - - describe("acceptNewPeer", () => { - it("should be ok", async () => { - axiosMock.onGet(`${peerMock.url}/peer/status`).reply(() => [ - 200, - { - header: { - height: 1, - id: genesisBlock.data.id, - }, - success: true, - }, - peerMock.headers, - ]); - - await monitor.acceptNewPeer(peerMock); - - expect(monitor.peers[peerMock.ip]).toBeObject(); - }); - }); - - describe("getPeers", () => { - it("should be ok", async () => { - const peers = monitor.getPeers(); - - expect(peers).toBeArray(); - expect(peers.length).toBe(5); // 5 from peers.json - }); - }); - - describe("discoverPeers", () => { - it("should be ok", async () => { - axiosMock.onGet(/.*\/peer\/status/).reply(() => [ - 200, - { - header: { - height: 1, - id: genesisBlock.data.id, - }, - success: true, - }, - peerMock.headers, - ]); - axiosMock.onGet(/.*\/peer\/list/).reply(() => [ - 200, - { - peers: [peerMock.toBroadcastInfo()], - success: true, - }, - peerMock.headers, - ]); - - await monitor.discoverPeers(); - const peers = monitor.getPeers(); - - expect(peers).toBeArray(); - expect(Object.keys(peers).length).toBe(6); // 5 from initial peers + 1 from peerMock - expect(peers.find(e => e.ip === peerMock.ip)).toBeDefined(); - }); - }); - - describe("getNetworkHeight", () => { - it("should be ok", async () => { - axiosMock.onGet(/.*\/peer\/status/).reply(() => [ - 200, - { - header: { - height: 1, - id: genesisBlock.data.id, - }, - height: 2, - success: true, - }, - peerMock.headers, - ]); - axiosMock.onGet(/.*\/peer\/list/).reply(() => [200, { peers: [] }, peerMock.headers]); - await monitor.discoverPeers(); - await monitor.cleanPeers(); - - const height = await monitor.getNetworkHeight(); - expect(height).toBe(2); - }); - - // TODO test with peers with different heights (use replyOnce) and check that median is OK - }); - - describe("getPBFTForgingStatus", () => { - it("should be ok", async () => { - axiosMock.onGet(/.*\/peer\/status/).reply(() => [200, { success: true, height: 2 }, peerMock.headers]); - axiosMock.onGet(/.*\/peer\/list/).reply(() => [200, { peers: [] }, peerMock.headers]); - - await monitor.discoverPeers(); - const pbftForgingStatus = monitor.getPBFTForgingStatus(); - - expect(pbftForgingStatus).toBeNumber(); - // TODO test mocking peers currentSlot & forgingAllowed - }); - }); - - describe("downloadBlocks", () => { - it("should be ok", async () => { - axiosMock.onGet(/.*\/peer\/blocks\/common/).reply(() => [ - 200, - { - success: true, - common: true, - }, - peerMock.headers, - ]); - axiosMock.onGet(/.*\/peer\/status/).reply(() => [ - 200, - { - success: true, - height: 2, - }, - peerMock.headers, - ]); - axiosMock.onGet(/.*\/peer\/blocks/).reply(() => [ - 200, - { - blocks: [{ height: 1, id: "1" }, { height: 2, id: "2" }], - }, - peerMock.headers, - ]); - - const blocks = await monitor.downloadBlocks(1); - - expect(blocks).toBeArray(); - expect(blocks.length).toBe(2); - }); - }); -}); diff --git a/packages/core-p2p/__tests__/utils/check-dns.test.ts b/packages/core-p2p/__tests__/utils/check-dns.test.ts deleted file mode 100644 index 0a78b44190..0000000000 --- a/packages/core-p2p/__tests__/utils/check-dns.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { checkDNS } from "../../src/utils"; -import { setUp, tearDown } from "../__support__/setup"; - -beforeAll(async () => { - await setUp(); -}); - -afterAll(async () => { - await tearDown(); -}); - -describe("Check DNS", () => { - it("should be ok", async () => { - const response = await checkDNS(["1.1.1.1"]); - expect(response).toBe("1.1.1.1"); - }); -}); diff --git a/packages/core-p2p/package.json b/packages/core-p2p/package.json index 3b00e2d24e..ae993c98d5 100644 --- a/packages/core-p2p/package.json +++ b/packages/core-p2p/package.json @@ -1,7 +1,7 @@ { "name": "@arkecosystem/core-p2p", - "description": "P2P API for Ark Core", - "version": "2.2.1", + "description": "P2P API for ARK Core", + "version": "2.3.15", "contributors": [ "François-Xavier Thoorens ", "Kristjan Košič ", @@ -15,35 +15,23 @@ "dist" ], "scripts": { - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", "prepublishOnly": "yarn build", "pretest": "bash ../../scripts/pre-test.sh", "compile": "../../node_modules/typescript/bin/tsc", "build": "yarn clean && yarn compile", "build:watch": "yarn clean && yarn compile -w", - "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "test": "cross-env CORE_ENV=test jest --runInBand --forceExit", - "test:coverage": "cross-env CORE_ENV=test jest --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$' --runInBand --forceExit", - "test:debug": "cross-env CORE_ENV=test node --inspect-brk ../../node_modules/.bin/jest --runInBand", - "test:watch": "cross-env CORE_ENV=test jest --runInBand --watch", - "test:watch:all": "cross-env CORE_ENV=test jest --runInBand --watchAll", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" + "clean": "del dist" }, "dependencies": { - "@arkecosystem/core-container": "^2.2.1", - "@arkecosystem/core-http-utils": "^2.2.1", - "@arkecosystem/core-interfaces": "^2.2.1", - "@arkecosystem/core-transaction-pool": "^2.2.1", - "@arkecosystem/crypto": "^2.2.1", - "ajv": "^6.9.1", - "axios": "^0.18.0", + "@arkecosystem/core-container": "^2.3.15", + "@arkecosystem/core-http-utils": "^2.3.15", + "@arkecosystem/core-interfaces": "^2.3.15", + "@arkecosystem/core-transaction-pool": "^2.3.15", + "@arkecosystem/core-utils": "^2.3.15", + "@arkecosystem/crypto": "^2.3.15", + "@faustbrian/dato": "^0.2.0", + "ajv": "^6.10.0", "boom": "^7.3.0", - "dayjs-ext": "^2.2.0", "delay": "^4.1.0", "hapi-rate-limit": "^3.1.1", "ip": "^1.1.5", @@ -59,40 +47,35 @@ "lodash.shuffle": "^4.2.0", "lodash.sumby": "^4.6.0", "lodash.take": "^4.1.1", - "micromatch": "^3.1.10", + "nanomatch": "^1.2.13", "pluralize": "^7.0.0", "pretty-ms": "^4.0.0", "semver": "^5.6.0", "sntp": "^3.0.2" }, "devDependencies": { - "@arkecosystem/core-test-utils": "^2.2.1", "@types/boom": "^7.2.1", "@types/ip": "^1.1.0", - "@types/joi": "^14.3.1", - "@types/lodash.chunk": "^4.2.4", - "@types/lodash.flatten": "^4.4.4", - "@types/lodash.get": "^4.4.4", - "@types/lodash.groupby": "^4.6.4", - "@types/lodash.head": "^4.0.4", - "@types/lodash.sample": "^4.2.4", - "@types/lodash.set": "^4.3.4", - "@types/lodash.shuffle": "^4.2.4", - "@types/lodash.sumby": "^4.6.4", - "@types/lodash.take": "^4.1.4", + "@types/joi": "^14.3.2", + "@types/lodash.chunk": "^4.2.6", + "@types/lodash.flatten": "^4.4.6", + "@types/lodash.get": "^4.4.6", + "@types/lodash.groupby": "^4.6.6", + "@types/lodash.head": "^4.0.6", + "@types/lodash.sample": "^4.2.6", + "@types/lodash.set": "^4.3.6", + "@types/lodash.shuffle": "^4.2.6", + "@types/lodash.sumby": "^4.6.6", + "@types/lodash.take": "^4.1.6", "@types/micromatch": "^3.1.0", "@types/pluralize": "^0.0.29", "@types/pretty-ms": "^4.0.0", - "@types/semver": "^5.5.0", - "axios-mock-adapter": "^1.16.0" + "@types/semver": "^5.5.0" }, "publishConfig": { "access": "public" }, "engines": { "node": ">=10.x" - }, - "jest": { - "preset": "../../jest-preset.json" } } diff --git a/packages/core-p2p/src/court/guard.ts b/packages/core-p2p/src/court/guard.ts index fb866bb2b4..46735bf8e3 100644 --- a/packages/core-p2p/src/court/guard.ts +++ b/packages/core-p2p/src/court/guard.ts @@ -1,64 +1,44 @@ import { app } from "@arkecosystem/core-container"; -import { Logger } from "@arkecosystem/core-interfaces"; -import dayjs from "dayjs-ext"; -import head from "lodash/head"; -import sumBy from "lodash/sumBy"; +import { Logger, P2P } from "@arkecosystem/core-interfaces"; +import { dato } from "@faustbrian/dato"; +import head from "lodash.head"; +import sumBy from "lodash.sumby"; import prettyMs from "pretty-ms"; import semver from "semver"; - import { config as localConfig } from "../config"; -import * as utils from "../utils"; import { offences } from "./offences"; -const config = app.getConfig(); -const logger = app.resolvePlugin("logger"); - -export interface ISuspension { - peer: any; - reason: string; - until: dayjs.Dayjs; - nextSuspensionReminder?: dayjs.Dayjs; -} - export class Guard { - public readonly suspensions: { [ip: string]: ISuspension }; public config: any; - private monitor: any; + public monitor: any; + public suspensions: { [ip: string]: P2P.ISuspension }; + + private readonly appConfig = app.getConfig(); + private readonly logger = app.resolvePlugin("logger"); - /** - * Create a new guard instance. - */ constructor() { this.suspensions = {}; this.config = localConfig; } - /** - * Initialise a new guard. - * @param {IMonitor} monitor - */ - public init(monitor) { + public init(monitor: P2P.IMonitor) { this.monitor = monitor; return this; } - /** - * Get a list of all suspended peers. - * @return {Object} - */ public all() { return this.suspensions; } - /** - * Get the suspended peer for the give IP. - * @return {Object} - */ - public get(ip) { + public get(ip: string) { return this.suspensions[ip]; } + public delete(ip: string): void { + delete this.suspensions[ip]; + } + /** * Suspends a peer unless whitelisted. * @param {Peer} peer @@ -70,7 +50,7 @@ export class Guard { } if (peer.offences.length > 0) { - if (dayjs().isAfter((head(peer.offences) as any).until)) { + if (dato().isAfter((head(peer.offences) as any).until)) { peer.offences = []; } } @@ -100,7 +80,7 @@ export class Guard { // Don't unsuspend critical offenders before the ban is expired. if (peer.offences.some(offence => offence.critical)) { - if (dayjs().isBefore(this.suspensions[peer.ip].until)) { + if (dato().isBefore(this.suspensions[peer.ip].until)) { return; } } @@ -116,7 +96,7 @@ export class Guard { * @return {void} */ public async resetSuspendedPeers() { - logger.info("Clearing suspended peers."); + this.logger.info("Clearing suspended peers."); await Promise.all(Object.values(this.suspensions).map(suspension => this.unsuspend(suspension.peer))); } @@ -128,20 +108,20 @@ export class Guard { public isSuspended(peer) { const suspendedPeer = this.get(peer.ip); - if (suspendedPeer && dayjs().isBefore(suspendedPeer.until)) { + if (suspendedPeer && dato().isBefore(suspendedPeer.until)) { const nextSuspensionReminder = suspendedPeer.nextSuspensionReminder; - if (!nextSuspensionReminder || dayjs().isAfter(nextSuspensionReminder)) { + if (!nextSuspensionReminder || dato().isAfter(nextSuspensionReminder)) { // @ts-ignore - const untilDiff = suspendedPeer.until.diff(dayjs()); + const untilDiff = suspendedPeer.until.diff(dato()); - logger.debug( + this.logger.debug( `${peer.ip} still suspended for ${prettyMs(untilDiff, { verbose: true, })} because of "${suspendedPeer.reason}".`, ); - suspendedPeer.nextSuspensionReminder = dayjs().add(5, "minute"); + suspendedPeer.nextSuspensionReminder = dato().addMinutes(5); } return true; @@ -195,7 +175,7 @@ export class Guard { */ public isValidNetwork(peer) { const nethash = peer.nethash || (peer.headers && peer.headers.nethash); - return nethash === config.get("network.nethash"); + return nethash === this.appConfig.get("network.nethash"); } /** @@ -219,7 +199,7 @@ export class Guard { /** * Decide for how long the peer should be banned. * @param {Peer} peer - * @return {dayjs} + * @return {Object} */ public __determineOffence(peer) { if (this.isBlacklisted(peer)) { @@ -297,11 +277,11 @@ export class Guard { offence = offences.REPEAT_OFFENDER; } - const until = dayjs().add(offence.number, offence.period); + const until = dato()[offence.period](offence.number); // @ts-ignore - const untilDiff = until.diff(dayjs()); + const untilDiff = until.diff(dato()); - logger.debug( + this.logger.debug( `Suspended ${peer.ip} for ${prettyMs(untilDiff, { verbose: true, })} because of "${offence.reason}"`, diff --git a/packages/core-p2p/src/court/offences.ts b/packages/core-p2p/src/court/offences.ts index 9ce3d3c3db..b08cd8b3af 100644 --- a/packages/core-p2p/src/court/offences.ts +++ b/packages/core-p2p/src/court/offences.ts @@ -1,95 +1,95 @@ export const offences = { BLACKLISTED: { number: 1, - period: "year", + period: "addYears", reason: "Blacklisted", weight: 10, critical: true, }, NO_COMMON_BLOCKS: { number: 5, - period: "minute", + period: "addMinutes", reason: "No Common Blocks", weight: 1, critical: true, }, NO_COMMON_ID: { number: 5, - period: "minute", + period: "addMinutes", reason: "No Common Id", weight: 1, critical: true, }, INVALID_VERSION: { number: 5, - period: "minute", + period: "addMinutes", reason: "Invalid Version", weight: 2, }, INVALID_MILESTONE_HASH: { number: 5, - period: "minute", + period: "addMinutes", reason: "Invalid Milestones", weight: 2, }, INVALID_HEIGHT: { number: 10, - period: "minute", + period: "addMinutes", reason: "Node is not at height", weight: 3, }, INVALID_NETWORK: { number: 5, - period: "minute", + period: "addMinutes", reason: "Invalid Network", weight: 5, critical: true, }, INVALID_STATUS: { number: 5, - period: "minute", + period: "addMinutes", reason: "Invalid Response Status", weight: 3, }, TIMEOUT: { - number: 2, - period: "minute", + number: 30, + period: "addSeconds", reason: "Timeout", weight: 2, }, HIGH_LATENCY: { number: 1, - period: "minute", + period: "addMinutes", reason: "High Latency", weight: 1, }, BLOCKCHAIN_NOT_READY: { number: 30, - period: "second", + period: "addSeconds", reason: "Blockchain not ready", weight: 0, }, TOO_MANY_REQUESTS: { number: 60, - period: "second", + period: "addSeconds", reason: "Rate limit exceeded", weight: 0, }, FORK: { number: 15, - period: "minute", + period: "addMinutes", reason: "Fork", weight: 10, }, UNKNOWN: { number: 10, - period: "minute", + period: "addMinutes", reason: "Unknown", weight: 5, }, REPEAT_OFFENDER: { number: 1, - period: "day", + period: "addDays", reason: "Repeat Offender", weight: 100, }, diff --git a/packages/core-p2p/src/defaults.ts b/packages/core-p2p/src/defaults.ts index 20b44ebdd3..567b88bb79 100644 --- a/packages/core-p2p/src/defaults.ts +++ b/packages/core-p2p/src/defaults.ts @@ -1,14 +1,14 @@ export const defaults = { host: process.env.CORE_P2P_HOST || "0.0.0.0", - port: process.env.CORE_P2P_PORT || 4002, + port: process.env.CORE_P2P_PORT || 4102, /** * The minimum peer version we expect */ - minimumVersions: [">=2.1.0", ">=2.2.0-alpha.0", ">=2.2.0-beta.0", ">=2.2.0-rc.0"], + minimumVersions: [">=2.1.0"], /** * The number of peers we expect to be available to start a relay */ - minimumNetworkReach: 20, + minimumNetworkReach: 15, /** * The timeout for requests to other peers */ @@ -16,7 +16,7 @@ export const defaults = { /** * The number of seconds until we allow forging */ - coldStart: 30, + coldStart: 15, /** * The maximum number of peers we will broadcast data to */ @@ -67,8 +67,4 @@ export const defaults = { }, ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1"], }, - /** - * Whether or not we enable the remote API (Caution!) - */ - remoteInterface: false, }; diff --git a/packages/core-p2p/src/errors.ts b/packages/core-p2p/src/errors.ts index 3aa800d540..d01c4f3dc1 100644 --- a/packages/core-p2p/src/errors.ts +++ b/packages/core-p2p/src/errors.ts @@ -19,8 +19,8 @@ export class P2PError extends Error { } export class PeerStatusResponseError extends P2PError { - constructor(message: string) { - super(`Failed to retrieve status: ${message}`); + constructor(ip: string) { + super(`Failed to retrieve status from peer ${ip}.`); } } diff --git a/packages/core-p2p/src/index.ts b/packages/core-p2p/src/index.ts index 5c15e09dbd..88319ca6b2 100644 --- a/packages/core-p2p/src/index.ts +++ b/packages/core-p2p/src/index.ts @@ -3,3 +3,4 @@ export * from "./peer"; export * from "./court"; export * from "./plugin"; export * from "./network-state"; +export * from "./server/types"; diff --git a/packages/core-p2p/src/interfaces.ts b/packages/core-p2p/src/interfaces.ts new file mode 100644 index 0000000000..80d6d27e2f --- /dev/null +++ b/packages/core-p2p/src/interfaces.ts @@ -0,0 +1,4 @@ +export interface IAcceptNewPeerOptions { + seed?: boolean; + lessVerbose?: boolean; +} diff --git a/packages/core-p2p/src/monitor.ts b/packages/core-p2p/src/monitor.ts index f525053690..690857566c 100644 --- a/packages/core-p2p/src/monitor.ts +++ b/packages/core-p2p/src/monitor.ts @@ -3,32 +3,23 @@ import { app } from "@arkecosystem/core-container"; import { Blockchain, Database, EventEmitter, Logger, P2P } from "@arkecosystem/core-interfaces"; import { slots } from "@arkecosystem/crypto"; -import dayjs from "dayjs-ext"; +import { dato, Dato } from "@faustbrian/dato"; import delay from "delay"; import fs from "fs"; -import groupBy from "lodash/groupBy"; -import sample from "lodash/sample"; -import shuffle from "lodash/shuffle"; -import take from "lodash/take"; +import groupBy from "lodash.groupby"; +import sample from "lodash.sample"; +import shuffle from "lodash.shuffle"; +import take from "lodash.take"; import pluralize from "pluralize"; import prettyMs from "pretty-ms"; - import { config as localConfig } from "./config"; import { guard, Guard } from "./court"; +import { PeerStatusResponseError } from "./errors"; +import { IAcceptNewPeerOptions } from "./interfaces"; import { NetworkState } from "./network-state"; import { Peer } from "./peer"; - import { checkDNS, checkNTP, isValidPeer, restorePeers } from "./utils"; -let config; -let logger: Logger.ILogger; -let emitter: EventEmitter.EventEmitter; - -interface IAcceptNewPeerOptions { - seed?: boolean; - lessVerbose?: boolean; -} - export class Monitor implements P2P.IMonitor { public peers: { [ip: string]: any }; public server: any; @@ -37,7 +28,11 @@ export class Monitor implements P2P.IMonitor { public nextUpdateNetworkStatusScheduled: boolean; private initializing: boolean; private pendingPeers: { [ip: string]: any }; - private coldStartPeriod: dayjs.Dayjs; + private coldStartPeriod: Dato; + + private readonly appConfig = app.getConfig(); + private readonly logger: Logger.ILogger = app.resolvePlugin("logger"); + private readonly emitter: EventEmitter.EventEmitter = app.resolvePlugin("event-emitter"); /** * @constructor @@ -45,7 +40,7 @@ export class Monitor implements P2P.IMonitor { */ constructor() { this.peers = {}; - this.coldStartPeriod = dayjs().add(localConfig.get("coldStart"), "second"); + this.coldStartPeriod = dato().addSeconds(localConfig.get("coldStart")); this.initializing = true; // Holds temporary peers which are in the process of being accepted. Prevents that @@ -58,13 +53,9 @@ export class Monitor implements P2P.IMonitor { * Method to run on startup. * @param {Object} options */ - public async start(options) { + public async start(options): Promise { this.config = options; - config = app.getConfig(); - logger = app.resolvePlugin("logger"); - emitter = app.resolvePlugin("event-emitter"); - await this.__checkDNSConnectivity(options.dns); await this.__checkNTPConnectivity(options.ntp); @@ -76,18 +67,12 @@ export class Monitor implements P2P.IMonitor { await this.populateSeedPeers(); if (this.config.skipDiscovery) { - logger.warn("Skipped peer discovery because the relay is in skip-discovery mode."); + this.logger.warn("Skipped peer discovery because the relay is in skip-discovery mode."); } else { await this.updateNetworkStatus(options.networkStart); for (const [version, peers] of Object.entries(groupBy(this.peers, "version"))) { - logger.info(`Discovered ${pluralize("peer", peers.length, true)} with v${version}.`); - } - - if (config.get("network.name") !== "mainnet") { - for (const [hashid, peers] of Object.entries(groupBy(this.peers, "hashid"))) { - logger.info(`Discovered ${pluralize("peer", peers.length, true)} on commit ${hashid}.`); - } + this.logger.info(`Discovered ${pluralize("peer", peers.length, true)} with v${version}.`); } } @@ -100,18 +85,18 @@ export class Monitor implements P2P.IMonitor { * @param {Boolean} networkStart * @return {Promise} */ - public async updateNetworkStatus(networkStart: boolean = false) { + public async updateNetworkStatus(networkStart: boolean = false): Promise { if (process.env.CORE_ENV === "test" || process.env.NODE_ENV === "test") { return; } if (networkStart) { - logger.warn("Skipped peer discovery because the relay is in genesis-start mode."); + this.logger.warn("Skipped peer discovery because the relay is in genesis-start mode."); return; } if (this.config.disableDiscovery) { - logger.warn("Skipped peer discovery because the relay is in non-discovery mode."); + this.logger.warn("Skipped peer discovery because the relay is in non-discovery mode."); return; } @@ -119,7 +104,7 @@ export class Monitor implements P2P.IMonitor { await this.discoverPeers(); await this.cleanPeers(); } catch (error) { - logger.error(`Network Status: ${error.message}`); + this.logger.error(`Network Status: ${error.message}`); } let nextRunDelaySeconds = 600; @@ -127,40 +112,31 @@ export class Monitor implements P2P.IMonitor { if (!this.hasMinimumPeers()) { await this.populateSeedPeers(); nextRunDelaySeconds = 5; - logger.info(`Couldn't find enough peers. Falling back to seed peers.`); + this.logger.info(`Couldn't find enough peers. Falling back to seed peers.`); } this.scheduleUpdateNetworkStatus(nextRunDelaySeconds); } - /** - * Accept and store a valid peer. - * @param {Peer} peer - * @throws {Error} If invalid peer - */ - public async acceptNewPeer(peer, options: IAcceptNewPeerOptions = {}) { + public validatePeer(peer, options: IAcceptNewPeerOptions = {}): boolean { if (this.config.disableDiscovery && !this.pendingPeers[peer.ip]) { - logger.warn(`Rejected ${peer.ip} because the relay is in non-discovery mode.`); - return; + this.logger.warn(`Rejected ${peer.ip} because the relay is in non-discovery mode.`); + return false; } if (!isValidPeer(peer) || this.guard.isSuspended(peer) || this.pendingPeers[peer.ip]) { - return; + return false; } - const newPeer = new Peer(peer.ip, peer.port); - newPeer.setHeaders(peer); - if (this.guard.isBlacklisted(peer)) { - logger.debug(`Rejected peer ${peer.ip} as it is blacklisted`); - - return this.guard.suspend(newPeer); + this.logger.debug(`Rejected peer ${peer.ip} as it is blacklisted`); + return false; } if (!this.guard.isValidVersion(peer) && !this.guard.isWhitelisted(peer)) { const minimumVersions: string[] = localConfig.get("minimumVersions"); - logger.debug( + this.logger.debug( `Rejected peer ${ peer.ip } as it doesn't meet the minimum version requirements. Expected: ${minimumVersions} - Received: ${ @@ -168,23 +144,33 @@ export class Monitor implements P2P.IMonitor { }`, ); - return this.guard.suspend(newPeer); + return false; } if (!this.guard.isValidNetwork(peer) && !options.seed) { - logger.debug( - `Rejected peer ${peer.ip} as it isn't on the same network. Expected: ${config.get( + this.logger.debug( + `Rejected peer ${peer.ip} as it isn't on the same network. Expected: ${this.appConfig.get( "network.nethash", )} - Received: ${peer.nethash}`, ); - return this.guard.suspend(newPeer); + return false; } + return true; + } + + /** + * Accept and store a valid peer. + */ + public async acceptNewPeer(peer, options: IAcceptNewPeerOptions = {}): Promise { if (this.getPeer(peer.ip)) { return; } + const newPeer = new Peer(peer.ip, peer.port); + newPeer.setHeaders(peer); + try { this.pendingPeers[peer.ip] = true; @@ -193,14 +179,17 @@ export class Monitor implements P2P.IMonitor { this.peers[peer.ip] = newPeer; if (!options.lessVerbose) { - logger.debug(`Accepted new peer ${newPeer.ip}:${newPeer.port}`); + this.logger.debug(`Accepted new peer ${newPeer.ip}:${newPeer.port}`); } - emitter.emit("peer.added", newPeer); + this.emitter.emit("peer.added", newPeer); } catch (error) { - logger.debug(`Could not accept new peer ${newPeer.ip}:${newPeer.port}: ${error}`); - - this.guard.suspend(newPeer); + if (error instanceof PeerStatusResponseError) { + this.logger.debug(error.message); + } else { + this.logger.debug(`Could not accept new peer ${newPeer.ip}:${newPeer.port}: ${error}`); + this.guard.suspend(newPeer); + } } finally { delete this.pendingPeers[peer.ip]; } @@ -210,7 +199,7 @@ export class Monitor implements P2P.IMonitor { * Remove peer from monitor. * @param {Peer} peer */ - public removePeer(peer) { + public removePeer(peer): void { delete this.peers[peer.ip]; } @@ -220,13 +209,13 @@ export class Monitor implements P2P.IMonitor { * @param {Boolean} tracker * @param {Boolean} forcePing */ - public async cleanPeers(fast = false, forcePing = false) { + public async cleanPeers(fast = false, forcePing = false): Promise { const keys = Object.keys(this.peers); let unresponsivePeers = 0; const pingDelay = fast ? 1500 : localConfig.get("globalTimeout"); const max = keys.length; - logger.info(`Checking ${max} peers :telescope:`); + this.logger.info(`Checking ${max} peers`); const peerErrors = {}; await Promise.all( keys.map(async ip => { @@ -242,7 +231,7 @@ export class Monitor implements P2P.IMonitor { peerErrors[error] = [peer]; } - emitter.emit("peer.removed", peer); + this.emitter.emit("peer.removed", peer); this.removePeer(peer); @@ -253,13 +242,13 @@ export class Monitor implements P2P.IMonitor { Object.keys(peerErrors).forEach((key: any) => { const peerCount = peerErrors[key].length; - logger.debug(`Removed ${peerCount} ${pluralize("peers", peerCount)} because of "${key}"`); + this.logger.debug(`Removed ${peerCount} ${pluralize("peers", peerCount)} because of "${key}"`); }); if (this.initializing) { - logger.info(`${max - unresponsivePeers} of ${max} peers on the network are responsive`); - logger.info(`Median Network Height: ${this.getNetworkHeight().toLocaleString()}`); - logger.info(`Network PBFT status: ${this.getPBFTForgingStatus()}`); + this.logger.info(`${max - unresponsivePeers} of ${max} peers on the network are responsive`); + this.logger.info(`Median Network Height: ${this.getNetworkHeight().toLocaleString()}`); + this.logger.info(`Network PBFT status: ${this.getPBFTForgingStatus()}`); } } @@ -268,7 +257,7 @@ export class Monitor implements P2P.IMonitor { * @param {Peer} peer * @return {void} */ - public suspendPeer(ip) { + public suspendPeer(ip): void { const peer = this.peers[ip]; if (peer && !this.guard.isSuspended(peer)) { @@ -280,7 +269,7 @@ export class Monitor implements P2P.IMonitor { * Get a list of all suspended peers. * @return {void} */ - public getSuspendedPeers() { + public getSuspendedPeers(): any { return this.guard.all(); } @@ -288,7 +277,7 @@ export class Monitor implements P2P.IMonitor { * Get all available peers. * @return {Peer[]} */ - public getPeers() { + public getPeers(): Peer[] { return Object.values(this.peers) as Peer[]; } @@ -297,11 +286,11 @@ export class Monitor implements P2P.IMonitor { * @param {String} ip * @return {Peer} */ - public getPeer(ip) { + public getPeer(ip): Peer { return this.peers[ip]; } - public async peerHasCommonBlocks(peer, blockIds) { + public async peerHasCommonBlocks(peer, blockIds): Promise { if (await peer.hasCommonBlocks(blockIds)) { return true; } @@ -316,7 +305,7 @@ export class Monitor implements P2P.IMonitor { /** * Populate list of available peers from random peers. */ - public async discoverPeers() { + public async discoverPeers(): Promise { const queryAtLeastNPeers = 4; let queriedPeers = 0; @@ -326,7 +315,7 @@ export class Monitor implements P2P.IMonitor { try { const hisPeers = await peer.getPeers(); queriedPeers++; - await Promise.all(hisPeers.map(p => this.acceptNewPeer(p, { lessVerbose: true }))); + await Promise.all(hisPeers.map(p => this.validateAndAcceptPeer(p, { lessVerbose: true }))); } catch (error) { // Just try with the next peer from shuffledPeers. } @@ -341,7 +330,7 @@ export class Monitor implements P2P.IMonitor { * Check if we have any peers. * @return {bool} */ - public hasPeers() { + public hasPeers(): boolean { return !!this.getPeers().length; } @@ -349,11 +338,11 @@ export class Monitor implements P2P.IMonitor { * Get the median network height. * @return {Number} */ - public getNetworkHeight() { + public getNetworkHeight(): number { const medians = this.getPeers() .filter(peer => peer.state.height) .map(peer => peer.state.height) - .sort(); + .sort((a, b) => a - b); return medians[Math.floor(medians.length / 2)] || 0; } @@ -362,7 +351,7 @@ export class Monitor implements P2P.IMonitor { * Get the PBFT Forging status. * @return {Number} */ - public getPBFTForgingStatus() { + public getPBFTForgingStatus(): number { const height = this.getNetworkHeight(); const slot = slots.getSlotNumber(); @@ -399,10 +388,11 @@ export class Monitor implements P2P.IMonitor { * suspended. * @return {void} */ - public async refreshPeersAfterFork() { - logger.info(`Refreshing ${this.getPeers().length} peers after fork.`); + public async refreshPeersAfterFork(): Promise { + this.logger.info(`Refreshing ${this.getPeers().length} peers after fork.`); // Reset all peers, except peers banned because of causing a fork. + await this.cleanPeers(false, true); await this.guard.resetSuspendedPeers(); // Ban peer who caused the fork @@ -417,18 +407,18 @@ export class Monitor implements P2P.IMonitor { * @param {Number} fromBlockHeight * @return {Object[]} */ - public async downloadBlocks(fromBlockHeight) { + public async downloadBlocks(fromBlockHeight): Promise { let randomPeer; try { randomPeer = this.getRandomPeerForDownloadingBlocks(); } catch (error) { - logger.error(`Could not download blocks: ${error.message}`); + this.logger.error(`Could not download blocks: ${error.message}`); return []; } try { - logger.info(`Downloading blocks from height ${fromBlockHeight.toLocaleString()} via ${randomPeer.ip}`); + this.logger.info(`Downloading blocks from height ${fromBlockHeight.toLocaleString()} via ${randomPeer.ip}`); const blocks = await randomPeer.downloadBlocks(fromBlockHeight); blocks.forEach(block => { @@ -437,7 +427,7 @@ export class Monitor implements P2P.IMonitor { return blocks; } catch (error) { - logger.error(`Could not download blocks: ${error.message}`); + this.logger.error(`Could not download blocks: ${error.message}`); return this.downloadBlocks(fromBlockHeight); } @@ -448,11 +438,13 @@ export class Monitor implements P2P.IMonitor { * @param {Block} block * @return {Promise} */ - public async broadcastBlock(block) { + public async broadcastBlock(block): Promise { const blockchain = app.resolvePlugin("blockchain"); if (!blockchain) { - logger.info(`Skipping broadcast of block ${block.data.height.toLocaleString()} as blockchain is not ready`); + this.logger.info( + `Skipping broadcast of block ${block.data.height.toLocaleString()} as blockchain is not ready`, + ); return; } @@ -482,7 +474,7 @@ export class Monitor implements P2P.IMonitor { peers = peers.filter(p => Math.random() < proba); } - logger.info( + this.logger.info( `Broadcasting block ${block.data.height.toLocaleString()} to ${pluralize("peer", peers.length, true)}`, ); @@ -493,10 +485,10 @@ export class Monitor implements P2P.IMonitor { * Broadcast transactions to a fixed number of random peers. * @param {Transaction[]} transactions */ - public async broadcastTransactions(transactions) { + public async broadcastTransactions(transactions): Promise { const peers = take(shuffle(this.getPeers()), localConfig.get("maxPeersBroadcast")); - logger.debug( + this.logger.debug( `Broadcasting ${pluralize("transaction", transactions.length, true)} to ${pluralize( "peer", peers.length, @@ -515,7 +507,7 @@ export class Monitor implements P2P.IMonitor { */ public async checkNetworkHealth(): Promise { if (!this.__isColdStartActive()) { - await this.cleanPeers(true, true); + await this.cleanPeers(false, true); await this.guard.resetSuspendedPeers(); } @@ -523,15 +515,20 @@ export class Monitor implements P2P.IMonitor { const peers = this.getPeers(); const suspendedPeers = Object.values(this.getSuspendedPeers()) - .map(suspendedPeer => suspendedPeer.peer) + .map((suspendedPeer: any) => suspendedPeer.peer) .filter(peer => peer.verification !== null); const allPeers = [...peers, ...suspendedPeers]; + if (!allPeers.length) { + this.logger.info("No peers available."); + return { forked: false }; + } + const forkedPeers = allPeers.filter(peer => peer.verification.forked); const majorityOnOurChain = forkedPeers.length / allPeers.length < 0.5; if (majorityOnOurChain) { - logger.info("The majority of peers is not forked. No need to rollback."); + this.logger.info("The majority of peers is not forked. No need to rollback."); return { forked: false }; } @@ -549,7 +546,9 @@ export class Monitor implements P2P.IMonitor { const peersMostCommonHeight = longestGroups[0]; const { highestCommonHeight } = peersMostCommonHeight[0].verification; - logger.info(`Rolling back to most common height ${highestCommonHeight}. Own height: ${lastBlock.data.height}`); + this.logger.info( + `Rolling back to most common height ${highestCommonHeight}. Own height: ${lastBlock.data.height}`, + ); // Now rollback blocks equal to the distance to the most common height. const blocksToRollback = lastBlock.data.height - highestCommonHeight; @@ -560,7 +559,7 @@ export class Monitor implements P2P.IMonitor { * Dump the list of active peers. * @return {void} */ - public dumpPeers() { + public cachePeers(): void { const peers = Object.values(this.peers).map(peer => ({ ip: peer.ip, port: peer.port, @@ -570,7 +569,7 @@ export class Monitor implements P2P.IMonitor { try { fs.writeFileSync(`${process.env.CORE_PATH_CACHE}/peers.json`, JSON.stringify(peers, null, 2)); } catch (err) { - logger.error(`Failed to dump the peer list because of "${err.message}"`); + this.logger.error(`Failed to dump the peer list because of "${err.message}"`); } } @@ -578,7 +577,7 @@ export class Monitor implements P2P.IMonitor { * Get last 10 block IDs from database. * @return {[]String} */ - public async __getRecentBlockIds() { + public async __getRecentBlockIds(): Promise { return app.resolvePlugin("database").getRecentBlockIds(); } @@ -587,21 +586,21 @@ export class Monitor implements P2P.IMonitor { * We need this for the network to start, so we dont forge, while * not all peers are up, or the network is not active */ - public __isColdStartActive() { - return this.coldStartPeriod.isAfter(dayjs()); + public __isColdStartActive(): boolean { + return this.coldStartPeriod.isAfter(dato()); } /** * Check if the node can connect to any DNS host. * @return {void} */ - public async __checkDNSConnectivity(options) { + public async __checkDNSConnectivity(options): Promise { try { const host = await checkDNS(options); - logger.info(`Your network connectivity has been verified by ${host}`); + this.logger.info(`Your network connectivity has been verified by ${host}`); } catch (error) { - logger.error(error.message); + this.logger.error(error.message); } } @@ -609,17 +608,21 @@ export class Monitor implements P2P.IMonitor { * Check if the node can connect to any NTP host. * @return {void} */ - public async __checkNTPConnectivity(options) { + public async __checkNTPConnectivity(options): Promise { try { const { host, time } = await checkNTP(options); - logger.info(`Your NTP connectivity has been verified by ${host}`); + this.logger.info(`Your NTP connectivity has been verified by ${host}`); - logger.info( - `Local clock is off by ${time.t < 0 ? "-" : ""}${prettyMs(Math.abs(time.t))} from NTP :alarm_clock:`, - ); + this.logger.info(`Local clock is off by ${time.t < 0 ? "-" : ""}${prettyMs(Math.abs(time.t))} from NTP`); } catch (error) { - logger.error(error.message); + this.logger.error(error.message); + } + } + + private async validateAndAcceptPeer(peer, options: IAcceptNewPeerOptions = {}): Promise { + if (this.validatePeer(peer, options)) { + await this.acceptNewPeer(peer, options); } } @@ -628,7 +631,7 @@ export class Monitor implements P2P.IMonitor { * @return {Peer} * @throws {Error} if a peer could not be selected */ - private getRandomPeerForDownloadingBlocks() { + private getRandomPeerForDownloadingBlocks(): Peer { const now = new Date().getTime(); const peersAll = this.getPeers(); @@ -649,7 +652,7 @@ export class Monitor implements P2P.IMonitor { * @param {Number} nextUpdateInSeconds * @returns {void} */ - private async scheduleUpdateNetworkStatus(nextUpdateInSeconds) { + private async scheduleUpdateNetworkStatus(nextUpdateInSeconds): Promise { if (this.nextUpdateNetworkStatusScheduled) { return; } @@ -667,9 +670,9 @@ export class Monitor implements P2P.IMonitor { * Returns if the minimum amount of peers are available. * @return {Boolean} */ - private hasMinimumPeers() { + private hasMinimumPeers(): boolean { if (this.config.ignoreMinimumNetworkReach) { - logger.warn("Ignored the minimum network reach because the relay is in seed mode."); + this.logger.warn("Ignored the minimum network reach because the relay is in seed mode."); return true; } @@ -681,26 +684,31 @@ export class Monitor implements P2P.IMonitor { * Populate the initial seed list. * @return {void} */ - private async populateSeedPeers() { - const peerList = config.get("peers.list"); + private async populateSeedPeers(): Promise { + const peerList = this.appConfig.get("peers.list"); if (!peerList) { - app.forceExit("No seed peers defined in peers.json :interrobang:"); + app.forceExit("No seed peers defined in peers.json"); } - let peers = peerList.map(peer => { + const peers = peerList.map(peer => { peer.version = app.getVersion(); return peer; }); - if (localConfig.get("peers")) { - peers = { ...peers, ...localConfig.get("peers") }; + const localConfigPeers = localConfig.get("peers"); + if (localConfigPeers) { + localConfigPeers.forEach(peerA => { + if (!peers.some(peerB => peerA.ip === peerB.ip && peerA.port === peerB.port)) { + peers.push(peerA); + } + }); } return Promise.all( - Object.values(peers).map((peer: any) => { - delete this.guard.suspensions[peer.ip]; - return this.acceptNewPeer(peer, { seed: true, lessVerbose: true }); + peers.map((peer: any) => { + this.guard.delete(peer.ip); + return this.validateAndAcceptPeer(peer, { seed: true, lessVerbose: true }); }), ); } diff --git a/packages/core-p2p/src/network-state.ts b/packages/core-p2p/src/network-state.ts index e024a2b091..f449b2f564 100644 --- a/packages/core-p2p/src/network-state.ts +++ b/packages/core-p2p/src/network-state.ts @@ -33,27 +33,15 @@ class QuorumDetails { */ public peersOverHeightBlockHeaders: { [id: string]: any } = {}; - /** - * Number of peers which are up to N (=3) blocks below `nodeHeight`. - * In other words peers lower than `nodeHeight` - N are not considered for quorum. - */ - public peersBelowHeightElasticity = 0; - - /** - * Number of ignored peers (i.e height far below `nodeHeight`). Ignored peers - * are not used for quorum. - */ - public peersQuorumIgnored = 0; - /** * The following properties are not mutual exclusive for a peer * and imply a peer is on the same `nodeHeight`. */ /** - * Number of peers with a different last block id. + * Number of peers that are on a different chain (forked). */ - public peersDifferentBlockId = 0; + public peersForked = 0; /** * Number of peers with a different slot. @@ -75,8 +63,8 @@ export enum NetworkStateStatus { } export class NetworkState { - private nodeHeight: number; - private lastBlockId: string; + public nodeHeight: number; + public lastBlockId: string; private quorumDetails: QuorumDetails; public constructor(readonly status: NetworkStateStatus, lastBlock?: any) { @@ -157,34 +145,26 @@ export class NetworkState { } private update(peer: Peer, currentSlot: number) { - if (peer.state.height === this.nodeHeight) { - let quorum = true; - if (peer.state.header.id !== this.lastBlockId) { - quorum = false; - this.quorumDetails.peersDifferentBlockId++; - } - - if (peer.state.currentSlot !== currentSlot) { - quorum = false; - this.quorumDetails.peersDifferentSlot++; - } - - if (!peer.state.forgingAllowed) { - this.quorumDetails.peersForgingNotAllowed++; - } - - quorum ? this.quorumDetails.peersQuorum++ : this.quorumDetails.peersNoQuorum++; - } else if (peer.state.height > this.nodeHeight) { + if (peer.state.height > this.nodeHeight) { this.quorumDetails.peersNoQuorum++; this.quorumDetails.peersOverHeight++; this.quorumDetails.peersOverHeightBlockHeaders[peer.state.header.id] = peer.state.header; - } else if (this.nodeHeight - peer.state.height < 3) { - // suppose the max network elasticity accross 3 blocks - this.quorumDetails.peersNoQuorum++; - this.quorumDetails.peersBelowHeightElasticity++; } else { - // Peers far below own height are ignored for quorum - this.quorumDetails.peersQuorumIgnored++; + if (peer.verification.forked) { + this.quorumDetails.peersNoQuorum++; + this.quorumDetails.peersForked++; + } else { + this.quorumDetails.peersQuorum++; + } + } + + // Just statistics in case something goes wrong. + if (peer.state.currentSlot !== currentSlot) { + this.quorumDetails.peersDifferentSlot++; + } + + if (!peer.state.forgingAllowed) { + this.quorumDetails.peersForgingNotAllowed++; } } } diff --git a/packages/core-p2p/src/peer-verifier.ts b/packages/core-p2p/src/peer-verifier.ts index acc0ce845f..7adb47b298 100644 --- a/packages/core-p2p/src/peer-verifier.ts +++ b/packages/core-p2p/src/peer-verifier.ts @@ -1,12 +1,16 @@ // tslint:disable:max-classes-per-file import { app } from "@arkecosystem/core-container"; -import { Database, Logger } from "@arkecosystem/core-interfaces"; +import { Database, Logger, Shared } from "@arkecosystem/core-interfaces"; import { CappedSet, NSect, roundCalculator } from "@arkecosystem/core-utils"; import { models } from "@arkecosystem/crypto"; import assert from "assert"; import { inspect } from "util"; import { Peer } from "./peer"; +interface IDelegateWallets { + [publicKey: string]: Database.IDelegateWallet; +} + enum Severity { /** * Printed at every step of the verification, even if leading to a successful verification. @@ -292,29 +296,30 @@ export class PeerVerifier { * @throws {Error} if the state verification could not complete before the deadline */ private async verifyPeerBlocks(startHeight: number, claimedHeight: number, deadline: number): Promise { - const round = roundCalculator.calculateRound(startHeight); - const lastBlockHeightInRound = round.round * round.maxDelegates; + const roundInfo = roundCalculator.calculateRound(startHeight); + const { maxDelegates, roundHeight } = roundInfo; + const lastBlockHeightInRound = roundHeight + maxDelegates; // Verify a few blocks that are not too far up from the last common block. Within the // same round as the last common block or in the next round if the last common block is // the last block in a round (so that the delegates calculations are still the same for // both chains). - const delegates = await this.getDelegates(round); + const delegates = await this.getDelegatesByRound(roundInfo); const hisBlocksByHeight = {}; const endHeight = Math.min(claimedHeight, lastBlockHeightInRound); - for (let h = startHeight; h <= endHeight; h++) { - if (hisBlocksByHeight[h] === undefined) { - if (!(await this.fetchBlocksFromHeight(h, hisBlocksByHeight, deadline))) { + for (let height = startHeight; height <= endHeight; height++) { + if (hisBlocksByHeight[height] === undefined) { + if (!(await this.fetchBlocksFromHeight(height, hisBlocksByHeight, deadline))) { return false; } } - assert(hisBlocksByHeight[h] !== undefined); + assert(hisBlocksByHeight[height] !== undefined); - if (!(await this.verifyPeerBlock(hisBlocksByHeight[h], h, delegates))) { + if (!(await this.verifyPeerBlock(hisBlocksByHeight[height], height, delegates))) { return false; } } @@ -324,36 +329,30 @@ export class PeerVerifier { /** * Get the delegates for the given round. - * @param {Object} round round to get delegates for - * @return {Object} a map of { publicKey: delegate, ... } of all delegates for the given round */ - private async getDelegates(round: any): Promise { - const numDelegates = round.maxDelegates; - - const heightOfFirstBlockInRound = (round.round - 1) * numDelegates + 1; + private async getDelegatesByRound(roundInfo: Shared.IRoundInfo): Promise { + const { round, maxDelegates } = roundInfo; - let delegates = await this.database.getActiveDelegates(heightOfFirstBlockInRound); + let delegates = await this.database.getActiveDelegates(roundInfo); if (delegates.length === 0) { // This must be the current round, still not saved into the database (it is saved // only after it has completed). So fetch the list of delegates from the wallet // manager. - // loadActiveDelegateList() is upset if we give it any height - it wants the height - // of the first block in the round. - delegates = this.database.walletManager.loadActiveDelegateList(numDelegates, heightOfFirstBlockInRound); + delegates = this.database.walletManager.loadActiveDelegateList(roundInfo); assert.strictEqual( delegates.length, - numDelegates, - `Couldn't derive the list of delegates for round ${round.round}. The database ` + + maxDelegates, + `Couldn't derive the list of delegates for round ${round}. The database ` + `returned empty list and the wallet manager returned ${this.anyToString(delegates)}.`, ); } - const delegatesByPublicKey = {}; + const delegatesByPublicKey = {} as IDelegateWallets; - for (const d of delegates) { - delegatesByPublicKey[d.publicKey] = d; + for (const delegate of delegates) { + delegatesByPublicKey[delegate.publicKey] = delegate; } return delegatesByPublicKey; @@ -384,7 +383,7 @@ export class PeerVerifier { return false; } - if (response.data.blocks.length === 0) { + if (response.body.blocks.length === 0) { this.log( Severity.DEBUG_EXTRA, `failure: could not get blocks starting from height ${height} ` + @@ -393,8 +392,8 @@ export class PeerVerifier { return false; } - for (let i = 0; i < response.data.blocks.length; i++) { - blocksByHeight[height + i] = response.data.blocks[i]; + for (let i = 0; i < response.body.blocks.length; i++) { + blocksByHeight[height + i] = response.body.blocks[i]; } return true; @@ -411,7 +410,7 @@ export class PeerVerifier { private async verifyPeerBlock( blockData: models.IBlockData, expectedHeight: number, - delegatesByPublicKey: any[], + delegatesByPublicKey: IDelegateWallets, ): Promise { if (PeerVerifier.verifiedBlocks.has(blockData.id)) { this.log( diff --git a/packages/core-p2p/src/peer.ts b/packages/core-p2p/src/peer.ts index 1eaf0fce36..511901e8d1 100644 --- a/packages/core-p2p/src/peer.ts +++ b/packages/core-p2p/src/peer.ts @@ -1,7 +1,7 @@ import { app } from "@arkecosystem/core-container"; import { Logger, P2P } from "@arkecosystem/core-interfaces"; -import axios from "axios"; -import dayjs from "dayjs-ext"; +import { httpie } from "@arkecosystem/core-utils"; +import { dato, Dato } from "@faustbrian/dato"; import Joi from "joi"; import util from "util"; import { config as localConfig } from "./config"; @@ -10,13 +10,12 @@ import { PeerVerificationResult, PeerVerifier } from "./peer-verifier"; import { replySchemas } from "./reply-schemas"; export class Peer implements P2P.IPeer { - public downloadSize: any; - public hashid: string; - public nethash: any; - public version: any; - public os: any; - public status: any; - public delay: any; + public downloadSize: number; + public nethash: string; + public version: string; + public os: string; + public status: string | number; + public delay: number; public ban: number; public offences: any[]; @@ -26,13 +25,12 @@ export class Peer implements P2P.IPeer { nethash: number; height: number | null; "Content-Type": "application/json"; - hashid?: string; - status?: any; + status?: string | number; }; public state: any; public url: string; - public lastPinged: dayjs.Dayjs | null; + public lastPinged: Dato | null; public verification: PeerVerificationResult | null; private config: any; @@ -61,10 +59,6 @@ export class Peer implements P2P.IPeer { height: null, "Content-Type": "application/json", }; - - if (this.config.get("network.name") !== "mainnet") { - this.headers.hashid = app.getHashid(); - } } /** @@ -72,7 +66,7 @@ export class Peer implements P2P.IPeer { * @param {Object} headers * @return {void} */ - public setHeaders(headers) { + public setHeaders(headers: Record): void { ["nethash", "os", "version"].forEach(key => { this[key] = headers[key]; }); @@ -83,7 +77,7 @@ export class Peer implements P2P.IPeer { * @param {String} value * @return {void} */ - public setStatus(value) { + public setStatus(value: string | number): void { this.headers.status = value; } @@ -92,7 +86,7 @@ export class Peer implements P2P.IPeer { * @return {Object} */ public toBroadcastInfo() { - const data = { + return { ip: this.ip, port: +this.port, nethash: this.nethash, @@ -102,12 +96,6 @@ export class Peer implements P2P.IPeer { height: this.state.height, delay: this.delay, }; - - if (this.config.get("network.name") !== "mainnet") { - (data as any).hashid = this.hashid || "unknown"; - } - - return data; } /** @@ -115,7 +103,7 @@ export class Peer implements P2P.IPeer { * @param {Block} block * @return {(Object|undefined)} */ - public async postBlock(block) { + public async postBlock(block): Promise { return this.__post( "/peer/blocks", { block }, @@ -131,7 +119,7 @@ export class Peer implements P2P.IPeer { * @param {Transaction[]} transactions * @return {(Object|undefined)} */ - public async postTransactions(transactions) { + public async postTransactions(transactions): Promise { try { const response = await this.__post( "/peer/transactions", @@ -155,13 +143,13 @@ export class Peer implements P2P.IPeer { * @param {Number} fromBlockHeight * @return {(Object[]|undefined)} */ - public async downloadBlocks(fromBlockHeight) { + public async downloadBlocks(fromBlockHeight): Promise { try { const response = await this.getPeerBlocks(fromBlockHeight); this.__parseHeaders(response); - const { blocks } = response.data; + const { blocks } = response.body; const size = blocks.length; if (size === 100 || size === 400) { @@ -186,7 +174,7 @@ export class Peer implements P2P.IPeer { * @return {Object} * @throws {Error} If fail to get peer status. */ - public async ping(delay: number, force = false) { + public async ping(delay: number, force: boolean = false): Promise { const deadline = new Date().getTime() + delay; if (this.recentlyPinged() && !force) { @@ -195,12 +183,8 @@ export class Peer implements P2P.IPeer { const body = await this.__get("/peer/status", delay); - if (!body) { - throw new Error(`Peer ${this.ip}: could not get status response`); - } - - if (!body.success) { - throw new PeerStatusResponseError(JSON.stringify(body)); + if (!body || !body.success) { + throw new PeerStatusResponseError(this.ip); } if (process.env.CORE_SKIP_PEER_STATE_VERIFICATION !== "true") { @@ -216,7 +200,7 @@ export class Peer implements P2P.IPeer { } } - this.lastPinged = dayjs(); + this.lastPinged = dato(); this.state = body; return body; } @@ -225,15 +209,15 @@ export class Peer implements P2P.IPeer { * Returns true if this peer was pinged the past 2 minutes. * @return {Boolean} */ - public recentlyPinged() { - return !!this.lastPinged && dayjs().diff(this.lastPinged, "minute") < 2; + public recentlyPinged(): boolean { + return !!this.lastPinged && dato().diffInMinutes(this.lastPinged) < 2; } /** * Refresh peer list. It removes blacklisted peers from the fetch * @return {Object[]} */ - public async getPeers() { + public async getPeers(): Promise { this.logger.info(`Fetching a fresh peer list from ${this.url}`); const body = await this.__get("/peer/list"); @@ -253,7 +237,7 @@ export class Peer implements P2P.IPeer { * @param {Number} timeoutMsec timeout for the operation, in milliseconds * @return {Boolean} */ - public async hasCommonBlocks(ids, timeoutMsec?: number) { + public async hasCommonBlocks(ids, timeoutMsec?: number): Promise { const errorMessage = `Could not determine common blocks with ${this.ip}`; try { let url = `/peer/blocks/common?ids=${ids.join(",")}`; @@ -292,29 +276,29 @@ export class Peer implements P2P.IPeer { * @param {Number} [timeout=10000] * @return {(Object|undefined)} */ - public async __get(endpoint, timeout?) { + public async __get(endpoint, timeout?): Promise { const temp = new Date().getTime(); try { - const response = await axios.get(`${this.url}${endpoint}`, { + const response = await httpie.get(`${this.url}${endpoint}`, { headers: this.headers, timeout: timeout || this.config.get("peers.globalTimeout"), }); this.__parseHeaders(response); - if (!this.validateReply(response.data, endpoint)) { + if (!this.validateReply(response.body, endpoint)) { return; } this.delay = new Date().getTime() - temp; - if (!response.data) { + if (!response.body) { this.logger.debug(`Request to ${this.url}${endpoint} failed: empty response`); return; } - return response.data; + return response.body; } catch (error) { this.delay = -1; @@ -330,16 +314,16 @@ export class Peer implements P2P.IPeer { * Perform POST request. * @param {String} endpoint * @param {Object} body - * @param {Object} headers + * @param {Object} opts * @return {(Object|undefined)} */ - public async __post(endpoint, body, headers) { + public async __post(endpoint, body, opts): Promise { try { - const response = await axios.post(`${this.url}${endpoint}`, body, headers); + const response = await httpie.post(`${this.url}${endpoint}`, { body, ...opts }); this.__parseHeaders(response); - return response.data; + return response.body; } catch (error) { this.logger.debug(`Request to ${this.url}${endpoint} failed because of "${error.message}"`); @@ -354,8 +338,8 @@ export class Peer implements P2P.IPeer { * @param {Object} response * @return {Object} */ - public __parseHeaders(response) { - ["nethash", "os", "version", "hashid"].forEach(key => { + public __parseHeaders(response): any { + ["nethash", "os", "version"].forEach(key => { this[key] = response.headers[key] || this[key]; }); @@ -377,13 +361,13 @@ export class Peer implements P2P.IPeer { */ public async getPeerBlocks(afterBlockHeight: number): Promise { const endpoint = "/peer/blocks"; - const response = await axios.get(`${this.url}${endpoint}`, { - params: { lastBlockHeight: afterBlockHeight }, + const response = await httpie.get(`${this.url}${endpoint}`, { + query: { lastBlockHeight: afterBlockHeight }, headers: this.headers, timeout: 10000, }); - if (!this.validateReply(response.data, endpoint)) { + if (!this.validateReply(response.body, endpoint)) { throw new Error("Invalid reply to request for blocks"); } diff --git a/packages/core-p2p/src/plugin.ts b/packages/core-p2p/src/plugin.ts index c5adda2c82..5d358c8aa8 100644 --- a/packages/core-p2p/src/plugin.ts +++ b/packages/core-p2p/src/plugin.ts @@ -27,7 +27,7 @@ export const plugin: Container.PluginDescriptor = { container.resolvePlugin("logger").info("Stopping P2P Interface"); const p2p = container.resolvePlugin("p2p"); - p2p.dumpPeers(); + p2p.cachePeers(); return p2p.server.stop(); }, diff --git a/packages/core-p2p/src/server/index.ts b/packages/core-p2p/src/server/index.ts index 432e54b2e5..b6af015d46 100755 --- a/packages/core-p2p/src/server/index.ts +++ b/packages/core-p2p/src/server/index.ts @@ -73,6 +73,10 @@ const startServer = async config => { }, }); + await server.register({ + plugin: plugins.hapiAjv, + }); + // await server.register({ // plugin: require('./plugins/transaction-pool-ready'), // options: { @@ -97,13 +101,6 @@ const startServer = async config => { routes: { prefix: "/internal" }, }); - if (config.remoteInterface) { - await server.register({ - plugin: require("./versions/remote"), - routes: { prefix: "/remote" }, - }); - } - return mountServer("P2P API", server); }; diff --git a/packages/core-p2p/src/server/plugins/accept-request.ts b/packages/core-p2p/src/server/plugins/accept-request.ts index 4b01cee644..6e2ca27ac5 100644 --- a/packages/core-p2p/src/server/plugins/accept-request.ts +++ b/packages/core-p2p/src/server/plugins/accept-request.ts @@ -1,6 +1,6 @@ import Boom from "boom"; import { monitor } from "../../monitor"; -import { isWhitelisted } from "../../utils"; +import { isLocalHost, isWhitelisted } from "../../utils"; /** * The register method used by hapi.js. @@ -41,7 +41,11 @@ const register = async (server, options) => { }); try { - await monitor.acceptNewPeer(peer); + if (monitor.validatePeer(peer)) { + monitor.acceptNewPeer(peer); + } else if (!isLocalHost(peer.ip) && request.method === "post") { + return Boom.forbidden(); + } } catch (error) { return Boom.badImplementation(error.message); } diff --git a/packages/core-p2p/src/server/plugins/set-headers.ts b/packages/core-p2p/src/server/plugins/set-headers.ts index de78f37d47..2ba3e8a6a9 100644 --- a/packages/core-p2p/src/server/plugins/set-headers.ts +++ b/packages/core-p2p/src/server/plugins/set-headers.ts @@ -21,11 +21,6 @@ const register = async (server, options) => { const requiredHeaders = ["nethash", "version", "port", "os", "height"]; - if (config.get("network.name") !== "mainnet") { - (headers as any).hashid = app.getHashid(); - requiredHeaders.push("hashid"); - } - server.ext({ type: "onPreResponse", async method(request, h) { diff --git a/packages/core-p2p/src/server/plugins/transaction-pool-ready.ts b/packages/core-p2p/src/server/plugins/transaction-pool-ready.ts index f74124aa85..d444d95038 100644 --- a/packages/core-p2p/src/server/plugins/transaction-pool-ready.ts +++ b/packages/core-p2p/src/server/plugins/transaction-pool-ready.ts @@ -16,7 +16,7 @@ const register = async (server, options) => { return h.continue; } - if (!app.resolvePlugin("transactionPool")) { + if (!app.resolvePlugin("transaction-pool")) { return Boom.serverUnavailable("Transaction Pool not ready"); } diff --git a/packages/core-p2p/src/server/types.ts b/packages/core-p2p/src/server/types.ts new file mode 100644 index 0000000000..0ebe68d9ca --- /dev/null +++ b/packages/core-p2p/src/server/types.ts @@ -0,0 +1,22 @@ +import { Database } from "@arkecosystem/core-interfaces"; +import { models } from "@arkecosystem/crypto"; + +export interface IResponse { + data: T; +} + +export interface ICurrentRound { + current: number; + reward: string; + timestamp: number; + delegates: Database.IDelegateWallet[]; + currentForger: Database.IDelegateWallet; + nextForger: Database.IDelegateWallet; + lastBlock: models.IBlockData; + canForge: boolean; +} +export interface IForgingTransactions { + transactions: string[]; + poolSize: number; + count: number; +} diff --git a/packages/core-p2p/src/server/versions/1/handlers.ts b/packages/core-p2p/src/server/versions/1/handlers.ts index 62d5158e44..8c318b833b 100644 --- a/packages/core-p2p/src/server/versions/1/handlers.ts +++ b/packages/core-p2p/src/server/versions/1/handlers.ts @@ -1,15 +1,14 @@ import { app } from "@arkecosystem/core-container"; -import { Blockchain, Database, Logger, P2P } from "@arkecosystem/core-interfaces"; -import { TransactionGuard, TransactionPool } from "@arkecosystem/core-transaction-pool"; -import { Joi, models, slots } from "@arkecosystem/crypto"; - +import { Blockchain, Database, Logger, P2P, TransactionPool } from "@arkecosystem/core-interfaces"; +import { TransactionGuard } from "@arkecosystem/core-transaction-pool"; +import { AjvWrapper, models, slots } from "@arkecosystem/crypto"; import pluralize from "pluralize"; import { monitor } from "../../../monitor"; -import { store as schemaBlock } from "../internal/schemas/blocks"; +import { schema } from "./schema"; const { Block } = models; -const transactionPool = app.resolvePlugin("transactionPool"); +const transactionPool = app.resolvePlugin("transaction-pool"); const logger = app.resolvePlugin("logger"); /** @@ -77,20 +76,15 @@ export const getCommonBlocks = { }; } - const blockchain = app.resolvePlugin("blockchain"); - - const ids = request.query.ids - .split(",") - .slice(0, 9) - .filter(id => id.match(/^\d+$/)); + const ids = request.query.ids.split(",").filter(id => AjvWrapper.instance().validate({ blockId: {} }, id)); try { - const commonBlocks = await blockchain.database.getCommonBlocks(ids); + const commonBlocks = await app.resolvePlugin("database").getCommonBlocks(ids); return { success: true, common: commonBlocks.length ? commonBlocks[0] : null, - lastBlockHeight: blockchain.getLastBlock().data.height, + lastBlockHeight: app.resolvePlugin("blockchain").getLastBlock().data.height, }; } catch (error) { return h @@ -146,7 +140,7 @@ export const postBlock = { * @param {Hapi.Toolkit} h * @return {Hapi.Response} */ - async handler(request, h) { + handler: async (request, h) => { const blockchain = app.resolvePlugin("blockchain"); try { @@ -185,7 +179,11 @@ export const postBlock = { } }, options: { - validate: schemaBlock, + plugins: { + "hapi-ajv": { + payloadSchema: schema.postBlock, + }, + }, }, }; @@ -231,12 +229,11 @@ export const postTransactions = { cors: { additionalHeaders: ["nethash", "port", "version"], }, - validate: { - payload: { - transactions: Joi.transactionArray() - .min(1) - .max(app.resolveOptions("transactionPool").maxTransactionsPerRequest) - .options({ stripUnknown: true }), + plugins: { + validate: { + "hapi-ajv": { + payloadSchema: schema.postTransactions, + }, }, }, }, diff --git a/packages/core-p2p/src/server/versions/1/index.ts b/packages/core-p2p/src/server/versions/1/index.ts index 79b1285940..a1d17b497b 100644 --- a/packages/core-p2p/src/server/versions/1/index.ts +++ b/packages/core-p2p/src/server/versions/1/index.ts @@ -24,7 +24,7 @@ const register = async (server, options) => { * @type {Object} */ export const plugin = { - name: "Ark P2P API - v1", + name: "ARK P2P API - v1", version: "0.1.0", register, }; diff --git a/packages/core-p2p/src/server/versions/1/schema.ts b/packages/core-p2p/src/server/versions/1/schema.ts index e269bf94ce..e7ae051073 100644 --- a/packages/core-p2p/src/server/versions/1/schema.ts +++ b/packages/core-p2p/src/server/versions/1/schema.ts @@ -1,7 +1,9 @@ +import { app } from "@arkecosystem/core-container"; + /** * @type {Object} */ -export = { +export const schema = { getStatus: { type: "object", properties: { @@ -43,6 +45,15 @@ export = { }, postTransactions: { type: "object", + required: ["transactions"], + additionalProperties: false, + properties: { + transactions: { + $ref: "transactions", + minItems: 1, + maxItems: app.resolveOptions("transaction-pool").maxTransactionsPerRequest, + }, + }, }, getTransactions: { type: "object", @@ -71,15 +82,11 @@ export = { }, postBlock: { type: "object", + required: ["block"], + additionalProperties: false, properties: { - success: { - type: "boolean", - }, - blockId: { - type: "string", - }, + block: { $ref: "block" }, }, - required: ["success", "blockId"], }, getBlock: { type: "object", diff --git a/packages/core-p2p/src/server/versions/config/index.ts b/packages/core-p2p/src/server/versions/config/index.ts index ab7fc862a5..158c601b09 100644 --- a/packages/core-p2p/src/server/versions/config/index.ts +++ b/packages/core-p2p/src/server/versions/config/index.ts @@ -22,7 +22,7 @@ const register = async (server, options) => { * @type {Object} */ export const plugin = { - name: "Ark P2P - Config API", + name: "ARK P2P - Config API", version: "0.1.0", register, }; diff --git a/packages/core-p2p/src/server/versions/config/transformers/plugins.ts b/packages/core-p2p/src/server/versions/config/transformers/plugins.ts index b5f7bcc1e8..87bd14e921 100644 --- a/packages/core-p2p/src/server/versions/config/transformers/plugins.ts +++ b/packages/core-p2p/src/server/versions/config/transformers/plugins.ts @@ -4,12 +4,7 @@ * @return {Object} */ export function transformPlugins(config) { - const allowed = [ - "@arkecosystem/core-api", - "@arkecosystem/core-graphql", - "@arkecosystem/core-json-rpc", - "@arkecosystem/core-webhooks", - ]; + const allowed = ["@arkecosystem/core-api", "@arkecosystem/core-json-rpc", "@arkecosystem/core-webhooks"]; const result = {}; diff --git a/packages/core-p2p/src/server/versions/internal/handlers/blocks.ts b/packages/core-p2p/src/server/versions/internal/handlers/blocks.ts index 601aa87b26..bdf830c76d 100644 --- a/packages/core-p2p/src/server/versions/internal/handlers/blocks.ts +++ b/packages/core-p2p/src/server/versions/internal/handlers/blocks.ts @@ -1,6 +1,6 @@ import { app } from "@arkecosystem/core-container"; import { Blockchain } from "@arkecosystem/core-interfaces"; -import * as schema from "../schemas/blocks"; +import { schema } from "../../1/schema"; /** * @type {Object} @@ -19,6 +19,10 @@ export const store = { return h.response(null).code(204); }, options: { - validate: schema.store, + plugins: { + "hapi-ajv": { + payloadSchema: schema.postBlock, + }, + }, }, }; diff --git a/packages/core-p2p/src/server/versions/internal/handlers/rounds.ts b/packages/core-p2p/src/server/versions/internal/handlers/rounds.ts index 61ba6a1731..02b3aa3090 100644 --- a/packages/core-p2p/src/server/versions/internal/handlers/rounds.ts +++ b/packages/core-p2p/src/server/versions/internal/handlers/rounds.ts @@ -1,5 +1,6 @@ import { app } from "@arkecosystem/core-container"; import { Blockchain, Database } from "@arkecosystem/core-interfaces"; +import { roundCalculator } from "@arkecosystem/core-utils"; import { slots } from "@arkecosystem/crypto"; const config = app.getConfig(); @@ -7,7 +8,7 @@ const config = app.getConfig(); /** * @type {Object} */ -export const current = { +export const current: object = { /** * @param {Hapi.Request} request * @param {Hapi.Toolkit} h @@ -20,20 +21,22 @@ export const current = { const lastBlock = blockchain.getLastBlock(); const height = lastBlock.data.height + 1; - const maxActive = config.getMilestone(height).activeDelegates; + const roundInfo = roundCalculator.calculateRound(height); + const { maxDelegates } = roundInfo; + const blockTime = config.getMilestone(height).blocktime; const reward = config.getMilestone(height).reward; - const delegates = await databaseService.getActiveDelegates(height); + const delegates = await databaseService.getActiveDelegates(roundInfo); const timestamp = slots.getTime(); - - const currentForger = parseInt((timestamp / blockTime) as any) % maxActive; - const nextForger = (parseInt((timestamp / blockTime) as any) + 1) % maxActive; + const blockTimestamp = slots.getSlotNumber(timestamp) * blockTime; + const currentForger = parseInt((timestamp / blockTime) as any) % maxDelegates; + const nextForger = (parseInt((timestamp / blockTime) as any) + 1) % maxDelegates; return { data: { - current: +(height / maxActive), + current: +(height / maxDelegates), reward, - timestamp, + timestamp: blockTimestamp, delegates, currentForger: delegates[currentForger], nextForger: delegates[nextForger], diff --git a/packages/core-p2p/src/server/versions/internal/handlers/transactions.ts b/packages/core-p2p/src/server/versions/internal/handlers/transactions.ts index 3c38f1ec2b..b81f3231b6 100644 --- a/packages/core-p2p/src/server/versions/internal/handlers/transactions.ts +++ b/packages/core-p2p/src/server/versions/internal/handlers/transactions.ts @@ -1,10 +1,9 @@ import { app } from "@arkecosystem/core-container"; import { Blockchain, Database } from "@arkecosystem/core-interfaces"; -import { models } from "@arkecosystem/crypto"; +import { Transaction } from "@arkecosystem/crypto"; import * as schema from "../schemas/transactions"; const config = app.getConfig(); -const { Transaction } = models; /** * @type {Object} @@ -16,7 +15,7 @@ export const verify: object = { * @return {Hapi.Response} */ async handler(request, h) { - const transaction = new Transaction(Transaction.deserialize(request.payload.transaction)); + const transaction = Transaction.fromHex(request.payload.transaction); return { data: { diff --git a/packages/core-p2p/src/server/versions/internal/handlers/utils.ts b/packages/core-p2p/src/server/versions/internal/handlers/utils.ts index 2e153079bd..6eab6b6a76 100644 --- a/packages/core-p2p/src/server/versions/internal/handlers/utils.ts +++ b/packages/core-p2p/src/server/versions/internal/handlers/utils.ts @@ -1,36 +1,10 @@ import { app } from "@arkecosystem/core-container"; -import { Blockchain, EventEmitter } from "@arkecosystem/core-interfaces"; +import { EventEmitter } from "@arkecosystem/core-interfaces"; const emitter = app.resolvePlugin("event-emitter"); import * as schema from "../schemas/utils"; -/** - * @type {Object} - */ -export const usernames = { - /** - * @param {Hapi.Request} request - * @param {Hapi.Toolkit} h - * @return {Hapi.Response} - */ - async handler(request, h) { - const blockchain = app.resolvePlugin("blockchain"); - const database = blockchain.database; - const walletManager = database.walletManager; - - const lastBlock = blockchain.getLastBlock(); - const delegates = await database.getActiveDelegates(lastBlock ? lastBlock.data.height + 1 : 1); - - const data = {}; - for (const delegate of delegates) { - data[delegate.publicKey] = walletManager.findByPublicKey(delegate.publicKey).username; - } - - return { data }; - }, -}; - /** * Emit the given event and payload to the local host. * @type {Object} diff --git a/packages/core-p2p/src/server/versions/internal/index.ts b/packages/core-p2p/src/server/versions/internal/index.ts index 243215c5a8..4ba97fe56f 100644 --- a/packages/core-p2p/src/server/versions/internal/index.ts +++ b/packages/core-p2p/src/server/versions/internal/index.ts @@ -24,7 +24,6 @@ const register = async (server, options) => { { method: "POST", path: "/transactions/verify", ...transactions.verify }, { method: "GET", path: "/transactions/forging", ...transactions.forging }, - { method: "GET", path: "/utils/usernames", ...utils.usernames }, { method: "POST", path: "/utils/events", ...utils.emitEvent }, ]); }; @@ -34,7 +33,7 @@ const register = async (server, options) => { * @type {Object} */ export const plugin = { - name: "Ark P2P API - Internal", + name: "ARK P2P API - Internal", version: "0.1.0", register, }; diff --git a/packages/core-p2p/src/server/versions/internal/schemas/blocks.ts b/packages/core-p2p/src/server/versions/internal/schemas/blocks.ts deleted file mode 100644 index b9a74bf5d9..0000000000 --- a/packages/core-p2p/src/server/versions/internal/schemas/blocks.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Joi } from "@arkecosystem/crypto"; - -/** - * @type {Object} - */ -export const store = { - payload: { - block: Joi.block().options({ stripUnknown: true }), - }, -}; diff --git a/packages/core-p2p/src/server/versions/remote/handlers/blockchain.ts b/packages/core-p2p/src/server/versions/remote/handlers/blockchain.ts deleted file mode 100644 index 420039e78a..0000000000 --- a/packages/core-p2p/src/server/versions/remote/handlers/blockchain.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { app } from "@arkecosystem/core-container"; -import * as schema from "../schemas/blockchain"; - -/** - * Respond with a blockchain event. - * @type {Object} - */ -export const emitEvent: object = { - /** - * @param {Hapi.Request} request - * @param {Hapi.Toolkit} h - * @return {Hapi.Response} - */ - handler: (request, h) => { - /* TODO: Where is this 'events' property coming from? I don't see it set anywhere in core-blockchain. - Leave untyped until I figure out what the proper implementation should be. - */ - const event = app.resolvePlugin("blockchain").events[request.params.event]; - - request.query.param ? event(request.query.params) : event(); - - return h.response(null).code(204); - }, - options: { - validate: schema.emitEvent, - }, -}; diff --git a/packages/core-p2p/src/server/versions/remote/index.ts b/packages/core-p2p/src/server/versions/remote/index.ts deleted file mode 100644 index 3d2fb75080..0000000000 --- a/packages/core-p2p/src/server/versions/remote/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -import * as blockchain from "./handlers/blockchain"; - -/** - * Register remote routes. - * @param {Hapi.Server} server - * @param {Object} options - * @return {void} - */ -const register = async (server, options) => { - server.route([{ method: "GET", path: "/blockchain/{event}", ...blockchain.emitEvent }]); -}; - -/** - * The struct used by hapi.js. - * @type {Object} - */ -export const plugin = { - name: "Ark P2P - Remote API", - version: "0.1.0", - register, -}; diff --git a/packages/core-p2p/src/server/versions/remote/schemas/blockchain.ts b/packages/core-p2p/src/server/versions/remote/schemas/blockchain.ts deleted file mode 100644 index 358df0f3ef..0000000000 --- a/packages/core-p2p/src/server/versions/remote/schemas/blockchain.ts +++ /dev/null @@ -1,10 +0,0 @@ -import Joi from "joi"; - -/** - * @type {Object} - */ -export const emitEvent = { - params: { - event: Joi.string(), - }, -}; diff --git a/packages/core-p2p/src/utils/check-dns.ts b/packages/core-p2p/src/utils/check-dns.ts index 2c8783a911..df09dcc779 100644 --- a/packages/core-p2p/src/utils/check-dns.ts +++ b/packages/core-p2p/src/utils/check-dns.ts @@ -1,7 +1,7 @@ import { app } from "@arkecosystem/core-container"; import { Logger } from "@arkecosystem/core-interfaces"; import dns from "dns"; -import shuffle from "lodash/shuffle"; +import shuffle from "lodash.shuffle"; import util from "util"; export const checkDNS = async hosts => { diff --git a/packages/core-p2p/src/utils/check-ntp.ts b/packages/core-p2p/src/utils/check-ntp.ts index a44e171405..ec532415fb 100644 --- a/packages/core-p2p/src/utils/check-ntp.ts +++ b/packages/core-p2p/src/utils/check-ntp.ts @@ -1,6 +1,6 @@ import { app } from "@arkecosystem/core-container"; import { Logger } from "@arkecosystem/core-interfaces"; -import shuffle from "lodash/shuffle"; +import shuffle from "lodash.shuffle"; import Sntp from "sntp"; /** diff --git a/packages/core-p2p/src/utils/index.ts b/packages/core-p2p/src/utils/index.ts index 3ae6e63e92..f34226aafd 100644 --- a/packages/core-p2p/src/utils/index.ts +++ b/packages/core-p2p/src/utils/index.ts @@ -2,4 +2,4 @@ export { checkDNS } from "./check-dns"; export { checkNTP } from "./check-ntp"; export { isWhitelisted } from "./is-whitelisted"; export { restorePeers } from "./restore-peers"; -export { isValidPeer } from "./is-valid-peer"; +export { isValidPeer, isLocalHost } from "./is-valid-peer"; diff --git a/packages/core-p2p/src/utils/is-valid-peer.ts b/packages/core-p2p/src/utils/is-valid-peer.ts index 7ce8403196..a4d7adca75 100644 --- a/packages/core-p2p/src/utils/is-valid-peer.ts +++ b/packages/core-p2p/src/utils/is-valid-peer.ts @@ -24,15 +24,7 @@ export const isValidPeer = (peer: { ip: string; status?: string | number }): boo return true; }; -const sanitizeRemoteAddress = (ip: string): string | null => { - try { - return process(ip).toString(); - } catch (error) { - return null; - } -}; - -const isLocalHost = (ip: string): boolean => { +export const isLocalHost = (ip: string): boolean => { try { const parsed = parse(ip); if (parsed.range() === "loopback") { @@ -49,3 +41,11 @@ const isLocalHost = (ip: string): boolean => { return false; } }; + +const sanitizeRemoteAddress = (ip: string): string | null => { + try { + return process(ip).toString(); + } catch (error) { + return null; + } +}; diff --git a/packages/core-p2p/src/utils/is-whitelisted.ts b/packages/core-p2p/src/utils/is-whitelisted.ts index 6e40c9e619..d83fe22758 100644 --- a/packages/core-p2p/src/utils/is-whitelisted.ts +++ b/packages/core-p2p/src/utils/is-whitelisted.ts @@ -1,4 +1,4 @@ -import mm from "micromatch"; +import nm from "nanomatch"; /** * Check if the given IP address is whitelisted. @@ -6,7 +6,7 @@ import mm from "micromatch"; export const isWhitelisted = (whitelist: string[], ip: string): boolean => { if (Array.isArray(whitelist)) { for (const item of whitelist) { - if (mm.isMatch(ip, item)) { + if (nm.isMatch(ip, item)) { return true; } } diff --git a/packages/core-snapshots/README.md b/packages/core-snapshots/README.md index 05607af2cf..d8fecc6529 100644 --- a/packages/core-snapshots/README.md +++ b/packages/core-snapshots/README.md @@ -1,7 +1,7 @@ -# Ark Core - Snapshots +# Persona Core - Snapshots

- +

## Documentation @@ -14,10 +14,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [Joshua Noack](https://github.com/supaiku0) -- [Kristjan Košič](https://github.com/kristjank) -- [All Contributors](../../../../contributors) +This project exists thanks to all the people who [contribute](../../../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-snapshots/__tests__/.gitkeep b/packages/core-snapshots/__tests__/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/core-snapshots/__tests__/transport/codec/lite/lite.test.ts b/packages/core-snapshots/__tests__/transport/codec/lite/lite.test.ts deleted file mode 100644 index 8c598d9b5d..0000000000 --- a/packages/core-snapshots/__tests__/transport/codec/lite/lite.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* tslint:disable:no-console */ - -import msgpack from "msgpack-lite"; -import { LiteCodec } from "../../../../dist/transport/codecs/lite-codec"; -import { blocks } from "../../../fixtures/blocks"; -import { transactions } from "../../../fixtures/transactions"; - -const codec = new LiteCodec(); - -beforeAll(async () => { - transactions.forEach((transaction: any) => { - transaction.serialized = Buffer.from(transaction.serializedHex, "hex"); - }); -}); - -describe("Lite codec testing", () => { - test("Encode/Decode single block", () => { - console.time("singleblock"); - const encoded = msgpack.encode(blocks[1], { codec: codec.blocks }); - const decoded = msgpack.decode(encoded, { codec: codec.blocks }); - - // removing helper property - delete decoded.previous_block_hex; - - expect(decoded).toEqual(blocks[1]); - console.timeEnd("singleblock"); - }); - - test("Encode/Decode blocks", () => { - console.time("blocks"); - for (const [index, block] of blocks.entries()) { - // TODO: skipping genesis for now - wrong id calculation - if (index === 0) { - continue; - } - - const encoded = msgpack.encode(block, { codec: codec.blocks }); - const decoded = msgpack.decode(encoded, { codec: codec.blocks }); - - // removing helper property - delete decoded.previous_block_hex; - - expect(block).toEqual(decoded); - } - console.timeEnd("blocks"); - }); - - test("Encode/Decode transactions - all types", () => { - console.time("transactions"); - for (const transaction of transactions) { - delete transaction.serializedHex; - - const encoded = msgpack.encode(transaction, { codec: codec.transactions }); - const decoded = msgpack.decode(encoded, { codec: codec.transactions }); - - expect(decoded).toEqual(transaction); - } - console.timeEnd("transactions"); - }); - - test("Encode/Decode transfer transactions", () => { - console.time("transactions lite transfer"); - const transferTransactions = transactions.filter(trx => trx.type === 0); - for (let i = 0; i < 100; i++) { - for (const transaction of transferTransactions) { - const encoded = msgpack.encode(transaction, { - codec: codec.transactions, - }); - const decoded = msgpack.decode(encoded, { codec: codec.transactions }); - - expect(decoded).toEqual(transaction); - } - } - console.timeEnd("transactions lite transfer"); - }); -}); diff --git a/packages/core-snapshots/package.json b/packages/core-snapshots/package.json index 803795a7c9..8d6bc48e7c 100644 --- a/packages/core-snapshots/package.json +++ b/packages/core-snapshots/package.json @@ -1,7 +1,7 @@ { "name": "@arkecosystem/core-snapshots", "description": "Provides live local streamed snapshots functionality for ARK Core", - "version": "2.2.1", + "version": "2.3.15", "contributors": [ "Kristjan Košič " ], @@ -12,47 +12,34 @@ "dist" ], "scripts": { - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", "prepublishOnly": "yarn build", - "pretest": "yarn lint && yarn build", "compile": "../../node_modules/typescript/bin/tsc", "build": "yarn clean && yarn copy && yarn compile", "build:watch": "yarn clean && yarn copy && yarn compile -w", "clean": "del dist", - "copy": "cd src/ && cpy './**/*.sql' --parents ../dist/ && cd ../", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "test": "cross-env CORE_ENV=test jest --runInBand --forceExit", - "test:coverage": "cross-env CORE_ENV=test jest --coverage --runInBand --forceExit", - "test:debug": "cross-env CORE_ENV=test node --inspect-brk ../../node_modules/.bin/jest --runInBand --watch", - "test:watch": "cross-env CORE_ENV=test jest --runInBand --watch", - "test:watch:all": "cross-env CORE_ENV=test jest --runInBand --watchAll", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" + "copy": "cd src/ && cpy './**/*.sql' --parents ../dist/ && cd ../" }, "dependencies": { - "@arkecosystem/core-container": "^2.2.1", - "@arkecosystem/core-database-postgres": "^2.2.1", - "@arkecosystem/core-interfaces": "^2.2.1", - "@arkecosystem/crypto": "^2.2.1", + "@arkecosystem/core-container": "^2.3.15", + "@arkecosystem/core-database-postgres": "^2.3.15", + "@arkecosystem/core-interfaces": "^2.3.15", + "@arkecosystem/core-utils": "^2.3.15", + "@arkecosystem/crypto": "^2.3.15", "JSONStream": "^1.3.5", - "bluebird": "^3.5.3", "cpy-cli": "^2.0.0", "create-hash": "^1.2.0", "fs-extra": "^7.0.1", "lodash.pick": "^4.4.0", "msgpack-lite": "^0.1.26", - "pg-promise": "^8.5.5", + "pg-promise": "^8.6.3", "pg-query-stream": "^2.0.0", "pluralize": "^7.0.0", "xcase": "^2.0.1" }, "devDependencies": { - "@types/bluebird": "^3.5.25", "@types/create-hash": "^1.2.0", "@types/fs-extra": "^5.0.5", - "@types/lodash.pick": "^4.4.4", + "@types/lodash.pick": "^4.4.6", "@types/msgpack-lite": "^0.1.6", "@types/pg-query-stream": "^1.0.2", "@types/pluralize": "^0.0.29" @@ -62,8 +49,5 @@ }, "engines": { "node": ">=10.x" - }, - "jest": { - "preset": "../../jest-preset.json" } } diff --git a/packages/core-snapshots/src/db/index.ts b/packages/core-snapshots/src/db/index.ts index 029cf0223a..2be13b3ccb 100644 --- a/packages/core-snapshots/src/db/index.ts +++ b/packages/core-snapshots/src/db/index.ts @@ -1,47 +1,30 @@ import { app } from "@arkecosystem/core-container"; -import { migrations, plugin, PostgresConnection } from "@arkecosystem/core-database-postgres"; -import { Logger } from "@arkecosystem/core-interfaces"; -import promise from "bluebird"; +import { PostgresConnection } from "@arkecosystem/core-database-postgres"; +import { Logger, Shared } from "@arkecosystem/core-interfaces"; import { queries } from "./queries"; import { rawQuery } from "./utils"; -import { columns } from "./utils/column-set"; const logger = app.resolvePlugin("logger"); -class Database { +export class Database { public db: any; public pgp: any; - public isSharedConnection: boolean; public blocksColumnSet: any; public transactionsColumnSet: any; - public async make(connection : PostgresConnection) { - if (connection) { - this.db = connection.db; - this.pgp = (connection as any).pgp; - this.__createColumnSets(); - this.isSharedConnection = true; - logger.info("Snapshots: reusing core-database-postgres connection from running core"); - return this; - } + public async make(connection: PostgresConnection) { + this.db = connection.db; + this.pgp = (connection as any).pgp; + this.createColumnSets(); - try { - const pgp = require("pg-promise")({ promiseLib: promise }); - this.pgp = pgp; - - const options: any = plugin.defaults.connection; - options.idleTimeoutMillis = 100; - - this.db = pgp(options); - this.__createColumnSets(); - await this.__runMigrations(); - logger.info("Snapshots: Database connected"); - this.isSharedConnection = false; - return this; - } catch (error) { - app.forceExit("Error while connecting to postgres", error); - return null; + return this; + } + + public close() { + if (!app.has("blockchain")) { + this.db.$pool.end(); + this.pgp.end(); } } @@ -53,38 +36,32 @@ class Database { return this.db.oneOrNone(queries.blocks.findByHeight, { height }); } - public async truncateChain() { - const tables = ["wallets", "rounds", "transactions", "blocks"]; - logger.info("Truncating tables: wallets, rounds, transactions, blocks"); + public async truncate() { try { - for (const table of tables) { + logger.info("Truncating tables: rounds, transactions, blocks"); + + for (const table of ["rounds", "transactions", "blocks"]) { await this.db.none(queries.truncate(table)); } - - return this.getLastBlock(); } catch (error) { - app.forceExit("Truncate chain error", error); + app.forceExit(error.message); } } - public async rollbackChain(height) { - const config = app.getConfig(); - const maxDelegates = config.getMilestone(height).activeDelegates; - const currentRound = Math.floor(height / maxDelegates); - const lastBlockHeight = currentRound * maxDelegates; - const lastRemainingBlock = await this.getBlockByHeight(lastBlockHeight); + public async rollbackChain(roundInfo: Shared.IRoundInfo) { + const { round, roundHeight } = roundInfo; + const lastRemainingBlock = await this.getBlockByHeight(roundHeight); try { if (lastRemainingBlock) { await Promise.all([ - this.db.none(queries.truncate("wallets")), this.db.none(queries.transactions.deleteFromTimestamp, { timestamp: lastRemainingBlock.timestamp, }), this.db.none(queries.blocks.deleteFromHeight, { height: lastRemainingBlock.height, }), - this.db.none(queries.rounds.deleteFromRound, { round: currentRound }), + this.db.none(queries.rounds.deleteFromRound, { round }), ]); } } catch (error) { @@ -103,6 +80,7 @@ class Database { "Wrong input height parameters for building export queries. Blocks at height not found in db.", ); } + return { blocks: rawQuery(this.pgp, queries.blocks.heightRange, { start: startBlock.height, @@ -132,25 +110,46 @@ class Database { } } - public close() { - if (!this.isSharedConnection) { - logger.debug("Closing snapshots-cli database connection"); - this.db.$pool.end(); - this.pgp.end(); - } - } - - public __createColumnSets() { - this.blocksColumnSet = new this.pgp.helpers.ColumnSet(columns.blocks, { - table: "blocks", - }); - this.transactionsColumnSet = new this.pgp.helpers.ColumnSet(columns.transactions, { table: "transactions" }); - } - - public async __runMigrations() { - for (const migration of migrations) { - await this.db.none(migration); - } + private createColumnSets() { + this.blocksColumnSet = new this.pgp.helpers.ColumnSet( + [ + "id", + "version", + "timestamp", + "previous_block", + "height", + "number_of_transactions", + "total_amount", + "total_fee", + "reward", + "payload_length", + "payload_hash", + "generator_public_key", + "block_signature", + ], + { + table: "blocks", + }, + ); + + this.transactionsColumnSet = new this.pgp.helpers.ColumnSet( + [ + "id", + "version", + "block_id", + "sequence", + "timestamp", + "sender_public_key", + "recipient_id", + "type", + "vendor_field_hex", + "amount", + "fee", + "serialized", + "asset", + ], + { table: "transactions" }, + ); } } diff --git a/packages/core-snapshots/src/db/queries/transactions/timestamp-range.sql b/packages/core-snapshots/src/db/queries/transactions/timestamp-range.sql index 49552472ce..fe74813547 100644 --- a/packages/core-snapshots/src/db/queries/transactions/timestamp-range.sql +++ b/packages/core-snapshots/src/db/queries/transactions/timestamp-range.sql @@ -10,7 +10,8 @@ SELECT vendor_field_hex, amount, fee, - serialized + serialized, + asset FROM transactions WHERE diff --git a/packages/core-snapshots/src/db/utils/column-set.ts b/packages/core-snapshots/src/db/utils/column-set.ts deleted file mode 100644 index cf26b38590..0000000000 --- a/packages/core-snapshots/src/db/utils/column-set.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* Column sets. - * If you modify the order you must adapt the sql files export orders also - */ -export const columns = { - blocks: [ - "id", - "version", - "timestamp", - "previous_block", - "height", - "number_of_transactions", - "total_amount", - "total_fee", - "reward", - "payload_length", - "payload_hash", - "generator_public_key", - "block_signature", - ], - transactions: [ - "id", - "version", - "block_id", - "sequence", - "timestamp", - "sender_public_key", - "recipient_id", - "type", - "vendor_field_hex", - "amount", - "fee", - "serialized", - ], -}; diff --git a/packages/core-snapshots/src/defaults.ts b/packages/core-snapshots/src/defaults.ts index a7d3c52f51..97f77d846e 100644 --- a/packages/core-snapshots/src/defaults.ts +++ b/packages/core-snapshots/src/defaults.ts @@ -1,4 +1,3 @@ export const defaults = { - codec: "lite", chunkSize: 50000, }; diff --git a/packages/core-snapshots/src/manager.ts b/packages/core-snapshots/src/manager.ts index 0e872d2305..bdaac23394 100644 --- a/packages/core-snapshots/src/manager.ts +++ b/packages/core-snapshots/src/manager.ts @@ -3,16 +3,18 @@ import { app } from "@arkecosystem/core-container"; import { PostgresConnection } from "@arkecosystem/core-database-postgres"; import { Logger } from "@arkecosystem/core-interfaces"; -import pick from "lodash/pick"; +import { roundCalculator } from "@arkecosystem/core-utils"; + +import pick from "lodash.pick"; const logger = app.resolvePlugin("logger"); -import { database } from "./db"; +import { database, Database } from "./db"; import * as utils from "./utils"; import { backupTransactionsToJSON, exportTable, importTable, verifyTable } from "./transport"; export class SnapshotManager { - public database: any; + public database: Database; constructor(readonly options) {} public async make(connection: PostgresConnection) { @@ -21,8 +23,8 @@ export class SnapshotManager { return this; } - public async exportData(options) { - const params = await this.__init(options, true); + public async dump(options) { + const params = await this.init(options, true); if (params.skipExportWhenNoChange) { logger.info(`Skipping export of snapshot, because ${params.meta.folder} is already up to date.`); @@ -33,30 +35,32 @@ export class SnapshotManager { blocks: await exportTable("blocks", params), transactions: await exportTable("transactions", params), folder: params.meta.folder, - codec: options.codec, skipCompression: params.meta.skipCompression, }; this.database.close(); + utils.writeMetaFile(metaInfo); } - public async importData(options) { - const params = await this.__init(options); + public async import(options) { + const params = await this.init(options); if (params.truncate) { - params.lastBlock = await this.database.truncateChain(); + params.lastBlock = await this.database.truncate(); } await importTable("blocks", params); await importTable("transactions", params); const lastBlock = await this.database.getLastBlock(); + logger.info( `Import from folder ${ params.meta.folder - } completed. Last block in database: ${lastBlock.height.toLocaleString()} :+1:`, + } completed. Last block in database: ${lastBlock.height.toLocaleString()}`, ); + if (!params.skipRestartRound) { const newLastBlock = await this.database.rollbackChain(lastBlock.height); logger.info( @@ -67,61 +71,61 @@ export class SnapshotManager { this.database.close(); } - public async verifyData(options) { - const params = await this.__init(options); + public async verify(options) { + const params = await this.init(options); await Promise.all([verifyTable("blocks", params), verifyTable("transactions", params)]); } - public async truncateChain() { - await this.database.truncateChain(); + public async truncate() { + await this.database.truncate(); this.database.close(); } - public async rollbackChain(height) { - const lastBlock = await this.database.getLastBlock(); - const config = app.getConfig(); - const maxDelegates = config.getMilestone(lastBlock.height).activeDelegates; + public async rollbackByHeight(height: number) { + if (!height || height <= 0) { + app.forceExit(`Rollback height ${height.toLocaleString()} is invalid.`); + } + + const currentHeight = (await this.database.getLastBlock()).height; + const roundInfo = roundCalculator.calculateRound(height); + const { round } = roundInfo; - const rollBackHeight = height === -1 ? lastBlock.height : height; - if (rollBackHeight >= lastBlock.height || rollBackHeight < 1) { + if (height >= currentHeight) { app.forceExit( - `Specified rollback block height: ${rollBackHeight.toLocaleString()} is not valid. Current database height: ${lastBlock.height.toLocaleString()}. Exiting.`, + `Rollback height ${height.toLocaleString()} is greater than the current height ${currentHeight.toLocaleString()}.`, ); } - if (height) { - const rollBackBlock = await this.database.getBlockByHeight(rollBackHeight); - const qTransactionBackup = await this.database.getTransactionsBackupQuery(rollBackBlock.timestamp); - await backupTransactionsToJSON( - `rollbackTransactionBackup.${+height + 1}.${lastBlock.height}.json`, - qTransactionBackup, - this.database, - ); - } + const rollbackBlock = await this.database.getBlockByHeight(height); + const queryTransactionBackup = await this.database.getTransactionsBackupQuery(rollbackBlock.timestamp); - const newLastBlock = await this.database.rollbackChain(rollBackHeight); + await backupTransactionsToJSON( + `rollbackTransactionBackup.${+height + 1}.${currentHeight}.json`, + queryTransactionBackup, + this.database, + ); + + const newLastBlock = await this.database.rollbackChain(roundInfo); logger.info( - `Rolling back chain to last finished round ${( - newLastBlock.height / maxDelegates - ).toLocaleString()} with last block height ${newLastBlock.height.toLocaleString()}`, + `Rolling back chain to last finished round ${round.toLocaleString()} with last block height ${newLastBlock.height.toLocaleString()}`, ); this.database.close(); } - /** - * Inits the process and creates json with needed paramaters for functions - * @param {JSONObject} from commander or util function {blocks, codec, truncate, signatureVerify, skipRestartRound, start, end} - * @return {JSONObject} with merged parameters, adding {lastBlock, database, meta {startHeight, endHeight, folder}, queries {blocks, transactions}} - */ - public async __init(options, exportAction = false) { + public async rollbackByNumber(amount: number) { + const { height } = await this.database.getLastBlock(); + + return this.rollbackByHeight(height - amount); + } + + private async init(options, exportAction: boolean = false) { const params: any = pick(options, [ "truncate", - "signatureVerify", "blocks", - "codec", + "verifySignatures", "skipRestartRound", "start", "end", @@ -130,13 +134,13 @@ export class SnapshotManager { const lastBlock = await this.database.getLastBlock(); params.lastBlock = lastBlock; - params.codec = params.codec || this.options.codec; params.chunkSize = this.options.chunkSize || 50000; if (exportAction) { if (!lastBlock) { app.forceExit("Database is empty. Export not possible."); } + params.meta = utils.setSnapshotInfo(params, lastBlock); params.queries = await this.database.getExportQueries(params.meta.startHeight, params.meta.endHeight); @@ -145,21 +149,23 @@ export class SnapshotManager { params.skipExportWhenNoChange = true; return params; } + const sourceSnapshotParams = utils.readMetaJSON(params.blocks); params.meta.skipCompression = sourceSnapshotParams.skipCompression; params.meta.startHeight = sourceSnapshotParams.blocks.startHeight; - utils.copySnapshot(options.blocks, params.meta.folder, params.codec); + utils.copySnapshot(options.blocks, params.meta.folder); } } else { params.meta = utils.getSnapshotInfo(options.blocks); } + if (options.trace) { - // tslint:disable-next-line:no-console - console.info(params.meta); - // tslint:disable-next-line:no-console - console.info(params.queries); + logger.info(params.meta); + logger.info(params.queries); } + params.database = this.database; + return params; } } diff --git a/packages/core-snapshots/src/plugin.ts b/packages/core-snapshots/src/plugin.ts index 4ab3891387..0f84c1f098 100644 --- a/packages/core-snapshots/src/plugin.ts +++ b/packages/core-snapshots/src/plugin.ts @@ -15,10 +15,6 @@ export const plugin: Container.PluginDescriptor = { const manager = new SnapshotManager(options); const databaseService = container.resolvePlugin("database"); - if(!!databaseService) { - const connection = databaseService.connection as any; - return await manager.make(connection as PostgresConnection); - } - return await manager.make(null); + return manager.make(databaseService.connection as PostgresConnection); }, }; diff --git a/packages/core-snapshots/src/transport/codec.ts b/packages/core-snapshots/src/transport/codec.ts new file mode 100644 index 0000000000..e587e61a10 --- /dev/null +++ b/packages/core-snapshots/src/transport/codec.ts @@ -0,0 +1,65 @@ +import { Bignum, models, Transaction } from "@arkecosystem/crypto"; +import { createCodec, decode, encode } from "msgpack-lite"; +import { camelizeKeys, decamelizeKeys } from "xcase"; + +function encodeBlock(block) { + return models.Block.serialize(camelizeKeys(block), true); +} + +function decodeBlock(buffer) { + const block = models.Block.deserialize(buffer.toString("hex"), true); + block.totalAmount = (block.totalAmount as Bignum).toFixed(); + block.totalFee = (block.totalFee as Bignum).toFixed(); + block.reward = (block.reward as Bignum).toFixed(); + + return decamelizeKeys(block); +} + +function encodeTransaction(transaction) { + transaction.blockId = transaction.block_id || transaction.blockId; + + return encode([ + transaction.id, + transaction.blockId, + transaction.sequence, + transaction.timestamp, + transaction.serialized, + ]); +} + +function decodeTransaction(buffer) { + const [id, blockId, sequence, timestamp, serialized] = decode(buffer); + + const transaction: any = Transaction.fromBytesUnsafe(serialized, id).data; + transaction.block_id = blockId; + transaction.sequence = sequence; + transaction.timestamp = timestamp; + transaction.amount = transaction.amount.toFixed(); + transaction.fee = transaction.fee.toFixed(); + transaction.vendorFieldHex = transaction.vendorFieldHex ? transaction.vendorFieldHex : null; + transaction.recipientId = transaction.recipientId ? transaction.recipientId : null; + transaction.asset = transaction.asset ? transaction.asset : null; + transaction.serialized = serialized; + + const decamelized = decamelizeKeys(transaction); + decamelized.serialized = serialized; // FIXME: decamelizeKeys mutilates Buffers + return decamelized; +} + +export class Codec { + static get blocks() { + const codec = createCodec(); + codec.addExtPacker(0x3f, Object, encodeBlock); + codec.addExtUnpacker(0x3f, decodeBlock); + + return codec; + } + + static get transactions() { + const codec = createCodec(); + codec.addExtPacker(0x4f, Object, encodeTransaction); + codec.addExtUnpacker(0x4f, decodeTransaction); + + return codec; + } +} diff --git a/packages/core-snapshots/src/transport/codecs/core-codec.ts b/packages/core-snapshots/src/transport/codecs/core-codec.ts deleted file mode 100644 index 777b390eac..0000000000 --- a/packages/core-snapshots/src/transport/codecs/core-codec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import msgpack from "msgpack-lite"; -import * as coreEncoders from "./core"; - -export class CoreCodec { - get blocks() { - const codec: any = msgpack.createCodec(); - codec.addExtPacker(0x3f, Object, coreEncoders.blockEncode); - codec.addExtUnpacker(0x3f, coreEncoders.blockDecode); - - return codec; - } - - get transactions() { - const codec: any = msgpack.createCodec(); - codec.addExtPacker(0x4f, Object, coreEncoders.transactionEncode); - codec.addExtUnpacker(0x4f, coreEncoders.transactionDecode); - - return codec; - } -} diff --git a/packages/core-snapshots/src/transport/codecs/core/index.ts b/packages/core-snapshots/src/transport/codecs/core/index.ts deleted file mode 100644 index 4b3d6e82c8..0000000000 --- a/packages/core-snapshots/src/transport/codecs/core/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Bignum, models } from "@arkecosystem/crypto"; -import msgpack from "msgpack-lite"; -import { camelizeKeys, decamelizeKeys } from "xcase"; -const { Block, Transaction } = models; - -export const blockEncode = blockRecord => { - const data = camelizeKeys(blockRecord); - return Block.serialize(data, true); -}; - -export const blockDecode = bufferData => { - const blockData = Block.deserialize(bufferData.toString("hex"), true); - blockData.id = Block.getIdFromSerialized(bufferData); - - blockData.totalAmount = (blockData.totalAmount as Bignum).toFixed(); - blockData.totalFee = (blockData.totalFee as Bignum).toFixed(); - blockData.reward = (blockData.reward as Bignum).toFixed(); - - return decamelizeKeys(blockData); -}; - -export const transactionEncode = transaction => - msgpack.encode([transaction.id, transaction.block_id, transaction.sequence, transaction.serialized]); - -export const transactionDecode = bufferData => { - const [id, blockId, sequence, serialized] = msgpack.decode(bufferData); - let transaction: any = {}; - transaction = Transaction.deserialize(serialized.toString("hex")); - - transaction.id = id; - transaction.block_id = blockId; - transaction.sequence = sequence; - transaction.amount = transaction.amount.toFixed(); - transaction.fee = transaction.fee.toFixed(); - transaction.vendorFieldHex = transaction.vendorFieldHex ? transaction.vendorFieldHex : null; - transaction.recipientId = transaction.recipientId ? transaction.recipientId : null; - transaction = decamelizeKeys(transaction); - - transaction.serialized = serialized; - return transaction; -}; diff --git a/packages/core-snapshots/src/transport/codecs/index.ts b/packages/core-snapshots/src/transport/codecs/index.ts deleted file mode 100644 index a7dcf56f5d..0000000000 --- a/packages/core-snapshots/src/transport/codecs/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { CoreCodec } from "./core-codec"; -import { LiteCodec } from "./lite-codec"; - -export function getCodec(codec) { - switch (codec) { - case "core": - return new CoreCodec(); - case "lite": - return new LiteCodec(); - case "msgpack": - return null; - default: - return new LiteCodec(); - } -} diff --git a/packages/core-snapshots/src/transport/codecs/lite-codec.ts b/packages/core-snapshots/src/transport/codecs/lite-codec.ts deleted file mode 100644 index 7d370f217e..0000000000 --- a/packages/core-snapshots/src/transport/codecs/lite-codec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import msgpack from "msgpack-lite"; -import * as liteEncoder from "./lite"; - -export class LiteCodec { - get blocks() { - const codec = msgpack.createCodec(); - codec.addExtPacker(0x3f, Object, liteEncoder.blockEncode); - codec.addExtUnpacker(0x3f, liteEncoder.blockDecode); - - return codec; - } - - get transactions() { - const codec = msgpack.createCodec(); - codec.addExtPacker(0x4f, Object, liteEncoder.transactionEncode); - codec.addExtUnpacker(0x4f, liteEncoder.transactionDecode); - - return codec; - } -} diff --git a/packages/core-snapshots/src/transport/codecs/lite/index.ts b/packages/core-snapshots/src/transport/codecs/lite/index.ts deleted file mode 100644 index 63739fa578..0000000000 --- a/packages/core-snapshots/src/transport/codecs/lite/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -import msgpack from "msgpack-lite"; -import { columns } from "../../../db/utils/column-set"; - -export const blockEncode = block => { - const values = Object.values(block); - return msgpack.encode(values); -}; - -export const blockDecode = bufferData => { - const values = msgpack.decode(bufferData); - const block = {}; - columns.blocks.forEach((column, i) => { - block[column] = values[i]; - }); - return block; -}; - -export const transactionEncode = transactionRecord => { - const values = Object.values(transactionRecord); - return msgpack.encode(values); -}; - -export const transactionDecode = bufferData => { - const values = msgpack.decode(bufferData); - const transaction = {}; - columns.transactions.forEach((column, i) => { - transaction[column] = values[i]; - }); - return transaction; -}; diff --git a/packages/core-snapshots/src/transport/index.ts b/packages/core-snapshots/src/transport/index.ts index 8dca3e4d96..2d4d55886d 100644 --- a/packages/core-snapshots/src/transport/index.ts +++ b/packages/core-snapshots/src/transport/index.ts @@ -9,26 +9,25 @@ import { app } from "@arkecosystem/core-container"; import { EventEmitter, Logger } from "@arkecosystem/core-interfaces"; import * as utils from "../utils"; -import { getCodec } from "./codecs"; +import { Codec } from "./codec"; import { canImportRecord, verifyData } from "./verification"; const logger = app.resolvePlugin("logger"); const emitter = app.resolvePlugin("event-emitter"); export const exportTable = async (table, options) => { - const snapFileName = utils.getPath(table, options.meta.folder, options.codec); - const codec = getCodec(options.codec); + const snapFileName = utils.getFilePath(table, options.meta.folder); const gzip = zlib.createGzip(); await fs.ensureFile(snapFileName); logger.info( - `Starting to export table ${table} to folder ${options.meta.folder}, codec: ${ - options.codec + `Starting to export table ${table} to folder ${ + options.meta.folder }, append:${!!options.blocks}, skipCompression: ${options.meta.skipCompression}`, ); try { const snapshotWriteStream = fs.createWriteStream(snapFileName, options.blocks ? { flags: "a" } : {}); - const encodeStream = msgpack.createEncodeStream(codec ? { codec: codec[table] } : {}); + const encodeStream = msgpack.createEncodeStream({ codec: Codec[table] }); const qs = new QueryStream(options.queries[table]); const data = await options.database.db.stream(qs, s => { @@ -57,14 +56,11 @@ export const exportTable = async (table, options) => { }; export const importTable = async (table, options) => { - const sourceFile = utils.getPath(table, options.meta.folder, options.codec); - const codec = getCodec(options.codec); + const sourceFile = utils.getFilePath(table, options.meta.folder); const gunzip = zlib.createGunzip(); - const decodeStream = msgpack.createDecodeStream(codec ? { codec: codec[table] } : {}); + const decodeStream = msgpack.createDecodeStream({ codec: Codec[table] }); logger.info( - `Starting to import table ${table} from ${sourceFile}, codec: ${options.codec}, skipCompression: ${ - options.meta.skipCompression - }`, + `Starting to import table ${table} from ${sourceFile}, skipCompression: ${options.meta.skipCompression}`, ); const readStream = options.meta.skipCompression @@ -90,9 +86,11 @@ export const importTable = async (table, options) => { // @ts-ignore for await (const record of readStream) { counter++; - if (!verifyData(table, record, prevData, options.signatureVerification)) { + + if (!verifyData(table, record, prevData, options.verifySignatures)) { app.forceExit(`Error verifying data. Payload ${JSON.stringify(record, null, 2)}`); } + if (canImportRecord(table, record, options.lastBlock)) { values.push(record); } @@ -106,14 +104,14 @@ export const importTable = async (table, options) => { if (values.length > 0) { await saveData(values); } + emitter.emit("complete"); }; export const verifyTable = async (table, options) => { - const sourceFile = utils.getPath(table, options.meta.folder, options.codec); - const codec = getCodec(options.codec); + const sourceFile = utils.getFilePath(table, options.meta.folder); const gunzip = zlib.createGunzip(); - const decodeStream = msgpack.createDecodeStream(codec ? { codec: codec[table] } : {}); + const decodeStream = msgpack.createDecodeStream({ codec: Codec[table] }); const readStream = options.meta.skipCompression ? fs.createReadStream(sourceFile).pipe(decodeStream) : fs @@ -125,14 +123,14 @@ export const verifyTable = async (table, options) => { let prevData = null; decodeStream.on("data", data => { - if (!verifyData(table, data, prevData, options.signatureVerification)) { + if (!verifyData(table, data, prevData, options.verifySignatures)) { app.forceExit(`Error verifying data. Payload ${JSON.stringify(data, null, 2)}`); } prevData = data; }); readStream.on("finish", () => { - logger.info(`Snapshot file ${sourceFile} succesfully verified :+1:`); + logger.info(`Snapshot file ${sourceFile} succesfully verified`); }); }; diff --git a/packages/core-snapshots/src/transport/verification.ts b/packages/core-snapshots/src/transport/verification.ts index d68e75c561..9d164f252f 100644 --- a/packages/core-snapshots/src/transport/verification.ts +++ b/packages/core-snapshots/src/transport/verification.ts @@ -1,42 +1,29 @@ import { app } from "@arkecosystem/core-container"; import { Logger } from "@arkecosystem/core-interfaces"; -import { crypto, models } from "@arkecosystem/crypto"; -import createHash from "create-hash"; +import { configManager, crypto, HashAlgorithms, models, Transaction } from "@arkecosystem/crypto"; import { camelizeKeys } from "xcase"; -const { Block, Transaction } = models; +const { Block } = models; const logger = app.resolvePlugin("logger"); -export const verifyData = (context, data, prevData, signatureVerification) => { - const verifyTransaction = () => { - if (!signatureVerification) { - return true; - } - - const transaction = new Transaction(Buffer.from(data.serialized).toString("hex")); - return transaction.verified; - }; +export const verifyData = (context, data, prevData, verifySignatures) => { + if (context === "blocks") { + const isBlockChained = () => { + if (!prevData) { + return true; + } + // genesis payload different as block.serialize stores + // block.previous_block with 00000 instead of null + // it fails on height 2 - chain check + // TODO: check to improve ser/deser for genesis + const genesisBlock = configManager.get("genesisBlock"); + if (data.height === 2 && data.previous_block === genesisBlock.id) { + return true; + } - const isBlockChained = () => { - if (!prevData) { - return true; - } - // genesis payload different as block.serialize stores - // block.previous_block with 00000 instead of null - // it fails on height 2 - chain check - // hardcoding for now - // TODO: check to improve ser/deser for genesis, add mainnet - if ( - data.height === 2 && - data.previous_block === "13114381566690093367" && - prevData.id === "12760288562212273414" - ) { - return true; - } - return data.height - prevData.height === 1 && data.previous_block === prevData.id; - }; + return data.height - prevData.height === 1 && data.previous_block === prevData.id; + }; - const verifyBlock = () => { if (!isBlockChained()) { logger.error( `Blocks are not chained. Current block: ${JSON.stringify(data)}, previous block: ${JSON.stringify( @@ -47,43 +34,45 @@ export const verifyData = (context, data, prevData, signatureVerification) => { } // TODO: manually calculate block ID and compare to existing - if (signatureVerification) { - const bytes: any = Block.serialize(camelizeKeys(data), false); - const hash = createHash("sha256") - .update(bytes) - .digest(); + if (verifySignatures) { + const bytes = Block.serialize(camelizeKeys(data), false); + const hash = HashAlgorithms.sha256(bytes); const signatureVerify = crypto.verifyHash(hash, data.block_signature, data.generator_public_key); + if (!signatureVerify) { logger.error(`Failed to verify signature: ${JSON.stringify(data)}`); } + return signatureVerify; } return true; - }; - - switch (context) { - case "blocks": - return verifyBlock(); - case "transactions": - return verifyTransaction(); - default: - return false; } + + if (context === "transactions") { + if (!verifySignatures) { + return true; + } + + return Transaction.fromBytes(data.serialized).verified; + } + + return false; }; export const canImportRecord = (context, data, lastBlock) => { if (!lastBlock) { - // empty db return true; } - switch (context) { - case "blocks": - return data.height > lastBlock.height; - case "transactions": - return data.timestamp > lastBlock.timestamp; - default: - return false; + + if (context === "blocks") { + return data.height > lastBlock.height; } + + if (context === "transactions") { + return data.timestamp > lastBlock.timestamp; + } + + return false; }; diff --git a/packages/core-snapshots/src/utils.ts b/packages/core-snapshots/src/utils.ts index 6b1559e74c..7fc3277551 100644 --- a/packages/core-snapshots/src/utils.ts +++ b/packages/core-snapshots/src/utils.ts @@ -1,43 +1,40 @@ import { app } from "@arkecosystem/core-container"; import { Logger } from "@arkecosystem/core-interfaces"; -import fs from "fs-extra"; +import { copyFileSync, ensureFileSync, existsSync, readJSONSync, writeFileSync } from "fs-extra"; -export const getPath = (table, folder, codec) => { - const filename = `${table}.${codec}`; - return this.getFilePath(filename, folder); -}; - -export const writeMetaFile = snapshotInfo => { - const path = `${process.env.CORE_PATH_DATA}/snapshots/${snapshotInfo.folder}/meta.json`; - fs.writeFileSync(path, JSON.stringify(snapshotInfo), "utf8"); -}; +export const writeMetaFile = snapshotInfo => + writeFileSync( + `${process.env.CORE_PATH_DATA}/snapshots/${snapshotInfo.folder}/meta.json`, + JSON.stringify(snapshotInfo), + "utf8", + ); export const getFilePath = (filename, folder) => `${process.env.CORE_PATH_DATA}/snapshots/${folder}/${filename}`; -export const copySnapshot = (sourceFolder, destFolder, codec) => { +export const copySnapshot = (sourceFolder, destFolder) => { const logger = app.resolvePlugin("logger"); logger.info(`Copying snapshot from ${sourceFolder} to a new file ${destFolder} for appending of data`); const paths = { source: { - blocks: this.getPath("blocks", sourceFolder, codec), - transactions: this.getPath("transactions", sourceFolder, codec), + blocks: this.getFilePath("blocks", sourceFolder), + transactions: this.getFilePath("transactions", sourceFolder), }, dest: { - blocks: this.getPath("blocks", destFolder, codec), - transactions: this.getPath("transactions", destFolder, codec), + blocks: this.getFilePath("blocks", destFolder), + transactions: this.getFilePath("transactions", destFolder), }, }; - fs.ensureFileSync(paths.dest.blocks); - fs.ensureFileSync(paths.dest.transactions); + ensureFileSync(paths.dest.blocks); + ensureFileSync(paths.dest.transactions); - if (!fs.existsSync(paths.source.blocks) || !fs.existsSync(paths.source.transactions)) { - app.forceExit(`Unable to copy snapshot from ${sourceFolder} as it doesn't exist :bomb:`); + if (!existsSync(paths.source.blocks) || !existsSync(paths.source.transactions)) { + app.forceExit(`Unable to copy snapshot from ${sourceFolder} as it doesn't exist`); } - fs.copyFileSync(paths.source.blocks, paths.dest.blocks); - fs.copyFileSync(paths.source.transactions, paths.dest.transactions); + copyFileSync(paths.source.blocks, paths.dest.blocks); + copyFileSync(paths.source.transactions, paths.dest.transactions); }; export const calcRecordCount = (table, currentCount, sourceFolder) => { @@ -72,18 +69,18 @@ export const getSnapshotInfo = folder => { export const readMetaJSON = folder => { const metaFileInfo = this.getFilePath("meta.json", folder); - if (!fs.existsSync(metaFileInfo)) { - app.forceExit("Meta file meta.json not found. Exiting :bomb:"); + + if (!existsSync(metaFileInfo)) { + app.forceExit("Meta file meta.json not found. Exiting"); } - return fs.readJSONSync(metaFileInfo); + return readJSONSync(metaFileInfo); }; export const setSnapshotInfo = (options, lastBlock) => { const meta = { startHeight: options.start !== -1 ? options.start : 1, endHeight: options.end !== -1 ? options.end : lastBlock.height, - codec: options.codec, skipCompression: options.skipCompression || false, folder: "", }; diff --git a/packages/core-test-utils/README.md b/packages/core-test-utils/README.md deleted file mode 100644 index f485e09f7c..0000000000 --- a/packages/core-test-utils/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Ark Core - Test Utilities - -

- -

- -## Documentation - -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core-test-utils.html). - -## Security - -If you discover a security vulnerability within this package, please send an e-mail to security@ark.io. All security vulnerabilities will be promptly addressed. - -## Credits - -- [Brian Faust](https://github.com/faustbrian) -- [Erwann Gentric](https://github.com/air1one) -- [Joshua Noack](https://github.com/supaiku0) -- [All Contributors](../../../../contributors) - -## License - -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) diff --git a/packages/core-test-utils/__tests__/generators/transactions.test.ts b/packages/core-test-utils/__tests__/generators/transactions.test.ts deleted file mode 100644 index bfec0f01dd..0000000000 --- a/packages/core-test-utils/__tests__/generators/transactions.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { constants } from "../../../crypto"; -import { generateTransaction } from "../../src/generators"; - -const { TransactionTypes } = constants; - -describe("generateTransactions", () => { - it("should create transfer transactions for devnet", () => { - const devnetAddress = "DJQL8LWj81nRJNv9bbUgNXXELcB3q5qjZH"; - const transactions = generateTransaction("devnet", TransactionTypes.Transfer, undefined, devnetAddress); - - for (const transaction of transactions) { - expect(transaction).toMatchObject({ recipientId: devnetAddress }); - } - }); -}); diff --git a/packages/core-test-utils/__tests__/generators/transactions/delegate.test.ts b/packages/core-test-utils/__tests__/generators/transactions/delegate.test.ts deleted file mode 100644 index 6ddb90a61c..0000000000 --- a/packages/core-test-utils/__tests__/generators/transactions/delegate.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { constants } from "../../../../crypto"; -import { generateDelegateRegistration } from "../../../src/generators"; - -const { TransactionTypes } = constants; - -describe("Delegate transaction", () => { - const quantity = 4; - const transactions = generateDelegateRegistration(undefined, undefined, quantity); - - it("should return an array", () => { - expect(transactions).toBeArrayOfSize(quantity); - }); - - it("should return an array of 4 delegate objects", () => { - for (const transaction of transactions) { - expect(transaction).toMatchObject({ - type: TransactionTypes.DelegateRegistration, - }); - } - }); -}); diff --git a/packages/core-test-utils/__tests__/generators/transactions/signature.test.ts b/packages/core-test-utils/__tests__/generators/transactions/signature.test.ts deleted file mode 100644 index 87ae6c2e7b..0000000000 --- a/packages/core-test-utils/__tests__/generators/transactions/signature.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { constants } from "../../../../crypto"; -import { generateSecondSignature } from "../../../src/generators"; - -const { TransactionTypes } = constants; - -describe("Signature transaction", () => { - const quantity = 4; - const transactions = generateSecondSignature(undefined, undefined, quantity); - - it("should return an array", () => { - expect(transactions).toBeArrayOfSize(quantity); - }); - - it("should return an array of 4 signature objects", () => { - for (const transaction of transactions) { - expect(transaction).toMatchObject({ - type: TransactionTypes.SecondSignature, - }); - } - }); -}); diff --git a/packages/core-test-utils/__tests__/generators/transactions/transfer.test.ts b/packages/core-test-utils/__tests__/generators/transactions/transfer.test.ts deleted file mode 100644 index fd314bea6f..0000000000 --- a/packages/core-test-utils/__tests__/generators/transactions/transfer.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Bignum, constants } from "../../../../crypto"; -import { generateTransfers } from "../../../src/generators"; - -const { TransactionTypes, SATOSHI } = constants; - -describe("Transfer transaction", () => { - const amount = new (Bignum as any)(20 * SATOSHI); - const quantity = 4; - const transactions = generateTransfers(undefined, undefined, undefined, amount, quantity); - - it("should return an array", () => { - expect(transactions).toBeArrayOfSize(quantity); - }); - - it("should return an array of 4 transfer objects", () => { - for (const transaction of transactions) { - expect(transaction).toMatchObject({ - type: TransactionTypes.Transfer, - }); - } - }); - - it("should return an array sending 20 ark", () => { - for (const transaction of transactions) { - expect(transaction).toMatchObject({ amount }); - } - }); -}); diff --git a/packages/core-test-utils/__tests__/generators/transactions/vote.test.ts b/packages/core-test-utils/__tests__/generators/transactions/vote.test.ts deleted file mode 100644 index 6f6f59949f..0000000000 --- a/packages/core-test-utils/__tests__/generators/transactions/vote.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { constants } from "../../../../crypto"; -import { generateVote } from "../../../src/generators"; - -const { TransactionTypes } = constants; - -describe("Vote transaction", () => { - const quantity = 4; - const transactions = generateVote(undefined, undefined, undefined, quantity); - - it("should return an array", () => { - expect(transactions).toBeArrayOfSize(quantity); - }); - - it("should return an array of 4 vote objects", () => { - for (const transaction of transactions) { - expect(transaction).toMatchObject({ type: TransactionTypes.Vote }); - } - }); -}); diff --git a/packages/core-test-utils/package.json b/packages/core-test-utils/package.json deleted file mode 100644 index c7f02d5d89..0000000000 --- a/packages/core-test-utils/package.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "name": "@arkecosystem/core-test-utils", - "description": "Test Utilities for Ark Core", - "version": "2.2.1", - "contributors": [ - "Brian Faust ", - "Erwann Gentric ", - "Joshua Noack " - ], - "license": "MIT", - "main": "dist/index", - "types": "dist/index", - "files": [ - "dist" - ], - "scripts": { - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", - "prepublishOnly": "yarn build", - "pretest": "yarn lint && yarn build", - "compile": "../../node_modules/typescript/bin/tsc", - "build": "yarn clean && yarn compile", - "build:watch": "yarn clean && yarn compile -w", - "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "test": "cross-env CORE_ENV=test jest --runInBand --forceExit", - "test:coverage": "cross-env CORE_ENV=test jest --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$' --runInBand --forceExit", - "test:debug": "cross-env CORE_ENV=test node --inspect-brk ../../node_modules/.bin/jest --runInBand", - "test:watch": "cross-env CORE_ENV=test jest --runInBand --watch", - "test:watch:all": "cross-env CORE_ENV=test jest --runInBand --watchAll", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" - }, - "dependencies": { - "@arkecosystem/core-container": "^2.2.1", - "@arkecosystem/core-interfaces": "^2.2.1", - "@arkecosystem/core-jest-matchers": "^2.2.1", - "@arkecosystem/crypto": "^2.2.1", - "awilix": "^4.2.0", - "bip39": "^2.5.0", - "lodash.get": "^4.4.2", - "lodash.isequal": "^4.5.0", - "lodash.isstring": "^4.0.1", - "lodash.sortby": "^4.7.0", - "superheroes": "^2.0.0", - "xstate": "^4.3.1" - }, - "devDependencies": { - "@types/bip39": "^2.4.1", - "@types/lodash.get": "^4.4.4", - "@types/lodash.isequal": "^4.5.3", - "@types/lodash.isstring": "^4.0.4", - "@types/lodash.sortby": "^4.7.4" - }, - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=10.x" - }, - "jest": { - "preset": "../../jest-preset.json" - } -} diff --git a/packages/core-test-utils/src/generators/index.ts b/packages/core-test-utils/src/generators/index.ts deleted file mode 100644 index 1416ec6ab3..0000000000 --- a/packages/core-test-utils/src/generators/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./transactions"; -export * from "./wallets"; diff --git a/packages/core-test-utils/src/generators/transactions/delegate.ts b/packages/core-test-utils/src/generators/transactions/delegate.ts deleted file mode 100644 index d598303b61..0000000000 --- a/packages/core-test-utils/src/generators/transactions/delegate.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { constants } from "@arkecosystem/crypto"; -import { generateTransaction } from "./transaction"; - -const { DelegateRegistration } = constants.TransactionTypes; - -export const generateDelegateRegistration = ( - network, - passphrase, - quantity: number = 1, - getStruct: boolean = false, - username?: string, - fee?: number, -) => { - if (Array.isArray(passphrase)) { - return passphrase.map( - p => generateTransaction(network, DelegateRegistration, p, username, undefined, 1, getStruct, fee)[0], - ); - } - return generateTransaction( - network, - DelegateRegistration, - passphrase, - username, - undefined, - quantity, - getStruct, - fee, - ); -}; diff --git a/packages/core-test-utils/src/generators/transactions/index.ts b/packages/core-test-utils/src/generators/transactions/index.ts deleted file mode 100644 index 4244c83812..0000000000 --- a/packages/core-test-utils/src/generators/transactions/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./delegate"; -export * from "./signature"; -export * from "./transaction"; -export * from "./transfer"; -export * from "./vote"; diff --git a/packages/core-test-utils/src/generators/transactions/signature.ts b/packages/core-test-utils/src/generators/transactions/signature.ts deleted file mode 100644 index 2874f1bc4b..0000000000 --- a/packages/core-test-utils/src/generators/transactions/signature.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { constants } from "@arkecosystem/crypto"; -import { generateTransaction } from "./transaction"; - -const { SecondSignature } = constants.TransactionTypes; - -export const generateSecondSignature = ( - network, - passphrase, - quantity: number = 10, - getStruct: boolean = false, - fee?: number, -) => { - if (Array.isArray(passphrase)) { - return passphrase.map( - p => generateTransaction(network, SecondSignature, p, undefined, undefined, 1, getStruct, fee)[0], - ); - } - return generateTransaction(network, SecondSignature, passphrase, undefined, undefined, quantity, getStruct, fee); -}; diff --git a/packages/core-test-utils/src/generators/transactions/transaction.ts b/packages/core-test-utils/src/generators/transactions/transaction.ts deleted file mode 100644 index 7757385296..0000000000 --- a/packages/core-test-utils/src/generators/transactions/transaction.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { client, constants, crypto } from "@arkecosystem/crypto"; -import superheroes from "superheroes"; -import { delegatesSecrets } from "../../fixtures/testnet/passphrases"; - -const defaultPassphrase = delegatesSecrets[0]; -const { Transfer, SecondSignature, DelegateRegistration, Vote } = constants.TransactionTypes; - -export const generateTransaction = ( - network, - type, - passphrase, - addressOrPublicKeyOrUsername, - amount: number = 2, - quantity: number = 10, - getStruct: boolean = false, - fee?: number, -) => { - network = network || "testnet"; - type = type || Transfer; - passphrase = passphrase || defaultPassphrase; - - if (!["mainnet", "devnet", "testnet", "unitnet"].includes(network)) { - throw new Error("Invalid network"); - } - - if (![Transfer, SecondSignature, DelegateRegistration, Vote].includes(type)) { - throw new Error("Invalid transaction type"); - } - - let secondPassphrase; - if (typeof passphrase === "object") { - secondPassphrase = passphrase.secondPassphrase; - passphrase = passphrase.passphrase; - } - - client.getConfigManager().setFromPreset(network); - - const transactions = []; - for (let i = 0; i < quantity; i++) { - let builder: any = client.getBuilder(); - switch (type) { - case Transfer: { - if (!addressOrPublicKeyOrUsername) { - addressOrPublicKeyOrUsername = crypto.getAddress(crypto.getKeys(passphrase).publicKey); - } - builder = builder - .transfer() - .recipientId(addressOrPublicKeyOrUsername) - .amount(amount) - .vendorField(`Test Transaction ${i + 1}`); - break; - } - case SecondSignature: { - builder = builder.secondSignature().signatureAsset(passphrase); - break; - } - case DelegateRegistration: { - const username = - addressOrPublicKeyOrUsername || - superheroes - .random() - .toLowerCase() - .replace(/[^a-z0-9]/g, "_") - .substring(0, 20); - builder = builder.delegateRegistration().usernameAsset(username); - break; - } - case Vote: { - if (!addressOrPublicKeyOrUsername) { - addressOrPublicKeyOrUsername = crypto.getKeys(passphrase).publicKey; - } - builder = builder.vote().votesAsset([`+${addressOrPublicKeyOrUsername}`]); - break; - } - default: { - throw new Error("Invalid transaction type"); - } - } - - if (fee || fee === 0) { - builder = builder.fee(fee); - } - - builder = builder.sign(passphrase); - - if (secondPassphrase) { - builder = builder.secondSign(secondPassphrase); - } - const tx = getStruct ? builder.getStruct() : builder.build(); - - transactions.push(tx); - } - - return transactions; -}; diff --git a/packages/core-test-utils/src/generators/transactions/transfer.ts b/packages/core-test-utils/src/generators/transactions/transfer.ts deleted file mode 100644 index 742f87f77a..0000000000 --- a/packages/core-test-utils/src/generators/transactions/transfer.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { constants } from "@arkecosystem/crypto"; -import { generateTransaction } from "./transaction"; - -const { Transfer } = constants.TransactionTypes; - -export const generateTransfers = ( - network: string, - passphrase: any = "secret passphrase", - address?: string, - amount: number = 2, - quantity: number = 10, - getStruct: boolean = false, - fee?: number, -) => { - if (Array.isArray(passphrase)) { - return passphrase.map( - p => generateTransaction(network, Transfer, passphrase, address, amount, 1, getStruct, fee)[0], - ); - } - return generateTransaction(network, Transfer, passphrase, address, amount, quantity, getStruct, fee); -}; diff --git a/packages/core-test-utils/src/generators/transactions/vote.ts b/packages/core-test-utils/src/generators/transactions/vote.ts deleted file mode 100644 index f05b9a73ed..0000000000 --- a/packages/core-test-utils/src/generators/transactions/vote.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { constants } from "@arkecosystem/crypto"; -import { generateTransaction } from "./transaction"; - -const { Vote } = constants.TransactionTypes; - -export const generateVote = ( - network, - passphrase, - publicKey, - quantity: number = 10, - getStruct: boolean = false, - fee?: number, -) => { - if (Array.isArray(passphrase)) { - return passphrase.map(p => generateTransaction(network, Vote, p, publicKey, undefined, 1, getStruct, fee)[0]); - } - return generateTransaction(network, Vote, passphrase, publicKey, undefined, quantity, getStruct, fee); -}; diff --git a/packages/core-tester-cli/.gitattributes b/packages/core-tester-cli/.gitattributes index 60cc52db63..63f6a5b970 100644 --- a/packages/core-tester-cli/.gitattributes +++ b/packages/core-tester-cli/.gitattributes @@ -2,10 +2,6 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore -/__tests__ export-ignore -/docs export-ignore /README.md export-ignore diff --git a/packages/core-tester-cli/README.md b/packages/core-tester-cli/README.md index 7242fb5d9c..83350bebc9 100644 --- a/packages/core-tester-cli/README.md +++ b/packages/core-tester-cli/README.md @@ -1,12 +1,12 @@ -# Ark Core - Tester CLI +# Persona Core - Tester CLI

- +

## Documentation -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core-tester-cli.html). +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/optional/core-tester-cli.html). ## Security @@ -14,10 +14,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [Brian Faust](https://github.com/faustbrian) -- [Alex Barnsley](https://github.com/alexbarnsley) -- [All Contributors](../../../../contributors) +This project exists thanks to all the people who [contribute](../../../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-tester-cli/__tests__/commands/delegate-registration.test.ts b/packages/core-tester-cli/__tests__/commands/delegate-registration.test.ts deleted file mode 100644 index 4fd7f335bc..0000000000 --- a/packages/core-tester-cli/__tests__/commands/delegate-registration.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -import "jest-extended"; - -import axios from "axios"; -import MockAdapter from "axios-mock-adapter"; -import superheroes from "superheroes"; -import { DelegateRegistrationCommand } from "../../src/commands/delegate-registration"; -import { arkToSatoshi } from "../../src/utils"; -import { toFlags } from "../shared"; - -const mockAxios = new MockAdapter(axios); - -beforeEach(() => { - // Just passthru. We'll test the Command class logic in its own test file more thoroughly - mockAxios.onGet("http://localhost:4003/api/v2/node/configuration").reply(200, { data: { constants: {} } }); - mockAxios.onGet("http://localhost:4000/config").reply(200, { data: { network: {} } }); - jest.spyOn(axios, "get"); - jest.spyOn(axios, "post"); -}); - -afterEach(() => { - mockAxios.reset(); -}); - -describe("Commands - Delegate Registration", () => { - it("should register as delegate", async () => { - const opts = { - delegateFee: 1, - number: 1, - }; - - const expectedDelegateName = "mr_bojangles"; - // call to delegates/{publicKey}/voters returns zero delegates - mockAxios.onGet(/http:\/\/localhost:4003\/api\/v2\/delegates/).reply(200, { - meta: { pageCount: 1 }, - data: [], - }); - jest.spyOn(superheroes, "random").mockImplementation(() => expectedDelegateName); - - mockAxios.onPost("http://localhost:4003/api/v2/transactions").reply(200, { data: {} }); - - const flags = toFlags(opts); - await DelegateRegistrationCommand.run(flags); - - expect(axios.post).toHaveBeenCalledWith( - "http://localhost:4003/api/v2/transactions", - { - transactions: [ - expect.objectContaining({ - fee: arkToSatoshi(opts.delegateFee), - asset: { - delegate: { - username: expectedDelegateName, - }, - }, - }), - ], - }, - expect.any(Object), - ); - }); -}); diff --git a/packages/core-tester-cli/__tests__/commands/second-signature.test.ts b/packages/core-tester-cli/__tests__/commands/second-signature.test.ts deleted file mode 100644 index cb600ec464..0000000000 --- a/packages/core-tester-cli/__tests__/commands/second-signature.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -import "jest-extended"; - -import axios from "axios"; -import MockAdapter from "axios-mock-adapter"; -import { SecondSignatureCommand } from "../../src/commands/second-signature"; -import { arkToSatoshi } from "../../src/utils"; -import { toFlags } from "../shared"; - -const mockAxios = new MockAdapter(axios); - -beforeEach(() => { - // Just passthru. We'll test the Command class logic in its own test file more thoroughly - mockAxios.onGet("http://localhost:4003/api/v2/node/configuration").reply(200, { data: { constants: {} } }); - mockAxios.onGet("http://localhost:4000/config").reply(200, { data: { network: {} } }); - jest.spyOn(axios, "get"); - jest.spyOn(axios, "post"); -}); - -afterEach(() => { - mockAxios.reset(); -}); - -describe("Commands - Second signature", () => { - it("should apply second signature", async () => { - const opts = { - signatureFee: 1, - number: 1, - }; - - mockAxios.onPost("http://localhost:4003/api/v2/transactions").reply(200, { data: {} }); - - const flags = toFlags(opts); - await SecondSignatureCommand.run(flags); - - expect(axios.post).toHaveBeenCalledWith( - "http://localhost:4003/api/v2/transactions", - { - transactions: [ - expect.objectContaining({ - fee: arkToSatoshi(opts.signatureFee), - asset: { - signature: { - publicKey: expect.any(String), - }, - }, - }), - ], - }, - expect.any(Object), - ); - }); -}); diff --git a/packages/core-tester-cli/__tests__/commands/transfer.test.ts b/packages/core-tester-cli/__tests__/commands/transfer.test.ts deleted file mode 100644 index 4a0c9ddb31..0000000000 --- a/packages/core-tester-cli/__tests__/commands/transfer.test.ts +++ /dev/null @@ -1,143 +0,0 @@ -import "jest-extended"; - -import axios from "axios"; -import MockAdapter from "axios-mock-adapter"; -import { TransferCommand } from "../../src/commands/transfer"; -import { arkToSatoshi } from "../../src/utils"; -import { toFlags } from "../shared"; - -const mockAxios = new MockAdapter(axios); - -beforeEach(() => { - // Just passthru. We'll test the Command class logic in its own test file more thoroughly - mockAxios.onGet("http://localhost:4003/api/v2/node/configuration").reply(200, { data: { constants: {} } }); - mockAxios.onGet("http://localhost:4000/config").reply(200, { data: { network: {} } }); - - jest.spyOn(axios, "post"); -}); - -afterEach(() => { - mockAxios.reset(); -}); - -afterAll(() => mockAxios.restore()); - -describe("Commands - Transfer", () => { - it("should postTransactions using custom smartBridge value", async () => { - const expectedRecipientId = "DFyUhQW52sNB5PZdS7VD9HknwYrSNHPQDq"; - const expectedTransactionAmount = 2; - const expectedFee = arkToSatoshi(0.1); - const opts = { - amount: expectedTransactionAmount, - transferFee: expectedFee, - number: 1, - smartBridge: "foo bar", - recipient: expectedRecipientId, - }; - - mockAxios.onPost("http://localhost:4003/api/v2/transactions").reply(200, { data: {} }); - let expectedTransactions = []; - // @ts-ignore - jest.spyOn(axios, "post").mockImplementation((uri, { transactions }) => { - expectedTransactions = transactions; - }); - - const flags = toFlags(opts); - await TransferCommand.run(flags); - - expect(expectedTransactions).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - vendorField: "foo bar", - amount: arkToSatoshi(expectedTransactionAmount), - fee: arkToSatoshi(expectedFee), - recipientId: expectedRecipientId, - }), - ]), - ); - }); - - it("should generate n transactions", async () => { - const expectedTxCount = 5; - const expectedRecipientId = "DFyUhQW52sNB5PZdS7VD9HknwYrSNHPQDq"; - const opts = { - amount: arkToSatoshi(2), - transferFee: arkToSatoshi(2), - number: expectedTxCount, - recipient: expectedRecipientId, - }; - - mockAxios.onPost("http://localhost:4003/api/v2/transactions").reply(200, { data: {} }); - let expectedTransactions = []; - // @ts-ignore - jest.spyOn(axios, "post").mockImplementation((uri, { transactions }) => { - expectedTransactions = transactions; - }); - - const flags = toFlags(opts); - await TransferCommand.run(flags); - - expect(expectedTransactions).toHaveLength(expectedTxCount); - for (const t of expectedTransactions) { - expect(t.vendorField).toMatch(/Transaction \d/); - expect(t.amount).toBeDefined(); - expect(t.fee).toBeDefined(); - } - }); - - it("should send n transactions to specified recipient", async () => { - const expectedTxCount = 10; - const expectedRecipientId = "DFyUhQW52sNB5PZdS7VD9HknwYrSNHPQDq"; - const opts = { - amount: arkToSatoshi(2), - transferFee: arkToSatoshi(0.1), - number: expectedTxCount, - recipient: expectedRecipientId, - }; - - mockAxios.onPost("http://localhost:4003/api/v2/transactions").reply(200, { data: {} }); - let expectedTransactions = []; - // @ts-ignore - jest.spyOn(axios, "post").mockImplementation((uri, { transactions }) => { - expectedTransactions = transactions; - }); - - const flags = toFlags(opts); - await TransferCommand.run(flags); - - expect(expectedTransactions).toHaveLength(expectedTxCount); - for (const t of expectedTransactions) { - expect(t.recipientId).toEqual(expectedRecipientId); - } - }); - - it("should sign with 2nd passphrase if specified", async () => { - const expectedTransactionAmount = arkToSatoshi(2); - const expectedFee = arkToSatoshi(0.1); - const expectedRecipientId = "DFyUhQW52sNB5PZdS7VD9HknwYrSNHPQDq"; - - const opts = { - amount: expectedTransactionAmount, - transferFee: expectedFee, - number: 1, - secondPassphrase: "she sells sea shells down by the sea shore", - recipient: expectedRecipientId, - }; - - mockAxios.onPost("http://localhost:4003/api/v2/transactions").reply(200, { data: {} }); - let expectedTransactions = []; - // @ts-ignore - jest.spyOn(axios, "post").mockImplementation((uri, { transactions }) => { - expectedTransactions = transactions; - }); - - const flags = toFlags(opts); - await TransferCommand.run(flags); - - expect(expectedTransactions).toHaveLength(1); - for (const t of expectedTransactions) { - expect(t.secondSignature).toBeDefined(); - expect(t.signSignature).toEqual(t.secondSignature); - } - }); -}); diff --git a/packages/core-tester-cli/__tests__/commands/vote.test.ts b/packages/core-tester-cli/__tests__/commands/vote.test.ts deleted file mode 100644 index 0488bdc1eb..0000000000 --- a/packages/core-tester-cli/__tests__/commands/vote.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -import "jest-extended"; - -import axios from "axios"; -import MockAdapter from "axios-mock-adapter"; -import { VoteCommand } from "../../src/commands/vote"; -import { arkToSatoshi } from "../../src/utils"; -import { toFlags } from "../shared"; - -const mockAxios = new MockAdapter(axios); - -beforeEach(() => { - // Just passthru. We'll test the Command class logic in its own test file more thoroughly - mockAxios.onGet("http://localhost:4003/api/v2/node/configuration").reply(200, { data: { constants: {} } }); - mockAxios.onGet("http://localhost:4000/config").reply(200, { data: { network: {} } }); - jest.spyOn(axios, "get"); - jest.spyOn(axios, "post"); -}); - -afterEach(() => { - mockAxios.reset(); -}); - -afterAll(() => mockAxios.restore()); - -describe("Commands - Vote", () => { - it("should vote for specified delegate", async () => { - const expectedDelegate = "03f294777f7376e970b2bd4805b4a90c8449b5935d530bdb566d02800ac44a4c00"; - const opts = { - number: 1, - voteFee: 1, - delegate: expectedDelegate, - }; - - mockAxios.onGet(/http:\/\/localhost:4003\/api\/v2\/delegates.*/).reply(200); - mockAxios.onPost("http://localhost:4003/api/v2/transactions").reply(200, { data: {} }); - - const flags = toFlags(opts); - await VoteCommand.run(flags); - - expect(axios.post).toHaveBeenCalledWith( - "http://localhost:4003/api/v2/transactions", - { - transactions: [ - expect.objectContaining({ - fee: arkToSatoshi(opts.voteFee), - asset: { - votes: [`+${expectedDelegate}`], - }, - }), - ], - }, - expect.any(Object), - ); - }); - - it("should vote random delegate if non specified", async () => { - const expectedDelegate = "03f294777f7376e970b2bd4805b4a90c8449b5935d530bdb566d02800ac44a4c00"; - const opts = { - number: 1, - voteFee: 1, - }; - - mockAxios.onPost("http://localhost:4003/api/v2/transactions").reply(200, { data: {} }); - mockAxios.onGet(/http:\/\/localhost:4003\/api\/v2\/delegates\/.*/).reply(200); // call to delegates/{publicKey}/voters - // call to /delegates - mockAxios.onGet(/http:\/\/localhost:4003\/api\/v2\/delegates/).reply(200, { - meta: { pageCount: 1 }, - data: [{ publicKey: expectedDelegate }], - }); - - const flags = toFlags(opts); - await VoteCommand.run(flags); - - expect(axios.post).toHaveBeenCalledWith( - "http://localhost:4003/api/v2/transactions", - { - transactions: [ - expect.objectContaining({ - fee: arkToSatoshi(opts.voteFee), - asset: { - votes: [`+${expectedDelegate}`], - }, - }), - ], - }, - expect.any(Object), - ); - }); -}); diff --git a/packages/core-tester-cli/__tests__/shared.ts b/packages/core-tester-cli/__tests__/shared.ts deleted file mode 100644 index 1562862cb6..0000000000 --- a/packages/core-tester-cli/__tests__/shared.ts +++ /dev/null @@ -1,7 +0,0 @@ -const defaultOpts = ["--skipTesting", "--skipValidation"]; - -export const toFlags = (opts: object): string[] => { - return Object.keys(opts) - .map(k => [`--${k}`, String(opts[k])]) - .reduce((a, b) => a.concat(b), defaultOpts); -}; diff --git a/packages/core-tester-cli/__tests__/utils.test.ts b/packages/core-tester-cli/__tests__/utils.test.ts deleted file mode 100644 index eac906a633..0000000000 --- a/packages/core-tester-cli/__tests__/utils.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { arkToSatoshi, parseFee, satoshiToArk } from "../src/utils"; - -describe("Utils", () => { - describe("parseFee", () => { - it("should give satoshi", () => { - expect(parseFee(0.1).toString()).toBe("10000000"); - expect(parseFee(1).toString()).toBe("100000000"); - expect(parseFee(10).toString()).toBe("1000000000"); - expect(parseFee("0.1").toString()).toBe("10000000"); - expect(parseFee("1").toString()).toBe("100000000"); - expect(parseFee("10").toString()).toBe("1000000000"); - expect(parseFee("0.001-0.005").toNumber()).toBeWithin(100000, 500000); - }); - }); - - describe("arkToSatoshi", () => { - it("should give satoshi", () => { - expect(arkToSatoshi(0.00000001).toString()).toBe("1"); - expect(arkToSatoshi(0.1).toString()).toBe("10000000"); - expect(arkToSatoshi(1).toString()).toBe("100000000"); - expect(arkToSatoshi(10).toString()).toBe("1000000000"); - }); - }); - - describe("satoshiToArk", () => { - it("should give ark", () => { - expect(satoshiToArk(1)).toBe("0.00000001 DѦ"); - expect(satoshiToArk(10000000)).toBe("0.1 DѦ"); - expect(satoshiToArk(100000000)).toBe("1 DѦ"); - expect(satoshiToArk(1000000000)).toBe("10 DѦ"); - }); - }); -}); diff --git a/packages/core-tester-cli/package.json b/packages/core-tester-cli/package.json index d7c4e90103..bbb2a8544b 100644 --- a/packages/core-tester-cli/package.json +++ b/packages/core-tester-cli/package.json @@ -1,7 +1,7 @@ { "name": "@arkecosystem/core-tester-cli", - "description": "Tester CLI for Ark Core", - "version": "2.2.1", + "description": "Tester CLI for ARK Core", + "version": "2.3.15", "contributors": [ "Brian Faust ", "Alex Barnsley " @@ -18,51 +18,35 @@ }, "scripts": { "tester": "./bin/run", - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", "prepublishOnly": "yarn build", "pretest": "bash ../../scripts/pre-test.sh", - "prepack": "oclif-dev manifest && npm shrinkwrap", + "prepack": "../../node_modules/.bin/oclif-dev manifest && npm shrinkwrap", "postpack": "rm -f oclif.manifest.json", "compile": "../../node_modules/typescript/bin/tsc", "build": "yarn clean && yarn compile", "build:watch": "yarn clean && yarn compile -w", - "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "test": "cross-env CORE_ENV=test jest --runInBand --forceExit", - "test:coverage": "cross-env CORE_ENV=test jest --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$' --runInBand --forceExit", - "test:debug": "cross-env CORE_ENV=test node --inspect-brk ../../node_modules/.bin/jest --runInBand", - "test:watch": "cross-env CORE_ENV=test jest --runInBand --watch", - "test:watch:all": "cross-env CORE_ENV=test jest --runInBand --watchAll", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" + "clean": "del dist" }, "dependencies": { - "@arkecosystem/core-utils": "^2.2.1", - "@arkecosystem/crypto": "^2.2.1", - "@oclif/command": "^1.5.10", - "@oclif/config": "^1.12.6", + "@arkecosystem/core-utils": "^2.3.15", + "@arkecosystem/crypto": "^2.3.15", + "@oclif/command": "^1.5.11", + "@oclif/config": "^1.12.9", "@oclif/plugin-help": "^2.1.6", "@oclif/plugin-not-found": "^1.2.2", - "axios": "^0.18.0", "bip39": "^2.5.0", "clipboardy": "^1.2.3", "delay": "^4.1.0", - "lodash.fill": "^3.4.0", - "pino": "^5.11.1", + "pino": "^5.11.2", "pino-pretty": "^2.5.0", "pluralize": "^7.0.0", - "superheroes": "^2.0.0" + "pokemon": "^1.2.3" }, "devDependencies": { - "@types/bip39": "^2.4.1", + "@types/bip39": "^2.4.2", "@types/clipboardy": "^1.1.0", - "@types/lodash.fill": "^3.4.4", - "@types/pino": "^5.8.4", - "@types/pluralize": "^0.0.29", - "axios-mock-adapter": "^1.16.0" + "@types/lodash.fill": "^3.4.6", + "@types/pluralize": "^0.0.29" }, "publishConfig": { "access": "public" @@ -70,12 +54,20 @@ "engines": { "node": ">=10.x" }, - "jest": { - "preset": "../../jest-preset.json" - }, "oclif": { "commands": "./dist/commands", - "bin": "snapshot", + "bin": "ark-tester", + "topics": { + "debug": { + "description": "debug blocks and transactions" + }, + "send": { + "description": "send transactions of various types" + }, + "make": { + "description": "make new identities" + } + }, "plugins": [ "@oclif/plugin-help", "@oclif/plugin-not-found" diff --git a/packages/core-tester-cli/src/commands/command.ts b/packages/core-tester-cli/src/commands/command.ts index a42858a8cd..c209681e07 100644 --- a/packages/core-tester-cli/src/commands/command.ts +++ b/packages/core-tester-cli/src/commands/command.ts @@ -1,337 +1,237 @@ import { bignumify } from "@arkecosystem/core-utils"; -import { Bignum, crypto } from "@arkecosystem/crypto"; +import { Address, Bignum, configManager, formatSatoshi, NetworkName } from "@arkecosystem/crypto"; import Command, { flags } from "@oclif/command"; -import bip39 from "bip39"; -import clipboardy from "clipboardy"; import delay from "delay"; -import fs from "fs"; -import path from "path"; -import pluralize from "pluralize"; -import { config } from "../config"; -import { customFlags } from "../flags"; -import { logger, paginate, request } from "../utils"; +import { satoshiFlag } from "../flags"; +import { HttpClient } from "../http-client"; +import { logger } from "../logger"; +import { Signer } from "../signer"; export abstract class BaseCommand extends Command { - public static flags = { + public static flagsConfig = { + host: flags.string({ + description: "API host", + default: "http://localhost", + }), + portAPI: flags.integer({ + description: "API port", + default: 4003, + }), + portP2P: flags.integer({ + description: "P2P port", + default: 4000, + }), + }; + + public static flagsSend = { + ...BaseCommand.flagsConfig, + passphrase: flags.string({ + description: "passphrase of initial wallet", + default: "clay harbor enemy utility margin pretty hub comic piece aerobic umbrella acquire", + }), + secondPassphrase: flags.string({ + description: "second passphrase of initial wallet", + }), number: flags.integer({ description: "number of wallets", - default: 10, + default: 1, }), - amount: customFlags.number({ + amount: satoshiFlag({ description: "initial wallet token amount", default: 2, }), - transferFee: customFlags.number({ + transferFee: satoshiFlag({ description: "transfer fee", default: 0.1, }), - baseUrl: flags.string({ - description: "base api url", + skipProbing: flags.boolean({ + description: "skip transaction probing", }), - apiPort: flags.integer({ - description: "base api port", - default: 4003, + waves: flags.integer({ + description: "number of waves to send", + default: 1, }), - p2pPort: flags.integer({ - description: "base p2p port", - default: 4002, - }), - passphrase: flags.string({ - description: "passphrase of initial wallet", - }), - secondPassphrase: flags.string({ - description: "second passphrase of initial wallet", - }), - skipValidation: flags.boolean({ - description: "skip transaction validations", + }; + + public static flagsDebug = { + network: flags.string({ + description: "network used for crypto", + default: "testnet", }), - skipTesting: flags.boolean({ - description: "skip testing", + log: flags.boolean({ + description: "log the data to the console", }), copy: flags.boolean({ - description: "copy the transactions to the clipboard", + description: "copy the data to the clipboard", }), }; - public options: any; - public config: any; + protected api: HttpClient; + protected p2p: HttpClient; + protected signer: Signer; + protected network: Record; + protected constants: Record; + + protected async make(command): Promise { + const { args, flags } = this.parse(command); + + this.api = new HttpClient(`${flags.host}:${flags.portAPI}/api/v2/`); + this.p2p = new HttpClient(`${flags.host}:${flags.portP2P}/`); + + await this.setupConstants(); + await this.setupNetwork(); - /** - * Init new instance of command. - * @param {Object} options - * @return {*} - */ - public async initialize(command): Promise { - // tslint:disable-next-line:no-shadowed-variable - const { flags } = this.parse(command); + configManager.setFromPreset(this.network.name); - this.options = flags; - this.applyConfig(); - await this.loadConstants(); - await this.loadNetworkConfig(); + this.signer = new Signer(this.network); - return { flags }; + return { args, flags }; } - /** - * Copy transactions to clipboard. - * @param {Object[]} transactions - * @return {void} - */ - public copyToClipboard(transactions) { - for (const transaction of transactions) { - transaction.serialized = transaction.serialized.toString("hex"); - } + protected makeOffline(command): any { + const { args, flags } = this.parse(command); + + configManager.setFromPreset(flags.network as NetworkName); + + this.signer = new Signer(configManager.all()); - clipboardy.writeSync(JSON.stringify(transactions)); - logger.info(`Copied ${pluralize("transaction", transactions.length, true)}`); + return { args, flags }; } - /** - * Generate wallets based on quantity. - * @param {Number} [quantity] - * @return {Object[]} - */ - public generateWallets(quantity: any = null) { - if (!quantity) { - quantity = this.options.number; + protected async sendTransaction(transactions: any[]): Promise> { + if (!Array.isArray(transactions)) { + transactions = [transactions]; } - const wallets = []; - for (let i = 0; i < quantity; i++) { - const passphrase = bip39.generateMnemonic(); - const keys = crypto.getKeys(passphrase); - const address = crypto.getAddress(keys.publicKey, this.config.network.version); + for (const transaction of transactions) { + let recipientId = transaction.recipientId; - wallets.push({ address, keys, passphrase }); - } + if (!recipientId) { + recipientId = Address.fromPublicKey(transaction.senderPublicKey, this.network.version); + } - const testWalletsPath = path.resolve(__dirname, "../../test-wallets"); - fs.appendFileSync(testWalletsPath, `${new Date().toLocaleDateString()} ${"-".repeat(70)}\n`); - for (const wallet of wallets) { - fs.appendFileSync(testWalletsPath, `${wallet.address}: ${wallet.passphrase}\n`); + logger.info( + `[T] ${transaction.id} (${recipientId} / ${this.fromSatoshi(transaction.amount)} / ${this.fromSatoshi( + transaction.fee, + )})`, + ); } - return wallets; + return this.api.post("transactions", { transactions }); } - /** - * Get delegate API response. - * @return {Object[]} - * @throws 'Could not get delegates' - */ - public async getDelegates() { + protected async knockTransaction(id: string): Promise { try { - const delegates = await paginate(this.config, "/api/v2/delegates"); + const { data } = await this.api.get(`transactions/${id}`); - return delegates; + logger.info(`[T] ${id} (${data.blockId})`); + + return true; } catch (error) { - const message = error.response ? error.response.data.message : error.message; - throw new Error(`Could not get delegates: ${message}`); + logger.error(error.message); + + logger.error(`[T] ${id} (not forged)`); + + return false; } } - /** - * Get transaction from API by ID. - * @param {String} id - * @return {(Object|null)} - */ - public async getTransaction(id) { - try { - const response = await request(this.config).get(`/api/v2/transactions/${id}`); + protected async knockBalance(address: string, expected: Bignum): Promise { + const actual = await this.getWalletBalance(address); - if (response.data) { - return response.data; - } - } catch (error) { - // + if (bignumify(expected).isEqualTo(actual)) { + logger.info(`[W] ${address} (${this.fromSatoshi(actual)})`); + } else { + logger.error(`[W] ${address} (${this.fromSatoshi(expected)} / ${this.fromSatoshi(actual)})`); } - - return null; } - /** - * Get delegate voters by public key. - * @param {String} publicKey - * @return {Object[]} - */ - public async getVoters(publicKey) { + protected async getWalletBalance(address: string): Promise { try { - return paginate(this.config, `/api/v2/delegates/${publicKey}/voters`); + const { data } = await this.api.get(`wallets/${address}`); + + return bignumify(data.balance); } catch (error) { - const message = error.response ? error.response.data.message : error.message; - throw new Error(`Could not get voters for '${publicKey}': ${message}`); + return Bignum.ZERO; } } - /** - * Get wallet balance by address. - * @param {String} address - * @return {Bignum} - */ - public async getWalletBalance(address) { - try { - return bignumify((await this.getWallet(address)).balance); - } catch (error) { - // - } + protected async broadcastTransactions(transactions) { + await this.sendTransaction(transactions); - return Bignum.ZERO; + return this.awaitConfirmations(transactions); } - /** - * Get wallet by address. - * @param {String} address - * @return {Object} - */ - public async getWallet(address) { + protected async getTransaction(id: string): Promise { try { - const response = await request(this.config).get(`/api/v2/wallets/${address}`); - - if (response.data) { - return response.data; - } + const { data } = await this.api.get(`transactions/${id}`); - return null; + return data; } catch (error) { - const message = error.response ? error.response.data.message : error.message; - throw new Error(`Could not get wallet for '${address}': ${message}`); + logger.error(error.message); + + return false; } } - /** - * Send transactions to API and wait for response. - * @param {Object[]} transactions - * @param {String} [transactionType] - * @param {Boolean} [wait=true] - * @return {Object} - */ - public async sendTransactions(transactions, transactionType: any = null, wait = true) { - const response = await this.postTransactions(transactions); - - if (wait) { - const delaySeconds = this.getTransactionDelaySeconds(transactions); - transactionType = `${transactionType ? `${transactionType} ` : ""}transactions`; - logger.info(`Waiting ${delaySeconds} seconds to apply ${transactionType}`); - await delay(delaySeconds * 1000); - } + protected castFlags(values: Record): string[] { + return Object.keys(BaseCommand.flagsConfig) + .map((key: string) => { + const value = values[key]; - return response; + if (value === undefined) { + return undefined; + } + + if (value === true) { + return `--${key}`; + } + + return `--${key}=${value}`; + }) + .filter(value => value !== undefined); } - /** - * Send transactions to API. - * @param {Object[]} transactions - * @return {Object} - */ - public async postTransactions(transactions) { - try { - const response = await request(this.config).post("/api/v2/transactions", { - transactions, - }); - return response.data; - } catch (error) { - const message = error.response ? error.response.data.message : error.message; - throw new Error(`Could not post transactions: ${message}`); - } + protected toSatoshi(value) { + return bignumify(value) + .times(1e8) + .toFixed(); } - /** - * Load constants from API and apply to config. - * @return {void} - */ - public async loadConstants() { - try { - this.config.constants = (await request(this.config).get("/api/v2/node/configuration")).data.constants; - } catch (error) { - logger.error("Failed to get constants: ", error.message); - process.exit(1); - } + protected fromSatoshi(satoshi) { + return formatSatoshi(satoshi); } - /** - * Load network from API and apply to config. - * @return {void} - */ - public async loadNetworkConfig() { + private async setupConstants() { try { - this.config.network = (await request(this.config).get("/config", true)).data.network; + const { data } = await this.api.get("node/configuration"); + + this.constants = data.constants; } catch (error) { - logger.error("Failed to get network config: ", error.message); + logger.error(error.message); process.exit(1); } } - /** - * Apply options to config. - * @return {void} - */ - protected applyConfig() { - this.config = { ...config }; - - if (this.options.baseUrl) { - this.config.baseUrl = this.options.baseUrl.replace(/\/+$/, ""); - } - - if (this.options.apiPort) { - this.config.apiPort = this.options.apiPort; - } - - if (this.options.p2pPort && process.env.NODE_ENV !== "test") { - this.config.p2pPort = this.options.p2pPort; - } - - if (this.options.passphrase) { - this.config.passphrase = this.options.passphrase; - } + private async setupNetwork() { + try { + const { data } = await this.p2p.get("config"); - if (this.options.secondPassphrase) { - this.config.secondPassphrase = this.options.secondPassphrase; + this.network = data.network; + } catch (error) { + logger.error(error.message); + process.exit(1); } } - /** - * Quit command and output error when problem sending transactions. - * @param {Error} error - * @return {void} - */ - protected problemSendingTransactions(error) { - const message = error.response ? error.response.data.message : error.message; - logger.error(`There was a problem sending transactions: ${message}`); - process.exit(1); - } - - /** - * Determine how long to wait for transactions to process. - * @param {Object[]} transactions - * @return {Number} - */ - protected getTransactionDelaySeconds(transactions) { + private async awaitConfirmations(transactions): Promise { if (process.env.NODE_ENV === "test") { - return 0; + return; } - const waitPerBlock = Math.round(this.config.constants.blocktime / 10) * 20; - - return waitPerBlock * Math.ceil(transactions.length / this.config.constants.block.maxTransactions); - } - - protected castFlags(values: Record): string[] { - return Object.keys(BaseCommand.flags) - .filter(k => !["copy"].includes(k)) - .map((key: string) => { - const value = values[key]; - - if (value === undefined) { - return undefined; - } - - if (value === true) { - return `--${key}`; - } + const waitPerBlock = + this.constants.blocktime * Math.ceil(transactions.length / this.constants.block.maxTransactions); - return `--${key}=${value}`; - }) - .filter(value => value !== undefined); + await delay(waitPerBlock * 1000); } } diff --git a/packages/core-tester-cli/src/commands/debug/deserialize.ts b/packages/core-tester-cli/src/commands/debug/deserialize.ts new file mode 100644 index 0000000000..585a6b24bb --- /dev/null +++ b/packages/core-tester-cli/src/commands/debug/deserialize.ts @@ -0,0 +1,37 @@ +import { configManager, models, NetworkName, Transaction } from "@arkecosystem/crypto"; +import { flags } from "@oclif/command"; +import { handleOutput } from "../../utils"; +import { BaseCommand } from "../command"; + +export class DeserializeCommand extends BaseCommand { + public static description: string = "Deserialize the given HEX"; + + public static flags = { + ...BaseCommand.flagsDebug, + data: flags.string({ + description: "the HEX blob to deserialize", + required: true, + default: "transaction", + }), + type: flags.string({ + description: "transaction or block", + required: true, + }), + }; + + public async run(): Promise { + const { flags } = this.parse(DeserializeCommand); + + configManager.setFromPreset(flags.network as NetworkName); + + let output; + if (flags.type === "transaction") { + output = Transaction.fromHex(flags.data).data; + } else { + const block = new models.Block(flags.data); + output = { data: block.data, transactions: block.transactions.map(tx => tx.data) }; + } + + return handleOutput(flags, JSON.stringify(output, null, 4)); + } +} diff --git a/packages/core-debugger-cli/src/commands/identity.ts b/packages/core-tester-cli/src/commands/debug/identity.ts similarity index 67% rename from packages/core-debugger-cli/src/commands/identity.ts rename to packages/core-tester-cli/src/commands/debug/identity.ts index 0ed91c6ba3..f4e8573c08 100644 --- a/packages/core-debugger-cli/src/commands/identity.ts +++ b/packages/core-tester-cli/src/commands/debug/identity.ts @@ -1,22 +1,17 @@ -import { crypto } from "@arkecosystem/crypto"; +import { configManager, crypto, NetworkName } from "@arkecosystem/crypto"; import { flags } from "@oclif/command"; -import { handleOutput } from "../utils"; -import { BaseCommand } from "./command"; +import { handleOutput } from "../../utils"; +import { BaseCommand } from "../command"; export class IdentityCommand extends BaseCommand { public static description: string = "Get identities from the given input"; public static flags = { - ...BaseCommand.flags, + ...BaseCommand.flagsDebug, data: flags.string({ description: "the data to get the identities from", required: true, }), - network: flags.integer({ - description: "the network version used for calculating the address.", - required: true, - default: 30, - }), type: flags.string({ description: "the input type is either of passphrase, privateKey or publicKey", required: true, @@ -24,9 +19,10 @@ export class IdentityCommand extends BaseCommand { }; public async run(): Promise { - // tslint:disable-next-line:no-shadowed-variable const { flags } = this.parse(IdentityCommand); + configManager.setFromPreset(flags.network as NetworkName); + let output; if (flags.type === "passphrase") { @@ -35,19 +31,19 @@ export class IdentityCommand extends BaseCommand { passphrase: flags.data, publicKey: keys.publicKey, privateKey: keys.privateKey, - address: crypto.getAddress(keys.publicKey, flags.network), + address: crypto.getAddress(keys.publicKey), }; } else if (flags.type === "privateKey") { const keys = crypto.getKeysByPrivateKey(flags.data); output = { publicKey: keys.publicKey, privateKey: keys.privateKey, - address: crypto.getAddress(keys.publicKey, flags.network), + address: crypto.getAddress(keys.publicKey), }; } else if (flags.type === "publicKey") { output = { publicKey: flags.data, - address: crypto.getAddress(flags.data, flags.network), + address: crypto.getAddress(flags.data), }; } diff --git a/packages/core-debugger-cli/src/commands/serialize.ts b/packages/core-tester-cli/src/commands/debug/serialize.ts similarity index 66% rename from packages/core-debugger-cli/src/commands/serialize.ts rename to packages/core-tester-cli/src/commands/debug/serialize.ts index 61c06590b6..54c548431e 100644 --- a/packages/core-debugger-cli/src/commands/serialize.ts +++ b/packages/core-tester-cli/src/commands/debug/serialize.ts @@ -1,15 +1,15 @@ -import { models } from "@arkecosystem/crypto"; +import { configManager, models, NetworkName, Transaction } from "@arkecosystem/crypto"; import { flags } from "@oclif/command"; -import { handleOutput } from "../utils"; -import { BaseCommand } from "./command"; +import { handleOutput } from "../../utils"; +import { BaseCommand } from "../command"; export class SerializeCommand extends BaseCommand { public static description: string = "Serialize the given JSON"; public static flags = { - ...BaseCommand.flags, + ...BaseCommand.flagsDebug, data: flags.string({ - description: "the HEX blob to serialize", + description: "the JSON to serialize", required: true, }), type: flags.string({ @@ -23,12 +23,13 @@ export class SerializeCommand extends BaseCommand { }; public async run(): Promise { - // tslint:disable-next-line:no-shadowed-variable const { flags } = this.parse(SerializeCommand); - const serialized: any = + configManager.setFromPreset(flags.network as NetworkName); + + const serialized = flags.type === "transaction" - ? models.Transaction.serialize(JSON.parse(flags.data)) + ? Transaction.fromData(JSON.parse(flags.data)).serialized : models.Block[flags.full ? "serializeFull" : "serialize"](JSON.parse(flags.data)); return handleOutput(flags, serialized.toString("hex")); diff --git a/packages/core-debugger-cli/src/commands/verify-second.ts b/packages/core-tester-cli/src/commands/debug/verify-second-signature.ts similarity index 65% rename from packages/core-debugger-cli/src/commands/verify-second.ts rename to packages/core-tester-cli/src/commands/debug/verify-second-signature.ts index aeed1cadb1..520389658b 100644 --- a/packages/core-debugger-cli/src/commands/verify-second.ts +++ b/packages/core-tester-cli/src/commands/debug/verify-second-signature.ts @@ -1,13 +1,13 @@ -import { crypto, models } from "@arkecosystem/crypto"; +import { configManager, crypto, NetworkName, Transaction } from "@arkecosystem/crypto"; import { flags } from "@oclif/command"; -import { handleOutput } from "../utils"; -import { BaseCommand } from "./command"; +import { handleOutput } from "../../utils"; +import { BaseCommand } from "../command"; export class VerifySecondSignatureCommand extends BaseCommand { public static description: string = "Verify a second signature of a transaction"; public static flags = { - ...BaseCommand.flags, + ...BaseCommand.flagsDebug, data: flags.string({ description: "the HEX blob to deserialize and verify", required: true, @@ -19,11 +19,12 @@ export class VerifySecondSignatureCommand extends BaseCommand { }; public async run(): Promise { - // tslint:disable-next-line:no-shadowed-variable const { flags } = this.parse(VerifySecondSignatureCommand); - const transaction = new models.Transaction(flags.data); + configManager.setFromPreset(flags.network as NetworkName); - return handleOutput(flags, crypto.verifySecondSignature(transaction, flags.publicKey)); + const { data } = Transaction.fromHex(flags.data); + + return handleOutput(flags, crypto.verifySecondSignature(data, flags.publicKey)); } } diff --git a/packages/core-debugger-cli/src/commands/verify.ts b/packages/core-tester-cli/src/commands/debug/verify.ts similarity index 51% rename from packages/core-debugger-cli/src/commands/verify.ts rename to packages/core-tester-cli/src/commands/debug/verify.ts index 37deb12c65..c7a3bf3734 100644 --- a/packages/core-debugger-cli/src/commands/verify.ts +++ b/packages/core-tester-cli/src/commands/debug/verify.ts @@ -1,13 +1,13 @@ -import { models } from "@arkecosystem/crypto"; +import { configManager, models, NetworkName, Transaction } from "@arkecosystem/crypto"; import { flags } from "@oclif/command"; -import { handleOutput } from "../utils"; -import { BaseCommand } from "./command"; +import { handleOutput } from "../../utils"; +import { BaseCommand } from "../command"; export class VerifyCommand extends BaseCommand { public static description: string = "Verify the given HEX"; public static flags = { - ...BaseCommand.flags, + ...BaseCommand.flagsDebug, data: flags.string({ description: "the HEX blob to deserialize and verify", required: true, @@ -19,16 +19,16 @@ export class VerifyCommand extends BaseCommand { }; public async run(): Promise { - // tslint:disable-next-line:no-shadowed-variable const { flags } = this.parse(VerifyCommand); - const deserialized = - flags.type === "transaction" - ? new models.Transaction(flags.data) - : new models.Block(models.Block.deserialize(flags.data)); + configManager.setFromPreset(flags.network as NetworkName); - const output = - deserialized instanceof models.Transaction ? deserialized.verify() : deserialized.verification.verified; + let output = false; + if (flags.type === "transaction") { + output = Transaction.fromHex(flags.data).verified; + } else { + output = new models.Block(models.Block.deserialize(flags.data)).verification.verified; + } return handleOutput(flags, output); } diff --git a/packages/core-tester-cli/src/commands/delegate-registration.ts b/packages/core-tester-cli/src/commands/delegate-registration.ts deleted file mode 100644 index 7ccf5dc6ee..0000000000 --- a/packages/core-tester-cli/src/commands/delegate-registration.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { client } from "@arkecosystem/crypto"; -import { flags } from "@oclif/command"; -import pluralize from "pluralize"; -import superheroes from "superheroes"; -import { customFlags } from "../flags"; -import { logger, parseFee, satoshiToArk } from "../utils"; -import { BaseCommand } from "./command"; -import { TransferCommand } from "./transfer"; - -export class DelegateRegistrationCommand extends BaseCommand { - public static description: string = "create multiple delegates"; - - public static flags = { - ...BaseCommand.flags, - delegateFee: customFlags.number({ - description: "delegate registration fee", - default: 25, - }), - }; - - /** - * Run delegate-registration command. - * @return {void} - */ - public async run(): Promise { - // tslint:disable-next-line: no-shadowed-variable - const { flags } = await this.initialize(DelegateRegistrationCommand); - - const wallets = this.generateWallets(); - - for (const wallet of wallets) { - await TransferCommand.run( - ["--amount", String(this.options.amount || 25), "--recipient", wallet.address, "--skipTesting"].concat( - this.castFlags(flags), - ), - ); - } - - const delegates = await this.getDelegates(); - - logger.error( - `Sending ${this.options.number} delegate registration ${pluralize("transaction", this.options.number)}`, - ); - - if (!this.options.skipValidation) { - logger.error(`Starting delegate count: ${delegates.length}`); - } - - const transactions = []; - const usedDelegateNames = delegates.map(delegate => delegate.username); - - wallets.forEach((wallet, i) => { - while (!wallet.username || usedDelegateNames.includes(wallet.username)) { - wallet.username = superheroes.random(); - } - - wallet.username = wallet.username.toLowerCase().replace(/ /g, "_"); - usedDelegateNames.push(wallet.username); - - const transaction = client - .getBuilder() - .delegateRegistration() - .fee(parseFee(this.options.delegateFee)) - .usernameAsset(wallet.username) - .network(this.config.network.version) - .sign(wallet.passphrase) - .secondSign(this.config.secondPassphrase) - .build(); - - transactions.push(transaction); - - logger.info( - `${i} ==> ${transaction.id}, ${wallet.address} (fee: ${satoshiToArk(transaction.fee)}, username: ${ - wallet.username - })`, - ); - }); - - if (this.options.copy) { - this.copyToClipboard(transactions); - return; - } - - const expectedDelegates = delegates.length + wallets.length; - if (!this.options.skipValidation) { - logger.info(`Expected end delegate count: ${expectedDelegates}`); - } - - try { - await this.sendTransactions(transactions, "delegate", !this.options.skipValidation); - - if (this.options.skipValidation) { - return; - } - - const targetDelegates = await this.getDelegates(); - logger.info(`All transactions have been sent! Total delegates: ${targetDelegates.length}`); - - if (targetDelegates.length !== expectedDelegates) { - logger.error( - `Delegate count incorrect. '${targetDelegates.length}' but should be '${expectedDelegates}'`, - ); - } - } catch (error) { - logger.error( - `There was a problem sending transactions: ${error.response ? error.response.data.message : error}`, - ); - } - } -} diff --git a/packages/core-tester-cli/src/commands/make/block.ts b/packages/core-tester-cli/src/commands/make/block.ts new file mode 100644 index 0000000000..8bb44b7a29 --- /dev/null +++ b/packages/core-tester-cli/src/commands/make/block.ts @@ -0,0 +1,105 @@ +import { configManager, models, slots } from "@arkecosystem/crypto"; +import { flags } from "@oclif/command"; +import { writeFileSync } from "fs"; +import { satoshiFlag } from "../../flags"; +import { copyToClipboard } from "../../utils"; +import { BaseCommand } from "../command"; + +export class BlockCommand extends BaseCommand { + public static description: string = "create new blocks"; + + public static flags = { + ...BaseCommand.flagsConfig, + ...BaseCommand.flagsDebug, + number: flags.integer({ + description: "number of blocks to generate", + default: 1, + }), + transactions: flags.integer({ + description: "number of transactions to generate", + default: 0, + }), + transactionAmount: satoshiFlag({ + description: "initial wallet token amount", + default: 2, + }), + transactionFee: satoshiFlag({ + description: "transfer fee", + default: 0.1, + }), + passphrase: flags.string({ + description: "passphrase of the forger", + default: "clay harbor enemy utility margin pretty hub comic piece aerobic umbrella acquire", + }), + previousBlock: flags.string({ + description: `Previous block to base the generated block(s) on. For example: '{ "height": 50, "id": "123", "idHex": "7b" }'`, + }), + write: flags.boolean({ + description: "write the blocks to the disk", + }), + log: flags.boolean({ + description: "log the data to the console", + default: true, + }), + }; + + public async run(): Promise { + const { flags } = this.makeOffline(BlockCommand); + + const genesisBlock = configManager.get("genesisBlock"); + const genesisWallets = genesisBlock.transactions.map(t => t.recipientId).filter(a => !!a); + + let previousBlock = flags.previousBlock ? JSON.parse(flags.previousBlock) : genesisBlock; + + const blocks: models.IBlockData[] = []; + + for (let i = 0; i < flags.number; i++) { + const milestone = configManager.getMilestone(previousBlock.height); + const delegate = new models.Delegate(flags.passphrase, configManager.get("pubKeyHash")); + + const transactions = []; + for (let i = 0; i < flags.transactions; i++) { + transactions.push( + this.signer.makeTransfer({ + ...flags, + ...{ + amount: flags.transactionAmount + i, + transferFee: flags.transactionFee, + recipient: genesisWallets[Math.floor(Math.random() * genesisWallets.length)], + }, + }), + ); + } + + const newBlock = await delegate.forge(transactions, { + previousBlock, + timestamp: slots.getSlotNumber(slots.getTime()) * milestone.blocktime, + reward: milestone.reward, + }); + + const blockPayload = newBlock.data; + blockPayload.transactions = newBlock.transactions.map(tx => ({ + ...tx.toJson(), + serialized: tx.serialized.toString("hex"), + })); + blockPayload.serialized = newBlock.serialized; + previousBlock = blockPayload; + + blocks.push(blockPayload); + } + + if (flags.log) { + console.log(JSON.stringify(blocks, null, 4)); + } + + if (flags.copy) { + copyToClipboard(JSON.stringify(blocks, null, 4)); + } + + if (flags.write) { + writeFileSync("./blocks.json", JSON.stringify(blocks)); + } + + return blocks; + } +} diff --git a/packages/core-tester-cli/src/commands/make/wallets.ts b/packages/core-tester-cli/src/commands/make/wallets.ts new file mode 100644 index 0000000000..13a65d5127 --- /dev/null +++ b/packages/core-tester-cli/src/commands/make/wallets.ts @@ -0,0 +1,46 @@ +import { crypto } from "@arkecosystem/crypto"; +import { flags } from "@oclif/command"; +import { generateMnemonic } from "bip39"; +import { writeFileSync } from "fs"; +import { copyToClipboard } from "../../utils"; +import { BaseCommand } from "../command"; + +export class WalletCommand extends BaseCommand { + public static description: string = "create new wallets"; + + public static flags = { + ...BaseCommand.flagsConfig, + quantity: flags.integer({ + description: "number of wallets to generate", + }), + copy: flags.boolean({ + description: "write the wallets to the clipboard", + }), + write: flags.boolean({ + description: "write the wallets to the disk", + }), + }; + + public async run(): Promise> { + const { flags } = await this.make(WalletCommand); + + const wallets = {}; + for (let i = 0; i < flags.quantity; i++) { + const passphrase = generateMnemonic(); + const keys = crypto.getKeys(passphrase); + const address = crypto.getAddress(keys.publicKey, this.network.version); + + wallets[address] = { address, keys, passphrase }; + } + + if (flags.copy) { + copyToClipboard(JSON.stringify(wallets)); + } + + if (flags.write) { + writeFileSync("./wallets.json", JSON.stringify(wallets)); + } + + return wallets; + } +} diff --git a/packages/core-tester-cli/src/commands/multi-signature.ts b/packages/core-tester-cli/src/commands/multi-signature.ts deleted file mode 100644 index adb4b97012..0000000000 --- a/packages/core-tester-cli/src/commands/multi-signature.ts +++ /dev/null @@ -1,352 +0,0 @@ -import { client } from "@arkecosystem/crypto"; -import { flags } from "@oclif/command"; -import take from "lodash/take"; -import pluralize from "pluralize"; -import { customFlags } from "../flags"; -import { arkToSatoshi, generateTransactions, logger, parseFee, satoshiToArk } from "../utils"; -import { BaseCommand } from "./command"; -import { TransferCommand } from "./transfer"; - -export class MultiSignatureCommand extends BaseCommand { - public static description: string = "create multiple multisig wallets"; - - public static flags = { - ...BaseCommand.flags, - multisigFee: customFlags.number({ - description: "multisig fee", - default: 5, - }), - min: flags.integer({ - description: "minimum number of signatures per transaction", - default: 2, - }), - lifetime: flags.integer({ - description: "lifetime of transaction", - default: 72, - }), - quantity: flags.integer({ - description: "number of signatures per wallet", - default: 3, - }), - skipTests: flags.boolean({ - description: "skip transaction tests", - }), - }; - - /** - * Run multi-signature command. - * @return {void} - */ - public async run(): Promise { - // tslint:disable-next-line: no-shadowed-variable - const { flags } = await this.initialize(MultiSignatureCommand); - - const approvalWallets = this.generateWallets(this.options.quantity); - const publicKeys = approvalWallets.map(wallet => `+${wallet.keys.publicKey}`); - const min = this.options.min ? Math.min(this.options.min, publicKeys.length) : publicKeys.length; - - const testCosts = this.options.skipTests ? 1 : 2; - const wallets = this.generateWallets(); - - for (const wallet of wallets) { - await TransferCommand.run( - [ - "--recipient", - wallet.address, - "--amount", - (publicKeys.length + 1) * 5 + testCosts, - "--skipTesting", - ].concat(this.castFlags(flags)), - ); - } - - const transactions = this.generateTransactions(wallets, approvalWallets, publicKeys, min); - - if (this.options.copy) { - this.copyToClipboard(transactions); - - return; - } - - try { - const response = await this.sendTransactions(transactions, "multi-signature", !this.options.skipValidation); - - if (!this.options.skipValidation) { - let hasUnprocessed = false; - for (const transaction of transactions) { - if (!response.accept.includes(transaction.id)) { - hasUnprocessed = true; - logger.error(`Multi-signature transaction '${transaction.id}' was not processed`); - } - } - if (hasUnprocessed) { - process.exit(1); - } - - for (const transaction of transactions) { - const tx = await this.getTransaction(transaction.id); - if (!tx) { - logger.error(`Transaction '${transaction.id}' should be on the blockchain`); - } - } - } - } catch (error) { - const message = error.response ? error.response.data.message : error.message; - logger.error(`There was a problem sending multi-signature transactions: ${message}`); - process.exit(1); - } - - if (this.options.skipTests || this.options.skipValidation) { - return; - } - - await this.testSendWithSignatures(wallets, approvalWallets); - await this.testSendWithMinSignatures(wallets, approvalWallets, min); - await this.testSendWithBelowMinSignatures(wallets, approvalWallets, min); - await this.testSendWithoutSignatures(wallets); - await this.testSendWithEmptySignatures(wallets); - await this.testNewMultiSignatureRegistration(wallets, approvalWallets, publicKeys, min); - } - - /** - * Generate batch of transactions based on wallets - * @param {Object[]} wallets - * @param {Object[]} [approvalWallets=[]] - * @param {String[]} [publicKeys=[]] - * @param {Number} [min=2] - * @param {Boolean} [log=true] - * @return {Object[]} - */ - public generateTransactions(wallets, approvalWallets = [], publicKeys = [], min = 2, log = true) { - const transactions = []; - wallets.forEach((wallet, i) => { - const builder = client.getBuilder().multiSignature(); - - builder - .fee(parseFee(this.options.multisigFee)) - .multiSignatureAsset({ - lifetime: this.options.lifetime, - keysgroup: publicKeys, - min, - }) - .network(this.config.network.version) - .sign(wallet.passphrase); - - if (wallet.secondPassphrase || this.config.secondPassphrase) { - builder.secondSign(wallet.secondPassphrase || this.config.secondPassphrase); - } - - if (approvalWallets) { - for (let j = approvalWallets.length - 1; j >= 0; j--) { - builder.multiSignatureSign(approvalWallets[j].passphrase); - } - } - - const transaction = builder.build(); - transactions.push(transaction); - - if (log) { - logger.info(`${i} ==> ${transaction.id}, ${wallet.address} (fee: ${satoshiToArk(transaction.fee)})`); - } - }); - - return transactions; - } - - /** - * Send transactions with approver signatures. - * @param {Object[]} wallets - * @param {Object[]} [approvalWallets=[]] - * @return {void} - */ - public async testSendWithSignatures(wallets, approvalWallets = []) { - logger.info("Sending transactions with signatures"); - - const transactions = generateTransactions(arkToSatoshi(2), wallets, approvalWallets, { - config: this.config, - ...this.options, - }); - - try { - await this.sendTransactions(transactions); - for (const transaction of transactions) { - const tx = await this.getTransaction(transaction.id); - if (!tx) { - logger.error(`Transaction '${transaction.id}' should be on the blockchain`); - } - } - } catch (error) { - this.problemSendingTransactions(error); - } - } - - /** - * Send transactions with min approver signatures. - * @param {Object[]} wallets - * @param {Object[]} [approvalWallets=[]] - * @param {Number} [min=2] - * @return {void} - */ - public async testSendWithMinSignatures(wallets, approvalWallets = [], min = 2) { - logger.info( - `Sending transactions with ${min} (min) of ${pluralize("signature", approvalWallets.length, true)}`, - ); - - const transactions = generateTransactions(arkToSatoshi(2), wallets, take(approvalWallets, min), { - config: this.config, - ...this.options, - }); - - try { - await this.sendTransactions(transactions); - for (const transaction of transactions) { - const tx = await this.getTransaction(transaction.id); - if (!tx) { - logger.error(`Transaction '${transaction.id}' should be on the blockchain`); - } - } - } catch (error) { - this.problemSendingTransactions(error); - } - } - - /** - * Send transactions with below min approver signatures. - * @param {Object[]} wallets - * @param {Object[]} [approvalWallets=[]] - * @param {Number} [min=2] - * @return {void} - */ - public async testSendWithBelowMinSignatures(wallets, approvalWallets = [], min = 2) { - const max = min - 1; - logger.info( - `Sending transactions with ${max} (below min) of ${pluralize("signature", approvalWallets.length, true)}`, - ); - - const transactions = generateTransactions(arkToSatoshi(2), wallets, take(approvalWallets, max), { - config: this.config, - ...this.options, - }); - - try { - await this.sendTransactions(transactions); - for (const transaction of transactions) { - try { - const tx = await this.getTransaction(transaction.id); - if (tx) { - logger.error(`Transaction '${transaction.id}' should not be on the blockchain`); - } - } catch (error) { - const message = error.response ? error.response.data.message : error.message; - if (message !== "Transaction not found") { - logger.error(`Failed to check transaction '${transaction.id}': ${message}`); - } - } - } - } catch (error) { - this.problemSendingTransactions(error); - } - } - - /** - * Send transactions without approver signatures. - * @param {Object[]} wallets - * @return {void} - */ - public async testSendWithoutSignatures(wallets) { - logger.info("Sending transactions without signatures"); - - const transactions = generateTransactions(arkToSatoshi(2), wallets, [], { - config: this.config, - ...this.options, - }); - - try { - await this.sendTransactions(transactions); - for (const transaction of transactions) { - try { - const tx = await this.getTransaction(transaction.id); - if (tx) { - logger.error(`Transaction '${transaction.id}' should not be on the blockchain`); - } - } catch (error) { - const message = error.response ? error.response.data.message : error.message; - if (message !== "Transaction not found") { - logger.error(`Failed to check transaction '${transaction.id}': ${message}`); - } - } - } - } catch (error) { - this.problemSendingTransactions(error); - } - } - - /** - * Send transactions with empty approver signatures. - * @param {Object[]} wallets - * @return {void} - */ - public async testSendWithEmptySignatures(wallets) { - logger.info("Sending transactions with empty signatures"); - - const transactions = generateTransactions(arkToSatoshi(2), wallets, [], { - config: this.config, - ...this.options, - }); - for (const transaction of transactions) { - transaction.data.signatures = []; - } - - try { - await this.sendTransactions(transactions); - for (const transaction of transactions) { - try { - const tx = await this.getTransaction(transaction.id); - if (tx) { - logger.error(`Transaction '${transaction.id}' should not be on the blockchain`); - } - } catch (error) { - const message = error.response ? error.response.data.message : error.message; - if (message !== "Transaction not found") { - logger.error(`Failed to check transaction '${transaction.id}': ${message}`); - } - } - } - } catch (error) { - this.problemSendingTransactions(error); - } - } - - /** - * Send transactions to re-register multi-signature wallets. - * @param {Object[]} wallets - * @param {Object[]} [approvalWallets=[]] - * @param {Object[]} [publicKeys=[]] - * @param {Number} [min=2] - * @return {void} - */ - public async testNewMultiSignatureRegistration(wallets, approvalWallets = [], publicKeys = [], min = 2) { - logger.info("Sending transactions to re-register multi-signature"); - - const transactions = this.generateTransactions(wallets, approvalWallets, publicKeys, min); - - try { - await this.sendTransactions(transactions); - for (const transaction of transactions) { - try { - const tx = await this.getTransaction(transaction.id); - if (tx) { - logger.error(`Transaction '${transaction.id}' should not be on the blockchain`); - } - } catch (error) { - const message = error.response ? error.response.data.message : error.message; - if (message !== "Transaction not found") { - logger.error(`Failed to check transaction '${transaction.id}': ${message}`); - } - } - } - } catch (error) { - this.problemSendingTransactions(error); - } - } -} diff --git a/packages/core-tester-cli/src/commands/second-signature.ts b/packages/core-tester-cli/src/commands/second-signature.ts deleted file mode 100644 index 5cc3325a9d..0000000000 --- a/packages/core-tester-cli/src/commands/second-signature.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { client } from "@arkecosystem/crypto"; -import { flags } from "@oclif/command"; -import pluralize from "pluralize"; -import { customFlags } from "../flags"; -import { logger, parseFee, satoshiToArk } from "../utils"; -import { BaseCommand } from "./command"; -import { TransferCommand } from "./transfer"; - -export class SecondSignatureCommand extends BaseCommand { - public static description: string = "create wallets with second signature"; - - public static flags = { - ...BaseCommand.flags, - signatureFee: customFlags.number({ - description: "second signature fee", - default: 5, - }), - }; - - /** - * Run second-signature command. - * @return {void} - */ - public async run(): Promise { - // tslint:disable-next-line: no-shadowed-variable - const { flags } = await this.initialize(SecondSignatureCommand); - - const wallets = this.generateWallets(); - - for (const wallet of wallets) { - await TransferCommand.run( - ["--recipient", wallet.address, "--amount", String(this.options.amount || 5), "--skipTesting"].concat( - this.castFlags(flags), - ), - ); - } - - logger.info(`Sending ${this.options.number} second signature ${pluralize("transaction", this.options.number)}`); - - const transactions = []; - wallets.forEach((wallet, i) => { - wallet.secondPassphrase = this.config.secondPassphrase || wallet.passphrase; - const transaction = client - .getBuilder() - .secondSignature() - .fee(parseFee(this.options.signatureFee)) - .signatureAsset(wallet.secondPassphrase) - .network(this.config.network.version) - .sign(wallet.passphrase) - .build(); - - wallet.publicKey = transaction.senderPublicKey; - wallet.secondPublicKey = transaction.asset.signature.publicKey; - transactions.push(transaction); - - logger.info(`${i} ==> ${transaction.id}, ${wallet.address} (fee: ${satoshiToArk(transaction.fee)})`); - }); - - if (this.options.copy) { - this.copyToClipboard(transactions); - return; - } - - try { - await this.sendTransactions(transactions, "second-signature", !this.options.skipValidation); - - if (this.options.skipValidation) { - return; - } - - for (const walletObject of wallets) { - const wallet = await this.getWallet(walletObject.address); - - if ( - wallet.secondPublicKey !== walletObject.secondPublicKey || - wallet.publicKey !== walletObject.publicKey - ) { - logger.error(`Invalid second signature for ${walletObject.address}.`); - } - } - } catch (error) { - logger.error( - `There was a problem sending transactions: ${error.response ? error.response.data.message : error}`, - ); - } - } -} diff --git a/packages/core-tester-cli/src/commands/send/delegate-registration.ts b/packages/core-tester-cli/src/commands/send/delegate-registration.ts new file mode 100644 index 0000000000..af3f8f0f5e --- /dev/null +++ b/packages/core-tester-cli/src/commands/send/delegate-registration.ts @@ -0,0 +1,86 @@ +import { Address } from "@arkecosystem/crypto"; +import pokemon from "pokemon"; +import { satoshiFlag } from "../../flags"; +import { logger } from "../../logger"; +import { SendCommand } from "../../shared/send"; +import { TransferCommand } from "./transfer"; + +export class DelegateRegistrationCommand extends SendCommand { + public static description: string = "create multiple delegates"; + + public static flags = { + ...SendCommand.flagsSend, + delegateFee: satoshiFlag({ + description: "delegate registration fee", + default: 25, + }), + }; + + protected getCommand(): any { + return DelegateRegistrationCommand; + } + + protected async createWalletsWithBalance(flags: Record): Promise { + return TransferCommand.run( + [`--amount=${flags.delegateFee}`, `--number=${flags.number}`, "--skipProbing"].concat( + this.castFlags(flags), + ), + ); + } + + protected async signTransactions(flags: Record, wallets: Record): Promise { + const transactions = []; + + for (const [address, wallet] of Object.entries(wallets)) { + wallets[address].username = pokemon + .random() + .toLowerCase() + .replace(/ /g, "_"); + + transactions.push( + this.signer.makeDelegate({ + ...flags, + ...{ + username: wallets[address].username, + // @ts-ignore + passphrase: wallet.passphrase, + }, + }), + ); + } + + return transactions; + } + + protected async expectBalances(transactions, wallets): Promise { + for (const transaction of transactions) { + const recipientId = Address.fromPublicKey(transaction.senderPublicKey, this.network.version); + + const currentBalance = await this.getWalletBalance(recipientId); + wallets[recipientId].expectedBalance = currentBalance.minus(transaction.fee); + } + } + + protected async verifyTransactions(transactions, wallets): Promise { + for (const transaction of transactions) { + const wasCreated = await this.knockTransaction(transaction.id); + + if (wasCreated) { + const recipientId = Address.fromPublicKey(transaction.senderPublicKey, this.network.version); + + await this.knockBalance(recipientId, wallets[recipientId].expectedBalance); + await this.knockUsername(recipientId, wallets[recipientId].username); + } + } + } + + private async knockUsername(address: string, expected: string): Promise { + const { username: actual } = (await this.api.get(`wallets/${address}`)).data; + + if (actual === expected) { + logger.info(`[W] ${address} (${actual})`); + } else { + logger.error(`[W] ${address} (${expected} / ${actual})`); + } + } +} diff --git a/packages/core-tester-cli/src/commands/send/second-signature-registration.ts b/packages/core-tester-cli/src/commands/send/second-signature-registration.ts new file mode 100644 index 0000000000..cd0adcd96d --- /dev/null +++ b/packages/core-tester-cli/src/commands/send/second-signature-registration.ts @@ -0,0 +1,82 @@ +import { Address } from "@arkecosystem/crypto"; +import { satoshiFlag } from "../../flags"; +import { logger } from "../../logger"; +import { SendCommand } from "../../shared/send"; +import { TransferCommand } from "./transfer"; + +export class SecondSignatureRegistrationCommand extends SendCommand { + public static description: string = "create wallets with second signature"; + + public static flags = { + ...SendCommand.flagsSend, + signatureFee: satoshiFlag({ + description: "second signature fee", + default: 5, + }), + }; + + protected getCommand(): any { + return SecondSignatureRegistrationCommand; + } + + protected async createWalletsWithBalance(flags: Record): Promise { + return TransferCommand.run( + [`--amount=${flags.signatureFee}`, `--number=${flags.number}`, "--skipProbing"].concat( + this.castFlags(flags), + ), + ); + } + + protected async signTransactions(flags: Record, wallets: Record): Promise { + const transactions = []; + + for (const [address, wallet] of Object.entries(wallets)) { + const transaction = this.signer.makeSecondSignature({ + ...flags, + ...{ + passphrase: wallet.passphrase, + secondPassphrase: wallet.passphrase, + }, + }); + + wallets[address].publicKey = transaction.senderPublicKey; + wallets[address].secondPublicKey = transaction.asset.signature.publicKey; + + transactions.push(transaction); + } + + return transactions; + } + + protected async expectBalances(transactions, wallets): Promise { + for (const transaction of transactions) { + const recipientId = Address.fromPublicKey(transaction.senderPublicKey, this.network.version); + + const currentBalance = await this.getWalletBalance(recipientId); + wallets[recipientId].expectedBalance = currentBalance.minus(transaction.fee); + } + } + + protected async verifyTransactions(transactions, wallets): Promise { + for (const transaction of transactions) { + const wasCreated = await this.knockTransaction(transaction.id); + + if (wasCreated) { + const recipientId = Address.fromPublicKey(transaction.senderPublicKey, this.network.version); + + await this.knockBalance(recipientId, wallets[recipientId].expectedBalance); + await this.knockSignature(recipientId, wallets[recipientId].secondPublicKey); + } + } + } + + private async knockSignature(address: string, expected: string): Promise { + const { secondPublicKey: actual } = (await this.api.get(`wallets/${address}`)).data; + + if (actual === expected) { + logger.info(`[W] ${address} (${actual})`); + } else { + logger.error(`[W] ${address} (${expected} / ${actual})`); + } + } +} diff --git a/packages/core-tester-cli/src/commands/send/transfer.ts b/packages/core-tester-cli/src/commands/send/transfer.ts new file mode 100644 index 0000000000..9e9be3fe32 --- /dev/null +++ b/packages/core-tester-cli/src/commands/send/transfer.ts @@ -0,0 +1,76 @@ +import { flags } from "@oclif/command"; +import { delay } from "bluebird"; +import { logger } from "../../logger"; +import { SendCommand } from "../../shared/send"; +import { WalletCommand } from "../make/wallets"; + +export class TransferCommand extends SendCommand { + public static description: string = "send multiple transactions"; + + public static flags = { + ...SendCommand.flagsSend, + recipient: flags.string({ + description: "recipient address", + }), + vendorField: flags.string({ + description: "vendor field to use", + }), + }; + + protected getCommand(): any { + return TransferCommand; + } + + protected async createWalletsWithBalance(flags: Record): Promise { + return WalletCommand.run([`--quantity=${flags.number}`].concat(this.castFlags(flags))); + } + + protected async signTransactions(flags: Record, wallets: Record): Promise { + const transactions = []; + + for (let i = 0; i < flags.number; i++) { + const vendorField = flags.vendorField || `Transaction ${i}`; + + if (flags.recipient) { + transactions.push( + this.signer.makeTransfer({ ...flags, ...{ recipient: flags.recipient, vendorField } }), + ); + } else { + for (const wallet of Object.keys(wallets)) { + transactions.push(this.signer.makeTransfer({ ...flags, ...{ recipient: wallet, vendorField } })); + } + } + } + + return transactions; + } + + protected async expectBalances(transactions, wallets): Promise { + for (const transaction of transactions) { + const currentBalance = await this.getWalletBalance(transaction.recipientId); + wallets[transaction.recipientId].expectedBalance = currentBalance.plus(transaction.amount); + } + } + + protected async verifyTransactions(transactions, wallets): Promise { + for (const transaction of transactions) { + const response = await this.getTransaction(transaction.id); + + if (!response) { + logger.error(`[T] ${transaction.id} (not forged)`); + + continue; + } + + logger.info(`[T] ${transaction.id} (${response.blockId})`); + + await this.knockBalance(transaction.recipientId, wallets[transaction.recipientId].expectedBalance); + + if (transaction.vendorField === response.vendorField) { + logger.info(`[T] ${transaction.id} (${transaction.vendorField})`); + } else { + logger.error(`[T] ${transaction.id} (${transaction.vendorField} / ${response.vendorField})`); + } + } + } +} diff --git a/packages/core-tester-cli/src/commands/send/vote.ts b/packages/core-tester-cli/src/commands/send/vote.ts new file mode 100644 index 0000000000..1dcaad6463 --- /dev/null +++ b/packages/core-tester-cli/src/commands/send/vote.ts @@ -0,0 +1,91 @@ +import { Address } from "@arkecosystem/crypto"; +import { flags } from "@oclif/command"; +import { satoshiFlag } from "../../flags"; +import { logger } from "../../logger"; +import { SendCommand } from "../../shared/send"; +import { TransferCommand } from "./transfer"; + +export class VoteCommand extends SendCommand { + public static description: string = "create multiple votes for a delegate"; + + public static flags = { + ...SendCommand.flagsSend, + delegate: flags.string({ + description: "delegate public key", + }), + voteFee: satoshiFlag({ + description: "vote fee", + default: 1, + }), + }; + + protected getCommand(): any { + return VoteCommand; + } + + protected async createWalletsWithBalance(flags: Record): Promise { + return TransferCommand.run( + [`--amount=${flags.voteFee}`, `--number=${flags.number}`, "--skipProbing"].concat(this.castFlags(flags)), + ); + } + + protected async signTransactions(flags: Record, wallets: Record): Promise { + const transactions = []; + + for (const [address, wallet] of Object.entries(wallets)) { + const delegate = flags.delegate || (await this.getRandomDelegate()); + + const transaction = this.signer.makeVote({ + ...flags, + ...{ + delegate, + passphrase: wallet.passphrase, + }, + }); + + wallets[address].vote = delegate; + + transactions.push(transaction); + } + + return transactions; + } + + protected async expectBalances(transactions, wallets): Promise { + for (const transaction of transactions) { + const recipientId = Address.fromPublicKey(transaction.senderPublicKey, this.network.version); + + const currentBalance = await this.getWalletBalance(recipientId); + wallets[recipientId].expectedBalance = currentBalance.minus(transaction.fee); + } + } + + protected async verifyTransactions(transactions, wallets): Promise { + for (const transaction of transactions) { + const wasCreated = await this.knockTransaction(transaction.id); + + if (wasCreated) { + const recipientId = Address.fromPublicKey(transaction.senderPublicKey, this.network.version); + + await this.knockBalance(recipientId, wallets[recipientId].expectedBalance); + await this.knockVote(recipientId, wallets[recipientId].vote); + } + } + } + + private async knockVote(address: string, expected: string): Promise { + const { vote: actual } = (await this.api.get(`wallets/${address}`)).data; + + if (actual === expected) { + logger.info(`[W] ${address} (${actual})`); + } else { + logger.error(`[W] ${address} (${expected} / ${actual})`); + } + } + + private async getRandomDelegate() { + const { data } = await this.api.get("delegates"); + + return data[0].publicKey; + } +} diff --git a/packages/core-tester-cli/src/commands/transfer.ts b/packages/core-tester-cli/src/commands/transfer.ts deleted file mode 100644 index cd1a07489e..0000000000 --- a/packages/core-tester-cli/src/commands/transfer.ts +++ /dev/null @@ -1,315 +0,0 @@ -import { Bignum, crypto } from "@arkecosystem/crypto"; -import { flags } from "@oclif/command"; -import delay from "delay"; -import unique from "lodash/uniq"; -import pluralize from "pluralize"; -import { arkToSatoshi, generateTransactions, logger, satoshiToArk } from "../utils"; -import { BaseCommand } from "./command"; - -export class TransferCommand extends BaseCommand { - public static description: string = "send multiple transactions"; - - public static flags = { - ...BaseCommand.flags, - recipient: flags.string({ - description: "recipient address", - }), - floodAttempts: flags.integer({ - description: "flood node with same transactions", - default: 0, - }), - skipSecondRun: flags.string({ - description: "skip second sending of transactions", - }), - smartBridge: flags.string({ - description: "smart-bridge value to use", - }), - }; - - /** - * Run transfer command. - * @param {Object} options - * @return {void} - */ - public async run(): Promise { - await this.initialize(TransferCommand); - - const primaryAddress = crypto.getAddress( - crypto.getKeys(this.config.passphrase).publicKey, - this.config.network.version, - ); - - let wallets = this.options.wallets; - if (wallets === undefined) { - wallets = this.generateWallets(); - } - - logger.info(`Sending ${wallets.length} transfer ${pluralize("transaction", wallets.length)}`); - - const walletBalance = await this.getWalletBalance(primaryAddress); - - if (!this.options.skipValidation) { - logger.info(`Sender starting balance: ${satoshiToArk(walletBalance)}`); - } - - let totalDeductions = Bignum.ZERO; - const transactionAmount = arkToSatoshi(this.options.amount || 2); - - const transactions = this.generateTransactions(transactionAmount, wallets, null, true); - for (const transaction of transactions) { - totalDeductions = totalDeductions.plus(transactionAmount).plus(transaction.fee); - } - - if (this.options.copy) { - this.copyToClipboard(transactions); - return; - } - - const expectedSenderBalance = new Bignum(walletBalance).minus(totalDeductions); - if (!this.options.skipValidation) { - logger.info(`Sender expected ending balance: ${satoshiToArk(expectedSenderBalance)}`); - } - - const runOptions = { - primaryAddress, - transactions, - wallets, - transactionAmount, - expectedSenderBalance, - skipValidation: this.options.skipValidation, - }; - - try { - if (!this.options.floodAttempts) { - const successfulTest = await this.performRun(runOptions, 1); - if ( - successfulTest && - !this.options.skipSecondRun && - !this.options.skipValidation && - !this.options.skipTesting - ) { - await this.performRun(runOptions, 2, false, true); - } - } else { - const attempts = this.options.floodAttempts; - for (let i = attempts; i > 0; i--) { - await this.performRun(runOptions, attempts - i + 1, i !== 1, i !== attempts); - } - } - } catch (error) { - const message = error.response ? error.response.data.message : error; - logger.error(`There was a problem sending transactions: ${message}`); - } - - if (this.options.skipValidation) { - return; - } - - await this.testVendorField(wallets); - await this.testEmptyVendorField(wallets); - - return; - } - - /** - * Generate batch of transactions based on wallets. - * @param {Bignum} transactionAmount - * @param {Object[]} wallets - * @param {Object[]} [approvalWallets=[]] - * @param {Boolean} [overridePassphrase=false] - * @param {String} [vendorField] - * @param {Boolean} [log=true] - * @return {Object[]} - */ - public generateTransactions( - transactionAmount, - wallets, - approvalWallets = [], - overridePassphrase = false, - vendorField = null, - log = true, - ) { - return generateTransactions(transactionAmount, wallets, approvalWallets, { - ...this.options, - config: this.config, - overridePassphrase, - vendorField: vendorField || this.options.smartBridge, - log, - }); - } - - /** - * Perform a run of transactions. - * @param {Object} runOptions - * @param {Number} [runNumber=1] - * @param {Boolean} [skipWait=false] - * @param {Boolean} [isSubsequentRun=false] - * @return {Boolean} - */ - public async performRun(runOptions, runNumber = 1, skipWait = false, isSubsequentRun = false) { - if (skipWait) { - runOptions.skipValidation = true; - this.sendTransactionsWithResults(runOptions, isSubsequentRun); - - return true; - } - - if (await this.sendTransactionsWithResults(runOptions, isSubsequentRun)) { - logger.info(`All transactions have been received and forged for run ${runNumber}!`); - - return true; - } - - logger.error(`Test failed on run ${runNumber}`); - - return false; - } - - /** - * Send transactions and validate results. - * @param {Object} runOptions - * @param {Boolean} isSubsequentRun - * @return {Boolean} - */ - public async sendTransactionsWithResults(runOptions, isSubsequentRun) { - let successfulTest = true; - - let postResponse; - try { - postResponse = await this.postTransactions(runOptions.transactions); - } catch (error) { - if (runOptions.skipValidation) { - return true; - } - - const message = error.response ? error.response.data.error : error.message; - logger.error(`Transaction request failed: ${message}`); - - return false; - } - - if (runOptions.skipValidation) { - return true; - } - - if (!isSubsequentRun && (!postResponse.accept || !postResponse.accept.length)) { - return false; - } - - if (!isSubsequentRun) { - for (const transaction of runOptions.transactions) { - if (!postResponse.accept.includes(transaction.id)) { - logger.error(`Transaction '${transaction.id}' didn't get approved on the network`); - - successfulTest = false; - } - } - } - - for (const key of Object.keys(postResponse)) { - if (key === "success") { - continue; - } - - const dataLength = postResponse[key].length; - const uniqueLength = unique(postResponse[key]).length; - if (dataLength !== uniqueLength) { - logger.error(`Response data for '${key}' has ${dataLength - uniqueLength} duplicate transaction ids`); - successfulTest = false; - } - } - - const delaySeconds = this.getTransactionDelaySeconds(runOptions.transactions); - logger.info(`Waiting ${delaySeconds} seconds to apply transfer transactions`); - await delay(delaySeconds * 1000); - - for (const transaction of runOptions.transactions) { - const transactionResponse = await this.getTransaction(transaction.id); - if (transactionResponse && transactionResponse.id !== transaction.id) { - logger.error(`Transaction '${transaction.id}' didn't get applied on the network`); - - successfulTest = false; - } - } - - if (runOptions.primaryAddress && runOptions.expectedSenderBalance) { - const walletBalance = await this.getWalletBalance(runOptions.primaryAddress); - if (!walletBalance.isEqualTo(runOptions.expectedSenderBalance)) { - successfulTest = false; - logger.error( - `Sender balance incorrect: '${satoshiToArk(walletBalance)}' but should be '${satoshiToArk( - runOptions.expectedSenderBalance, - )}'`, - ); - } - } - - for (const wallet of runOptions.wallets) { - const balance = await this.getWalletBalance(wallet.address); - if (!balance.isEqualTo(runOptions.transactionAmount)) { - successfulTest = false; - logger.error( - `Incorrect destination balance for ${wallet.address}. Should be '${satoshiToArk( - runOptions.transactionAmount, - )}' but is '${satoshiToArk(balance)}'`, - ); - } - } - - return successfulTest; - } - - /** - * Test vendor field is set correctly on blockchain. - * @param {Object[]} wallets - * @return {void} - */ - public async testVendorField(wallets) { - logger.info("Testing VendorField value is set correctly"); - - const transactions = this.generateTransactions(arkToSatoshi(2), wallets, null, null, "Testing VendorField"); - - try { - await this.sendTransactions(transactions); - - for (const transaction of transactions) { - const tx = await this.getTransaction(transaction.id); - if (!tx) { - logger.error(`Transaction '${transaction.id}' should be on the blockchain`); - } else if (tx.vendorField !== "Testing VendorField") { - logger.error(`Transaction '${transaction.id}' does not have correct vendorField value`); - } - } - } catch (error) { - this.problemSendingTransactions(error); - } - } - - /** - * Test empty vendor field is set correctly on blockchain. - * @param {Object[]} wallets - * @return {void} - */ - public async testEmptyVendorField(wallets) { - logger.info("Testing empty VendorField value"); - - const transactions = this.generateTransactions(arkToSatoshi(2), wallets, null, null, null); - - try { - await this.sendTransactions(transactions); - - for (const transaction of transactions) { - const tx = await this.getTransaction(transaction.id); - if (!tx) { - logger.error(`Transaction '${transaction.id}' should be on the blockchain`); - } else if (tx.vendorField) { - logger.error( - `Transaction '${transaction.id}' should not have vendorField value '${tx.vendorField}'`, - ); - } - } - } catch (error) { - this.problemSendingTransactions(error); - } - } -} diff --git a/packages/core-tester-cli/src/commands/vote.ts b/packages/core-tester-cli/src/commands/vote.ts deleted file mode 100644 index d8a4941f81..0000000000 --- a/packages/core-tester-cli/src/commands/vote.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { client } from "@arkecosystem/crypto"; -import { flags } from "@oclif/command"; -import sample from "lodash/sample"; -import pluralize from "pluralize"; -import { customFlags } from "../flags"; -import { logger, parseFee, satoshiToArk } from "../utils"; -import { BaseCommand } from "./command"; -import { TransferCommand } from "./transfer"; - -export class VoteCommand extends BaseCommand { - public static description: string = "create multiple votes for a delegate"; - - public static flags = { - ...BaseCommand.flags, - delegate: flags.string({ - description: "delegate public key", - }), - voteFee: customFlags.number({ - description: "vote fee", - default: 1, - }), - }; - - /** - * Run vote command. - * @return {void} - */ - public async run(): Promise { - // tslint:disable-next-line: no-shadowed-variable - const { flags } = await this.initialize(VoteCommand); - - const wallets = this.generateWallets(); - - for (const wallet of wallets) { - await TransferCommand.run( - ["--recipient", wallet.address, "--amount", String(2), "--skipTesting"].concat(this.castFlags(flags)), - ); - } - - let delegate = this.options.delegate; - if (!delegate) { - try { - delegate = sample(await this.getDelegates()).publicKey; - } catch (error) { - logger.error(error); - return; - } - } - - const voters = await this.getVoters(delegate); - logger.info(`Sending ${this.options.number} vote ${pluralize("transaction", this.options.number)}`); - - const transactions = []; - wallets.forEach((wallet, i) => { - const transaction = client - .getBuilder() - .vote() - .fee(parseFee(this.options.voteFee)) - .votesAsset([`+${delegate}`]) - .network(this.config.network.version) - .sign(wallet.passphrase) - .secondSign(this.config.secondPassphrase) - .build(); - - transactions.push(transaction); - - logger.info(`${i} ==> ${transaction.id}, ${wallet.address} (fee: ${satoshiToArk(transaction.fee)})`); - }); - - if (this.options.copy) { - this.copyToClipboard(transactions); - return; - } - - const expectedVoterCount = voters.length + wallets.length; - if (!this.options.skipValidation) { - logger.info(`Expected end voters: ${expectedVoterCount}`); - } - - try { - await this.sendTransactions(transactions, "vote", !this.options.skipValidation); - - if (this.options.skipValidation) { - return; - } - - const voterCount = (await this.getVoters(delegate)).length; - - logger.info(`All transactions have been sent! Total voters: ${voterCount}`); - - if (voterCount !== expectedVoterCount) { - logger.error(`Delegate voter count incorrect. '${voterCount}' but should be '${expectedVoterCount}'`); - } - } catch (error) { - logger.error( - `There was a problem sending transactions: ${error.response ? error.response.data.message : error}`, - ); - } - } -} diff --git a/packages/core-tester-cli/src/config.ts b/packages/core-tester-cli/src/config.ts deleted file mode 100644 index 49fdac960a..0000000000 --- a/packages/core-tester-cli/src/config.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const config = Object.freeze({ - apiPort: 4003, - p2pPort: 4000, - baseUrl: "http://localhost", - passphrase: "clay harbor enemy utility margin pretty hub comic piece aerobic umbrella acquire", - secondPassphrase: "", -}); diff --git a/packages/core-tester-cli/src/flags.ts b/packages/core-tester-cli/src/flags.ts index a75effe727..d9820bc27a 100644 --- a/packages/core-tester-cli/src/flags.ts +++ b/packages/core-tester-cli/src/flags.ts @@ -1,14 +1,13 @@ -import { flags as oclifFlags } from "@oclif/command"; +import { flags } from "@oclif/command"; -export const customFlags = { - number: oclifFlags.build({ - parse: input => { - const value = Number(input); - if (value < 1 / 1e8) { - throw new Error(`Expected number greater than 1 satoshi.`); - } +export const satoshiFlag = flags.build({ + parse: input => { + const value = Number(input); - return value; - }, - }), -}; + if (value < 1 / 1e8) { + throw new Error(`Expected number greater than 1 satoshi.`); + } + + return value; + }, +}); diff --git a/packages/core-tester-cli/src/http-client.ts b/packages/core-tester-cli/src/http-client.ts new file mode 100644 index 0000000000..e54808b66e --- /dev/null +++ b/packages/core-tester-cli/src/http-client.ts @@ -0,0 +1,34 @@ +import { httpie } from "@arkecosystem/core-utils"; +import { logger } from "./logger"; + +export class HttpClient { + private baseUrl: string; + + public constructor(baseUrl: string) { + this.baseUrl = baseUrl; + } + + public async get(path: string, query?: any, headers?: any): Promise { + const fullURL = `${this.baseUrl}${path}`; + + try { + const { body } = await httpie.get(fullURL, { query, headers }); + + return body; + } catch (error) { + logger.error(`${fullURL}: ${error.message}`); + } + } + + public async post(path: string, payload: any): Promise { + const fullURL = `${this.baseUrl}${path}`; + + try { + const { body } = await httpie.post(fullURL, { body: payload }); + + return body; + } catch (error) { + logger.error(`${fullURL}: ${error.message}`); + } + } +} diff --git a/packages/core-tester-cli/src/logger.ts b/packages/core-tester-cli/src/logger.ts new file mode 100644 index 0000000000..1a857882bd --- /dev/null +++ b/packages/core-tester-cli/src/logger.ts @@ -0,0 +1,7 @@ +import pino from "pino"; + +export const logger = pino({ + name: "core-tester-cli", + safe: true, + prettyPrint: true, +}); diff --git a/packages/core-tester-cli/src/shared/send.ts b/packages/core-tester-cli/src/shared/send.ts new file mode 100644 index 0000000000..ce00c7e980 --- /dev/null +++ b/packages/core-tester-cli/src/shared/send.ts @@ -0,0 +1,45 @@ +import { BaseCommand } from "../commands/command"; + +export abstract class SendCommand extends BaseCommand { + public async run(): Promise { + // Parse... + const { flags } = await this.make(this.getCommand()); + + // Waves... + let wallets = {}; + for (let i = 0; i < flags.waves; i++) { + // Prepare... + const newWallets = await this.createWalletsWithBalance(flags); + wallets = { ...wallets, ...newWallets }; + + // Sign... + const transactions = await this.signTransactions(flags, newWallets); + + // Expect... + if (!flags.skipProbing) { + await this.expectBalances(transactions, newWallets); + } + + // Send... + await this.broadcastTransactions(transactions); + + // Verify... + if (!flags.skipProbing) { + await this.verifyTransactions(transactions, newWallets); + } + } + + // Return... + return wallets; + } + + protected abstract getCommand(): Promise; + + protected abstract async createWalletsWithBalance(flags: Record): Promise; + + protected abstract async signTransactions(flags: Record, wallets: Record): Promise; + + protected abstract async expectBalances(transactions, wallets): Promise; + + protected abstract async verifyTransactions(transactions, wallets): Promise; +} diff --git a/packages/core-tester-cli/src/signer.ts b/packages/core-tester-cli/src/signer.ts new file mode 100644 index 0000000000..bfa7c2ef76 --- /dev/null +++ b/packages/core-tester-cli/src/signer.ts @@ -0,0 +1,81 @@ +import { bignumify } from "@arkecosystem/core-utils"; +import { client } from "@arkecosystem/crypto"; + +export class Signer { + protected network: Record; + + public constructor(network) { + this.network = network; + } + + public makeTransfer(opts: Record): any { + const transaction = client + .getBuilder() + .transfer() + .fee(this.toSatoshi(opts.transferFee)) + .network(this.network.version) + .recipientId(opts.recipient) + .amount(this.toSatoshi(opts.amount)); + + if (opts.vendorField) { + transaction.vendorField(opts.vendorField); + } + + transaction.sign(opts.passphrase); + + if (opts.secondPassphrase) { + transaction.secondSign(opts.secondPassphrase); + } + + return transaction.getStruct(); + } + + public makeDelegate(opts: Record): any { + const transaction = client + .getBuilder() + .delegateRegistration() + .fee(this.toSatoshi(opts.delegateFee)) + .network(this.network.version) + .usernameAsset(opts.username) + .sign(opts.passphrase); + + if (opts.secondPassphrase) { + transaction.secondSign(opts.secondPassphrase); + } + + return transaction.getStruct(); + } + + public makeSecondSignature(opts: Record): any { + return client + .getBuilder() + .secondSignature() + .fee(this.toSatoshi(opts.signatureFee)) + .network(this.network.version) + .signatureAsset(opts.secondPassphrase) + .sign(opts.passphrase) + .getStruct(); + } + + public makeVote(opts: Record): any { + const transaction = client + .getBuilder() + .vote() + .fee(this.toSatoshi(opts.voteFee)) + .votesAsset([`+${opts.delegate}`]) + .network(this.network.version) + .sign(opts.passphrase); + + if (opts.secondPassphrase) { + transaction.secondSign(opts.secondPassphrase); + } + + return transaction.getStruct(); + } + + private toSatoshi(value) { + return bignumify(value) + .times(1e8) + .toFixed(); + } +} diff --git a/packages/core-tester-cli/src/utils.ts b/packages/core-tester-cli/src/utils.ts index 6af7ea0894..8ff6884a38 100644 --- a/packages/core-tester-cli/src/utils.ts +++ b/packages/core-tester-cli/src/utils.ts @@ -1,142 +1,17 @@ -import { bignumify } from "@arkecosystem/core-utils"; -import { Bignum, client, formatSatoshi } from "@arkecosystem/crypto"; -import axios from "axios"; -import pino from "pino"; +import clipboardy from "clipboardy"; -export const logger = pino({ - name: "core-tester-cli", - safe: true, - prettyPrint: true, -}); - -export function request(config) { - const headers: any = {}; - if (config && config.network) { - headers.nethash = config.network.nethash; - headers.version = "2.1.0"; - headers.port = config.p2pPort; - headers["Content-Type"] = "application/json"; - } - - return { - get: async (endpoint, isP2P = false) => { - const baseUrl = `${config.baseUrl}:${isP2P ? config.p2pPort : config.apiPort}`; - - return (await axios.get(baseUrl + endpoint, { headers })).data; - }, - post: async (endpoint, data, isP2P = false) => { - const baseUrl = `${config.baseUrl}:${isP2P ? config.p2pPort : config.apiPort}`; - - return (await axios.post(baseUrl + endpoint, data, { headers })).data; - }, - }; +export function copyToClipboard(data) { + clipboardy.writeSync(JSON.stringify(data)); } -export async function paginate(config, endpoint) { - const data = []; - let page = 1; - let maxPages = null; - while (maxPages === null || page <= maxPages) { - const response = await request(config).get(`${endpoint}?page=${page}`); - if (response) { - page++; - maxPages = response.meta.pageCount; - data.push(...response.data); - } else { - break; - } +export function handleOutput(opts, data) { + if (opts.copy) { + return copyToClipboard(data); } - return data; -} - -/** - * Generate batch of transactions based on wallets. - */ -export function generateTransactions( - amountPerTransaction: any, - wallets: any[], - approvalWallets: any[], - options: { - config: any; - overridePassphrase?: false; - vendorField?: string; - log?: boolean; - [key: string]: any; - }, -) { - const transactions = []; - wallets.forEach((wallet, i) => { - const builder = client.getBuilder().transfer(); - // noinspection JSCheckFunctionSignatures - builder - .fee(this.parseFee(options.transferFee)) - .recipientId(options.recipient || wallet.address) - .network(options.config.network.version) - .amount(amountPerTransaction) - .vendorField(options.vendorField === undefined ? `Transaction ${i + 1}` : options.vendorField) - .sign(options.overridePassphrase ? options.config.passphrase : wallet.passphrase); - - if (wallet.secondPassphrase || options.config.secondPassphrase) { - builder.secondSign(wallet.secondPassphrase || options.config.secondPassphrase); - } - - if (approvalWallets) { - for (let j = approvalWallets.length - 1; j >= 0; j--) { - builder.multiSignatureSign(approvalWallets[j].passphrase); - } - } - - const transaction = builder.build(); - transactions.push(transaction); - - if (options.log) { - logger.info( - `${i} ==> ${transaction.id}, ${transaction.recipientId} (fee: ${this.satoshiToArk(transaction.fee)})`, - ); - } - }); - - return transactions; -} - -/** - * Parse fee based on input. - * @param {(String|Number)} fee - * @return {Bignum} - */ -export function parseFee(fee): Bignum { - if (typeof fee === "string" && fee.indexOf("-") !== -1) { - const feeRange = fee.split("-").map( - f => - +bignumify(f) - .times(1e8) - .toFixed(), - ); - if (feeRange[1] < feeRange[0]) { - return bignumify(feeRange[0]); - } - - return bignumify(Math.floor(Math.random() * (feeRange[1] - feeRange[0] + 1) + feeRange[0])); + if (opts.log) { + return console.log(data); } - return bignumify(fee).times(1e8); -} - -/** - * Convert ARK to Satoshi. - * @param {Number} ark - * @return {Bignum} - */ -export function arkToSatoshi(ark) { - return bignumify(ark * 1e8); -} - -/** - * Convert Satoshi to ARK. - * @param {Bignum} satoshi - * @return {String} - */ -export function satoshiToArk(satoshi) { - return formatSatoshi(satoshi); + return data; } diff --git a/packages/core-transaction-pool/.gitattributes b/packages/core-transaction-pool/.gitattributes index 60cc52db63..63f6a5b970 100644 --- a/packages/core-transaction-pool/.gitattributes +++ b/packages/core-transaction-pool/.gitattributes @@ -2,10 +2,6 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore -/__tests__ export-ignore -/docs export-ignore /README.md export-ignore diff --git a/packages/core-transaction-pool/README.md b/packages/core-transaction-pool/README.md index 5bf2c68208..b126c1b4a8 100644 --- a/packages/core-transaction-pool/README.md +++ b/packages/core-transaction-pool/README.md @@ -1,12 +1,12 @@ -# Ark Core - Transaction Pool +# Persona Core - Transaction Pool

- +

## Documentation -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core-transaction-pool.html). +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/required/core-transaction-pool.html). ## Security @@ -14,13 +14,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [Kristjan Košič](https://github.com/kristjank) -- [Brian Faust](https://github.com/faustbrian) -- [Alex Barnsley](https://github.com/alexbarnsley) -- [Vasil Dimov](https://github.com/vasild) -- [Joshua Noack](https://github.com/supaiku0) -- [All Contributors](../../../../contributors) +This project exists thanks to all the people who [contribute](../../../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-transaction-pool/__tests__/__fixtures__/transactions.ts b/packages/core-transaction-pool/__tests__/__fixtures__/transactions.ts deleted file mode 100644 index fae23dd151..0000000000 --- a/packages/core-transaction-pool/__tests__/__fixtures__/transactions.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { generators } from "@arkecosystem/core-test-utils"; -import { delegates } from "@arkecosystem/core-test-utils/src/fixtures/unitnet/delegates"; -const { generateTransfers } = generators; - -export const transactions = { - dummy1: generateTransfers( - "unitnet", - delegates[0].passphrase, - "AFzQCx5YpGg5vKMBg4xbuYbqkhvMkKfKe5", - 200000000, - 1, - false, - 10000000, - )[0], - - dummy2: generateTransfers( - "unitnet", - delegates[0].passphrase, - "DFyDKsyvR4x9D9zrfEaPmeJxSniT5N5qY8", - 10000000, - 1, - false, - 10000000, - )[0], - - dummy3: generateTransfers( - "unitnet", - delegates[0].passphrase, - "ANqvJEMZcmUpcKBC8xiP1TntVkJeuZ3Lw3", - 200000000, - 1, - false, - 10000000, - )[0], - - dummy4: generateTransfers( - "unitnet", - delegates[0].passphrase, - "AJ5eV59hu4xrbRCpoP3of7fEYWUteSVa8k", - 200000000, - 1, - false, - 10000000, - )[0], - - dummy5: generateTransfers( - "unitnet", - delegates[0].passphrase, - "ASvC1E9hMLfANTi63S2gUMvr7rVZYJBj3u", - 200000000, - 1, - false, - 10000000, - )[0], - - dummy6: generateTransfers( - "unitnet", - delegates[0].passphrase, - "Ac8utEr7XRebWRvArSBnbVoxbq6bXftAmL", - 200000000, - 1, - false, - 10000000, - )[0], - - dummy7: generateTransfers( - "unitnet", - delegates[0].passphrase, - "ANWEaVfvAh3VTyZNYcuFESUum1XBmAvAdj", - 200000000, - 1, - false, - 10000000, - )[0], - - dummy8: generateTransfers( - "unitnet", - delegates[0].passphrase, - "ALsZS24Dn4HYXwed5kAC5fKyB9BFzdmcSx", - 200000000, - 1, - false, - 10000000, - )[0], - - dummy9: generateTransfers( - "unitnet", - delegates[0].passphrase, - "ANuaLhRuBJhTcHao7kTfDcfsewLQGr7x5G", - 200000000, - 1, - false, - 10000000, - )[0], - - dummy10: generateTransfers( - "unitnet", - delegates[1].passphrase, - "DFyDKsyvR4x9D9zrfEaPmeJxSniT5N5qY8", - 200000000, - 1, - false, - 10000000, - )[0], - - dummyLarge1: generateTransfers( - "unitnet", - delegates[1].passphrase, - "AFzQCx5YpGg5vKMBg4xbuYbqkhvMkKfKe5", - 200000000, - 1, - false, - 10000000, - )[0], - - dummyLarge2: generateTransfers( - "unitnet", - delegates[1].passphrase, - "DFyDKsyvR4x9D9zrfEaPmeJxSniT5N5qY8", - 200000000, - 1, - false, - 10000000, - )[0], - - dynamicFeeNormalDummy1: generateTransfers( - "unitnet", - delegates[0].passphrase, - "AcjGpvDJEQdBVwspYsAs16B8Rv66zo7gyd", - 200000000, - 1, - false, - 280000, - )[0], - - dynamicFeeLowDummy2: generateTransfers( - "unitnet", - delegates[0].passphrase, - "AabMvWPVKbdTHRcGBpATq9TEMiMD5xeJh9", - 200000000, - 1, - false, - 100, - )[0], - - dynamicFeeZero: generateTransfers( - "unitnet", - delegates[0].passphrase, - "AVnRZSvrAeeSJZN3oSBxEF6mvvVpuKUXL5", - 200000000, - 1, - false, - 0, - )[0], - - dummyExp1: generateTransfers( - "unitnet", - delegates[1].passphrase, - "AFzQCx5YpGg5vKMBg4xbuYbqkhvMkKfKe5", - 200000000, - 1, - )[0], - - dummyExp2: generateTransfers( - "unitnet", - delegates[1].passphrase, - "DFyDKsyvR4x9D9zrfEaPmeJxSniT5N5qY8", - 200000000, - 1, - )[0], -}; diff --git a/packages/core-transaction-pool/__tests__/guard.test.ts b/packages/core-transaction-pool/__tests__/guard.test.ts deleted file mode 100644 index 4b849d939f..0000000000 --- a/packages/core-transaction-pool/__tests__/guard.test.ts +++ /dev/null @@ -1,1152 +0,0 @@ -import { Container } from "@arkecosystem/core-interfaces"; -import { generators } from "@arkecosystem/core-test-utils"; -import { configManager, constants, crypto, models, slots } from "@arkecosystem/crypto"; -import bip39 from "bip39"; -import "jest-extended"; -import { delegates, genesisBlock, wallets, wallets2ndSig } from "../../core-test-utils/src/fixtures/unitnet"; -import { config as localConfig } from "../src/config"; -import { setUpFull, tearDownFull } from "./__support__/setup"; - -const { Block } = models; -const { - generateDelegateRegistration, - generateSecondSignature, - generateTransfers, - generateVote, - generateWallets, -} = generators; - -let TransactionGuard; - -let container: Container.IContainer; -let guard; -let transactionPool; -let blockchain; - -beforeAll(async () => { - container = await setUpFull(); - - TransactionGuard = require("../src").TransactionGuard; - - transactionPool = container.resolvePlugin("transactionPool"); - blockchain = container.resolvePlugin("blockchain"); - localConfig.init(transactionPool.options); -}); - -afterAll(async () => { - await tearDownFull(); -}); - -beforeEach(() => { - transactionPool.flush(); - guard = new TransactionGuard(transactionPool); -}); - -describe("Transaction Guard", () => { - describe("validate", () => { - it.each([false, true])( - "should not apply transactions for chained transfers involving cold wallets", - async inverseOrder => { - /* The logic here is we can't have a chained transfer A => B => C if B is a cold wallet. - A => B needs to be first confirmed (forged), then B can transfer to C - */ - - const satoshi = 10 ** 8; - // don't re-use the same delegate (need clean balance) - const delegate = inverseOrder ? delegates[8] : delegates[9]; - const delegateWallet = transactionPool.walletManager.findByAddress(delegate.address); - - const newWallets = generateWallets("unitnet", 2); - const poolWallets = newWallets.map(w => transactionPool.walletManager.findByAddress(w.address)); - - expect(+delegateWallet.balance).toBe(+delegate.balance); - poolWallets.forEach(w => { - expect(+w.balance).toBe(0); - }); - - const transfer0 = { - // transfer from delegate to wallet 0 - from: delegate, - to: newWallets[0], - amount: 100 * satoshi, - }; - const transfer1 = { - // transfer from wallet 0 to wallet 1 - from: newWallets[0], - to: newWallets[1], - amount: 55 * satoshi, - }; - const transfers = [transfer0, transfer1]; - if (inverseOrder) { - transfers.reverse(); - } - - for (const t of transfers) { - const transferTx = generateTransfers("unitnet", t.from.passphrase, t.to.address, t.amount, 1)[0]; - - await guard.validate([transferTx]); - } - - // apply again transfer from 0 to 1 - const transfer = generateTransfers( - "unitnet", - transfer1.from.passphrase, - transfer1.to.address, - transfer1.amount, - 1, - )[0]; - - await guard.validate([transfer]); - - const expectedError = { - message: '["Cold wallet is not allowed to send until receiving transaction is confirmed."]', - type: "ERR_APPLY", - }; - expect(guard.errors[transfer.id]).toContainEqual(expectedError); - - // check final balances - expect(+delegateWallet.balance).toBe(delegate.balance - (100 + 0.1) * satoshi); - expect(+poolWallets[0].balance).toBe(0); - expect(+poolWallets[1].balance).toBe(0); - }, - ); - - it("should not apply the tx to the balance of the sender & recipient with dyn fee < min fee", async () => { - const delegate0 = delegates[14]; - const { publicKey } = crypto.getKeys(bip39.generateMnemonic()); - const newAddress = crypto.getAddress(publicKey); - - const delegateWallet = transactionPool.walletManager.findByPublicKey(delegate0.publicKey); - const newWallet = transactionPool.walletManager.findByPublicKey(publicKey); - - expect(+delegateWallet.balance).toBe(+delegate0.balance); - expect(+newWallet.balance).toBe(0); - - const amount1 = 123 * 10 ** 8; - const fee = 10; - const transfers = generateTransfers("unitnet", delegate0.secret, newAddress, amount1, 1, false, fee); - - await guard.validate(transfers); - - expect(+delegateWallet.balance).toBe(+delegate0.balance); - expect(+newWallet.balance).toBe(0); - }); - - it("should update the balance of the sender & recipient with dyn fee > min fee", async () => { - const delegate1 = delegates[1]; - const { publicKey } = crypto.getKeys(bip39.generateMnemonic()); - const newAddress = crypto.getAddress(publicKey); - - const delegateWallet = transactionPool.walletManager.findByPublicKey(delegate1.publicKey); - const newWallet = transactionPool.walletManager.findByPublicKey(publicKey); - - expect(+delegateWallet.balance).toBe(+delegate1.balance); - expect(+newWallet.balance).toBe(0); - - const amount1 = +delegateWallet.balance / 2; - const fee = 0.1 * 10 ** 8; - const transfers = generateTransfers("unitnet", delegate1.secret, newAddress, amount1, 1, false, fee); - - await guard.validate(transfers); - expect(guard.errors).toEqual({}); - - // simulate forged transaction - newWallet.applyTransactionToRecipient(transfers[0]); - - expect(+delegateWallet.balance).toBe(+delegate1.balance - amount1 - fee); - expect(+newWallet.balance).toBe(amount1); - }); - - it("should update the balance of the sender & recipient with multiple transactions type", async () => { - const delegate2 = delegates[2]; - const newWalletPassphrase = bip39.generateMnemonic(); - const { publicKey } = crypto.getKeys(newWalletPassphrase); - const newAddress = crypto.getAddress(publicKey); - - const delegateWallet = transactionPool.walletManager.findByPublicKey(delegate2.publicKey); - const newWallet = transactionPool.walletManager.findByPublicKey(publicKey); - - expect(+delegateWallet.balance).toBe(+delegate2.balance); - expect(+newWallet.balance).toBe(0); - expect(guard.errors).toEqual({}); - - const amount1 = +delegateWallet.balance / 2; - const fee = 0.1 * 10 ** 8; - const voteFee = 10 ** 8; - const delegateRegFee = 25 * 10 ** 8; - const signatureFee = 5 * 10 ** 8; - const transfers = generateTransfers("unitnet", delegate2.secret, newAddress, amount1, 1, false, fee); - const votes = generateVote("unitnet", newWalletPassphrase, delegate2.publicKey, 1); - const delegateRegs = generateDelegateRegistration("unitnet", newWalletPassphrase, 1); - const signatures = generateSecondSignature("unitnet", newWalletPassphrase, 1); - - // Index wallets to not encounter cold wallet error - const allTransactions = [...transfers, ...votes, ...delegateRegs, ...signatures]; - - allTransactions.forEach(transaction => { - container.resolvePlugin("database").walletManager.findByPublicKey(transaction.senderPublicKey); - }); - - // first validate the 1st transfer so that new wallet is updated with the amount - await guard.validate(transfers); - - // simulate forged transaction - newWallet.applyTransactionToRecipient(transfers[0]); - - expect(guard.errors).toEqual({}); - expect(+newWallet.balance).toBe(amount1); - - // reset guard, if not the 1st transaction will still be in this.accept and mess up - guard = new TransactionGuard(transactionPool); - - await guard.validate([votes[0], delegateRegs[0], signatures[0]]); - - expect(guard.errors).toEqual({}); - expect(+delegateWallet.balance).toBe(+delegate2.balance - amount1 - fee); - expect(+newWallet.balance).toBe(amount1 - voteFee - delegateRegFee - signatureFee); - }); - - it("should not accept transaction in excess", async () => { - const delegate3 = delegates[3]; - const newWalletPassphrase = bip39.generateMnemonic(); - const { publicKey } = crypto.getKeys(newWalletPassphrase); - const newAddress = crypto.getAddress(publicKey); - - const delegateWallet = transactionPool.walletManager.findByPublicKey(delegate3.publicKey); - const newWallet = transactionPool.walletManager.findByPublicKey(publicKey); - - // Make sure it is not considered a cold wallet - container.resolvePlugin("database").walletManager.reindex(newWallet); - - expect(+delegateWallet.balance).toBe(+delegate3.balance); - expect(+newWallet.balance).toBe(0); - - // first, transfer coins to new wallet so that we can test from it then - const amount1 = 1000 * 10 ** 8; - const fee = 0.1 * 10 ** 8; - const transfers1 = generateTransfers("unitnet", delegate3.secret, newAddress, amount1, 1); - await guard.validate(transfers1); - - // simulate forged transaction - newWallet.applyTransactionToRecipient(transfers1[0]); - - expect(+delegateWallet.balance).toBe(+delegate3.balance - amount1 - fee); - expect(+newWallet.balance).toBe(amount1); - - // transfer almost everything from new wallet so that we don't have enough for any other transaction - const amount2 = 999 * 10 ** 8; - const transfers2 = generateTransfers("unitnet", newWalletPassphrase, delegate3.address, amount2, 1); - await guard.validate(transfers2); - - // simulate forged transaction - delegateWallet.applyTransactionToRecipient(transfers2[0]); - - expect(+newWallet.balance).toBe(amount1 - amount2 - fee); - - // now try to validate any other transaction - should not be accepted because in excess - const transferAmount = 0.5 * 10 ** 8; - const transferDynFee = 0.5 * 10 ** 8; - const allTransactions = [ - generateTransfers( - "unitnet", - newWalletPassphrase, - delegate3.address, - transferAmount, - 1, - false, - transferDynFee, - ), - generateSecondSignature("unitnet", newWalletPassphrase, 1), - generateVote("unitnet", newWalletPassphrase, delegate3.publicKey, 1), - generateDelegateRegistration("unitnet", newWalletPassphrase, 1), - ]; - - for (const transaction of allTransactions) { - await guard.validate(transaction); - - const errorExpected = [ - { - message: `["[PoolWalletManager] Can't apply transaction id:${transaction[0].id} from sender:${ - newWallet.address - }","Insufficient balance in the wallet"]`, - type: "ERR_APPLY", - }, - ]; - expect(guard.errors[transaction[0].id]).toEqual(errorExpected); - - expect(+delegateWallet.balance).toBe(+delegate3.balance - amount1 - fee + amount2); - expect(+newWallet.balance).toBe(amount1 - amount2 - fee); - } - }); - - it("should not validate 2 double spending transactions", async () => { - const amount = 245098000000000 - 5098000000000; // a bit less than the delegates' balance - const transactions = generateTransfers( - "unitnet", - delegates[0].secret, - delegates[1].address, - amount, - 2, - true, - ); - - const result = await guard.validate(transactions); - - expect(result.errors[transactions[1].id]).toEqual([ - { - message: `["[PoolWalletManager] Can't apply transaction id:${transactions[1].id} from sender:${ - delegates[0].address - }","Insufficient balance in the wallet"]`, - type: "ERR_APPLY", - }, - ]); - }); - - it.each([3, 5, 8])("should validate emptying wallet with %i transactions", async txNumber => { - // use txNumber so that we use a different delegate for each test case - const sender = delegates[txNumber]; - const senderWallet = transactionPool.walletManager.findByPublicKey(sender.publicKey); - const receivers = generateWallets("unitnet", 2); - const amountPlusFee = Math.floor(senderWallet.balance / txNumber); - const lastAmountPlusFee = senderWallet.balance - (txNumber - 1) * amountPlusFee; - const transferFee = 10000000; - - const transactions = generateTransfers( - "unitnet", - sender.secret, - receivers[0].address, - amountPlusFee - transferFee, - txNumber - 1, - true, - ); - const lastTransaction = generateTransfers( - "unitnet", - sender.secret, - receivers[1].address, - lastAmountPlusFee - transferFee, - 1, - true, - ); - // we change the receiver in lastTransaction to prevent having 2 exact - // same transactions with same id (if not, could be same as transactions[0]) - - const result = await guard.validate(transactions.concat(lastTransaction)); - - expect(result.errors).toEqual(null); - }); - - it.each([3, 5, 8])( - "should not validate emptying wallet with %i transactions when the last one is 1 satoshi too much", - async txNumber => { - // use txNumber + 1 so that we don't use the same delegates as the above test - const sender = delegates[txNumber + 1]; - const receivers = generateWallets("unitnet", 2); - const amountPlusFee = Math.floor(sender.balance / txNumber); - const lastAmountPlusFee = sender.balance - (txNumber - 1) * amountPlusFee + 1; - const transferFee = 10000000; - - const transactions = generateTransfers( - "unitnet", - sender.secret, - receivers[0].address, - amountPlusFee - transferFee, - txNumber - 1, - true, - ); - const lastTransaction = generateTransfers( - "unitnet", - sender.secret, - receivers[1].address, - lastAmountPlusFee - transferFee, - 1, - true, - ); - // we change the receiver in lastTransaction to prevent having 2 - // exact same transactions with same id (if not, could be same as transactions[0]) - - const allTransactions = transactions.concat(lastTransaction); - - const result = await guard.validate(allTransactions); - - expect(Object.keys(result.errors).length).toBe(1); - expect(result.errors[lastTransaction[0].id]).toEqual([ - { - message: `["[PoolWalletManager] Can't apply transaction id:${ - lastTransaction[0].id - } from sender:${sender.address}","Insufficient balance in the wallet"]`, - type: "ERR_APPLY", - }, - ]); - }, - ); - - it("should compute transaction id and therefore validate transactions with wrong id", async () => { - const sender = delegates[21]; - const receivers = generateWallets("unitnet", 1); - - const transactions = generateTransfers("unitnet", sender.secret, receivers[0].address, 50, 1, true); - const transactionId = transactions[0].id; - transactions[0].id = "11111"; - - const result = await guard.validate(transactions); - expect(result.accept).toEqual([transactionId]); - expect(result.broadcast).toEqual([transactionId]); - expect(result.errors).toBeNull(); - }); - - it("should not validate when multiple wallets register the same username in the same transaction payload", async () => { - const delegateRegistrations = [ - generateDelegateRegistration("unitnet", wallets[14].passphrase, 1, false, "test_delegate")[0], - generateDelegateRegistration("unitnet", wallets[15].passphrase, 1, false, "test_delegate")[0], - ]; - - const result = await guard.validate(delegateRegistrations); - expect(result.invalid).toEqual(delegateRegistrations.map(transaction => transaction.id)); - - delegateRegistrations.forEach(tx => { - expect(guard.errors[tx.id]).toEqual([ - { - type: "ERR_CONFLICT", - message: `Multiple delegate registrations for "${ - tx.asset.delegate.username - }" in transaction payload`, - }, - ]); - }); - - const wallet1 = transactionPool.walletManager.findByPublicKey(wallets[14].keys.publicKey); - const wallet2 = transactionPool.walletManager.findByPublicKey(wallets[15].keys.publicKey); - - expect(wallet1.username).toBe(null); - expect(wallet2.username).toBe(null); - }); - - describe("Sign a transaction then change some fields shouldn't pass validation", () => { - const secondSignatureError = (id, address) => [ - id, - "ERR_APPLY", - `["[PoolWalletManager] Can't apply transaction id:${id} from sender:${address}","Failed to verify second-signature"]`, - ]; - - it("should not validate when changing fields after signing - transfer", async () => { - const sender = delegates[21]; - const notSender = delegates[22]; - - // the fields we are going to modify after signing - const modifiedFields = [ - { timestamp: 111111 }, - { amount: 111 }, - { fee: 1111111 }, - { recipientId: "ANqvJEMZcmUpcKBC8xiP1TntVkJeuZ3Lw3" }, - // we are also going to modify senderPublicKey but separately - ]; - - // generate transfers, "simple" and 2nd signed - const transfers = generateTransfers( - "unitnet", - sender.secret, - "AFzQCx5YpGg5vKMBg4xbuYbqkhvMkKfKe5", - 50, - modifiedFields.length + 1, // + 1 because we will use it to modify senderPublicKey separately - true, - ); - const transfers2ndSigned = generateTransfers( - "unitnet", - { passphrase: wallets2ndSig[0].passphrase, secondPassphrase: wallets2ndSig[0].secondPassphrase }, - "AFzQCx5YpGg5vKMBg4xbuYbqkhvMkKfKe5", - 50, - modifiedFields.length + 1, // + 1 because we will use it to modify senderPublicKey separately - true, - ); - - // modify transaction fields and try to validate - const modifiedTransactions = [ - ...modifiedFields.map((objField, index) => Object.assign({}, transfers[index], objField)), - Object.assign({}, transfers[transfers.length - 1], { senderPublicKey: notSender.publicKey }), - ...modifiedFields.map((objField, index) => Object.assign({}, transfers2ndSigned[index], objField)), - Object.assign({}, transfers2ndSigned[transfers2ndSigned.length - 1], { - senderPublicKey: wallets2ndSig[1].keys.publicKey, - }), - ]; - const result = await guard.validate(modifiedTransactions); - - const expectedErrors = [ - ...transfers.map(transfer => [ - transfer.id, - "ERR_BAD_DATA", - "Transaction didn't pass the verification process.", - ]), - ...transfers2ndSigned - .slice(0, -1) - .map(transfer => secondSignatureError(transfer.id, wallets2ndSig[0].address)), - secondSignatureError( - transfers2ndSigned[transfers2ndSigned.length - 1].id, - wallets2ndSig[1].address, - ), - ]; - - expect( - Object.keys(result.errors).map(id => [id, result.errors[id][0].type, result.errors[id][0].message]), - ).toEqual(expectedErrors); - expect(result.invalid).toEqual(modifiedTransactions.map(transaction => transaction.id)); - expect(result.accept).toEqual([]); - expect(result.broadcast).toEqual([]); - }); - - it("should not validate when changing fields after signing - delegate registration", async () => { - // the fields we are going to modify after signing - const modifiedFieldsDelReg = [ - { timestamp: 111111 }, - { fee: 1111111 }, - // we are also going to modify senderPublicKey but separately - ]; - - // generate delegate registrations, "simple" and 2nd signed - const delegateRegs = generateDelegateRegistration( - "unitnet", - wallets.slice(0, modifiedFieldsDelReg.length + 1).map(w => w.passphrase), - 1, - true, - ); - const delegateRegs2ndSigned = generateDelegateRegistration( - "unitnet", - wallets2ndSig - .slice(0, modifiedFieldsDelReg.length + 1) - .map(w => ({ passphrase: w.passphrase, secondPassphrase: w.secondPassphrase })), - 1, - true, - ); - - // modify transaction fields and try to validate - const modifiedTransactions = [ - ...modifiedFieldsDelReg.map((objField, index) => Object.assign({}, delegateRegs[index], objField)), - Object.assign({}, delegateRegs[delegateRegs.length - 1], { - senderPublicKey: wallets[50].keys.publicKey, - }), - ...modifiedFieldsDelReg.map((objField, index) => - Object.assign({}, delegateRegs2ndSigned[index], objField), - ), - Object.assign({}, delegateRegs2ndSigned[delegateRegs2ndSigned.length - 1], { - senderPublicKey: wallets2ndSig[50].keys.publicKey, - }), - ]; - const result = await guard.validate(modifiedTransactions); - - const expectedErrors = [ - ...delegateRegs.map(tx => [ - tx.id, - "ERR_BAD_DATA", - "Transaction didn't pass the verification process.", - ]), - ...delegateRegs2ndSigned - .slice(0, -1) - .map((tx, index) => secondSignatureError(tx.id, wallets2ndSig[index].address)), - secondSignatureError( - delegateRegs2ndSigned[delegateRegs2ndSigned.length - 1].id, - wallets2ndSig[50].address, - ), - ]; - - expect( - Object.keys(result.errors).map(id => [id, result.errors[id][0].type, result.errors[id][0].message]), - ).toEqual(expectedErrors); - expect(result.invalid).toEqual(modifiedTransactions.map(transaction => transaction.id)); - expect(result.accept).toEqual([]); - expect(result.broadcast).toEqual([]); - }); - - it("should not validate when changing fields after signing - vote", async () => { - // the fields we are going to modify after signing - const modifiedFieldsVote = [ - { timestamp: 111111 }, - { fee: 1111111 }, - // we are also going to modify senderPublicKey but separately - ]; - - // generate votes, "simple" and 2nd signed - const votes = generateVote( - "unitnet", - wallets.slice(0, modifiedFieldsVote.length + 1).map(w => w.passphrase), - delegates[21].publicKey, - 1, - true, - ); - const votes2ndSigned = generateVote( - "unitnet", - wallets2ndSig - .slice(0, modifiedFieldsVote.length + 1) - .map(w => ({ passphrase: w.passphrase, secondPassphrase: w.secondPassphrase })), - delegates[21].publicKey, - 1, - true, - ); - - // modify transaction fields and try to validate - const modifiedTransactions = [ - ...modifiedFieldsVote.map((objField, index) => Object.assign({}, votes[index], objField)), - Object.assign({}, votes[votes.length - 1], { senderPublicKey: wallets[50].keys.publicKey }), - ...modifiedFieldsVote.map((objField, index) => Object.assign({}, votes2ndSigned[index], objField)), - Object.assign({}, votes2ndSigned[votes2ndSigned.length - 1], { - senderPublicKey: wallets2ndSig[50].keys.publicKey, - }), - ]; - const result = await guard.validate(modifiedTransactions); - - const expectedErrors = [ - ...votes.map(tx => [tx.id, "ERR_BAD_DATA", "Transaction didn't pass the verification process."]), - ...votes2ndSigned - .slice(0, -1) - .map((tx, index) => secondSignatureError(tx.id, wallets2ndSig[index].address)), - secondSignatureError(votes2ndSigned[votes2ndSigned.length - 1].id, wallets2ndSig[50].address), - ]; - - expect( - Object.keys(result.errors).map(id => [id, result.errors[id][0].type, result.errors[id][0].message]), - ).toEqual(expectedErrors); - expect(result.invalid).toEqual(modifiedTransactions.map(transaction => transaction.id)); - expect(result.accept).toEqual([]); - expect(result.broadcast).toEqual([]); - }); - - it("should not validate when changing fields after signing - 2nd signature registration", async () => { - // the fields we are going to modify after signing - const modifiedFields2ndSig = [ - { timestamp: 111111 }, - { fee: 1111111 }, - { senderPublicKey: wallets[50].keys.publicKey }, - ]; - - const secondSigs = generateSecondSignature( - "unitnet", - wallets.slice(0, modifiedFields2ndSig.length).map(w => w.passphrase), - 1, - true, - ); - - const modifiedTransactions = modifiedFields2ndSig.map((objField, index) => - Object.assign({}, secondSigs[index], objField), - ); - const result = await guard.validate(modifiedTransactions); - - expect( - Object.keys(result.errors).map(id => [id, result.errors[id][0].type, result.errors[id][0].message]), - ).toEqual( - secondSigs.map(tx => [tx.id, "ERR_BAD_DATA", "Transaction didn't pass the verification process."]), - ); - expect(result.invalid).toEqual(modifiedTransactions.map(transaction => transaction.id)); - expect(result.accept).toEqual([]); - expect(result.broadcast).toEqual([]); - }); - }); - - describe("Transaction replay shouldn't pass validation", () => { - afterEach(async () => blockchain.removeBlocks(blockchain.getLastHeight() - 1)); // resets to height 1 - - const addBlock = async transactions => { - // makes blockchain accept a new block with the transactions specified - const block = { - id: "17882607875259085966", - version: 0, - timestamp: 46583330, - height: 2, - reward: 0, - previousBlock: genesisBlock.id, - numberOfTransactions: 1, - transactions, - totalAmount: transactions.reduce((acc, curr) => acc + curr.amount), - totalFee: transactions.reduce((acc, curr) => acc + curr.fee), - payloadLength: 0, - payloadHash: genesisBlock.payloadHash, - generatorPublicKey: delegates[0].publicKey, - blockSignature: - "3045022100e7385c6ea42bd950f7f6ab8c8619cf2f66a41d8f8f185b0bc99af032cb25f30d02200b6210176a6cedfdcbe483167fd91c21d740e0e4011d24d679c601fdd46b0de9", - createdAt: "2019-07-11T16:48:50.550Z", - }; - const blockVerified = new Block(block); - blockVerified.verification.verified = true; - - await blockchain.processBlock(blockVerified, () => null); - }; - const forgedErrorMessage = id => ({ - [id]: [ - { - message: "Already forged.", - type: "ERR_FORGED", - }, - ], - }); - - it("should not validate an already forged transaction", async () => { - const transfers = generateTransfers("unitnet", wallets[0].passphrase, wallets[1].address, 11, 1, true); - await addBlock(transfers); - - const result = await guard.validate(transfers); - - expect(result.errors).toEqual(forgedErrorMessage(transfers[0].id)); - }); - - it("should not validate an already forged transaction - trying to tweak tx id", async () => { - const transfers = generateTransfers("unitnet", wallets[0].passphrase, wallets[1].address, 11, 1, true); - await addBlock(transfers); - - const realTransferId = transfers[0].id; - transfers[0].id = "123456"; - - const result = await guard.validate(transfers); - - expect(result.errors).toEqual(forgedErrorMessage(realTransferId)); - }); - }); - }); - - describe("__cacheTransactions", () => { - it("should add transactions to cache", () => { - const transactions = generateTransfers("unitnet", wallets[10].passphrase, wallets[11].address, 35, 3); - expect(guard.__cacheTransactions(transactions)).toEqual(transactions); - }); - - it("should not add a transaction already in cache and add it as an error", () => { - const transactions = generateTransfers("unitnet", wallets[11].passphrase, wallets[12].address, 35, 3); - expect(guard.__cacheTransactions(transactions)).toEqual(transactions); - expect(guard.__cacheTransactions([transactions[0]])).toEqual([]); - expect(guard.errors).toEqual({ - [transactions[0].id]: [ - { - message: "Already in cache.", - type: "ERR_DUPLICATE", - }, - ], - }); - }); - }); - - describe("getBroadcastTransactions", () => { - it("should return broadcast transaction", async () => { - const transactions = generateTransfers("unitnet", wallets[10].passphrase, wallets[11].address, 25, 3); - - await guard.validate(transactions); - expect(guard.getBroadcastTransactions()).toEqual(transactions); - }); - }); - - describe("__filterAndTransformTransactions", () => { - it("should reject duplicate transactions", () => { - const transactionExists = guard.pool.transactionExists; - guard.pool.transactionExists = jest.fn(() => true); - - const tx = { id: "1" }; - guard.__filterAndTransformTransactions([tx]); - - expect(guard.errors[tx.id]).toEqual([ - { - message: `Duplicate transaction ${tx.id}`, - type: "ERR_DUPLICATE", - }, - ]); - - guard.pool.transactionExists = transactionExists; - }); - - it("should reject blocked senders", () => { - const transactionExists = guard.pool.transactionExists; - guard.pool.transactionExists = jest.fn(() => false); - const isSenderBlocked = guard.pool.isSenderBlocked; - guard.pool.isSenderBlocked = jest.fn(() => true); - - const tx = { id: "1", senderPublicKey: "affe" }; - guard.__filterAndTransformTransactions([tx]); - - expect(guard.errors[tx.id]).toEqual([ - { - message: `Transaction ${tx.id} rejected. Sender ${tx.senderPublicKey} is blocked.`, - type: "ERR_SENDER_BLOCKED", - }, - ]); - - guard.pool.isSenderBlocked = isSenderBlocked; - guard.pool.transactionExists = transactionExists; - }); - - it("should reject transactions that are too large", () => { - const tx = generateTransfers("unitnet", wallets[11].passphrase, wallets[12].address, 1, 3)[0]; - tx.data.signatures = [""]; - for (let i = 0; i < transactionPool.options.maxTransactionBytes; i++) { - tx.data.signatures += "1"; - } - guard.__filterAndTransformTransactions([tx]); - - expect(guard.errors[tx.id]).toEqual([ - { - message: `Transaction ${tx.id} is larger than ${ - transactionPool.options.maxTransactionBytes - } bytes.`, - type: "ERR_TOO_LARGE", - }, - ]); - }); - - it("should reject transactions from the future", () => { - const now = 47157042; // seconds since genesis block - const transactionExists = guard.pool.transactionExists; - guard.pool.transactionExists = jest.fn(() => false); - const getTime = slots.getTime; - slots.getTime = jest.fn(() => now); - - const secondsInFuture = 3601; - const tx = { - id: "1", - senderPublicKey: "affe", - timestamp: slots.getTime() + secondsInFuture, - }; - guard.__filterAndTransformTransactions([tx]); - - expect(guard.errors[tx.id]).toEqual([ - { - message: `Transaction ${tx.id} is ${secondsInFuture} seconds in the future`, - type: "ERR_FROM_FUTURE", - }, - ]); - - slots.getTime = getTime; - guard.pool.transactionExists = transactionExists; - }); - - it("should accept transaction with correct network byte", () => { - const transactionExists = guard.pool.transactionExists; - guard.pool.transactionExists = jest.fn(() => false); - - const canApply = guard.pool.walletManager.canApply; - guard.pool.walletManager.canApply = jest.fn(() => true); - - const tx = { - id: "1", - network: 23, - senderPublicKey: "023ee98f453661a1cb765fd60df95b4efb1e110660ffb88ae31c2368a70f1f7359", - }; - guard.__filterAndTransformTransactions([tx]); - - expect(guard.errors[tx.id]).not.toEqual([ - { - message: `Transaction network '${tx.network}' does not match '${configManager.get("pubKeyHash")}'`, - type: "ERR_WRONG_NETWORK", - }, - ]); - - guard.pool.transactionExists = transactionExists; - guard.pool.walletManager.canApply = canApply; - }); - - it("should accept transaction with missing network byte", () => { - const transactionExists = guard.pool.transactionExists; - guard.pool.transactionExists = jest.fn(() => false); - - const canApply = guard.pool.walletManager.canApply; - guard.pool.walletManager.canApply = jest.fn(() => true); - - const tx = { - id: "1", - senderPublicKey: "023ee98f453661a1cb765fd60df95b4efb1e110660ffb88ae31c2368a70f1f7359", - }; - guard.__filterAndTransformTransactions([tx]); - - expect(guard.errors[tx.id].type).not.toEqual("ERR_WRONG_NETWORK"); - - guard.pool.transactionExists = transactionExists; - guard.pool.walletManager.canApply = canApply; - }); - - it("should not accept transaction with wrong network byte", () => { - const transactionExists = guard.pool.transactionExists; - guard.pool.transactionExists = jest.fn(() => false); - - const canApply = guard.pool.walletManager.canApply; - guard.pool.walletManager.canApply = jest.fn(() => true); - - const tx = { - id: "1", - network: 2, - senderPublicKey: "023ee98f453661a1cb765fd60df95b4efb1e110660ffb88ae31c2368a70f1f7359", - }; - guard.__filterAndTransformTransactions([tx]); - - expect(guard.errors[tx.id]).toEqual([ - { - message: `Transaction network '${tx.network}' does not match '${configManager.get("pubKeyHash")}'`, - type: "ERR_WRONG_NETWORK", - }, - ]); - - guard.pool.transactionExists = transactionExists; - guard.pool.walletManager.canApply = canApply; - }); - - it("should not accept transaction if pool hasExceededMaxTransactions and add it to excess", () => { - const transactions = generateTransfers("unitnet", wallets[10].passphrase, wallets[11].address, 35, 1); - - jest.spyOn(guard.pool, "hasExceededMaxTransactions").mockImplementationOnce(tx => true); - - guard.__filterAndTransformTransactions(transactions); - - expect(guard.excess).toEqual([transactions[0].id]); - expect(guard.accept).toEqual(new Map()); - expect(guard.broadcast).toEqual(new Map()); - }); - - it("should push a ERR_UNKNOWN error if something threw in validated transaction block", () => { - const transactions = generateTransfers("unitnet", wallets[10].passphrase, wallets[11].address, 35, 1); - - // use guard.accept.set() call to introduce a throw - jest.spyOn(guard.accept, "set").mockImplementationOnce(() => { - throw new Error("hey"); - }); - - guard.__filterAndTransformTransactions(transactions); - - expect(guard.accept).toEqual(new Map()); - expect(guard.broadcast).toEqual(new Map()); - expect(guard.errors[transactions[0].id]).toEqual([ - { - message: `hey`, - type: "ERR_UNKNOWN", - }, - ]); - }); - }); - - describe("__validateTransaction", () => { - it("should not validate when recipient is not on the same network", async () => { - const transactions = generateTransfers( - "unitnet", - wallets[10].passphrase, - "DEJHR83JFmGpXYkJiaqn7wPGztwjheLAmY", - 35, - 1, - ); - - expect(guard.__validateTransaction(transactions[0])).toBeFalse(); - expect(guard.errors).toEqual({ - [transactions[0].id]: [ - { - type: "ERR_INVALID_RECIPIENT", - message: `Recipient ${ - transactions[0].recipientId - } is not on the same network: ${configManager.get("pubKeyHash")}`, - }, - ], - }); - }); - - it("should not validate a delegate registration if an existing registration for the same username from a different wallet exists in the pool", async () => { - const delegateRegistrations = [ - generateDelegateRegistration("unitnet", wallets[16].passphrase, 1, false, "test_delegate")[0], - generateDelegateRegistration("unitnet", wallets[17].passphrase, 1, false, "test_delegate")[0], - ]; - - expect(guard.__validateTransaction(delegateRegistrations[0])).toBeTrue(); - guard.accept.set(delegateRegistrations[0].id, delegateRegistrations[0]); - guard.__addTransactionsToPool(); - expect(guard.errors).toEqual({}); - expect(guard.__validateTransaction(delegateRegistrations[1])).toBeFalse(); - expect(guard.errors[delegateRegistrations[1].id]).toEqual([ - { - type: "ERR_PENDING", - message: `Delegate registration for "${ - delegateRegistrations[1].asset.delegate.username - }" already in the pool`, - }, - ]); - - const wallet1 = transactionPool.walletManager.findByPublicKey(wallets[16].keys.publicKey); - const wallet2 = transactionPool.walletManager.findByPublicKey(wallets[17].keys.publicKey); - - expect(wallet1.username).toBe("test_delegate"); - expect(wallet2.username).toBe(null); - }); - - it("should not validate when sender has same type transactions in the pool (only for 2nd sig, delegate registration, vote)", async () => { - jest.spyOn(guard.pool.walletManager, "canApply").mockImplementation(() => true); - const votes = [ - generateVote("unitnet", wallets[10].passphrase, delegates[0].publicKey, 1)[0], - generateVote("unitnet", wallets[10].passphrase, delegates[1].publicKey, 1)[0], - ]; - const delegateRegs = generateDelegateRegistration("unitnet", wallets[11].passphrase, 2); - const signatures = generateSecondSignature("unitnet", wallets[12].passphrase, 2); - - for (const transactions of [votes, delegateRegs, signatures]) { - await guard.validate([transactions[0]]); - expect(guard.__validateTransaction(transactions[1])).toBeFalse(); - expect(guard.errors[transactions[1].id]).toEqual([ - { - type: "ERR_PENDING", - message: - `Sender ${transactions[1].senderPublicKey} already has a transaction of type ` + - `'${constants.TransactionTypes[transactions[1].type]}' in the pool`, - }, - ]); - } - - jest.restoreAllMocks(); - }); - - it("should not validate unsupported transaction types", async () => { - jest.spyOn(guard.pool.walletManager, "canApply").mockImplementation(() => true); - - // use a random transaction as a base - then play with type - const baseTransaction = generateDelegateRegistration("unitnet", wallets[11].passphrase, 1)[0]; - - for (const transactionType of [ - constants.TransactionTypes.MultiSignature, - constants.TransactionTypes.Ipfs, - constants.TransactionTypes.TimelockTransfer, - constants.TransactionTypes.MultiPayment, - constants.TransactionTypes.DelegateResignation, - 99, - ]) { - baseTransaction.type = transactionType; - baseTransaction.id = transactionType; - - expect(guard.__validateTransaction(baseTransaction)).toBeFalse(); - expect(guard.errors[baseTransaction.id]).toEqual([ - { - type: "ERR_UNSUPPORTED", - message: `Invalidating transaction of unsupported type '${ - constants.TransactionTypes[transactionType] - }'`, - }, - ]); - } - - jest.restoreAllMocks(); - }); - }); - - describe("__removeForgedTransactions", () => { - it("should remove forged transactions", async () => { - const database = container.resolvePlugin("database"); - const getForgedTransactionsIds = database.getForgedTransactionsIds; - - const transfers = generateTransfers("unitnet", delegates[0].secret, delegates[0].senderPublicKey, 1, 4); - - transfers.forEach(tx => { - guard.accept.set(tx.id, tx); - guard.broadcast.set(tx.id, tx); - }); - - const forgedTx = transfers[2]; - database.getForgedTransactionsIds = jest.fn(() => [forgedTx.id]); - - await guard.__removeForgedTransactions(); - - expect(guard.accept.size).toBe(3); - expect(guard.broadcast.size).toBe(3); - - expect(guard.errors[forgedTx.id]).toHaveLength(1); - expect(guard.errors[forgedTx.id][0].type).toEqual("ERR_FORGED"); - - database.getForgedTransactionsIds = getForgedTransactionsIds; - }); - }); - - describe("__addTransactionsToPool", () => { - it("should add transactions to the pool", () => { - const transfers = generateTransfers("unitnet", delegates[0].secret, delegates[0].senderPublicKey, 1, 4); - - transfers.forEach(tx => { - guard.accept.set(tx.id, tx); - guard.broadcast.set(tx.id, tx); - }); - - expect(guard.errors).toEqual({}); - - guard.__addTransactionsToPool(); - - expect(guard.errors).toEqual({}); - expect(guard.accept.size).toBe(4); - expect(guard.broadcast.size).toBe(4); - }); - - it("should raise ERR_ALREADY_IN_POOL when adding existing transactions", () => { - const transfers = generateTransfers("unitnet", delegates[0].secret, delegates[0].senderPublicKey, 1, 4); - - transfers.forEach(tx => { - guard.accept.set(tx.id, tx); - guard.broadcast.set(tx.id, tx); - }); - - expect(guard.errors).toEqual({}); - - guard.__addTransactionsToPool(); - - expect(guard.errors).toEqual({}); - expect(guard.accept.size).toBe(4); - expect(guard.broadcast.size).toBe(4); - - // Adding again invokes ERR_ALREADY_IN_POOL - guard.__addTransactionsToPool(); - - expect(guard.accept.size).toBe(0); - expect(guard.broadcast.size).toBe(0); - - for (const transfer of transfers) { - expect(guard.errors[transfer.id]).toHaveLength(1); - expect(guard.errors[transfer.id][0].type).toEqual("ERR_ALREADY_IN_POOL"); - } - }); - - it("should raise ERR_POOL_FULL when attempting to add transactions to a full pool", () => { - const poolSize = transactionPool.options.maxTransactionsInPool; - transactionPool.options.maxTransactionsInPool = 3; - - const transfers = generateTransfers("unitnet", delegates[0].secret, delegates[0].senderPublicKey, 1, 4); - - transfers.forEach(tx => { - guard.accept.set(tx.id, tx); - guard.broadcast.set(tx.id, tx); - }); - - guard.__addTransactionsToPool(); - - expect(guard.accept.size).toBe(3); - expect(guard.broadcast.size).toBe(4); - - expect(guard.errors[transfers[3].id]).toHaveLength(1); - expect(guard.errors[transfers[3].id][0].type).toEqual("ERR_POOL_FULL"); - - transactionPool.options.maxTransactionsInPool = poolSize; - }); - }); - - describe("__pushError", () => { - it("should have error for transaction", () => { - expect(guard.errors).toBeEmpty(); - - guard.__pushError({ id: 1 }, "ERR_INVALID", "Invalid."); - - expect(guard.errors).toBeObject(); - expect(guard.errors["1"]).toBeArray(); - expect(guard.errors["1"]).toHaveLength(1); - expect(guard.errors["1"]).toEqual([{ message: "Invalid.", type: "ERR_INVALID" }]); - - expect(guard.invalid.size).toEqual(1); - expect(guard.invalid.entries().next().value[1]).toEqual({ id: 1 }); - }); - - it("should have multiple errors for transaction", () => { - expect(guard.errors).toBeEmpty(); - - guard.__pushError({ id: 1 }, "ERR_INVALID", "Invalid 1."); - guard.__pushError({ id: 1 }, "ERR_INVALID", "Invalid 2."); - - expect(guard.errors).toBeObject(); - expect(guard.errors["1"]).toBeArray(); - expect(guard.errors["1"]).toHaveLength(2); - expect(guard.errors["1"]).toEqual([ - { message: "Invalid 1.", type: "ERR_INVALID" }, - { message: "Invalid 2.", type: "ERR_INVALID" }, - ]); - - expect(guard.invalid.size).toEqual(1); - expect(guard.invalid.entries().next().value[1]).toEqual({ id: 1 }); - }); - }); -}); diff --git a/packages/core-transaction-pool/__tests__/manager.test.ts b/packages/core-transaction-pool/__tests__/manager.test.ts deleted file mode 100644 index 2ed028da2c..0000000000 --- a/packages/core-transaction-pool/__tests__/manager.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import "jest-extended"; -import { transactionPoolManager } from "../src/manager"; - -class FakeDriver { - public make() { - return this; - } -} - -describe("Transaction Pool Manager", () => { - describe("connection", () => { - it("should return the drive-connection", async () => { - await transactionPoolManager.makeConnection(new FakeDriver()); - - expect(transactionPoolManager.connection()).toBeInstanceOf(FakeDriver); - }); - - it("should return the drive-connection for a different name", async () => { - await transactionPoolManager.makeConnection(new FakeDriver(), "testing"); - - expect(transactionPoolManager.connection("testing")).toBeInstanceOf(FakeDriver); - }); - }); -}); diff --git a/packages/core-transaction-pool/package.json b/packages/core-transaction-pool/package.json index 18cd18f8c1..0423b13727 100644 --- a/packages/core-transaction-pool/package.json +++ b/packages/core-transaction-pool/package.json @@ -1,7 +1,7 @@ { "name": "@arkecosystem/core-transaction-pool", - "description": "Transaction Pool Manager for Ark Core", - "version": "2.2.1", + "description": "Transaction Pool Manager for ARK Core", + "version": "2.3.15", "contributors": [ "Kristjan Košič ", "Brian Faust ", @@ -16,46 +16,38 @@ "dist" ], "scripts": { - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", "prepublishOnly": "yarn build", "pretest": "bash ../../scripts/pre-test.sh", "compile": "../../node_modules/typescript/bin/tsc", "build": "yarn clean && yarn compile", "build:watch": "yarn clean && yarn compile -w", - "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "test": "cross-env CORE_ENV=test jest --runInBand --forceExit", - "test:coverage": "cross-env CORE_ENV=test jest --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$' --runInBand --forceExit", - "test:debug": "cross-env CORE_ENV=test node --inspect-brk ../../node_modules/.bin/jest --runInBand", - "test:watch": "cross-env CORE_ENV=test jest --runInBand --watch", - "test:watch:all": "cross-env CORE_ENV=test jest --runInBand --watchAll", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" + "clean": "del dist" }, "dependencies": { - "@arkecosystem/core-container": "^2.2.1", - "@arkecosystem/core-database": "^2.2.1", - "@arkecosystem/core-interfaces": "^2.2.1", - "@arkecosystem/crypto": "^2.2.1", + "@arkecosystem/core-container": "^2.3.15", + "@arkecosystem/core-database": "^2.3.15", + "@arkecosystem/core-interfaces": "^2.3.15", + "@arkecosystem/core-transactions": "^2.3.15", + "@arkecosystem/crypto": "^2.3.15", + "@faustbrian/dato": "^0.2.0", + "@types/better-sqlite3": "^5.2.2", + "@types/fs-extra": "^5.0.5", + "@types/pluralize": "^0.0.29", "better-sqlite3": "^5.4.0", "bs58check": "^2.1.2", - "dayjs-ext": "^2.2.0", "delay": "^4.1.0", "fs-extra": "^7.0.1", "pluralize": "^7.0.0" }, "devDependencies": { - "@arkecosystem/core-test-utils": "^2.2.1", - "@arkecosystem/core-utils": "^2.2.1", + "@arkecosystem/core-utils": "^2.3.15", "@types/better-sqlite3": "^5.2.2", - "@types/bip39": "^2.4.1", + "@types/bip39": "^2.4.2", "@types/fs-extra": "^5.0.5", "@types/pluralize": "^0.0.29", "@types/random-seed": "^0.3.3", "bip39": "^2.5.0", + "lodash.clonedeep": "^4.5.0", "random-seed": "^0.3.0" }, "publishConfig": { @@ -63,8 +55,5 @@ }, "engines": { "node": ">=10.x" - }, - "jest": { - "preset": "../../jest-preset.json" } } diff --git a/packages/core-transaction-pool/src/connection.ts b/packages/core-transaction-pool/src/connection.ts index 4baa937702..60322f93d0 100644 --- a/packages/core-transaction-pool/src/connection.ts +++ b/packages/core-transaction-pool/src/connection.ts @@ -1,18 +1,16 @@ import { app } from "@arkecosystem/core-container"; -import { Database, EventEmitter, Logger, TransactionPool as transactionPool } from "@arkecosystem/core-interfaces"; +import { Blockchain, Database, EventEmitter, Logger, TransactionPool } from "@arkecosystem/core-interfaces"; +import { TransactionHandlerRegistry } from "@arkecosystem/core-transactions"; +import { dato, Dato } from "@faustbrian/dato"; import assert from "assert"; -import dayjs from "dayjs-ext"; import { PoolWalletManager } from "./pool-wallet-manager"; +import { Bignum, constants, ITransactionData, models, Transaction } from "@arkecosystem/crypto"; import { Mem } from "./mem"; import { MemPoolTransaction } from "./mem-pool-transaction"; import { Storage } from "./storage"; -const databaseService = app.resolvePlugin("database"); -const emitter = app.resolvePlugin("event-emitter"); -const logger = app.resolvePlugin("logger"); - /** * Transaction pool. It uses a hybrid storage - caching the data * in memory and occasionally saving it to a persistent, on-disk storage (SQLite), @@ -20,12 +18,16 @@ const logger = app.resolvePlugin("logger"); * data (everything other than add or remove transaction) are served from the * in-memory storage. */ -export class TransactionPool implements transactionPool.ITransactionPool { - public walletManager: any; - public blockedByPublicKey: any; - public mem: any; - public storage: any; - public loggedAllowedSenders: any[]; +export class Connection implements TransactionPool.IConnection { + public walletManager: PoolWalletManager; + public mem: Mem; + public storage: Storage; + public loggedAllowedSenders: string[]; + private blockedByPublicKey: { [key: string]: Dato }; + + private readonly databaseService = app.resolvePlugin("database"); + private readonly emitter = app.resolvePlugin("event-emitter"); + private readonly logger = app.resolvePlugin("logger"); /** * Create a new transaction pool instance. @@ -35,12 +37,13 @@ export class TransactionPool implements transactionPool.ITransactionPool { this.walletManager = new PoolWalletManager(); this.blockedByPublicKey = {}; } + /** * Make the transaction pool instance. Load all transactions in the pool from * the on-disk database, saved there from a previous run. * @return {TransactionPool} */ - public async make() { + public async make(): Promise { this.mem = new Mem(); this.storage = new Storage(this.options.storage); this.loggedAllowedSenders = []; @@ -53,7 +56,7 @@ export class TransactionPool implements transactionPool.ITransactionPool { // Remove transactions that were forged while we were offline. const allIds = all.map(memPoolTransaction => memPoolTransaction.transaction.id); - const forgedIds = await databaseService.getForgedTransactionsIds(allIds); + const forgedIds = await this.databaseService.getForgedTransactionsIds(allIds); forgedIds.forEach(id => this.removeTransactionById(id)); @@ -70,7 +73,6 @@ export class TransactionPool implements transactionPool.ITransactionPool { /** * Disconnect from transaction pool. - * @return {void} */ public disconnect() { this.__syncToPersistentStorage(); @@ -90,9 +92,8 @@ export class TransactionPool implements transactionPool.ITransactionPool { /** * Get the number of transactions in the pool. - * @return {Number} */ - public getPoolSize() { + public getPoolSize(): number { this.__purgeExpired(); return this.mem.getSize(); @@ -100,10 +101,8 @@ export class TransactionPool implements transactionPool.ITransactionPool { /** * Get the number of transactions in the pool from a specific sender - * @param {String} senderPublicKey - * @returns {Number} */ - public getSenderSize(senderPublicKey) { + public getSenderSize(senderPublicKey: string): number { this.__purgeExpired(); return this.mem.getBySender(senderPublicKey).size; @@ -119,20 +118,28 @@ export class TransactionPool implements transactionPool.ITransactionPool { * notAdded: [ { transaction: Transaction, type: String, message: String }, ... ] * } */ - public addTransactions(transactions) { + public addTransactions(transactions: Transaction[]) { const added = []; const notAdded = []; - for (const t of transactions) { - const result = this.addTransaction(t); + for (const transaction of transactions) { + const result = this.addTransaction(transaction); if (result.success) { - added.push(t); + added.push(transaction); } else { notAdded.push(result); } } + if (added.length > 0) { + this.emitter.emit("transaction.pool.added", added); + } + + if (notAdded.length > 0) { + this.emitter.emit("transaction.pool.rejected", notAdded); + } + return { added, notAdded }; } @@ -143,9 +150,9 @@ export class TransactionPool implements transactionPool.ITransactionPool { * and applied to the pool or not. In case it was not successful, the type and message * property yield information about the error. */ - public addTransaction(transaction) { + public addTransaction(transaction: Transaction): TransactionPool.IAddTransactionResponse { if (this.transactionExists(transaction.id)) { - logger.debug( + this.logger.debug( "Transaction pool: ignoring attempt to add a transaction that is already " + `in the pool, id: ${transaction.id}`, ); @@ -161,16 +168,19 @@ export class TransactionPool implements transactionPool.ITransactionPool { const all = this.mem.getTransactionsOrderedByFee(); const lowest = all[all.length - 1].transaction; - if (lowest.fee.isLessThan(transaction.fee)) { + const fee = transaction.data.fee as Bignum; + const lowestFee = lowest.data.fee as Bignum; + + if (lowestFee.isLessThan(fee)) { this.walletManager.revertTransactionForSender(lowest); - this.mem.remove(lowest.id, lowest.senderPublicKey); + this.mem.remove(lowest.id, lowest.data.senderPublicKey); } else { return this.__createError( transaction, "ERR_POOL_FULL", `Pool is full (has ${poolSize} transactions) and this transaction's fee ` + - `${transaction.fee.toFixed()} is not higher than the lowest fee already in pool ` + - `${lowest.fee.toFixed()}`, + `${fee.toFixed()} is not higher than the lowest fee already in pool ` + + `${lowestFee.toFixed()}`, ); } } @@ -178,15 +188,16 @@ export class TransactionPool implements transactionPool.ITransactionPool { this.mem.add(new MemPoolTransaction(transaction), this.options.maxTransactionAge); // Apply transaction to pool wallet manager. - const senderWallet = this.walletManager.findByPublicKey(transaction.senderPublicKey); + const senderWallet = this.walletManager.findByPublicKey(transaction.data.senderPublicKey); + // TODO: rework error handling const errors = []; - if (this.walletManager.canApply(transaction.data, errors)) { - senderWallet.applyTransactionToSender(transaction); + if (this.walletManager.canApply(transaction, errors)) { + const transactionHandler = TransactionHandlerRegistry.get(transaction.type); + transactionHandler.applyToSender(transaction, senderWallet); } else { // Remove tx again from the pool this.mem.remove(transaction.id); - return this.__createError(transaction, "ERR_APPLY", JSON.stringify(errors)); } @@ -195,41 +206,34 @@ export class TransactionPool implements transactionPool.ITransactionPool { } /** - * Remove a transaction from the pool by transaction object. - * @param {Transaction} transaction - * @return {void} + * Remove a transaction from the pool by transaction. */ - public removeTransaction(transaction) { - this.removeTransactionById(transaction.id, transaction.senderPublicKey); + public removeTransaction(transaction: Transaction) { + this.removeTransactionById(transaction.id, transaction.data.senderPublicKey); } /** * Remove a transaction from the pool by id. - * @param {String} id - * @param {String} senderPublicKey - * @return {void} */ - public removeTransactionById(id, senderPublicKey?) { + public removeTransactionById(id: string, senderPublicKey?: string) { this.mem.remove(id, senderPublicKey); this.__syncToPersistentStorageIfNecessary(); + + this.emitter.emit("transaction.pool.removed", id); } /** * Get all transactions that are ready to be forged. - * @param {Number} blockSize - * @return {(Array|void)} */ - public getTransactionsForForging(blockSize) { - return this.getTransactions(0, blockSize, this.options.maxTransactionBytes); + public getTransactionsForForging(blockSize: number): string[] { + return this.getTransactions(0, blockSize, this.options.maxTransactionBytes).map(tx => tx.toString("hex")); } /** * Get a transaction by transaction id. - * @param {String} id - * @return {(Transaction|undefined)} */ - public getTransaction(id) { + public getTransaction(id: string): Transaction { this.__purgeExpired(); return this.mem.getTransactionById(id); @@ -237,36 +241,24 @@ export class TransactionPool implements transactionPool.ITransactionPool { /** * Get all transactions within the specified range [start, start + size), ordered by fee. - * @param {Number} start - * @param {Number} size - * @param {Number} maxBytes for the total transaction array or 0 for no limit - * @return {(Array|void)} array of serialized transaction hex strings */ - public getTransactions(start, size, maxBytes?: number) { - return this.getTransactionsData(start, size, "serialized", maxBytes); + public getTransactions(start: number, size: number, maxBytes?: number): Buffer[] { + return this.getTransactionsData(start, size, "serialized", maxBytes) as Buffer[]; } /** * Get all transactions within the specified range [start, start + size). - * @param {Number} start - * @param {Number} size - * @return {Array} array of transactions IDs in the specified range */ - public getTransactionIdsForForging(start, size) { - return this.getTransactionsData(start, size, "id", this.options.maxTransactionBytes); + public getTransactionIdsForForging(start: number, size: number): string[] { + return this.getTransactionsData(start, size, "id", this.options.maxTransactionBytes) as string[]; } /** * Get data from all transactions within the specified range [start, start + size). * Transactions are ordered by fee (highest fee first) or by * insertion time, if fees equal (earliest transaction first). - * @param {Number} start - * @param {Number} size - * @param {Number} maxBytes for the total transaction array or 0 for no limit - * @param {String} property - * @return {Array} array of transaction[property] */ - public getTransactionsData(start, size, property, maxBytes = 0) { + public getTransactionsData(start: number, size: number, property: string, maxBytes = 0): string[] | Buffer[] { this.__purgeExpired(); const data = []; @@ -306,24 +298,20 @@ export class TransactionPool implements transactionPool.ITransactionPool { /** * Remove all transactions from the transaction pool belonging to specific sender. - * @param {String} senderPublicKey - * @return {void} */ - public removeTransactionsForSender(senderPublicKey) { + public removeTransactionsForSender(senderPublicKey: string) { this.mem.getBySender(senderPublicKey).forEach(e => this.removeTransactionById(e.transaction.id)); } /** * Check whether sender of transaction has exceeded max transactions in queue. - * @param {Transaction} transaction - * @return {Boolean} true if exceeded */ - public hasExceededMaxTransactions(transaction) { + public hasExceededMaxTransactions(transaction: ITransactionData): boolean { this.__purgeExpired(); if (this.options.allowedSenders.includes(transaction.senderPublicKey)) { if (!this.loggedAllowedSenders.includes(transaction.senderPublicKey)) { - logger.debug( + this.logger.debug( `Transaction pool: allowing sender public key: ${ transaction.senderPublicKey } (listed in options.allowedSenders), thus skipping throttling.`, @@ -341,7 +329,6 @@ export class TransactionPool implements transactionPool.ITransactionPool { /** * Flush the pool (delete all transactions from it). - * @return {void} */ public flush() { this.mem.flush(); @@ -351,10 +338,8 @@ export class TransactionPool implements transactionPool.ITransactionPool { /** * Checks if a transaction exists in the pool. - * @param {String} transactionId - * @return {Boolean} */ - public transactionExists(transactionId) { + public transactionExists(transactionId: string): boolean { if (!this.mem.transactionExists(transactionId)) { // If it does not exist then no need to purge expired transactions because // we know it will not exist after purge too. @@ -368,15 +353,13 @@ export class TransactionPool implements transactionPool.ITransactionPool { /** * Check if transaction sender is blocked - * @param {String} senderPublicKey - * @return {Boolean} */ - public isSenderBlocked(senderPublicKey) { + public isSenderBlocked(senderPublicKey: string): boolean { if (!this.blockedByPublicKey[senderPublicKey]) { return false; } - if (this.blockedByPublicKey[senderPublicKey] < dayjs()) { + if (dato().isAfter(this.blockedByPublicKey[senderPublicKey])) { delete this.blockedByPublicKey[senderPublicKey]; return false; } @@ -386,15 +369,13 @@ export class TransactionPool implements transactionPool.ITransactionPool { /** * Blocks sender for a specified time - * @param {String} senderPublicKey - * @return {Time} blockReleaseTime */ - public blockSender(senderPublicKey) { - const blockReleaseTime = dayjs().add(1, "hour"); + public blockSender(senderPublicKey: string): Dato { + const blockReleaseTime = dato().addHours(1); this.blockedByPublicKey[senderPublicKey] = blockReleaseTime; - logger.warn(`Sender ${senderPublicKey} blocked until ${this.blockedByPublicKey[senderPublicKey]} :stopwatch:`); + this.logger.warn(`Sender ${senderPublicKey} blocked until ${this.blockedByPublicKey[senderPublicKey].toUTC()}`); return blockReleaseTime; } @@ -403,14 +384,13 @@ export class TransactionPool implements transactionPool.ITransactionPool { * Processes recently accepted block by the blockchain. * It removes block transaction from the pool and adjusts * pool wallets for non existing transactions. - * - * @param {Object} block - * @return {void} */ - public acceptChainedBlock(block) { - for (const { data } of block.transactions) { + public acceptChainedBlock(block: models.Block) { + for (const transaction of block.transactions) { + const { data } = transaction; const exists = this.transactionExists(data.id); const senderPublicKey = data.senderPublicKey; + const transactionHandler = TransactionHandlerRegistry.get(transaction.type); const senderWallet = this.walletManager.exists(senderPublicKey) ? this.walletManager.findByPublicKey(senderPublicKey) @@ -421,25 +401,28 @@ export class TransactionPool implements transactionPool.ITransactionPool { : false; if (recipientWallet) { - recipientWallet.applyTransactionToRecipient(data); + transactionHandler.applyToRecipient(transaction, recipientWallet); } if (exists) { - this.removeTransaction(data); + this.removeTransaction(transaction); } else if (senderWallet) { - const errors = []; - if (senderWallet.canApply(data, errors)) { - senderWallet.applyTransactionToSender(data); - } else { + // TODO: rework error handling + try { + transactionHandler.canBeApplied(transaction, senderWallet); + } catch (error) { this.purgeByPublicKey(data.senderPublicKey); this.blockSender(data.senderPublicKey); - logger.error( + this.logger.error( `CanApply transaction test failed on acceptChainedBlock() in transaction pool for transaction id:${ data.id - } due to ${JSON.stringify(errors)}. Possible double spending attack :bomb:`, + } due to ${error.message}. Possible double spending attack`, ); + return; } + + transactionHandler.applyToSender(transaction, senderWallet); } if ( @@ -454,7 +437,7 @@ export class TransactionPool implements transactionPool.ITransactionPool { // if delegate in poll wallet manager - apply rewards and fees if (this.walletManager.exists(block.data.generatorPublicKey)) { const delegateWallet = this.walletManager.findByPublicKey(block.data.generatorPublicKey); - const increase = block.data.reward.plus(block.data.totalFee); + const increase = (block.data.reward as Bignum).plus(block.data.totalFee); delegateWallet.balance = delegateWallet.balance.plus(increase); } @@ -466,13 +449,12 @@ export class TransactionPool implements transactionPool.ITransactionPool { * Removes all the wallets from pool manager and applies transaction from pool - if any * It waits for the node to sync, and then check the transactions in pool * and validates them and apply to the pool manager. - * @return {void} */ public async buildWallets() { this.walletManager.reset(); const poolTransactionIds = await this.getTransactionIdsForForging(0, this.getPoolSize()); - app.resolve("state").removeCachedTransactionIds(poolTransactionIds); + app.resolve("state").removeCachedTransactionIds(poolTransactionIds); poolTransactionIds.forEach(transactionId => { const transaction = this.getTransaction(transactionId); @@ -480,20 +462,23 @@ export class TransactionPool implements transactionPool.ITransactionPool { return; } - const senderWallet = this.walletManager.findByPublicKey(transaction.senderPublicKey); - const errors = []; - if (senderWallet && senderWallet.canApply(transaction.data, errors)) { - senderWallet.applyTransactionToSender(transaction); - } else { - logger.error(`BuildWallets from pool: ${JSON.stringify(errors)}`); - this.purgeByPublicKey(transaction.senderPublicKey); + const transactionHandler = TransactionHandlerRegistry.get(transaction.type); + const senderWallet = this.walletManager.findByPublicKey(transaction.data.senderPublicKey); + + // TODO: rework error handling + try { + transactionHandler.canBeApplied(transaction, senderWallet); + transactionHandler.applyToSender(transaction, senderWallet); + } catch (error) { + this.logger.error(`BuildWallets from pool: ${error.message}`); + this.purgeByPublicKey(transaction.data.senderPublicKey); } }); - logger.info("Transaction Pool Manager build wallets complete"); + this.logger.info("Transaction Pool Manager build wallets complete"); } - public purgeByPublicKey(senderPublicKey) { - logger.debug(`Purging sender: ${senderPublicKey} from pool wallet manager`); + public purgeByPublicKey(senderPublicKey: string) { + this.logger.debug(`Purging sender: ${senderPublicKey} from pool wallet manager`); this.removeTransactionsForSender(senderPublicKey); @@ -503,10 +488,9 @@ export class TransactionPool implements transactionPool.ITransactionPool { /** * Purges all transactions from senders with at least one * invalid transaction. - * @param {Block} block */ - public purgeSendersWithInvalidTransactions(block) { - const publicKeys = new Set(block.transactions.filter(tx => !tx.verified).map(tx => tx.senderPublicKey)); + public purgeSendersWithInvalidTransactions(block: models.Block) { + const publicKeys = new Set(block.transactions.filter(tx => !tx.verified).map(tx => tx.data.senderPublicKey)); publicKeys.forEach(publicKey => this.purgeByPublicKey(publicKey)); } @@ -514,13 +498,12 @@ export class TransactionPool implements transactionPool.ITransactionPool { /** * Purges all transactions from the block. * Purges if transaction exists. It assumes that if trx exists that also wallet exists in pool - * @param {Block} block */ - public purgeBlock(block) { + public purgeBlock(block: models.Block) { block.transactions.forEach(tx => { if (this.transactionExists(tx.id)) { this.removeTransaction(tx); - this.walletManager.findByPublicKey(tx.senderPublicKey).revertTransactionForSender(tx); + this.walletManager.revertTransactionForSender(tx); } }); } @@ -528,12 +511,8 @@ export class TransactionPool implements transactionPool.ITransactionPool { /** * Check whether a given sender has any transactions of the specified type * in the pool. - * @param {String} senderPublicKey public key of the sender - * @param {Number} transactionType transaction type, must be one of - * TransactionTypes.* and is compared against transaction.type. - * @return {Boolean} true if exist */ - public senderHasTransactionsOfType(senderPublicKey, transactionType) { + public senderHasTransactionsOfType(senderPublicKey: string, transactionType: constants.TransactionTypes): boolean { this.__purgeExpired(); for (const memPoolTransaction of this.mem.getBySender(senderPublicKey)) { @@ -548,7 +527,6 @@ export class TransactionPool implements transactionPool.ITransactionPool { /** * Sync the in-memory storage to the persistent (on-disk) storage if too * many changes have been accumulated in-memory. - * @return {void} */ public __syncToPersistentStorageIfNecessary() { if (this.options.syncInterval <= this.mem.getNumberOfDirty()) { @@ -569,12 +547,12 @@ export class TransactionPool implements transactionPool.ITransactionPool { /** * Create an error object which the TransactionGuard understands. - * @param {Transaction} transaction - * @param {String} type - * @param {String} message - * @return {Object} */ - public __createError(transaction, type, message) { + public __createError( + transaction: Transaction, + type: string, + message: string, + ): TransactionPool.IAddTransactionErrorResponse { return { transaction, type, @@ -585,14 +563,13 @@ export class TransactionPool implements transactionPool.ITransactionPool { /** * Remove all transactions from the pool that have expired. - * @return {void} */ private __purgeExpired() { for (const transaction of this.mem.getExpired(this.options.maxTransactionAge)) { - emitter.emit("transaction.expired", transaction.data); + this.emitter.emit("transaction.expired", transaction.data); this.walletManager.revertTransactionForSender(transaction); - this.mem.remove(transaction.id, transaction.senderPublicKey); + this.mem.remove(transaction.id, transaction.data.senderPublicKey); this.__syncToPersistentStorageIfNecessary(); } } diff --git a/packages/core-transaction-pool/src/defaults.ts b/packages/core-transaction-pool/src/defaults.ts index f0343163ce..19cf20efb3 100644 --- a/packages/core-transaction-pool/src/defaults.ts +++ b/packages/core-transaction-pool/src/defaults.ts @@ -9,7 +9,7 @@ export const defaults = { maxTransactionsInPool: process.env.CORE_MAX_TRANSACTIONS_IN_POOL || 100000, maxTransactionsPerSender: process.env.CORE_TRANSACTION_POOL_MAX_PER_SENDER || 300, allowedSenders: [], - maxTransactionsPerRequest: process.env.CORE_TRANSACTION_POOL_MAX_PER_REQUEST || 40, + maxTransactionsPerRequest: process.env.CORE_TRANSACTION_POOL_MAX_PER_REQUEST || 150, maxTransactionBytes: process.env.CORE_TRANSACTION_POOL_MAX_TRANSACTIONS_SIZE || 1047876, maxTransactionAge: 21600, dynamicFees: { diff --git a/packages/core-transaction-pool/src/dynamic-fee/index.ts b/packages/core-transaction-pool/src/dynamic-fee/index.ts index 9bf385052f..97810659c6 100644 --- a/packages/core-transaction-pool/src/dynamic-fee/index.ts +++ b/packages/core-transaction-pool/src/dynamic-fee/index.ts @@ -1,3 +1 @@ -import { calculateFee, dynamicFeeMatcher } from "./matcher"; - -export { calculateFee, dynamicFeeMatcher }; +export { calculateFee, dynamicFeeMatcher } from "./matcher"; diff --git a/packages/core-transaction-pool/src/dynamic-fee/matcher.ts b/packages/core-transaction-pool/src/dynamic-fee/matcher.ts index 3b71cc7bbe..08345ce768 100644 --- a/packages/core-transaction-pool/src/dynamic-fee/matcher.ts +++ b/packages/core-transaction-pool/src/dynamic-fee/matcher.ts @@ -1,23 +1,25 @@ import { app } from "@arkecosystem/core-container"; import { Logger } from "@arkecosystem/core-interfaces"; -import { constants, feeManager, formatSatoshi } from "@arkecosystem/crypto"; -import camelCase from "lodash/camelCase"; +import { Bignum, constants, feeManager, formatSatoshi, Transaction } from "@arkecosystem/crypto"; +import camelCase from "lodash.camelcase"; import { config as localConfig } from "../config"; /** * Calculate minimum fee of a transaction for entering the pool. - * @param {Number} Minimum fee SATOSHI/byte - * @param {Transaction} Transaction for which we calculate the fee - * @returns {Number} Calculated minimum acceptable fee in SATOSHI */ -export function calculateFee(satoshiPerByte, transaction) { +export function calculateFee(satoshiPerByte: number, transaction: Transaction): number { if (satoshiPerByte <= 0) { satoshiPerByte = 1; } - const addonBytes = localConfig.get("dynamicFees.addonBytes")[ - camelCase(constants.TransactionTypes[transaction.type]) - ]; + let key; + if (transaction.type in constants.TransactionTypes) { + key = camelCase(constants.TransactionTypes[transaction.type]); + } else { + key = camelCase(transaction.constructor.name.replace("Transaction", "")); + } + + const addonBytes = localConfig.get("dynamicFees.addonBytes")[key]; // serialized is in hex const transactionSizeInBytes = transaction.serialized.length / 2; @@ -31,10 +33,10 @@ export function calculateFee(satoshiPerByte, transaction) { * @param {Transaction} Transaction - transaction to check * @return {Object} { broadcast: Boolean, enterPool: Boolean } */ -export function dynamicFeeMatcher(transaction) { +export function dynamicFeeMatcher(transaction: Transaction): { broadcast: boolean; enterPool: boolean } { const logger = app.resolvePlugin("logger"); - const fee = +transaction.fee.toFixed(); + const fee = +(transaction.data.fee as Bignum).toFixed(); const id = transaction.id; const dynamicFees = localConfig.get("dynamicFees"); @@ -79,7 +81,7 @@ export function dynamicFeeMatcher(transaction) { } } else { // Static fees - const staticFee = feeManager.getForTransaction(transaction); + const staticFee = feeManager.getForTransaction(transaction.data); if (fee === staticFee) { broadcast = true; diff --git a/packages/core-transaction-pool/src/factory.ts b/packages/core-transaction-pool/src/factory.ts new file mode 100644 index 0000000000..afa8add7a9 --- /dev/null +++ b/packages/core-transaction-pool/src/factory.ts @@ -0,0 +1,7 @@ +import { TransactionPool } from "@arkecosystem/core-interfaces"; + +export class ConnectionFactory { + public async make(connection: TransactionPool.IConnection): Promise { + return connection.make(); + } +} diff --git a/packages/core-transaction-pool/src/guard.ts b/packages/core-transaction-pool/src/guard.ts index c7a66fcf2a..13de7a2e53 100644 --- a/packages/core-transaction-pool/src/guard.ts +++ b/packages/core-transaction-pool/src/guard.ts @@ -1,44 +1,28 @@ import { app } from "@arkecosystem/core-container"; -import { Database, Logger, TransactionPool as transactionPool } from "@arkecosystem/core-interfaces"; -import { configManager, constants, models, slots } from "@arkecosystem/crypto"; +import { Blockchain, Database, Logger, TransactionPool } from "@arkecosystem/core-interfaces"; +import { errors, TransactionHandlerRegistry } from "@arkecosystem/core-transactions"; +import { + configManager, + constants, + errors as cryptoErrors, + ITransactionData, + slots, + Transaction, +} from "@arkecosystem/crypto"; import pluralize from "pluralize"; -import { TransactionPool } from "./connection"; import { dynamicFeeMatcher } from "./dynamic-fee"; -import { MemPoolTransaction } from "./mem-pool-transaction"; -import { isRecipientOnActiveNetwork } from "./utils"; -const { TransactionTypes } = constants; -const { Transaction } = models; - -export class TransactionGuard implements transactionPool.ITransactionGuard { - public transactions: models.Transaction[] = []; +export class TransactionGuard implements TransactionPool.IGuard { + public transactions: ITransactionData[] = []; public excess: string[] = []; - public accept: Map = new Map(); - public broadcast: Map = new Map(); - public invalid: Map = new Map(); - public errors: { [key: string]: transactionPool.TransactionErrorDTO[] } = {}; + public accept: Map = new Map(); + public broadcast: Map = new Map(); + public invalid: Map = new Map(); + public errors: { [key: string]: TransactionPool.ITransactionErrorResponse[] } = {}; - /** - * Create a new transaction guard instance. - * @param {TransactionPoolInterface} pool - * @return {void} - */ - constructor(private pool: TransactionPool) {} + constructor(public pool: TransactionPool.IConnection) {} - /** - * Validate the specified transactions and accepted transactions to the pool. - * @param {Array} transactions - * @return Object { - * accept: array of transaction ids that qualify for entering the pool - * broadcast: array of of transaction ids that qualify for broadcasting - * invalid: array of invalid transaction ids - * excess: array of transaction ids that exceed sender's quota in the pool - * errors: Object with - * keys=transaction id (for each element in invalid[]), - * value=[ { type, message }, ... ] - * } - */ - public async validate(transactions: models.Transaction[]): Promise { + public async validate(transactions: ITransactionData[]): Promise { this.pool.loggedAllowedSenders = []; // Cache transactions @@ -69,14 +53,13 @@ export class TransactionGuard implements transactionPool.ITransactionGuard { /** * Cache the given transactions and return which got added. Already cached * transactions are not returned. - * @return {Array} */ - public __cacheTransactions(transactions) { - const { added, notAdded } = app.resolve("state").cacheTransactions(transactions); + public __cacheTransactions(transactions: ITransactionData[]) { + const { added, notAdded } = app.resolve("state").cacheTransactions(transactions); notAdded.forEach(transaction => { if (!this.errors[transaction.id]) { - this.__pushError(transaction, "ERR_DUPLICATE", "Already in cache."); + this.pushError(transaction, "ERR_DUPLICATE", "Already in cache."); } }); @@ -85,9 +68,8 @@ export class TransactionGuard implements transactionPool.ITransactionGuard { /** * Get broadcast transactions. - * @return {Array} */ - public getBroadcastTransactions(): models.Transaction[] { + public getBroadcastTransactions(): Transaction[] { return Array.from(this.broadcast.values()); } @@ -101,23 +83,21 @@ export class TransactionGuard implements transactionPool.ITransactionGuard { * - dynamic fee mismatch * - transactions based on type specific restrictions * - not valid crypto transactions - * @param {Array} transactions - * @return {void} */ - public __filterAndTransformTransactions(transactions) { + public __filterAndTransformTransactions(transactions: ITransactionData[]): void { transactions.forEach(transaction => { const exists = this.pool.transactionExists(transaction.id); if (exists) { - this.__pushError(transaction, "ERR_DUPLICATE", `Duplicate transaction ${transaction.id}`); + this.pushError(transaction, "ERR_DUPLICATE", `Duplicate transaction ${transaction.id}`); } else if (this.pool.isSenderBlocked(transaction.senderPublicKey)) { - this.__pushError( + this.pushError( transaction, "ERR_SENDER_BLOCKED", `Transaction ${transaction.id} rejected. Sender ${transaction.senderPublicKey} is blocked.`, ); } else if (JSON.stringify(transaction).length > this.pool.options.maxTransactionBytes) { - this.__pushError( + this.pushError( transaction, "ERR_TOO_LARGE", `Transaction ${transaction.id} is larger than ${this.pool.options.maxTransactionBytes} bytes.`, @@ -126,34 +106,44 @@ export class TransactionGuard implements transactionPool.ITransactionGuard { this.excess.push(transaction.id); } else if (this.__validateTransaction(transaction)) { try { - const trx = new Transaction(transaction); + const receivedId = transaction.id; + const trx = Transaction.fromData(transaction); if (trx.verified) { - const dynamicFee = dynamicFeeMatcher(trx); - - if (!dynamicFee.enterPool && !dynamicFee.broadcast) { - this.__pushError( - transaction, - "ERR_LOW_FEE", - "The fee is too low to broadcast and accept the transaction", - ); - } else { - if (dynamicFee.enterPool) { - this.accept.set(trx.id, trx); - } - - if (dynamicFee.broadcast) { - this.broadcast.set(trx.id, trx); + const applyErrors = []; + if (this.pool.walletManager.canApply(trx, applyErrors)) { + const dynamicFee = dynamicFeeMatcher(trx); + if (!dynamicFee.enterPool && !dynamicFee.broadcast) { + this.pushError( + transaction, + "ERR_LOW_FEE", + "The fee is too low to broadcast and accept the transaction", + ); + } else { + if (dynamicFee.enterPool) { + this.accept.set(trx.data.id, trx); + } + + if (dynamicFee.broadcast) { + this.broadcast.set(trx.data.id, trx); + } } + } else { + this.pushError(transaction, "ERR_APPLY", JSON.stringify(applyErrors)); } } else { - this.__pushError( + transaction.id = receivedId; + this.pushError( transaction, "ERR_BAD_DATA", "Transaction didn't pass the verification process.", ); } } catch (error) { - this.__pushError(transaction, "ERR_UNKNOWN", error.message); + if (error instanceof cryptoErrors.TransactionSchemaError) { + this.pushError(transaction, "ERR_TRANSACTION_SCHEMA", error.message); + } else { + this.pushError(transaction, "ERR_UNKNOWN", error.message); + } } } }); @@ -169,11 +159,11 @@ export class TransactionGuard implements transactionPool.ITransactionGuard { * - if sender already has another transaction of the same type, for types that * - only allow one transaction at a time in the pool (e.g. vote) */ - public __validateTransaction(transaction) { + public __validateTransaction(transaction: ITransactionData): boolean { const now = slots.getTime(); if (transaction.timestamp > now + 3600) { const secondsInFuture = transaction.timestamp - now; - this.__pushError( + this.pushError( transaction, "ERR_FROM_FUTURE", `Transaction ${transaction.id} is ${secondsInFuture} seconds in the future`, @@ -181,44 +171,8 @@ export class TransactionGuard implements transactionPool.ITransactionGuard { return false; } - const errors = []; - - // This check must come before canApply otherwise a wallet may be incorrectly assigned a username when multiple - // conflicting delegate registrations for the same username exist in the same transaction payload - if (transaction.type === TransactionTypes.DelegateRegistration) { - const username = transaction.asset.delegate.username; - const delegateRegistrationsInPayload = this.transactions.filter( - tx => tx.type === TransactionTypes.DelegateRegistration && tx.asset.delegate.username === username, - ); - if (delegateRegistrationsInPayload.length > 1) { - this.__pushError( - transaction, - "ERR_CONFLICT", - `Multiple delegate registrations for "${username}" in transaction payload`, - ); - return false; - } - - const delegateRegistrationsInPool: MemPoolTransaction[] = Array.from( - this.pool.getTransactionsByType(TransactionTypes.DelegateRegistration), - ); - if (delegateRegistrationsInPool.some(memTx => memTx.transaction.asset.delegate.username === username)) { - this.__pushError( - transaction, - "ERR_PENDING", - `Delegate registration for "${username}" already in the pool`, - ); - return false; - } - } - - if (!this.pool.walletManager.canApply(transaction, errors)) { - this.__pushError(transaction, "ERR_APPLY", JSON.stringify(errors)); - return false; - } - if (transaction.network && transaction.network !== configManager.get("pubKeyHash")) { - this.__pushError( + this.pushError( transaction, "ERR_WRONG_NETWORK", `Transaction network '${transaction.network}' does not match '${configManager.get("pubKeyHash")}'`, @@ -226,52 +180,27 @@ export class TransactionGuard implements transactionPool.ITransactionGuard { return false; } - switch (transaction.type) { - case TransactionTypes.Transfer: - if (!isRecipientOnActiveNetwork(transaction)) { - this.__pushError( - transaction, - "ERR_INVALID_RECIPIENT", - `Recipient ${transaction.recipientId} is not on the same network: ${configManager.get( - "pubKeyHash", - )}`, - ); - return false; - } - break; - case TransactionTypes.SecondSignature: - case TransactionTypes.DelegateRegistration: - case TransactionTypes.Vote: - if (this.pool.senderHasTransactionsOfType(transaction.senderPublicKey, transaction.type)) { - this.__pushError( - transaction, - "ERR_PENDING", - `Sender ${transaction.senderPublicKey} already has a transaction of type ` + - `'${TransactionTypes[transaction.type]}' in the pool`, - ); - return false; - } - break; - case TransactionTypes.MultiSignature: - case TransactionTypes.Ipfs: - case TransactionTypes.TimelockTransfer: - case TransactionTypes.MultiPayment: - case TransactionTypes.DelegateResignation: - default: - this.__pushError( + const { type } = transaction; + try { + const handler = TransactionHandlerRegistry.get(type); + return handler.canEnterTransactionPool(transaction, this); + } catch (error) { + if (error instanceof errors.InvalidTransactionTypeError) { + this.pushError( transaction, "ERR_UNSUPPORTED", - "Invalidating transaction of unsupported type " + `'${TransactionTypes[transaction.type]}'`, + `Invalidating transaction of unsupported type '${constants.TransactionTypes[type]}'`, ); - return false; + } else { + this.pushError(transaction, "ERR_UNKNOWN", error.message); + } } - return true; + return false; } /** * Remove already forged transactions. - * @return {void} */ public async __removeForgedTransactions() { const databaseService = app.resolvePlugin("database"); @@ -283,7 +212,7 @@ export class TransactionGuard implements transactionPool.ITransactionGuard { app.resolve("state").removeCachedTransactionIds(forgedIdsSet); forgedIdsSet.forEach(id => { - this.__pushError(this.accept.get(id), "ERR_FORGED", "Already forged."); + this.pushError(this.accept.get(id).data, "ERR_FORGED", "Already forged."); this.accept.delete(id); this.broadcast.delete(id); @@ -292,11 +221,10 @@ export class TransactionGuard implements transactionPool.ITransactionGuard { /** * Add accepted transactions to the pool and filter rejected ones. - * @return {void} */ public __addTransactionsToPool() { // Add transactions to the transaction pool - const { added, notAdded } = this.pool.addTransactions(Array.from(this.accept.values())); + const { notAdded } = this.pool.addTransactions(Array.from(this.accept.values())); // Exclude transactions which were refused from the pool notAdded.forEach(item => { @@ -307,20 +235,16 @@ export class TransactionGuard implements transactionPool.ITransactionGuard { this.broadcast.delete(item.transaction.id); } - this.__pushError(item.transaction, item.type, item.message); + this.pushError(item.transaction.data, item.type, item.message); }); } /** * Adds a transaction to the errors object. The transaction id is mapped to an * array of errors. There may be multiple errors associated with a transaction in - * which case __pushError is called multiple times. - * @param {Transaction} transaction - * @param {String} type - * @param {String} message - * @return {void} + * which case pushError is called multiple times. */ - public __pushError(transaction, type, message) { + public pushError(transaction: ITransactionData, type: string, message: string) { if (!this.errors[transaction.id]) { this.errors[transaction.id] = []; } @@ -332,7 +256,6 @@ export class TransactionGuard implements transactionPool.ITransactionGuard { /** * Print compact transaction stats. - * @return {void} */ public __printStats() { const properties = ["accept", "broadcast", "excess", "invalid"]; diff --git a/packages/core-transaction-pool/src/manager.ts b/packages/core-transaction-pool/src/manager.ts index 57af9922bc..8be830c914 100644 --- a/packages/core-transaction-pool/src/manager.ts +++ b/packages/core-transaction-pool/src/manager.ts @@ -1,32 +1,23 @@ -class TransactionPoolManager { - private connections: { [key: string]: any }; +import { TransactionPool } from "@arkecosystem/core-interfaces"; +import { ConnectionFactory } from "./factory"; - /** - * Create a new transaction pool manager instance. - * @constructor - */ - constructor() { - this.connections = {}; - } +export class ConnectionManager { + private readonly factory: ConnectionFactory = new ConnectionFactory(); + private readonly connections: Map = new Map< + string, + TransactionPool.IConnection + >(); - /** - * Get a transaction pool instance. - * @param {String} name - * @return {TransactionPoolInterface} - */ - public connection(name = "default") { - return this.connections[name]; + public connection(name = "default"): TransactionPool.IConnection { + return this.connections.get(name); } - /** - * Make the transaction pool instance. - * @param {TransactionPoolInterface} connection - * @param {String} name - * @return {void} - */ - public async makeConnection(connection, name = "default") { - this.connections[name] = await connection.make(); + public async createConnection( + connection: TransactionPool.IConnection, + name = "default", + ): Promise { + this.connections.set(name, await this.factory.make(connection)); + + return this.connection(name); } } - -export const transactionPoolManager = new TransactionPoolManager(); diff --git a/packages/core-transaction-pool/src/mem-pool-transaction.ts b/packages/core-transaction-pool/src/mem-pool-transaction.ts index 7ca0e015ef..86fb698fcb 100644 --- a/packages/core-transaction-pool/src/mem-pool-transaction.ts +++ b/packages/core-transaction-pool/src/mem-pool-transaction.ts @@ -1,10 +1,9 @@ // tslint:disable:variable-name -import { constants, models } from "@arkecosystem/crypto"; +import { constants, Transaction } from "@arkecosystem/crypto"; import assert from "assert"; const { TransactionTypes } = constants; -const { Transaction } = models; /** * A mem pool transaction. @@ -13,18 +12,13 @@ const { Transaction } = models; * + a get-expiration-time method used to remove old transactions from the pool */ export class MemPoolTransaction { - private _transaction: any; + private _transaction: Transaction; private _sequence: number; /** * Construct a MemPoolTransaction object. - * @param {Transaction} transaction base transaction object - * @param {Number} sequence insertion order sequence or undefined; - * if this is undefined at creation time, - * then it is assigned later using the - * setter method below */ - constructor(transaction, sequence?) { + constructor(transaction: Transaction, sequence?: number) { assert(transaction instanceof Transaction); this._transaction = transaction; @@ -34,15 +28,15 @@ export class MemPoolTransaction { } } - get transaction() { + get transaction(): Transaction { return this._transaction; } - get sequence() { + get sequence(): number { return this._sequence; } - set sequence(seq) { + set sequence(seq: number) { assert.strictEqual(this._sequence, undefined); this._sequence = seq; } @@ -53,15 +47,15 @@ export class MemPoolTransaction { * @param {Number} maxTransactionAge maximum age (in seconds) of a transaction * @return {Number} expiration time or null if the transaction does not expire */ - public expireAt(maxTransactionAge) { + public expireAt(maxTransactionAge: number): number { const t = this._transaction; - if (t.expiration > 0) { - return t.expiration; + if (t.data.expiration > 0) { + return t.data.expiration; } if (t.type !== TransactionTypes.TimelockTransfer) { - return t.timestamp + maxTransactionAge; + return t.data.timestamp + maxTransactionAge; } return null; diff --git a/packages/core-transaction-pool/src/mem.ts b/packages/core-transaction-pool/src/mem.ts index 2c5aaa090c..62d22ae52f 100644 --- a/packages/core-transaction-pool/src/mem.ts +++ b/packages/core-transaction-pool/src/mem.ts @@ -1,4 +1,4 @@ -import { slots } from "@arkecosystem/crypto"; +import { Bignum, slots, Transaction } from "@arkecosystem/crypto"; import assert from "assert"; import { MemPoolTransaction } from "./mem-pool-transaction"; @@ -96,7 +96,7 @@ export class Mem { * not need to schedule the transaction * that is being added for saving to disk */ - public add(memPoolTransaction, maxTransactionAge, thisIsDBLoad = false) { + public add(memPoolTransaction: MemPoolTransaction, maxTransactionAge: number, thisIsDBLoad?: boolean) { const transaction = memPoolTransaction.transaction; assert.strictEqual(this.byId[transaction.id], undefined); @@ -118,7 +118,7 @@ export class Mem { this.byId[transaction.id] = memPoolTransaction; - const sender = transaction.senderPublicKey; + const sender = transaction.data.senderPublicKey; const type = transaction.type; if (this.bySender[sender] === undefined) { @@ -156,17 +156,15 @@ export class Mem { /** * Remove a transaction. - * @param {String} id id of the transaction to remove - * @param {String} senderPublicKey public key of the sender, could be undefined */ - public remove(id, senderPublicKey) { + public remove(id: string, senderPublicKey?: string) { if (this.byId[id] === undefined) { // Not found, not in pool return; } if (senderPublicKey === undefined) { - senderPublicKey = this.byId[id].transaction.senderPublicKey; + senderPublicKey = this.byId[id].transaction.data.senderPublicKey; } const memPoolTransaction = this.byId[id]; @@ -207,18 +205,15 @@ export class Mem { /** * Get the number of transactions. - * @return Number */ - public getSize() { + public getSize(): number { return this.all.length; } /** * Get all transactions from a given sender. - * @param {String} senderPublicKey public key of the sender - * @return {Set of MemPoolTransaction} all transactions for the given sender, could be empty Set */ - public getBySender(senderPublicKey) { + public getBySender(senderPublicKey: string): Set { const memPoolTransactions = this.bySender[senderPublicKey]; if (memPoolTransactions !== undefined) { return memPoolTransactions; @@ -241,10 +236,8 @@ export class Mem { /** * Get a transaction, given its id. - * @param {String} id transaction id - * @return {Transaction|undefined} */ - public getTransactionById(id) { + public getTransactionById(id: string): Transaction | undefined { if (this.byId[id] === undefined) { return undefined; } @@ -255,15 +248,16 @@ export class Mem { * Get an array of all transactions ordered by fee. * Transactions are ordered by fee (highest fee first) or by * insertion time, if fees equal (earliest transaction first). - * @return {Array of MemPoolTransaction} transactions */ - public getTransactionsOrderedByFee() { + public getTransactionsOrderedByFee(): MemPoolTransaction[] { if (!this.allIsSorted) { this.all.sort((a, b) => { - if (a.transaction.fee.isGreaterThan(b.transaction.fee)) { + const feeA = a.transaction.data.fee as Bignum; + const feeB = b.transaction.data.fee as Bignum; + if (feeA.isGreaterThan(feeB)) { return -1; } - if (a.transaction.fee.isLessThan(b.transaction.fee)) { + if (feeA.isLessThan(feeB)) { return 1; } return a.sequence - b.sequence; @@ -276,19 +270,15 @@ export class Mem { /** * Check if a transaction with a given id exists. - * @param {String} id transaction id - * @return {Boolean} true if exists */ - public transactionExists(id) { + public transactionExists(id: string): boolean { return this.byId[id] !== undefined; } /** * Get the expired transactions. - * @param {Number} maxTransactionAge maximum age of a transaction in seconds - * @return {Array of Transaction} expired transactions */ - public getExpired(maxTransactionAge) { + public getExpired(maxTransactionAge: number): Transaction[] { if (!this.byExpirationIsSorted) { this.byExpiration.sort((a, b) => a.expireAt(maxTransactionAge) - b.expireAt(maxTransactionAge)); this.byExpirationIsSorted = true; @@ -317,6 +307,7 @@ export class Mem { this.allIsSorted = true; this.byId = {}; this.bySender = {}; + this.byType = {}; this.byExpiration = []; this.byExpirationIsSorted = true; this.dirty.added.clear(); @@ -326,9 +317,8 @@ export class Mem { /** * Get the number of dirty transactions (added or removed, but those additions or * removals have not been applied to the persistent storage). - * @return {Number} number of dirty transactions */ - public getNumberOfDirty() { + public getNumberOfDirty(): number { return this.dirty.added.size + this.dirty.removed.size; } @@ -336,10 +326,9 @@ export class Mem { * Get the dirty transactions that were added and forget they are dirty. * In other words, get the transactions that were added since the last * call to this method (or to the flush() method). - * @return {Array of MemPoolTransaction} */ - public getDirtyAddedAndForget() { - const added = []; + public getDirtyAddedAndForget(): MemPoolTransaction[] { + const added: MemPoolTransaction[] = []; this.dirty.added.forEach(id => added.push(this.byId[id])); this.dirty.added.clear(); return added; @@ -349,9 +338,8 @@ export class Mem { * Get the ids of dirty transactions that were removed and forget them completely. * In other words, get the transactions that were removed since the last * call to this method (or to the flush() method). - * @return {Array of String} transaction ids */ - public getDirtyRemovedAndForget() { + public getDirtyRemovedAndForget(): string[] { const removed = Array.from(this.dirty.removed); this.dirty.removed.clear(); return removed; diff --git a/packages/core-transaction-pool/src/plugin.ts b/packages/core-transaction-pool/src/plugin.ts index 8795125ec1..8bbba613f1 100644 --- a/packages/core-transaction-pool/src/plugin.ts +++ b/packages/core-transaction-pool/src/plugin.ts @@ -1,25 +1,25 @@ -import { Container, Logger } from "@arkecosystem/core-interfaces"; +import { Container, Logger, TransactionPool } from "@arkecosystem/core-interfaces"; import { config } from "./config"; -import { TransactionPool } from "./connection"; +import { Connection } from "./connection"; import { defaults } from "./defaults"; -import { transactionPoolManager } from "./manager"; +import { ConnectionManager } from "./manager"; export const plugin: Container.PluginDescriptor = { pkg: require("../package.json"), defaults, - alias: "transactionPool", + alias: "transaction-pool", async register(container: Container.IContainer, options) { config.init(options); container.resolvePlugin("logger").info("Connecting to transaction pool"); - await transactionPoolManager.makeConnection(new TransactionPool(options)); + const connectionManager: ConnectionManager = new ConnectionManager(); - return transactionPoolManager.connection(); + return connectionManager.createConnection(new Connection(options)); }, async deregister(container: Container.IContainer, options) { container.resolvePlugin("logger").info("Disconnecting from transaction pool"); - return transactionPoolManager.connection().disconnect(); + return container.resolvePlugin("transaction-pool").disconnect(); }, }; diff --git a/packages/core-transaction-pool/src/pool-wallet-manager.ts b/packages/core-transaction-pool/src/pool-wallet-manager.ts index 167ca5a147..79c90aa779 100644 --- a/packages/core-transaction-pool/src/pool-wallet-manager.ts +++ b/packages/core-transaction-pool/src/pool-wallet-manager.ts @@ -1,21 +1,11 @@ import { app } from "@arkecosystem/core-container"; -import { WalletManager } from "@arkecosystem/core-database"; +import { Wallet, WalletManager } from "@arkecosystem/core-database"; import { Database } from "@arkecosystem/core-interfaces"; -import { constants, crypto, isException, models } from "@arkecosystem/crypto"; - -const { Wallet } = models; -const { TransactionTypes } = constants; +import { TransactionHandlerRegistry } from "@arkecosystem/core-transactions"; +import { crypto, isException, Transaction } from "@arkecosystem/crypto"; export class PoolWalletManager extends WalletManager { - public databaseService = app.resolvePlugin("database"); - - /** - * Create a new pool wallet manager instance. - * @constructor - */ - constructor() { - super(); - } + public readonly databaseService = app.resolvePlugin("database"); /** * Get a wallet by the given address. If wallet is not found it is copied from blockchain @@ -26,7 +16,7 @@ export class PoolWalletManager extends WalletManager { * @return {(Wallet|null)} */ public findByAddress(address) { - if (!this.byAddress[address]) { + if (address && !this.byAddress[address]) { const blockchainWallet = this.databaseService.walletManager.findByAddress(address); const wallet = Object.assign(new Wallet(address), blockchainWallet); // do not modify @@ -38,20 +28,17 @@ export class PoolWalletManager extends WalletManager { public deleteWallet(publicKey) { this.forgetByPublicKey(publicKey); - this.forgetByAddress(crypto.getAddress(publicKey, this.networkId)); + this.forgetByAddress(crypto.getAddress(publicKey)); } /** * Checks if the transaction can be applied. - * @param {Object|Transaction} transaction - * @param {Array} errors The errors are written into the array. - * @return {Boolean} */ - public canApply(transaction, errors) { + public canApply(transaction: Transaction, errors): boolean { // Edge case if sender is unknown and has no balance. // NOTE: Check is performed against the database wallet manager. - if (!this.databaseService.walletManager.exists(transaction.senderPublicKey)) { - const senderAddress = crypto.getAddress(transaction.senderPublicKey, this.networkId); + if (!this.databaseService.walletManager.exists(transaction.data.senderPublicKey)) { + const senderAddress = crypto.getAddress(transaction.data.senderPublicKey); if (this.databaseService.walletManager.findByAddress(senderAddress).balance.isZero()) { errors.push("Cold wallet is not allowed to send until receiving transaction is confirmed."); @@ -59,42 +46,22 @@ export class PoolWalletManager extends WalletManager { } } - const sender = this.findByPublicKey(transaction.senderPublicKey); - const { type, asset } = transaction; - - if ( - type === TransactionTypes.DelegateRegistration && - this.databaseService.walletManager.findByUsername(asset.delegate.username.toLowerCase()) - ) { - this.logger.error( - `[PoolWalletManager] Can't apply transaction ${ - transaction.id - }: delegate name already taken. Data: ${JSON.stringify(transaction)}`, - ); - - errors.push(`Can't apply transaction ${transaction.id}: delegate name already taken.`); - // NOTE: We use the vote public key, because vote transactions have the same sender and recipient. - } else if ( - type === TransactionTypes.Vote && - !this.databaseService.walletManager.isDelegate(asset.votes[0].slice(1)) - ) { - this.logger.error( - `[PoolWalletManager] Can't apply vote transaction: delegate ${ - asset.votes[0] - } does not exist. Data: ${JSON.stringify(transaction)}`, - ); + const { data } = transaction; + const sender = this.findByPublicKey(data.senderPublicKey); - errors.push(`Can't apply transaction ${transaction.id}: delegate ${asset.votes[0]} does not exist.`); - } else if (isException(transaction)) { + if (isException(data)) { this.logger.warn( `Transaction forcibly applied because it has been added as an exception: ${transaction.id}`, ); - } else if (!sender.canApply(transaction, errors)) { - const message = `[PoolWalletManager] Can't apply transaction id:${transaction.id} from sender:${ - sender.address - }`; - this.logger.error(`${message} due to ${JSON.stringify(errors)}`); - errors.unshift(message); + } else { + try { + const transactionHandler = TransactionHandlerRegistry.get(transaction.type); + transactionHandler.canBeApplied(transaction, sender, this.databaseService.walletManager); + } catch (error) { + const message = `[PoolWalletManager] Can't apply transaction ${transaction.id} from ${sender.address}`; + this.logger.error(`${message} due to ${JSON.stringify(error.message)}`); + errors.unshift(error.message); + } } return errors.length === 0; @@ -102,15 +69,12 @@ export class PoolWalletManager extends WalletManager { /** * Remove the given transaction from a sender only. - * @param {Transaction} transaction - * @return {Transaction} */ - public revertTransactionForSender(transaction) { + public revertTransactionForSender(transaction: Transaction) { const { data } = transaction; const sender = this.findByPublicKey(data.senderPublicKey); // Should exist - sender.revertTransactionForSender(data); - - return data; + const transactionHandler = TransactionHandlerRegistry.get(transaction.type); + transactionHandler.revertForSender(transaction, sender); } } diff --git a/packages/core-transaction-pool/src/storage.ts b/packages/core-transaction-pool/src/storage.ts index 32ffce7a82..29325e0f9e 100644 --- a/packages/core-transaction-pool/src/storage.ts +++ b/packages/core-transaction-pool/src/storage.ts @@ -1,10 +1,8 @@ -import { models } from "@arkecosystem/crypto"; +import { Transaction } from "@arkecosystem/crypto"; import BetterSqlite3 from "better-sqlite3"; import fs from "fs-extra"; import { MemPoolTransaction } from "./mem-pool-transaction"; -const { Transaction } = models; - /** * A permanent storage (on-disk), supporting some basic functionalities required * by the transaction pool. @@ -15,9 +13,8 @@ export class Storage { /** * Construct the storage. - * @param {String} file */ - constructor(file) { + constructor(file: string) { this.table = "pool"; fs.ensureFileSync(file); @@ -44,7 +41,6 @@ export class Storage { /** * Add a bunch of new entries to the storage. - * @param {Array of MemPoolTransaction} data new entries to be added */ public bulkAdd(data: MemPoolTransaction[]) { if (data.length === 0) { @@ -62,7 +58,7 @@ export class Storage { insertStatement.run({ sequence: d.sequence, id: d.transaction.id, - serialized: Buffer.from(d.transaction.serialized, "hex"), + serialized: d.transaction.serialized, }), ); @@ -76,7 +72,6 @@ export class Storage { /** * Remove a bunch of entries, given their ids. - * @param {Array of String} ids ids of the elements to be removed */ public bulkRemoveById(ids: string[]) { if (ids.length === 0) { @@ -94,13 +89,12 @@ export class Storage { /** * Load all entries. - * @return {Array of MemPoolTransaction} */ public loadAll(): MemPoolTransaction[] { const rows = this.db.prepare(`SELECT sequence, lower(HEX(serialized)) AS serialized FROM ${this.table};`).all(); return rows - .map(r => ({ tx: new Transaction(r.serialized), ...r })) + .map(r => ({ tx: Transaction.fromHex(r.serialized), ...r })) .filter(r => r.tx.verified) .map(r => new MemPoolTransaction(r.tx, r.sequence)); } diff --git a/packages/core-transaction-pool/src/utils.ts b/packages/core-transaction-pool/src/utils.ts index 40c32a9707..dc1a1a7489 100644 --- a/packages/core-transaction-pool/src/utils.ts +++ b/packages/core-transaction-pool/src/utils.ts @@ -1,16 +1,14 @@ import { app } from "@arkecosystem/core-container"; import { Logger } from "@arkecosystem/core-interfaces"; -import { configManager } from "@arkecosystem/crypto"; +import { configManager, ITransactionData } from "@arkecosystem/crypto"; import bs58check from "bs58check"; const logger = app.resolvePlugin("logger"); /** * Checks if transaction recipient is on the same network as blockchain - * @param {Transaction} - * @return {Boolean} */ -export function isRecipientOnActiveNetwork(transaction) { +export function isRecipientOnActiveNetwork(transaction: ITransactionData): boolean { const recipientPrefix = bs58check.decode(transaction.recipientId).readUInt8(0); if (recipientPrefix === configManager.get("pubKeyHash")) { diff --git a/packages/core-transactions/.gitattributes b/packages/core-transactions/.gitattributes new file mode 100644 index 0000000000..63f6a5b970 --- /dev/null +++ b/packages/core-transactions/.gitattributes @@ -0,0 +1,7 @@ +# Path-based git attributes +# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html + +# Ignore all test and documentation with "export-ignore". +/.gitattributes export-ignore +/.gitignore export-ignore +/README.md export-ignore diff --git a/packages/core-transactions/README.md b/packages/core-transactions/README.md new file mode 100644 index 0000000000..cd0a6ccd76 --- /dev/null +++ b/packages/core-transactions/README.md @@ -0,0 +1,21 @@ +# Persona Core - Transactions + +

+ +

+ +## Documentation + +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/required/crypto.html). + +## Security + +If you discover a security vulnerability within this package, please send an e-mail to security@ark.io. All security vulnerabilities will be promptly addressed. + +## Credits + +This project exists thanks to all the people who [contribute](../../../../contributors). + +## License + +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-transactions/package.json b/packages/core-transactions/package.json new file mode 100644 index 0000000000..ad659c6023 --- /dev/null +++ b/packages/core-transactions/package.json @@ -0,0 +1,32 @@ +{ + "name": "@arkecosystem/core-transactions", + "description": "Transaction Services for ARK Core", + "version": "2.3.15", + "contributors": [ + "Joshua Noack " + ], + "license": "MIT", + "main": "dist/index.js", + "types": "dist/index", + "files": [ + "dist" + ], + "scripts": { + "prepublishOnly": "yarn build", + "compile": "../../node_modules/typescript/bin/tsc", + "build": "yarn clean && yarn compile", + "build:watch": "yarn clean && yarn compile -w", + "clean": "del dist" + }, + "dependencies": { + "@arkecosystem/core-interfaces": "^2.3.15", + "@arkecosystem/crypto": "^2.3.15", + "bs58check": "^2.1.2" + }, + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=10.x" + } +} diff --git a/packages/core-transactions/src/errors.ts b/packages/core-transactions/src/errors.ts new file mode 100644 index 0000000000..ec5dc8beba --- /dev/null +++ b/packages/core-transactions/src/errors.ts @@ -0,0 +1,147 @@ +// tslint:disable:max-classes-per-file + +export class TransactionError extends Error { + constructor(message: string) { + super(message); + + Object.defineProperty(this, "message", { + enumerable: false, + value: message, + }); + + Object.defineProperty(this, "name", { + enumerable: false, + value: this.constructor.name, + }); + + Error.captureStackTrace(this, this.constructor); + } +} + +export class NotImplementedError extends TransactionError { + constructor() { + super(`Feature is not available.`); + } +} + +export class TransactionHandlerAlreadyRegisteredError extends TransactionError { + constructor(type: number) { + super(`Transaction service for type ${type} is already registered.`); + } +} + +export class InvalidTransactionTypeError extends TransactionError { + constructor(type: number) { + super(`Transaction type ${type} does not exist.`); + } +} + +export class InsufficientBalanceError extends TransactionError { + constructor() { + super(`Insufficient balance in the wallet.`); + } +} + +export class SenderWalletMismatchError extends TransactionError { + constructor() { + super(`Failed to apply transaction, because the public key does not match the wallet.`); + } +} + +export class UnexpectedSecondSignatureError extends TransactionError { + constructor() { + super(`Failed to apply transaction, because wallet does not allow second signatures.`); + } +} + +export class UnexpectedMultiSignatureError extends TransactionError { + constructor() { + super(`Failed to apply transaction, because multi signatures are currently not supported.`); + } +} + +export class InvalidSecondSignatureError extends TransactionError { + constructor() { + super(`Failed to apply transaction, because the second signature could not be verified.`); + } +} + +export class WalletUsernameEmptyError extends TransactionError { + constructor() { + super(`Failed to apply transaction, because the username is empty.`); + } +} + +export class WalletUsernameNotEmptyError extends TransactionError { + constructor() { + super(`Failed to apply transaction, because the wallet already has a registered username.`); + } +} + +export class WalletNoUsernameError extends TransactionError { + constructor() { + super(`Failed to apply transaction, because the wallet has no registered username.`); + } +} + +export class WalletUsernameAlreadyRegisteredError extends TransactionError { + constructor(username: string) { + super(`Failed to apply transaction, because the username '${username}' is already registered.`); + } +} + +export class SecondSignatureAlreadyRegisteredError extends TransactionError { + constructor() { + super(`Failed to apply transaction, because second signature is already enabled.`); + } +} + +export class AlreadyVotedError extends TransactionError { + constructor() { + super(`Failed to apply transaction, because the wallet already voted.`); + } +} + +export class NoVoteError extends TransactionError { + constructor() { + super(`Failed to apply transaction, because the wallet has not voted.`); + } +} + +export class UnvoteMismatchError extends TransactionError { + constructor() { + super(`Failed to apply transaction, because the wallet vote does not match.`); + } +} + +export class VotedForNonDelegateError extends TransactionError { + constructor(vote: string) { + super(`Failed to apply transaction, because only delegates can be voted.`); + } +} + +export class MultiSignatureAlreadyRegisteredError extends TransactionError { + constructor() { + super(`Failed to apply transaction, because multi signature is already enabled.`); + } +} + +export class MultiSignatureMinimumKeysError extends TransactionError { + constructor() { + super(`Failed to apply transaction, because too few keys were provided.`); + } +} + +export class MultiSignatureKeyCountMismatchError extends TransactionError { + constructor() { + super( + `Failed to apply transaction, because the number of provided keys does not match the number of signatures.`, + ); + } +} + +export class InvalidMultiSignatureError extends TransactionError { + constructor() { + super(`Failed to apply transaction, because the multi signature could not be verified.`); + } +} diff --git a/packages/core-transactions/src/handler-registry.ts b/packages/core-transactions/src/handler-registry.ts new file mode 100644 index 0000000000..d290fa4ea1 --- /dev/null +++ b/packages/core-transactions/src/handler-registry.ts @@ -0,0 +1,68 @@ +import { constants, TransactionRegistry } from "@arkecosystem/crypto"; +import { InvalidTransactionTypeError, TransactionHandlerAlreadyRegisteredError } from "./errors"; +import { transactionHandlers } from "./handlers"; +import { TransactionHandler } from "./handlers/transaction"; + +export type TransactionHandlerConstructor = new () => TransactionHandler; + +class TransactionHandlerRegistry { + private readonly coreTransactionHandlers = new Map(); + private readonly customTransactionHandlers = new Map(); + + constructor() { + transactionHandlers.forEach((service: TransactionHandlerConstructor) => { + this.registerCoreTransactionHandler(service); + }); + } + + public get(type: constants.TransactionTypes | number): TransactionHandler { + if (this.coreTransactionHandlers.has(type)) { + return this.coreTransactionHandlers.get(type); + } + + if (this.customTransactionHandlers.has(type)) { + return this.customTransactionHandlers.get(type); + } + + throw new InvalidTransactionTypeError(type); + } + + public registerCustomTransactionHandler(constructor: TransactionHandlerConstructor): void { + const service = new constructor(); + const transactionConstructor = service.getConstructor(); + const { type } = transactionConstructor; + + if (this.customTransactionHandlers.has(type)) { + throw new TransactionHandlerAlreadyRegisteredError(type); + } + + TransactionRegistry.registerCustomType(transactionConstructor); + + this.customTransactionHandlers.set(type, service); + } + + public deregisterCustomTransactionHandler(constructor: TransactionHandlerConstructor): void { + const service = new constructor(); + const transactionConstructor = service.getConstructor(); + const { type } = transactionConstructor; + + if (this.customTransactionHandlers.has(type)) { + TransactionRegistry.deregisterCustomType(type); + this.customTransactionHandlers.delete(type); + } + } + + private registerCoreTransactionHandler(constructor: TransactionHandlerConstructor) { + const service = new constructor(); + const transactionConstructor = service.getConstructor(); + const { type } = transactionConstructor; + + if (this.coreTransactionHandlers.has(type)) { + throw new TransactionHandlerAlreadyRegisteredError(type); + } + + this.coreTransactionHandlers.set(type, service); + } +} + +export const transactionHandlerRegistry = new TransactionHandlerRegistry(); diff --git a/packages/core-transactions/src/handlers/delegate-registration.ts b/packages/core-transactions/src/handlers/delegate-registration.ts new file mode 100644 index 0000000000..2a61fb771d --- /dev/null +++ b/packages/core-transactions/src/handlers/delegate-registration.ts @@ -0,0 +1,92 @@ +import { Database, EventEmitter, TransactionPool } from "@arkecosystem/core-interfaces"; +import { + constants, + DelegateRegistrationTransaction, + ITransactionData, + Transaction, + TransactionConstructor, +} from "@arkecosystem/crypto"; +import { WalletUsernameAlreadyRegisteredError, WalletUsernameEmptyError, WalletUsernameNotEmptyError } from "../errors"; +import { TransactionHandler } from "./transaction"; + +const { TransactionTypes } = constants; + +export class DelegateRegistrationTransactionHandler extends TransactionHandler { + public getConstructor(): TransactionConstructor { + return DelegateRegistrationTransaction; + } + + public canBeApplied( + transaction: Transaction, + wallet: Database.IWallet, + walletManager?: Database.IWalletManager, + ): boolean { + const { data } = transaction; + const { username } = data.asset.delegate; + if (!username) { + throw new WalletUsernameEmptyError(); + } + + if (wallet.username) { + throw new WalletUsernameNotEmptyError(); + } + + if (walletManager) { + if (walletManager.findByUsername(username)) { + throw new WalletUsernameAlreadyRegisteredError(username); + } + } + + return super.canBeApplied(transaction, wallet, walletManager); + } + + public apply(transaction: Transaction, wallet: Database.IWallet): void { + const { data } = transaction; + wallet.username = data.asset.delegate.username; + } + + public revert(transaction: Transaction, wallet: Database.IWallet): void { + wallet.username = null; + } + + public emitEvents(transaction: Transaction, emitter: EventEmitter.EventEmitter): void { + emitter.emit("delegate.registered", transaction.data); + } + + public canEnterTransactionPool(data: ITransactionData, guard: TransactionPool.IGuard): boolean { + if ( + this.typeFromSenderAlreadyInPool(data, guard) || + this.secondSignatureRegistrationFromSenderAlreadyInPool(data, guard) + ) { + return false; + } + + const { username } = data.asset.delegate; + const delegateRegistrationsSameNameInPayload = guard.transactions.filter( + tx => tx.type === TransactionTypes.DelegateRegistration && tx.asset.delegate.username === username, + ); + + if (delegateRegistrationsSameNameInPayload.length > 1) { + guard.pushError( + data, + "ERR_CONFLICT", + `Multiple delegate registrations for "${username}" in transaction payload`, + ); + return false; + } + + const delegateRegistrationsInPool: ITransactionData[] = Array.from( + guard.pool.getTransactionsByType(TransactionTypes.DelegateRegistration), + ).map((memTx: any) => memTx.transaction.data); + + const containsDelegateRegistrationForSameNameInPool = delegateRegistrationsInPool.some( + transaction => transaction.asset.delegate.username === username, + ); + if (containsDelegateRegistrationForSameNameInPool) { + guard.pushError(data, "ERR_PENDING", `Delegate registration for "${username}" already in the pool`); + return false; + } + + return true; + } +} diff --git a/packages/core-transactions/src/handlers/delegate-resignation.ts b/packages/core-transactions/src/handlers/delegate-resignation.ts new file mode 100644 index 0000000000..008210052d --- /dev/null +++ b/packages/core-transactions/src/handlers/delegate-resignation.ts @@ -0,0 +1,29 @@ +import { Database, EventEmitter } from "@arkecosystem/core-interfaces"; +import { DelegateResignationTransaction, Transaction, TransactionConstructor } from "@arkecosystem/crypto"; +import { TransactionHandler } from "./transaction"; + +export class DelegateResignationTransactionHandler extends TransactionHandler { + public getConstructor(): TransactionConstructor { + return DelegateResignationTransaction; + } + + public canBeApplied( + transaction: Transaction, + wallet: Database.IWallet, + walletManager?: Database.IWalletManager, + ): boolean { + return super.canBeApplied(transaction, wallet, walletManager); + } + + public apply(transaction: Transaction, wallet: Database.IWallet): void { + return; + } + + public revert(transaction: Transaction, wallet: Database.IWallet): void { + return; + } + + public emitEvents(transaction: Transaction, emitter: EventEmitter.EventEmitter): void { + emitter.emit("delegate.resigned", transaction.data); + } +} diff --git a/packages/core-transactions/src/handlers/index.ts b/packages/core-transactions/src/handlers/index.ts new file mode 100644 index 0000000000..c423e36c92 --- /dev/null +++ b/packages/core-transactions/src/handlers/index.ts @@ -0,0 +1,21 @@ +import { DelegateRegistrationTransactionHandler } from "./delegate-registration"; +import { DelegateResignationTransactionHandler } from "./delegate-resignation"; +import { IpfsTransactionHandler } from "./ipfs"; +import { MultiPaymentTransactionHandler } from "./multi-payment"; +import { MultiSignatureTransactionHandler } from "./multi-signature"; +import { SecondSignatureTransactionHandler } from "./second-signature"; +import { TimelockTransferTransactionHandler } from "./timelock-transfer"; +import { TransferTransactionHandler } from "./transfer"; +import { VoteTransactionHandler } from "./vote"; + +export const transactionHandlers = [ + TransferTransactionHandler, + SecondSignatureTransactionHandler, + VoteTransactionHandler, + DelegateRegistrationTransactionHandler, + MultiSignatureTransactionHandler, + IpfsTransactionHandler, + TimelockTransferTransactionHandler, + MultiPaymentTransactionHandler, + DelegateResignationTransactionHandler, +]; diff --git a/packages/core-transactions/src/handlers/ipfs.ts b/packages/core-transactions/src/handlers/ipfs.ts new file mode 100644 index 0000000000..103a082ecc --- /dev/null +++ b/packages/core-transactions/src/handlers/ipfs.ts @@ -0,0 +1,25 @@ +import { Database } from "@arkecosystem/core-interfaces"; +import { IpfsTransaction, Transaction, TransactionConstructor } from "@arkecosystem/crypto"; +import { TransactionHandler } from "./transaction"; + +export class IpfsTransactionHandler extends TransactionHandler { + public getConstructor(): TransactionConstructor { + return IpfsTransaction; + } + + public canBeApplied( + transaction: Transaction, + wallet: Database.IWallet, + walletManager?: Database.IWalletManager, + ): boolean { + return super.canBeApplied(transaction, wallet, walletManager); + } + + public apply(transaction: Transaction, wallet: Database.IWallet): void { + return; + } + + public revert(transaction: Transaction, wallet: Database.IWallet): void { + return; + } +} diff --git a/packages/core-transactions/src/handlers/multi-payment.ts b/packages/core-transactions/src/handlers/multi-payment.ts new file mode 100644 index 0000000000..a82bf8c9c2 --- /dev/null +++ b/packages/core-transactions/src/handlers/multi-payment.ts @@ -0,0 +1,25 @@ +import { Database } from "@arkecosystem/core-interfaces"; +import { MultiPaymentTransaction, Transaction, TransactionConstructor } from "@arkecosystem/crypto"; +import { TransactionHandler } from "./transaction"; + +export class MultiPaymentTransactionHandler extends TransactionHandler { + public getConstructor(): TransactionConstructor { + return MultiPaymentTransaction; + } + + public canBeApplied( + transaction: Transaction, + wallet: Database.IWallet, + walletManager?: Database.IWalletManager, + ): boolean { + return super.canBeApplied(transaction, wallet, walletManager); + } + + public apply(transaction: Transaction, wallet: Database.IWallet): void { + return; + } + + public revert(transaction: Transaction, wallet: Database.IWallet): void { + return; + } +} diff --git a/packages/core-transactions/src/handlers/multi-signature.ts b/packages/core-transactions/src/handlers/multi-signature.ts new file mode 100644 index 0000000000..fc7fd5dda7 --- /dev/null +++ b/packages/core-transactions/src/handlers/multi-signature.ts @@ -0,0 +1,50 @@ +import { Database } from "@arkecosystem/core-interfaces"; +import { MultiSignatureRegistrationTransaction, Transaction, TransactionConstructor } from "@arkecosystem/crypto"; +import { + InvalidMultiSignatureError, + MultiSignatureAlreadyRegisteredError, + MultiSignatureKeyCountMismatchError, + MultiSignatureMinimumKeysError, +} from "../errors"; +import { TransactionHandler } from "./transaction"; + +export class MultiSignatureTransactionHandler extends TransactionHandler { + public getConstructor(): TransactionConstructor { + return MultiSignatureRegistrationTransaction; + } + + // TODO: AIP18 + public canBeApplied( + transaction: Transaction, + wallet: Database.IWallet, + walletManager?: Database.IWalletManager, + ): boolean { + const { data } = transaction; + if (wallet.multisignature) { + throw new MultiSignatureAlreadyRegisteredError(); + } + + const { keysgroup, min } = data.asset.multisignature; + if (keysgroup.length < min) { + throw new MultiSignatureMinimumKeysError(); + } + + if (keysgroup.length !== data.signatures.length) { + throw new MultiSignatureKeyCountMismatchError(); + } + + if (!wallet.verifySignatures(data, data.asset.multisignature)) { + throw new InvalidMultiSignatureError(); + } + + return super.canBeApplied(transaction, wallet, walletManager); + } + + public apply(transaction: Transaction, wallet: Database.IWallet): void { + wallet.multisignature = transaction.data.asset.multisignature; + } + + public revert(transaction: Transaction, wallet: Database.IWallet): void { + wallet.multisignature = null; + } +} diff --git a/packages/core-transactions/src/handlers/second-signature.ts b/packages/core-transactions/src/handlers/second-signature.ts new file mode 100644 index 0000000000..c3ac468ba2 --- /dev/null +++ b/packages/core-transactions/src/handlers/second-signature.ts @@ -0,0 +1,39 @@ +import { Database, TransactionPool } from "@arkecosystem/core-interfaces"; +import { + ITransactionData, + SecondSignatureRegistrationTransaction, + Transaction, + TransactionConstructor, +} from "@arkecosystem/crypto"; +import { SecondSignatureAlreadyRegisteredError } from "../errors"; +import { TransactionHandler } from "./transaction"; + +export class SecondSignatureTransactionHandler extends TransactionHandler { + public getConstructor(): TransactionConstructor { + return SecondSignatureRegistrationTransaction; + } + + public canBeApplied( + transaction: Transaction, + wallet: Database.IWallet, + walletManager?: Database.IWalletManager, + ): boolean { + if (wallet.secondPublicKey) { + throw new SecondSignatureAlreadyRegisteredError(); + } + + return super.canBeApplied(transaction, wallet, walletManager); + } + + public apply(transaction: Transaction, wallet: Database.IWallet): void { + wallet.secondPublicKey = transaction.data.asset.signature.publicKey; + } + + public revert(transaction: Transaction, wallet: Database.IWallet): void { + wallet.secondPublicKey = null; + } + + public canEnterTransactionPool(data: ITransactionData, guard: TransactionPool.IGuard): boolean { + return !this.typeFromSenderAlreadyInPool(data, guard); + } +} diff --git a/packages/core-transactions/src/handlers/timelock-transfer.ts b/packages/core-transactions/src/handlers/timelock-transfer.ts new file mode 100644 index 0000000000..ec41bd8e76 --- /dev/null +++ b/packages/core-transactions/src/handlers/timelock-transfer.ts @@ -0,0 +1,25 @@ +import { Database } from "@arkecosystem/core-interfaces"; +import { TimelockTransferTransaction, Transaction, TransactionConstructor } from "@arkecosystem/crypto"; +import { TransactionHandler } from "./transaction"; + +export class TimelockTransferTransactionHandler extends TransactionHandler { + public getConstructor(): TransactionConstructor { + return TimelockTransferTransaction; + } + + public canBeApplied( + transaction: Transaction, + wallet: Database.IWallet, + walletManager?: Database.IWalletManager, + ): boolean { + return super.canBeApplied(transaction, wallet, walletManager); + } + + public apply(transaction: Transaction, wallet: Database.IWallet): void { + return; + } + + public revert(transaction: Transaction, wallet: Database.IWallet): void { + return; + } +} diff --git a/packages/core-transactions/src/handlers/transaction.ts b/packages/core-transactions/src/handlers/transaction.ts new file mode 100644 index 0000000000..cff6af9653 --- /dev/null +++ b/packages/core-transactions/src/handlers/transaction.ts @@ -0,0 +1,158 @@ +// tslint:disable:max-classes-per-file + +import { Database, EventEmitter, TransactionPool } from "@arkecosystem/core-interfaces"; +import { + configManager, + constants, + crypto, + ITransactionData, + Transaction, + TransactionConstructor, +} from "@arkecosystem/crypto"; + +import { + InsufficientBalanceError, + InvalidSecondSignatureError, + SenderWalletMismatchError, + UnexpectedMultiSignatureError, + UnexpectedSecondSignatureError, +} from "../errors"; +import { ITransactionHandler } from "../interfaces"; + +const { TransactionTypes } = constants; + +export abstract class TransactionHandler implements ITransactionHandler { + public abstract getConstructor(): TransactionConstructor; + + /** + * Wallet logic + */ + public canBeApplied( + transaction: Transaction, + wallet: Database.IWallet, + walletManager?: Database.IWalletManager, + ): boolean { + // NOTE: Checks if it can be applied based on sender wallet + // could be merged with `apply` so they are coupled together :thinking_face: + + const { data } = transaction; + if (wallet.multisignature) { + throw new UnexpectedMultiSignatureError(); + } + + if ( + wallet.balance + .minus(data.amount) + .minus(data.fee) + .isLessThan(0) + ) { + throw new InsufficientBalanceError(); + } + + if (data.senderPublicKey !== wallet.publicKey) { + throw new SenderWalletMismatchError(); + } + + if (wallet.secondPublicKey) { + if (!crypto.verifySecondSignature(data, wallet.secondPublicKey)) { + throw new InvalidSecondSignatureError(); + } + } else { + if (data.secondSignature || data.signSignature) { + // Accept invalid second signature fields prior the applied patch. + // NOTE: only applies to devnet. + if (!configManager.getMilestone().ignoreInvalidSecondSignatureField) { + throw new UnexpectedSecondSignatureError(); + } + } + } + + return true; + } + + public applyToSender(transaction: Transaction, wallet: Database.IWallet): void { + const { data } = transaction; + if (data.senderPublicKey === wallet.publicKey || crypto.getAddress(data.senderPublicKey) === wallet.address) { + wallet.balance = wallet.balance.minus(data.amount).minus(data.fee); + + this.apply(transaction, wallet); + } + } + + public applyToRecipient(transaction: Transaction, wallet: Database.IWallet): void { + const { data } = transaction; + if (data.recipientId === wallet.address) { + wallet.balance = wallet.balance.plus(data.amount); + } + } + + public revertForSender(transaction: Transaction, wallet: Database.IWallet): void { + const { data } = transaction; + if (data.senderPublicKey === wallet.publicKey || crypto.getAddress(data.senderPublicKey) === wallet.address) { + wallet.balance = wallet.balance.plus(data.amount).plus(data.fee); + + this.revert(transaction, wallet); + } + } + + public revertForRecipient(transaction: Transaction, wallet: Database.IWallet): void { + const { data } = transaction; + if (data.recipientId === wallet.address) { + wallet.balance = wallet.balance.minus(data.amount); + } + } + + public abstract apply(transaction: Transaction, wallet: Database.IWallet): void; + public abstract revert(transaction: Transaction, wallet: Database.IWallet): void; + + /** + * Database Service + */ + // tslint:disable-next-line:no-empty + public emitEvents(transaction: Transaction, emitter: EventEmitter.EventEmitter): void {} + + /** + * Transaction Pool logic + */ + public canEnterTransactionPool(data: ITransactionData, guard: TransactionPool.IGuard): boolean { + guard.pushError( + data, + "ERR_UNSUPPORTED", + `Invalidating transaction of unsupported type '${TransactionTypes[data.type]}'`, + ); + return false; + } + + protected typeFromSenderAlreadyInPool(data: ITransactionData, guard: TransactionPool.IGuard): boolean { + const { senderPublicKey, type } = data; + if (guard.pool.senderHasTransactionsOfType(senderPublicKey, type)) { + guard.pushError( + data, + "ERR_PENDING", + `Sender ${senderPublicKey} already has a transaction of type '${TransactionTypes[type]}' in the pool`, + ); + + return true; + } + + return false; + } + + protected secondSignatureRegistrationFromSenderAlreadyInPool( + data: ITransactionData, + guard: TransactionPool.IGuard, + ): boolean { + const { senderPublicKey } = data; + if (guard.pool.senderHasTransactionsOfType(senderPublicKey, TransactionTypes.SecondSignature)) { + guard.pushError( + data, + "ERR_PENDING", + `Cannot accept transaction from sender ${senderPublicKey} while its second signature registration is in the pool`, + ); + + return true; + } + + return false; + } +} diff --git a/packages/core-transactions/src/handlers/transfer.ts b/packages/core-transactions/src/handlers/transfer.ts new file mode 100644 index 0000000000..6e386928f7 --- /dev/null +++ b/packages/core-transactions/src/handlers/transfer.ts @@ -0,0 +1,56 @@ +import { Database, TransactionPool } from "@arkecosystem/core-interfaces"; +import { + configManager, + constants, + ITransactionData, + Transaction, + TransactionConstructor, + TransferTransaction, +} from "@arkecosystem/crypto"; +import { isRecipientOnActiveNetwork } from "../utils"; +import { TransactionHandler } from "./transaction"; + +const { TransactionTypes } = constants; + +export class TransferTransactionHandler extends TransactionHandler { + public getConstructor(): TransactionConstructor { + return TransferTransaction; + } + + public canBeApplied( + transaction: Transaction, + wallet: Database.IWallet, + walletManager?: Database.IWalletManager, + ): boolean { + return super.canBeApplied(transaction, wallet, walletManager); + } + + public hasVendorField(): boolean { + return true; + } + + public apply(transaction: Transaction, wallet: Database.IWallet): void { + return; + } + + public revert(transaction: Transaction, wallet: Database.IWallet): void { + return; + } + + public canEnterTransactionPool(data: ITransactionData, guard: TransactionPool.IGuard): boolean { + if (this.secondSignatureRegistrationFromSenderAlreadyInPool(data, guard)) { + return false; + } + + if (!isRecipientOnActiveNetwork(data)) { + guard.pushError( + data, + "ERR_INVALID_RECIPIENT", + `Recipient ${data.recipientId} is not on the same network: ${configManager.get("pubKeyHash")}`, + ); + return false; + } + + return true; + } +} diff --git a/packages/core-transactions/src/handlers/vote.ts b/packages/core-transactions/src/handlers/vote.ts new file mode 100644 index 0000000000..80a259c9a4 --- /dev/null +++ b/packages/core-transactions/src/handlers/vote.ts @@ -0,0 +1,74 @@ +import { Database, EventEmitter, TransactionPool } from "@arkecosystem/core-interfaces"; +import { ITransactionData, Transaction, TransactionConstructor, VoteTransaction } from "@arkecosystem/crypto"; +import { AlreadyVotedError, NoVoteError, UnvoteMismatchError, VotedForNonDelegateError } from "../errors"; +import { TransactionHandler } from "./transaction"; + +export class VoteTransactionHandler extends TransactionHandler { + public getConstructor(): TransactionConstructor { + return VoteTransaction; + } + + public canBeApplied( + transaction: Transaction, + wallet: Database.IWallet, + walletManager?: Database.IWalletManager, + ): boolean { + const { data } = transaction; + const vote = data.asset.votes[0]; + if (vote.startsWith("+")) { + if (wallet.vote) { + throw new AlreadyVotedError(); + } + } else { + if (!wallet.vote) { + throw new NoVoteError(); + } else if (wallet.vote !== vote.slice(1)) { + throw new UnvoteMismatchError(); + } + } + + if (walletManager) { + if (!walletManager.isDelegate(vote.slice(1))) { + throw new VotedForNonDelegateError(vote); + } + } + + return super.canBeApplied(transaction, wallet, walletManager); + } + + public apply(transaction: Transaction, wallet: Database.IWallet): void { + const { data } = transaction; + const vote = data.asset.votes[0]; + if (vote.startsWith("+")) { + wallet.vote = vote.slice(1); + } else { + wallet.vote = null; + } + } + + public revert(transaction: Transaction, wallet: Database.IWallet): void { + const { data } = transaction; + const vote = data.asset.votes[0]; + if (vote.startsWith("+")) { + wallet.vote = null; + } else { + wallet.vote = vote.slice(1); + } + } + + public emitEvents(transaction: Transaction, emitter: EventEmitter.EventEmitter): void { + const vote = transaction.data.asset.votes[0]; + + emitter.emit(vote.startsWith("+") ? "wallet.vote" : "wallet.unvote", { + delegate: vote, + transaction: transaction.data, + }); + } + + public canEnterTransactionPool(data: ITransactionData, guard: TransactionPool.IGuard): boolean { + return ( + !this.typeFromSenderAlreadyInPool(data, guard) && + !this.secondSignatureRegistrationFromSenderAlreadyInPool(data, guard) + ); + } +} diff --git a/packages/core-transactions/src/index.ts b/packages/core-transactions/src/index.ts new file mode 100644 index 0000000000..4b843d599f --- /dev/null +++ b/packages/core-transactions/src/index.ts @@ -0,0 +1,7 @@ +import * as errors from "./errors"; + +export * from "./handlers/transaction"; +export * from "./interfaces"; + +export { errors }; +export { transactionHandlerRegistry as TransactionHandlerRegistry } from "./handler-registry"; diff --git a/packages/core-transactions/src/interfaces.ts b/packages/core-transactions/src/interfaces.ts new file mode 100644 index 0000000000..554ab8aac9 --- /dev/null +++ b/packages/core-transactions/src/interfaces.ts @@ -0,0 +1,17 @@ +import { Database, EventEmitter, TransactionPool } from "@arkecosystem/core-interfaces"; +import { ITransactionData, Transaction, TransactionConstructor } from "@arkecosystem/crypto"; + +export interface ITransactionHandler { + getConstructor(): TransactionConstructor; + + canBeApplied(transaction: Transaction, wallet: Database.IWallet, walletManager?: Database.IWalletManager): boolean; + applyToSender(transaction: Transaction, wallet: Database.IWallet): void; + applyToRecipient(transaction: Transaction, wallet: Database.IWallet): void; + revertForSender(transaction: Transaction, wallet: Database.IWallet): void; + revertForRecipient(transaction: Transaction, wallet: Database.IWallet): void; + apply(transaction: Transaction, wallet: Database.IWallet): void; + revert(transaction: Transaction, wallet: Database.IWallet): void; + + canEnterTransactionPool(data: ITransactionData, guard: TransactionPool.IGuard): boolean; + emitEvents(transaction: Transaction, emitter: EventEmitter.EventEmitter): void; +} diff --git a/packages/core-transactions/src/utils.ts b/packages/core-transactions/src/utils.ts new file mode 100644 index 0000000000..e8bded4c3a --- /dev/null +++ b/packages/core-transactions/src/utils.ts @@ -0,0 +1,12 @@ +import { configManager, ITransactionData } from "@arkecosystem/crypto"; +import bs58check from "bs58check"; + +export function isRecipientOnActiveNetwork(transaction: ITransactionData): boolean { + const recipientPrefix = bs58check.decode(transaction.recipientId).readUInt8(0); + + if (recipientPrefix === configManager.get("pubKeyHash")) { + return true; + } + + return false; +} diff --git a/packages/core-transactions/tsconfig.json b/packages/core-transactions/tsconfig.json new file mode 100644 index 0000000000..0b089c5fa8 --- /dev/null +++ b/packages/core-transactions/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": ["src/**/**.ts"] +} diff --git a/packages/core-utils/.gitattributes b/packages/core-utils/.gitattributes index 60cc52db63..63f6a5b970 100644 --- a/packages/core-utils/.gitattributes +++ b/packages/core-utils/.gitattributes @@ -2,10 +2,6 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore -/__tests__ export-ignore -/docs export-ignore /README.md export-ignore diff --git a/packages/core-utils/README.md b/packages/core-utils/README.md index f0a6c9a94d..85e952b597 100644 --- a/packages/core-utils/README.md +++ b/packages/core-utils/README.md @@ -1,12 +1,12 @@ -# Ark Core - Utilities +# Persona Core - Utilities

- +

## Documentation -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core-utils.html). +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/required/core-utils.html). ## Security @@ -14,10 +14,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [Brian Faust](https://github.com/faustbrian) -- [Joshua Noack](https://github.com/supaiku0) -- [All Contributors](../../../../contributors) +This project exists thanks to all the people who [contribute](../../../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-utils/__tests__/delegate-calculator.test.ts b/packages/core-utils/__tests__/delegate-calculator.test.ts deleted file mode 100644 index eef8b99b41..0000000000 --- a/packages/core-utils/__tests__/delegate-calculator.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -import "./__support__/mocks/core-container-calculator"; - -import { Bignum, models } from "@arkecosystem/crypto"; -import "jest-extended"; -import { calculateApproval, calculateProductivity } from "../src/delegate-calculator"; - -let delegate; - -beforeEach(() => { - delegate = new models.Wallet("D61xc3yoBQDitwjqUspMPx1ooET6r1XLt7"); - delegate.producedBlocks = 0; - delegate.missedBlocks = 0; -}); - -describe("Delegate Calculator", () => { - describe("calculateApproval", () => { - it("should calculate correctly with a height", () => { - delegate.voteBalance = new Bignum(10000 * 1e8); - - expect(calculateApproval(delegate, 1)).toBe(1); - }); - - it("should calculate correctly without a height", () => { - delegate.voteBalance = new Bignum(10000 * 1e8); - - expect(calculateApproval(delegate)).toBe(1); - }); - - it("should calculate correctly with 2 decimals", () => { - delegate.voteBalance = new Bignum(16500 * 1e8); - - expect(calculateApproval(delegate, 1)).toBe(1.65); - }); - }); - - describe("calculateProductivity", () => { - it("should calculate correctly for a value above 0", () => { - delegate.missedBlocks = 10; - delegate.producedBlocks = 100; - - expect(calculateProductivity(delegate)).toBe(90.91); - }); - - it("should calculate correctly for a value of 0", () => { - delegate.missedBlocks = 0; - delegate.producedBlocks = 0; - - expect(calculateProductivity(delegate)).toBe(0.0); - }); - }); -}); diff --git a/packages/core-utils/__tests__/round-calculator.test.ts b/packages/core-utils/__tests__/round-calculator.test.ts deleted file mode 100644 index 7466050694..0000000000 --- a/packages/core-utils/__tests__/round-calculator.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import "./__support__/mocks/core-container"; - -import { app } from "@arkecosystem/core-container"; -import "jest-extended"; -import { calculateRound, isNewRound } from "../src/round-calculator"; - -describe("Round calculator", () => { - describe("calculateRound", () => { - it("should calculate the round when nextRound is the same", () => { - const { round, nextRound } = calculateRound(1); - expect(round).toBe(1); - expect(nextRound).toBe(1); - }); - - it("should calculate the round when nextRound is not the same", () => { - const { round, nextRound } = calculateRound(51); - expect(round).toBe(1); - expect(nextRound).toBe(2); - }); - }); - - describe("isNewRound", () => { - it("should determine the beginning of a new round", () => { - expect(isNewRound(1)).toBeTrue(); - expect(isNewRound(2)).toBeFalse(); - expect(isNewRound(52)).toBeTrue(); - }); - }); -}); diff --git a/packages/core-utils/package.json b/packages/core-utils/package.json index 6a0adc1f38..da252dddd6 100644 --- a/packages/core-utils/package.json +++ b/packages/core-utils/package.json @@ -1,7 +1,7 @@ { "name": "@arkecosystem/core-utils", - "description": "Utilities for Ark Core", - "version": "2.2.1", + "description": "Utilities for ARK Core", + "version": "2.3.15", "contributors": [ "Brian Faust " ], @@ -11,38 +11,25 @@ "dist" ], "scripts": { - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", "prepublishOnly": "yarn build", - "pretest": "yarn lint && yarn build", "compile": "../../node_modules/typescript/bin/tsc", "build": "yarn clean && yarn compile", "build:watch": "yarn clean && yarn compile -w", - "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "test": "cross-env CORE_ENV=test jest --runInBand --forceExit", - "test:coverage": "cross-env CORE_ENV=test jest --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$' --runInBand --forceExit", - "test:debug": "cross-env CORE_ENV=test node --inspect-brk ../../node_modules/.bin/jest --runInBand", - "test:watch": "cross-env CORE_ENV=test jest --runInBand --watch", - "test:watch:all": "cross-env CORE_ENV=test jest --runInBand --watchAll", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" + "clean": "del dist" }, "dependencies": { - "@arkecosystem/core-container": "^2.2.1", - "@arkecosystem/crypto": "^2.2.1", + "@arkecosystem/core-container": "^2.3.15", + "@arkecosystem/core-interfaces": "^2.3.15", + "@arkecosystem/crypto": "^2.3.15", + "@faustbrian/dato": "^0.2.0", "cli-table3": "^0.5.1", - "dayjs-ext": "^2.2.0" + "fast-json-parse": "^1.0.3", + "got": "^9.6.0" }, "publishConfig": { "access": "public" }, "engines": { "node": ">=10.x" - }, - "jest": { - "preset": "../../jest-preset.json" } } diff --git a/packages/core-utils/src/delegate-calculator.ts b/packages/core-utils/src/delegate-calculator.ts index dd097f74bd..8aa47a1be4 100644 --- a/packages/core-utils/src/delegate-calculator.ts +++ b/packages/core-utils/src/delegate-calculator.ts @@ -30,19 +30,15 @@ function calculateApproval(delegate, height: any = null) { } /** - * Calculate the productivity of the given delegate. - * @param {Delegate} delegate - * @return {Number} Productivity, with 2 decimals + * Calculate the forged total of the given delegate. + * @param {Delegate} delegate + * @return {Bignum} Forged total */ -function calculateProductivity(delegate) { - const missedBlocks = +delegate.missedBlocks; - const producedBlocks = +delegate.producedBlocks; - - if (!missedBlocks && !producedBlocks) { - return +(0).toFixed(2); - } +function calculateForgedTotal(delegate) { + const forgedFees = new Bignum(delegate.forgedFees); + const forgedRewards = new Bignum(delegate.forgedRewards); - return +(100 - missedBlocks / ((producedBlocks + missedBlocks) / 100)).toFixed(2); + return +forgedFees.plus(forgedRewards).toFixed(); } -export { calculateApproval, calculateProductivity }; +export { calculateApproval, calculateForgedTotal }; diff --git a/packages/core-utils/src/format-timestamp.ts b/packages/core-utils/src/format-timestamp.ts index fccb15518b..90638bce29 100644 --- a/packages/core-utils/src/format-timestamp.ts +++ b/packages/core-utils/src/format-timestamp.ts @@ -1,21 +1,19 @@ import { app } from "@arkecosystem/core-container"; -import dayjs from "dayjs-ext"; +import { dato } from "@faustbrian/dato"; /** * Format the given epoch based timestamp into human and unix. * @param {Number} epochStamp * @return {Object} */ -function formatTimestamp(epochStamp) { +export function formatTimestamp(epochStamp) { const constants = app.getConfig().getMilestone(1); // @ts-ignore - const timestamp = dayjs(constants.epoch).add(epochStamp, "seconds"); + const timestamp = dato(constants.epoch).addSeconds(epochStamp); return { epoch: epochStamp, - unix: timestamp.unix(), - human: timestamp.toISOString(), + unix: timestamp.toUnix(), + human: timestamp.toISO(), }; } - -export { formatTimestamp }; diff --git a/packages/core-utils/src/has-some-property.ts b/packages/core-utils/src/has-some-property.ts new file mode 100644 index 0000000000..f80c66da48 --- /dev/null +++ b/packages/core-utils/src/has-some-property.ts @@ -0,0 +1,9 @@ +/** + * Check if an object has at least one of the given properties. + * @param {Object} object + * @param {Array} props + * @return {Boolean} + */ +export function hasSomeProperty(object, props): boolean { + return props.some(prop => object.hasOwnProperty(prop)); +} diff --git a/packages/core-utils/src/httpie.ts b/packages/core-utils/src/httpie.ts new file mode 100644 index 0000000000..67057a47a8 --- /dev/null +++ b/packages/core-utils/src/httpie.ts @@ -0,0 +1,94 @@ +// tslint:disable: max-classes-per-file + +import parseJSON from "fast-json-parse"; +import got from "got"; + +export class HttpieError extends Error { + constructor(error) { + super(error.message); + + Object.defineProperty(this, "message", { + enumerable: false, + value: error.message, + }); + + Object.defineProperty(this, "name", { + enumerable: false, + value: this.constructor.name, + }); + + if (error.response) { + Object.defineProperty(this, "response", { + enumerable: false, + value: { + body: parseJSON(error.response.body).value, + headers: error.response.headers, + status: error.response.statusCode, + }, + }); + } + + Error.captureStackTrace(this, this.constructor); + } +} + +export interface IHttpieResponse { + body: T; + headers: { [key: string]: string }; + status: number; +} + +class Httpie { + public async get(url: string, opts?): Promise> { + return this.sendRequest("get", url, opts); + } + + public async post(url: string, opts?): Promise> { + return this.sendRequest("post", url, opts); + } + + public async put(url: string, opts?): Promise> { + return this.sendRequest("put", url, opts); + } + + public async patch(url: string, opts?): Promise> { + return this.sendRequest("patch", url, opts); + } + + public async head(url: string, opts?): Promise> { + return this.sendRequest("head", url, opts); + } + + public async delete(url: string, opts?): Promise> { + return this.sendRequest("delete", url, opts); + } + + private async sendRequest(method: string, url: string, opts?): Promise> { + if (!opts) { + opts = {}; + } + + if (opts.body && typeof opts !== "string") { + opts.body = JSON.stringify(opts.body); + } + + // Do not retry unless explicitly stated. + if (!opts.retry) { + opts.retry = { retries: 0 }; + } + + try { + const { body, headers, statusCode } = await got[method](url, opts); + + return { + body: parseJSON(body).value, + headers, + status: statusCode, + }; + } catch (error) { + throw new HttpieError(error); + } + } +} + +export const httpie = new Httpie(); diff --git a/packages/core-utils/src/index.ts b/packages/core-utils/src/index.ts index a53757ecff..d2f481a050 100644 --- a/packages/core-utils/src/index.ts +++ b/packages/core-utils/src/index.ts @@ -1,13 +1,26 @@ import { bignumify } from "./bignumify"; import { CappedSet } from "./capped-set"; -import { calculateApproval, calculateProductivity } from "./delegate-calculator"; +import { calculateApproval, calculateForgedTotal } from "./delegate-calculator"; import { formatTimestamp } from "./format-timestamp"; +import { hasSomeProperty } from "./has-some-property"; +import { httpie, IHttpieResponse } from "./httpie"; import { NSect } from "./nsect"; import { calculateRound, isNewRound } from "./round-calculator"; import { calculate } from "./supply-calculator"; -const delegateCalculator = { calculateApproval, calculateProductivity }; +const delegateCalculator = { calculateApproval, calculateForgedTotal }; const roundCalculator = { calculateRound, isNewRound }; const supplyCalculator = { calculate }; -export { CappedSet, NSect, bignumify, delegateCalculator, formatTimestamp, roundCalculator, supplyCalculator }; +export { + CappedSet, + NSect, + bignumify, + delegateCalculator, + formatTimestamp, + IHttpieResponse, + httpie, + hasSomeProperty, + roundCalculator, + supplyCalculator, +}; diff --git a/packages/core-utils/src/nsect.ts b/packages/core-utils/src/nsect.ts index ec13dfe85e..b7337d34f7 100644 --- a/packages/core-utils/src/nsect.ts +++ b/packages/core-utils/src/nsect.ts @@ -75,6 +75,20 @@ export class NSect { assert.notStrictEqual(indexOfHighestMatching, -1); assert(indexOfHighestMatching < indexesToProbe.length - 1); + if (indexesToProbe[indexOfHighestMatching] + 1 === indexesToProbe[indexOfHighestMatching + 1]) { + // In a narrow range, it may happen that: + // highestMatching = 1100 + // indexesToProbe[0] = 1099, is a match + // indexesToProbe[1] = 1100, is a match + // indexesToProbe[2] = 1101, is not a match + // indexesToProbe[3] = 1103, is not a match + // indexOfHighestMatching = 1 + // Then we know highestMatching is the definitive result because the probe + // declared that the data at index 1100 matches and the data at index 1101 + // does not match. + break; + } + low = highestMatching + 1; high = indexesToProbe[indexOfHighestMatching + 1] - 1; } diff --git a/packages/core-utils/src/round-calculator.ts b/packages/core-utils/src/round-calculator.ts index 0ef1b6694c..4b30ae7fbb 100644 --- a/packages/core-utils/src/round-calculator.ts +++ b/packages/core-utils/src/round-calculator.ts @@ -1,31 +1,85 @@ import { app } from "@arkecosystem/core-container"; +import { Shared } from "@arkecosystem/core-interfaces"; -/** - * Calculate the round and nextRound based on the height and active delegates. - * @param {Number} height - * @param {Number} maxDelegates - * @return {Object} - */ -function calculateRound(height, maxDelegates: any = null) { +export const calculateRound = (height: number): Shared.IRoundInfo => { const config = app.getConfig(); - maxDelegates = maxDelegates || config.getMilestone(height).activeDelegates; + const { milestones } = config.config; - const round = Math.floor((height - 1) / maxDelegates) + 1; - const nextRound = Math.floor(height / maxDelegates) + 1; + let round = 0; + let roundHeight = 1; + let nextRound = 0; + let maxDelegates = 0; - return { round, nextRound, maxDelegates }; -} + let milestoneHeight = height; + let milestone = null; -/** - * Detect if height is the beginning of a new round. - * @param {Number} height - * @return {boolean} true if new round, false if not - */ -function isNewRound(height) { - const config = app.getConfig(); - const maxDelegates = config.getMilestone(height).activeDelegates; + for (let i = 0, j = 0; i < milestones.length; i++) { + if (!milestone || milestone.activeDelegates !== milestones[i].activeDelegates) { + milestone = milestones[i]; + } + maxDelegates = milestone.activeDelegates; + + let delegateCountChanged = false; + let nextMilestoneHeight = milestone.height; + + for (j = i + 1; j < milestones.length; j++) { + const nextMilestone = milestones[j]; + if (nextMilestone.height > height) { + break; + } + + if ( + nextMilestone.activeDelegates !== milestone.activeDelegates && + nextMilestone.height > milestone.height + ) { + console.assert(isNewRound(nextMilestone.height)); + delegateCountChanged = true; + maxDelegates = nextMilestone.activeDelegates; + milestoneHeight = nextMilestone.height - milestone.height; + nextMilestoneHeight = nextMilestone.height; + i = j - 1; + break; + } + } + + if (delegateCountChanged) { + console.assert(milestoneHeight % milestone.activeDelegates === 0); + round += milestoneHeight / milestone.activeDelegates; + roundHeight += milestoneHeight; + } + + if (i === milestones.length - 1 || milestones[i + 1].height > height) { + const roundIncrease = + Math.floor((height - nextMilestoneHeight) / maxDelegates) + (delegateCountChanged ? 0 : 1); + round += roundIncrease; + roundHeight += (roundIncrease - 1) * maxDelegates; + nextRound = round + ((height - (nextMilestoneHeight - 1)) % maxDelegates === 0 ? 1 : 0); + break; + } + + delegateCountChanged = false; + } + + return { round, roundHeight, nextRound, maxDelegates }; +}; + +export const isNewRound = (height: number): boolean => { + const { config } = app.getConfig(); + + // Since milestones are merged, find the first milestone to introduce the delegate count. + let milestone; + for (let i = config.milestones.length - 1; i >= 0; i--) { + const temp = config.milestones[i]; + if (temp.height > height) { + continue; + } - return height % maxDelegates === 1; -} + if (!milestone || temp.activeDelegates === milestone.activeDelegates) { + milestone = temp; + } else { + break; + } + } -export { calculateRound, isNewRound }; + return height === 1 || (height - milestone.height) % milestone.activeDelegates === 0; +}; diff --git a/packages/core-utils/src/supply-calculator.ts b/packages/core-utils/src/supply-calculator.ts index 6b6624da21..8331570ace 100644 --- a/packages/core-utils/src/supply-calculator.ts +++ b/packages/core-utils/src/supply-calculator.ts @@ -1,6 +1,6 @@ import { app } from "@arkecosystem/core-container"; import { Blockchain } from "@arkecosystem/core-interfaces"; -import { Bignum, configManager } from "@arkecosystem/crypto"; +import { Bignum } from "@arkecosystem/crypto"; /** * Calculate the total supply at the given height diff --git a/packages/core-vote-report/.gitattributes b/packages/core-vote-report/.gitattributes index 60cc52db63..63f6a5b970 100644 --- a/packages/core-vote-report/.gitattributes +++ b/packages/core-vote-report/.gitattributes @@ -2,10 +2,6 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore -/__tests__ export-ignore -/docs export-ignore /README.md export-ignore diff --git a/packages/core-vote-report/README.md b/packages/core-vote-report/README.md index d517f17f8f..cc2499676e 100644 --- a/packages/core-vote-report/README.md +++ b/packages/core-vote-report/README.md @@ -1,12 +1,12 @@ -# Ark Core - Vote Report +# Persona Core - Vote Report

- +

## Documentation -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core-vote-report.html). +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/optional/core-vote-report.html). ## Security @@ -14,9 +14,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [Brian Faust](https://github.com/faustbrian) -- [All Contributors](../../../../contributors) +This project exists thanks to all the people who [contribute](../../../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-vote-report/__tests__/__support__/setup.ts b/packages/core-vote-report/__tests__/__support__/setup.ts deleted file mode 100644 index c23a414551..0000000000 --- a/packages/core-vote-report/__tests__/__support__/setup.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { app } from "@arkecosystem/core-container"; -import { setUpContainer } from "@arkecosystem/core-test-utils/src/helpers/container"; -import { defaults } from "../../src/defaults"; -import { startServer } from "../../src/server"; - -jest.setTimeout(60000); - -let server; -async function setUp() { - await setUpContainer({ - exit: "@arkecosystem/core-blockchain", - }); - - server = await startServer(defaults); -} - -async function tearDown() { - await server.stop(); - await app.tearDown(); -} - -export { setUp, tearDown }; diff --git a/packages/core-vote-report/package.json b/packages/core-vote-report/package.json index 2d9b06f3b1..57854a912e 100644 --- a/packages/core-vote-report/package.json +++ b/packages/core-vote-report/package.json @@ -1,7 +1,7 @@ { "name": "@arkecosystem/core-vote-report", - "description": "Vote Report for Ark Core", - "version": "2.2.1", + "description": "Vote Report for ARK Core", + "version": "2.3.15", "contributors": [ "Brian Faust " ], @@ -11,44 +11,30 @@ "dist" ], "scripts": { - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", "prepublishOnly": "yarn build", - "pretest": "yarn lint && yarn build", "compile": "../../node_modules/typescript/bin/tsc", "build": "yarn clean && yarn compile && cp -r src/templates dist/templates", "build:watch": "yarn clean && yarn compile -w", - "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "test": "cross-env CORE_ENV=test jest --runInBand --forceExit", - "test:coverage": "cross-env CORE_ENV=test jest --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$' --runInBand --forceExit", - "test:debug": "cross-env CORE_ENV=test node --inspect-brk ../../node_modules/.bin/jest --runInBand", - "test:watch": "cross-env CORE_ENV=test jest --runInBand --watch", - "test:watch:all": "cross-env CORE_ENV=test jest --runInBand --watchAll", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" + "clean": "del dist" }, "dependencies": { - "@arkecosystem/core-container": "^2.2.1", - "@arkecosystem/core-http-utils": "^2.2.1", - "@arkecosystem/core-utils": "^2.2.1", - "@arkecosystem/crypto": "^2.2.1", + "@arkecosystem/core-container": "^2.3.15", + "@arkecosystem/core-http-utils": "^2.3.15", + "@arkecosystem/core-utils": "^2.3.15", + "@arkecosystem/crypto": "^2.3.15", "handlebars": "^4.1.0", - "lodash.sumby": "^4.6.0" + "lodash.sumby": "^4.6.0", + "vision": "^5.4.4" }, "devDependencies": { - "@types/handlebars": "^4.0.40", - "@types/lodash.sumby": "^4.6.4" + "@types/handlebars": "^4.1.0", + "@types/lodash.sumby": "^4.6.6", + "@types/vision": "^5.3.6" }, "publishConfig": { "access": "public" }, "engines": { "node": ">=10.x" - }, - "jest": { - "preset": "../../jest-preset.json" } } diff --git a/packages/core-vote-report/src/handler.ts b/packages/core-vote-report/src/handler.ts index 79d91da923..61e41534c0 100644 --- a/packages/core-vote-report/src/handler.ts +++ b/packages/core-vote-report/src/handler.ts @@ -1,19 +1,19 @@ import { app } from "@arkecosystem/core-container"; import { Blockchain, Database } from "@arkecosystem/core-interfaces"; -import { delegateCalculator, supplyCalculator } from "@arkecosystem/core-utils"; -import { configManager } from "@arkecosystem/crypto"; -import sumBy from "lodash/sumBy"; +import { delegateCalculator, roundCalculator, supplyCalculator } from "@arkecosystem/core-utils"; +import { Bignum, configManager } from "@arkecosystem/crypto"; +import sumBy from "lodash.sumby"; export function handler(request, h) { const config = app.getConfig(); const blockchain = app.resolvePlugin("blockchain"); const databaseService = app.resolvePlugin("database"); - const formatDelegates = (delegates, lastHeight) => - delegates.map((delegate, index) => { + const formatDelegates = (delegates: Database.IWallet[], lastHeight: number) => + delegates.map((delegate: Database.IWallet, index: number) => { const filteredVoters = databaseService.walletManager .allByPublicKey() - .filter(wallet => wallet.vote === delegate.publicKey && wallet.balance.toNumber() > 0.1 * 1e8); + .filter(wallet => wallet.vote === delegate.publicKey && (wallet.balance as Bignum).gt(0.1 * 1e8)); const approval = Number(delegateCalculator.calculateApproval(delegate, lastHeight)).toLocaleString( undefined, @@ -42,7 +42,8 @@ export function handler(request, h) { }); const lastBlock = blockchain.getLastBlock(); - const constants = config.getMilestone(lastBlock.data.height); + const { maxDelegates } = roundCalculator.calculateRound(lastBlock.data.height); + // @ts-ignore const delegateRows = request.server.app.config.delegateRows; @@ -56,12 +57,14 @@ export function handler(request, h) { }) .sort((a, b) => (a as any).rate - (b as any).rate); - const active = allByUsername.slice(0, constants.activeDelegates); - const standby = allByUsername.slice(constants.activeDelegates + 1, delegateRows); + const active = allByUsername.slice(0, maxDelegates); + const standby = allByUsername.slice(maxDelegates + 1, delegateRows); - const voters = databaseService.walletManager.allByPublicKey().filter(wallet => wallet.vote && wallet.balance.toNumber() > 0.1 * 1e8); + const voters = databaseService.walletManager + .allByPublicKey() + .filter(wallet => wallet.vote && (wallet.balance as Bignum).gt(0.1 * 1e8)); - const totalVotes = sumBy(voters, (wallet: any) => +wallet.balance.toFixed()); + const totalVotes = sumBy(voters, wallet => +wallet.balance.toFixed()); const percentage = (totalVotes * 100) / supply; const client = configManager.get("client"); @@ -70,7 +73,7 @@ export function handler(request, h) { .view("index", { client, voteHeader: `Vote ${client.token}`.padStart(10), - activeDelegatesCount: constants.activeDelegates, + activeDelegatesCount: maxDelegates, activeDelegates: formatDelegates(active, lastBlock.data.height), standbyDelegates: formatDelegates(standby, lastBlock.data.height), voters: voters.length.toLocaleString(undefined, { diff --git a/packages/core-vote-report/src/server.ts b/packages/core-vote-report/src/server.ts index 468edcfa47..8b6b3beb9f 100644 --- a/packages/core-vote-report/src/server.ts +++ b/packages/core-vote-report/src/server.ts @@ -14,6 +14,7 @@ export async function startServer(config) { relativeTo: __dirname, path: "templates", }), + [require("vision")], ); // @ts-ignore diff --git a/packages/core-webhooks/.gitattributes b/packages/core-webhooks/.gitattributes index 60cc52db63..63f6a5b970 100644 --- a/packages/core-webhooks/.gitattributes +++ b/packages/core-webhooks/.gitattributes @@ -2,10 +2,6 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore -/__tests__ export-ignore -/docs export-ignore /README.md export-ignore diff --git a/packages/core-webhooks/README.md b/packages/core-webhooks/README.md index f71d705e2c..d5cf751cf4 100644 --- a/packages/core-webhooks/README.md +++ b/packages/core-webhooks/README.md @@ -1,12 +1,12 @@ -# Ark Core - Webhooks +# Persona Core - Webhooks

- +

## Documentation -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core-webhooks.html). +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/optional/core-webhooks.html). ## Security @@ -14,9 +14,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [Brian Faust](https://github.com/faustbrian) -- [All Contributors](../../../../contributors) +This project exists thanks to all the people who [contribute](../../../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core-webhooks/__tests__/__support__/setup.ts b/packages/core-webhooks/__tests__/__support__/setup.ts deleted file mode 100644 index 7d7053b3b7..0000000000 --- a/packages/core-webhooks/__tests__/__support__/setup.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { app } from "@arkecosystem/core-container"; -import { setUpContainer } from "@arkecosystem/core-test-utils/src/helpers/container"; -import { database } from "../../src/database"; -import { webhookManager } from "../../src/manager"; -import { startServer } from "../../src/server"; - -jest.setTimeout(60000); - -async function setUp() { - process.env.CORE_WEBHOOKS_ENABLED = "true"; - - await setUpContainer({ - exclude: ["@arkecosystem/core-api", "@arkecosystem/core-graphql", "@arkecosystem/core-forger"], - }); - - await database.setUp({ - dialect: "sqlite", - storage: `${process.env.CORE_PATH_DATA}/webhooks.sqlite`, - logging: process.env.CORE_DB_LOGGING, - }); - - await webhookManager.setUp(); - - await startServer({ - enabled: false, - host: process.env.CORE_WEBHOOKS_HOST || "0.0.0.0", - port: process.env.CORE_WEBHOOKS_PORT || 4004, - whitelist: ["127.0.0.1", "::ffff:127.0.0.1"], - pagination: { - limit: 100, - include: ["/api/webhooks"], - }, - }); -} - -async function tearDown() { - await app.tearDown(); -} - -export { setUp, tearDown }; diff --git a/packages/core-webhooks/__tests__/__support__/utils.ts b/packages/core-webhooks/__tests__/__support__/utils.ts deleted file mode 100644 index 8b44919ce8..0000000000 --- a/packages/core-webhooks/__tests__/__support__/utils.ts +++ /dev/null @@ -1,61 +0,0 @@ -import axios from "axios"; -import "jest-extended"; - -function request(method, path, params = {}) { - const url = `http://localhost:4004/api/${path}`; - const instance = axios[method.toLowerCase()]; - - return ["GET", "DELETE"].includes(method) ? instance(url, { params }) : instance(url, params); -} - -function expectJson(response) { - expect(response.data).toBeObject(); -} - -function expectStatus(response, code) { - expect(response.status).toBe(code); -} - -function expectResource(response) { - expect(response.data.data).toBeObject(); -} - -function expectCollection(response) { - expect(Array.isArray(response.data.data)).toBe(true); -} - -function expectPaginator(response, firstPage = true) { - expect(response.data.meta).toBeObject(); - expect(response.data.meta).toHaveProperty("count"); - expect(response.data.meta).toHaveProperty("pageCount"); - expect(response.data.meta).toHaveProperty("totalCount"); - expect(response.data.meta).toHaveProperty("next"); - expect(response.data.meta).toHaveProperty("previous"); - expect(response.data.meta).toHaveProperty("self"); - expect(response.data.meta).toHaveProperty("first"); - expect(response.data.meta).toHaveProperty("last"); -} - -function expectSuccessful(response, statusCode = 200) { - this.expectStatus(response, statusCode); - this.expectJson(response); -} - -function expectError(response, statusCode = 404) { - this.expectStatus(response, statusCode); - this.expectJson(response); - expect(response.data.statusCode).toBeNumber(); - expect(response.data.error).toBeString(); - expect(response.data.message).toBeString(); -} - -export { - request, - expectJson, - expectStatus, - expectResource, - expectCollection, - expectPaginator, - expectSuccessful, - expectError, -}; diff --git a/packages/core-webhooks/__tests__/server.test.ts b/packages/core-webhooks/__tests__/server.test.ts deleted file mode 100644 index 11b644e3a6..0000000000 --- a/packages/core-webhooks/__tests__/server.test.ts +++ /dev/null @@ -1,116 +0,0 @@ -import "jest-extended"; -import { setUp, tearDown } from "./__support__/setup"; -import * as utils from "./__support__/utils"; - -beforeAll(async () => { - await setUp(); -}); - -afterAll(async () => { - await tearDown(); -}); - -const postData = { - event: "block.forged", - target: "https://httpbin.org/post", - enabled: true, - conditions: [ - { - key: "generatorPublicKey", - condition: "eq", - value: "test-generator", - }, - { - key: "fee", - condition: "gte", - value: "123", - }, - ], -}; - -function createWebhook(data = null) { - return utils.request("POST", "webhooks", data || postData); -} - -describe("API 2.0 - Webhooks", () => { - describe("GET /webhooks", () => { - it("should GET all the webhooks", async () => { - const response = await utils.request("GET", "webhooks"); - utils.expectSuccessful(response); - utils.expectCollection(response); - }); - }); - - describe("POST /webhooks", () => { - it("should POST a new webhook with a simple condition", async () => { - const response = await createWebhook(); - utils.expectSuccessful(response, 201); - utils.expectResource(response); - }); - - it("should POST a new webhook with a complex condition", async () => { - const response = await createWebhook({ - event: "block.forged", - target: "https://httpbin.org/post", - enabled: true, - conditions: [ - { - key: "fee", - condition: "between", - value: { - min: 1, - max: 2, - }, - }, - ], - }); - utils.expectSuccessful(response, 201); - utils.expectResource(response); - }); - - it("should POST a new webhook with an empty array as condition", async () => { - const response = await createWebhook({ - event: "block.forged", - target: "https://httpbin.org/post", - enabled: true, - conditions: [], - }); - utils.expectSuccessful(response, 201); - utils.expectResource(response); - }); - }); - - describe("GET /webhooks/{id}", () => { - it("should GET a webhook by the given id", async () => { - const webhook = await createWebhook(); - - const response = await utils.request("GET", `webhooks/${webhook.data.data.id}`); - utils.expectSuccessful(response); - utils.expectResource(response); - - const { data } = response.data; - const webhookData = Object.assign(webhook.data.data, { - token: data.token.substring(0, 32), - }); - expect(data).toEqual(webhookData); - }); - }); - - describe("PUT /webhooks/{id}", () => { - it("should PUT a webhook by the given id", async () => { - const webhook = await createWebhook(); - - const response = await utils.request("PUT", `webhooks/${webhook.data.data.id}`, postData); - utils.expectStatus(response, 204); - }); - }); - - describe("DELETE /webhooks/{id}", () => { - it("should DELETE a webhook by the given id", async () => { - const webhook = await createWebhook(); - - const response = await utils.request("DELETE", `webhooks/${webhook.data.data.id}`); - utils.expectStatus(response, 204); - }); - }); -}); diff --git a/packages/core-webhooks/package.json b/packages/core-webhooks/package.json index 574cfddae7..687267381c 100644 --- a/packages/core-webhooks/package.json +++ b/packages/core-webhooks/package.json @@ -1,7 +1,7 @@ { "name": "@arkecosystem/core-webhooks", - "description": "Webhooks for Ark Core", - "version": "2.2.1", + "description": "Webhooks for ARK Core", + "version": "2.3.15", "contributors": [ "Brian Faust " ], @@ -11,45 +11,32 @@ "dist" ], "scripts": { - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", "prepublishOnly": "yarn build", "pretest": "bash ../../scripts/pre-test.sh", "compile": "../../node_modules/typescript/bin/tsc", "build": "yarn clean && yarn compile", "build:watch": "yarn clean && yarn compile -w", - "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "test": "cross-env CORE_ENV=test jest --runInBand --forceExit", - "test:coverage": "cross-env CORE_ENV=test jest --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$' --runInBand --forceExit", - "test:debug": "cross-env CORE_ENV=test node --inspect-brk ../../node_modules/.bin/jest --runInBand", - "test:watch": "cross-env CORE_ENV=test jest --runInBand --watch", - "test:watch:all": "cross-env CORE_ENV=test jest --runInBand --watchAll", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" + "clean": "del dist" }, "dependencies": { - "@arkecosystem/core-container": "^2.2.1", - "@arkecosystem/core-http-utils": "^2.2.1", - "@arkecosystem/core-interfaces": "^2.2.1", - "axios": "^0.18.0", + "@arkecosystem/core-container": "^2.3.15", + "@arkecosystem/core-event-emitter": "^2.3.15", + "@arkecosystem/core-http-utils": "^2.3.15", + "@arkecosystem/core-interfaces": "^2.3.15", + "@arkecosystem/core-utils": "^2.3.15", "boom": "^7.3.0", "fs-extra": "^7.0.1", - "hapi-pagination": "^2.0.1", + "hapi-pagination": "^2.1.0", "joi": "^14.3.1", - "sequelize": "^4.42.0", - "sqlite3": "^4.0.6", - "umzug": "^2.2.0" + "lowdb": "^1.0.0", + "uuid": "^3.3.2" }, "devDependencies": { - "@arkecosystem/core-test-utils": "^2.2.1", "@types/boom": "^7.2.1", "@types/fs-extra": "^5.0.5", - "@types/joi": "^14.3.1", - "@types/sequelize": "^4.27.36", - "@types/sqlite3": "^3.1.3", + "@types/joi": "^14.3.2", + "@types/sequelize": "^4.27.39", + "@types/sqlite3": "^3.1.5", "@types/umzug": "^2.2.0" }, "publishConfig": { @@ -57,8 +44,5 @@ }, "engines": { "node": ">=10.x" - }, - "jest": { - "preset": "../../jest-preset.json" } } diff --git a/packages/core-webhooks/src/conditions.ts b/packages/core-webhooks/src/conditions.ts index 055bf5ad45..1f1bf5e77c 100644 --- a/packages/core-webhooks/src/conditions.ts +++ b/packages/core-webhooks/src/conditions.ts @@ -1,14 +1,47 @@ -const between = (actual, expected) => actual > expected.min && actual < expected.max; -const contains = (actual, expected) => actual.includes(expected); -const eq = (actual, expected) => actual === expected; -const falsy = actual => actual === false; -const gt = (actual, expected) => actual > expected; -const gte = (actual, expected) => actual >= expected; -const lt = (actual, expected) => actual < expected; -const lte = (actual, expected) => actual <= expected; -const ne = (actual, expected) => actual !== expected; -const notBetween = (actual, expected) => !between(actual, expected); -const regexp = (actual, expected) => new RegExp(expected).test(actual); -const truthy = actual => actual === true; - -export { between, contains, eq, falsy, gt, gte, lt, lte, ne, notBetween, regexp, truthy }; +export function between(actual, expected) { + return actual > expected.min && actual < expected.max; +} + +export function contains(actual, expected) { + return actual.includes(expected); +} + +export function eq(actual, expected) { + return actual === expected; +} + +export function falsy(actual) { + return actual === false; +} + +export function gt(actual, expected) { + return actual > expected; +} + +export function gte(actual, expected) { + return actual >= expected; +} + +export function lt(actual, expected) { + return actual < expected; +} + +export function lte(actual, expected) { + return actual <= expected; +} + +export function ne(actual, expected) { + return actual !== expected; +} + +export function notBetween(actual, expected) { + return !between(actual, expected); +} + +export function regexp(actual, expected) { + return new RegExp(expected).test(actual); +} + +export function truthy(actual) { + return actual === true; +} diff --git a/packages/core-webhooks/src/database.ts b/packages/core-webhooks/src/database.ts new file mode 100644 index 0000000000..67c037125f --- /dev/null +++ b/packages/core-webhooks/src/database.ts @@ -0,0 +1,82 @@ +import { ensureFileSync, existsSync, removeSync } from "fs-extra"; +import lowdb from "lowdb"; +import FileSync from "lowdb/adapters/FileSync"; +import uuidv4 from "uuid/v4"; + +class Database { + private database: lowdb.LowdbSync; + + public make(): void { + const adapterFile: string = `${process.env.CORE_PATH_CACHE}/webhooks.json`; + + if (!existsSync(adapterFile)) { + ensureFileSync(adapterFile); + } + + this.database = lowdb(new FileSync(adapterFile)); + this.database.defaults({ webhooks: [] }).write(); + } + + public paginate(params) { + const rows = this.database + .get("webhooks", []) + .slice(params.offset, params.offset + params.limit) + .value(); + + return { rows, count: rows.length }; + } + + public findById(id) { + return this.database + .get("webhooks") + .find({ id }) + .value(); + } + + public findByEvent(event) { + const rows = this.database + .get("webhooks") + .filter({ event }) + .value(); + + return { rows, count: rows.length }; + } + + public create(data) { + data.id = uuidv4(); + + this.database + .get("webhooks") + .push(data) + .write(); + + return this.findById(data.id); + } + + public update(id, data) { + return this.database + .get("webhooks") + .find({ id }) + .assign(data) + .write(); + } + + public destroy(id) { + try { + return this.database + .get("webhooks") + .remove({ id }) + .write(); + } catch (error) { + return false; + } + } + + public reset(): void { + removeSync(`${process.env.CORE_PATH_CACHE}/webhooks.json`); + + this.make(); + } +} + +export const database = new Database(); diff --git a/packages/core-webhooks/src/database/index.ts b/packages/core-webhooks/src/database/index.ts deleted file mode 100644 index ba1d355037..0000000000 --- a/packages/core-webhooks/src/database/index.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { app } from "@arkecosystem/core-container"; -import fs from "fs-extra"; -import path from "path"; -import Sequelize from "sequelize"; -import Umzug from "umzug"; - -class Database { - public connection: any; - public model: any; - - /** - * Set up the database connection. - * @param {Object} config - * @return {void} - */ - public async setUp(config) { - if (this.connection) { - throw new Error("Webhooks database already initialised"); - } - - if (config.dialect === "sqlite" && config.storage !== ":memory:") { - await fs.ensureFile(config.storage); - } - - this.connection = new Sequelize({ - ...config, - ...{ operatorsAliases: Sequelize.Op }, - }); - - try { - await this.connection.authenticate(); - await this.__runMigrations(); - this.__registerModels(); - } catch (error) { - app.forceExit("Unable to connect to the database!", error); - } - } - - /** - * Paginate all webhooks. - * @param {Object} params - * @return {Object} - */ - public paginate(params) { - return this.model.findAndCountAll(params); - } - - /** - * Get a webhook for the given id. - * @param {Number} id - * @return {Object} - */ - public findById(id) { - return this.model.findById(id); - } - - /** - * Get all webhooks for the given event. - * @param {String} event - * @return {Array} - */ - public findByEvent(event) { - return this.model.findAll({ where: { event } }); - } - - /** - * Store a new webhook. - * @param {Object} data - * @return {Object} - */ - public create(data) { - return this.model.create(data); - } - - /** - * Update the webhook for the given id. - * @param {Number} id - * @param {Object} data - * @return {Boolean} - */ - public async update(id, data) { - try { - const webhook = await this.model.findById(id); - - webhook.update(data); - - return true; - } catch (e) { - return false; - } - } - - /** - * Destroy the webhook for the given id. - * @param {Number} id - * @return {Boolean} - */ - public async destroy(id) { - try { - const webhook = await this.model.findById(id); - - webhook.destroy(); - - return true; - } catch (e) { - return false; - } - } - - /** - * Run all migrations. - * @return {Boolean} - */ - public async __runMigrations() { - const umzug = new Umzug({ - storage: "sequelize", - storageOptions: { - sequelize: this.connection, - }, - migrations: { - params: [this.connection.getQueryInterface(), Sequelize], - path: path.join(__dirname, "migrations"), - }, - }); - - return umzug.up(); - } - - /** - * Register all models. - * @return {void} - */ - public __registerModels() { - this.model = this.connection.define( - "webhook", - { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: Sequelize.INTEGER, - }, - event: Sequelize.STRING, - target: Sequelize.STRING, - conditions: Sequelize.JSON, - token: { - unique: true, - type: Sequelize.STRING, - }, - enabled: Sequelize.BOOLEAN, - }, - {}, - ); - } -} - -export const database = new Database(); diff --git a/packages/core-webhooks/src/database/migrations/20180305163843-create-webhook.ts b/packages/core-webhooks/src/database/migrations/20180305163843-create-webhook.ts deleted file mode 100644 index b3fa71da0f..0000000000 --- a/packages/core-webhooks/src/database/migrations/20180305163843-create-webhook.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * The webhooks migration. - * @type {Object} - */ -module.exports = { - /** - * Run the migrations. - * @param {Sequelize.QueryInterface} queryInterface - * @param {Sequelize} Sequelize - * @return {void} - */ - async up(queryInterface, Sequelize) { - await queryInterface.createTable("webhooks", { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: Sequelize.INTEGER, - }, - event: Sequelize.STRING, - target: Sequelize.STRING, - conditions: Sequelize.JSON, - token: { - unique: true, - type: Sequelize.STRING, - }, - enabled: Sequelize.BOOLEAN, - createdAt: { - allowNull: false, - type: Sequelize.DATE, - }, - updatedAt: { - allowNull: false, - type: Sequelize.DATE, - }, - }); - - queryInterface.addIndex("webhooks", ["event"]); - }, - /** - * Reverse the migrations. - * @param {Sequelize.QueryInterface} queryInterface - * @param {Sequelize} Sequelize - * @return {void} - */ - async down(queryInterface, Sequelize) { - return queryInterface.dropTable("webhooks"); - }, -}; diff --git a/packages/core-webhooks/src/defaults.ts b/packages/core-webhooks/src/defaults.ts index 098ccc6fab..8d76fcd136 100644 --- a/packages/core-webhooks/src/defaults.ts +++ b/packages/core-webhooks/src/defaults.ts @@ -1,18 +1,8 @@ export const defaults = { enabled: process.env.CORE_WEBHOOKS_ENABLED, - database: { - dialect: "sqlite", - storage: `${process.env.CORE_PATH_DATA}/webhooks.sqlite`, - logging: process.env.CORE_DB_LOGGING, - }, server: { - enabled: process.env.CORE_WEBHOOKS_API_ENABLED, host: process.env.CORE_WEBHOOKS_HOST || "0.0.0.0", port: process.env.CORE_WEBHOOKS_PORT || 4004, whitelist: ["127.0.0.1", "::ffff:127.0.0.1"], - pagination: { - limit: 100, - include: ["/api/webhooks"], - }, }, }; diff --git a/packages/core-webhooks/src/index.ts b/packages/core-webhooks/src/index.ts index 109e380a5e..f98e64596d 100644 --- a/packages/core-webhooks/src/index.ts +++ b/packages/core-webhooks/src/index.ts @@ -1,7 +1,7 @@ import { Container, Logger } from "@arkecosystem/core-interfaces"; import { database } from "./database"; import { defaults } from "./defaults"; -import { webhookManager } from "./manager"; +import { WebhookManager } from "./manager"; import { startServer } from "./server"; export const plugin: Container.PluginDescriptor = { @@ -9,23 +9,17 @@ export const plugin: Container.PluginDescriptor = { defaults, alias: "webhooks", async register(container: Container.IContainer, options) { - const logger = container.resolvePlugin("logger"); - if (!options.enabled) { - logger.info("Webhooks are disabled :grey_exclamation:"); - + container.resolvePlugin("logger").info("Webhooks are disabled"); return; } - await database.setUp(options.database); - - await webhookManager.setUp(); + database.make(); - if (options.server.enabled) { - return startServer(options.server); - } + const manager = new WebhookManager(); + await manager.setUp(); - logger.info("Webhooks API server is disabled :grey_exclamation:"); + return startServer(options.server); }, async deregister(container: Container.IContainer, options) { if (options.server.enabled) { diff --git a/packages/core-webhooks/src/manager.ts b/packages/core-webhooks/src/manager.ts index 953cc02c2d..59effcd214 100644 --- a/packages/core-webhooks/src/manager.ts +++ b/packages/core-webhooks/src/manager.ts @@ -1,40 +1,31 @@ import { app } from "@arkecosystem/core-container"; -import { Blockchain, EventEmitter, Logger } from "@arkecosystem/core-interfaces"; -import axios from "axios"; +import { ApplicationEvents } from "@arkecosystem/core-event-emitter"; +import { EventEmitter, Logger } from "@arkecosystem/core-interfaces"; +import { httpie } from "@arkecosystem/core-utils"; import * as conditions from "./conditions"; import { database } from "./database"; -class WebhookManager { - public config: any; - public logger = app.resolvePlugin("logger"); +export class WebhookManager { + private readonly logger: Logger.ILogger = app.resolvePlugin("logger"); + private readonly emitter: EventEmitter.EventEmitter = app.resolvePlugin("event-emitter"); - /** - * Set up the webhook app. - * @return {void} - */ public async setUp() { - const emitter = app.resolvePlugin("event-emitter"); - const blockchain = app.resolvePlugin("blockchain"); + for (const event of Object.values(ApplicationEvents)) { + this.emitter.on(event, async payload => { + const { rows } = await database.findByEvent(event); - for (const event of blockchain.getEvents()) { - emitter.on(event, async payload => { - const webhooks = await database.findByEvent(event); - - for (const webhook of this.getMatchingWebhooks(webhooks, payload)) { + for (const webhook of this.getMatchingWebhooks(rows, payload)) { try { - const response = await axios.post( - webhook.target, - { + const response = await httpie.post(webhook.target, { + body: { timestamp: +new Date(), data: payload, event: webhook.event, }, - { - headers: { - Authorization: webhook.token, - }, + headers: { + Authorization: webhook.token, }, - ); + }); this.logger.debug( `Webhooks Job ${webhook.id} completed! Event [${webhook.event}] has been transmitted to [${ @@ -49,12 +40,6 @@ class WebhookManager { } } - /** - * Get all webhooks. - * @param {Array} webhooks - * @param {Object} payload - * @return {Array} - */ private getMatchingWebhooks(webhooks, payload) { const matches = []; @@ -83,5 +68,3 @@ class WebhookManager { return matches; } } - -export const webhookManager = new WebhookManager(); diff --git a/packages/core-webhooks/src/server/handler.ts b/packages/core-webhooks/src/server/handler.ts deleted file mode 100644 index 42ad2714ad..0000000000 --- a/packages/core-webhooks/src/server/handler.ts +++ /dev/null @@ -1,110 +0,0 @@ -import Boom from "boom"; -import { randomBytes } from "crypto"; -import { database } from "../database"; -import * as schema from "./schema"; -import * as utils from "./utils"; - -/** - * @type {Object} - */ -const index = { - /** - * @param {Hapi.Request} request - * @param {Hapi.Toolkit} h - * @return {Hapi.Response} - */ - async handler(request, h) { - const webhooks = await database.paginate(utils.paginate(request)); - - return utils.toPagination(request, webhooks); - }, -}; - -/** - * @type {Object} - */ -const store = { - /** - * @param {Hapi.Request} request - * @param {Hapi.Toolkit} h - * @return {Hapi.Response} - */ - async handler(request, h) { - const token = randomBytes(32).toString("hex"); - - request.payload.token = token.substring(0, 32); - - const webhook = await database.create(request.payload); - webhook.token = token; - - return h.response(utils.respondWithResource(request, webhook)).code(201); - }, - options: { - plugins: { - pagination: { - enabled: false, - }, - }, - validate: schema.store, - }, -}; - -/** - * @type {Object} - */ -const show = { - /** - * @param {Hapi.Request} request - * @param {Hapi.Toolkit} h - * @return {Hapi.Response} - */ - async handler(request, h) { - const webhook = await database.findById(request.params.id); - delete webhook.token; - - return utils.respondWithResource(request, webhook); - }, - options: { - validate: schema.show, - }, -}; - -/** - * @type {Object} - */ -const update = { - /** - * @param {Hapi.Request} request - * @param {Hapi.Toolkit} h - * @return {Hapi.Response} - */ - async handler(request, h) { - await database.update(request.params.id, request.payload); - - return h.response(null).code(204); - }, - options: { - validate: schema.update, - }, -}; - -/** - * @type {Object} - */ -const destroy = { - /** - * @param {Hapi.Request} request - * @param {Hapi.Toolkit} h - * @return {Hapi.Response} - */ - async handler(request, h) { - await database.destroy(request.params.id); - - return h.response(null).code(204); - }, - options: { - validate: schema.destroy, - }, -}; - -export { index, store, show, update, destroy }; diff --git a/packages/core-webhooks/src/server/index.ts b/packages/core-webhooks/src/server/index.ts index 889d13d9a9..4978f25fe9 100644 --- a/packages/core-webhooks/src/server/index.ts +++ b/packages/core-webhooks/src/server/index.ts @@ -1,5 +1,8 @@ import { createServer, mountServer, plugins } from "@arkecosystem/core-http-utils"; -import { registerRoutes } from "./routes"; +import { randomBytes } from "crypto"; +import { database } from "../database"; +import * as schema from "./schema"; +import * as utils from "./utils"; export async function startServer(config) { const server = await createServer({ @@ -31,23 +34,87 @@ export async function startServer(config) { }, query: { limit: { - default: config.pagination.limit, + default: 100, }, }, results: { name: "data", }, routes: { - include: config.pagination.include, + include: ["/api/webhooks"], exclude: ["*"], }, }, }); - await server.register({ - plugin: registerRoutes, - routes: { prefix: "/api" }, - options: config, + server.route({ + method: "GET", + path: "/api/webhooks", + handler: request => { + return utils.toPagination(database.paginate(utils.paginate(request))); + }, + }); + + server.route({ + method: "POST", + path: "/api/webhooks", + handler(request: any, h) { + const token = randomBytes(32).toString("hex"); + request.payload.token = token.substring(0, 32); + + const webhook: any = database.create(request.payload); + webhook.token = token; + + return h.response(utils.respondWithResource(webhook)).code(201); + }, + options: { + plugins: { + pagination: { + enabled: false, + }, + }, + validate: schema.store, + }, + }); + + server.route({ + method: "GET", + path: "/api/webhooks/{id}", + async handler(request) { + const webhook: any = database.findById(request.params.id); + delete webhook.token; + + return utils.respondWithResource(webhook); + }, + options: { + validate: schema.show, + }, + }); + + server.route({ + method: "PUT", + path: "/api/webhooks/{id}", + handler: (request, h) => { + database.update(request.params.id, request.payload); + + return h.response(null).code(204); + }, + options: { + validate: schema.update, + }, + }); + + server.route({ + method: "DELETE", + path: "/api/webhooks/{id}", + handler: (request, h) => { + database.destroy(request.params.id); + + return h.response(null).code(204); + }, + options: { + validate: schema.destroy, + }, }); return mountServer("Webhook API", server); diff --git a/packages/core-webhooks/src/server/routes.ts b/packages/core-webhooks/src/server/routes.ts deleted file mode 100644 index 1e2bb7c4ad..0000000000 --- a/packages/core-webhooks/src/server/routes.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { destroy, index, show, store, update } from "./handler"; - -export const registerRoutes = { - name: "Ark Webhooks API", - version: "0.1.0", - async register(server, options) { - server.route([ - { - method: "GET", - path: "/webhooks", - ...index, - }, - { - method: "POST", - path: "/webhooks", - ...store, - }, - { - method: "GET", - path: "/webhooks/{id}", - ...show, - }, - { - method: "PUT", - path: "/webhooks/{id}", - ...update, - }, - { - method: "DELETE", - path: "/webhooks/{id}", - ...destroy, - }, - ]); - }, -}; diff --git a/packages/core-webhooks/src/server/schema.ts b/packages/core-webhooks/src/server/schema.ts index 5bae3ad945..3009a463f8 100644 --- a/packages/core-webhooks/src/server/schema.ts +++ b/packages/core-webhooks/src/server/schema.ts @@ -1,6 +1,6 @@ import Joi from "joi"; -const conditions = [ +export const conditions = [ "between", "contains", "eq", @@ -15,7 +15,7 @@ const conditions = [ "truthy", ]; -const index = { +export const index = { query: { page: Joi.number() .integer() @@ -26,13 +26,13 @@ const index = { }, }; -const show = { +export const show = { params: { id: Joi.string(), }, }; -const store = { +export const store = { payload: { event: Joi.string().required(), target: Joi.string() @@ -49,7 +49,7 @@ const store = { }, }; -const update = { +export const update = { params: { id: Joi.string(), }, @@ -67,10 +67,8 @@ const update = { }, }; -const destroy = { +export const destroy = { params: { id: Joi.string(), }, }; - -export { index, show, store, update, destroy }; diff --git a/packages/core-webhooks/src/server/transformer.ts b/packages/core-webhooks/src/server/transformer.ts deleted file mode 100644 index 801abde24b..0000000000 --- a/packages/core-webhooks/src/server/transformer.ts +++ /dev/null @@ -1,10 +0,0 @@ -export function transform(model) { - return { - id: model.id, - event: model.event, - target: model.target, - token: model.token, - enabled: model.enabled, - conditions: model.conditions, - }; -} diff --git a/packages/core-webhooks/src/server/utils.ts b/packages/core-webhooks/src/server/utils.ts index eebd60d875..4a4f376ece 100644 --- a/packages/core-webhooks/src/server/utils.ts +++ b/packages/core-webhooks/src/server/utils.ts @@ -1,84 +1,30 @@ import Boom from "boom"; -import { transform } from "./transformer"; -/** - * Transform the given data into a resource. - * @param {Hapi.Request} request - * @param {Object} data - * @return {Object} - */ -const transformResource = (request, data) => transform(data); - -/** - * Transform the given data into a collection. - * @param {Hapi.Request} request - * @param {Object} data - * @return {Array} - */ -const transformCollection = (request, data) => data.map(d => transformResource(request, d)); - -/** - * Create a pagination object for the request. - * @param {Hapi.Request} request - * @return {Object} - */ -const paginate = request => ({ - offset: (request.query.page - 1) * request.query.limit, - limit: request.query.limit, -}); - -/** - * Respond with a resource. - * @param {Hapi.Request} request - * @param {Object} data - * @return {Hapi.Response} - */ -const respondWithResource = (request, data) => (data ? { data: transformResource(request, data) } : Boom.notFound()); - -/** - * Respond with a collection. - * @param {Hapi.Request} request - * @param {Object} data - * @return {Object} - */ -const respondWithCollection = (request, data) => ({ - data: transformCollection(request, data), -}); - -/** - * Alias of "transformResource". - * @param {Hapi.Request} request - * @param {Object} data - * @return {Hapi.Response} - */ -const toResource = (request, data) => transformResource(request, data); - -/** - * Alias of "transformCollection". - * @param {Hapi.Request} request - * @param {Object} data - * @return {Hapi.Response} - */ -const toCollection = (request, data) => transformCollection(request, data); - -/** - * Transform the given data into a pagination. - * @param {Hapi.Request} request - * @param {Object} data - * @return {Hapi.Response} - */ -const toPagination = (request, data) => ({ - results: transformCollection(request, data.rows), - totalCount: data.count, -}); - -export { - transformResource, - transformCollection, - paginate, - respondWithResource, - respondWithCollection, - toResource, - toCollection, - toPagination, -}; +export function transformResource(model) { + return { + id: model.id, + event: model.event, + target: model.target, + token: model.token, + enabled: model.enabled, + conditions: model.conditions, + }; +} + +export function paginate(request) { + return { + offset: (request.query.page - 1) * request.query.limit, + limit: request.query.limit, + }; +} + +export function respondWithResource(data) { + return data ? { data: transformResource(data) } : Boom.notFound(); +} + +export function toPagination(data) { + return { + results: data.rows.map(d => transformResource(d)), + totalCount: data.count, + }; +} diff --git a/packages/core/.gitattributes b/packages/core/.gitattributes index 60cc52db63..63f6a5b970 100644 --- a/packages/core/.gitattributes +++ b/packages/core/.gitattributes @@ -2,10 +2,6 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore -/__tests__ export-ignore -/docs export-ignore /README.md export-ignore diff --git a/packages/core/README.md b/packages/core/README.md index 9b1ca1c190..70a47d3e7a 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -1,12 +1,12 @@ -# Ark Core - Core +# Persona Core - Core

- +

## Documentation -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/core.html). +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/guidebook/core/plugins/required/core.html). ## Security @@ -14,12 +14,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [All Contributors](../../../../contributors) -- [Alex Barnsley](https://github.com/alexbarnsley) -- [Brian Faust](https://github.com/faustbrian) -- [François-Xavier Thoorens](https://github.com/fix) -- [Kristjan Košič](https://github.com/kristjank) +This project exists thanks to all the people who [contribute](../../../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/core/bin/config/devnet/.env b/packages/core/bin/config/devnet/.env index 774f2f5204..fc91553312 100644 --- a/packages/core/bin/config/devnet/.env +++ b/packages/core/bin/config/devnet/.env @@ -4,13 +4,13 @@ CORE_DB_HOST=localhost CORE_DB_PORT=5432 CORE_P2P_HOST=0.0.0.0 -CORE_P2P_PORT=4002 +CORE_P2P_PORT=4101 CORE_WEBHOOKS_HOST=0.0.0.0 -CORE_WEBHOOKS_PORT=4004 +CORE_WEBHOOKS_PORT=4104 CORE_JSON_RPC_HOST=0.0.0.0 CORE_JSON_RPC_PORT=8080 CORE_API_HOST=0.0.0.0 -CORE_API_PORT=4003 +CORE_API_PORT=4103 diff --git a/packages/core/bin/config/devnet/peers.json b/packages/core/bin/config/devnet/peers.json index dd8d460934..4148a9009b 100644 --- a/packages/core/bin/config/devnet/peers.json +++ b/packages/core/bin/config/devnet/peers.json @@ -1,25 +1,25 @@ { "list": [ - { - "ip": "167.114.29.51", - "port": 4002 - }, - { - "ip": "167.114.29.52", - "port": 4002 - }, - { - "ip": "167.114.29.53", - "port": 4002 - }, - { - "ip": "167.114.29.54", - "port": 4002 - }, - { - "ip": "167.114.29.55", - "port": 4002 - } + { + "ip": "5.135.75.64", + "port": 4101 + }, + { + "ip": "5.135.75.65", + "port": 4101 + }, + { + "ip": "5.135.75.66", + "port": 4101 + }, + { + "ip": "5.135.75.67", + "port": 4101 + }, + { + "ip": "5.135.75.68", + "port": 4101 + } ], - "sources": ["https://raw.githubusercontent.com/ArkEcosystem/peers/master/devnet.json"] + "sources": [] } diff --git a/packages/core/bin/config/devnet/plugins.js b/packages/core/bin/config/devnet/plugins.js index 2daa2e526d..56050965e5 100644 --- a/packages/core/bin/config/devnet/plugins.js +++ b/packages/core/bin/config/devnet/plugins.js @@ -1,19 +1,6 @@ module.exports = { "@arkecosystem/core-event-emitter": {}, - "@arkecosystem/core-logger-winston": { - transports: { - console: { - options: { - level: process.env.CORE_LOG_LEVEL || "debug", - }, - }, - dailyRotate: { - options: { - level: process.env.CORE_LOG_LEVEL || "debug", - }, - }, - }, - }, + "@arkecosystem/core-logger-pino": {}, "@arkecosystem/core-database-postgres": { connection: { host: process.env.CORE_DB_HOST || "localhost", @@ -27,49 +14,30 @@ module.exports = { enabled: !process.env.CORE_TRANSACTION_POOL_DISABLED, maxTransactionsPerSender: process.env.CORE_TRANSACTION_POOL_MAX_PER_SENDER || 300, allowedSenders: [], - dynamicFees: { - enabled: true, - minFeePool: 1000, - minFeeBroadcast: 1000, - addonBytes: { - transfer: 100, - secondSignature: 250, - delegateRegistration: 400000, - vote: 100, - multiSignature: 500, - ipfs: 250, - timelockTransfer: 500, - multiPayment: 500, - delegateResignation: 400000, - }, - }, }, "@arkecosystem/core-p2p": { host: process.env.CORE_P2P_HOST || "0.0.0.0", - port: process.env.CORE_P2P_PORT || 4002, + port: process.env.CORE_P2P_PORT || 4101, minimumNetworkReach: 5, coldStart: 5, }, - "@arkecosystem/core-blockchain": { - fastRebuild: false, - }, + "@arkecosystem/core-blockchain": {}, "@arkecosystem/core-api": { enabled: !process.env.CORE_API_DISABLED, host: process.env.CORE_API_HOST || "0.0.0.0", - port: process.env.CORE_API_PORT || 4003, + port: process.env.CORE_API_PORT || 4103, whitelist: ["*"], }, "@arkecosystem/core-webhooks": { enabled: process.env.CORE_WEBHOOKS_ENABLED, server: { - enabled: process.env.CORE_WEBHOOKS_API_ENABLED, host: process.env.CORE_WEBHOOKS_HOST || "0.0.0.0", - port: process.env.CORE_WEBHOOKS_PORT || 4004, + port: process.env.CORE_WEBHOOKS_PORT || 4104, whitelist: ["127.0.0.1", "::ffff:127.0.0.1"], }, }, "@arkecosystem/core-forger": { - hosts: [`http://127.0.0.1:${process.env.CORE_P2P_PORT || 4002}`], + hosts: [`http://127.0.0.1:${process.env.CORE_P2P_PORT || 4101}`], }, "@arkecosystem/core-json-rpc": { enabled: process.env.CORE_JSON_RPC_ENABLED, diff --git a/packages/core/bin/config/mainnet/.env b/packages/core/bin/config/mainnet/.env index cee689565f..85a3c95778 100644 --- a/packages/core/bin/config/mainnet/.env +++ b/packages/core/bin/config/mainnet/.env @@ -4,13 +4,13 @@ CORE_DB_HOST=localhost CORE_DB_PORT=5432 CORE_P2P_HOST=0.0.0.0 -CORE_P2P_PORT=4001 +CORE_P2P_PORT=4102 CORE_WEBHOOKS_HOST=0.0.0.0 -CORE_WEBHOOKS_PORT=4004 +CORE_WEBHOOKS_PORT=4104 CORE_JSON_RPC_HOST=0.0.0.0 CORE_JSON_RPC_PORT=8080 CORE_API_HOST=0.0.0.0 -CORE_API_PORT=4003 +CORE_API_PORT=4103 diff --git a/packages/core/bin/config/mainnet/peers.json b/packages/core/bin/config/mainnet/peers.json index 3e03eeaf98..2cd6365260 100644 --- a/packages/core/bin/config/mainnet/peers.json +++ b/packages/core/bin/config/mainnet/peers.json @@ -1,261 +1,17 @@ { "list": [ { - "ip": "5.196.105.32", - "port": 4001 + "ip": "54.37.188.122", + "port": 4102 }, { - "ip": "5.196.105.33", - "port": 4001 + "ip": "54.37.188.123", + "port": 4102 }, { - "ip": "5.196.105.34", - "port": 4001 - }, - { - "ip": "5.196.105.35", - "port": 4001 - }, - { - "ip": "5.196.105.36", - "port": 4001 - }, - { - "ip": "5.196.105.37", - "port": 4001 - }, - { - "ip": "5.196.105.38", - "port": 4001 - }, - { - "ip": "5.196.105.39", - "port": 4001 - }, - { - "ip": "178.32.65.136", - "port": 4001 - }, - { - "ip": "178.32.65.137", - "port": 4001 - }, - { - "ip": "178.32.65.138", - "port": 4001 - }, - { - "ip": "178.32.65.139", - "port": 4001 - }, - { - "ip": "178.32.65.140", - "port": 4001 - }, - { - "ip": "178.32.65.141", - "port": 4001 - }, - { - "ip": "178.32.65.142", - "port": 4001 - }, - { - "ip": "178.32.65.143", - "port": 4001 - }, - { - "ip": "5.196.105.40", - "port": 4001 - }, - { - "ip": "5.196.105.41", - "port": 4001 - }, - { - "ip": "5.196.105.42", - "port": 4001 - }, - { - "ip": "5.196.105.43", - "port": 4001 - }, - { - "ip": "5.196.105.44", - "port": 4001 - }, - { - "ip": "5.196.105.45", - "port": 4001 - }, - { - "ip": "5.196.105.46", - "port": 4001 - }, - { - "ip": "5.196.105.47", - "port": 4001 - }, - { - "ip": "54.38.120.32", - "port": 4001 - }, - { - "ip": "54.38.120.33", - "port": 4001 - }, - { - "ip": "54.38.120.34", - "port": 4001 - }, - { - "ip": "54.38.120.35", - "port": 4001 - }, - { - "ip": "54.38.120.36", - "port": 4001 - }, - { - "ip": "54.38.120.37", - "port": 4001 - }, - { - "ip": "54.38.120.38", - "port": 4001 - }, - { - "ip": "54.38.120.39", - "port": 4001 - }, - { - "ip": "151.80.125.32", - "port": 4001 - }, - { - "ip": "151.80.125.33", - "port": 4001 - }, - { - "ip": "151.80.125.34", - "port": 4001 - }, - { - "ip": "151.80.125.35", - "port": 4001 - }, - { - "ip": "151.80.125.36", - "port": 4001 - }, - { - "ip": "151.80.125.37", - "port": 4001 - }, - { - "ip": "151.80.125.38", - "port": 4001 - }, - { - "ip": "151.80.125.39", - "port": 4001 - }, - { - "ip": "213.32.41.104", - "port": 4001 - }, - { - "ip": "213.32.41.105", - "port": 4001 - }, - { - "ip": "213.32.41.106", - "port": 4001 - }, - { - "ip": "213.32.41.107", - "port": 4001 - }, - { - "ip": "213.32.41.108", - "port": 4001 - }, - { - "ip": "213.32.41.109", - "port": 4001 - }, - { - "ip": "213.32.41.110", - "port": 4001 - }, - { - "ip": "213.32.41.111", - "port": 4001 - }, - { - "ip": "5.135.22.92", - "port": 4001 - }, - { - "ip": "5.135.22.93", - "port": 4001 - }, - { - "ip": "5.135.22.94", - "port": 4001 - }, - { - "ip": "5.135.22.95", - "port": 4001 - }, - { - "ip": "5.135.52.96", - "port": 4001 - }, - { - "ip": "5.135.52.97", - "port": 4001 - }, - { - "ip": "5.135.52.98", - "port": 4001 - }, - { - "ip": "5.135.52.99", - "port": 4001 - }, - { - "ip": "51.255.105.52", - "port": 4001 - }, - { - "ip": "51.255.105.53", - "port": 4001 - }, - { - "ip": "51.255.105.54", - "port": 4001 - }, - { - "ip": "51.255.105.55", - "port": 4001 - }, - { - "ip": "46.105.160.104", - "port": 4001 - }, - { - "ip": "46.105.160.105", - "port": 4001 - }, - { - "ip": "46.105.160.106", - "port": 4001 - }, - { - "ip": "46.105.160.107", - "port": 4001 + "ip": "54.37.188.124", + "port": 4102 } ], - "sources": ["https://raw.githubusercontent.com/ArkEcosystem/peers/master/mainnet.json"] + "sources": [] } diff --git a/packages/core/bin/config/mainnet/plugins.js b/packages/core/bin/config/mainnet/plugins.js index 03e36299b3..ce37d493d4 100644 --- a/packages/core/bin/config/mainnet/plugins.js +++ b/packages/core/bin/config/mainnet/plugins.js @@ -1,19 +1,6 @@ module.exports = { "@arkecosystem/core-event-emitter": {}, - "@arkecosystem/core-logger-winston": { - transports: { - console: { - options: { - level: process.env.CORE_LOG_LEVEL || "debug", - }, - }, - dailyRotate: { - options: { - level: process.env.CORE_LOG_LEVEL || "debug", - }, - }, - }, - }, + "@arkecosystem/core-logger-pino": {}, "@arkecosystem/core-database-postgres": { connection: { host: process.env.CORE_DB_HOST || "localhost", @@ -27,47 +14,30 @@ module.exports = { enabled: !process.env.CORE_TRANSACTION_POOL_DISABLED, maxTransactionsPerSender: process.env.CORE_TRANSACTION_POOL_MAX_PER_SENDER || 300, allowedSenders: [], - dynamicFees: { - enabled: true, - minFeePool: 3000, - minFeeBroadcast: 3000, - addonBytes: { - transfer: 100, - secondSignature: 250, - delegateRegistration: 400000, - vote: 100, - multiSignature: 500, - ipfs: 250, - timelockTransfer: 500, - multiPayment: 500, - delegateResignation: 400000, - }, - }, }, "@arkecosystem/core-p2p": { host: process.env.CORE_P2P_HOST || "0.0.0.0", - port: process.env.CORE_P2P_PORT || 4001, - }, - "@arkecosystem/core-blockchain": { - fastRebuild: false, + port: process.env.CORE_P2P_PORT || 4102, + minimumNetworkReach: 2, + coldStart: 10, }, + "@arkecosystem/core-blockchain": {}, "@arkecosystem/core-api": { enabled: !process.env.CORE_API_DISABLED, host: process.env.CORE_API_HOST || "0.0.0.0", - port: process.env.CORE_API_PORT || 4003, + port: process.env.CORE_API_PORT || 4103, whitelist: ["*"], }, "@arkecosystem/core-webhooks": { enabled: process.env.CORE_WEBHOOKS_ENABLED, server: { - enabled: process.env.CORE_WEBHOOKS_API_ENABLED, host: process.env.CORE_WEBHOOKS_HOST || "0.0.0.0", - port: process.env.CORE_WEBHOOKS_PORT || 4004, + port: process.env.CORE_WEBHOOKS_PORT || 4104, whitelist: ["127.0.0.1", "::ffff:127.0.0.1"], }, }, "@arkecosystem/core-forger": { - hosts: [`http://127.0.0.1:${process.env.CORE_P2P_PORT || 4001}`], + hosts: [`http://127.0.0.1:${process.env.CORE_P2P_PORT || 4102}`], }, "@arkecosystem/core-json-rpc": { enabled: process.env.CORE_JSON_RPC_ENABLED, diff --git a/packages/core/bin/config/testnet/.env b/packages/core/bin/config/testnet/.env index e5d8cd0a20..669f798ce6 100644 --- a/packages/core/bin/config/testnet/.env +++ b/packages/core/bin/config/testnet/.env @@ -4,13 +4,13 @@ CORE_DB_HOST=localhost CORE_DB_PORT=5432 CORE_P2P_HOST=0.0.0.0 -CORE_P2P_PORT=4000 +CORE_P2P_PORT=4100 CORE_WEBHOOKS_HOST=0.0.0.0 -CORE_WEBHOOKS_PORT=4004 +CORE_WEBHOOKS_PORT=4104 CORE_JSON_RPC_HOST=0.0.0.0 CORE_JSON_RPC_PORT=8080 CORE_API_HOST=0.0.0.0 -CORE_API_PORT=4003 +CORE_API_PORT=4103 diff --git a/packages/core/bin/config/testnet/peers.json b/packages/core/bin/config/testnet/peers.json index 13faa40da6..bfa8734e37 100644 --- a/packages/core/bin/config/testnet/peers.json +++ b/packages/core/bin/config/testnet/peers.json @@ -2,7 +2,7 @@ "list": [ { "ip": "127.0.0.1", - "port": 4000 + "port": 4100 } ] } diff --git a/packages/core/bin/config/testnet/plugins.js b/packages/core/bin/config/testnet/plugins.js index f15ac8164a..dcb60e0f9c 100644 --- a/packages/core/bin/config/testnet/plugins.js +++ b/packages/core/bin/config/testnet/plugins.js @@ -1,19 +1,6 @@ module.exports = { "@arkecosystem/core-event-emitter": {}, - "@arkecosystem/core-logger-winston": { - transports: { - console: { - options: { - level: process.env.CORE_LOG_LEVEL || "debug", - }, - }, - dailyRotate: { - options: { - level: process.env.CORE_LOG_LEVEL || "debug", - }, - }, - }, - }, + "@arkecosystem/core-logger-pino": {}, "@arkecosystem/core-database-postgres": { connection: { host: process.env.CORE_DB_HOST || "localhost", @@ -27,49 +14,30 @@ module.exports = { enabled: true, maxTransactionsPerSender: process.env.CORE_TRANSACTION_POOL_MAX_PER_SENDER || 300, allowedSenders: [], - dynamicFees: { - enabled: true, - minFeePool: 1000, - minFeeBroadcast: 1000, - addonBytes: { - transfer: 100, - secondSignature: 250, - delegateRegistration: 400000, - vote: 100, - multiSignature: 500, - ipfs: 250, - timelockTransfer: 500, - multiPayment: 500, - delegateResignation: 400000, - }, - }, }, "@arkecosystem/core-p2p": { host: process.env.CORE_P2P_HOST || "0.0.0.0", - port: process.env.CORE_P2P_PORT || 4000, + port: process.env.CORE_P2P_PORT || 4100, minimumNetworkReach: 5, coldStart: 5, }, - "@arkecosystem/core-blockchain": { - fastRebuild: false, - }, + "@arkecosystem/core-blockchain": {}, "@arkecosystem/core-api": { enabled: !process.env.CORE_API_DISABLED, host: process.env.CORE_API_HOST || "0.0.0.0", - port: process.env.CORE_API_PORT || 4003, + port: process.env.CORE_API_PORT || 4103, whitelist: ["*"], }, "@arkecosystem/core-webhooks": { enabled: process.env.CORE_WEBHOOKS_ENABLED, server: { - enabled: process.env.CORE_WEBHOOKS_API_ENABLED, host: process.env.CORE_WEBHOOKS_HOST || "0.0.0.0", - port: process.env.CORE_WEBHOOKS_PORT || 4004, + port: process.env.CORE_WEBHOOKS_PORT || 4104, whitelist: ["127.0.0.1", "::ffff:127.0.0.1"], }, }, "@arkecosystem/core-forger": { - hosts: [`http://127.0.0.1:${process.env.CORE_P2P_PORT || 4000}`], + hosts: [`http://127.0.0.1:${process.env.CORE_P2P_PORT || 4100}`], }, "@arkecosystem/core-json-rpc": { enabled: process.env.CORE_JSON_RPC_ENABLED, diff --git a/packages/core/package.json b/packages/core/package.json index 105daaa5a7..4e9c1d30cb 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@arkecosystem/core", - "description": "Core of the Ark Blockchain", - "version": "2.2.1", + "description": "Core of the ARK Blockchain", + "version": "2.3.15", "contributors": [ "François-Xavier Thoorens ", "Kristjan Košič ", @@ -17,28 +17,21 @@ "/oclif.manifest.json" ], "bin": { - "ark": "./bin/run" + "persona": "./bin/run" }, "scripts": { - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", "prepublishOnly": "yarn build", - "pretest": "yarn lint && yarn build", - "prepack": "oclif-dev manifest && npm shrinkwrap", + "prepack": "../../node_modules/.bin/oclif-dev manifest && npm shrinkwrap", "postpack": "rm -f oclif.manifest.json", "compile": "../../node_modules/typescript/bin/tsc", "build": "yarn clean && yarn compile && yarn copy", "build:watch": "yarn clean && yarn copy && yarn compile -w", "clean": "del dist", - "docs": "../../node_modules/typedoc/bin/typedoc src --out docs", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", "copy": "cd ./src && cpy './config' '../dist/' --parents && cd ..", "debug:start": "node --inspect-brk yarn ark core:run", "debug:relay": "node --inspect-brk yarn ark relay:run", "debug:forger": "node --inspect-brk yarn ark forger:run", - "ark": "./bin/run", + "persona": "./bin/run", "start:mainnet": "cross-env CORE_PATH_CONFIG=./bin/config/mainnet yarn ark core:run", "start:devnet": "cross-env CORE_PATH_CONFIG=./bin/config/devnet yarn ark core:run", "start:testnet": "cross-env CORE_PATH_CONFIG=./bin/config/testnet CORE_ENV=test yarn ark core:run", @@ -48,37 +41,39 @@ "forger:mainnet": "cross-env CORE_PATH_CONFIG=./bin/config/mainnet yarn ark forger:run", "forger:devnet": "cross-env CORE_PATH_CONFIG=./bin/config/devnet yarn ark forger:run", "forger:testnet": "cross-env CORE_PATH_CONFIG=./bin/config/testnet CORE_ENV=test yarn ark forger:run", - "full:testnet": "cross-env CORE_PATH_CONFIG=./bin/config/testnet CORE_ENV=test yarn ark core:run --networkStart", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" + "full:testnet": "cross-env CORE_PATH_CONFIG=./bin/config/testnet CORE_ENV=test yarn ark core:run --networkStart" }, "dependencies": { - "@arkecosystem/core-api": "^2.2.1", - "@arkecosystem/core-blockchain": "^2.2.1", - "@arkecosystem/core-container": "^2.2.1", - "@arkecosystem/core-database-postgres": "^2.2.1", - "@arkecosystem/core-event-emitter": "^2.2.1", - "@arkecosystem/core-forger": "^2.2.1", - "@arkecosystem/core-json-rpc": "^2.2.1", - "@arkecosystem/core-logger-winston": "^2.2.1", - "@arkecosystem/core-p2p": "^2.2.1", - "@arkecosystem/core-snapshots": "^2.2.1", - "@arkecosystem/core-transaction-pool": "^2.2.1", - "@arkecosystem/core-webhooks": "^2.2.1", - "@arkecosystem/crypto": "^2.2.1", - "@oclif/command": "^1.5.8", - "@oclif/config": "^1.10.3", + "@arkecosystem/core-api": "^2.3.15", + "@arkecosystem/core-blockchain": "^2.3.15", + "@arkecosystem/core-container": "^2.3.15", + "@arkecosystem/core-database-postgres": "^2.3.15", + "@arkecosystem/core-event-emitter": "^2.3.15", + "@arkecosystem/core-forger": "^2.3.15", + "@arkecosystem/core-json-rpc": "^2.3.15", + "@arkecosystem/core-logger-pino": "^2.3.15", + "@arkecosystem/core-p2p": "^2.3.15", + "@arkecosystem/core-snapshots": "^2.3.15", + "@arkecosystem/core-transaction-pool": "^2.3.15", + "@arkecosystem/core-utils": "^2.3.15", + "@arkecosystem/core-webhooks": "^2.3.15", + "@arkecosystem/crypto": "^2.3.15", + "@faustbrian/dato": "^0.2.0", + "@oclif/command": "^1.5.11", + "@oclif/config": "^1.12.9", "@oclif/plugin-autocomplete": "^0.1.0", "@oclif/plugin-commands": "^1.2.2", - "@oclif/plugin-help": "^2.1.4", - "@oclif/plugin-plugins": "^1.7.3", + "@oclif/plugin-help": "^2.1.6", + "@oclif/plugin-not-found": "^1.2.2", + "@oclif/plugin-plugins": "^1.7.7", "bip38": "^2.0.2", "bip39": "^2.5.0", "chalk": "^2.4.2", + "clear": "^0.1.0", "cli-progress": "^2.1.1", "cli-table3": "^0.5.1", - "cli-ux": "^5.1.0", - "dayjs-ext": "^2.2.0", - "env-paths": "^2.0.0", + "cli-ux": "^5.2.0", + "env-paths": "^2.1.0", "envfile": "^3.0.0", "execa": "^1.0.0", "fast-levenshtein": "^2.0.6", @@ -86,32 +81,30 @@ "latest-version": "^4.0.0", "listr": "^0.14.3", "lodash.minby": "^4.6.0", - "ora": "^3.0.0", + "nodejs-tail": "^1.1.0", + "ora": "^3.2.0", "pretty-bytes": "^5.1.0", "pretty-ms": "^4.0.0", - "prompts": "^2.0.0", + "prompts": "^2.0.3", + "read-last-lines": "^1.7.0", "semver": "^5.6.0", - "tail": "^2.0.2", "wif": "^2.0.6" }, "devDependencies": { - "@types/bip38": "^2.0.0", - "@types/bip39": "^2.4.1", + "@types/bip38": "^2.0.1", + "@types/bip39": "^2.4.2", "@types/cli-progress": "^1.8.0", - "@types/env-paths": "^1.0.2", "@types/execa": "^0.9.0", - "@types/fs-extra": "^5.0.4", - "@types/got": "^9.4.0", + "@types/fs-extra": "^5.0.5", + "@types/got": "^9.4.1", "@types/latest-version": "^4.0.0", "@types/listr": "^0.13.0", "@types/log-symbols": "^2.0.0", - "@types/ora": "^3.0.0", - "@types/pino": "^5.8.4", + "@types/ora": "^3.2.0", "@types/pretty-bytes": "^5.1.0", "@types/pretty-ms": "^4.0.0", "@types/prompts": "^1.2.0", "@types/semver": "^5.5.0", - "@types/tail": "^1.2.0", "@types/wif": "^2.0.1" }, "publishConfig": { @@ -120,9 +113,6 @@ "engines": { "node": ">=10.x" }, - "jest": { - "preset": "../../jest-preset.json" - }, "oclif": { "commands": "./dist/commands", "hooks": { @@ -134,7 +124,7 @@ "./dist/hooks/command_not_found/suggest" ] }, - "bin": "ark", + "bin": "persona", "topics": { "config": { "description": "manage core config variables" diff --git a/packages/core/src/commands/command.ts b/packages/core/src/commands/command.ts index 0fc618ea65..c04101594d 100644 --- a/packages/core/src/commands/command.ts +++ b/packages/core/src/commands/command.ts @@ -1,7 +1,8 @@ +import { Container } from "@arkecosystem/core-interfaces"; import { networks } from "@arkecosystem/crypto"; import Command, { flags } from "@oclif/command"; import cli from "cli-ux"; -import envPaths from "env-paths"; +import envPaths, { Paths } from "env-paths"; import { existsSync, readdirSync } from "fs"; import Listr from "listr"; import { join, resolve } from "path"; @@ -58,6 +59,10 @@ export abstract class BaseCommand extends Command { description: "the password for the encrypted bip38", dependsOn: ["bip38"], }), + suffix: flags.string({ + hidden: true, + default: "forger", + }), }; public static flagsSnapshot: Record = { @@ -68,6 +73,10 @@ export abstract class BaseCommand extends Command { trace: flags.boolean({ description: "dumps generated queries and settings to console", }), + suffix: flags.string({ + hidden: true, + default: "snapshot", + }), }; protected tasks: Array<{ title: string; task: any }> = []; @@ -88,7 +97,7 @@ export abstract class BaseCommand extends Command { return config; } - protected async buildApplication(app, flags: CommandFlags, config: Options) { + protected async buildApplication(app: Container.IContainer, flags: CommandFlags, config: Options) { await app.setUp(version, flags, { ...{ skipPlugins: flags.skipPlugins }, ...config, @@ -130,8 +139,8 @@ export abstract class BaseCommand extends Command { } } - protected async getPaths(flags: CommandFlags): Promise { - let paths: envPaths.Paths = this.getEnvPaths(flags); + protected async getPaths(flags: CommandFlags): Promise { + let paths: Paths = this.getEnvPaths(flags); for (const [key, value] of Object.entries(paths)) { paths[key] = `${value}/${flags.network}`; @@ -298,19 +307,26 @@ export abstract class BaseCommand extends Command { return this.getNetworks().map(network => ({ title: network, value: network })); } - protected async restartProcess(processName: string) { + protected async restartRunningProcessPrompt(processName: string, showPrompt: boolean = true) { if (processManager.isRunning(processName)) { - await confirm(`Would you like to restart the ${processName} process?`, () => { - try { - cli.action.start(`Restarting ${processName}`); - - processManager.restart(processName); - } catch (error) { - error.stderr ? this.error(`${error.message}: ${error.stderr}`) : this.error(error.message); - } finally { - cli.action.stop(); - } - }); + if (showPrompt) { + await confirm(`Would you like to restart the ${processName} process?`, () => { + this.restartProcess(processName); + }); + } else { + this.restartProcess(processName); + } + } + } + + protected restartProcess(processName: string): void { + try { + cli.action.start(`Restarting ${processName}`); + processManager.restart(processName); + } catch (error) { + error.stderr ? this.error(`${error.message}: ${error.stderr}`) : this.error(error.message); + } finally { + cli.action.stop(); } } @@ -346,7 +362,7 @@ export abstract class BaseCommand extends Command { } } - private getEnvPaths(flags: CommandFlags): envPaths.Paths { + private getEnvPaths(flags: CommandFlags): Paths { return envPaths(flags.token, { suffix: "core" }); } } diff --git a/packages/core/src/commands/config/cli.ts b/packages/core/src/commands/config/cli.ts index 6ede365dc5..2ec16f095b 100644 --- a/packages/core/src/commands/config/cli.ts +++ b/packages/core/src/commands/config/cli.ts @@ -13,7 +13,7 @@ export class CommandLineInterfaceCommand extends BaseCommand { $ ark config:cli --token=mine `, `Switch the npm registry channel -$ ark config:cli --channel=mine +$ ark config:cli --channel=next `, ]; @@ -23,7 +23,7 @@ $ ark config:cli --channel=mine }), channel: flags.string({ description: "the name of the channel that should be used", - options: ["alpha", "beta", "rc", "latest"], + options: ["next", "latest"], }), }; @@ -62,9 +62,9 @@ $ ark config:cli --channel=mine const { flags } = await this.parseWithNetwork(CommandLineInterfaceCommand); - await this.restartProcess(`${flags.token}-core`); - await this.restartProcess(`${flags.token}-relay`); - await this.restartProcess(`${flags.token}-forger`); + await this.restartRunningProcessPrompt(`${flags.token}-core`); + await this.restartRunningProcessPrompt(`${flags.token}-relay`); + await this.restartRunningProcessPrompt(`${flags.token}-forger`); } catch (err) { this.error(err.message); } finally { diff --git a/packages/core/src/commands/config/forger/index.ts b/packages/core/src/commands/config/forger/index.ts index 528abe454e..bd053bf4ce 100644 --- a/packages/core/src/commands/config/forger/index.ts +++ b/packages/core/src/commands/config/forger/index.ts @@ -28,6 +28,8 @@ $ ark config:forger --method=bip39 public async run(): Promise { const { flags } = await this.parseWithNetwork(ForgerCommand); + delete flags.suffix; + if (flags.method === "bip38") { return BIP38Command.run(this.formatFlags(flags)); } diff --git a/packages/core/src/commands/config/reset.ts b/packages/core/src/commands/config/reset.ts index a35f43833f..a12704fe81 100644 --- a/packages/core/src/commands/config/reset.ts +++ b/packages/core/src/commands/config/reset.ts @@ -1,4 +1,3 @@ -import { flags } from "@oclif/command"; import fs from "fs-extra"; import prompts from "prompts"; import { CommandFlags } from "../../types"; diff --git a/packages/core/src/commands/core/log.ts b/packages/core/src/commands/core/log.ts index f18e1f655f..fb0506d55f 100644 --- a/packages/core/src/commands/core/log.ts +++ b/packages/core/src/commands/core/log.ts @@ -13,6 +13,10 @@ export class LogCommand extends AbstractLogCommand { error: flags.boolean({ description: "only show error output", }), + lines: flags.integer({ + description: "number of lines to tail", + default: 15, + }), }; public getClass() { diff --git a/packages/core/src/commands/core/run.ts b/packages/core/src/commands/core/run.ts index d0715d687a..8d4bc7a76b 100644 --- a/packages/core/src/commands/core/run.ts +++ b/packages/core/src/commands/core/run.ts @@ -1,4 +1,5 @@ import { app } from "@arkecosystem/core-container"; +import { flags } from "@oclif/command"; import { CommandFlags } from "../../types"; import { BaseCommand } from "../command"; @@ -30,6 +31,10 @@ $ ark core:run --launchMode=seed ...BaseCommand.flagsNetwork, ...BaseCommand.flagsBehaviour, ...BaseCommand.flagsForger, + suffix: flags.string({ + hidden: true, + default: "core", + }), }; public async run(): Promise { diff --git a/packages/core/src/commands/core/start.ts b/packages/core/src/commands/core/start.ts index 593695098e..b6c21f1bb4 100644 --- a/packages/core/src/commands/core/start.ts +++ b/packages/core/src/commands/core/start.ts @@ -39,6 +39,10 @@ $ ark core:start --no-daemon default: true, allowNo: true, }), + suffix: flags.string({ + hidden: true, + default: "core", + }), }; public getClass() { @@ -50,7 +54,7 @@ $ ark core:start --no-daemon this.abortRunningProcess(`${flags.token}-relay`); try { - const { bip38, password } = await this.buildBIP38(flags); + await this.buildBIP38(flags); await this.runWithPm2( { diff --git a/packages/core/src/commands/forger/log.ts b/packages/core/src/commands/forger/log.ts index 4acb824d0a..1a3d8b11b7 100644 --- a/packages/core/src/commands/forger/log.ts +++ b/packages/core/src/commands/forger/log.ts @@ -13,6 +13,10 @@ export class LogCommand extends AbstractLogCommand { error: flags.boolean({ description: "only show error output", }), + lines: flags.integer({ + description: "number of lines to tail", + default: 15, + }), }; public getClass() { diff --git a/packages/core/src/commands/forger/run.ts b/packages/core/src/commands/forger/run.ts index 959875ab6c..de3395250a 100644 --- a/packages/core/src/commands/forger/run.ts +++ b/packages/core/src/commands/forger/run.ts @@ -27,7 +27,7 @@ $ ark forger:run --bip38="..." --password="..." "@arkecosystem/core-event-emitter", "@arkecosystem/core-config", "@arkecosystem/core-logger", - "@arkecosystem/core-logger-winston", + "@arkecosystem/core-logger-pino", "@arkecosystem/core-forger", ], options: { diff --git a/packages/core/src/commands/forger/start.ts b/packages/core/src/commands/forger/start.ts index 57bb99af13..aecbb87bb6 100644 --- a/packages/core/src/commands/forger/start.ts +++ b/packages/core/src/commands/forger/start.ts @@ -36,7 +36,7 @@ $ ark forger:start --no-daemon this.abortRunningProcess(`${flags.token}-core`); try { - const { bip38, password } = await this.buildBIP38(flags); + await this.buildBIP38(flags); await this.runWithPm2( { diff --git a/packages/core/src/commands/reinstall.ts b/packages/core/src/commands/reinstall.ts new file mode 100644 index 0000000000..e1b44b6761 --- /dev/null +++ b/packages/core/src/commands/reinstall.ts @@ -0,0 +1,53 @@ +import { flags } from "@oclif/command"; +import cli from "cli-ux"; + +import { confirm } from "../helpers/prompts"; +import { installFromChannel } from "../helpers/update"; +import { CommandFlags } from "../types"; +import { BaseCommand } from "./command"; + +export class ReinstallCommand extends BaseCommand { + public static description: string = "Reinstall the core"; + + public static flags: CommandFlags = { + force: flags.boolean({ + description: "force a reinstall", + }), + }; + + public async run(): Promise { + const { flags } = await this.parseWithNetwork(ReinstallCommand); + + if (flags.force) { + return this.performInstall(flags); + } + + try { + await confirm("Are you sure you want to reinstall?", async () => { + try { + await this.performInstall(flags); + } catch (err) { + this.error(err.message); + } finally { + cli.action.stop(); + } + }); + } catch (err) { + this.error(err.message); + } + } + + private async performInstall(flags: CommandFlags): Promise { + cli.action.start(`Reinstalling ${this.config.version}`); + + await installFromChannel(this.config.name, this.config.version); + + cli.action.stop(); + + this.warn(`Version ${this.config.version} has been installed.`); + + await this.restartRunningProcessPrompt(`${flags.token}-core`); + await this.restartRunningProcessPrompt(`${flags.token}-relay`); + await this.restartRunningProcessPrompt(`${flags.token}-forger`); + } +} diff --git a/packages/core/src/commands/relay/log.ts b/packages/core/src/commands/relay/log.ts index 99917ec16d..0d6775614c 100644 --- a/packages/core/src/commands/relay/log.ts +++ b/packages/core/src/commands/relay/log.ts @@ -13,6 +13,10 @@ export class LogCommand extends AbstractLogCommand { error: flags.boolean({ description: "only show error output", }), + lines: flags.integer({ + description: "number of lines to tail", + default: 15, + }), }; public getClass() { diff --git a/packages/core/src/commands/relay/run.ts b/packages/core/src/commands/relay/run.ts index 16d86d823c..3a43af8fe6 100644 --- a/packages/core/src/commands/relay/run.ts +++ b/packages/core/src/commands/relay/run.ts @@ -1,4 +1,5 @@ import { app } from "@arkecosystem/core-container"; +import { flags } from "@oclif/command"; import { CommandFlags } from "../../types"; import { BaseCommand } from "../command"; @@ -29,12 +30,16 @@ $ ark relay:run --launchMode=seed public static flags: CommandFlags = { ...BaseCommand.flagsNetwork, ...BaseCommand.flagsBehaviour, + suffix: flags.string({ + hidden: true, + default: "relay", + }), }; public async run(): Promise { const { flags } = await this.parseWithNetwork(RunCommand); - await this.buildApplication(app, flags, { + await super.buildApplication(app, flags, { exclude: ["@arkecosystem/core-forger"], options: { "@arkecosystem/core-p2p": this.buildPeerOptions(flags), diff --git a/packages/core/src/commands/relay/start.ts b/packages/core/src/commands/relay/start.ts index 23bbe186b6..27b790c94f 100644 --- a/packages/core/src/commands/relay/start.ts +++ b/packages/core/src/commands/relay/start.ts @@ -38,6 +38,10 @@ $ ark relay:start --no-daemon default: true, allowNo: true, }), + suffix: flags.string({ + hidden: true, + default: "relay", + }), }; public getClass() { diff --git a/packages/core/src/commands/snapshot/dump.ts b/packages/core/src/commands/snapshot/dump.ts index d1ea02a906..c3cabe4671 100644 --- a/packages/core/src/commands/snapshot/dump.ts +++ b/packages/core/src/commands/snapshot/dump.ts @@ -21,9 +21,6 @@ export class DumpCommand extends BaseCommand { description: "end network height to export", default: -1, }), - codec: flags.string({ - description: "codec name, default is msg-lite binary", - }), }; public async run(): Promise { @@ -35,6 +32,6 @@ export class DumpCommand extends BaseCommand { this.error("The @arkecosystem/core-snapshots plugin is not installed."); } - await app.resolvePlugin("snapshots").exportData(flags); + await app.resolvePlugin("snapshots").dump(flags); } } diff --git a/packages/core/src/commands/snapshot/restore.ts b/packages/core/src/commands/snapshot/restore.ts index 51f9a33468..23fdc6b196 100644 --- a/packages/core/src/commands/snapshot/restore.ts +++ b/packages/core/src/commands/snapshot/restore.ts @@ -16,16 +16,13 @@ export class RestoreCommand extends BaseCommand { description: "blocks to import, corelates to folder name", required: true, }), - codec: flags.string({ - description: "codec name, default is msg-lite binary", - }), truncate: flags.boolean({ description: "empty all tables before running import", }), skipRestartRound: flags.boolean({ description: "skip revert to current round", }), - signatureVerify: flags.boolean({ + verifySignatures: flags.boolean({ description: "signature verification", }), }; @@ -60,6 +57,6 @@ export class RestoreCommand extends BaseCommand { progressBar.stop(); }); - await app.resolvePlugin("snapshots").importData(flags); + await app.resolvePlugin("snapshots").import(flags); } } diff --git a/packages/core/src/commands/snapshot/rollback.ts b/packages/core/src/commands/snapshot/rollback.ts index 0e03f455f4..19a3fc8320 100644 --- a/packages/core/src/commands/snapshot/rollback.ts +++ b/packages/core/src/commands/snapshot/rollback.ts @@ -12,8 +12,10 @@ export class RollbackCommand extends BaseCommand { public static flags: CommandFlags = { ...BaseCommand.flagsSnapshot, height: flags.integer({ - description: "block network height number to rollback", - default: -1, + description: "the height after the roll back", + }), + number: flags.integer({ + description: "the number of blocks to roll back", }), }; @@ -26,14 +28,12 @@ export class RollbackCommand extends BaseCommand { this.error("The @arkecosystem/core-snapshots plugin is not installed."); } - const logger = app.resolvePlugin("logger"); - - if (flags.height === -1) { - logger.warn("Rollback height is not specified. Rolling back to last completed round."); + if (flags.height) { + await app.resolvePlugin("snapshots").rollbackByHeight(flags.height); + } else if (flags.number) { + await app.resolvePlugin("snapshots").rollbackByNumber(flags.number); + } else { + this.error("Please specify either a height or number of blocks to roll back."); } - - logger.info(`Starting the process of blockchain rollback to block height of ${flags.height.toLocaleString()}`); - - await app.resolvePlugin("snapshots").rollbackChain(flags.height); } } diff --git a/packages/core/src/commands/snapshot/truncate.ts b/packages/core/src/commands/snapshot/truncate.ts index ed7cbf6b0b..ad9c99d08b 100644 --- a/packages/core/src/commands/snapshot/truncate.ts +++ b/packages/core/src/commands/snapshot/truncate.ts @@ -15,6 +15,6 @@ export class TruncateCommand extends BaseCommand { this.error("The @arkecosystem/core-snapshots plugin is not installed."); } - await app.resolvePlugin("snapshots").truncateChain(); + await app.resolvePlugin("snapshots").truncate(); } } diff --git a/packages/core/src/commands/snapshot/verify.ts b/packages/core/src/commands/snapshot/verify.ts index 3b9d69a5e0..97e83d007e 100644 --- a/packages/core/src/commands/snapshot/verify.ts +++ b/packages/core/src/commands/snapshot/verify.ts @@ -13,10 +13,7 @@ export class VerifyCommand extends BaseCommand { blocks: flags.string({ description: "blocks to verify, corelates to folder name", }), - codec: flags.string({ - description: "codec name, default is msg-lite binary", - }), - signatureVerify: flags.boolean({ + verifySignatures: flags.boolean({ description: "signature verification", }), }; @@ -30,6 +27,6 @@ export class VerifyCommand extends BaseCommand { this.error("The @arkecosystem/core-snapshots plugin is not installed."); } - await app.resolvePlugin("snapshots").verifyData(flags); + await app.resolvePlugin("snapshots").verify(flags); } } diff --git a/packages/core/src/commands/top.ts b/packages/core/src/commands/top.ts index 6aee1f4071..958bf77f42 100644 --- a/packages/core/src/commands/top.ts +++ b/packages/core/src/commands/top.ts @@ -1,5 +1,5 @@ +import { dato } from "@faustbrian/dato"; import Table from "cli-table3"; -import dayjs from "dayjs-ext"; import prettyBytes from "pretty-bytes"; import prettyMs from "pretty-ms"; import { processManager } from "../process-manager"; @@ -40,7 +40,7 @@ $ ark top process.pm2_env.version, process.pm2_env.status, // @ts-ignore - prettyMs(dayjs().diff(process.pm2_env.pm_uptime)), + prettyMs(dato().diff(process.pm2_env.pm_uptime)), `${process.monit.cpu}%`, prettyBytes(process.monit.memory), ]); diff --git a/packages/core/src/commands/update.ts b/packages/core/src/commands/update.ts index 31e76378cc..7625a0bcb3 100644 --- a/packages/core/src/commands/update.ts +++ b/packages/core/src/commands/update.ts @@ -1,13 +1,36 @@ +import { hasSomeProperty } from "@arkecosystem/core-utils"; +import { flags } from "@oclif/command"; import Chalk from "chalk"; import cli from "cli-ux"; import { removeSync } from "fs-extra"; import { confirm } from "../helpers/prompts"; import { checkForUpdates, installFromChannel } from "../helpers/update"; +import { CommandFlags } from "../types"; import { BaseCommand } from "./command"; export class UpdateCommand extends BaseCommand { public static description: string = "Update the core installation"; + public static flags: CommandFlags = { + force: flags.boolean({ + description: "force an update", + }), + restart: flags.boolean({ + description: "restart all running processes", + exclusive: ["restartCore", "restartRelay", "restartForger"], + allowNo: true, + }), + restartCore: flags.boolean({ + description: "restart the core process", + }), + restartRelay: flags.boolean({ + description: "restart the relay process", + }), + restartForger: flags.boolean({ + description: "restart the forger process", + }), + }; + public async run(): Promise { const state = await checkForUpdates(this); @@ -17,31 +40,22 @@ export class UpdateCommand extends BaseCommand { return; } - try { - const currentVersion = state.currentVersion; - const newVersion = state.updateVersion; + const { flags } = await this.parseWithNetwork(UpdateCommand); + + if (flags.force) { + return this.performUpdate(flags, state); + } + try { this.warn( - `${state.name} update available from ${Chalk.greenBright(currentVersion)} to ${Chalk.greenBright( - newVersion, + `${state.name} update available from ${Chalk.greenBright(state.currentVersion)} to ${Chalk.greenBright( + state.updateVersion, )}.`, ); await confirm("Would you like to update?", async () => { try { - cli.action.start(`Updating from ${currentVersion} to ${newVersion}`); - - await installFromChannel(state.name, state.channel); - - cli.action.stop(); - - removeSync(state.cache); - - const { flags } = await this.parseWithNetwork(UpdateCommand); - - await this.restartProcess(`${flags.token}-core`); - await this.restartProcess(`${flags.token}-relay`); - await this.restartProcess(`${flags.token}-forger`); + await this.performUpdate(flags, state); } catch (err) { this.error(err.message); } finally { @@ -52,4 +66,44 @@ export class UpdateCommand extends BaseCommand { this.error(err.message); } } + + private async performUpdate(flags: CommandFlags, state: Record): Promise { + cli.action.start(`Updating from ${state.currentVersion} to ${state.updateVersion}`); + + await installFromChannel(state.name, state.updateVersion); + + cli.action.stop(); + + removeSync(state.cache); + + this.warn(`Version ${state.updateVersion} has been installed.`); + + if (this.hasRestartFlag(flags)) { + if (flags.restart) { + this.restartRunningProcessPrompt(`${flags.token}-core`, false); + this.restartRunningProcessPrompt(`${flags.token}-relay`, false); + this.restartRunningProcessPrompt(`${flags.token}-forger`, false); + } else { + if (flags.restartCore) { + this.restartRunningProcessPrompt(`${flags.token}-core`, false); + } + + if (flags.restartRelay) { + this.restartRunningProcessPrompt(`${flags.token}-relay`, false); + } + + if (flags.restartForger) { + this.restartRunningProcessPrompt(`${flags.token}-forger`, false); + } + } + } else { + await this.restartRunningProcessPrompt(`${flags.token}-core`); + await this.restartRunningProcessPrompt(`${flags.token}-relay`); + await this.restartRunningProcessPrompt(`${flags.token}-forger`); + } + } + + private hasRestartFlag(flags: CommandFlags): boolean { + return hasSomeProperty(flags, ["restart", "restartCore", "restartRelay", "restartForger"]); + } } diff --git a/packages/core/src/helpers/snapshot.ts b/packages/core/src/helpers/snapshot.ts index 69bb6e6056..d145f6ea48 100644 --- a/packages/core/src/helpers/snapshot.ts +++ b/packages/core/src/helpers/snapshot.ts @@ -1,23 +1,18 @@ import { app } from "@arkecosystem/core-container"; +import { Container } from "@arkecosystem/core-interfaces"; // tslint:disable-next-line:no-var-requires const { version } = require("../../package.json"); -export async function setUpLite(options) { - process.env.CORE_SKIP_BLOCKCHAIN = "true"; - +export async function setUpLite(options): Promise { await app.setUp(version, options, { include: [ - "@arkecosystem/core-logger", - "@arkecosystem/core-logger-winston", "@arkecosystem/core-event-emitter", + "@arkecosystem/core-logger-pino", + "@arkecosystem/core-database-postgres", "@arkecosystem/core-snapshots", ], }); return app; } - -export async function tearDown() { - return app.tearDown(); -} diff --git a/packages/core/src/helpers/update.ts b/packages/core/src/helpers/update.ts index 4484d547f3..30af2b195f 100644 --- a/packages/core/src/helpers/update.ts +++ b/packages/core/src/helpers/update.ts @@ -2,11 +2,11 @@ import { IConfig } from "@oclif/config"; import cli from "cli-ux"; import { shell } from "execa"; import { closeSync, openSync, statSync } from "fs"; -import { existsSync } from "fs-extra"; -import { ensureDirSync } from "fs-extra"; +import { ensureDirSync, existsSync } from "fs-extra"; import latestVersion from "latest-version"; import { join } from "path"; import semver from "semver"; + import { configManager } from "./config"; async function getLatestVersion(name: string, channel: string): Promise { @@ -40,7 +40,7 @@ export async function installFromChannel(pkg, channel) { } export function getRegistryChannel(config: IConfig): string { - const channels: string[] = ["alpha", "beta", "rc"]; + const channels: string[] = ["next"]; let channel: string = "latest"; for (const item of channels) { diff --git a/packages/core/src/hooks/command_not_found/suggest.ts b/packages/core/src/hooks/command_not_found/suggest.ts index 1a4eb6144c..4fee527c17 100644 --- a/packages/core/src/hooks/command_not_found/suggest.ts +++ b/packages/core/src/hooks/command_not_found/suggest.ts @@ -3,7 +3,7 @@ import { Hook } from "@oclif/config"; import Chalk from "chalk"; import * as Levenshtein from "fast-levenshtein"; -import minBy from "lodash/minBy"; +import minBy from "lodash.minby"; import { confirm } from "../../helpers/prompts"; function closest(commandIDs: string[], cmd: string) { diff --git a/packages/core/src/hooks/init/update.ts b/packages/core/src/hooks/init/update.ts index 92d0a0ffed..0f1c6e8b05 100644 --- a/packages/core/src/hooks/init/update.ts +++ b/packages/core/src/hooks/init/update.ts @@ -22,16 +22,14 @@ export const init: Hook<"init"> = async function({ id, config }) { )}. Review the latest release and run "ark update" once you wish to update.`, ); - const branch = { - alpha: "develop", - beta: "develop", - rc: "develop", + const branch: Record = { + next: "develop", latest: "master", }[state.channel]; await cli.url( `Click here to read the changelog for ${state.currentVersion}.`, - `https://github.com/ArkEcosystem/core/blob/${branch}/CHANGELOG.md`, + `https://github.com/ARKEcosystem/core/blob/${branch}/CHANGELOG.md`, ); } }; diff --git a/packages/core/src/shared/log.ts b/packages/core/src/shared/log.ts index badd5ab768..6a5a3ba10f 100644 --- a/packages/core/src/shared/log.ts +++ b/packages/core/src/shared/log.ts @@ -1,5 +1,6 @@ -import cli from "cli-ux"; -import { Tail } from "tail"; +import clear from "clear"; +import Tail from "nodejs-tail"; +import readLastLines from "read-last-lines"; import { BaseCommand } from "../commands/command"; import { processManager } from "../process-manager"; @@ -15,19 +16,19 @@ export abstract class AbstractLogCommand extends BaseCommand { const file = flags.error ? pm2_env.pm_err_log_path : pm2_env.pm_out_log_path; - const log = new Tail(file); + clear(); + + this.log( + `Tailing last ${flags.lines} lines for [${processName}] process (change the value with --lines option)`, + ); - cli.action.start(`Waiting for ${file}`); + this.log((await readLastLines.read(file, flags.lines)).trim()); - log.on("line", data => { - console.log(data); + const log = new Tail(file); - if (cli.action.running) { - cli.action.stop(); - } - }); + log.on("line", this.log); - log.on("error", error => console.error("ERROR: ", error)); + log.watch(); } public abstract getClass(); diff --git a/packages/core/src/shared/start.ts b/packages/core/src/shared/start.ts index 4941cdcd24..da571e32fc 100644 --- a/packages/core/src/shared/start.ts +++ b/packages/core/src/shared/start.ts @@ -1,9 +1,8 @@ import cli from "cli-ux"; -import prompts from "prompts"; + import { BaseCommand } from "../commands/command"; -import { ProcessState } from "../enums"; import { processManager } from "../process-manager"; -import { CommandFlags, ProcessDescription } from "../types"; +import { CommandFlags, ProcessOptions } from "../types"; export abstract class AbstractStartCommand extends BaseCommand { public async run(): Promise { @@ -16,7 +15,7 @@ export abstract class AbstractStartCommand extends BaseCommand { protected abstract async runProcess(flags: CommandFlags): Promise; - protected async runWithPm2(options: any, flags: CommandFlags) { + protected async runWithPm2(options: ProcessOptions, flags: CommandFlags) { const processName = options.name; try { diff --git a/packages/core/src/shared/status.ts b/packages/core/src/shared/status.ts index 34697f24bb..47de13e515 100644 --- a/packages/core/src/shared/status.ts +++ b/packages/core/src/shared/status.ts @@ -1,5 +1,5 @@ +import { dato } from "@faustbrian/dato"; import Table from "cli-table3"; -import dayjs from "dayjs-ext"; import prettyBytes from "pretty-bytes"; import prettyMs from "pretty-ms"; import { BaseCommand } from "../commands/command"; @@ -26,7 +26,7 @@ export abstract class AbstractStatusCommand extends BaseCommand { app.pm2_env.version, app.pm2_env.status, // @ts-ignore - prettyMs(dayjs().diff(app.pm2_env.pm_uptime)), + prettyMs(dato().diff(app.pm2_env.pm_uptime)), `${app.monit.cpu}%`, prettyBytes(app.monit.memory), ]); diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 2ae74e9a6d..a735cad2ee 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -3,3 +3,5 @@ export type ProcessDescription = Record; export type CommandFlags = Record; export type Options = Record; + +export type ProcessOptions = Record<"name" | "script" | "args", string>; diff --git a/packages/crypto/.gitattributes b/packages/crypto/.gitattributes index 60cc52db63..63f6a5b970 100644 --- a/packages/crypto/.gitattributes +++ b/packages/crypto/.gitattributes @@ -2,10 +2,6 @@ # https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html # Ignore all test and documentation with "export-ignore". -/.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.travis.yml export-ignore -/__tests__ export-ignore -/docs export-ignore /README.md export-ignore diff --git a/packages/crypto/README.md b/packages/crypto/README.md index 462718f2cd..cb57497247 100644 --- a/packages/crypto/README.md +++ b/packages/crypto/README.md @@ -1,34 +1,12 @@ -# Ark - Crypto +# ARK - Crypto

-## Installation +## Documentation -```bash -yarn add @arkecosystem/crypto -``` - -If you want to use the CDN version: - -```html - -``` - -## Usage - -Import the library in node.js: - -``` -import ArkEcosystemCrypto from @arkecosystem/crypto -``` - -Use the library: - -``` -const constants = ArkEcosystemCrypto.constants -``` +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/sdk/cryptography/usage.html). ## Security @@ -36,14 +14,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [Alex Barnsley](https://github.com/alexbarnsley) -- [Brian Faust](https://github.com/faustbrian) -- [François-Xavier Thoorens](https://github.com/fix) -- [Joshua Noack](https://github.com/supaiku0) -- [Juan A. Martín](https://github.com/j-a-m-l) -- [Lúcio Rubens](https://github.com/luciorubeens) -- [All Contributors](../../../../contributors) +This project exists thanks to all the people who [contribute](../../../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) diff --git a/packages/crypto/__tests__/deserializers/transaction.test.ts b/packages/crypto/__tests__/deserializers/transaction.test.ts deleted file mode 100644 index 6120b72b23..0000000000 --- a/packages/crypto/__tests__/deserializers/transaction.test.ts +++ /dev/null @@ -1,301 +0,0 @@ -import ByteBuffer from "bytebuffer"; -import { client } from "../../src/client"; -import { TransactionDeserializer } from "../../src/deserializers"; -import { TransactionSerializer } from "../../src/serializers"; -import { Bignum } from "../../src/utils"; - -describe("Transaction serializer / deserializer", () => { - const checkCommonFields = (deserialized, expected) => { - const fieldsToCheck = ["version", "network", "type", "timestamp", "senderPublicKey", "fee", "amount"]; - fieldsToCheck.forEach(field => { - expect(deserialized[field].toString()).toEqual(expected[field].toString()); - }); - }; - - describe("ser/deserialize - transfer", () => { - const transfer = client - .getBuilder() - .transfer() - .recipientId("D5q7YfEFDky1JJVQQEy4MGyiUhr5cGg47F") - .amount(10000) - .fee(50000000) - .vendorField("yo") - .version(1) - .network(30) - .sign("dummy passphrase") - .getStruct(); - - it("should ser/deserialize giving back original fields", () => { - const serialized = TransactionSerializer.serialize(transfer).toString("hex"); - const deserialized = TransactionDeserializer.deserialize(serialized); - - checkCommonFields(deserialized, transfer); - - expect(deserialized.vendorField).toBe(transfer.vendorField); - expect(deserialized.recipientId).toBe(transfer.recipientId); - }); - - it("should ser/deserialize giving back original fields - with vendorFieldHex", () => { - delete transfer.vendorField; - const vendorField = "cool vendor field"; - transfer.vendorFieldHex = new Buffer(vendorField).toString("hex"); - - const serialized = TransactionSerializer.serialize(transfer).toString("hex"); - const deserialized = TransactionDeserializer.deserialize(serialized); - - checkCommonFields(deserialized, transfer); - - expect(deserialized.vendorField).toBe(vendorField); - expect(deserialized.recipientId).toBe(transfer.recipientId); - }); - }); - - describe("ser/deserialize - second signature", () => { - it("should ser/deserialize giving back original fields", () => { - const secondSignature = client - .getBuilder() - .secondSignature() - .signatureAsset("signature") - .fee(50000000) - .version(1) - .network(30) - .sign("dummy passphrase") - .getStruct(); - - const serialized = TransactionSerializer.serialize(secondSignature).toString("hex"); - const deserialized = TransactionDeserializer.deserialize(serialized); - - checkCommonFields(deserialized, secondSignature); - - expect(deserialized.asset).toEqual(secondSignature.asset); - }); - }); - - describe("ser/deserialize - delegate registration", () => { - it("should ser/deserialize giving back original fields", () => { - const delegateRegistration = client - .getBuilder() - .delegateRegistration() - .usernameAsset("homer") - .version(1) - .network(30) - .sign("dummy passphrase") - .getStruct(); - - const serialized = TransactionSerializer.serialize(delegateRegistration).toString("hex"); - const deserialized = TransactionDeserializer.deserialize(serialized); - - checkCommonFields(deserialized, delegateRegistration); - - expect(deserialized.asset).toEqual(delegateRegistration.asset); - }); - }); - - describe("ser/deserialize - vote", () => { - it("should ser/deserialize giving back original fields", () => { - const vote = client - .getBuilder() - .vote() - .votesAsset(["+02bcfa0951a92e7876db1fb71996a853b57f996972ed059a950d910f7d541706c9"]) - .fee(50000000) - .version(1) - .network(30) - .sign("dummy passphrase") - .getStruct(); - - const serialized = TransactionSerializer.serialize(vote).toString("hex"); - const deserialized = TransactionDeserializer.deserialize(serialized); - - checkCommonFields(deserialized, vote); - - expect(deserialized.asset).toEqual(vote.asset); - }); - }); - - describe("ser/deserialize - multi signature", () => { - const multiSignature = client - .getBuilder() - .multiSignature() - .multiSignatureAsset({ - keysgroup: [ - "+0376982a97dadbc65e694743d386084548a65431a82ce935ac9d957b1cffab2784", - "+03793904e0df839809bc89f2839e1ae4f8b1ea97ede6592b7d1e4d0ee194ca2998", - ], - lifetime: 72, - min: 2, - }) - .version(1) - .network(30) - .sign("dummy passphrase") - .multiSignatureSign("multi passphrase 1") - .multiSignatureSign("multi passphrase 2") - .getStruct(); - - it("should ser/deserialize giving back original fields", () => { - const serialized = TransactionSerializer.serialize(multiSignature).toString("hex"); - const deserialized = TransactionDeserializer.deserialize(serialized); - - checkCommonFields(deserialized, multiSignature); - - expect(deserialized.asset).toEqual(multiSignature.asset); - }); - - it("should ser/deserialize giving back original fields - v2 keysgroup", () => { - multiSignature.asset.multisignature.keysgroup = [ - "0376982a97dadbc65e694743d386084548a65431a82ce935ac9d957b1cffab2784", - "03793904e0df839809bc89f2839e1ae4f8b1ea97ede6592b7d1e4d0ee194ca2998", - ]; - multiSignature.version = 2; - - const serialized = TransactionSerializer.serialize(multiSignature).toString("hex"); - const deserialized = TransactionDeserializer.deserialize(serialized); - - checkCommonFields(deserialized, multiSignature); - - expect(deserialized.asset).toEqual(multiSignature.asset); - }); - }); - - describe("ser/deserialize - ipfs", () => { - it("should ser/deserialize giving back original fields", () => { - const ipfs = client - .getBuilder() - .ipfs() - .fee(50000000) - .version(1) - .network(30) - .dag("da304502") - .sign("dummy passphrase") - .getStruct(); - - const serialized = TransactionSerializer.serialize(ipfs).toString("hex"); - const deserialized = TransactionDeserializer.deserialize(serialized); - - checkCommonFields(deserialized, ipfs); - - expect(deserialized.asset).toEqual(ipfs.asset); - }); - }); - - describe("ser/deserialize - timelock transfer", () => { - it("should ser/deserialize giving back original fields", () => { - const timelockTransfer = client - .getBuilder() - .timelockTransfer() - .recipientId("D5q7YfEFDky1JJVQQEy4MGyiUhr5cGg47F") - .amount(10000) - .fee(50000000) - .version(1) - .network(30) - .timelock(12, 0x00) - .sign("dummy passphrase") - .getStruct(); - - // expect(timelockTransfer).toEqual({}) - const serialized = TransactionSerializer.serialize(timelockTransfer).toString("hex"); - const deserialized = TransactionDeserializer.deserialize(serialized); - - checkCommonFields(deserialized, timelockTransfer); - - expect(deserialized.timelockType).toEqual(timelockTransfer.timelockType); - expect(deserialized.timelock).toEqual(timelockTransfer.timelock); - }); - }); - - describe("ser/deserialize - multi payment", () => { - it("should ser/deserialize giving back original fields", () => { - const multiPayment = client - .getBuilder() - .multiPayment() - .fee(50000000) - .version(1) - .network(30) - .addPayment("D5q7YfEFDky1JJVQQEy4MGyiUhr5cGg47F", 1555) - .addPayment("D5q7YfEFDky1JJVQQEy4MGyiUhr5cGg47F", 5000) - .sign("dummy passphrase") - .getStruct(); - - const serialized = TransactionSerializer.serialize(multiPayment).toString("hex"); - const deserialized = TransactionDeserializer.deserialize(serialized); - - checkCommonFields(deserialized, multiPayment); - - expect(deserialized.asset).toEqual(multiPayment.asset); - }); - }); - - describe("ser/deserialize - delegate resignation", () => { - it("should ser/deserialize giving back original fields", () => { - const delegateResignation = client - .getBuilder() - .delegateResignation() - .fee(50000000) - .version(1) - .network(30) - .sign("dummy passphrase") - .getStruct(); - - const serialized = TransactionSerializer.serialize(delegateResignation).toString("hex"); - const deserialized = TransactionDeserializer.deserialize(serialized); - - checkCommonFields(deserialized, delegateResignation); - }); - }); - - describe("deserialize - others", () => { - it("should throw if type is not supported", () => { - const serializeWrongType = transaction => { - // copy-paste from transaction serializer, common stuff - const buffer = new ByteBuffer(512, true); - buffer.writeByte(0xff); // fill, to disambiguate from v1 - buffer.writeByte(transaction.version || 0x01); // version - buffer.writeByte(transaction.network); - buffer.writeByte(transaction.type); - buffer.writeUint32(transaction.timestamp); - buffer.append(transaction.senderPublicKey, "hex"); - buffer.writeUint64(+new Bignum(transaction.fee).toFixed()); - buffer.writeByte(0x00); - - return Buffer.from(buffer.flip().toBuffer()); - }; - const transactionWrongType = client - .getBuilder() - .transfer() - .recipientId("D5q7YfEFDky1JJVQQEy4MGyiUhr5cGg47F") - .amount(10000) - .fee(50000000) - .vendorField("yo") - .version(1) - .network(30) - .sign("dummy passphrase") - .getStruct(); - transactionWrongType.type = 55; - - const serialized = serializeWrongType(transactionWrongType).toString("hex"); - expect(() => TransactionDeserializer.deserialize(serialized)).toThrow( - `Type ${transactionWrongType.type} not supported.`, - ); - }); - }); - - describe("serialize - others", () => { - it("should throw if type is not supported", () => { - const transactionWrongType = client - .getBuilder() - .transfer() - .recipientId("D5q7YfEFDky1JJVQQEy4MGyiUhr5cGg47F") - .amount(10000) - .fee(50000000) - .vendorField("yo") - .version(1) - .network(30) - .sign("dummy passphrase") - .getStruct(); - transactionWrongType.type = 55; - - expect(() => TransactionSerializer.serialize(transactionWrongType)).toThrow( - `Type ${transactionWrongType.type} not supported.`, - ); - }); - }); -}); diff --git a/packages/crypto/__tests__/fixtures/block.ts b/packages/crypto/__tests__/fixtures/block.ts deleted file mode 100644 index f8b223e113..0000000000 --- a/packages/crypto/__tests__/fixtures/block.ts +++ /dev/null @@ -1,327 +0,0 @@ -export const dummyBlock = { - id: "7176646138626297930", - version: 0, - height: 2243161, - timestamp: 24760440, - previousBlock: "3112633353705641986", - numberOfTransactions: 7, - totalAmount: "3890300", - totalFee: "70000000", - reward: "200000000", - payloadLength: 224, - payloadHash: "3784b953afcf936bdffd43fdf005b5732b49c1fc6b11e195c364c20b2eb06282", - generatorPublicKey: "020f5df4d2bc736d12ce43af5b1663885a893fade7ee5e62b3cc59315a63e6a325", - blockSignature: - "3045022100eee6c37b5e592e99811d588532726353592923f347c701d52912e6d583443e400220277ffe38ad31e216ba0907c4738fed19b2071246b150c72c0a52bae4477ebe29", - transactions: [ - { - type: 0, - amount: 555760, - fee: 10000000, - recipientId: "DB4gFuDztmdGALMb8i1U4Z4R5SktxpNTAY", - timestamp: 24760418, - asset: {}, - vendorField: "Goose Voter - True Block Weight", - senderPublicKey: "0265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c0", - signature: - "304402204f12469157b19edd06ba25fcad3d4a5ef5b057c23f9e02de4641e6f8eef0553e022010121ab282f83efe1043de9c16bbf2c6845a03684229a0d7c965ffb9abdfb978", - signSignature: - "30450221008327862f0b9178d6665f7d6674978c5caf749649558d814244b1c66cdf945c40022015918134ef01fed3fe2a2efde3327917731344332724522c75c2799a14f78717", - id: "170543154a3b79459cbaa529f9f62b6f1342682799eb549dbf09fcca2d1f9c11", - senderId: "DB8LnnQqYvHpG4WkGJ9AJWBYEct7G3yRZg", - hop: 2, - broadcast: false, - blockId: "7176646138626297930", - }, - { - type: 0, - amount: 555750, - fee: 10000000, - recipientId: "DGExsNogZR7JFa2656ZFP9TMWJYJh5djzQ", - timestamp: 24760416, - asset: {}, - vendorField: "Goose Voter - True Block Weight", - senderPublicKey: "0265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c0", - signature: - "304402205f82feb8c5d1d79c565c2ff7badb93e4c9827b132d135dda11cb25427d4ef8ac02205ff136f970533c4ec4c7d0cd1ea7e02d7b62629b66c6c93265f608d7f2389727", - signSignature: - "304402207e912031fcc700d8a55fbc415993302a0d8e6aea128397141b640b6dba52331702201fd1ad3984e42af44f548907add6cb7ad72ca0070c8cc1d8dc9bbda208c56bd9", - id: "1da153f37eceda233ff1b407ac18e47b3cae47c14cdcd5297d929618a916c4a7", - senderId: "DB8LnnQqYvHpG4WkGJ9AJWBYEct7G3yRZg", - hop: 2, - broadcast: false, - blockId: "7176646138626297930", - }, - { - type: 0, - amount: 555770, - fee: 10000000, - recipientId: "DHGK5np6LuMMErfRfC5CmjpGu3ME85c25n", - timestamp: 24760420, - asset: {}, - vendorField: "Goose Voter - True Block Weight", - senderPublicKey: "0265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c0", - signature: - "304502210083216e6969e068770e6d2fe5c244881002309df84d20290ddf3f858967ed010202202a479b3da5080ea475d310ff13494654b42db75886a8808bd211b4bdb9146a7a", - signSignature: - "3045022100e1dcab3406bbeb968146a4a391909ce41df9b71592a753b001e7c2ee1d382c5102202a74aeafd4a152ec61854636fbae829c41f1416c1e0637a0809408394973099f", - id: "1e255f07dc25ce22d900ea81663c8f00d05a7b7c061e6fc3c731b05d642fa0b9", - senderId: "DB8LnnQqYvHpG4WkGJ9AJWBYEct7G3yRZg", - hop: 2, - broadcast: false, - blockId: "7176646138626297930", - }, - { - type: 0, - amount: 555750, - fee: 10000000, - recipientId: "D7pcLJNGe197ibmWEmT8mM9KKU1htrcDyW", - timestamp: 24760417, - asset: {}, - vendorField: "Goose Voter - True Block Weight", - senderPublicKey: "0265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c0", - signature: - "3045022100cd4fa9855227be11e17201419dacfbbd5d9946df8d6792a9488160025693821402207fb83969bad6a26959f437b5bb88e255b0a48eb04964d0c0d29f7ee94bd15e11", - signSignature: - "304402205f50c2991a17743d17ffbb09159cadc35a3f848044261842879ccf5be9d81c5e022023bf21c32fb6e94494104f15f8d3a942ab120d0abd6fb4c93790b68e1b307a79", - id: "66336c61d6ec623f8a1d2fd156a0fac16a4fe93bb3fba337859355c2119923a8", - senderId: "DB8LnnQqYvHpG4WkGJ9AJWBYEct7G3yRZg", - hop: 2, - broadcast: false, - blockId: "7176646138626297930", - }, - { - type: 0, - amount: 555760, - fee: 10000000, - recipientId: "DD4yhwzryQdNGqKtezmycToQv63g27Tqqq", - timestamp: 24760418, - asset: {}, - vendorField: "Goose Voter - True Block Weight", - senderPublicKey: "0265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c0", - signature: - "30450221009c792062e13399ac6756b2e9f137194d06e106360ac0f3e24e55c7249cee0b3602205dc1d9c76d0451d1cb5a2396783a13e6d2d790ccfd49291e3d0a78349f7ea0e8", - signSignature: - "30440220083ba8a9af49b8be6e93794d71ec43ffc96a158375810e5d9f2478e71655315b0220278402ecaa1d224dab9f0f3b28295bbaea339c85c7400edafdc49df87439fc64", - id: "78db36f7d79f51c67d7210ee3819dfb8d0d47b16a7484ebf55c5a055b17209a3", - senderId: "DB8LnnQqYvHpG4WkGJ9AJWBYEct7G3yRZg", - hop: 2, - broadcast: false, - blockId: "7176646138626297930", - }, - { - type: 0, - amount: 555760, - fee: 10000000, - recipientId: "D5LiYGXL5keycWuTF6AFFwSRc6Mt4uEHMu", - timestamp: 24760419, - asset: {}, - vendorField: "Goose Voter - True Block Weight", - senderPublicKey: "0265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c0", - signature: - "3044022063c65263e42be02bd9831b375c1d76a88332f00ed0557ecc1e7d2375ca40070902206797b5932c0bad68444beb5a38daa7cadf536ee2144e0d9777b812284d14374e", - signSignature: - "3045022100b04da6692f75d43229ffd8486c1517e8952d38b4c03dfac38b6b360190a5c33e0220776622e5f09f92a1258b4a011f22181c977b622b8d1bbb2f83b42f4126d00739", - id: "83c80bb58777bb43f5037544b44ef69f191d3548fd1b2a00bed368f9f0d694c5", - senderId: "DB8LnnQqYvHpG4WkGJ9AJWBYEct7G3yRZg", - hop: 2, - broadcast: false, - blockId: "7176646138626297930", - }, - { - type: 0, - amount: 555750, - fee: 10000000, - recipientId: "DPopNLwMvv4zSjdZnqUk8HFH13Mcb7NbEK", - timestamp: 24760416, - asset: {}, - vendorField: "Goose Voter - True Block Weight", - senderPublicKey: "0265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c0", - signature: - "3045022100d4513c3608c2072e38e7a0e3bb8daf2cd5f7cc6fec9a5570dccd1eda696c591902202ecbbf3c9d0757be7b23c8b1cc6481c51600d158756c47fcb6f4a7f4893e31c4", - signSignature: - "304402201fed4858d0806dd32220960900a871dd2f60e1f623af75feef9b1034a9a0a46402205a29b27c63fcc3e1ee1e77ecbbf4dd6e7db09901e7a09b9fd490cd68d62392cb", - id: "d2faf992fdd5da96d6d15038b6ddb65230338fa2096e45e44da51daad5e2f3ca", - senderId: "DB8LnnQqYvHpG4WkGJ9AJWBYEct7G3yRZg", - hop: 2, - broadcast: false, - blockId: "7176646138626297930", - }, - ], -}; - -export const dummyBlock2 = { - data: { - id: "7176646138626297930", - version: 0, - height: 2243161, - timestamp: 24760440, - previousBlock: "3112633353705641986", - numberOfTransactions: 7, - totalAmount: "3890300", - totalFee: "70000000", - reward: "200000000", - payloadLength: 224, - payloadHash: "3784b953afcf936bdffd43fdf005b5732b49c1fc6b11e195c364c20b2eb06282", - generatorPublicKey: "020f5df4d2bc736d12ce43af5b1663885a893fade7ee5e62b3cc59315a63e6a325", - blockSignature: - "3045022100eee6c37b5e592e99811d588532726353592923f347c701d52912e6d583443e400220277ffe38ad31e216ba0907c4738fed19b2071246b150c72c0a52bae4477ebe29", - transactions: [ - { - type: 0, - amount: 555760, - fee: 10000000, - recipientId: "DB4gFuDztmdGALMb8i1U4Z4R5SktxpNTAY", - timestamp: 24760418, - asset: {}, - vendorField: "Goose Voter - True Block Weight", - senderPublicKey: "0265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c0", - signature: - "304402204f12469157b19edd06ba25fcad3d4a5ef5b057c23f9e02de4641e6f8eef0553e022010121ab282f83efe1043de9c16bbf2c6845a03684229a0d7c965ffb9abdfb978", - signSignature: - "30450221008327862f0b9178d6665f7d6674978c5caf749649558d814244b1c66cdf945c40022015918134ef01fed3fe2a2efde3327917731344332724522c75c2799a14f78717", - id: "170543154a3b79459cbaa529f9f62b6f1342682799eb549dbf09fcca2d1f9c11", - senderId: "DB8LnnQqYvHpG4WkGJ9AJWBYEct7G3yRZg", - hop: 2, - broadcast: false, - blockId: "7176646138626297930", - }, - { - type: 0, - amount: 555750, - fee: 10000000, - recipientId: "DGExsNogZR7JFa2656ZFP9TMWJYJh5djzQ", - timestamp: 24760416, - asset: {}, - vendorField: "Goose Voter - True Block Weight", - senderPublicKey: "0265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c0", - signature: - "304402205f82feb8c5d1d79c565c2ff7badb93e4c9827b132d135dda11cb25427d4ef8ac02205ff136f970533c4ec4c7d0cd1ea7e02d7b62629b66c6c93265f608d7f2389727", - signSignature: - "304402207e912031fcc700d8a55fbc415993302a0d8e6aea128397141b640b6dba52331702201fd1ad3984e42af44f548907add6cb7ad72ca0070c8cc1d8dc9bbda208c56bd9", - id: "1da153f37eceda233ff1b407ac18e47b3cae47c14cdcd5297d929618a916c4a7", - senderId: "DB8LnnQqYvHpG4WkGJ9AJWBYEct7G3yRZg", - hop: 2, - broadcast: false, - blockId: "7176646138626297930", - }, - { - type: 0, - amount: 555770, - fee: 10000000, - recipientId: "DHGK5np6LuMMErfRfC5CmjpGu3ME85c25n", - timestamp: 24760420, - asset: {}, - vendorField: "Goose Voter - True Block Weight", - senderPublicKey: "0265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c0", - signature: - "304502210083216e6969e068770e6d2fe5c244881002309df84d20290ddf3f858967ed010202202a479b3da5080ea475d310ff13494654b42db75886a8808bd211b4bdb9146a7a", - signSignature: - "3045022100e1dcab3406bbeb968146a4a391909ce41df9b71592a753b001e7c2ee1d382c5102202a74aeafd4a152ec61854636fbae829c41f1416c1e0637a0809408394973099f", - id: "1e255f07dc25ce22d900ea81663c8f00d05a7b7c061e6fc3c731b05d642fa0b9", - senderId: "DB8LnnQqYvHpG4WkGJ9AJWBYEct7G3yRZg", - hop: 2, - broadcast: false, - blockId: "7176646138626297930", - }, - { - type: 0, - amount: 555750, - fee: 10000000, - recipientId: "D7pcLJNGe197ibmWEmT8mM9KKU1htrcDyW", - timestamp: 24760417, - asset: {}, - vendorField: "Goose Voter - True Block Weight", - senderPublicKey: "0265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c0", - signature: - "3045022100cd4fa9855227be11e17201419dacfbbd5d9946df8d6792a9488160025693821402207fb83969bad6a26959f437b5bb88e255b0a48eb04964d0c0d29f7ee94bd15e11", - signSignature: - "304402205f50c2991a17743d17ffbb09159cadc35a3f848044261842879ccf5be9d81c5e022023bf21c32fb6e94494104f15f8d3a942ab120d0abd6fb4c93790b68e1b307a79", - id: "66336c61d6ec623f8a1d2fd156a0fac16a4fe93bb3fba337859355c2119923a8", - senderId: "DB8LnnQqYvHpG4WkGJ9AJWBYEct7G3yRZg", - hop: 2, - broadcast: false, - blockId: "7176646138626297930", - }, - { - type: 0, - amount: 555760, - fee: 10000000, - recipientId: "DD4yhwzryQdNGqKtezmycToQv63g27Tqqq", - timestamp: 24760418, - asset: {}, - vendorField: "Goose Voter - True Block Weight", - senderPublicKey: "0265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c0", - signature: - "30450221009c792062e13399ac6756b2e9f137194d06e106360ac0f3e24e55c7249cee0b3602205dc1d9c76d0451d1cb5a2396783a13e6d2d790ccfd49291e3d0a78349f7ea0e8", - signSignature: - "30440220083ba8a9af49b8be6e93794d71ec43ffc96a158375810e5d9f2478e71655315b0220278402ecaa1d224dab9f0f3b28295bbaea339c85c7400edafdc49df87439fc64", - id: "78db36f7d79f51c67d7210ee3819dfb8d0d47b16a7484ebf55c5a055b17209a3", - senderId: "DB8LnnQqYvHpG4WkGJ9AJWBYEct7G3yRZg", - hop: 2, - broadcast: false, - blockId: "7176646138626297930", - }, - { - type: 0, - amount: 555760, - fee: 10000000, - recipientId: "D5LiYGXL5keycWuTF6AFFwSRc6Mt4uEHMu", - timestamp: 24760419, - asset: {}, - vendorField: "Goose Voter - True Block Weight", - senderPublicKey: "0265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c0", - signature: - "3044022063c65263e42be02bd9831b375c1d76a88332f00ed0557ecc1e7d2375ca40070902206797b5932c0bad68444beb5a38daa7cadf536ee2144e0d9777b812284d14374e", - signSignature: - "3045022100b04da6692f75d43229ffd8486c1517e8952d38b4c03dfac38b6b360190a5c33e0220776622e5f09f92a1258b4a011f22181c977b622b8d1bbb2f83b42f4126d00739", - id: "83c80bb58777bb43f5037544b44ef69f191d3548fd1b2a00bed368f9f0d694c5", - senderId: "DB8LnnQqYvHpG4WkGJ9AJWBYEct7G3yRZg", - hop: 2, - broadcast: false, - blockId: "7176646138626297930", - }, - { - type: 0, - amount: 555750, - fee: 10000000, - recipientId: "DPopNLwMvv4zSjdZnqUk8HFH13Mcb7NbEK", - timestamp: 24760416, - asset: {}, - vendorField: "Goose Voter - True Block Weight", - senderPublicKey: "0265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c0", - signature: - "3045022100d4513c3608c2072e38e7a0e3bb8daf2cd5f7cc6fec9a5570dccd1eda696c591902202ecbbf3c9d0757be7b23c8b1cc6481c51600d158756c47fcb6f4a7f4893e31c4", - signSignature: - "304402201fed4858d0806dd32220960900a871dd2f60e1f623af75feef9b1034a9a0a46402205a29b27c63fcc3e1ee1e77ecbbf4dd6e7db09901e7a09b9fd490cd68d62392cb", - id: "d2faf992fdd5da96d6d15038b6ddb65230338fa2096e45e44da51daad5e2f3ca", - senderId: "DB8LnnQqYvHpG4WkGJ9AJWBYEct7G3yRZg", - hop: 2, - broadcast: false, - blockId: "7176646138626297930", - }, - ], - }, - serialized: - "0000000078d07901593a22002b324b8b33a85802070000007c5c3b0000000000801d2c040000000000c2eb0b00000000e00000003784b953afcf936bdffd43fdf005b5732b49c1fc6b11e195c364c20b2eb06282020f5df4d2bc736d12ce43af5b1663885a893fade7ee5e62b3cc59315a63e6a3253045022100eee6c37b5e592e99811d588532726353592923f347c701d52912e6d583443e400220277ffe38ad31e216ba0907c4738fed19b2071246b150c72c0a52bae4477ebe29", - serializedFull: - "0000000078d07901593a22002b324b8b33a85802070000007c5c3b0000000000801d2c040000000000c2eb0b00000000e00000003784b953afcf936bdffd43fdf005b5732b49c1fc6b11e195c364c20b2eb06282020f5df4d2bc736d12ce43af5b1663885a893fade7ee5e62b3cc59315a63e6a3253045022100eee6c37b5e592e99811d588532726353592923f347c701d52912e6d583443e400220277ffe38ad31e216ba0907c4738fed19b2071246b150c72c0a52bae4477ebe29ff000000fe00000000010000ff000000ff000000ff000000ff000000ff011e0062d079010265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c080969800000000001f476f6f736520566f746572202d205472756520426c6f636b20576569676874f07a080000000000000000001e40fad23d21da7a4fd4decb5c49726ea22f5e6bf6304402204f12469157b19edd06ba25fcad3d4a5ef5b057c23f9e02de4641e6f8eef0553e022010121ab282f83efe1043de9c16bbf2c6845a03684229a0d7c965ffb9abdfb97830450221008327862f0b9178d6665f7d6674978c5caf749649558d814244b1c66cdf945c40022015918134ef01fed3fe2a2efde3327917731344332724522c75c2799a14f78717ff011e0060d079010265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c080969800000000001f476f6f736520566f746572202d205472756520426c6f636b20576569676874e67a080000000000000000001e79c579fb08f448879c22fe965906b4e3b88d02ed304402205f82feb8c5d1d79c565c2ff7badb93e4c9827b132d135dda11cb25427d4ef8ac02205ff136f970533c4ec4c7d0cd1ea7e02d7b62629b66c6c93265f608d7f2389727304402207e912031fcc700d8a55fbc415993302a0d8e6aea128397141b640b6dba52331702201fd1ad3984e42af44f548907add6cb7ad72ca0070c8cc1d8dc9bbda208c56bd9ff011e0064d079010265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c080969800000000001f476f6f736520566f746572202d205472756520426c6f636b20576569676874fa7a080000000000000000001e84fee45dde2b11525afe192a2e991d014ff93a36304502210083216e6969e068770e6d2fe5c244881002309df84d20290ddf3f858967ed010202202a479b3da5080ea475d310ff13494654b42db75886a8808bd211b4bdb9146a7a3045022100e1dcab3406bbeb968146a4a391909ce41df9b71592a753b001e7c2ee1d382c5102202a74aeafd4a152ec61854636fbae829c41f1416c1e0637a0809408394973099fff011e0061d079010265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c080969800000000001f476f6f736520566f746572202d205472756520426c6f636b20576569676874e67a080000000000000000001e1d69583ede5ee82d220e74bffb36bae2ce762dfb3045022100cd4fa9855227be11e17201419dacfbbd5d9946df8d6792a9488160025693821402207fb83969bad6a26959f437b5bb88e255b0a48eb04964d0c0d29f7ee94bd15e11304402205f50c2991a17743d17ffbb09159cadc35a3f848044261842879ccf5be9d81c5e022023bf21c32fb6e94494104f15f8d3a942ab120d0abd6fb4c93790b68e1b307a79ff011e0062d079010265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c080969800000000001f476f6f736520566f746572202d205472756520426c6f636b20576569676874f07a080000000000000000001e56f9a37a859f4f84e93ce7593e809b15a524db2930450221009c792062e13399ac6756b2e9f137194d06e106360ac0f3e24e55c7249cee0b3602205dc1d9c76d0451d1cb5a2396783a13e6d2d790ccfd49291e3d0a78349f7ea0e830440220083ba8a9af49b8be6e93794d71ec43ffc96a158375810e5d9f2478e71655315b0220278402ecaa1d224dab9f0f3b28295bbaea339c85c7400edafdc49df87439fc64ff011e0063d079010265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c080969800000000001f476f6f736520566f746572202d205472756520426c6f636b20576569676874f07a080000000000000000001e0232a083c16aba4362dddec1b3050ffdd6d43f2e3044022063c65263e42be02bd9831b375c1d76a88332f00ed0557ecc1e7d2375ca40070902206797b5932c0bad68444beb5a38daa7cadf536ee2144e0d9777b812284d14374e3045022100b04da6692f75d43229ffd8486c1517e8952d38b4c03dfac38b6b360190a5c33e0220776622e5f09f92a1258b4a011f22181c977b622b8d1bbb2f83b42f4126d00739ff011e0060d079010265c1f6b8c1966a90f3fed7bc32fd4f42238ab4938fdb2a4e7ddd01ae8b58b4c080969800000000001f476f6f736520566f746572202d205472756520426c6f636b20576569676874e67a080000000000000000001eccc4fce0dc95f9951ee40c09a7ae807746cf51403045022100d4513c3608c2072e38e7a0e3bb8daf2cd5f7cc6fec9a5570dccd1eda696c591902202ecbbf3c9d0757be7b23c8b1cc6481c51600d158756c47fcb6f4a7f4893e31c4304402201fed4858d0806dd32220960900a871dd2f60e1f623af75feef9b1034a9a0a46402205a29b27c63fcc3e1ee1e77ecbbf4dd6e7db09901e7a09b9fd490cd68d62392cb", -}; - -export const dummyBlock3 = { - id: "7242383292164246617", - version: 0, - timestamp: 46583338, - height: 3, - reward: "0", - previousBlock: "17882607875259085966", - numberOfTransactions: 0, - totalAmount: "0", - totalFee: "0", - payloadLength: 0, - payloadHash: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - generatorPublicKey: "038082dad560a22ea003022015e3136b21ef1ffd9f2fd50049026cbe8e2258ca17", - blockSignature: - "304402204087bb1d2c82b9178b02b9b3f285de260cdf0778643064fe6c7aef27321d49520220594c57009c1fca543350126d277c6adeb674c00685a464c3e4bf0d634dc37e39", - createdAt: "2018-09-11T16:48:58.431Z", -}; diff --git a/packages/crypto/__tests__/fixtures/transaction.ts b/packages/crypto/__tests__/fixtures/transaction.ts deleted file mode 100644 index 25f937989a..0000000000 --- a/packages/crypto/__tests__/fixtures/transaction.ts +++ /dev/null @@ -1,16 +0,0 @@ -export const transaction = { - version: 1, - network: 23, - type: 0, - timestamp: 58126413, - senderPublicKey: "03d7dfe44e771039334f4712fb95ad355254f674c8f5d286503199157b7bf7c357", - fee: 10000000, - amount: 200000000, - vendorFieldHex: "5472616e73616374696f6e2037", - expiration: 0, - recipientId: "APyFYXxXtUrvZFnEuwLopfst94GMY5Zkeq", - signature: - "3045022100bac5b7699748a891b39ff5439e16ea1a694e93954b248be6b8082da01e5386310220129eb06a58b9f80d36ea3cdc903e6cc0240bbe1d371339ffe15c87742af1427d", - vendorField: "Transaction 7", - id: "00d2025f7914a8e794bdaea404a579840cf71402cef312d2080c7ecd86177e5f", -}; diff --git a/packages/crypto/__tests__/handlers/transactions/delegate-registration.test.ts b/packages/crypto/__tests__/handlers/transactions/delegate-registration.test.ts deleted file mode 100644 index f10b547bb9..0000000000 --- a/packages/crypto/__tests__/handlers/transactions/delegate-registration.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -import "jest-extended"; -import { DelegateRegistrationHandler } from "../../../src/handlers/transactions/delegate-registration"; -import { Bignum } from "../../../src/utils"; -import { wallet as originalWallet } from "./__fixtures__/wallet"; - -const handler = new DelegateRegistrationHandler(); - -let wallet; -let transaction; -let errors; - -beforeEach(() => { - wallet = originalWallet; - - transaction = { - version: 1, - id: "943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4", - blockid: "11233167632577333611", - type: 2, - timestamp: 36482198, - amount: Bignum.ZERO, - fee: new Bignum(10000000), - senderId: "DTRdbaUW3RQQSL5By4G43JVaeHiqfVp9oh", - recipientId: "DTRdbaUW3RQQSL5By4G43JVaeHiqfVp9oh", - senderPublicKey: "034da006f958beba78ec54443df4a3f52237253f7ae8cbdb17dccf3feaa57f3126", - signature: - "304402205881204c6e515965098099b0e20a7bf104cd1bad6cfe8efd1641729fcbfdbf1502203cfa3bd9efb2ad250e2709aaf719ac0db04cb85d27a96bc8149aeaab224de82b", - asset: { - delegate: { - username: "dummy", - publicKey: ("a" as any).repeat(66), - }, - }, - }; - - errors = []; -}); - -describe("DelegateRegistrationHandler", () => { - describe("canApply", () => { - it("should be true", () => { - expect(handler.canApply(wallet, transaction, [])).toBeTrue(); - }); - - it("should be false if wallet already registered a username", () => { - wallet.username = "dummy"; - - expect(handler.canApply(wallet, transaction, errors)).toBeFalse(); - expect(errors).toContain("Wallet already has a registered username"); - }); - - it("should be false if wallet has insufficient funds", () => { - wallet.balance = Bignum.ZERO; - - expect(handler.canApply(wallet, transaction, errors)).toBeFalse(); - expect(errors).toContain("Insufficient balance in the wallet"); - }); - }); - - describe("apply", () => { - it("should set username", () => { - handler.applyTransactionToSender(wallet, transaction); - - expect(wallet.username).toBe("dummy"); - }); - }); - - describe("revert", () => { - it("should unset username", () => { - handler.revertTransactionForSender(wallet, transaction); - - expect(wallet.username).toBeNull(); - }); - }); -}); diff --git a/packages/crypto/__tests__/handlers/transactions/delegate-resignation.test.ts b/packages/crypto/__tests__/handlers/transactions/delegate-resignation.test.ts deleted file mode 100644 index fb8b5b5adc..0000000000 --- a/packages/crypto/__tests__/handlers/transactions/delegate-resignation.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import "jest-extended"; - -import { DelegateResignationHandler } from "../../../src/handlers/transactions/delegate-resignation"; -import { Bignum } from "../../../src/utils"; -import { transaction as originalTransaction } from "./__fixtures__/transaction"; -import { wallet as originalWallet } from "./__fixtures__/wallet"; - -const handler = new DelegateResignationHandler(); - -let wallet; -let transaction; -let errors; - -beforeEach(() => { - wallet = originalWallet; - transaction = originalTransaction; - - errors = []; -}); - -describe("DelegateResignationHandler", () => { - describe("canApply", () => { - it("should be truth", () => { - wallet.username = "dummy"; - - expect(handler.canApply(wallet, transaction, [])).toBeTrue(); - }); - - it("should be false if wallet has no registered username", () => { - wallet.username = null; - - expect(handler.canApply(wallet, transaction, errors)).toBeFalse(); - expect(errors).toContain("Wallet has not registered a username"); - }); - - it("should be false if wallet has insufficient funds", () => { - wallet.balance = Bignum.ZERO; - - expect(handler.canApply(wallet, transaction, errors)).toBeFalse(); - expect(errors).toContain("Insufficient balance in the wallet"); - }); - }); -}); diff --git a/packages/crypto/__tests__/handlers/transactions/handler.test.ts b/packages/crypto/__tests__/handlers/transactions/handler.test.ts deleted file mode 100644 index 8c9cc8a8f1..0000000000 --- a/packages/crypto/__tests__/handlers/transactions/handler.test.ts +++ /dev/null @@ -1,262 +0,0 @@ -import "jest-extended"; - -import { SATOSHI } from "../../../src/constants"; -import { DelegateRegistrationHandler } from "../../../src/handlers/transactions/delegate-registration"; -import { DelegateResignationHandler } from "../../../src/handlers/transactions/delegate-resignation"; -import { Handler } from "../../../src/handlers/transactions/handler"; -import { SecondSignatureHandler } from "../../../src/handlers/transactions/second-signature"; -import { TransferHandler } from "../../../src/handlers/transactions/transfer"; -import { VoteHandler } from "../../../src/handlers/transactions/vote"; -import { configManager } from "../../../src/managers"; -import { Bignum } from "../../../src/utils"; - -let wallet; -let transaction; -let transactionWithSecondSignature; -let errors; - -class FakeHandler extends Handler { - // tslint:disable-next-line:no-shadowed-variable - public apply(wallet: any, transaction: any) { - throw new Error("Method not implemented."); - } - - // tslint:disable-next-line:no-shadowed-variable - public revert(wallet: any, transaction: any) { - throw new Error("Method not implemented."); - } -} - -beforeEach(() => { - wallet = { - address: "D5q7YfEFDky1JJVQQEy4MGyiUhr5cGg47F", - balance: new Bignum(4527654310), - publicKey: "02a47a2f594635737d2ce9898680812ff7fa6aaa64ddea1360474c110e9985a087", - }; - - transaction = { - id: "65a4f09a3a19d212a65d27de05d1ae7e0c461e088a35499996667f98d2a3897c", - signature: - "304402206974568da7c363155decbc20ddc17746a2e7e663901c426f5a41411374cc6d18022052f4353ec93227713f9907f2bb2549e6bc42584b736aa5f9ff36e2c239154648", - timestamp: 54836734, - type: 0, - fee: new Bignum(10000000), - senderPublicKey: "02a47a2f594635737d2ce9898680812ff7fa6aaa64ddea1360474c110e9985a087", - amount: new Bignum(10000000), - recipientId: "D5q7YfEFDky1JJVQQEy4MGyiUhr5cGg47F", - }; - - transactionWithSecondSignature = { - id: "e3b29bba60d5f1f2aad2087dea44644f166b00ae3db1a16a99b622dc4f3900f8", - signature: - "304402206974568da7c363155decbc20ddc17746a2e7e663901c426f5a41411374cc6d18022052f4353ec93227713f9907f2bb2549e6bc42584b736aa5f9ff36e2c239154648", - signSignature: - "304402202d0ae57c6a0afb225443b56c6e049cb08df48b5813362f7e11574b96f225738f0220055b5a941cc70100404a7788c57b37e2e806acf58c4284c567dc53477f546540", - timestamp: 54836734, - type: 0, - fee: new Bignum(10000000), - senderPublicKey: "02a47a2f594635737d2ce9898680812ff7fa6aaa64ddea1360474c110e9985a087", - amount: new Bignum(10000000), - recipientId: "D5q7YfEFDky1JJVQQEy4MGyiUhr5cGg47F", - }; - - errors = []; -}); - -describe("Specific handler - fake handler tests", () => { - const handler = new FakeHandler(); - describe("canApply", () => { - it("should be true", () => { - expect(handler.canApply(wallet, transaction, errors)).toBeTrue(); - expect(errors).toHaveLength(0); - }); - - it("should be true even with publicKey case mismatch", () => { - transaction.senderPublicKey = transaction.senderPublicKey.toUpperCase(); - wallet.publicKey = wallet.publicKey.toLowerCase(); - - expect(handler.canApply(wallet, transaction, [])).toBeTrue(); - }); - - it("should be true if the transaction has a second signature but wallet does not, when ignoreInvalidSecondSignatureField=true", () => { - configManager.getMilestone().ignoreInvalidSecondSignatureField = true; - - expect(handler.canApply(wallet, transactionWithSecondSignature, errors)).toBeTrue(); - }); - }); -}); - -describe.each([ - ["Transfer handler", TransferHandler], - ["Vote handler", VoteHandler], - ["Delegate registration handler", DelegateRegistrationHandler], - ["Delegate resignation handler", DelegateResignationHandler], - ["Second signature handler", SecondSignatureHandler], - ["Fake handler", FakeHandler], -])("Commmon handler tests - %s", (handlerDesc, TransactionHandler) => { - const handler = new TransactionHandler(); - describe("canApply", () => { - it("should be false if wallet publicKey does not match tx senderPublicKey", () => { - transaction.senderPublicKey = "a".repeat(66); - const result = handler.canApply(wallet, transaction, errors); - - expect(result).toBeFalse(); - expect(errors).toContain('wallet "publicKey" does not match transaction "senderPublicKey"'); - }); - - it("should be false if the transaction has a second signature but wallet does not", () => { - delete configManager.getMilestone().ignoreInvalidSecondSignatureField; - - expect(handler.canApply(wallet, transactionWithSecondSignature, errors)).toBeFalse(); - expect(errors).toContain("Invalid second-signature field"); - }); - - it("should be false if the wallet has a second public key but the transaction second signature does not match", () => { - transaction.senderPublicKey = transaction.senderPublicKey.toUpperCase(); - wallet.secondPublicKey = "invalid-public-key"; - - expect(handler.canApply(wallet, transaction, errors)).toBeFalse(); - expect(errors).toContain( - handlerDesc === "Second signature handler" - ? "Wallet already has a second signature" - : "Failed to verify second-signature", - ); - }); - - it("should be false if the validation fails", () => { - delete transaction.senderPublicKey; - - expect(handler.canApply(wallet, transaction, errors)).toBeFalse(); - expect(errors).toContain('child "senderPublicKey" fails because ["senderPublicKey" is required]'); - }); - - it("should be false if wallet has not enough balance", () => { - wallet.balance = transaction.amount.plus(transaction.fee).minus(1); // 1 satoshi short - - expect(handler.canApply(wallet, transaction, errors)).toBeFalse(); - expect(errors).toContain("Insufficient balance in the wallet"); - }); - }); - - describe("applyTransactionToSender", () => { - it("should be ok", () => { - handler.apply = jest.fn(); - - const initialBalance = 1000 * SATOSHI; - wallet.balance = new Bignum(initialBalance); - - handler.applyTransactionToSender(wallet, transaction); - - expect(wallet.balance).toEqual(new Bignum(initialBalance).minus(transaction.amount).minus(transaction.fee)); - }); - - it("should not be ok", () => { - handler.apply = jest.fn(); - - transaction.senderPublicKey = "a".repeat(66); - - const initialBalance = 1000 * SATOSHI; - wallet.balance = new Bignum(initialBalance); - - handler.applyTransactionToSender(wallet, transaction); - - expect(wallet.balance).toEqual(new Bignum(initialBalance)); - }); - - it("should not fail due to case mismatch", () => { - handler.apply = jest.fn(); - - const initialBalance = 1000 * SATOSHI; - wallet.balance = new Bignum(initialBalance); - transaction.senderPublicKey = transaction.senderPublicKey.toUpperCase(); - wallet.publicKey = wallet.publicKey.toLowerCase(); - - handler.applyTransactionToSender(wallet, transaction); - - expect(wallet.balance).toEqual(new Bignum(initialBalance).minus(transaction.amount).minus(transaction.fee)); - }); - }); - - describe("revertTransactionForSender", () => { - it("should be ok", () => { - handler.revert = jest.fn(); - - const initialBalance = 1000 * SATOSHI; - wallet.balance = new Bignum(initialBalance); - - handler.revertTransactionForSender(wallet, transaction); - - expect(wallet.balance).toEqual(new Bignum(initialBalance).plus(transaction.amount).plus(transaction.fee)); - }); - - it("should not be ok", () => { - handler.revert = jest.fn(); - - transaction.senderPublicKey = "a".repeat(66); - - const initialBalance = 1000 * SATOSHI; - wallet.balance = new Bignum(initialBalance); - - handler.revertTransactionForSender(wallet, transaction); - - expect(wallet.balance).toEqual(new Bignum(initialBalance)); - }); - - it("should not fail due to case mismatch", () => { - handler.revert = jest.fn(); - - const initialBalance = 1000 * SATOSHI; - wallet.balance = new Bignum(initialBalance); - transaction.senderPublicKey = transaction.senderPublicKey.toUpperCase(); - wallet.publicKey = wallet.publicKey.toLowerCase(); - - handler.revertTransactionForSender(wallet, transaction); - - expect(wallet.balance).toEqual(new Bignum(initialBalance).plus(transaction.amount).plus(transaction.fee)); - }); - }); - - describe("applyTransactionToRecipient", () => { - it("should be ok", () => { - const initialBalance = 1000 * SATOSHI; - wallet.balance = new Bignum(initialBalance); - - handler.applyTransactionToRecipient(wallet, transaction); - - expect(wallet.balance).toEqual(new Bignum(initialBalance).plus(transaction.amount)); - }); - - it("should not be ok", () => { - transaction.recipientId = "invalid-recipientId"; - - const initialBalance = 1000 * SATOSHI; - wallet.balance = new Bignum(initialBalance); - - handler.applyTransactionToRecipient(wallet, transaction); - - expect(wallet.balance).toEqual(new Bignum(initialBalance)); - }); - }); - - describe("revertTransactionForRecipient", () => { - it("should be ok", () => { - const initialBalance = 1000 * SATOSHI; - wallet.balance = new Bignum(initialBalance); - - handler.revertTransactionForRecipient(wallet, transaction); - - expect(wallet.balance).toEqual(new Bignum(initialBalance - transaction.amount)); - }); - - it("should not be ok", () => { - transaction.recipientId = "invalid-recipientId"; - - const initialBalance = 1000 * SATOSHI; - wallet.balance = new Bignum(initialBalance); - - handler.revertTransactionForRecipient(wallet, transaction); - - expect(wallet.balance).toEqual(new Bignum(initialBalance)); - }); - }); -}); diff --git a/packages/crypto/__tests__/handlers/transactions/ipfs.test.ts b/packages/crypto/__tests__/handlers/transactions/ipfs.test.ts deleted file mode 100644 index 90afd0f7f2..0000000000 --- a/packages/crypto/__tests__/handlers/transactions/ipfs.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import "jest-extended"; - -import { IpfsHandler } from "../../../src/handlers/transactions/ipfs"; -import { Bignum } from "../../../src/utils"; -import { transaction as originalTransaction } from "./__fixtures__/transaction"; -import { wallet as originalWallet } from "./__fixtures__/wallet"; - -const handler = new IpfsHandler(); - -let wallet; -let transaction; -let errors; - -beforeEach(() => { - wallet = originalWallet; - transaction = originalTransaction; - - errors = []; -}); - -describe("IpfsHandler", () => { - describe("canApply", () => { - it("should be true", () => { - expect(handler.canApply(wallet, transaction, [])).toBeTrue(); - }); - - it("should be false", () => { - transaction.senderPublicKey = ("a" as any).repeat(66); - - expect(handler.canApply(wallet, transaction, [])).toBeFalse(); - }); - - it("should be false if wallet has insufficient funds", () => { - wallet.balance = Bignum.ZERO; - - expect(handler.canApply(wallet, transaction, errors)).toBeFalse(); - expect(errors).toContain("Insufficient balance in the wallet"); - }); - }); -}); diff --git a/packages/crypto/__tests__/handlers/transactions/multi-payment.test.ts b/packages/crypto/__tests__/handlers/transactions/multi-payment.test.ts deleted file mode 100644 index df8e4ba7f1..0000000000 --- a/packages/crypto/__tests__/handlers/transactions/multi-payment.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -import "jest-extended"; - -import { MultiPaymentHandler } from "../../../src/handlers/transactions/multi-payment"; -import { Bignum } from "../../../src/utils"; -import { transaction as originalTransaction } from "./__fixtures__/transaction"; -import { wallet as originalWallet } from "./__fixtures__/wallet"; - -const handler = new MultiPaymentHandler(); - -let wallet; -let transaction; -let errors; - -beforeEach(() => { - wallet = originalWallet; - - transaction = { - version: 1, - id: "943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4", - blockid: "11233167632577333611", - type: 7, - timestamp: 36482198, - amount: new Bignum(0), - fee: new Bignum(10000000), - senderId: "DTRdbaUW3RQQSL5By4G43JVaeHiqfVp9oh", - recipientId: "DTRdbaUW3RQQSL5By4G43JVaeHiqfVp9oh", - senderPublicKey: "034da006f958beba78ec54443df4a3f52237253f7ae8cbdb17dccf3feaa57f3126", - signature: - "304402205881204c6e515965098099b0e20a7bf104cd1bad6cfe8efd1641729fcbfdbf1502203cfa3bd9efb2ad250e2709aaf719ac0db04cb85d27a96bc8149aeaab224de82b", - asset: { - payments: [ - { - amount: new Bignum(10), - recipientId: "a", - }, - { - amount: new Bignum(20), - recipientId: "b", - }, - { - amount: new Bignum(30), - recipientId: "c", - }, - { - amount: new Bignum(40), - recipientId: "d", - }, - { - amount: new Bignum(50), - recipientId: "e", - }, - ], - }, - }; - - errors = []; -}); - -describe.skip("MultiPaymentHandler", () => { - describe("canApply", () => { - it("should be true", () => { - expect(handler.canApply(wallet, transaction, [])).toBeTrue(); - expect(errors).toBeEmpty(); - }); - - it("should be false if wallet has insufficient funds", () => { - wallet.balance = Bignum.ZERO; - - expect(handler.canApply(wallet, transaction, errors)).toBeFalse(); - expect(errors).toContain("Insufficient balance in the wallet"); - }); - - it("should be false if wallet has insufficient funds send all payouts", () => { - wallet.balance = new Bignum(10000149); - - expect(handler.canApply(wallet, transaction, errors)).toBeFalse(); - expect(errors).toContain("Insufficient balance in the wallet to transfer all payments"); - }); - }); -}); diff --git a/packages/crypto/__tests__/handlers/transactions/multi-signature.test.ts b/packages/crypto/__tests__/handlers/transactions/multi-signature.test.ts deleted file mode 100644 index aa87bbede6..0000000000 --- a/packages/crypto/__tests__/handlers/transactions/multi-signature.test.ts +++ /dev/null @@ -1,193 +0,0 @@ -import "jest-extended"; - -import { crypto } from "../../../src/crypto"; -import { MultiSignatureHandler } from "../../../src/handlers/transactions/multi-signature"; -import { Wallet } from "../../../src/models/wallet"; -import { Bignum } from "../../../src/utils"; -import { transactionValidator } from "../../../src/validation"; - -const handler = new MultiSignatureHandler(); - -let wallet; -let transaction; -let multisignatureTest; -let errors; - -beforeEach(() => { - wallet = new Wallet("D61xc3yoBQDitwjqUspMPx1ooET6r1XLt7"); - wallet.balance = new Bignum(100390000000); - wallet.publicKey = "026f717e50bf3dbb9d8593996df5435ba22217410fc7a132f3d2c942a01a00a202"; - wallet.secondPublicKey = "0380728436880a0a11eadf608c4d4e7f793719e044ee5151074a5f2d5d43cb9066"; - wallet.multisignature = multisignatureTest; - - transaction = { - version: 1, - id: "e22ddd7385b42c00f79b9c6ecd253333ddef6e0bf955341ace2e63dad1f4bd70", - type: 4, - timestamp: 48059808, - amount: Bignum.ZERO, - fee: new Bignum(8000000000), - recipientId: "DGN48KSVFx88chiSu7JbqkAXstqtM1uLJQ", - senderPublicKey: "026f717e50bf3dbb9d8593996df5435ba22217410fc7a132f3d2c942a01a00a202", - signature: - "30450221008baddfae37be66d725e22d9e93c10334d859558f2aef38762803178dbb39354f022025a9bdc7fc4c86d3f67cd1d012dbee3d5691ab3188b5457fdeae82fdd5995767", - signSignature: - "3045022100eb9844a235309309f805235ec40336260cc3dc2c3cbb4cb687dd55b32d8f405402202a98ca5b3b2ad31cec0ed01d9c085a828dd5c07c3893858d4c127fce57d6d410", - signatures: [ - "3045022100f073a3f59ed753f98734462dbe7c9082bb7cb9d46348c671708c93df2fdd2a7602206dc19039d3561f8d1226755dd3b0ca25f359347729eff066eaf3cc3b5c18bc59", - "3045022100c560d6d8504b6761245f7bb3e3b723380b50c380ae30c9544c781f3a9b1359a702206b50506ba6c0a39bed7bec226b55bf9ece979716eb95e2a757f025d3592fde17", - "30440220344345bcb9754ab242dc27bd3d705e5213597914183818005ff1f2e91466f17a0220474c27d05cd5f121c3cad0295e6fc9f8a8cdfa03647e70eb3783e4c1139dde04", - "3045022100998e29255a8f1c140aa41d93ec43271fd8d0e5b9c18df366e3c7b59cc0c293d902205292dd36e9db18f072f00559267361b9426ab26bff2ee613ec0c3627317b4dab", - "3044022007379b5643032d9e9d3395298776be041b2a85a211be2d7b6a5855cf030ae0ca02203c5d3da458034483fdef9f43ee4db4428999cfeb8893795f695e663407238090", - "3044022060461195aeb4386dfab1e3618cdec48f4b988ea394461962379cdbfe8f17b7110220415522adf0239bff7e44e6c0cc8d57211d9d9fe745a6ba2911a81586d5dfc5dc", - "3044022057355ab8ad9502745895a649aede98dfe829c46465eda57438720baeaa6ece5c02200ed3c2eb019579b243380ac066d691f6f27012dc6b93a1403e1a49c992cc0812", - "3044022010cf1079e46cbac198e49f763795095c3a1f33b772cf3e6f335c313f786eb0570220450a110a813cc5453265f0e97850794b0f9d5c6efd6d9ea08009df3d4b9f2299", - "3044022000f35223b23f03413f17538b157e62388c0b150fc046fdcc35792a48d694499402205c2e494ca74565e7841cd6034228cd3d9b57bb832f0da5834991bf92b415c0b8", - "304402206b69cbd52335fca4a510fa1dcb1417617ad1128aa06dcf543d1f11890e46fdef022012d3054adc0a924429d34091910bf82c0abc757f39cfc0887c7e4d9b35f21ad6", - "30440220490bf3e963aa500404e5d559dc06bb1ce176ddbab92f46add87c17c19c3781c90220775f0a3f65d95e3e268eb1f2f2fc86044995e7ebdd1f51f99a973fd00c952d57", - "30450221008795d2e1a454c2cbda92d5fcb7e539372cefbe9f8a181d658abcbd2ba18da8e702207b395488d31f037dc158c12799885edb94f36b1437b2bda79d9074c9a82aa686", - "3045022100cf706e93a9984a958dba6e17287d17febae005d277afc77890e0a3912ee7ed3d02206618718ee68cae209c42a801b7b295fa2564878838712a1b22beaa3637b57c58", - "30440220743aba2fdb663dda73b64ada17812a98adf26f2419c6ab2cfba8f66666527a91022036ade1b37b72079eb43b51a8fe5e31da2e42f7b6d0b437f8a693cc276b9123b8", - "304402206902fc8f519670a7768ad13f1b2d69373aa14c89b70020f83273d6bb0cfd89d102207de30b9ac0c17ff11e364b72c41f1cd8d4e6dccbe28399cb78e96eea32deef12", - ], - asset: { - multisignature: { - min: 15, - lifetime: 72, - keysgroup: [ - "+0217e9e2a1aca300a7011acaadf60af94252875568373546895f227c050d48aac5", - "+02b3b3233c171a122f88c1dbe44539dfefb36530ca3ec04163aef9f448a1823795", - "+03a3013f144160e1964b97e78117571e571a631f0042efcd0de309c7159c7886c8", - "+02fb475ef881b8f56e00407095a87319934c34467db11d3230e54d9328c6cddbe5", - "+03ab9cc2c5364f1676a94b2b5ff3fbc3705e8ce94c6e7e4712890905addf765a3f", - "+024be9e731a63f86b56e5f48dbdfb3443a0628c82ea308ee4c88d3fcbe3183eb9d", - "+0371b8fd17fb1f31095e8a1586bbe29e205904c9100de07c84090a423929a20dcf", - "+02cc09a7c5560db72e312f58a9f5ca4b60b5109efc5ce9dd58a116fa16516bb493", - "+02145fbe9309ebb1547eb332686efb4d8b6e2aaa1fe636663bf6ab1000e5cf72d3", - "+0274966781d4d23f8991530b33bdb051905cde809ae52e58e45cfd1bc8f6f70cc6", - "+0347288f8db9be069415c6c97fd4825867f4bd9b9f78557e8aa1244890beb85001", - "+035359097c405e90516be78104de0ca17001da2826397e0937b8b1e8e613fff352", - "+021aa343234514f8fdaf5e668bdc822a42805382567fa2ca9a5e06e92065f5658a", - "+033a28a0a9592952336918ddded08dd55503b82852fe67df1d358f07a575910844", - "+02747bec17b02cc09345c8c0dbeb09bff2db74d1c355135e10af0001eb1dc00265", - ], - }, - }, - confirmations: 1091040, - }; - - multisignatureTest = { - min: 15, - lifetime: 72, - keysgroup: [ - "034a7aca6841cfbdc688f09d55345f21c7ffbd1844693fa68d607fc94f729cbbea", - "02fd6743ddfdc7c5bac24145e449c2e4f2d569b5297dd7bf088c3bc219f582a2f0", - "02f9c51812f4be127b9f1f21cb4e146eca6aecc85739a243db0f1064981deda216", - "0214d60ca95cd87a097ed6e6e42281acb68ae1815c8f494b8ff18d24dc9e072171", - "02a14634e04e80b05acd56bc361af98498d76fbf5233f8d62773ceaa07172ddaa6", - "021a9ba0e72f02b8fa7bad386582ec1d6c05b7664c892bf2a86035a85350f37203", - "02e3ba373c6c352316b748e75358ead36504b0ef5139d215fb6a83a330c4eb60d5", - "0309039bfa18d6fd790edb79438783b27fbcab06040a2fdaa66fb81ad53ca8db5f", - "0393d12aff5962fa9065487959719a81c5d991e7c48a823039acd9254c2b673841", - "03d3265264f06fe1dd752798403a73e537eb461fc729c83a84b579e8434adfe7c4", - "02acfa496a6c12cb9acc3219993b17c62d19f4b570996c12a37d6e89eaa9716859", - "03136f2101f1767b0d63d9545410bcaf3a941b2b6f06851093f3c679e0d31ca1cd", - "02e6ec3941be86177bf0b998589c07da1b73e990466fdaee347c972c10f61b3797", - "037dcd05d921a9f2ddd11960fec2ea9904fc55cad030549a6c5f5a41b2e35e56d2", - "03206f7ae26f14cffb62b8c28b5e632952cdeb84b7c74ac0c2198b08bd84ee4f23", - ], - }; - - errors = []; -}); - -describe("MultiSignatureHandler", () => { - describe("canApply", () => { - it("should be true", () => { - delete wallet.multisignature; - - expect(handler.canApply(wallet, transaction, [])).toBeTrue(); - }); - - it.skip("should be false if the wallet already has multisignatures", () => { - wallet.verifySignatures = jest.fn(() => true); - wallet.multisignature = multisignatureTest; - - expect(handler.canApply(wallet, transaction, errors)).toBeFalse(); - expect(errors).toEqual(["Wallet is already a multi-signature wallet"]); - }); - - it.skip("should be false if failure to verify signatures", () => { - wallet.verifySignatures = jest.fn(() => false); - wallet.multisignature = multisignatureTest; - - expect(handler.canApply(wallet, transaction, errors)).toBeFalse(); - expect(errors).toEqual(["Failed to verify multi-signatures"]); - }); - - it("should be false if failure to verify signatures in asset", () => { - wallet.verifySignatures = jest.fn(() => false); - delete wallet.multisignature; - - expect(handler.canApply(wallet, transaction, errors)).toBeFalse(); - expect(errors).toEqual(["Failed to verify multi-signatures"]); - }); - - it("should be false if the number of keys is less than minimum", () => { - delete wallet.multisignature; - - wallet.verifySignatures = jest.fn(() => true); - crypto.verifySecondSignature = jest.fn(() => true); - // @ts-ignore - transactionValidator.validate = jest.fn(() => ({ fails: false })); - - transaction.asset.multisignature.keysgroup.splice(0, 5); - - expect(handler.canApply(wallet, transaction, errors)).toBeFalse(); - expect(errors).toEqual(["Specified key count does not meet minimum key count"]); - }); - - it("should be false if the number of keys does not equal the signature count", () => { - delete wallet.multisignature; - - wallet.verifySignatures = jest.fn(() => true); - crypto.verifySecondSignature = jest.fn(() => true); - // @ts-ignore - transactionValidator.validate = jest.fn(() => ({ fails: false })); - - transaction.signatures.splice(0, 5); - - expect(handler.canApply(wallet, transaction, errors)).toBeFalse(); - expect(errors).toEqual(["Specified key count does not equal signature count"]); - }); - - it("should be false if wallet has insufficient funds", () => { - delete wallet.multisignature; - - wallet.balance = Bignum.ZERO; - - expect(handler.canApply(wallet, transaction, errors)).toBeFalse(); - expect(errors).toContain("Insufficient balance in the wallet"); - }); - }); - - describe("apply", () => { - it("should be ok", () => { - wallet.multisignature = null; - - expect(wallet.multisignature).toBeNull(); - - handler.applyTransactionToSender(wallet, transaction); - - expect(wallet.multisignature).toEqual(transaction.asset.multisignature); - }); - }); - - describe("revert", () => { - it("should be ok", () => { - handler.revertTransactionForSender(wallet, transaction); - - expect(wallet.multisignature).toBeNull(); - }); - }); -}); diff --git a/packages/crypto/__tests__/handlers/transactions/second-signature.test.ts b/packages/crypto/__tests__/handlers/transactions/second-signature.test.ts deleted file mode 100644 index 677509d05b..0000000000 --- a/packages/crypto/__tests__/handlers/transactions/second-signature.test.ts +++ /dev/null @@ -1,101 +0,0 @@ -import "jest-extended"; - -import { SecondSignatureHandler } from "../../../src/handlers/transactions/second-signature"; -import { Bignum } from "../../../src/utils"; - -const handler = new SecondSignatureHandler(); - -let wallet; -let transaction; -let errors; - -beforeEach(() => { - wallet = { - address: "DSD9Wi2rfqzDb3REUB5MELQGrsUAjY67gj", - balance: new Bignum("6453530000000"), - publicKey: "03cba4fd60f856ad034ee0a9146432757ae35956b640c26fb6674061924b05a5c9", - }; - - transaction = { - version: 1, - network: 30, - type: 1, - timestamp: 53995738, - senderPublicKey: "03cba4fd60f856ad034ee0a9146432757ae35956b640c26fb6674061924b05a5c9", - fee: new Bignum(500000000), - asset: { - signature: { - publicKey: "02d5cfcbc4920d041d2a54b29e1f69173536796fd50f62af0f88ad6adc6df07cb8", - }, - }, - signature: - "3044022064e7abe87c186b201eaeeb9587097432816c94b52b85520a70da1d78b93456aa0220205e263a278c64771d46038f116c37dc16c86e73664e7e829951d7c5544c6d3e", - amount: Bignum.ZERO, - recipientId: "DSD9Wi2rfqzDb3REUB5MELQGrsUAjY67gj", - id: "e5a4cf622a24d459987f093e14a14c6b0492834358f86099afe1a2d14457cf31", - }; - - errors = []; -}); - -describe("SecondSignatureHandler", () => { - describe("canApply", () => { - it("should be true", () => { - expect(handler.canApply(wallet, transaction, errors)).toBeTrue(); - - expect(errors).toBeEmpty(); - }); - - it("should be false if wallet already has a second signature", () => { - wallet.secondPublicKey = "02d5cfcbc4920d041d2a54b29e1f69173536796fd50f62af0f88ad6adc6df07cb8"; - - expect(handler.canApply(wallet, transaction, errors)).toBeFalse(); - expect(errors).toContain("Wallet already has a second signature"); - }); - - it("should be false if wallet has insufficient funds", () => { - wallet.balance = Bignum.ZERO; - - expect(handler.canApply(wallet, transaction, errors)).toBeFalse(); - expect(errors).toContain("Insufficient balance in the wallet"); - }); - }); - - describe("apply", () => { - it("should apply second signature registration", () => { - expect(handler.canApply(wallet, transaction, [])).toBeTrue(); - - handler.applyTransactionToSender(wallet, transaction); - - expect(wallet.secondPublicKey).toBe("02d5cfcbc4920d041d2a54b29e1f69173536796fd50f62af0f88ad6adc6df07cb8"); - }); - - it("should be invalid to apply a second signature registration twice", () => { - expect(handler.canApply(wallet, transaction, errors)).toBeTrue(); - expect(errors).toBeEmpty(); - - handler.applyTransactionToSender(wallet, transaction); - - expect(wallet.secondPublicKey).toBe("02d5cfcbc4920d041d2a54b29e1f69173536796fd50f62af0f88ad6adc6df07cb8"); - - expect(handler.canApply(wallet, transaction, errors)).toBeFalse(); - expect(errors).toContain("Wallet already has a second signature"); - }); - }); - - describe("revert", () => { - it("should be ok", () => { - expect(wallet.secondPublicKey).toBeUndefined(); - - expect(handler.canApply(wallet, transaction, [])).toBeTrue(); - - handler.applyTransactionToSender(wallet, transaction); - - expect(wallet.secondPublicKey).toBe("02d5cfcbc4920d041d2a54b29e1f69173536796fd50f62af0f88ad6adc6df07cb8"); - - handler.revertTransactionForSender(wallet, transaction); - - expect(wallet.secondPublicKey).toBeUndefined(); - }); - }); -}); diff --git a/packages/crypto/__tests__/handlers/transactions/timelock-transfer.test.ts b/packages/crypto/__tests__/handlers/transactions/timelock-transfer.test.ts deleted file mode 100644 index bf1efa2610..0000000000 --- a/packages/crypto/__tests__/handlers/transactions/timelock-transfer.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import "jest-extended"; - -import { TimelockTransferHandler } from "../../../src/handlers/transactions/timelock-transfer"; -import { Bignum } from "../../../src/utils"; -import { transaction as originalTransaction } from "./__fixtures__/transaction"; -import { wallet as originalWallet } from "./__fixtures__/wallet"; - -const handler = new TimelockTransferHandler(); - -let wallet; -let transaction; -let errors; - -beforeEach(() => { - wallet = originalWallet; - transaction = originalTransaction; - - errors = []; -}); - -describe("TimelockTransferHandler", () => { - describe("canApply", () => { - it("should be true", () => { - expect(handler.canApply(wallet, transaction, [])).toBeTrue(); - }); - - it("should be false", () => { - transaction.senderPublicKey = "a".repeat(66); - - expect(handler.canApply(wallet, transaction, [])).toBeFalse(); - }); - - it("should be false if wallet has insufficient funds", () => { - wallet.balance = Bignum.ZERO; - - expect(handler.canApply(wallet, transaction, errors)).toBeFalse(); - expect(errors).toContain("Insufficient balance in the wallet"); - }); - }); -}); diff --git a/packages/crypto/__tests__/handlers/transactions/transfer.test.ts b/packages/crypto/__tests__/handlers/transactions/transfer.test.ts deleted file mode 100644 index 7967ae31ba..0000000000 --- a/packages/crypto/__tests__/handlers/transactions/transfer.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import "jest-extended"; - -import { TransferHandler } from "../../../src/handlers/transactions/transfer"; -import { transaction as originalTransaction } from "./__fixtures__/transaction"; -import { wallet as originalWallet } from "./__fixtures__/wallet"; - -const handler = new TransferHandler(); - -let wallet; -let transaction; - -beforeEach(() => { - wallet = originalWallet; - transaction = originalTransaction; -}); - -describe("TransferHandler", () => { - it("should be instantiated", () => { - expect(handler.constructor.name).toBe("TransferHandler"); - }); - - describe("canApply", () => { - it("should be true", () => { - expect(handler.canApply(wallet, transaction, [])).toBeTrue(); - }); - - it("should be false", () => { - transaction.senderPublicKey = "a".repeat(66); - - expect(handler.canApply(wallet, transaction, [])).toBeFalse(); - }); - }); -}); diff --git a/packages/crypto/__tests__/handlers/transactions/vote.test.ts b/packages/crypto/__tests__/handlers/transactions/vote.test.ts deleted file mode 100644 index a84071e4e0..0000000000 --- a/packages/crypto/__tests__/handlers/transactions/vote.test.ts +++ /dev/null @@ -1,152 +0,0 @@ -import "jest-extended"; - -import { VoteHandler } from "../../../src/handlers/transactions/vote"; -import { Bignum } from "../../../src/utils"; - -const handler = new VoteHandler(); - -let wallet; -let voteTransaction; -let unvoteTransaction; -let errors; - -beforeEach(() => { - wallet = { - address: "DQ7VAW7u171hwDW75R1BqfHbA9yiKRCBSh", - balance: new Bignum("6453530000000"), - publicKey: "02a47a2f594635737d2ce9898680812ff7fa6aaa64ddea1360474c110e9985a087", - vote: null, - }; - - voteTransaction = { - id: "73cbce62d69308ff7e69f1a7836106a16dc59907198aea4bb80d340232e53041", - signature: - "3045022100f53da6eb18ca7954bb7c620ceeaf5cb3433685d173401146aea35ee8e5f5c95002204ea57f573745c8f5c57b256e38397d3e1977bdbfac295128320401c6117bb2f3", - timestamp: 54833694, - type: 3, - fee: new Bignum(100000000), - senderPublicKey: "02a47a2f594635737d2ce9898680812ff7fa6aaa64ddea1360474c110e9985a087", - amount: Bignum.ZERO, - recipientId: "DLvBAvLePTJ9DfDzby5AAkqPqwCVDCT647", - asset: { - votes: ["+02d0d835266297f15c192be2636eb3fbc30b39b87fc583ff112062ef8ae1a1f2af"], - }, - }; - - unvoteTransaction = { - id: "d714bc0443208f9281ad83f9f3d941628b875c84f65a09601148ce87ca879cb9", - signature: - "3045022100957106a924eb40df6ff530cff80fede0195c30284fdb5671e736c7d0b57696f6022072b0fd80af235d79701e9aea74ef48366ef9f5aecedbb5d502e6392569c059c8", - timestamp: 54833718, - type: 3, - fee: new Bignum(100000000), - senderPublicKey: "02a47a2f594635737d2ce9898680812ff7fa6aaa64ddea1360474c110e9985a087", - amount: Bignum.ZERO, - recipientId: "DLvBAvLePTJ9DfDzby5AAkqPqwCVDCT647", - asset: { - votes: ["-02d0d835266297f15c192be2636eb3fbc30b39b87fc583ff112062ef8ae1a1f2af"], - }, - }; - - errors = []; -}); - -describe("VoteHandler", () => { - describe("canApply", () => { - it("should be true if the vote is valid and the wallet has not voted", () => { - expect(handler.canApply(wallet, voteTransaction, errors)).toBeTrue(); - expect(errors).toBeEmpty(); - }); - - it("should be true if the unvote is valid and the wallet has voted", () => { - wallet.vote = "02d0d835266297f15c192be2636eb3fbc30b39b87fc583ff112062ef8ae1a1f2af"; - - expect(handler.canApply(wallet, unvoteTransaction, errors)).toBeTrue(); - expect(errors).toBeEmpty(); - }); - - it("should be false if wallet has already voted", () => { - wallet.vote = "02d0d835266297f15c192be2636eb3fbc30b39b87fc583ff112062ef8ae1a1f2af"; - - expect(handler.canApply(wallet, voteTransaction, errors)).toBeFalse(); - expect(errors).toContain("Wallet has already voted"); - }); - - it("should be false if the asset public key differs from the currently voted one", () => { - wallet.vote = "a310ad026647eed112d1a46145eed58b8c19c67c505a67f1199361a511ce7860c0"; - - expect(handler.canApply(wallet, unvoteTransaction, errors)).toBeFalse(); - expect(errors).toContain("The unvote public key does not match the currently voted one"); - }); - - it("should be false if unvoting a non-voted wallet", () => { - expect(handler.canApply(wallet, unvoteTransaction, errors)).toBeFalse(); - expect(errors).toContain("Wallet has not voted yet"); - }); - - it("should be false if wallet has insufficient funds", () => { - wallet.balance = Bignum.ZERO; - - expect(handler.canApply(wallet, voteTransaction, errors)).toBeFalse(); - expect(errors).toContain("Insufficient balance in the wallet"); - }); - }); - - describe("apply", () => { - describe("vote", () => { - it("should be ok", () => { - expect(wallet.vote).toBeNull(); - - handler.applyTransactionToSender(wallet, voteTransaction); - - expect(wallet.vote).not.toBeNull(); - }); - - it("should not be ok", () => { - wallet.vote = "02d0d835266297f15c192be2636eb3fbc30b39b87fc583ff112062ef8ae1a1f2af"; - - expect(wallet.vote).not.toBeNull(); - - handler.applyTransactionToSender(wallet, voteTransaction); - - expect(wallet.vote).not.toBeNull(); - }); - }); - - describe("unvote", () => { - it("should remove the vote from the wallet", () => { - wallet.vote = "02d0d835266297f15c192be2636eb3fbc30b39b87fc583ff112062ef8ae1a1f2af"; - - expect(wallet.vote).not.toBeNull(); - - handler.applyTransactionToSender(wallet, unvoteTransaction); - - expect(wallet.vote).toBeNull(); - }); - }); - }); - - describe("revert", () => { - describe("vote", () => { - it("should remove the vote from the wallet", () => { - wallet.vote = "02d0d835266297f15c192be2636eb3fbc30b39b87fc583ff112062ef8ae1a1f2af"; - - expect(wallet.vote).not.toBeNull(); - - handler.revertTransactionForSender(wallet, voteTransaction); - - expect(wallet.vote).toBeNull(); - }); - }); - - describe("unvote", () => { - it("should add the vote to the wallet", () => { - expect(wallet.vote).toBeNull(); - - handler.revertTransactionForSender(wallet, unvoteTransaction); - - expect(wallet.vote).toBe("02d0d835266297f15c192be2636eb3fbc30b39b87fc583ff112062ef8ae1a1f2af"); - }); - }); - }); -}); diff --git a/packages/crypto/__tests__/managers/config.test.ts b/packages/crypto/__tests__/managers/config.test.ts deleted file mode 100644 index e00cb50f66..0000000000 --- a/packages/crypto/__tests__/managers/config.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import "jest-extended"; - -import { TransactionTypes } from "../../src/constants"; -import { configManager } from "../../src/managers/config"; -import { feeManager } from "../../src/managers/fee"; -import { devnet, mainnet } from "../../src/networks"; - -beforeEach(() => configManager.setConfig(devnet)); - -describe("Configuration", () => { - it("should be instantiated", () => { - expect(configManager).toBeObject(); - }); - - it("should be set on runtime", () => { - configManager.setConfig(mainnet); - - expect(configManager.all()).toContainAllKeys([ - ...Object.keys(mainnet.network), - ...["milestones", "exceptions", "genesisBlock"], - ]); - }); - - it('key should be "set"', () => { - configManager.set("key", "value"); - - expect(configManager.get("key")).toBe("value"); - }); - - it('key should be "get"', () => { - expect(configManager.get("nethash")).toBe("2a44f340d76ffc3df204c5f38cd355b7496c9065a1ade2ef92071436bd72e867"); - }); - - it("should build milestones", () => { - expect(configManager.milestones).toEqual(devnet.milestones); - }); - - it("should build fees", () => { - const feesStatic = devnet.milestones[0].fees.staticFees; - - expect(feeManager.get(TransactionTypes.Transfer)).toEqual(feesStatic.transfer); - expect(feeManager.get(TransactionTypes.SecondSignature)).toEqual(feesStatic.secondSignature); - expect(feeManager.get(TransactionTypes.DelegateRegistration)).toEqual(feesStatic.delegateRegistration); - expect(feeManager.get(TransactionTypes.Vote)).toEqual(feesStatic.vote); - expect(feeManager.get(TransactionTypes.MultiSignature)).toEqual(feesStatic.multiSignature); - expect(feeManager.get(TransactionTypes.Ipfs)).toEqual(feesStatic.ipfs); - expect(feeManager.get(TransactionTypes.TimelockTransfer)).toEqual(feesStatic.timelockTransfer); - expect(feeManager.get(TransactionTypes.MultiPayment)).toEqual(feesStatic.multiPayment); - expect(feeManager.get(TransactionTypes.DelegateResignation)).toEqual(feesStatic.delegateResignation); - }); - - it("should get milestone for height", () => { - expect(configManager.getMilestone(21600)).toEqual(devnet.milestones[2]); - }); - - it("should get milestone for this.height if height is not provided as parameter", () => { - configManager.setHeight(21600); - expect(configManager.getMilestone()).toEqual(devnet.milestones[2]); - }); - - it("should set the height", () => { - configManager.setHeight(21600); - - expect(configManager.getHeight()).toEqual(21600); - }); -}); diff --git a/packages/crypto/__tests__/models/transaction.test.ts b/packages/crypto/__tests__/models/transaction.test.ts deleted file mode 100644 index 53375e69f6..0000000000 --- a/packages/crypto/__tests__/models/transaction.test.ts +++ /dev/null @@ -1,231 +0,0 @@ -import "jest-extended"; - -import { transactionBuilder as builder } from "../../src/builder"; -import { crypto } from "../../src/crypto/crypto"; -import { configManager } from "../../src/managers/config"; -import { Transaction } from "../../src/models/transaction"; -import { transaction as transactionData } from "../fixtures/transaction"; - -import { TransactionTypeError } from "../../src/errors"; -import { devnet } from "../../src/networks"; - -const createRandomTx = type => { - let transaction; - - switch (type) { - case 0: { - // transfer - transaction = builder - .transfer() - .recipientId("AMw3TiLrmVmwmFVwRzn96kkUsUpFTqsAEX") - .amount(1000 * 1e10) - .vendorField(Math.random().toString(36)) - .sign(Math.random().toString(36)) - .secondSign(Math.random().toString(36)) - .build(); - break; - } - - case 1: { - // second signature - transaction = builder - .secondSignature() - .signatureAsset(Math.random().toString(36)) - .sign(Math.random().toString(36)) - .build(); - break; - } - - case 2: { - // delegate registration - transaction = builder - .delegateRegistration() - .usernameAsset("dummy-delegate") - .sign(Math.random().toString(36)) - .build(); - break; - } - - case 3: { - // vote registration - transaction = builder - .vote() - .votesAsset(["+036928c98ee53a1f52ed01dd87db10ffe1980eb47cd7c0a7d688321f47b5d7d760"]) - .sign(Math.random().toString(36)) - .build(); - break; - } - - case 4: { - // multisignature registration - const passphrases = [1, 2, 3].map(() => Math.random().toString(36)); - const publicKeys = passphrases.map(passphrase => `+${crypto.getKeys(passphrase).publicKey}`); - const min = Math.min(1, publicKeys.length); - const max = Math.max(1, publicKeys.length); - const minSignatures = Math.floor(Math.random() * (max - min)) + min; - - const transactionBuilder = builder - .multiSignature() - .multiSignatureAsset({ - keysgroup: publicKeys, - min: minSignatures, - lifetime: Math.floor(Math.random() * (72 - 1)) + 1, - }) - .sign(Math.random().toString(36)); - - for (let i = 0; i < minSignatures; i++) { - transactionBuilder.multiSignatureSign(passphrases[i]); - } - - transaction = transactionBuilder.build(); - break; - } - default: { - throw new TransactionTypeError(type); - } - } - - return transaction; -}; - -describe("Models - Transaction", () => { - beforeEach(() => configManager.setConfig(devnet)); - - describe("static fromBytes", () => { - it("should verify all transactions", () => { - [0, 1, 2, 3] - .map(type => createRandomTx(type)) - .forEach(transaction => { - const ser = Transaction.serialize(transaction.data).toString("hex"); - const newTransaction = new Transaction(ser); - expect(newTransaction.data).toEqual(transaction.data); - expect(newTransaction.verified).toBeTrue(); - }); - }); - - it("should create a transaction", () => { - const hex = Transaction.serialize(transactionData).toString("hex"); - const transaction = new Transaction(hex); - expect(transaction).toBeInstanceOf(Transaction); - - // We can't compare the data directly, since the created instance uses Bignums. - // ... call toJson() which casts the Bignums to numbers beforehand. - expect(transaction.toJson()).toEqual(transactionData); - }); - }); - - describe("static deserialize", () => { - it("should match transaction id", () => { - [0, 1, 2, 3] - .map(type => createRandomTx(type)) - .forEach(transaction => { - const originalId = transaction.data.id; - const newTransaction = new Transaction(transaction.data); - expect(newTransaction.id).toEqual(originalId); - }); - }); - }); - - describe("should deserialize correctly some tests transactions", () => { - const txs = [ - { - id: "80d75c7b90288246199e4a97ba726bad6639595ef92ad7c2bd14fd31563241ab", - network: 0x17, - height: 918991, - type: 1, - timestamp: 7410965, - amount: 0, - fee: 500000000, - recipientId: "AP4UQ6j9hAHsxudpXh47RNQi7oF1AEfkAG", - senderPublicKey: "03ca269b2942104b2ad601ccfbe7bd30b14b99cb55210ef7c1a5e25b6669646b99", - signature: - "3045022100d01e0cf0813a722ab5ad92aece2d4d1c3a537422e2ea769182f9172417224e890220437e407db51c4c47393db2e5b1258b2e3ecb707738a5ffdc6e96f08aee7e9c74", - asset: { - signature: { - publicKey: "03c0e7e86dadd316275a31d84a1fdccd00cd26cc059982f95a1b24382c6ec2ceb0", - }, - }, - }, - { - id: "89f354918b36197269b0e5514f8da66f19829a024f664ccc124bfaabe0266e10", - version: 1, - timestamp: 48068690, - senderPublicKey: "03b7d1da5c1b9f8efd0737d47123eb9cf7eb6d58198ef31bb6f01aa04bc4dda19d", - recipientId: "DHPNjqCaTR9KYtC8nHh7Zt1G86Xj4YiU2V", - type: 1, - amount: "0", - fee: "500000000", - signature: - "3045022100e8e03bdac70e18f220feacba25c1575aa89d1ab61673e54eb2aff38439666d2702207e2d84290d7ef2571f5b2fab7e22a77dec96b1c4187cf9def15be74db98e2700", - asset: { - signature: { - publicKey: "03b7d1da5c1b9f8efd0737d47123eb9cf7eb6d58198ef31bb6f01aa04bc4dda19d", - }, - }, - }, - { - id: "a50af2bb1f043d128480346d0b49f5b3165716d5c630c6b0978dc7aa168e77a8", - version: 1, - timestamp: 48068923, - senderPublicKey: "03173fd793c4bac0d64e9bd74ec5c2055716a7c0266feec9d6d94cb75be097b75d", - recipientId: "DQrj9eh9otRgz2jWdu1K1ASBQqZA6dTkra", - type: 1, - amount: "0", - fee: "500000000", - signature: - "3045022100b263d28a5da58b17c874a5666afab0657f8492266554ad8ff722b00d41e1493d02200c2156dd9b9c1739f1c2099e98b763952bc7ef0423ad9786dcd32f7ffaf4aafc", - asset: { - signature: { - publicKey: "03173fd793c4bac0d64e9bd74ec5c2055716a7c0266feec9d6d94cb75be097b75d", - }, - }, - }, - { - id: "68e34dc1c417cbfb47e5deea142974bc24c8d03df206f168c8b23d6a4decff73", - version: 1, - timestamp: 48068956, - senderPublicKey: "02813ade967f05384e0567841d175294b4102c06c428011646e5ef989212925fcf", - recipientId: "D8PGSYLUC3CxYaXoKjMA2gjV4RaeBpwghZ", - type: 1, - amount: "0", - fee: "500000000", - signature: - "3045022100e593eb501e89941461e247606d088b6e226cc5b5224f89cede532d35f9b16250022034bbdd098493639221e808301e0a99c3790ef9c6d357ac10266c518a2a66066f", - asset: { - signature: { - publicKey: "02813ade967f05384e0567841d175294b4102c06c428011646e5ef989212925fcf", - }, - }, - }, - { - id: "b4b3433be888b4b95b68b83a84a08e40d748b0ad92acf8487072ef01c1de251a", - version: 1, - timestamp: 48069792, - senderPublicKey: "03f9f9dafc06faf4a54be2e45cd7a5523e41f38bb439f6f93cf00a0990e7afc116", - recipientId: "DNuwcwYGTHDdhTPWMTYekhuGM1fFUpW9Jj", - type: 1, - amount: "0", - fee: "500000000", - signature: - "3044022052d1e5be426a79f827a67597fd460237de65e035593144e4e3afb0e82ab40f3802201d6e31892d000e73532bf8659851a3d221205d65ed1c0b8d08ce46b72c7f00ae", - asset: { - signature: { - publicKey: "03f9f9dafc06faf4a54be2e45cd7a5523e41f38bb439f6f93cf00a0990e7afc116", - }, - }, - }, - ]; - txs.forEach(tx => - it(`txid: ${tx.id}`, () => { - const newtx = new Transaction(tx); - expect(newtx.id).toEqual(tx.id); - }), - ); - }); - - it("Signatures are verified", () => { - [0, 1, 2, 3] - .map(type => createRandomTx(type)) - .forEach(transaction => expect(crypto.verify(transaction)).toBeTrue()); - }); -}); diff --git a/packages/crypto/__tests__/validation/extensions/bignumber.test.ts b/packages/crypto/__tests__/validation/extensions/bignumber.test.ts deleted file mode 100644 index a2980b5774..0000000000 --- a/packages/crypto/__tests__/validation/extensions/bignumber.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -import BigNumber from "bignumber.js"; -import Joi from "joi"; -import { extensions } from "../../../src/validation/extensions"; - -const shouldPass = value => expect(value.error).toBeNull(); -const shouldFail = (value, message) => expect(value.error.details[0].message).toBe(`"value" ${message}`); - -const validator = Joi.extend(extensions); - -let bigNumber; - -beforeEach(() => { - bigNumber = new BigNumber(100); -}); - -describe("BigNumber validation extension", () => { - it("passes when validating if only the same number", () => { - shouldPass(validator.validate(bigNumber, validator.bignumber().only(100))); - }); - - it("fails when validating if only a different number", () => { - shouldFail(validator.validate(bigNumber, validator.bignumber().only(2)), "is different from allowed value"); - }); - - it("passes when validating if minimum a smaller or equal number", () => { - shouldPass(validator.validate(bigNumber, validator.bignumber().min(20))); - - shouldPass(validator.validate(bigNumber, validator.bignumber().min(100))); - }); - - describe("min", () => { - it("should pass", () => { - shouldPass(validator.validate(new BigNumber(1), validator.bignumber().min(1))); - }); - - it("should fail", () => { - shouldFail(validator.validate(new BigNumber(1), validator.bignumber().min(2)), "is less than minimum"); - }); - }); - - describe("max", () => { - it("should pass", () => { - shouldPass(validator.validate(new BigNumber(1), validator.bignumber().max(2))); - }); - - it("should fail", () => { - shouldFail(validator.validate(new BigNumber(2), validator.bignumber().max(1)), "is greater than maximum"); - }); - }); - - describe("only", () => { - it("should pass", () => { - shouldPass(validator.validate(new BigNumber(0), validator.bignumber().only(0))); - }); - - it("should fail", () => { - shouldFail( - validator.validate(new BigNumber(0), validator.bignumber().only(1)), - "is different from allowed value", - ); - }); - }); - - describe("integer", () => { - it("should pass", () => { - shouldPass(validator.validate(new BigNumber(1), validator.bignumber().integer())); - }); - - it("should fail", () => { - shouldFail( - validator.validate(new BigNumber(123.456), validator.bignumber().integer()), - "is not an integer", - ); - }); - }); - - describe("positive", () => { - it("should pass", () => { - shouldPass(validator.validate(new BigNumber(1), validator.bignumber().positive())); - }); - - it("should fail", () => { - shouldFail(validator.validate(new BigNumber(-1), validator.bignumber().positive()), "is not positive"); - }); - }); -}); diff --git a/packages/crypto/__tests__/validation/extensions/transactions/delegate-registration.test.ts b/packages/crypto/__tests__/validation/extensions/transactions/delegate-registration.test.ts deleted file mode 100644 index b613d81e2f..0000000000 --- a/packages/crypto/__tests__/validation/extensions/transactions/delegate-registration.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -// tslint:disable:no-empty - -import Joi from "joi"; -import { constants, transactionBuilder } from "../../../../src"; -import { extensions } from "../../../../src/validation/extensions"; - -const validator = Joi.extend(extensions); - -let transaction; -beforeEach(() => { - transaction = transactionBuilder.delegateRegistration(); -}); - -describe("Delegate Registration Transaction", () => { - it("should be valid", () => { - transaction.usernameAsset("delegate1").sign("passphrase"); - - expect(validator.validate(transaction.getStruct(), validator.delegateRegistration()).error).toBeNull(); - }); - - it("should be invalid due to no transaction as object", () => { - expect(validator.validate("test", validator.delegateRegistration()).error).not.toBeNull(); - }); - - it("should be invalid due to non-zero amount", () => { - transaction - .usernameAsset("delegate1") - .amount(10 * constants.SATOSHI) - .sign("passphrase"); - - expect(validator.validate(transaction.getStruct(), validator.delegateRegistration()).error).not.toBeNull(); - }); - - it("should be invalid due to space in username", () => { - transaction.usernameAsset("test 123").sign("passphrase"); - - expect(validator.validate(transaction.getStruct(), validator.delegateRegistration()).error).not.toBeNull(); - }); - - it("should be invalid due to non-alphanumeric in username", () => { - transaction.usernameAsset("£££").sign("passphrase"); - - expect(validator.validate(transaction.getStruct(), validator.delegateRegistration()).error).not.toBeNull(); - }); - - it("should be invalid due to username too long", () => { - transaction.usernameAsset("1234567890123456789012345").sign("passphrase"); - - expect(validator.validate(transaction.getStruct(), validator.delegateRegistration()).error).not.toBeNull(); - }); - - it("should be invalid due to undefined username", () => { - try { - transaction.usernameAsset(undefined).sign("passphrase"); - expect(validator.validate(transaction.getStruct(), validator.delegateRegistration()).error).not.toBeNull(); - } catch (error) {} - }); - - it("should be invalid due to no username", () => { - transaction.usernameAsset("").sign("passphrase"); - - expect(validator.validate(transaction.getStruct(), validator.delegateRegistration()).error).not.toBeNull(); - }); - - it("should be invalid due to capitals in username", () => { - transaction.usernameAsset("I_AM_INVALID").sign("passphrase"); - - expect(validator.validate(transaction.getStruct(), validator.delegateRegistration()).error).not.toBeNull(); - }); - - it("should be invalid due to wrong transaction type", () => { - transaction = transactionBuilder.transfer(); - transaction - .recipientId(null) - .amount(10 * constants.SATOSHI) - .sign("passphrase"); - - expect(validator.validate(transaction.getStruct(), validator.delegateRegistration()).error).not.toBeNull(); - }); -}); diff --git a/packages/crypto/__tests__/validation/extensions/transactions/multi-signature.test.ts b/packages/crypto/__tests__/validation/extensions/transactions/multi-signature.test.ts deleted file mode 100644 index 8d1ed49343..0000000000 --- a/packages/crypto/__tests__/validation/extensions/transactions/multi-signature.test.ts +++ /dev/null @@ -1,176 +0,0 @@ -// tslint:disable:no-empty - -import Joi from "joi"; -import { constants, crypto, transactionBuilder } from "../../../../src"; -import { extensions } from "../../../../src/validation/extensions"; - -const validator = Joi.extend(extensions); - -const passphrase = "passphrase 1"; -const publicKey = "+03e8021105a6c202097e97e6c6d650942d913099bf6c9f14a6815df1023dde3b87"; -const passphrases = [passphrase, "passphrase 2", "passphrase 3"]; -const keysGroup = [ - publicKey, - "+03dfdaaa7fd28bc9359874b7e33138f4d0afe9937e152c59b83a99fae7eeb94899", - "+03de72ef9d3ebf1b374f1214f5b8dde823690ab2aa32b4b8b3226cc568aaed1562", -]; - -const signTransaction = (tx, values) => { - values.map(value => tx.multiSignatureSign(value)); -}; - -let transaction; -let multiSignatureAsset; -beforeEach(() => { - transaction = transactionBuilder.multiSignature(); - multiSignatureAsset = { - min: 1, - keysgroup: keysGroup, - lifetime: 72, - }; -}); - -describe("Multi Signature Transaction", () => { - it("should be valid with min of 3", () => { - multiSignatureAsset.min = 3; - transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); - signTransaction(transaction, passphrases); - expect(validator.validate(transaction.getStruct(), validator.multiSignature()).error).toBeNull(); - }); - - it("should be valid with 3 public keys", () => { - transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); - signTransaction(transaction, passphrases); - expect(validator.validate(transaction.getStruct(), validator.multiSignature()).error).toBeNull(); - }); - - it("should be valid with lifetime of 10", () => { - multiSignatureAsset.lifetime = 10; - transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); - signTransaction(transaction, passphrases); - expect(validator.validate(transaction.getStruct(), validator.multiSignature()).error).toBeNull(); - }); - - it("should be invalid due to no transaction as object", () => { - expect(validator.validate("test", validator.multiSignature()).error).not.toBeNull(); - }); - - it("should be invalid due to non-zero amount", () => { - transaction - .multiSignatureAsset(multiSignatureAsset) - .amount(10 * constants.SATOSHI) - .sign("passphrase"); - signTransaction(transaction, passphrases); - expect(validator.validate(transaction.getStruct(), validator.multiSignature()).error).not.toBeNull(); - }); - - it("should be invalid due to zero fee", () => { - transaction - .multiSignatureAsset(multiSignatureAsset) - .fee(0) - .sign("passphrase"); - signTransaction(transaction, passphrases); - expect(validator.validate(transaction.getStruct(), validator.multiSignature()).error).not.toBeNull(); - }); - - it("should be invalid due to min too low", () => { - multiSignatureAsset.min = 0; - transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); - signTransaction(transaction, passphrases); - expect(validator.validate(transaction.getStruct(), validator.multiSignature()).error).not.toBeNull(); - }); - - it("should be invalid due to min too high", () => { - multiSignatureAsset.min = multiSignatureAsset.keysgroup.length + 1; - transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); - signTransaction(transaction, passphrases); - expect(validator.validate(transaction.getStruct(), validator.multiSignature()).error).not.toBeNull(); - }); - - it("should be invalid due to lifetime too low", () => { - multiSignatureAsset.lifetime = 0; - transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); - signTransaction(transaction, passphrases); - expect(validator.validate(transaction.getStruct(), validator.multiSignature()).error).not.toBeNull(); - }); - - it("should be invalid due to lifetime too high", () => { - multiSignatureAsset.lifetime = 100; - transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); - signTransaction(transaction, passphrases); - expect(validator.validate(transaction.getStruct(), validator.multiSignature()).error).not.toBeNull(); - }); - - it("should be invalid due to no public keys", () => { - multiSignatureAsset.keysgroup = []; - transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); - signTransaction(transaction, passphrases); - expect(validator.validate(transaction.getStruct(), validator.multiSignature()).error).not.toBeNull(); - }); - - it("should be invalid due to too many public keys", () => { - const values = []; - multiSignatureAsset.keysgroup = []; - for (let i = 0; i < 20; i++) { - const value = `passphrase ${i}`; - values.push(value); - multiSignatureAsset.keysgroup.push(crypto.getKeys(value).publicKey); - } - transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); - signTransaction(transaction, values); - expect(validator.validate(transaction.getStruct(), validator.multiSignature()).error).not.toBeNull(); - }); - - it("should be invalid due to duplicate public keys", () => { - multiSignatureAsset.keysgroup = [publicKey, publicKey]; - transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); - signTransaction(transaction, passphrases); - expect(validator.validate(transaction.getStruct(), validator.multiSignature()).error).not.toBeNull(); - }); - - it("should be invalid due to no signatures", () => { - transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); - expect(validator.validate(transaction.getStruct(), validator.multiSignature()).error).not.toBeNull(); - }); - - it("should be invalid due to not enough signatures", () => { - transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); - signTransaction(transaction, passphrases.slice(1)); - expect(validator.validate(transaction.getStruct(), validator.multiSignature()).error).not.toBeNull(); - }); - - it("should be invalid due to too many signatures", () => { - transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); - signTransaction(transaction, ["wrong passphrase", ...passphrases]); - expect(validator.validate(transaction.getStruct(), validator.multiSignature()).error).not.toBeNull(); - }); - - it('should be invalid due to no "+" for publicKeys', () => { - multiSignatureAsset.keysgroup = keysGroup.map(value => value.slice(1)); - transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); - signTransaction(transaction, passphrases); - expect(validator.validate(transaction.getStruct(), validator.multiSignature()).error).not.toBeNull(); - }); - - it('should be invalid due to having "-" for publicKeys', () => { - multiSignatureAsset.keysgroup = keysGroup.map(value => `-${value.slice(1)}`); - transaction.multiSignatureAsset(multiSignatureAsset).sign("passphrase"); - signTransaction(transaction, passphrases); - expect(validator.validate(transaction.getStruct(), validator.multiSignature()).error).not.toBeNull(); - }); - - it("should be invalid due to wrong keysgroup type", () => { - try { - multiSignatureAsset.keysgroup = publicKey; - transaction.multiSignatureAsset(publicKey).sign("passphrase"); - signTransaction(transaction, passphrases); - expect(validator.validate(transaction.getStruct(), validator.multiSignature()).error).not.toBeNull(); - } catch (error) {} - }); - - it("should be invalid due to wrong transaction type", () => { - transaction = transactionBuilder.delegateRegistration(); - transaction.usernameAsset("delegate_name").sign("passphrase"); - expect(validator.validate(transaction.getStruct(), validator.multiSignature()).errors).not.toBeNull(); - }); -}); diff --git a/packages/crypto/__tests__/validation/extensions/transactions/second-signature.test.ts b/packages/crypto/__tests__/validation/extensions/transactions/second-signature.test.ts deleted file mode 100644 index b65d72efe2..0000000000 --- a/packages/crypto/__tests__/validation/extensions/transactions/second-signature.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import Joi from "joi"; -import { constants, transactionBuilder } from "../../../../src"; -import { extensions } from "../../../../src/validation/extensions"; - -const validator = Joi.extend(extensions); - -let transaction; -beforeEach(() => { - transaction = transactionBuilder.secondSignature(); -}); - -// NOTE: some tests aren't strictly about the second signature - -describe("Second Signature Transaction", () => { - it("should be valid", () => { - transaction.signatureAsset("second passphrase").sign("passphrase"); - expect(validator.validate(transaction.getStruct(), validator.secondSignature()).error).toBeNull(); - }); - - it("should be valid with correct data", () => { - transaction - .signatureAsset("second passphrase") - .fee(1 * constants.SATOSHI) - .sign("passphrase"); - expect(validator.validate(transaction.getStruct(), validator.secondSignature()).error).toBeNull(); - }); - - it("should be invalid due to no transaction as object", () => { - expect(validator.validate("test", validator.secondSignature()).error).not.toBeNull(); - }); - - it("should be invalid due to non-zero amount", () => { - transaction - .signatureAsset("second passphrase") - .amount(10 * constants.SATOSHI) - .sign("passphrase"); - expect(validator.validate(transaction.getStruct(), validator.secondSignature()).error).not.toBeNull(); - }); - - it("should be invalid due to zero fee", () => { - transaction - .signatureAsset("second passphrase") - .fee(0) - .sign("passphrase"); - expect(validator.validate(transaction.getStruct(), validator.secondSignature()).error).not.toBeNull(); - }); - - it("should be invalid due to second signature", () => { - transaction - .signatureAsset("second passphrase") - .fee(1) - .sign("passphrase") - .secondSign("second passphrase"); - expect(validator.validate(transaction.getStruct(), validator.secondSignature())).not.toBeNull(); - }); - - it("should be invalid due to wrong transaction type", () => { - transaction = transactionBuilder.delegateRegistration(); - transaction.usernameAsset("delegate_name").sign("passphrase"); - expect(validator.validate(transaction.getStruct(), validator.secondSignature()).error).not.toBeNull(); - }); -}); diff --git a/packages/crypto/__tests__/validation/extensions/transactions/transfer.test.ts b/packages/crypto/__tests__/validation/extensions/transactions/transfer.test.ts deleted file mode 100644 index aa8c5b8157..0000000000 --- a/packages/crypto/__tests__/validation/extensions/transactions/transfer.test.ts +++ /dev/null @@ -1,181 +0,0 @@ -import Joi from "joi"; -import { configManager, constants, transactionBuilder } from "../../../../src"; -import { extensions } from "../../../../src/validation/extensions"; - -const validator = Joi.extend(extensions); - -const address = "APnDzjtDb1FthuqcLMeL5XMWb1uD1KeMGi"; -const fee = 1 * constants.SATOSHI; -const amount = 10 * constants.SATOSHI; - -let transaction; -beforeEach(() => { - transaction = transactionBuilder.transfer(); -}); - -describe("Transfer Transaction", () => { - it("should be valid", () => { - transaction - .recipientId(address) - .amount(amount) - .sign("passphrase"); - expect(validator.validate(transaction.getStruct(), validator.transfer()).error).toBeNull(); - }); - - it("should be valid with correct data", () => { - transaction - .recipientId(address) - .amount(amount) - .fee(fee) - .vendorField("Ahoy") - .sign("passphrase"); - expect(validator.validate(transaction.getStruct(), validator.transfer()).error).toBeNull(); - }); - - it("should be valid with up to 64 bytes in vendor field", () => { - transaction - .recipientId(address) - .amount(amount) - .fee(fee) - .vendorField("a".repeat(64)) - .sign("passphrase"); - expect(validator.validate(transaction.getStruct(), validator.transfer()).error).toBeNull(); - - transaction - .recipientId(address) - .amount(amount) - .fee(fee) - .vendorField("⊁".repeat(21)) - .sign("passphrase"); - expect(validator.validate(transaction.getStruct(), validator.transfer()).error).toBeNull(); - }); - - it("should be invalid with more than 64 bytes in vendor field", () => { - transaction - .recipientId(address) - .amount(amount) - .fee(fee); - - // Bypass vendorfield check by manually assigning a vendorfield > 64 bytes - transaction.data.vendorField = "a".repeat(65); - transaction.sign("passphrase"); - - expect(validator.validate(transaction.getStruct(), validator.transfer()).error).not.toBeNull(); - - transaction - .recipientId(address) - .amount(amount) - .fee(fee); - - // Bypass vendorfield check by manually assigning a vendorfield > 64 bytes - transaction.vendorField("⊁".repeat(22)); - transaction.sign("passphrase"); - - expect(validator.validate(transaction.getStruct(), validator.transfer()).error).not.toBeNull(); - }); - - it("should be invalid due to no transaction as object", () => { - expect(validator.validate("test", validator.transfer()).error).not.toBeNull(); - }); - - it("should be invalid due to no address", () => { - transaction - .recipientId(null) - .amount(amount) - .sign("passphrase"); - expect(validator.validate(transaction.getStruct(), validator.transfer()).error).not.toBeNull(); - }); - - it("should be invalid due to invalid address", () => { - transaction - .recipientId(address) - .amount(amount) - .sign("passphrase"); - const struct = transaction.getStruct(); - struct.recipientId = "woop"; - expect(validator.validate(struct, validator.transfer()).error).not.toBeNull(); - }); - - it("should be invalid due to zero amount", () => { - transaction - .recipientId(address) - .amount(0) - .sign("passphrase"); - expect(validator.validate(transaction.getStruct(), validator.transfer()).error).not.toBeNull(); - }); - - it("should be invalid due to zero fee", () => { - transaction - .recipientId(address) - .amount(1) - .fee(0) - .sign("passphrase"); - expect(validator.validate(transaction.getStruct(), validator.transfer()).error).not.toBeNull(); - }); - - it("should be invalid due to wrong transaction type", () => { - transaction = transactionBuilder.delegateRegistration(); - transaction.usernameAsset("delegate_name").sign("passphrase"); - expect(validator.validate(transaction.getStruct(), validator.transfer()).error).not.toBeNull(); - }); - - it("should be valid due to missing network byte", () => { - transaction - .recipientId(address) - .amount(1) - .fee(1) - .sign("passphrase"); - - expect(validator.validate(transaction.getStruct(), validator.transfer()).error).toBeNull(); - }); - - it("should be valid due to correct network byte", () => { - transaction - .recipientId(address) - .amount(1) - .fee(1) - .network(configManager.get("pubKeyHash")) - .sign("passphrase"); - - expect(validator.validate(transaction.getStruct(), validator.transfer()).error).toBeNull(); - }); - - it("should be invalid due to wrong network byte", () => { - transaction - .recipientId(address) - .amount(1) - .fee(1) - .network(1) - .sign("passphrase"); - - expect(validator.validate(transaction.getStruct(), validator.transfer()).error).not.toBeNull(); - }); - - it("should be valid after a network change", () => { - configManager.setFromPreset("devnet"); - - let transfer = transaction - .recipientId(address) - .amount(1) - .fee(1) - .network(configManager.get("pubKeyHash")) - .sign("passphrase") - .build(); - - expect(transfer.data.network).toBe(30); - expect(validator.validate(transfer.data, validator.transfer()).error).toBeNull(); - - configManager.setFromPreset("mainnet"); - - transfer = transaction - .recipientId(address) - .amount(1) - .fee(1) - .network(configManager.get("pubKeyHash")) - .sign("passphrase") - .build(); - - expect(transfer.data.network).toBe(23); - expect(validator.validate(transfer.data, validator.transfer()).error).toBeNull(); - }); -}); diff --git a/packages/crypto/__tests__/validation/extensions/transactions/vote.test.ts b/packages/crypto/__tests__/validation/extensions/transactions/vote.test.ts deleted file mode 100644 index 2b9bd06f2b..0000000000 --- a/packages/crypto/__tests__/validation/extensions/transactions/vote.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -// tslint:disable:no-empty - -import Joi from "joi"; -import { constants, transactionBuilder } from "../../../../src"; -import { extensions } from "../../../../src/validation/extensions"; - -const validator = Joi.extend(extensions); - -const vote = "+02bcfa0951a92e7876db1fb71996a853b57f996972ed059a950d910f7d541706c9"; -const unvote = "-0326580718fc86ba609799ac95fcd2721af259beb5afa81bfce0ab7d9fe95de991"; -const votes = [vote, "+0310ad026647eed112d1a46145eed58b8c19c67c505a67f1199361a511ce7860c0", unvote]; -const invalidVotes = [ - "02bcfa0951a92e7876db1fb71996a853b57f996972ed059a950d910f7d541706c9", - "0310ad026647eed112d1a46145eed58b8c19c67c505a67f1199361a511ce7860c0", - "0326580718fc86ba609799ac95fcd2721af259beb5afa81bfce0ab7d9fe95de991", -]; - -let transaction; -beforeEach(() => { - transaction = transactionBuilder.vote(); -}); - -describe("Vote Transaction", () => { - it("should be valid with 1 vote", () => { - transaction - .votesAsset([vote]) - - .sign("passphrase"); - expect(validator.validate(transaction.getStruct(), validator.vote()).error).toBeNull(); - }); - - it("should be valid with 1 unvote", () => { - transaction.votesAsset([unvote]).sign("passphrase"); - - expect(validator.validate(transaction.getStruct(), validator.vote()).error).toBeNull(); - }); - - it("should be invalid due to no transaction as object", () => { - expect(validator.validate("test", validator.vote()).error).not.toBeNull(); - }); - - it("should be invalid due to non-zero amount", () => { - transaction - .votesAsset([vote]) - .amount(10 * constants.SATOSHI) - .sign("passphrase"); - - expect(validator.validate(transaction.getStruct(), validator.vote()).error).not.toBeNull(); - }); - - it("should be invalid due to zero fee", () => { - transaction - .votesAsset(votes) - .fee(0) - .sign("passphrase"); - - expect(validator.validate(transaction.getStruct(), validator.vote()).error).not.toBeNull(); - }); - - it("should be invalid due to no votes", () => { - transaction.votesAsset([]).sign("passphrase"); - - expect(validator.validate(transaction.getStruct(), validator.vote()).error).not.toBeNull(); - }); - - it("should be invalid due to more than 1 vote", () => { - transaction.votesAsset(votes).sign("passphrase"); - - expect(validator.validate(transaction.getStruct(), validator.vote()).error).not.toBeNull(); - }); - - it("should be invalid due to invalid votes", () => { - transaction.votesAsset(invalidVotes).sign("passphrase"); - - expect(validator.validate(transaction.getStruct(), validator.vote()).error).not.toBeNull(); - }); - - it("should be invalid due to wrong vote type", () => { - try { - transaction.votesAsset(vote).sign("passphrase"); - expect(validator.validate(transaction.getStruct(), validator.vote()).error).not.toBeNull(); - } catch (error) {} - }); - - it("should be invalid due to wrong transaction type", () => { - transaction = transactionBuilder.delegateRegistration(); - transaction.usernameAsset("delegate_name").sign("passphrase"); - - expect(validator.validate(transaction.getStruct(), validator.vote()).error).not.toBeNull(); - }); -}); diff --git a/packages/crypto/__tests__/validation/validator.test.ts b/packages/crypto/__tests__/validation/validator.test.ts deleted file mode 100644 index 6b068d146d..0000000000 --- a/packages/crypto/__tests__/validation/validator.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import "jest-extended"; -import Joi from "joi"; -import { Bignum } from "../../dist"; -import { Validator } from "../../src/validation/validator"; - -describe("Validator", () => { - describe("validate", () => { - it("should validate a simple number", async () => { - Validator.init(); - - const schema = { - a: Joi.number(), - }; - - const value = { - a: 123, - }; - - const result = await Validator.validate(value, schema); - expect(result).toEqual(value); - }); - - it("should validate using extended schemas", async () => { - Validator.init(); - - const schema = { - a: Validator.joi.bignumber(), - }; - - const value = { - a: new Bignum(12), - }; - - const result = await Validator.validate(value, schema); - expect(result).toEqual(value); - }); - - it("should return an error if an error was thrown", () => { - Validator.joi = { - validate: () => { - throw new Error("erreur"); - }, - }; - const result = Validator.validate("", ""); - expect(result.error).toBeDefined(); - }); - }); -}); diff --git a/packages/crypto/build/webpack.config.js b/packages/crypto/build/webpack.config.js index 12f79fc59d..2a88a1db3f 100644 --- a/packages/crypto/build/webpack.config.js +++ b/packages/crypto/build/webpack.config.js @@ -30,7 +30,7 @@ const browserConfig = { }, output: { ...format(pkg.browser), - library: "ArkEcosystemCrypto", + library: "ARKEcosystemCrypto", libraryTarget: "umd", umdNamedDefine: true, globalObject: "this", diff --git a/packages/crypto/package.json b/packages/crypto/package.json index 374fa98395..1754b3f51a 100644 --- a/packages/crypto/package.json +++ b/packages/crypto/package.json @@ -1,7 +1,7 @@ { "name": "@arkecosystem/crypto", - "description": "Crypto utilities for the Ark Blockchain", - "version": "2.2.1", + "description": "Crypto utilities for the ARK Blockchain", + "version": "2.3.15", "contributors": [ "François-Xavier Thoorens ", "Brian Faust ", @@ -19,40 +19,33 @@ "dist" ], "scripts": { - "publish:alpha": "npm publish --tag alpha", - "publish:beta": "npm publish --tag beta", - "publish:rc": "npm publish --tag rc", - "publish:latest": "npm publish --tag latest", "prepublishOnly": "yarn build", - "pretest": "yarn lint && yarn build", "compile": "../../node_modules/typescript/bin/tsc", "build": "yarn clean && tsc", "build:watch": "yarn clean && yarn compile -w", "clean": "del dist", - "lint": "../../node_modules/tslint/bin/tslint -c ../../tslint.json 'src/**/*.ts' '__tests__/**/*.ts' --fix", - "bundle": "rimraf dist && cross-env NODE_ENV=production webpack --config build/webpack.config.js", - "test": "cross-env CORE_ENV=test jest --runInBand --forceExit", - "test:coverage": "cross-env CORE_ENV=test jest --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$' --runInBand --forceExit", - "test:debug": "cross-env CORE_ENV=test node --inspect-brk ../../node_modules/.bin/jest --runInBand", - "test:watch": "jest --watch", - "test:watch:all": "jest --watchAll", - "updates": "../../node_modules/npm-check-updates/bin/npm-check-updates -a" + "bundle": "rimraf dist && cross-env NODE_ENV=production webpack --config build/webpack.config.js" }, "dependencies": { - "bignumber.js": "^8.0.2", + "@faustbrian/dato": "^0.2.0", + "ajv": "^6.10.0", + "ajv-keywords": "^3.4.0", + "bcrypto": "^3.0.2", + "bignumber.js": "^8.1.1", "bip32": "^1.0.2", "bip39": "^2.5.0", + "browserify-aes": "^1.2.0", "bs58check": "^2.1.2", + "buffer-xor": "^2.0.2", "bytebuffer": "^5.0.1", "create-hash": "^1.2.0", - "dayjs-ext": "^2.2.0", - "deepmerge": "^3.1.0", + "deepmerge": "^3.2.0", "joi": "^14.3.1", "lodash.camelcase": "^4.3.0", "lodash.get": "^4.4.2", "lodash.set": "^4.3.2", "lodash.sumby": "^4.6.0", - "node-forge": "^0.8.0", + "node-forge": "^0.8.1", "otplib": "^10.0.1", "pluralize": "^7.0.0", "secp256k1": "^3.6.2", @@ -61,14 +54,14 @@ }, "devDependencies": { "@types/bip32": "^1.0.1", - "@types/bip39": "^2.4.1", + "@types/bip39": "^2.4.2", "@types/bytebuffer": "^5.0.40", "@types/create-hash": "^1.2.0", - "@types/joi": "^14.3.1", - "@types/lodash.camelcase": "^4.3.4", - "@types/lodash.get": "^4.4.4", - "@types/lodash.set": "^4.3.4", - "@types/lodash.sumby": "^4.6.4", + "@types/joi": "^14.3.2", + "@types/lodash.camelcase": "^4.3.6", + "@types/lodash.get": "^4.4.6", + "@types/lodash.set": "^4.3.6", + "@types/lodash.sumby": "^4.6.6", "@types/node-forge": "^0.7.11", "@types/otplib": "^7.0.0", "@types/pluralize": "^0.0.29", @@ -82,8 +75,5 @@ }, "publishConfig": { "access": "public" - }, - "jest": { - "preset": "../../jest-preset.json" } } diff --git a/packages/crypto/src/builder/transactions/delegate-registration.ts b/packages/crypto/src/builder/transactions/delegate-registration.ts index 3f86750902..6358f9af7b 100644 --- a/packages/crypto/src/builder/transactions/delegate-registration.ts +++ b/packages/crypto/src/builder/transactions/delegate-registration.ts @@ -1,7 +1,6 @@ import { TransactionTypes } from "../../constants"; -import { crypto } from "../../crypto"; import { feeManager } from "../../managers"; -import { ITransactionAsset, ITransactionData } from "../../models"; +import { ITransactionAsset, ITransactionData } from "../../transactions"; import { TransactionBuilder } from "./transaction"; export class DelegateRegistrationBuilder extends TransactionBuilder { diff --git a/packages/crypto/src/builder/transactions/delegate-resignation.ts b/packages/crypto/src/builder/transactions/delegate-resignation.ts index 7aef76cdf2..f19c4394b3 100644 --- a/packages/crypto/src/builder/transactions/delegate-resignation.ts +++ b/packages/crypto/src/builder/transactions/delegate-resignation.ts @@ -1,6 +1,6 @@ import { TransactionTypes } from "../../constants"; import { feeManager } from "../../managers"; -import { ITransactionData } from "../../models"; +import { ITransactionData } from "../../transactions"; import { TransactionBuilder } from "./transaction"; export class DelegateResignationBuilder extends TransactionBuilder { diff --git a/packages/crypto/src/builder/transactions/ipfs.ts b/packages/crypto/src/builder/transactions/ipfs.ts index 6c52f36557..efd1f51fc6 100644 --- a/packages/crypto/src/builder/transactions/ipfs.ts +++ b/packages/crypto/src/builder/transactions/ipfs.ts @@ -1,6 +1,6 @@ import { TransactionTypes } from "../../constants"; import { feeManager } from "../../managers"; -import { ITransactionData } from "../../models"; +import { ITransactionData } from "../../transactions"; import { TransactionBuilder } from "./transaction"; export class IPFSBuilder extends TransactionBuilder { diff --git a/packages/crypto/src/builder/transactions/multi-payment.ts b/packages/crypto/src/builder/transactions/multi-payment.ts index 46479a263c..5f9f3192e6 100644 --- a/packages/crypto/src/builder/transactions/multi-payment.ts +++ b/packages/crypto/src/builder/transactions/multi-payment.ts @@ -1,7 +1,7 @@ import { TransactionTypes } from "../../constants"; import { MaximumPaymentCountExceededError } from "../../errors"; import { feeManager } from "../../managers"; -import { ITransactionData } from "../../models"; +import { ITransactionData } from "../../transactions"; import { Bignum } from "../../utils"; import { TransactionBuilder } from "./transaction"; diff --git a/packages/crypto/src/builder/transactions/multi-signature.ts b/packages/crypto/src/builder/transactions/multi-signature.ts index 61ca5bd3ea..3c35e5bc46 100644 --- a/packages/crypto/src/builder/transactions/multi-signature.ts +++ b/packages/crypto/src/builder/transactions/multi-signature.ts @@ -1,6 +1,6 @@ import { TransactionTypes } from "../../constants"; import { feeManager } from "../../managers"; -import { IMultiSignatureAsset, ITransactionAsset, ITransactionData } from "../../models"; +import { IMultiSignatureAsset, ITransactionAsset, ITransactionData } from "../../transactions"; import { TransactionBuilder } from "./transaction"; export class MultiSignatureBuilder extends TransactionBuilder { diff --git a/packages/crypto/src/builder/transactions/second-signature.ts b/packages/crypto/src/builder/transactions/second-signature.ts index e47f67a5db..e32cfc74f8 100644 --- a/packages/crypto/src/builder/transactions/second-signature.ts +++ b/packages/crypto/src/builder/transactions/second-signature.ts @@ -1,7 +1,7 @@ import { TransactionTypes } from "../../constants"; import { crypto } from "../../crypto"; import { feeManager } from "../../managers"; -import { ITransactionAsset, ITransactionData } from "../../models"; +import { ITransactionAsset, ITransactionData } from "../../transactions"; import { TransactionBuilder } from "./transaction"; export class SecondSignatureBuilder extends TransactionBuilder { diff --git a/packages/crypto/src/builder/transactions/timelock-transfer.ts b/packages/crypto/src/builder/transactions/timelock-transfer.ts index 9e72b9ae51..2d23c0e3a4 100644 --- a/packages/crypto/src/builder/transactions/timelock-transfer.ts +++ b/packages/crypto/src/builder/transactions/timelock-transfer.ts @@ -1,6 +1,6 @@ import { TransactionTypes } from "../../constants"; import { feeManager } from "../../managers"; -import { ITransactionData } from "../../models"; +import { ITransactionData } from "../../transactions"; import { TransactionBuilder } from "./transaction"; export class TimelockTransferBuilder extends TransactionBuilder { diff --git a/packages/crypto/src/builder/transactions/transaction.ts b/packages/crypto/src/builder/transactions/transaction.ts index 660bd7a15b..03a2670f53 100644 --- a/packages/crypto/src/builder/transactions/transaction.ts +++ b/packages/crypto/src/builder/transactions/transaction.ts @@ -1,9 +1,9 @@ import { crypto, slots } from "../../crypto"; import { MissingTransactionSignatureError } from "../../errors"; import { configManager } from "../../managers"; -import { ITransactionData, Transaction } from "../../models"; import { INetwork } from "../../networks"; -import { Bignum } from "../../utils"; +import { ITransactionData, Transaction } from "../../transactions"; +import { Bignum, maxVendorFieldLength } from "../../utils"; export abstract class TransactionBuilder> { public data: ITransactionData; @@ -22,7 +22,7 @@ export abstract class TransactionBuilder = {}): Transaction { - return new Transaction({ ...this.data, ...data }); + return Transaction.fromData({ ...this.data, ...data }, false); } /** @@ -80,7 +80,7 @@ export abstract class TransactionBuilder { diff --git a/packages/crypto/src/builder/transactions/vote.ts b/packages/crypto/src/builder/transactions/vote.ts index e41fcbd3da..7814abf277 100644 --- a/packages/crypto/src/builder/transactions/vote.ts +++ b/packages/crypto/src/builder/transactions/vote.ts @@ -1,6 +1,6 @@ import { TransactionTypes } from "../../constants"; import { feeManager } from "../../managers"; -import { ITransactionData } from "../../models"; +import { ITransactionData } from "../../transactions"; import { TransactionBuilder } from "./transaction"; export class VoteBuilder extends TransactionBuilder { diff --git a/packages/crypto/src/crypto/crypto.ts b/packages/crypto/src/crypto/crypto.ts index 52313eeab0..7ae3fd3e5f 100644 --- a/packages/crypto/src/crypto/crypto.ts +++ b/packages/crypto/src/crypto/crypto.ts @@ -1,19 +1,10 @@ -/* tslint:disable:no-shadowed-variable */ - -import bs58check from "bs58check"; -import ByteBuffer from "bytebuffer"; import secp256k1 from "secp256k1"; - -import { TransactionVersionError } from "../errors"; import { Address, KeyPair, Keys, PublicKey, WIF } from "../identities"; -import { configManager } from "../managers"; -import { feeManager } from "../managers"; -import { ITransactionData } from "../models"; -import { Bignum } from "../utils"; +import { configManager, feeManager } from "../managers"; +import { ITransactionData } from "../transactions"; +import { ISerializeOptions, TransactionSerializer } from "../transactions/serializers/transaction"; import { HashAlgorithms } from "./hash-algorithms"; -const { transactionIdFixTable } = configManager.getPreset("mainnet").exceptions; - class Crypto { /** * Get transaction fee. @@ -23,180 +14,26 @@ class Crypto { } /** - * Get the byte representation of the transaction. + * Get transaction id. */ - public getBytes( - transaction: ITransactionData, - skipSignature: boolean = false, - skipSecondSignature: boolean = false, - ): Buffer { - if (transaction.version && transaction.version !== 1) { - throw new TransactionVersionError(transaction.version); - } - - let assetSize = 0; - let assetBytes = null; - - switch (transaction.type) { - case 1: { - // Signature - const { signature } = transaction.asset; - const bb = new ByteBuffer(33, true); - const publicKeyBuffer = Buffer.from(signature.publicKey, "hex"); - - for (const byte of publicKeyBuffer) { - bb.writeByte(byte); - } - - bb.flip(); - - assetBytes = new Uint8Array(bb.toArrayBuffer()); - assetSize = assetBytes.length; - break; - } - - case 2: { - // Delegate - assetBytes = Buffer.from(transaction.asset.delegate.username, "utf8"); - assetSize = assetBytes.length; - break; - } - - case 3: { - // Vote - if (transaction.asset.votes !== null) { - assetBytes = Buffer.from(transaction.asset.votes.join(""), "utf8"); - assetSize = assetBytes.length; - } - break; - } - - case 4: { - // Multi-Signature - const keysgroupBuffer = Buffer.from(transaction.asset.multisignature.keysgroup.join(""), "utf8"); - const bb = new ByteBuffer(1 + 1 + keysgroupBuffer.length, true); - - bb.writeByte(transaction.asset.multisignature.min); - bb.writeByte(transaction.asset.multisignature.lifetime); - - for (const byte of keysgroupBuffer) { - bb.writeByte(byte); - } - - bb.flip(); - - assetBytes = bb.toBuffer(); - assetSize = assetBytes.length; - break; - } - } - - const bb = new ByteBuffer(1 + 4 + 32 + 8 + 8 + 21 + 64 + 64 + 64 + assetSize, true); - bb.writeByte(transaction.type); - bb.writeInt(transaction.timestamp); - - const senderPublicKeyBuffer = Buffer.from(transaction.senderPublicKey, "hex"); - for (const byte of senderPublicKeyBuffer) { - bb.writeByte(byte); - } + public getId(transaction: ITransactionData): string { + const id = this.getHash(transaction).toString("hex"); // Apply fix for broken type 1 and 4 transactions, which were // erroneously calculated with a recipient id. - const isBrokenTransaction = Object.values(transactionIdFixTable).includes(transaction.id); - const correctType = transaction.type !== 1 && transaction.type !== 4; - if (transaction.recipientId && (isBrokenTransaction || correctType)) { - const recipient = bs58check.decode(transaction.recipientId); - for (const byte of recipient) { - bb.writeByte(byte); - } - } else { - for (let i = 0; i < 21; i++) { - bb.writeByte(0); - } - } - - if (transaction.vendorFieldHex) { - const vf = Buffer.from(transaction.vendorFieldHex, "hex"); - const fillstart = vf.length; - for (let i = 0; i < fillstart; i++) { - bb.writeByte(vf[i]); - } - for (let i = fillstart; i < 64; i++) { - bb.writeByte(0); - } - } else if (transaction.vendorField) { - const vf = Buffer.from(transaction.vendorField); - const fillstart = vf.length; - for (let i = 0; i < fillstart; i++) { - bb.writeByte(vf[i]); - } - for (let i = fillstart; i < 64; i++) { - bb.writeByte(0); - } - } else { - for (let i = 0; i < 64; i++) { - bb.writeByte(0); - } - } - - bb.writeInt64(+new Bignum(transaction.amount).toFixed()); - bb.writeInt64(+new Bignum(transaction.fee).toFixed()); - - if (assetSize > 0) { - for (let i = 0; i < assetSize; i++) { - bb.writeByte(assetBytes[i]); - } - } - - if (!skipSignature && transaction.signature) { - const signatureBuffer = Buffer.from(transaction.signature, "hex"); - for (const byte of signatureBuffer) { - bb.writeByte(byte); - } - } - - if (!skipSecondSignature && transaction.signSignature) { - const signSignatureBuffer = Buffer.from(transaction.signSignature, "hex"); - for (const byte of signSignatureBuffer) { - bb.writeByte(byte); - } - } - - bb.flip(); - const arrayBuffer = new Uint8Array(bb.toArrayBuffer()); - const buffer = []; - - for (let i = 0; i < arrayBuffer.length; i++) { - buffer[i] = arrayBuffer[i]; - } - - return Buffer.from(buffer); - } - - /** - * Get transaction id. - */ - public getId(transaction: ITransactionData): string { - if (transaction.version && transaction.version !== 1) { - throw new TransactionVersionError(transaction.version); + const { transactionIdFixTable } = configManager.get("exceptions"); + if (transactionIdFixTable && transactionIdFixTable[id]) { + return transactionIdFixTable[id]; } - return this.getHash(transaction).toString("hex"); + return id; } /** * Get transaction hash. */ - public getHash( - transaction: ITransactionData, - skipSignature: boolean = false, - skipSecondSignature: boolean = false, - ): Buffer { - if (transaction.version && transaction.version !== 1) { - throw new TransactionVersionError(transaction.version); - } - - const bytes = this.getBytes(transaction, skipSignature, skipSecondSignature); + public getHash(transaction: ITransactionData, options?: ISerializeOptions): Buffer { + const bytes = TransactionSerializer.getBytes(transaction, options); return HashAlgorithms.sha256(bytes); } @@ -204,13 +41,7 @@ class Crypto { * Sign transaction. */ public sign(transaction: ITransactionData, keys: KeyPair): string { - let hash; - if (!transaction.version || transaction.version === 1) { - hash = this.getHash(transaction, true, true); - } else { - hash = this.getHash(transaction, false, false); - } - + const hash = this.getHash(transaction, { excludeSignature: true, excludeSecondSignature: true }); const signature = this.signHash(hash, keys); if (!transaction.signature) { @@ -224,7 +55,7 @@ class Crypto { * Sign transaction with second signature. */ public secondSign(transaction: ITransactionData, keys: KeyPair): string { - const hash = this.getHash(transaction, false, true); + const hash = this.getHash(transaction, { excludeSecondSignature: true }); const signature = this.signHash(hash, keys); if (!transaction.secondSignature) { @@ -255,7 +86,7 @@ class Crypto { return false; } - const hash = this.getHash(transaction, true, true); + const hash = this.getHash(transaction, { excludeSignature: true, excludeSecondSignature: true }); return this.verifyHash(hash, transaction.signature, transaction.senderPublicKey); } @@ -263,20 +94,14 @@ class Crypto { * Verify second signature for transaction. */ public verifySecondSignature(transaction: ITransactionData, publicKey: string): boolean { - let hash; - let secondSignature; - if (transaction.version && transaction.version !== 1) { - hash = this.getHash(transaction); - secondSignature = transaction.secondSignature; - } else { - hash = this.getHash(transaction, false, true); - secondSignature = transaction.signSignature; - } - + // tslint:disable-next-line:prefer-const + let { secondSignature, signSignature } = transaction; + secondSignature = secondSignature || signSignature; if (!secondSignature) { return false; } + const hash = this.getHash(transaction, { excludeSecondSignature: true }); return this.verifyHash(hash, secondSignature, publicKey); } diff --git a/packages/crypto/src/crypto/hash-algorithms.ts b/packages/crypto/src/crypto/hash-algorithms.ts index 80417db8ba..e83df256a3 100644 --- a/packages/crypto/src/crypto/hash-algorithms.ts +++ b/packages/crypto/src/crypto/hash-algorithms.ts @@ -1,22 +1,18 @@ -import createHash from "create-hash"; +import { Hash160, Hash256, RIPEMD160, SHA1, SHA256 } from "bcrypto"; export class HashAlgorithms { /** * Create a "ripemd160" buffer. */ public static ripemd160(buffer: Buffer | string): Buffer { - return createHash("rmd160") - .update(buffer) - .digest(); + return RIPEMD160.digest(this.bufferize(buffer)); } /** * Create a "sha1" buffer. */ public static sha1(buffer: Buffer | string): Buffer { - return createHash("sha1") - .update(buffer) - .digest(); + return SHA1.digest(this.bufferize(buffer)); } /** @@ -24,23 +20,34 @@ export class HashAlgorithms { * @param {Buffer} buffer * @return {Buffer} */ - public static sha256(buffer: Buffer | string): Buffer { - return createHash("sha256") - .update(buffer) - .digest(); + public static sha256(buffer: Buffer | string | Buffer[]): Buffer { + if (Array.isArray(buffer)) { + let sha256 = SHA256.ctx; + sha256.init(); + buffer.forEach(element => { + sha256 = sha256.update(element); + }); + + return sha256.final(); + } + + return SHA256.digest(this.bufferize(buffer)); } /** * Create a "hash160" buffer. */ public static hash160(buffer: Buffer | string): Buffer { - return this.ripemd160(this.sha256(buffer)); + return Hash160.digest(this.bufferize(buffer)); } /** * Create a "hash256" buffer. */ public static hash256(buffer: Buffer | string): Buffer { - return this.sha256(this.sha256(buffer)); + return Hash256.digest(this.bufferize(buffer)); } + + private static bufferize = (buffer: Buffer | string) => + (buffer = buffer instanceof Buffer ? buffer : Buffer.from(buffer)); } diff --git a/packages/crypto/src/crypto/slots.ts b/packages/crypto/src/crypto/slots.ts index c7675a8782..f63559062d 100644 --- a/packages/crypto/src/crypto/slots.ts +++ b/packages/crypto/src/crypto/slots.ts @@ -1,4 +1,4 @@ -import dayjs from "dayjs-ext"; +import { dato, Dato } from "@faustbrian/dato"; import { configManager } from "../managers"; class Slots { @@ -36,10 +36,10 @@ class Slots { */ public getEpochTime(time?: number): number { if (time === undefined) { - time = dayjs().valueOf(); + time = dato().toMilliseconds(); } - const start = this.beginEpochTime().valueOf(); + const start = this.beginEpochTime().toMilliseconds(); return Math.floor((time - start) / 1000); } @@ -47,8 +47,8 @@ class Slots { /** * Get beginning epoch time. */ - public beginEpochTime(): dayjs.Dayjs { - return dayjs(this.getMilestone("epoch")).utc(); + public beginEpochTime(): Dato { + return dato(this.getMilestone("epoch")); } /** @@ -66,7 +66,7 @@ class Slots { epochTime = this.getTime(); } - const start = Math.floor(this.beginEpochTime().valueOf() / 1000) * 1000; + const start = Math.floor(this.beginEpochTime().toMilliseconds() / 1000) * 1000; return start + epochTime * 1000; } @@ -105,13 +105,6 @@ class Slots { return this.getSlotNumber() + 1; } - /** - * Get the last slot number. - */ - public getLastSlot(nextSlot: number): number { - return nextSlot + this.getMilestone("activeDelegates"); - } - /** * Checks if forging is allowed */ diff --git a/packages/crypto/src/deserializers/block.ts b/packages/crypto/src/deserializers/block.ts deleted file mode 100644 index b5dc02ce5c..0000000000 --- a/packages/crypto/src/deserializers/block.ts +++ /dev/null @@ -1,76 +0,0 @@ -import ByteBuffer from "bytebuffer"; -import { configManager } from "../managers"; -import { Transaction } from "../models"; -import { Block, IBlockData } from "../models/block"; -import { Bignum } from "../utils"; - -const { outlookTable } = configManager.getPreset("mainnet").exceptions; - -class BlockDeserializer { - public deserialize(serializedHex: string, headerOnly: boolean = false): IBlockData { - const block = {} as IBlockData; - const buf = ByteBuffer.fromHex(serializedHex, true); - - this.deserializeHeader(block, buf); - - headerOnly = headerOnly || buf.remaining() === 0; - if (!headerOnly) { - this.deserializeTransactions(block, buf); - } - - block.idHex = Block.getIdHex(block); - block.id = new Bignum(block.idHex, 16).toFixed(); - - if (outlookTable[block.id]) { - block.id = outlookTable[block.id]; - block.idHex = Block.toBytesHex(block.id); - } - - return block; - } - - private deserializeHeader(block: IBlockData, buf: ByteBuffer): void { - block.version = buf.readUint32(); - block.timestamp = buf.readUint32(); - block.height = buf.readUint32(); - block.previousBlockHex = buf.readBytes(8).toString("hex"); - block.previousBlock = new Bignum(block.previousBlockHex, 16).toFixed(); - block.numberOfTransactions = buf.readUint32(); - block.totalAmount = new Bignum(buf.readUint64().toString()); - block.totalFee = new Bignum(buf.readUint64().toString()); - block.reward = new Bignum(buf.readUint64().toString()); - block.payloadLength = buf.readUint32(); - block.payloadHash = buf.readBytes(32).toString("hex"); - block.generatorPublicKey = buf.readBytes(33).toString("hex"); - - const signatureLength = (): number => { - buf.mark(); - const lengthHex = buf - .skip(1) - .readBytes(1) - .toString("hex"); - buf.reset(); - - return parseInt(lengthHex, 16) + 2; - }; - - block.blockSignature = buf.readBytes(signatureLength()).toString("hex"); - } - - private deserializeTransactions(block: IBlockData, buf: ByteBuffer): any { - const transactionLengths = []; - - for (let i = 0; i < block.numberOfTransactions; i++) { - transactionLengths.push(buf.readUint32()); - } - - block.transactions = []; - transactionLengths.forEach(length => { - const serializedHex = buf.readBytes(length).toString("hex"); - const transaction = new Transaction(serializedHex); - block.transactions.push(transaction); - }); - } -} - -export const blockDeserializer = new BlockDeserializer(); diff --git a/packages/crypto/src/deserializers/transaction.ts b/packages/crypto/src/deserializers/transaction.ts deleted file mode 100644 index 49e5ff22eb..0000000000 --- a/packages/crypto/src/deserializers/transaction.ts +++ /dev/null @@ -1,238 +0,0 @@ -import bs58check from "bs58check"; -import ByteBuffer from "bytebuffer"; -import { TransactionTypes } from "../constants"; -import { crypto } from "../crypto"; -import { TransactionTypeError } from "../errors"; -import { configManager } from "../managers"; -import { Transaction } from "../models"; -import { IMultiSignatureAsset, ITransactionData } from "../models/transaction"; -import { Bignum } from "../utils"; - -const { transactionIdFixTable } = configManager.getPreset("mainnet").exceptions; - -// Reference: https://github.com/ArkEcosystem/AIPs/blob/master/AIPS/aip-11.md -class TransactionDeserializer { - public deserialize(serializedHex: string): ITransactionData { - const transaction = {} as ITransactionData; - const buf = ByteBuffer.fromHex(serializedHex, true); - - this.deserializeCommon(transaction, buf); - this.deserializeVendorField(transaction, buf); - this.deserializeType(transaction, buf); - this.deserializeSignatures(transaction, buf); - this.applyV1Compatibility(transaction); - - return transaction; - } - - private deserializeCommon(transaction: ITransactionData, buf: ByteBuffer): void { - buf.skip(1); // Skip 0xFF marker - transaction.version = buf.readUint8(); - transaction.network = buf.readUint8(); - transaction.type = buf.readUint8(); - transaction.timestamp = buf.readUint32(); - transaction.senderPublicKey = buf.readBytes(33).toString("hex"); // serializedHex.substring(16, 16 + 33 * 2); - transaction.fee = new Bignum(buf.readUint64().toString()); - transaction.amount = Bignum.ZERO; - } - - private deserializeVendorField(transaction: ITransactionData, buf: ByteBuffer): void { - if (!Transaction.canHaveVendorField(transaction.type)) { - buf.skip(1); - return; - } - - const vendorFieldLength = buf.readUint8(); - if (vendorFieldLength > 0) { - transaction.vendorFieldHex = buf.readBytes(vendorFieldLength).toString("hex"); - } - } - - private deserializeType(transaction: ITransactionData, buf: ByteBuffer): void { - if (transaction.type === TransactionTypes.Transfer) { - this.deserializeTransfer(transaction, buf); - } else if (transaction.type === TransactionTypes.SecondSignature) { - this.deserializeSecondSignature(transaction, buf); - } else if (transaction.type === TransactionTypes.DelegateRegistration) { - this.deserializeDelegateRegistration(transaction, buf); - } else if (transaction.type === TransactionTypes.Vote) { - this.deserializeVote(transaction, buf); - } else if (transaction.type === TransactionTypes.MultiSignature) { - this.deserializeMultiSignature(transaction, buf); - } else if (transaction.type === TransactionTypes.Ipfs) { - this.deserializeIpfs(transaction, buf); - } else if (transaction.type === TransactionTypes.TimelockTransfer) { - this.deserializeTimelockTransfer(transaction, buf); - } else if (transaction.type === TransactionTypes.MultiPayment) { - this.deserializeMultiPayment(transaction, buf); - } else if (transaction.type === TransactionTypes.DelegateResignation) { - this.deserializeDelegateResignation(transaction, buf); - } else { - throw new TransactionTypeError(transaction.type); - } - } - - private deserializeTransfer(transaction: ITransactionData, buf: ByteBuffer): void { - transaction.amount = new Bignum(buf.readUint64().toString()); - transaction.expiration = buf.readUint32(); - transaction.recipientId = bs58check.encode(buf.readBytes(21).toBuffer()); - } - - private deserializeSecondSignature(transaction: ITransactionData, buf: ByteBuffer): void { - transaction.asset = { - signature: { - publicKey: buf.readBytes(33).toString("hex"), - }, - }; - } - - private deserializeDelegateRegistration(transaction: ITransactionData, buf: ByteBuffer): void { - const usernamelength = buf.readUint8(); - - transaction.asset = { - delegate: { - username: buf.readString(usernamelength), - }, - }; - } - - private deserializeVote(transaction: ITransactionData, buf: ByteBuffer): void { - const votelength = buf.readUint8(); - transaction.asset = { votes: [] }; - - for (let i = 0; i < votelength; i++) { - let vote = buf.readBytes(34).toString("hex"); - vote = (vote[1] === "1" ? "+" : "-") + vote.slice(2); - transaction.asset.votes.push(vote); - } - } - - private deserializeMultiSignature(transaction: ITransactionData, buf: ByteBuffer): void { - transaction.asset = { multisignature: { keysgroup: [] } as IMultiSignatureAsset }; - transaction.asset.multisignature.min = buf.readUint8(); - - const num = buf.readUint8(); - transaction.asset.multisignature.lifetime = buf.readUint8(); - - for (let index = 0; index < num; index++) { - const key = buf.readBytes(33).toString("hex"); - transaction.asset.multisignature.keysgroup.push(key); - } - } - - private deserializeIpfs(transaction: ITransactionData, buf: ByteBuffer): void { - const dagLength = buf.readUint8(); - transaction.asset = { - ipfs: { - dag: buf.readBytes(dagLength).toString("hex"), - }, - }; - } - - private deserializeTimelockTransfer(transaction: ITransactionData, buf: ByteBuffer): void { - transaction.amount = new Bignum(buf.readUint64().toString()); - transaction.timelockType = buf.readUint8(); - transaction.timelock = buf.readUint64().toNumber(); - transaction.recipientId = bs58check.encode(buf.readBytes(21).toBuffer()); - } - - private deserializeMultiPayment(transaction: ITransactionData, buf: ByteBuffer): void { - const payments = []; - const total = buf.readUint32(); - - for (let j = 0; j < total; j++) { - const payment: any = {}; - payment.amount = new Bignum(buf.readUint64().toString()); - payment.recipientId = bs58check.encode(buf.readBytes(21).toBuffer()); - payments.push(payment); - } - - transaction.amount = payments.reduce((a, p) => a.plus(p.amount), Bignum.ZERO); - transaction.asset = { payments }; - } - - private deserializeDelegateResignation(transaction: ITransactionData, buf: ByteBuffer): void { - return; - } - - private deserializeSignatures(transaction: ITransactionData, buf: ByteBuffer) { - const currentSignatureLength = (): number => { - buf.mark(); - const lengthHex = buf - .skip(1) - .readBytes(1) - .toString("hex"); - buf.reset(); - - return parseInt(lengthHex, 16) + 2; - }; - - // Signature - if (buf.remaining()) { - const signatureLength = currentSignatureLength(); - transaction.signature = buf.readBytes(signatureLength).toString("hex"); - } - - const beginningMultiSignature = () => { - buf.mark(); - const marker = buf.readUint8(); - buf.reset(); - return marker === 255; - }; - - // Second Signature - if (buf.remaining() && !beginningMultiSignature()) { - const secondSignatureLength = currentSignatureLength(); - transaction.secondSignature = buf.readBytes(secondSignatureLength).toString("hex"); - } - - // Multi Signatures - if (buf.remaining() && beginningMultiSignature()) { - buf.skip(1); - transaction.signatures = []; - - while (buf.remaining()) { - const multiSignatureLength = currentSignatureLength(); - const multiSignature = buf.readBytes(multiSignatureLength).toString("hex"); - transaction.signatures.push(multiSignature); - } - } - } - - private applyV1Compatibility(transaction: ITransactionData): void { - if (transaction.version && transaction.version !== 1) { - return; - } - - if (transaction.secondSignature) { - transaction.signSignature = transaction.secondSignature; - } - - if (transaction.type === TransactionTypes.Vote) { - transaction.recipientId = crypto.getAddress(transaction.senderPublicKey, transaction.network); - } else if (transaction.type === TransactionTypes.MultiSignature) { - transaction.asset.multisignature.keysgroup = transaction.asset.multisignature.keysgroup.map(k => `+${k}`); - } - - if (transaction.vendorFieldHex) { - transaction.vendorField = Buffer.from(transaction.vendorFieldHex, "hex").toString("utf8"); - } - - if ( - transaction.type === TransactionTypes.SecondSignature || - transaction.type === TransactionTypes.MultiSignature - ) { - transaction.recipientId = crypto.getAddress(transaction.senderPublicKey, transaction.network); - } - - transaction.id = crypto.getId(transaction); - - // Apply fix for broken type 1 and 4 transactions, which were - // erroneously calculated with a recipient id. - if (transactionIdFixTable[transaction.id]) { - transaction.id = transactionIdFixTable[transaction.id]; - } - } -} - -export const transactionDeserializer = new TransactionDeserializer(); diff --git a/packages/crypto/src/errors.ts b/packages/crypto/src/errors.ts index 0ac47992ee..99d500bbac 100644 --- a/packages/crypto/src/errors.ts +++ b/packages/crypto/src/errors.ts @@ -48,6 +48,12 @@ export class NetworkVersionError extends CryptoError { } } +export class NotImplementedError extends CryptoError { + constructor() { + super(`Feature is not available.`); + } +} + export class PrivateKeyLengthError extends CryptoError { constructor(expected: string | number, given: string | number) { super(`Expected length to be ${expected}, but got ${given}.`); @@ -66,12 +72,48 @@ export class TransactionTypeError extends CryptoError { } } +export class MalformedTransactionBytesError extends CryptoError { + constructor() { + super(`Failed to deserialize transaction, because the bytes are malformed.`); + } +} + +export class TransactionSchemaError extends CryptoError { + constructor(what: string) { + super(what); + } +} + export class TransactionVersionError extends CryptoError { constructor(given: number) { super(`Version ${given} not supported.`); } } +export class UnkownTransactionError extends CryptoError { + constructor(given: number) { + super(`Transaction type ${given} is not registered.`); + } +} + +export class TransactionAlreadyRegisteredError extends CryptoError { + constructor(name: string) { + super(`Transaction type ${name} is already registered.`); + } +} + +export class TransactionTypeInvalidRangeError extends CryptoError { + constructor(given: number) { + super(`Custom transaction type must be in the range 100-255 (${given}).`); + } +} + +export class MissingMilestoneFeeError extends CryptoError { + constructor(name: string) { + super(`Missing milestone fee for '${name}'.`); + } +} + export class MaximumPaymentCountExceededError extends CryptoError { constructor(given: number) { super(`Expected a maximum of 2258 payments, but got ${given}.`); @@ -83,3 +125,25 @@ export class MissingTransactionSignatureError extends CryptoError { super(`Expected the transaction to be signed.`); } } + +export class BlockSchemaError extends CryptoError { + constructor(what: string) { + super(what); + } +} + +export class PreviousBlockIdFormatError extends CryptoError { + constructor(thisBlockHeight: number, previousBlockId: string) { + super( + `The config denotes that the block at height ${thisBlockHeight - 1} ` + + `must use full SHA256 block id, but the next block (at ${thisBlockHeight}) ` + + `contains previous block id "${previousBlockId}"`, + ); + } +} + +export class InvalidMilestoneConfigurationError extends CryptoError { + constructor(message: string) { + super(message); + } +} diff --git a/packages/crypto/src/handlers/transactions/delegate-registration.ts b/packages/crypto/src/handlers/transactions/delegate-registration.ts deleted file mode 100644 index 1b63937696..0000000000 --- a/packages/crypto/src/handlers/transactions/delegate-registration.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { ITransactionData, Wallet } from "../../models"; -import { Handler } from "./handler"; - -export class DelegateRegistrationHandler extends Handler { - /** - * Check if the transaction can be applied to the wallet. - */ - public canApply(wallet: Wallet, transaction: ITransactionData, errors: string[]): boolean { - if (!super.canApply(wallet, transaction, errors)) { - return false; - } - - const username = transaction.asset.delegate.username; - // TODO: Checking whether the username is a lowercase version of itself seems silly. Why can't we mutate it to lowercase - const canApply = !wallet.username && username && username === username.toLowerCase(); - if (!canApply) { - errors.push("Wallet already has a registered username"); - } - return canApply; - } - - /** - * Apply the transaction to the wallet. - */ - protected apply(wallet: Wallet, transaction: ITransactionData): void { - wallet.username = transaction.asset.delegate.username; - } - - /** - * Revert the transaction from the wallet. - */ - protected revert(wallet: Wallet, transaction: ITransactionData): void { - wallet.username = null; - } -} diff --git a/packages/crypto/src/handlers/transactions/delegate-resignation.ts b/packages/crypto/src/handlers/transactions/delegate-resignation.ts deleted file mode 100644 index 01daeda39e..0000000000 --- a/packages/crypto/src/handlers/transactions/delegate-resignation.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { ITransactionData, Wallet } from "../../models"; -import { Handler } from "./handler"; - -export class DelegateResignationHandler extends Handler { - /** - * Check if the transaction can be applied to the wallet. - */ - public canApply(wallet: Wallet, transaction: ITransactionData, errors: string[]): boolean { - if (!super.canApply(wallet, transaction, errors)) { - return false; - } - - const canApply = !!wallet.username; - if (!canApply) { - errors.push("Wallet has not registered a username"); - } - return canApply; - } - - /** - * Apply the transaction to the wallet. - */ - protected apply(wallet: Wallet, transaction: ITransactionData): void { - // - } - - /** - * Revert the transaction from the wallet. - */ - protected revert(wallet: Wallet, transaction: ITransactionData): void { - // - } -} diff --git a/packages/crypto/src/handlers/transactions/handler.ts b/packages/crypto/src/handlers/transactions/handler.ts deleted file mode 100644 index 18dc8f6e48..0000000000 --- a/packages/crypto/src/handlers/transactions/handler.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { crypto } from "../../crypto"; -import { configManager } from "../../managers"; -import { ITransactionData, Wallet } from "../../models"; -import { transactionValidator } from "../../validation"; - -export abstract class Handler { - /** - * Check if the transaction can be applied to the wallet. - */ - public canApply(wallet: Wallet, transaction: ITransactionData, errors: string[]): boolean { - const validationResult = transactionValidator.validate(transaction); - - if (validationResult.fails) { - errors.push(validationResult.fails.message); - return false; - } - - if (wallet.multisignature) { - return false; - } - - if ( - wallet.balance - .minus(transaction.amount) - .minus(transaction.fee) - .isLessThan(0) - ) { - errors.push("Insufficient balance in the wallet"); - return false; - } - - if (!(transaction.senderPublicKey.toLowerCase() === wallet.publicKey.toLowerCase())) { - errors.push('wallet "publicKey" does not match transaction "senderPublicKey"'); - return false; - } - - if (!wallet.secondPublicKey && (transaction.secondSignature || transaction.signSignature)) { - // Accept invalid second signature fields prior the applied patch. - if (configManager.getMilestone().ignoreInvalidSecondSignatureField) { - return true; - } - - errors.push("Invalid second-signature field"); - return false; - } - - // TODO: this can blow up if 2nd phrase and other transactions are in the wrong order - if (wallet.secondPublicKey && !crypto.verifySecondSignature(transaction, wallet.secondPublicKey)) { - errors.push("Failed to verify second-signature"); - return false; - } - - return true; - } - - /** - * Associate this wallet as the sender of a transaction. - */ - public applyTransactionToSender(wallet: Wallet, transaction: ITransactionData): void { - if ( - transaction.senderPublicKey.toLowerCase() === wallet.publicKey.toLowerCase() || - crypto.getAddress(transaction.senderPublicKey) === wallet.address - ) { - wallet.balance = wallet.balance.minus(transaction.amount).minus(transaction.fee); - - this.apply(wallet, transaction); - - wallet.dirty = true; - } - } - - /** - * Remove this wallet as the sender of a transaction. - */ - public revertTransactionForSender(wallet: Wallet, transaction: ITransactionData): void { - if ( - transaction.senderPublicKey.toLowerCase() === wallet.publicKey.toLowerCase() || - crypto.getAddress(transaction.senderPublicKey) === wallet.address - ) { - wallet.balance = wallet.balance.plus(transaction.amount).plus(transaction.fee); - - this.revert(wallet, transaction); - - wallet.dirty = true; - } - } - - /** - * Add transaction balance to this wallet. - */ - public applyTransactionToRecipient(wallet: Wallet, transaction: ITransactionData): void { - if (transaction.recipientId === wallet.address) { - wallet.balance = wallet.balance.plus(transaction.amount); - wallet.dirty = true; - } - } - - /** - * Remove transaction balance from this wallet. - */ - public revertTransactionForRecipient(wallet: Wallet, transaction: ITransactionData): void { - if (transaction.recipientId === wallet.address) { - wallet.balance = wallet.balance.minus(transaction.amount); - wallet.dirty = true; - } - } - - protected abstract apply(wallet: Wallet, transaction: ITransactionData): void; - - protected abstract revert(wallet: Wallet, transaction: ITransactionData): void; -} diff --git a/packages/crypto/src/handlers/transactions/index.ts b/packages/crypto/src/handlers/transactions/index.ts deleted file mode 100644 index e316bc00ea..0000000000 --- a/packages/crypto/src/handlers/transactions/index.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { TransactionTypes } from "../../constants"; -import { ITransactionData, Wallet } from "../../models"; -import { DelegateRegistrationHandler } from "./delegate-registration"; -import { DelegateResignationHandler } from "./delegate-resignation"; -import { Handler } from "./handler"; -import { IpfsHandler } from "./ipfs"; -import { MultiPaymentHandler } from "./multi-payment"; -import { MultiSignatureHandler } from "./multi-signature"; -import { SecondSignatureHandler } from "./second-signature"; -import { TimelockTransferHandler } from "./timelock-transfer"; -import { TransferHandler } from "./transfer"; -import { VoteHandler } from "./vote"; - -class TransactionHandler { - public handlers: { [x in TransactionTypes]: typeof Handler }; - - constructor() { - this.handlers = { - [TransactionTypes.Transfer]: TransferHandler, - [TransactionTypes.SecondSignature]: SecondSignatureHandler, - [TransactionTypes.DelegateRegistration]: DelegateRegistrationHandler, - [TransactionTypes.Vote]: VoteHandler, - [TransactionTypes.MultiSignature]: MultiSignatureHandler, - [TransactionTypes.Ipfs]: IpfsHandler, - [TransactionTypes.TimelockTransfer]: TimelockTransferHandler, - [TransactionTypes.MultiPayment]: MultiPaymentHandler, - [TransactionTypes.DelegateResignation]: DelegateResignationHandler, - }; - } - - /** - * Check if the transaction can be applied to the wallet. - */ - public canApply(wallet: Wallet, transaction: ITransactionData, errors: string[]): boolean { - return this.getHandler(transaction).canApply(wallet, transaction, errors); - } - - /** - * Associate this wallet as the sender of a transaction. - */ - public applyTransactionToSender(wallet: Wallet, transaction: ITransactionData): void { - this.getHandler(transaction).applyTransactionToSender(wallet, transaction); - } - - /** - * Add transaction balance to this wallet. - */ - public applyTransactionToRecipient(wallet: Wallet, transaction: ITransactionData): void { - this.getHandler(transaction).applyTransactionToRecipient(wallet, transaction); - } - - /** - * Remove this wallet as the sender of a transaction. - */ - public revertTransactionForSender(wallet: Wallet, transaction: ITransactionData): void { - this.getHandler(transaction).revertTransactionForSender(wallet, transaction); - } - - /** - * Remove transaction balance from this wallet. - */ - public revertTransactionForRecipient(wallet: Wallet, transaction: ITransactionData): void { - this.getHandler(transaction).revertTransactionForRecipient(wallet, transaction); - } - - private getHandler(transaction: ITransactionData): Handler { - return new (this.handlers[transaction.type] as any)(); - } -} - -export const transactionHandler = new TransactionHandler(); diff --git a/packages/crypto/src/handlers/transactions/ipfs.ts b/packages/crypto/src/handlers/transactions/ipfs.ts deleted file mode 100644 index 7b58dd0655..0000000000 --- a/packages/crypto/src/handlers/transactions/ipfs.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ITransactionData, Wallet } from "../../models"; -import { Handler } from "./handler"; - -export class IpfsHandler extends Handler { - /** - * Check if the transaction can be applied to the wallet. - */ - public canApply(wallet: Wallet, transaction: ITransactionData, errors: string[]): boolean { - return super.canApply(wallet, transaction, errors); - } - - /** - * Apply the transaction to the wallet. - */ - protected apply(wallet: Wallet, transaction: ITransactionData): void { - // - } - - /** - * Revert the transaction from the wallet. - */ - protected revert(wallet: Wallet, transaction: ITransactionData): void { - // - } -} diff --git a/packages/crypto/src/handlers/transactions/multi-payment.ts b/packages/crypto/src/handlers/transactions/multi-payment.ts deleted file mode 100644 index caff8e8932..0000000000 --- a/packages/crypto/src/handlers/transactions/multi-payment.ts +++ /dev/null @@ -1,42 +0,0 @@ -import sumBy from "lodash/sumBy"; -import { ITransactionData, Wallet } from "../../models"; -import { Handler } from "./handler"; - -export class MultiPaymentHandler extends Handler { - /** - * Check if the transaction can be applied to the wallet. - */ - public canApply(wallet: Wallet, transaction: ITransactionData, errors: string[]): boolean { - if (!super.canApply(wallet, transaction, errors)) { - return false; - } - - const amount = sumBy(transaction.asset.payments, (payment: any) => payment.amount.toFixed()); - - if ( - wallet.balance - .minus(amount) - .minus(transaction.fee) - .isLessThan(0) - ) { - errors.push("Insufficient balance in the wallet to transfer all payments"); - return false; - } - - return true; - } - - /** - * Apply the transaction to the wallet. - */ - protected apply(wallet: Wallet, transaction: ITransactionData): void { - // - } - - /** - * Revert the transaction from the wallet. - */ - protected revert(wallet: Wallet, transaction: ITransactionData): void { - // - } -} diff --git a/packages/crypto/src/handlers/transactions/multi-signature.ts b/packages/crypto/src/handlers/transactions/multi-signature.ts deleted file mode 100644 index 92689863b6..0000000000 --- a/packages/crypto/src/handlers/transactions/multi-signature.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { ITransactionData, Wallet } from "../../models"; -import { Handler } from "./handler"; - -export class MultiSignatureHandler extends Handler { - /** - * Check if the transaction can be applied to the wallet. - */ - public canApply(wallet: Wallet, transaction: ITransactionData, errors: string[]): boolean { - if (!super.canApply(wallet, transaction, errors)) { - return false; - } - - if (wallet.multisignature) { - errors.push("Wallet is already a multi-signature wallet"); - return false; - } - - const keysgroup = transaction.asset.multisignature.keysgroup; - - if (keysgroup.length < transaction.asset.multisignature.min) { - errors.push("Specified key count does not meet minimum key count"); - return false; - } - - if (keysgroup.length !== transaction.signatures.length) { - errors.push("Specified key count does not equal signature count"); - return false; - } - - if (!wallet.verifySignatures(transaction, transaction.asset.multisignature)) { - errors.push("Failed to verify multi-signatures"); - return false; - } - - return true; - } - - /** - * Apply the transaction to the wallet. - */ - protected apply(wallet: Wallet, transaction: ITransactionData): void { - wallet.multisignature = transaction.asset.multisignature; - } - - /** - * Revert the transaction from the wallet. - */ - protected revert(wallet: Wallet, transaction: ITransactionData): void { - wallet.multisignature = null; - } -} diff --git a/packages/crypto/src/handlers/transactions/second-signature.ts b/packages/crypto/src/handlers/transactions/second-signature.ts deleted file mode 100644 index ac1b8418fe..0000000000 --- a/packages/crypto/src/handlers/transactions/second-signature.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { ITransactionData, Wallet } from "../../models"; -import { Handler } from "./handler"; - -export class SecondSignatureHandler extends Handler { - /** - * Check if the transaction can be applied to the wallet. - */ - public canApply(wallet: Wallet, transaction: ITransactionData, errors: string[]): boolean { - if (wallet.secondPublicKey) { - errors.push("Wallet already has a second signature"); - return false; - } - - if (!super.canApply(wallet, transaction, errors)) { - return false; - } - - return true; - } - - /** - * Apply the transaction to the wallet. - */ - protected apply(wallet: Wallet, transaction: ITransactionData): void { - wallet.secondPublicKey = transaction.asset.signature.publicKey; - } - - /** - * Revert the transaction from the wallet. - */ - protected revert(wallet: Wallet, transaction: ITransactionData): void { - delete wallet.secondPublicKey; - } -} diff --git a/packages/crypto/src/handlers/transactions/timelock-transfer.ts b/packages/crypto/src/handlers/transactions/timelock-transfer.ts deleted file mode 100644 index 0ac4f4c8a4..0000000000 --- a/packages/crypto/src/handlers/transactions/timelock-transfer.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ITransactionData, Wallet } from "../../models"; -import { Handler } from "./handler"; - -export class TimelockTransferHandler extends Handler { - /** - * Check if the transaction can be applied to the wallet. - */ - public canApply(wallet: Wallet, transaction: ITransactionData, errors: string[]): boolean { - return super.canApply(wallet, transaction, errors); - } - - /** - * Apply the transaction to the wallet. - */ - protected apply(wallet: Wallet, transaction: ITransactionData): void { - // - } - - /** - * Revert the transaction from the wallet. - */ - protected revert(wallet: Wallet, transaction: ITransactionData): void { - // - } -} diff --git a/packages/crypto/src/handlers/transactions/transfer.ts b/packages/crypto/src/handlers/transactions/transfer.ts deleted file mode 100644 index de649e93b3..0000000000 --- a/packages/crypto/src/handlers/transactions/transfer.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ITransactionData, Wallet } from "../../models"; -import { Handler } from "./handler"; - -export class TransferHandler extends Handler { - /** - * Check if the transaction can be applied to the wallet. - */ - public canApply(wallet: Wallet, transaction: ITransactionData, errors: string[]): boolean { - return super.canApply(wallet, transaction, errors); - } - - /** - * Apply the transaction to the wallet. - */ - protected apply(wallet: Wallet, transaction: ITransactionData): void { - // - } - - /** - * Revert the transaction from the wallet. - */ - protected revert(wallet: Wallet, transaction: ITransactionData): void { - // - } -} diff --git a/packages/crypto/src/handlers/transactions/vote.ts b/packages/crypto/src/handlers/transactions/vote.ts deleted file mode 100644 index 9660394e3f..0000000000 --- a/packages/crypto/src/handlers/transactions/vote.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { ITransactionData, Wallet } from "../../models"; -import { Handler } from "./handler"; - -export class VoteHandler extends Handler { - /** - * Check if the transaction can be applied to the wallet. - */ - public canApply(wallet: Wallet, transaction: ITransactionData, errors: string[]): boolean { - if (!super.canApply(wallet, transaction, errors)) { - return false; - } - - const vote = transaction.asset.votes[0]; - if (vote.startsWith("-") && (!wallet.vote || wallet.vote !== vote.slice(1))) { - if (!wallet.vote) { - errors.push("Wallet has not voted yet"); - } else { - errors.push("The unvote public key does not match the currently voted one"); - } - - return false; - } - - if (vote.startsWith("+") && wallet.vote) { - errors.push("Wallet has already voted"); - return false; - } - return true; - } - - /** - * Apply the transaction to the wallet. - */ - protected apply(wallet: Wallet, transaction: ITransactionData): void { - const vote = transaction.asset.votes[0]; - - if (vote.startsWith("+")) { - wallet.vote = vote.slice(1); - } - - if (vote.startsWith("-")) { - wallet.vote = null; - } - } - - /** - * Revert the transaction from the wallet. - */ - protected revert(wallet: Wallet, transaction: ITransactionData): void { - const vote = transaction.asset.votes[0]; - - if (vote.startsWith("+")) { - wallet.vote = null; - } - - if (vote.startsWith("-")) { - wallet.vote = vote.slice(1); - } - } -} diff --git a/packages/crypto/src/identities/keys.ts b/packages/crypto/src/identities/keys.ts index 5c685c8d68..95a31f483a 100644 --- a/packages/crypto/src/identities/keys.ts +++ b/packages/crypto/src/identities/keys.ts @@ -4,7 +4,6 @@ import wif from "wif"; import { HashAlgorithms } from "../crypto"; import { NetworkVersionError } from "../errors"; import { configManager } from "../managers"; -import { INetwork } from "../networks"; export interface KeyPair { publicKey: string; diff --git a/packages/crypto/src/index.ts b/packages/crypto/src/index.ts index f4c8b15bbd..17a2edd8df 100644 --- a/packages/crypto/src/index.ts +++ b/packages/crypto/src/index.ts @@ -1,9 +1,11 @@ export * from "./builder"; import * as constants from "./constants"; +import * as errors from "./errors"; import * as models from "./models"; import * as networks from "./networks"; +export * from "./transactions"; export * from "./identities"; export * from "./managers"; export * from "./utils"; @@ -11,4 +13,4 @@ export * from "./validation"; export * from "./crypto"; export * from "./client"; -export { constants, models, networks }; +export { constants, errors, models, networks }; diff --git a/packages/crypto/src/interfaces.ts b/packages/crypto/src/interfaces.ts new file mode 100644 index 0000000000..7e4ef77a70 --- /dev/null +++ b/packages/crypto/src/interfaces.ts @@ -0,0 +1,6 @@ +import { Bignum } from "./utils"; + +export interface MultiPaymentItem { + amount: Bignum; + recipientId: string; +} diff --git a/packages/crypto/src/managers/config.ts b/packages/crypto/src/managers/config.ts index 5e6592b186..89bc14db93 100644 --- a/packages/crypto/src/managers/config.ts +++ b/packages/crypto/src/managers/config.ts @@ -1,11 +1,12 @@ import deepmerge from "deepmerge"; -import camelCase from "lodash/camelCase"; -import get from "lodash/get"; -import set from "lodash/set"; -import { feeManager } from "./fee"; +import camelCase from "lodash.camelcase"; +import get from "lodash.get"; +import set from "lodash.set"; import { TransactionTypes } from "../constants"; +import { InvalidMilestoneConfigurationError } from "../errors"; import * as networks from "../networks"; +import { feeManager } from "./fee"; interface IMilestone { index: number; @@ -43,6 +44,8 @@ export class ConfigManager { this.config.milestones = config.milestones; this.config.genesisBlock = config.genesisBlock; + this.validateMilestones(); + this.buildConstants(); this.buildFees(); } @@ -87,6 +90,7 @@ export class ConfigManager { */ public setHeight(value: number): void { this.height = value; + this.buildFees(); } /** @@ -142,12 +146,38 @@ export class ConfigManager { } } + private validateMilestones(): void { + const delegateMilestones = this.config.milestones + .sort((a, b) => a.height - b.height) + .filter(milestone => milestone.activeDelegates); + + for (let i = 1; i < delegateMilestones.length; i++) { + const previous = delegateMilestones[i - 1]; + const current = delegateMilestones[i]; + + if (previous.activeDelegates === current.activeDelegates) { + continue; + } + + if ((current.height - previous.height) % previous.activeDelegates !== 0) { + throw new InvalidMilestoneConfigurationError( + `Bad milestone at height: ${ + current.height + }. The number of delegates can only be changed at the beginning of a new round.`, + ); + } + } + } + /** * Build fees from config constants. */ private buildFees(): void { - for (const type of Object.keys(TransactionTypes)) { - feeManager.set(TransactionTypes[type], this.getMilestone().fees.staticFees[camelCase(type)]); + for (const key of Object.keys(TransactionTypes)) { + const type = TransactionTypes[key]; + if (typeof type === "number") { + feeManager.set(type, this.getMilestone().fees.staticFees[camelCase(key)]); + } } } } diff --git a/packages/crypto/src/managers/fee.ts b/packages/crypto/src/managers/fee.ts index e836b2038b..0003394a57 100644 --- a/packages/crypto/src/managers/fee.ts +++ b/packages/crypto/src/managers/fee.ts @@ -1,20 +1,20 @@ import { TransactionTypes } from "../constants"; -import { ITransactionData } from "../models"; +import { ITransactionData } from "../transactions"; export class FeeManager { - public fees: { [key in TransactionTypes]?: number } = {}; + public fees: { [key: number]: number } = {}; /** * Set fee value based on type. */ - public set(type: TransactionTypes, value: number) { + public set(type: TransactionTypes | number, value: number) { this.fees[type] = value; } /** * Get fee value based on type. */ - public get(type: TransactionTypes): number { + public get(type: TransactionTypes | number): number { return this.fees[type]; } diff --git a/packages/crypto/src/managers/index.ts b/packages/crypto/src/managers/index.ts index 7345bebea4..4379632509 100644 --- a/packages/crypto/src/managers/index.ts +++ b/packages/crypto/src/managers/index.ts @@ -1,5 +1,5 @@ -import { configManager } from "./config"; +import { configManager, NetworkName } from "./config"; import { feeManager } from "./fee"; import { NetworkManager } from "./network"; -export { configManager, feeManager, NetworkManager }; +export { configManager, feeManager, NetworkManager, NetworkName }; diff --git a/packages/crypto/src/managers/network.ts b/packages/crypto/src/managers/network.ts index 4ae8d54b56..be3f7bd94a 100644 --- a/packages/crypto/src/managers/network.ts +++ b/packages/crypto/src/managers/network.ts @@ -1,4 +1,4 @@ -import get from "lodash/get"; +import get from "lodash.get"; import * as networks from "../networks"; import { NetworkName } from "./config"; diff --git a/packages/crypto/src/models/block.ts b/packages/crypto/src/models/block.ts index 5e1bf954f5..f2e661f082 100644 --- a/packages/crypto/src/models/block.ts +++ b/packages/crypto/src/models/block.ts @@ -1,11 +1,12 @@ -import { createHash } from "crypto"; import pluralize from "pluralize"; -import { crypto, slots } from "../crypto"; -import { BlockDeserializer } from "../deserializers"; +import { crypto, HashAlgorithms, slots } from "../crypto"; +import { BlockSchemaError } from "../errors"; import { configManager } from "../managers/config"; -import { BlockSerializer } from "../serializers"; -import { Bignum } from "../utils"; -import { ITransactionData, Transaction } from "./transaction"; +import { ITransactionData, Transaction } from "../transactions"; +import { BlockDeserializer } from "../transactions/deserializers"; +import { BlockSerializer } from "../transactions/serializers"; +import { Bignum, isException } from "../utils"; +import { AjvWrapper } from "../validation"; export interface BlockVerification { verified: boolean; @@ -38,35 +39,6 @@ export interface IBlockData { transactions?: ITransactionData[]; } -/** - * TODO copy some parts to ArkDocs - * @classdesc This model holds the block data, its verification and serialization - * - * A Block model stores on the db: - * - id - * - version (version of the block: could be used for changing how they are forged) - * - timestamp (related to the genesis block) - * - previousBlock (id of the previous block) - * - height - * - numberOfTransactions - * - totalAmount (in satoshi) - * - totalFee (in satoshi) - * - reward (in satoshi) - * - payloadHash (hash of the transactions) - * - payloadLength (total length in bytes of the IDs of the transactions) - * - generatorPublicKey (public key of the delegate that forged this block) - * - blockSignature - * - * The `transactions` are stored too, but in a different table. - * - * These data is exposed through the `data` attributed as a plain object and - * serialized through the `serialized` attribute. - * - * In the future the IDs could be changed to use the hexadecimal version of them, - * which would be more efficient for performance, disk usage and bandwidth reasons. - * That is why there are some attributes, such as `idHex` and `previousBlockHex`. - */ - export class Block implements IBlock { /** * Create block from data. @@ -75,9 +47,7 @@ export class Block implements IBlock { data.generatorPublicKey = keys.publicKey; const payloadHash: Buffer = Block.serialize(data, false); - const hash = createHash("sha256") - .update(payloadHash) - .digest(); + const hash = HashAlgorithms.sha256(payloadHash); data.blockSignature = crypto.signHash(hash, keys); data.id = Block.getId(data); @@ -89,7 +59,7 @@ export class Block implements IBlock { * Deserialize block from hex string. */ public static deserialize(hexString, headerOnly = false): IBlockData { - return BlockDeserializer.deserialize(hexString, headerOnly); + return BlockDeserializer.deserialize(hexString, headerOnly).data; } /** @@ -107,10 +77,15 @@ export class Block implements IBlock { } public static getIdHex(data): string { + const constants = configManager.getMilestone(data.height); const payloadHash: any = Block.serialize(data); - const hash = createHash("sha256") - .update(payloadHash) - .digest(); + + const hash = HashAlgorithms.sha256(payloadHash); + + if (constants.block.idFullSha256) { + return hash.toString("hex"); + } + const temp = Buffer.alloc(8); for (let i = 0; i < 8; i++) { @@ -124,23 +99,14 @@ export class Block implements IBlock { return "0".repeat(16 - temp.length) + temp; } - /** - * Get block id from already serialized buffer - */ - public static getIdFromSerialized(serializedBuffer: Buffer): string { - const hash = createHash("sha256") - .update(serializedBuffer) - .digest(); - const temp = Buffer.alloc(8); + public static getId(data): string { + const constants = configManager.getMilestone(data.height); + const idHex = Block.getIdHex(data); - for (let i = 0; i < 8; i++) { - temp[i] = hash[7 - i]; + if (constants.block.idFullSha256) { + return idHex; } - return new Bignum(temp.toString("hex"), 16).toFixed(); - } - public static getId(data): string { - const idHex = Block.getIdHex(data); return new Bignum(idHex, 16).toFixed(); } @@ -150,30 +116,38 @@ export class Block implements IBlock { public verification: BlockVerification; constructor(data: IBlockData | string) { + let deserialized; if (typeof data === "string") { - data = Block.deserialize(data); + this.serialized = data; + } else { + this.serialized = Block.serializeFull(data).toString("hex"); } - this.serialized = Block.serializeFull(data).toString("hex"); - this.data = Block.deserialize(this.serialized); + deserialized = BlockDeserializer.deserialize(this.serialized); + this.data = deserialized.data; + + const { value, error } = AjvWrapper.validate("block", deserialized.data); + if (error !== null && !(isException(value) || this.data.transactions.some(tx => isException(tx)))) { + throw new BlockSchemaError(error); + } + + this.data = value; // TODO genesis block calculated id is wrong for some reason - if (data.height === 1) { - this.applyGenesisBlockFix(data); + if (this.data.height === 1) { + this.applyGenesisBlockFix(data as IBlockData); } // fix on real timestamp, this is overloading transaction // timestamp with block timestamp for storage only // also add sequence to keep database sequence - const { transactions } = this.data; - this.transactions = transactions - ? transactions.map((transaction, index) => { - transaction.blockId = this.data.id; - transaction.timestamp = this.data.timestamp; - transaction.sequence = index; - return transaction as Transaction; - }) - : []; + const { transactions } = deserialized; + this.transactions = transactions.map((transaction, index) => { + transaction.data.blockId = this.data.id; + transaction.timestamp = this.data.timestamp; + transaction.data.sequence = index; + return transaction; + }); delete this.data.transactions; @@ -217,14 +191,12 @@ export class Block implements IBlock { */ public verifySignature(): boolean { const bytes: any = Block.serialize(this.data, false); - const hash = createHash("sha256") - .update(bytes) - .digest(); + const hash = HashAlgorithms.sha256(bytes); return crypto.verifyHash(hash, this.data.blockSignature, this.data.generatorPublicKey); } - public toJson(): any { + public toJson(): IBlockData { const blockData = Object.assign({}, this.data) as IBlockData; ["reward", "totalAmount", "totalFee"].forEach((key: string) => { blockData[key] = +(blockData[key] as Bignum).toFixed(); @@ -280,11 +252,10 @@ export class Block implements IBlock { // } let size = 0; - const payloadHash = createHash("sha256"); const invalidTransactions = this.transactions.filter(tx => !tx.verified); if (invalidTransactions.length > 0) { result.errors.push("One or more transactions are not verified:"); - invalidTransactions.forEach(tx => result.errors.push(`=> ${tx.serialized}`)); + invalidTransactions.forEach(tx => result.errors.push(`=> ${tx.serialized.toString("hex")}`)); } if (this.transactions.length !== block.numberOfTransactions) { @@ -301,6 +272,7 @@ export class Block implements IBlock { const appliedTransactions = {}; let totalAmount = Bignum.ZERO; let totalFee = Bignum.ZERO; + const payloadBuffers = []; this.transactions.forEach(transaction => { const bytes = Buffer.from(transaction.data.id, "hex"); @@ -314,7 +286,7 @@ export class Block implements IBlock { totalFee = totalFee.plus(transaction.data.fee); size += bytes.length; - payloadHash.update(bytes); + payloadBuffers.push(bytes); }); if (!totalAmount.isEqualTo(block.totalAmount)) { @@ -329,7 +301,7 @@ export class Block implements IBlock { result.errors.push("Payload is too large"); } - if (payloadHash.digest().toString("hex") !== block.payloadHash) { + if (HashAlgorithms.sha256(payloadBuffers).toString("hex") !== block.payloadHash) { result.errors.push("Invalid payload hash"); } } catch (error) { @@ -344,6 +316,5 @@ export class Block implements IBlock { private applyGenesisBlockFix(data: IBlockData): void { this.data.id = data.id; this.data.idHex = Block.toBytesHex(this.data.id); - delete this.data.previousBlock; } } diff --git a/packages/crypto/src/models/delegate.ts b/packages/crypto/src/models/delegate.ts index 51bc696ceb..ccfbf68a9c 100644 --- a/packages/crypto/src/models/delegate.ts +++ b/packages/crypto/src/models/delegate.ts @@ -1,16 +1,16 @@ -import { createHash } from "crypto"; import forge from "node-forge"; import { authenticator } from "otplib"; import wif from "wif"; import * as bip38 from "../crypto/bip38"; import { Bignum } from "../utils"; +import { HashAlgorithms } from "../crypto"; import { crypto } from "../crypto/crypto"; import { KeyPair } from "../identities"; import { INetwork } from "../networks"; +import { ITransactionData } from "../transactions"; import { sortTransactions } from "../utils"; import { Block, IBlockData } from "./block"; -import { Transaction } from "./transaction"; export class Delegate { /** @@ -93,19 +93,19 @@ export class Delegate { /** * Forge block - we consider transactions are signed, verified and unique. */ - public forge(transactions: Transaction[], options: any): Block | null { + public forge(transactions: ITransactionData[], options: any): Block | null { if (!options.version && (this.encryptedKeys || !this.bip38)) { const transactionData = { amount: Bignum.ZERO, fee: Bignum.ZERO, - sha256: createHash("sha256"), }; + const payloadBuffers = []; const sortedTransactions = sortTransactions(transactions); sortedTransactions.forEach(transaction => { transactionData.amount = transactionData.amount.plus(transaction.amount); transactionData.fee = transactionData.fee.plus(transaction.fee); - transactionData.sha256.update(Buffer.from(transaction.id, "hex")); + payloadBuffers.push(Buffer.from(transaction.id, "hex")); }); const data: IBlockData = { @@ -120,7 +120,7 @@ export class Delegate { totalFee: transactionData.fee, reward: options.reward, payloadLength: 32 * sortedTransactions.length, - payloadHash: transactionData.sha256.digest().toString("hex"), + payloadHash: HashAlgorithms.sha256(payloadBuffers).toString("hex"), transactions: sortedTransactions, }; diff --git a/packages/crypto/src/models/index.ts b/packages/crypto/src/models/index.ts index 0609cc9af5..d9e0457c46 100644 --- a/packages/crypto/src/models/index.ts +++ b/packages/crypto/src/models/index.ts @@ -1,4 +1,2 @@ export * from "./block"; -export * from "./transaction"; export * from "./delegate"; -export * from "./wallet"; diff --git a/packages/crypto/src/models/transaction.ts b/packages/crypto/src/models/transaction.ts deleted file mode 100644 index 013cc32bd7..0000000000 --- a/packages/crypto/src/models/transaction.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { TransactionTypes } from "../constants"; -import { crypto } from "../crypto"; -import { TransactionDeserializer } from "../deserializers"; -import { TransactionSerializer } from "../serializers"; -import { Bignum, isException } from "../utils"; - -export interface ITransactionAsset { - signature?: { - publicKey: string; - }; - delegate?: { - username: string; - publicKey?: string; - }; - votes?: string[]; - multisignature?: IMultiSignatureAsset; - ipfs?: { - dag: string; - }; - payments?: any; -} - -export interface IMultiSignatureAsset { - min: number; - keysgroup: string[]; - lifetime: number; -} - -export interface ITransactionData { - version?: number; - network?: number; - - type: TransactionTypes; - timestamp: number; - senderPublicKey: string; - - fee: Bignum | number | string; - amount: Bignum | number | string; - - expiration?: number; - recipientId?: string; - - asset?: ITransactionAsset; - vendorField?: string; - vendorFieldHex?: string; - - id?: string; - signature?: string; - secondSignature?: string; - signSignature?: string; - signatures?: string[]; - - blockId?: string; - sequence?: number; - - timelock?: any; - timelockType?: number; - - ipfsHash?: string; - payments?: { [key: string]: any }; -} - -/** - * TODO copy some parts to ArkDocs - * @classdesc This model holds the transaction data and its serialization - * - * A Transaction stores on the db: - * - id - * - version (version of the transaction generation process, ie: serialization) - * - blockId (id of the block that contains the transaction) - * - timestamp (related to the genesis block) - * - senderPublicKey (public key of the sender) - * - recipientId (address of the recipient) - * - type - * - vendorFieldHex (hexadecimal version of the vendorField) - * - amount (in satoshi) - * - fee (in satoshi) - * - serialized - * - * Apart, the Model includes other fields: - * - signature - * - secondSignature - * - vendorField - * - * - assets - * - network - */ - -export class Transaction implements ITransactionData { - public static serialize(transaction: ITransactionData): Buffer { - return TransactionSerializer.serialize(transaction); - } - - public static deserialize(hexString: string): ITransactionData { - return TransactionDeserializer.deserialize(hexString); - } - - public static canHaveVendorField(type: number): boolean { - return [TransactionTypes.Transfer, TransactionTypes.TimelockTransfer].includes(type); - } - - public data: ITransactionData; - public serialized: string; - public verified: boolean; - - // TODO: remove all duplicated data properties from Transaction class - public id: string; - public version: number; - public network: number; - public type: TransactionTypes; - public timestamp: number; - public senderPublicKey: string; - public fee: Bignum; - public amount: Bignum; - public expiration?: number; - public recipientId?: string; - public asset?: any; - public vendorField?: string; - public vendorFieldHex?: string; - public signature: string; - public secondSignature?: string; - public signSignature?: string; - public signatures?: string[]; - public blockId?: string; - public sequence?: number; - public timelock?: any; - public timelockType?: number; - - constructor(data: string | ITransactionData) { - if (typeof data === "string") { - this.serialized = data; - } else { - this.serialized = Transaction.serialize(data).toString("hex"); - } - - this.data = Transaction.deserialize(this.serialized); - this.verified = (this.data.type < 4 && crypto.verify(this.data)) || isException(this.data); - - // TODO: remove this - [ - "id", - "sequence", - "version", - "timestamp", - "senderPublicKey", - "recipientId", - "type", - "vendorField", - "vendorFieldHex", - "amount", - "fee", - "blockId", - "signature", - "signatures", - "secondSignature", - "signSignature", - "asset", - "expiration", - "timelock", - "timelockType", - ].forEach(key => { - this[key] = this.data[key]; - }, this); - } - - public verify(): boolean { - return this.verified; - } - - public toJson(): any { - const data = Object.assign({}, this.data); - data.amount = +(data.amount as Bignum).toFixed(); - data.fee = +(data.fee as Bignum).toFixed(); - - return data; - } -} diff --git a/packages/crypto/src/networks/devnet/exceptions.json b/packages/crypto/src/networks/devnet/exceptions.json index 3cce29d8a9..3451f6439e 100644 --- a/packages/crypto/src/networks/devnet/exceptions.json +++ b/packages/crypto/src/networks/devnet/exceptions.json @@ -1,350 +1,6 @@ { - "blocks": [ - "15895730198424359628", - "14746174532446639362", - "15249141324902969334", - "12360802297474246584", - "2565729258675312304", - "12614646598841308905", - "8274406339991077743", - "1661383348822169561", - "15467742607784975524", - "3665174254391236833", - "18033869253067308940", - "9121030900295704150", - "4296553458016414976", - "6837659293375391985", - "16540521480028827748", - "1485997193168364918", - "14159698257459587584", - "7561147498738550191", - "6247200360319694668", - "7363268091423233950", - "8738693892321921533", - "9014317427571908796", - "15519361274991733193", - "14013227271822852495", - "12603272471546364995", - "1944108005996955253", - "8469356042757089608", - "3433946900869474802", - "11257633501887013743", - "2997965849869498353", - "9196430932294555781", - "6730395143580220680", - "5806654366498055250", - "13290912469992409149", - "9502002558776276513", - "330791153715252718", - "12084096509112875921", - "7079194814443264009", - "15946707936026547597", - "1641736062116508620", - "5245034769798442586", - "4073147595542846301", - "11129434526540201266", - "15355810214343508168", - "834201289153220685", - "4785149476172130294", - "9808224912335721998", - "11229968119222422821", - "6766557974469507237", - "2066948671330348076", - "13308773643111727094", - "15649739201370841265", - "17287484123727410951", - "1739406121453748889", - "16969775483726255451", - "5174570296595098048", - "10957882104586895269", - "16222316251056394079", - "11019993339496601918", - "7648775833276915174", - "5947225658884952613", - "17256370470460685782", - "5681801935518263609", - "6853934810393582972", - "621694479387726255", - "649083198759873217", - "4052333663180604671", - "5348794590580429562", - "7723209448992965570", - "15836524583901486981", - "12478859533758330380", - "13701809340863213986", - "4296553458016414976", - "6837659293375391985", - "16540521480028827748", - "1485997193168364918", - "14159698257459587584", - "6247200360319694668", - "7363268091423233950", - "9014317427571908796", - "15519361274991733193", - "12603272471546364995", - "1944108005996955253", - "8469356042757089608", - "2997965849869498353", - "9196430932294555781", - "5806654366498055250", - "13290912469992409149", - "9502002558776276513", - "330791153715252718", - "7079194814443264009", - "15946707936026547597", - "1641736062116508620", - "5245034769798442586", - "2565729258675312304", - "12614646598841308905", - "8274406339991077743", - "1661383348822169561", - "15467742607784975524", - "3665174254391236833", - "17417028847837598792", - "14220651316552198137", - "13101468344291730322", - "6671890826474701031" - ], - "transactions": [ - "76bd168e57a4431a64617c4e7864df1e0be89831eabaa230e37643efae2def6f", - "90d06cb306dcc33faba59545e03d91ee83b0409e66a45ffe6a9e3b1049a0c521", - "0f7a3e8036fbaae7c76f7615b09b8c4f1337e96d87042d86e03cc5ab9b6ed745", - "23733214f347970a34ccd772f89396056309744688bd6dbc35129e1f12d46d2f", - "e30d7290ca9cab570aa72bf0365dde39b8d75fe65a4e804844e5708a021f8ab4", - "03d3902bb30f71c29151f8b85ff478be1dc5264785c8c84550b6b59339dc03c9", - "7a00dc347a50186bc0d789a7899f1a4dcbc1e5d5adbb5359df238cd2b9363b41", - "bbe266eac2bbc505b40e74ae6d1960d7c76d3ca8d4b942b6046f0c5f750ff9f4", - "cb63ee14068a8d2987c90ecb12998653161cd8748af7790c75592647260d3266", - "d184752c1546c366866013aa00a2a0f9b40463656072334fc302ff783ff4ee98", - "f8122e3d8b7ad31c58ed3254196b16c23249b8372f06de42191c43bfcf39849d", - "0529fd0fe210f128b8090685093c8c6d6a1a0809e9f174a2917c8f80c090ec69", - "6fa076d2c25f0ab89b71c9cf4694c36202a67efb65ffdfc58624ea80f4009bf6", - "3b3099b15dab0cbda88a454ecede7cc24cc8dc08901bc0be7337538feae0e2e9", - "e3ec42eae6134e74a1ed858e8f7e2e65aab66dc2561ee6f2affc1f4742ab2970", - "d4aa08cefeba30724e2c5e08880585b9912d7fbf383aeab6b7fbee9525bf32f2", - "618621ca07903b74a205bee148c34cb1b6384aca0db1fab2bbdd82866cfe2309", - "56425c88a7be0ae35a99fd74f7934adc0b7abb00f51eb5a2dadc7bb564f05b9a", - "13f331dd16ca3ca8533262b554396ca0e99ff1f68ea6052c9dea7504d8701e4b", - "1855d6aea924d93582e92b81208901532928d282f150e496e501d044ecbe7136", - "3632a80ff935e14da59baced343f113900d52bdf6070ff19af352332172ced44", - "363bfbb7ae14858ab4a0f0049259b72d785657785d52363d6a9aaedd9bfc7488", - "4114409365b496d4751a4424dce769125eac6dbd87091287a98f16f85204930d", - "4659270deac2c6b0030a2db6a78c681ea629421438c7b73b963504483830e6e2", - "523be7376cb099367d805eb1a4ee83b3ae1645ab04c03b3ebd3277ec5b967810", - "52441513709b67c68a8a150ee1f54d2e33afe0a34f0197ecf2a616fd98b74b73", - "574f3579f2bd2bbf4f60ca1edb96dde23a88827fece9e4098c8e9740d13301e0", - "9890ba52dc8ae3386e06dd4a5c98152a249528bbbfb3af5c5a328e9a9a74e0fd", - "38fa93e15190fbb648a98b63dca816abf2d9b405f4695ad9b7e5088a4543463a", - "e29718a6b12666d2dc52b9cf99295b93cdf4eef057ad9bf4f47fd57654fe9f20", - "6bf6a0c115bc062f872828d0310e6826caf20cee6bdc5f4196d00b1b2bb8a745", - "6d949f412c183b47fe697600f903a7740d3b44b258a4a5da8af056587bc6eca0", - "81440f6f528463fbfb37884f42d8f2bd3ef94ead662eee0e62fae4b063217620", - "89f64e9c09b5509f006fbb9239349e97caf5513f591e110e44dd44a18078636f", - "8d6ab35a4625c91316e549ebef9085e5e4cbb7be85cf9146500086bac3a0ed8b", - "9e6ff0848991e3b88eb28845884e6c79459564e73e835219760592d35dc8a2a3", - "bc3f66e88a7bf2682adf54e00d938b93f5631521ea4a0c861e8c9dca38b84e16", - "c4f9333bfdbb7d495873ca51de29375b3b9dbf46e700c220b4bb4536ee69f793", - "d5b21daf82695a13b6a332c35836c42d167337315dbddf4699c68ef23466c31d", - "d8ebd64ad471d91ee2a12fee9c986e43a13cfef836f168515d107ee8dea58d5b", - "dd25b474f12cb920b1b1349125d83c0ccfce7dea27f0e4d06baab46ebada458d", - "ddcbee2daf18a52177c04473d91d5e63329ab3dccda76ed6eb3b41f640e67694", - "de11387163af72a28cbad86960d462887c3fd3285ccac84cec8aaecd1c84c678", - "f929ad9669b0020a37a17f6bd334f4649d43755968cdba7ad0d15cd2e4b8d9db", - "ff1f8fa07cc528fa68d87f1d4ff6c3ae8df9d7365f5eef077b2146446f71192f", - "8809592c1097b679702c2750fbc9307559bc1d554175d807ccce40942c0560d4", - "99a6e5c8624fd7642c6128ba287bb0c9ec2da1e0e8dcaed423df13bfcd0522b5", - "a9fb683405a8664e1d0fbffa605ffa3b15db99f2913cdd4bfb0408448c22a058", - "ace1e3a0de725e8a944611a6bb3dda6c309c26c6f415b4749d084c6421f38d30", - "fddfa2b8c797b66f247a7c69245568e5e93c0a6baaac467cf5247d464c803f7a", - "5088d9dd9c1aa0cd4fc5682a0db737b8fd81348424f0a796d81fe781dcc20062", - "6b5e0f73ebd9677afe42cd83d271e00ef41813e734a9e4d312e07726ce53a4b8", - "6c87d87bb867e0419812f832e07c06fe1fdba941a9adca02197c8d61246d61ac", - "0cbc773f731a02430ba7feb8b2a3af90e294cb1370f97e06c65a5e343a72986b", - "53580b0571bae7fdb77fe2470f38f00f2ae15c6a5ace19d837decbd893f2f6ba", - "f7338be76d01ae40f9cc1993390644191da6ec0f84b10c6df6f52026650556c1", - "03a740dadd99686b916bf298b90c829eca00d25112fdadaef444ff41d2fabda3", - "1b2b43d0a91ab424df5aadec60bac7044c82f5c3c13c9ec588f00a5cd50f3b2e", - "c1d1560bf2a44f2a59bfa2244d303ce1ebaa0cc434a409c9c159ec0365eaed4a", - "ed9a0a81cfa79e0b5af947780fb249d46c7669c4386c83e2a2ed4cfa9f576c76", - "f9c42c2401c69df14bed689c4b42ee3e0ef92b0f20eb2f6777620548b7ab95ad", - "2d2b1a34684d56066ef8681b2b520dea3cfca36b70674fa828ed95972a5b6aa0", - "3eebeb0d6eb1877c7b9611920b16414e98cfe718ec62294a63c3f70668183354", - "1a37cd89a0bc6f257f37431ce93494dc63476102a079b611f05c79f33224ce1e", - "e979723514200e6d69663f8cc6ed671fa418fa69c66c53cdf6494e4db8fbd574", - "60ec3ff66908b4ee077a22fe00ee276241cec475de576e0fd8c35fd4635eccbd", - "24f407b8761b4a0fc54bbcc2e070c811f7dc4c6f9240ed715a8a95924acfa345", - "66d2f629bda689e61aa83375d81c7374244c22d203efe8f1fc70a71fb4439487", - "97b73c30314f7c5fad0314f34d0d218376186267c67ba636b106dab4b55789b3", - "9a2ba4134745d1182e96dfb016385c31f260545bf1d9382e4e757112305e55a7", - "734988e30e42d124a7b3dea66d060ab567dcb8b65d806c32d8607fbc67bad292", - "e354b6c4891980135cc8201f257d1b052d46fc33d6555b633c59bd33f6b281cb", - "3945e67bb5e864d2dd206293f1e778fa2181db5f81c2efc0a89e8fe53e2a2e7c", - "15b1f1ffb2394e17d373c8573e39ebc395dfe98ae2b0793e6ad0d80fa2b12d13", - "4490ef5d8e4001c8687e7e394fd80e163d63b45fc45f655e8a085eef70102a3a", - "c7229a92f4d875112098934900d745a42b37e2888b2c86ee451bcb4cfb5e0c1f", - "c6377ddedd7bcf3b0025d3d00f3dd1d44c94e2baca2b1c1381f70a02a5e6e0da", - "fdd724dacef7442578efe93e841386c3410f44d7a5eb9bd0aec4c59964386f7d", - "ff9956d76b6b1f56cec744bd7f6fa6b408fe846ec278458a2591a43b1b8239fb", - "fe7ba0d0713821dfc43ca5024cfa430829eefacae2daf83f08375b552b578bd2", - "08b55a8e470a018881e27d65fc56fbdd5d562d0f6e1ae753ee49a6d9e7a02bad", - "1472b726972a477b4a20af71a185fe4159012d1c3bf523ca0b3936fa86d7f06a", - "3464941177cb161dbebd477abac7598fef15b47dfc1d99ac212b488b86c00bc8", - "761ac9b796de81737e3f140f831890a5eb7a86a25ed1a6b5bd48d58959f6baf3", - "2fb6f7fdc049f21a14bffa984fcf0e4d6cda6d30d549209e21b4d8ef9ff56e7a", - "7afc8b68390e07585d1289da18a4d89e9ed6a91abd21aa365636ab9e054c2089", - "9e9f37dd168ddd2fef9783b927e32194ec0e2735c8a42bfab3584030bac47c0b", - "0d37689318e83197de4472927d08a651ced33fae48a5595e033db4b7816fccf0", - "13cc5a4906bfc47342282eff7c8c5d6e2d824a3ba7b417365f07e473be8d5586", - "1d9cad8cfe1c0ccc3c49befcf3dbea1aeeb329b03279f900938ff4574e729d17", - "56b78c8935e2bcd301f3be154ea05a3bff9b6fedd59827eb052269d8a052d32f", - "ca82c604121d02677f6a2c5aaf7b0387a395955f0a3ca2cc9d4df86ddec51151", - "ca8a6575eb9aac88abd838bf76d3859244598f30c4e2ad68383c39d350363944", - "c2d9fc71c8e3ee3edbbc4827f7cd4b37e19d21756801ee67c2832f19ef50f403", - "89b622c919168b5aa3bc8018459d45aabdd3b4dcda5a867fa9f2a7eb875e2f8e", - "69ce928210329b22320d923d756fa61c986b8e710f23d41547bac851c5283b30", - "4fc69b18868a8e055421278c04a713d9e249903fbcc888df688b423d3b5de3e2", - "7b8a5d8ceb73764400fdb11e7f2e018975f5c8a5c569b5bd7345a01f8a76ac8e", - "3b2a22bc2fdc46233d51ad9e92ed15323ad399c1e7f7da20cee4f47c45240881", - "d97a0a8f8db3d9c75ded3e0c472d821c106c31548bba90c4f9df3f2b0f8c0db0", - "9da6e6f92938b5dde9e1836952dba31c3e501380a38d70c39242e38d82e9acf7", - "7175ca33fffa0a5327119d493e67c5185d56649a580aa95366dec2468ae01c32", - "9b76f5cf7bb2c248965e9b8608774370ed3fab3124621036368ef142cc5688e1", - "e516a8301f254877c65daa491f7cfd995833f528c2c2af7df70c83c41933e35b", - "f637c84b6a249181bc010df54d6271ea65996c528885c7ef6ea0198039df3854", - "fde663f1c01256855568c80d967ffd73c93a3096db0943ef8801929a0bf3fe7e", - "ffc9fa27a918e3bae19aeda9b6e236602a029249701862b3e919642784279e03", - "b1df50fcf4eea46c32ac98628d8b8b2a4249c7906b866e5331f6bdaad02af823", - "27eb23f7e828cf4b5402fd941ffad69f6b4236fa12831738094070aa812fc632", - "340c1207a356b6c00b8d35cca5b5977a322ccbae2cb9c70c7ad8f5c9999ec581", - "fb38cb3f4e42da4c948fa13ef1a3ade1ab4830b51eb751432ae8f989a4035df0", - "890ad9c2b67cf29acb2a8e0d8495887ccdb681c3828c0d49dfb3da341f810546", - "d66d7ca4a3b7ac2eff15bcc98e96b3aefc7ea18c088a13ac1cac97408cd15402", - "4bb2dea1d7b44889e369853421293d812b18cc3328dc52d368156b17e8825f54", - "23dd658fb3e50fac70af521c50b43f95d5194d0d27dd149d170a4771141d4370", - "4f555646ae62f6ac458b00f36d570e935c3b9b83116636e01292ffd4d1761c74", - "aa93866398c6a9e065cbafa1db5e12b40510c3da299d97d14c56d12c4f44d42b", - "72cea8d4bde0d262747a4627a478ec8afd40ebe5b5c38a3e6ae157c1d73d766b", - "c5f9e74cff064b7bcbc9b2b59199b835f5abf0a7d5d1f77cc5e01b29142d9210", - "57976122fc03bca4a5438a12e667507b95d543f959b99161146b3851c1591f3d", - "ce6e4b199059b9f2b8baaa6475a7e8002f412598be0666943c170d3f48ec3042", - "f2d0e7cf9cda469eb510092df957b58287f5b3c46dd596fc0398905eb1bbb1e8", - "fd866d74827a4d468f97260e05700f8f4a317ae98d0c1e0c307f707c21960aab", - "3a0f3747086ed148b3af107ec5e02fbea4c3036b568b313de45c5d13f9348bd2", - "d9ba72dd34f95d003a3b177dc7656244cf2693d72337463dc16eb6ff1d8f679f", - "276ef67f4c04365e81d22a55bfcd542eb54d8e178359c7084253de0941de22c9", - "591b307b57e1f924ccfda0e44aa626f3055bfc330897b12aca5fc99d004ef377", - "2f7f4453bb28befa7e3ad8a241a72455ed16b94f8d65ead817cdf06e5d320542", - "b4fa3a85e757e4c07f01380c8a684d380353d9810707699cd59b791f836c6571", - "ea23f249ed30ef8d4b12dcb967cb1ab728bf283f7180f13ed2e1a4e392293754", - "eccd803527df3ce48f4be72e5e3a6f278d4ea257ab984857c152448bd8e4b3de", - "fc9a5dc83e58c3594ebcbccdf76740f41d66329fe89dd5203cc23bf37eff2db4", - "487a87fab87abaf2ef38fa802051d107d60bc257b5be62ead2a6e0fe97bbeb95", - "9c93a90c3a81891d483b22d5ca132398f6acc6dc1095d3ae70c7cc9ddfd3028a", - "d21115e0d477d54e56349c2789914f561a02ef26d25a95ed4041927d9bce6158", - "34ce6311cfa5205d452db2b0e7f74717fad33119ebd71071194273dd09e28aa6", - "df898fb403c3487776c0afcd93c71755947924d093e55bdcc645cd7736235543", - "c5b34b3acb869e509b0bb1634c37c466f05361de269e1cf432a661908de9bb17", - "e321699c6d3829309e2052d2fa73facd8285dcbcdaca90503daa6a4e10488649", - "d140e1425b21cfacd07ece474784b935f170670ea7d44c07e61b7f1559c33ec1", - "02d81bc62bf59e2861aa0e1078a0fa67b44ed10d20c15bf610d2c60ee6d87d30", - "f8440d2fe36a2496281192a6f2017c3e0b1143dffc1299c5717f75c30baf8b82", - "5f028b575a7f0b0f417e3b3642b8608515cb3e7aa05db77b956a69b4bfbd981e", - "605cb7e40c8ef9bfd34f7ece54fc68a915c589691d8e19105d909061643b39fa", - "223e866865d9b65a44b6f320dd2f5422ec209c73fb302f2b08bce1d79bd05a6e", - "e78e533751d7a5745cd30247ee58392a3811b6546d291727e9887d6d55352864", - "088202b75537ba3e7456903780c2c91cf72c58e8e9856e3cc6d5c465fed00724", - "cb3dee266d937c04453d6ff7002d9948e5be09bedd480076e73aba4d85a1a39f", - "f1e17f743a17d85b1d2567ac748dba7cbe1d5dfa3707e1beb8862a56fcdab821", - "f27bc6a1baf3a75b4877f7a1cff333bede90f394195ee9a8b7098ae7e7e38067", - "f5ec7a17a63210d7e1ea28ba45168a9a88f6c9f35485d265b4dc98c0a5ad5927", - "f5f17b136c8955668fc75c98953b6b903065cdba9e905e6f3b94af225689aeea", - "386d6d2933965ffcb33c5f6dcf886e5df8a4bcc79e97c3a85dda83f3b1d5e3df", - "42001376bff16d35118fc05a271d0795a6d30b2fd7e9458974a1c2683a87c00a", - "64cd978b06ea47ccd4ed727e451e285b148cbc850be89edf2b9b4825c798886a", - "9be031bdf2f988e083808d50daed3e86b7c7c7b758e03e45de365cfc5cdad98b", - "0efc0a63612582eba21394b53491658d45dc64b230e25ec8f1aab4df6e273006", - "3bc7ed0ae39c641e02ab6591e1957a99755db6702724d2824d45491f33dd1a40", - "430063c50d13e4b386a8577340b48a9f4e7620b09445420e9b640cdf49feb72a", - "6c4578227054d105f28c3b3b622203ae3bf5156e326a3d2cc9b4c4e835ea8112", - "7fa78167690601ad2dd159d819aca03dce84cbd4cda1ac4d7ff981ef6f7d69e8", - "88d4bb35378e34fc45e3caa8fdeb2205699361f8e2aa488e0e98be5b7ba870ed", - "a478e34f5cb0e313d7f70e051bf549e5e3ff7e69ac93d5c4d156ba055fe447b5", - "caed90e15dcb174044f519930f10801c073b36f05b3179c75bc7f36160a70079", - "ec48a8a8b24e441695b24ba2d4f6cd964d24a6bb7ab6ce037252a0a302ff0f03", - "f172d80d6db5e5080abee7718c73a8d61892a96196c3d886aad9cb8644da808d", - "ff172882257b8f162f16a9a670b27039e23809b76a80842f70dd1febb00732b9", - "9d6926f2649eb3e4157be68fc931a4487b5eb498a61397d8a4bdf81416db8a31", - "ac4bc7ba22b020c8ac3bec48dced41361e63fb1604f1848a800ab20c379619f8", - "86e6e88147fac70815bb41498becb00ba631c23465838901e4ddc1b5db158ec4", - "d5e42d7e6cdee582655901fe187513db6caaa4722c659256a642a5fdbe5061cf", - "000f3c8a706ecedb17416431061eb9a15d925a328edd4c0478481673ca385005", - "16e1bb38bdbe7128bd85816e4933bce9a356d4b77868b647b17a3b4624263e30", - "a06928b3e6493b223524826a54ca70885fdbc65cfd712e756dd8168e523d9519", - "dc39a407d04ea4c14b0603d3402b5111d17d90ab18229bf2c8f7395c88d10155", - "c73f441649bdbe652fbd05c37e81696e6991c2b129161fd90223c56fa9c7965e", - "dd984076dba89d32d5c3a47e3fd53b04d2c4263f20781d2dd3e7c68c0be2d997", - "1ab767240d156568db3a836167097c81599514a3e84440081fd2eecdbd3349b8", - "c70c8eacd8d1d9da965b2d9836e7b9ed47ee67e9b75ecf20e8b4581aed13a328", - "23226558116e9fef05e4d257afa4c6bbf01f73bb0c1589ae7cec2eebd92cc491", - "9671f9586b38f1dc1a6b715c2c5e4e4f30e2247b541b4e91f8f716e8a0f6c9e4", - "daf91acfb23f6b3a950df087d80774ebb74fe194e01e048e974a643093ae656d", - "81a7f9c699cb9b9123eeccd81de1cae1b620033f337b367590c204628f392cdc", - "4ee69f95f7eeac9d4c3768905f33172cf7651726b90effa867d16365af440874", - "9f99233049815668ef9a20fa14e912f42d6a16dc276b6e5fb560afea1c2406fe", - "53572b544b88b1876a15ef324f02ba4a594fb341ee1c88a7d904b2658ba73789", - "46f5570912d57c022e7abcd9dbda9478531c4f1539b1eb2603ba009bf2c2fc83", - "a341122217619a4c34542a573481087bb07c41125be77af4aa56b6a13d4e43bc", - "73df531d66828721c8440b0a008a4de62b331693a01a12706ea624f9d187eace", - "37b97b5300332288f518a0e46582a989a88d75ceb3f5795fb70f95a8db88fa64", - - "fc8248f9d5d054d09961e2c27d9b6dd87dd7461f654f72ddce8547954f7e8761", - "0bd5e40ef75d8a76882386eac6569ef4e04488ea1b74f1f3947c0219d25788c4", - "a82bd2475912be3aa5f85833e6b2b4973df2eae80447dc70e0f35efb149c7844", - "c7778b52f808b29d7f8de036a7686e2e4bf8cbeda360ed432a26c089efbeb31b", - "9ec8e3f9b6f097c65e292bc6218e53656cc21bf2b4b27e3b716b921a93f08a0a", - "a90ff245459a7066d7132a193b864c40fa4eb2597100847780c8029364a7fcdd", - "e299b1783031142cf4f228b844d6c00d3045962d50910b8fa995f15781562e6c", - "3c9951cd7cba4ec48eb04d49a2e811798f30d8d5330ffc901ed7d59c712ae87e", - "7c20c3e55d771a9603397fcef5c32547fbf99b37aa6030836cd1bc405bc183c4", - "65cfe144d953b9141312f294fff70067e2ca27515449e37297c4b87b24956898", - "5c34237e466740aa62a6d2f060c839b737094c4c8cfc92343ebb8229ce45f51e", - "5835af622801fb643601b350c23966042af115dfe4ab34c6e0b33200bf62b9bb", - "9a3a919b2b9dde7ff91c531f928f4141ff22afc1a9185b98c1a8075f57025a29", - "1f203b08c54729503b536fa75faa0c127f99befa40e7a73a7c4da7d7f0955f2d", - "2b58f2ff1d421bfd063b87d3ebf2118e2e8b4bdcbedc7d888efaff3e13e3c26e", - "a1d2d2318a4fcbb3145e3972c1a6165f19914f5829a4f248bc083af5055c5acc", - "8854cb9d82913c46d738a829e8ac1271ebb8541973ca72659fbd9dd5c410a18a", - "af94b014f96ca01a04df6e22aa256fa3f13fa6eb0f55de536d2e4f226bbcd75c", - "e6ef65acaae9650d4b0693595141fd7adac13608fd0f078de4a57d11ca315102", - "0c9d308bdc166d94b85d4f2ff700e38545776e2bf933a44fbc7b023c50060298", - "0ef6356cd9a36e92847b05121dfea3513f1ba1ee4352ff3d3f700f66808b322b", - "9c8c5c2945060938c10c7f866688f384f89b18b42ea92190f4637f5de5d0dc7b", - "515c760e0833cafd0c43d3b243cda756cd83a1be6a59785166b911229785c7f0", - "e35691e6bbd4f9b79b936110ffa513001fa45bd00442fb7b7cf6b82411ba8ce4", - "386b8f59783d9b84370534c62354edc230f49dedf7bce380e43c02f7372eead4", - "b89777887c2ffc97076e69c6dada7a26d08e9d69aa56c49ac6b0266efe841622", - "76c6f9406d1306bb482d9229bcc523f9c1c5c6388a59565ce3b19e0e1f26e9df", - "32aa60577531c190e6a29d28f434367c84c2f0a62eceba5c5483a3983639d51a", - - "035d32c0eba6e76bc248a202aa23d501d35ea5f1ce113ac4f496f1274e03df54", - "fb945ec58fb74f597eab09952cf0a0b54ff35941767571a2edfabffd5545ce2b", - "a47427dc0605bc933d989b00cb92b0d036915592d36a7348b3277084e3aa8ec0", - "fe5043a086d9b27b45c4d9411013c822e9ecc7a77b6f506a58900e18c6623bd0", - "9b7f70815fa01c0b5baa42f96e00a8519562648d06d52ea6e7687291ce17a4a7", - "0376a9b2d6a13b69d9084b3312e8370b604931c0890daeba234ebcd40ce0914c", - "9c785f4feb5de0380969b4ad82a25049d4e4cbc1e57698c80a4845f0734d928b", - "37e1680dad0fd00fe1bd17633a0908699b35ac42928f198c65be812b640fa735", - "224d769ac8515e50c1a2a7f62fcd7be60ee54fb7230288c79d35d0c49924efa8", - "b43c2940feb8794c95266051e80cdfa5e3f68f9891753a1411b74baef3531bca", - "7bdaddc8802d157c747e0021eaedda99aea98f5193db6b8a7d77d13fbebe8582", - "f87c7fa28e4420a8395c77d803ab7d46a71fbe6fac3fbd955e483e2e81bf881c", - "af41b49f9865fc3f99795557f4a54a35895ea64141146c7b9d569c6455332255", - "20b7557aabfa5ae2916ee0c62aa9622436ff91d48de3c05bec3345d018932359", - "ec86c0951a797a42b9e86262d705ad22bd6ee9e2ee93e99aa4ee25be2302f0b8", - "74e103631915eb8d240e3e49e6cdbb991e90c4e63f5170e3c9d80fcfac83f06b", - "60e7fc3094f4d45dbd105d589a5bcc156eda7ce85b412833ad1814c285fcf447", - "0b92d7e3e8601cd3e098d9aef3644d6f961a82a309fffe0f35fc55defc234554", - "169ae1ec30b70da7312a5c26a20b224298426517ab531fedbac251f0ee0c66a7", - "941a5b54b2e0086d972b9c9fbbb7b35b4f09ffff215350db7c7d936106e71592" - ] + "blocks": [], + "transactions": [], + "outlookTable": {}, + "transactionIdFixTable": {} } diff --git a/packages/crypto/src/networks/devnet/genesisBlock.json b/packages/crypto/src/networks/devnet/genesisBlock.json index 846c0216ac..1b63018fd3 100644 --- a/packages/crypto/src/networks/devnet/genesisBlock.json +++ b/packages/crypto/src/networks/devnet/genesisBlock.json @@ -1,896 +1,908 @@ { - "version": 0, - "totalAmount": 12500000000000000, - "totalFee": 0, - "reward": 0, - "payloadHash": "2a44f340d76ffc3df204c5f38cd355b7496c9065a1ade2ef92071436bd72e867", - "timestamp": 0, - "numberOfTransactions": 52, - "payloadLength": 11395, - "previousBlock": null, - "generatorPublicKey": "03d3fdad9c5b25bf8880e6b519eb3611a5c0b31adebc8455f0e096175b28321aff", - "transactions": [ - { - "type": 0, - "amount": 12500000000000000, - "fee": 0, - "recipientId": "D6Z26L69gdk9qYmTv5uzk3uGepigtHY4ax", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0208e6835a8f020cfad439c059b89addc1ce21f8cab0af6e6957e22d3720bff8a4", - "signature": "304402203a3f0f80aad4e0561ae975f241f72a074245f1205d676d290d6e5630ed4c027502207b31fee68e64007c380a4b6baccd4db9b496daef5f7894676586e1347ac30a3b", - "id": "3e3817fd0c35bc36674f3874c2953fa3e35877cbcdb44a08bdc6083dbd39d572", - "senderId": "DLK7ts2DpkbeBjFamuFtHLoDAq5upDhCmf" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02511f16ffb7b7e9afc12f04f317a11d9644e4be9eb5a5f64673946ad0f6336f34", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_1", - "publicKey": "02511f16ffb7b7e9afc12f04f317a11d9644e4be9eb5a5f64673946ad0f6336f34" - } - }, - "signature": "304402205f6acbc1b91787b97a02ce8dd2f511ec8ab8786a9e3ba058173a94e80a1b4d49022044ec8f8e7a14dbb661a3d9803484d220a5038488b99befb43b6a22a5b7c499d4", - "id": "cbff39a30fea596d0cea50c78cfbb23a6d8546ef1487abe7d5023ae949357832", - "senderId": "DL6wmfnA2acPLpBjKS4zPGsSwxkTtGANsK" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03697abb61ee85e020a35a1d2701112e7e16477ac9d2eb2e8900a27995edc917a2", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_3", - "publicKey": "03697abb61ee85e020a35a1d2701112e7e16477ac9d2eb2e8900a27995edc917a2" - } - }, - "signature": "304402204cff61e3c4f0aa15a31f4a84611b7ab555a2b15ebe7012b6cfc99f711842277e022040dd69a0e6ba7e1b8be9d7dc7df64f0f23bf119f01e9babedba3851d65ba3263", - "id": "bebaa71c139fb53d5453833ac9c8f1491bcbf96be4d10ce5ea1bcf5e7f86e07d", - "senderId": "DMCAKuFyjRhZreFtgaBjV43Qtb3EzVUfVz" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "027e2269d8a770343223bedc49bab31b3c52fb4c1df6627153e6374ac23e2d878b", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_4", - "publicKey": "027e2269d8a770343223bedc49bab31b3c52fb4c1df6627153e6374ac23e2d878b" - } - }, - "signature": "304402200f86542051d29cf0c9a2dcaffa1729b7d59e85c9c01a19d00b5cf2d8193c160a02201841255da9bf8fecc6c78b868c7ebb3b450372a4fffeda0b31bdecf344fb096f", - "id": "44a36f5fcc58f2091a25c1346edf2a0a707e5d58c89be2f814f1c658fda7c559", - "senderId": "D5pVkhZbSb4UNXvfmF6j7zdau8yGxfKwSv" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03858d4d3b77c7c227f6fe3e18b5807aa476828cb712663dcd79df87e439cc07c5", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_5", - "publicKey": "03858d4d3b77c7c227f6fe3e18b5807aa476828cb712663dcd79df87e439cc07c5" - } - }, - "signature": "3045022100cdea6cbd578c8380c5ffc4635af5c9cf9a06b8ce8eeeb3a03a93936c60f0b67a022027a8efce7700d9e4192f50091cb5d72b8c18ca867df92d1d9cd40dc3475da4a2", - "id": "e12ade6f3832d1aced7ddb25bb63e06efdc1f99e3eb16c501f7cb790b107a09e", - "senderId": "DAjqLwuWd4t4rEDZ6xpk7Fcyndv4Qcr5WZ" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "023918d30ff448ec897e12b77ccd529835c78aee07db1682639320c253cc21a1c7", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_6", - "publicKey": "023918d30ff448ec897e12b77ccd529835c78aee07db1682639320c253cc21a1c7" - } - }, - "signature": "304402207b6770015a921fd6ed1d0e0faa06ede9af7e66925a9dd2ddd1553e48179cadf9022044ecaee06f0661abccdff90be1157c5410fbb42ed8e67db413201c61740d7e39", - "id": "2c5969f690dc7d8e0c6720405e172b4ae06ce186f7ba99972bbe7dd587b3b319", - "senderId": "DSMxEhoudwLYVt1jtHDu1dtisa2gS7LeCW" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03b12f99375c3b0e4f5f5c7ea74e723f0b84a6f169b47d9105ed2a179f30c82df2", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_7", - "publicKey": "03b12f99375c3b0e4f5f5c7ea74e723f0b84a6f169b47d9105ed2a179f30c82df2" - } - }, - "signature": "3045022100899b81a7fd3683fe1cb60e8ea070dda891ed22e9ce386538d1f57a79f801e90e02201592f75d7d1dfb15da63c27d190d915fca192e9d69013af7d8087fcc7662a13e", - "id": "5191f5fce08b980f0004f654f0af58eba43a112c25eac53e29778239c7c2b8ee", - "senderId": "DNv1iScT2DJBWzpJd1AFYkTx1xkAZ9XVJk" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02c3d1ae1b8fe831218f78cf09d864e60818ebdba4aacc74ecc2bcf2734aadf5ea", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_8", - "publicKey": "02c3d1ae1b8fe831218f78cf09d864e60818ebdba4aacc74ecc2bcf2734aadf5ea" - } - }, - "signature": "3045022100b71c3d87f13aafa9e4e7ecb0293daa2f40ac703a8678f58c3a3c04c57845d04802206b0c9e48051b23380c158abc441372f4074b1fb76040fef0a008fd9a9a1a948b", - "id": "269b9bf296615f4cca0812f3d15ef8f0afec02b123088e46008ce4a353def912", - "senderId": "DEHtM61jVo4uJWP23B6mGrb6p9batXCHZs" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02be5acbe305bc5382ebd7998f3f42744c793245f37a645916771ff123fb7b4ef0", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_9", - "publicKey": "02be5acbe305bc5382ebd7998f3f42744c793245f37a645916771ff123fb7b4ef0" - } - }, - "signature": "304502210094b62a07ee25d1058f5eb2c71d82acf55f62b787cb017e821eb22885f16c81bd022074d2f06ba34771e2fdb65b5d46a8ad69d05e3e2500549c8c9dd978503b8ebf2a", - "id": "588c80d5df11abff1247672f875a9c44c36b45e742187b9071f1a9e04a1ec3b0", - "senderId": "DKXY49bVxzzr3QhLpoYWPvdKcBrKfchE9h" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02e9ef70986ab6de9dbd5e1f430018bb8dea671d30c1e34af5146a48f2b73d551d", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_10", - "publicKey": "02e9ef70986ab6de9dbd5e1f430018bb8dea671d30c1e34af5146a48f2b73d551d" - } - }, - "signature": "3044022038243ef0738d6edaf8313fd51bc0f1482a055880e1a37e0d28f13e7287d3f5f802206e3cc68b90437b8b879a83c490aac772d89c8f15c28011b7409545c9e934d922", - "id": "a11ff1c1ffae49f6cc08f1b36606519f215f3ddaac5dca616f3e0858f33205e3", - "senderId": "DQEAsYqgNQ92wqmH6b1WGwsJ9JWhJtMTbQ" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "024c149adb250db9d314462e7e3628c8a63d263812d85306a530c3ee1f5ba31618", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_11", - "publicKey": "024c149adb250db9d314462e7e3628c8a63d263812d85306a530c3ee1f5ba31618" - } - }, - "signature": "3044022001e9b6ab03157a9ab0b3a882e27576bb496b0a749f2b4c9f6bfb77ae3a18d72502206f4bc9e9f4375dae548c9d480b4b149af35a3625b1aea1e9298494dcd292b935", - "id": "e5034eeb44b0762a884d346a0d8806e0776bf7194973d978e07a2247d457b6c0", - "senderId": "DM38Tc7HUZ5T41swShV2ELVtvjghVgHWLw" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "022eedf9f1cdae0cfaae635fe415b6a8f1912bc89bc3880ec41135d62cbbebd3d3", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_12", - "publicKey": "022eedf9f1cdae0cfaae635fe415b6a8f1912bc89bc3880ec41135d62cbbebd3d3" - } - }, - "signature": "30450221009bff8a22cdad663c21f587e828cf774c3b399a66fbe9a4161f22a932f0a389bb022045b1ee28e49cc95d280e4d07bc044459381c8d7dfbaed94e1bac2e22ece22f67", - "id": "831e9c84af1e61afb18fc7561681558009b51206e920495b2b708e9f65f261cd", - "senderId": "DReUcXWdCz2QLKzHM9NdZQE7fAwAyPwAmd" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02bc4cfee3716fcf191caa51c7bd2205a796b504b9ad5461864681cf1b33912003", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_13", - "publicKey": "02bc4cfee3716fcf191caa51c7bd2205a796b504b9ad5461864681cf1b33912003" - } - }, - "signature": "304402203ef34aac4fb45505b2e869e1a1f075a1967bd7f7fdb95362b33bc30862007517022028f65b2409c64c0832f1874f5e7e6173b107bff4f3737fc41c2042cf1fc9370a", - "id": "5d7cbc26b116c9daefe94965b5a64e34fd6a7e42de3619a71d0ba5976976c966", - "senderId": "DLnysb6HbtTcNpff87P5f47qVpFxYAqUSY" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03f3512aa9717b2ca83d371ed3bb2d3ff922969f3ceef92f65c060afa2bc2244fb", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_14", - "publicKey": "03f3512aa9717b2ca83d371ed3bb2d3ff922969f3ceef92f65c060afa2bc2244fb" - } - }, - "signature": "30440220763f33a056e01ee9749e60a17722138dee67b88cc1a54099e30939787ca6890c0220606b4e77d517b9e2d3f2fcf92176f081f3ce35c76ea4dc838bfe5f9122635bc8", - "id": "8ad18dfd85c61f3fbbabf8f3a0363cee55966c2cad784f96406aaeeb8d87e8ac", - "senderId": "DRqv1yELdrVc3z4ViTe9ueWiPm8Dbs5ZV2" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02e311d97f92dc879860ec0d63b344239f17149ed1700b279b5ef52d2baaa0226f", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_15", - "publicKey": "02e311d97f92dc879860ec0d63b344239f17149ed1700b279b5ef52d2baaa0226f" - } - }, - "signature": "304402207f764b224b3baa195c4385ed608ff2ae4516f00a0d26c370fbe8d2da246f31fb02205be484657a0550bbcf334fe20cca1e5d4b709d7288d497fa6f8ebc9dc08e4d91", - "id": "75604d72872f730d7c38b9d73c916e4a532408ea0074850a581f4b28bd62acdf", - "senderId": "D8vwEEvKgMPVvvK2Zwzyb5uHzRadurCcKq" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03c57b6a3eb7d01ade51f95c8ae4e8ebeb7ca7b8422ab0fb2a236de5d1a5bc6a1b", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_16", - "publicKey": "03c57b6a3eb7d01ade51f95c8ae4e8ebeb7ca7b8422ab0fb2a236de5d1a5bc6a1b" - } - }, - "signature": "304402203fabd59dfcfdf9a2789b08b3fa44685f0327e314af34c61a70ae0a857a54cb7302207a87e1d17b1526a16dad101bac823dd6c3075b9a65ea0045e9b60ffa0dba51f8", - "id": "d52d2fa7a2dde4e8f76e9a4d87c9b85100390e85e502f7f53760b062e7c58b2b", - "senderId": "DHg1jYVS23D6GP7RuhckuJsYAr6crH6c3Z" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "020aac4ec02d47d306b394b79d3351c56c1253cd67fe2c1a38ceba59b896d584d1", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_17", - "publicKey": "020aac4ec02d47d306b394b79d3351c56c1253cd67fe2c1a38ceba59b896d584d1" - } - }, - "signature": "3044022036d24450a81aa6343f4e7911301f8fa1e8a7a4d439ad140035f5033841da95a302206fa9f1721e1fe5985dce006270ae5e25f4bf128b8d007350caa7041252a68356", - "id": "d11fcfd463a40465ed61d8d624f0a4141c7a66629d0fe36d40d1b181abfe7673", - "senderId": "DRgF3PvzeGWndQjET7dZsSmnrc6uAy23ES" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02ff046250cc01d4ccb74fcf6ff9b6977f5a67539cd9c461f9d41436677f4035b7", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_18", - "publicKey": "02ff046250cc01d4ccb74fcf6ff9b6977f5a67539cd9c461f9d41436677f4035b7" - } - }, - "signature": "30440220347cba116760fab827b31ebb2169679cfdfbe6b5c104ea74a2e4e3878bb064e3022015a44c54db1909777d2d4023716de8cc30484b0659d9776400ce9d7ccb84041b", - "id": "4933cee8da17f173db0dbbd4746dde532dbadd50060dda1d571e2ff918593a54", - "senderId": "D9RmfFg3xPMdxwo4AsNA1xbBoM2eY2quBM" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03d6ba3d1132d7a74bbe9da3923168aad70146b5646c6d6505bb7f500b211b332c", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_19", - "publicKey": "03d6ba3d1132d7a74bbe9da3923168aad70146b5646c6d6505bb7f500b211b332c" - } - }, - "signature": "30450221009aacd21661b435f58c40969cbab10600c504fd5d705ee174cb9177e36f893149022049870b13972d9eaa6a3a4335d892ab2f0620c231ce90d0a9cffeaf40f9a3093f", - "id": "8cb3517ff945dad8652d439c6c4db47a040aa41359897f45d6089ad325f4e239", - "senderId": "DFjnYr6USjvtaz9VZv7agd144W77WUiWtN" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03ef692bb144c368b4844ceca3ffd30fb8c82b97b5b40220473e9009925637e9f9", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_20", - "publicKey": "03ef692bb144c368b4844ceca3ffd30fb8c82b97b5b40220473e9009925637e9f9" - } - }, - "signature": "304402203ea9c88ba6bbe508e1753b77593be7213b4fe9833ea5b09168a4c85575b5b1a702205cde0fb97c4786d9a87c3afdc3a1ea0557bcb00530f33aaeda67fc544f39e3ef", - "id": "1bc38d97669d9bab1b4f59927d1f752732ac400c80cc3e2972a59c4dc1f59423", - "senderId": "DHaJZBGNrWLzbWbVTc5TnHMJXKeT6comq9" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02aabcdb8511f55b6a28593979b726ef55b1f5bbf16a83205c2e2bfc9d8c2909e3", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_21", - "publicKey": "02aabcdb8511f55b6a28593979b726ef55b1f5bbf16a83205c2e2bfc9d8c2909e3" - } - }, - "signature": "3045022100ff5a0dca3050e01b69688c27ee08b9954b9424354fd1ba5d9f100d982d938fc402203159fecb536b4a4dcbb262e37df5649f1497ac2a5e58f54f9f2a1a5b2fb981d0", - "id": "8232a75bf0a7e2133276dc9f66c969ab8710105b5128c293d8a51efe5328f99b", - "senderId": "DT1ftBZEuQm49xzqfq1MiniwvLkFtr42KK" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03b1e1d0b5e1dbe57109d78119785e8c5985a47e282a048bdf6e8b1fe61b7c5c13", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_22", - "publicKey": "03b1e1d0b5e1dbe57109d78119785e8c5985a47e282a048bdf6e8b1fe61b7c5c13" - } - }, - "signature": "3045022100f041225c982e862436317172c593d016e6f1f6d1f5c732d3ba1806a5956015ea022079bac9a025de182905dd19e151f8414051227387e42067089c5017371585fa91", - "id": "c13a52286d075cd6ab637047dd7512514352fe25d32fc721d8cb867fd620454c", - "senderId": "DG1hN8QzbjsMdQhCjpHDQoc7BgBt7WBRum" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "030efa0d981385ad10d237588ed67eee4a245ca552673b7f137cc2e9c9dc7143d9", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_23", - "publicKey": "030efa0d981385ad10d237588ed67eee4a245ca552673b7f137cc2e9c9dc7143d9" - } - }, - "signature": "30440220478da5abd03f51d3a1b2313027624ad38a3af2e832fe4998fc70524a2032d14002200f4ded96178002713f05ef0bd80b2f5a96182ee5f9b2a4508c663c2c6169aa82", - "id": "bc9ab5bde4513df5c7ea88d2b625e2b9dc33681d3b6ac8c2e620d92032f35588", - "senderId": "DSQhC4JyfWaxsZKzF9Kfq5knVifCZA5jLK" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "0335fec08c867b80e3b71545be195e1b9876b2040d5393fc177b6edca78bde8e42", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_24", - "publicKey": "0335fec08c867b80e3b71545be195e1b9876b2040d5393fc177b6edca78bde8e42" - } - }, - "signature": "3044022001a806c7d5a40553fb13a1fde597f53a1904229d3c2ffba8bf9c86c10f436170022058403f447c5d3aa98af0e4fd111c541e33e5d9f271093fa8860ff131ed6eb6a1", - "id": "f585f7fc3b709c91128b1c2c09fb6b8d43ceb6220b85e5e1257715de748eb6d7", - "senderId": "DNKFUB9u37jWrePHMXhe3qWde69JFe3HzZ" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02a5522a8cb7bc98f8adb024f92d8455918e18d3280e427e7d73d40a03a1698b96", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_25", - "publicKey": "02a5522a8cb7bc98f8adb024f92d8455918e18d3280e427e7d73d40a03a1698b96" - } - }, - "signature": "30440220116373ef6690b97809bf2063ff3a617a5ed8ac351978ad2ffe8b2424c8f8727a02206c64f816b524f388c878fbc51a0e1229fe8e0cc774964711581055fe576965d1", - "id": "b07658aea5a70cdb8cb9001625979681d49a8b1a6989e64a5098b959a6f1f76a", - "senderId": "DFUAdEcBMCPfBq3ZjM1DViAp7oLL486zEB" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02951227bb3bc5309aeb96460dbdf945746012810bb4020f35c20feae4eea7e5d4", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_26", - "publicKey": "02951227bb3bc5309aeb96460dbdf945746012810bb4020f35c20feae4eea7e5d4" - } - }, - "signature": "3045022100fdf80150b0cc8956bfc2cbed753a52343b9b95c858620c3e59aed44630114a9e022041a34e074ae8e36ddd7ec352837395733f93eab9a77f3e4635c6739c54d22048", - "id": "f60145575d1dfaaee2d8c7927c30bae6ba7c82c4381ffef374b7a09388998052", - "senderId": "D7JJ4ZfkJDwDCwuwzhtbCFapBUCWU3HHGP" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "0259d9ca7922c277b0e7407a88703bbb98f5da43a335b0eefa6c4642f072acfe79", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_2", - "publicKey": "0259d9ca7922c277b0e7407a88703bbb98f5da43a335b0eefa6c4642f072acfe79" - } - }, - "signature": "3045022100e90fd1d6bc5572317f97de693c8186c550c81e5d2ab9314dc56572984d72967102206fcb2d63ef004a9356048ec44a083886f7ff73af5fde973da1275ef694244938", - "id": "7d1409e3032e05f3571439148ed8914ec9d7cd11943bab4170823049dbcda9ba", - "senderId": "DCZFtPKqK5iz294jyhrXPXKGRwQpGru6o5" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "027f997ab2d8874c8f06d12ef78d39e309ed887a1141d2ba0f50aec8abffaffcde", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_28", - "publicKey": "027f997ab2d8874c8f06d12ef78d39e309ed887a1141d2ba0f50aec8abffaffcde" - } - }, - "signature": "304402203aada267711da498f7c6e3714d51ae89c0f4dffdcca85740eaca3994cb36b08602206b7c29fbfdae516c5cc677b8932f9cb2112c7f231abfdd040148ddd6cbe930ed", - "id": "056716dada5916f4f8e108a49a93d9b5c4d2da5773e9d47ec81509624a46e638", - "senderId": "DK52aX793nEMRpDvRVB2euBgGXiCpRfmwQ" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02fdcb7706624aba3c050c19bdd05f74a0c746614462584dfbba3705810ba2d69c", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_29", - "publicKey": "02fdcb7706624aba3c050c19bdd05f74a0c746614462584dfbba3705810ba2d69c" - } - }, - "signature": "304502210086fe27f8a2527269b59728ddce8cefb41c4b11efb49cedadafbf364ff972beba02204df3a83777933f4cb0941748e148a3f0d2b03b5e1be93ff429b1108ba1791c04", - "id": "583e706ee292251a32526a647576a4467162ebe4dd61fdf4e9bb878083745c7d", - "senderId": "DP8LLTXWNZfafDbetQEWm5uc8YYpNz1wFF" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "0212a11582a28f178b906edbf8e8c447ce1ba86041ee731e595ae4d39ef034c2ad", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_30", - "publicKey": "0212a11582a28f178b906edbf8e8c447ce1ba86041ee731e595ae4d39ef034c2ad" - } - }, - "signature": "3044022065af2653051345aef10cebf5f98210f228af00768b094eb0f82a5a0765180ca202203c87461e7e40f17273be4638f604b01cae72c47ffe8815917d0dae6f6195709b", - "id": "4041e3a8e3760e40e7b3d0269935b7894df156dbdd08092a9dfab32a00dc0572", - "senderId": "DQUtJHQToFo9Kbgm6R9afGamvXptDzc2mi" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "027b320c5429334ecf846122492d12b898a756bf1347aa61f7bf1dcd706315a9fb", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_31", - "publicKey": "027b320c5429334ecf846122492d12b898a756bf1347aa61f7bf1dcd706315a9fb" - } - }, - "signature": "30450221009c0682b562cb4405cea7e522d7b98b628af4b33c1bfbea354d67351a0d3a066402204e1c060c533c8a8896dbfd5e6219e2a0e7d96c9d40cbbb23bbdf8bc6c0450bb8", - "id": "ffc28e2cddf4494fa4bdebd90e5b8fa41253c1faa02a95eca0d7ba6bd04ca616", - "senderId": "DJmvhhiQFSrEQCq9FUxvcLcpcBjx7K3yLt" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02ff842d25fc8eec9e1382e6468188b3fd130ab6246240fc97958ce83d6d147eaf", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_32", - "publicKey": "02ff842d25fc8eec9e1382e6468188b3fd130ab6246240fc97958ce83d6d147eaf" - } - }, - "signature": "304402206f3e7f51cea90b6180dee3dbb80a0e7fa5f06ab6266b73ae8f1cc05de077247a022017d5aa06a94eee0c3be3764fb644c02db8a0367010e726c646e75df3e41aecaf", - "id": "062b54b6fc5abdc8e7d57a98c9a8f2d0ddfd42c98c640a6242c9af10fe792d68", - "senderId": "DRDyyHNmfF2vijmA3kmMp2JvmBVMLJTTBr" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03a3c6fd74a23fbe1e02f08d9c626ebb255b48de7ba8c283ee27c9303be81a2933", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_33", - "publicKey": "03a3c6fd74a23fbe1e02f08d9c626ebb255b48de7ba8c283ee27c9303be81a2933" - } - }, - "signature": "3045022100e35cca00771ea7b1dfb141681aebfc30a698976203b2429b21d296980f278b6b02203bef1d5571dc1109fbf7eb956e411bce5d11ae06557d232a6fb7c5e8055a489b", - "id": "d1955dd9e4ebafd3c33fc2752ddeebfb5066c0e9c06275e13b3904d2db4f59ae", - "senderId": "DTKn3J3pMjtPLJxZtjvqR5T6DefPzjgGdf" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03b906102928cf97c6ddeb59cefb0e1e02105a22ab1acc3b4906214a16d494db0a", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_34", - "publicKey": "03b906102928cf97c6ddeb59cefb0e1e02105a22ab1acc3b4906214a16d494db0a" - } - }, - "signature": "3045022100901a161168ef69bc7b35610809e892d0aafe2b275f2f03752fe46d54301bd16502203b143cb62708efd18ee11cf5405a05023935ffc867c39b78397dced75aa7b0ab", - "id": "a9a246807ec148536909e633b7422dc90dc8ffe858b3146e0b3d870100ebc488", - "senderId": "DA4RAKRngGDfgEA7L6nQjYVEKcZPgi8EdP" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "034ecce71a7690b1c314f71789fb32373afb79fef870fa6ea2843be4c54812b0e5", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_35", - "publicKey": "034ecce71a7690b1c314f71789fb32373afb79fef870fa6ea2843be4c54812b0e5" - } - }, - "signature": "304402200e5bc71301bef7a018e9715f18cb705d59253ecb9ba90dca9846272f70168b6602206d5cd8f334883480502e565b1602daa4c6168f631d449faae098e773305386f2", - "id": "e15317b81533925b0c39b50feb18b865f7122621a33266f606787a2a6e97bad7", - "senderId": "DLqV4kuQMq1j9nEFoWKEF9jPQQdTh7CNWx" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03f89a2f86fe683dbbe4856a43c4c326fb2938ae2647fd334ad33261c7c2dee265", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_36", - "publicKey": "03f89a2f86fe683dbbe4856a43c4c326fb2938ae2647fd334ad33261c7c2dee265" - } - }, - "signature": "30440220310fa69482a0cea90eb033c258b1f6f012799f76fbe17abccc36c55fb33b00200220287647844189029eed0f7b30e28759d346e6cd212cf1ed20bbb3457e066f8234", - "id": "b6f546a0017599ca64784ee5fba9fa524fe861ab1a9e54249d3a87c94477f5bd", - "senderId": "DMjBzD8BP4WwzpZ8yMVGHkr1z5jsA2A5Ze" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "0349e7e2afb470994a8323e9623a6dab227c69d5f09f1a59991fd92880123ffe75", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_37", - "publicKey": "0349e7e2afb470994a8323e9623a6dab227c69d5f09f1a59991fd92880123ffe75" - } - }, - "signature": "3045022100b38c62db607d567ab0e9180dabc42662ad02b1ad86ec962af54085403bb4405702203b8b95a3941e8a825102a55af6c374364efb95f807cb146d318c2255379c132b", - "id": "1df66d5b41a604ffa298cac7a60a3cf69f8c5fb9f6e8da67cd6f8e6fcf6807c0", - "senderId": "DJic2dfPgxaGJeme1tQPpLuvkJMKo6PDfP" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02237692aa37cba4fe67d557973ed610ea175ea44d3f5cf4c3ce8ca7553ca1de17", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_38", - "publicKey": "02237692aa37cba4fe67d557973ed610ea175ea44d3f5cf4c3ce8ca7553ca1de17" - } - }, - "signature": "3044022022d6b16ab19faf9d9a86bf52e6cbe3568f2c3be19169657b49cce412b2287d4c02200ba4ec759b7d1e2fdd268111fea5ea49d54f2c5d9c04723e4b4fa64025362dfe", - "id": "3fbf00f4d88c94d736bbff3ef4722183c9b2993a0afc2b992a590b2cf93c5d64", - "senderId": "DQFLoHAkjrY7eqJrdPu2NdS8KgLQJSnP6Y" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02ac8d84d81648154f79ba64fbf64cd6ee60f385b2aa52e8eb72bc1374bf55a68c", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_39", - "publicKey": "02ac8d84d81648154f79ba64fbf64cd6ee60f385b2aa52e8eb72bc1374bf55a68c" - } - }, - "signature": "304402205097c9e4a3e3020f13753bd9749cf7263e8aed0165582f449da057a4f46d14310220699527199ff99af5ab3a81cc13d8100ae8dc85c84ddecbb2211daa7ccaff9361", - "id": "637aa19839b81f70c84ebc0e1611e18a260a55fa55af363873f7c2401a8ac18d", - "senderId": "DDU4aLrxw9VYJzrMTYtRAyDM9fKsqciiYd" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "026a423b3323de175dd82788c7eab57850c6a37ea6a470308ebadd7007baf8ceb3", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_40", - "publicKey": "026a423b3323de175dd82788c7eab57850c6a37ea6a470308ebadd7007baf8ceb3" - } - }, - "signature": "304502210099fb7d275d0e4a42febbec6279f853df99b6d4423df38df484511772e8af7e44022011933c855a3253eb1c68ae7d6c6daef095a93fc55942b77cb502f070d1559bee", - "id": "ef351a3237490b7b09d74f37340aaa8a589adee9ded564974ad230f7ca0e0aa9", - "senderId": "D8xN3Nsa3KfC3H68Ek9xnkfdSwzv8Kkh3q" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02637b15aa50fa95018609a6d7b52b025de807a41b79b164626cee87dd6f61a662", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_41", - "publicKey": "02637b15aa50fa95018609a6d7b52b025de807a41b79b164626cee87dd6f61a662" - } - }, - "signature": "3045022100923763d4651f207103a4c8c5d7cb40cc9423b66d03dc155b320fda25b6c24c860220088a9c96d50a8123856be4762284f0390dc0775f05a1bf40c54fe034c74c6cfb", - "id": "d1c6a20ba8d77777b0d3f48bd0c0c0b1dbca53c602a98645ed103ffb7a09d816", - "senderId": "DF2hqXQjZvzowY6wLxgwMeH638Cgfd9pGB" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "0346e1a1b5cb0720e863e8cf8a91dc8ed827e09fb4a4812f9f4d51f606e5359516", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_42", - "publicKey": "0346e1a1b5cb0720e863e8cf8a91dc8ed827e09fb4a4812f9f4d51f606e5359516" - } - }, - "signature": "304402202e2550948f1a31becfa76f7fc1698313cd597148ce9530fec24a03bc8b3b28d302203b75f4b837b83602acb796baebd684d8807140a445277728987325ca1d4f32a7", - "id": "0240a4a290d3973ebd34eee830567a6c8250229f5f3d67cfa7400787bfc5f158", - "senderId": "D5L5zXgvqtg7qoGimt5vYhFuf5Ued6iWVr" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "021b0f58eca7f123428a8647ffe0644a9454c510f066d3864c27d8c7ad8f5a8aa4", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_43", - "publicKey": "021b0f58eca7f123428a8647ffe0644a9454c510f066d3864c27d8c7ad8f5a8aa4" - } - }, - "signature": "3045022100bd702d74bc7a476bdb8b2eeac513fa57a2a06f7a3726933c0edc13413c1fdeb90220014682b24f98f2ad79fd53913ef4e691c4dafb5e00535c1c220529bcaa6e0998", - "id": "4b4c0d5999f70244ff66b14e77bb6f1ae3279458e784222f9b8a81b3915111ca", - "senderId": "DNpPXpZrRSKsk9aiM2p4g5PiKFHxEFCtqb" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "0335238d54c6ee73d79769d4ed43888d9b18e38c3594e656bb5a5544649d4f5ffe", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_44", - "publicKey": "0335238d54c6ee73d79769d4ed43888d9b18e38c3594e656bb5a5544649d4f5ffe" - } - }, - "signature": "3045022100d7dc0aceb038ca7a910b9337cc8fb325c9523874b4bb79d8491a6d5d413391c1022008c6f2b0bc8ade96163d577241437c6e1a376d36525a3739596cba2077abd1ed", - "id": "dd002a3a4f56a33c33cde898966887009adcf53f4cb84093d6a5670ed43d6248", - "senderId": "DLL6884s6tbNX2Lwbbsi5FNonm8GEvQ6J7" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "022e13de675e14a409ce636706c76d42857c673d8dc0dda4e5bfceffdbf86e13c9", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_45", - "publicKey": "022e13de675e14a409ce636706c76d42857c673d8dc0dda4e5bfceffdbf86e13c9" - } - }, - "signature": "3045022100a57a10ee4e79394818345347cf3a4f6c9e4f039b4fb5586da040ff34a6b96cff022078416a630aa400f9c988f8c43d4a5484d14bce536d91f3a6dede075896d32842", - "id": "c8b11bc8306047f9b368c510dbb6027a7c9b4d6df3c5505079ac334446437fd9", - "senderId": "D7u9gSS3KsykEoRys7DxsRNwHjpYoG8mqS" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "029a20963b506afabb2bd805830a46cef8d59218cd88c0dca9d2a0158045b1c3e0", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_46", - "publicKey": "029a20963b506afabb2bd805830a46cef8d59218cd88c0dca9d2a0158045b1c3e0" - } - }, - "signature": "30450221009ee57a3ff9a8db841853411ad4068065aed82d35bed8a89f80995e70848f78dc02206a6628a2a8347a91aebcb78e1d575c0a1f8754103909a8a225ce3ac834a84830", - "id": "4f5a69ef4ee644cee1e5f8c0fffba9156d26a3c27ff88531a5c3bc53d77cde41", - "senderId": "DGKgCQ1srb8HZyr47RqQqMvGZ4cDyr4eMo" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03f08761f99892996c6771761955ec41ee6cdffadd43171228f5f28f8c76423b3d", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_47", - "publicKey": "03f08761f99892996c6771761955ec41ee6cdffadd43171228f5f28f8c76423b3d" - } - }, - "signature": "3045022100d65c0d76bf792c61a8c93070cdcfcc56330f1ffb37185774d04f77115c1acbc70220092dfa82e91e4b7f47a2a6d4ee57965d80ddc82fca9758250b22c4271b5ca303", - "id": "e36de13f1ef1c5749f697a04943279d8b398c5758eb30b7c8c685d45cd2da4ac", - "senderId": "DPv1wzB2xJtJQH5PaCMnw1V9mUBugzP8yQ" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03ea97a59522c4cb4bb3420fc94555f6223813d9817dd421bf533b390a7ea140db", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_48", - "publicKey": "03ea97a59522c4cb4bb3420fc94555f6223813d9817dd421bf533b390a7ea140db" - } - }, - "signature": "304402200a378960653a3d2f8b7959828cc6591a7e345bccd78bf4467e727841504f6dd302205129a7d4ff5a695ea4c05881824540753d09343ea591f8744d408917ad81465c", - "id": "c1da6ef5cb25b778cd8401127ee15b22e2e95cd37fe6da489e8ab35dd8138eb4", - "senderId": "DMDtE1ueKfVYHs2Kmxb2XepLnzvSM47fwh" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "027710de44279aaa41f8e75d5ae61a085020c414db2dbea02bbec0e0b1f27bcd22", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_49", - "publicKey": "027710de44279aaa41f8e75d5ae61a085020c414db2dbea02bbec0e0b1f27bcd22" - } - }, - "signature": "3045022100dc408846105dd8d3849aa24b5f8ed40836f17b1c5c0d8c82977fb278f8bd9d3702200b6c097658fb5b1030f1cdbc8c3c63d402b108b4236b2bc63ddc6c6be4a05633", - "id": "f9ef93a7ff6a96ddfe6b0f0a58f5c5bd9d906e6f7b5f2c8d1372fbbab37ebf74", - "senderId": "DNPBUxxGQUKDPX3XKUXa3pc4GK8yz7L97T" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02677f73453da6073f5cf76db8f65fabc1a3b7aadc7b06027e0df709f14e097790", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_50", - "publicKey": "02677f73453da6073f5cf76db8f65fabc1a3b7aadc7b06027e0df709f14e097790" - } - }, - "signature": "3045022100e6a5a91dcd3c70f3beb78eb15432171b000606557c5c13073d155422d931b1460220073b2aaee4cc7f6788b3b1781e881f20cc163ab7f81be9d32977e2eb194d9ffe", - "id": "de4fd6a03861476286161177b6d4e4b421871d8bf393dea05c5daf2de733de56", - "senderId": "DRW3wNMA4ijPfm7KA3XtupDNb5Hb8kL4AE" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02257c58004e5ae23716d1c44beea0cca7f5b522a692df367bae9015a4f15c1670", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_51", - "publicKey": "02257c58004e5ae23716d1c44beea0cca7f5b522a692df367bae9015a4f15c1670" - } - }, - "signature": "30450221008a4abd4350b2069b2552e6b2c26d1d8fffc78914a071d98f1682de78497fbac502204b65e5b30255438e92d1980017c2fd5bbf3436839bf42d933d83a032d5b68f1d", - "id": "93288bd9dda27eaa1a395c0e575802f2c89a088c2166fd49e4dcfc8480d5812e", - "senderId": "D8vKwaX6ksU3mWg7tJDm7v1dbxy4cMo4dh" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03508436f55577f406be58a5e7e59307cea823943c5312d62f4e3f3c63966f6e7c", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_27", - "publicKey": "03508436f55577f406be58a5e7e59307cea823943c5312d62f4e3f3c63966f6e7c" - } - }, - "signature": "3045022100aa88c4528c44c168fa205cc88b240ad73bde9bd0bf7b6c3607d15cd7dd1a6bfe022024c361cf430531ddf6345fe00c40eeb4f7ebeebd853ecf927f34f465ec87d134", - "id": "7d7418341dabf8406726f30b33d22db8a6e5713a36b87f4d5c2a12e44cae2564", - "senderId": "DKY5eyQUKKYyaCfPp6MUv3Y4FW6EbNF53A" - } - ], - "height": 1, - "id": "13114381566690093367", - "blockSignature": "3044022035694a9b99a9236655c658eb07fc3b02ce5edcc24b76424a7287c54ed3822b0602203621e92defb360490610f763d85e94c2db2807a4bd7756cc8a6a585463ef7bae" + "version": 0, + "totalAmount": 6677610500000000, + "totalFee": 0, + "reward": 0, + "payloadHash": "b4e87739ca85f7eabf844a643930573e9a2dd9da291662e74d26962b5c3f0ed9", + "timestamp": 0, + "numberOfTransactions": 53, + "payloadLength": 11605, + "previousBlock": null, + "generatorPublicKey": "02c46160ff76589bb09622202f804aadd2c62add7fd3e835fc1b2afb27d9544490", + "transactions": [ + { + "type": 0, + "amount": 100000000, + "fee": 0, + "recipientId": "TdHZeuLrmkw56naX1wEmZ9dtRBiXEusJ4J", + "timestamp": 0, + "asset": {}, + "senderPublicKey": "03efbc30df58bbe9fd4dad1e2e602ecc6b3dfd87cfa0a01c99cbb41064ad719a20", + "signature": "304402203945efcc5f1b5ce6d1f0d376b6319ef9a241e8cbae31072acd287800f9c5268b0220494a09e4224eda00d91a574ae3ebf8b0677741ac2fb832756ef5dd59883bfafc", + "id": "8da30fca2d49bdac98bef5e24b00b3d060c77c91e55d1e9f055ebd18780f14a5", + "senderId": "Tv3ykk6TL9w2jkkM2D7PrNCZpLpG4fxEw1" + }, + { + "type": 0, + "amount": 6677610400000000, + "fee": 0, + "recipientId": "Tuo2S5FsZL74k8axgL73JVcNo9PSfM8kPc", + "timestamp": 0, + "asset": {}, + "senderPublicKey": "03efbc30df58bbe9fd4dad1e2e602ecc6b3dfd87cfa0a01c99cbb41064ad719a20", + "signature": "3045022100e829738f8d2963408b5037de482052706a40dc8413db97404c150efbd127433402201f70d62b55d5cfcd422075e50f5480f114670539aa49b3fa6ad82c60772c56e6", + "id": "83e3ccda70edd6cbdfbc6035ab41bef0f6cec1279ddba058bd15f2f33a1efce2", + "senderId": "Tv3ykk6TL9w2jkkM2D7PrNCZpLpG4fxEw1" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03c698409b109cb06231a4d5f1b3fee4ca53dadd2718043954e275a5a2d47dfc51", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_3", + "publicKey": "03c698409b109cb06231a4d5f1b3fee4ca53dadd2718043954e275a5a2d47dfc51" + } + }, + "signature": "3045022100a62c055de518f2f303b6c31a0c6cd38b7e9dbb8d3974020d8cf9f2284da9501302204ebbed47279983fc6420a6dc159ed9e94404b593430941dc99851d244bf886d2", + "id": "b8fedb1dc4c7d694f9d177ea9823054368db410a5b79e9ac860c0d029c55c5da", + "senderId": "TgBrgvHHUuGFzqgcxJUoVhBTFoTrPsSebj" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "039abebbfa2ee412ce5c91c9c75d4fd430685b40b26afad26d55476061ff415587", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_4", + "publicKey": "039abebbfa2ee412ce5c91c9c75d4fd430685b40b26afad26d55476061ff415587" + } + }, + "signature": "3045022100ae943ed6c103126aa102730ca00de52b7bb1891654910fa3edbdfe4cceec61b602202ad040fb64ab3cedaee5dd5ee4ba95dd8ac1924d457d047e67553b8db86cf0d7", + "id": "85013a25133c2edf6d34ae02db81eb22410f9107fadfbc18df4a0ee70d8db9e4", + "senderId": "TwXMiuxjmqmj63tiVQw9GV2q1EaZr4bZ5a" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03dcd194529267d39a6655bf3e660adfa1f5fa8815578a9dde9e101903b98e392e", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_5", + "publicKey": "03dcd194529267d39a6655bf3e660adfa1f5fa8815578a9dde9e101903b98e392e" + } + }, + "signature": "3045022100eb14babd194855ecc3dc016695fc230653f7f07ec2bc0e7bbab3e0703cce28aa02206e2d8d621fd66239d641293d16dd191f23ed15553c41b2b4d332cdf80669220c", + "id": "25a7dffba899b63f5de5aa27d0ababa1a1e101ee7a285d1609721cc50dcdce3e", + "senderId": "TbWfRarjYL2Sq3XomKsn5SzVZnh2kTTU7U" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03eb9670fc23f815427bb4e1959b3641c9d71a32e0d4f63f143ab6d643a7add105", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_6", + "publicKey": "03eb9670fc23f815427bb4e1959b3641c9d71a32e0d4f63f143ab6d643a7add105" + } + }, + "signature": "3044022056133cc2d999ac5af7bc7ca446b4be59669c37063ddb77f90eabfac38f827e900220750a679a2ba09c0d6cee3d84b6ba15c01fcdf3d30d5b90cd29aba49137e1814c", + "id": "a80100877d98a3a4531a3068d02147271ab34d0c4a923be9706a650d8968d5a5", + "senderId": "TqurrTe76wWZMvAUaXQvVFFS6rpgmXCcTN" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0245a1770bef1d9e3a1a2a7436f5376e26cf3307283052f13286a25b0c8491417f", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_7", + "publicKey": "0245a1770bef1d9e3a1a2a7436f5376e26cf3307283052f13286a25b0c8491417f" + } + }, + "signature": "304402206a3554900f6254d77e334fdad3513b4e682cc0eac7c9e4677ca3183adc2eba8302202a951c6590756638fb1c6bb4adbbe57422285d756210c2959f22a384a04b5edd", + "id": "c273f5fe39e831f2202271f150327ac1fdca87e5d94a5ad4619a3bd65e3cb8c5", + "senderId": "TbJUhMX2XWv2s8euqCKmZR28Ysb1qw22cX" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02ef7a1ce808e4804ae000fd429b801ab2a5cfc2ebd936a4f645546b567dfe7da8", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_8", + "publicKey": "02ef7a1ce808e4804ae000fd429b801ab2a5cfc2ebd936a4f645546b567dfe7da8" + } + }, + "signature": "304502210088b6e59d517d747af631766ca13dbf4a5883857a74a6670b17700022034e9db30220187bebdd2d16ebbc857d8c692db2df49b6c559317f6f245c75963242f0de800c", + "id": "25ba94f124316ab9a9aa03eacc4ad1fa90d68bd4ed656b0cc0e2e58ff337967e", + "senderId": "TumaZpAp3UMv3H8PFzmgenyMyB3C4hjVfF" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02630b72e68ec5e7b53f3e09c201a366d1eca02013169b52be904b7325e3ca95c9", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_9", + "publicKey": "02630b72e68ec5e7b53f3e09c201a366d1eca02013169b52be904b7325e3ca95c9" + } + }, + "signature": "304502210083cb8449aaa014d08ba18ef331d5030aa6ab5b8de8736f44151a5921370ef9fc02201408e544323bd4cb72624f3e444d6cd4c06cbe0c1f1576adf20054b2a9da8757", + "id": "29778ab71211b17376c8f46cba14c1de9b3423e0817bf2cab8ac56ccd5277279", + "senderId": "TtcgDMoQbsEX5NLwJR6WdF5DzaZemhQhtm" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03aa74728aba501caeb32c63bbe0276d970357ddb0db600935546f60aa9c1f2167", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_10", + "publicKey": "03aa74728aba501caeb32c63bbe0276d970357ddb0db600935546f60aa9c1f2167" + } + }, + "signature": "30450221008244c24ce63c2391ae9d8f46311f56467bf4b912f781081785d7a78e2c959ec002203443c3fc1de3962287eae74fbdac6320d6b4834fe0bb8115477c6fe99f7f94c4", + "id": "ec89d3aa3fc95c37b3784989d70ee37a31636d93bc76cf46d6478d6200aa63fa", + "senderId": "TutMaP7zMCtKiGLmNnHBHbjcBUSWM4kYRb" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02934557aba947d14f6aceea8e150bdfd89db47a77153c186fef22c921bee39eb3", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_11", + "publicKey": "02934557aba947d14f6aceea8e150bdfd89db47a77153c186fef22c921bee39eb3" + } + }, + "signature": "3044022063551e473c5206b4d5c2162a6dff977ff0765ad9b0c100076158301791aa17ab02202b665860edd5c748cfd220ef0174928c42f0b9b06bb9d6745e778f7a82db2bd9", + "id": "4ae775a95c41275b58023614a97d56e4f9e68bf60342e185baf44f63c7c9ce8d", + "senderId": "Tm3kAr3KoXYp8EMKe1iqFLuhpEDGtcH6qc" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "029e1af1c0d2e209a8f048a4ba4b0f6fc643efb909db83d9c3f9cfb93db2e39a5a", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_12", + "publicKey": "029e1af1c0d2e209a8f048a4ba4b0f6fc643efb909db83d9c3f9cfb93db2e39a5a" + } + }, + "signature": "304402200ad71f71d4007712d430203c7928733ffc9f5cab81444b573e1b9d15608a486002200490f71625fa470978cd931e725efb198310eaa3f04e6d71ce1a3cabe37f8760", + "id": "202b0ed1471c735fb17fdcf43533d70a25b1b23c2c1f4fd0bd7a0c485a5ad650", + "senderId": "TrT5ZmLu6W2BWzrh4qTwdcAcrR9fmEuU1c" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03c691f4abbf5e80d773beff927fee5fcb44602b24faac6dde0430ce3ab1fd205e", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_13", + "publicKey": "03c691f4abbf5e80d773beff927fee5fcb44602b24faac6dde0430ce3ab1fd205e" + } + }, + "signature": "304402205525673f0a9a3d183c257462b41db976840ab0336d227aa28b5d4273e911b084022031690df8a8dad596ac435facabecf612c412a6520da571ebb5c86fcfa2c993d6", + "id": "e739cd555b3e8216f54b6cda1f1bec1b66b7509b0c3e8e68be0a27dc331b8df4", + "senderId": "TsNQtbgLgN3nfpz2qo83HXgmAsPzWVfV5H" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02d50ae0fa13fe2b3197d7c10775046fbee743ff9064923e80eb78cba6b0113903", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_14", + "publicKey": "02d50ae0fa13fe2b3197d7c10775046fbee743ff9064923e80eb78cba6b0113903" + } + }, + "signature": "3045022100f3364fc27cdfcf03135fa1347e8c2088eaf22147b9e88595e2f7850d0335674e022029b4133104bee86738449ff980cb948d147ae642c4a596ac4812c7d1fa4a069b", + "id": "d3c0b41c0fa35c29d76efb4757208b6842b6221ccda863a1372441c8530c9726", + "senderId": "Tk1DM96QxbNSo7VkqVC4syEVegLM7JyX61" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03cf1e28ed07c75c4e9ccbb7c4eee41a4b36b631243974fa3ecf1aab2218dc507c", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_15", + "publicKey": "03cf1e28ed07c75c4e9ccbb7c4eee41a4b36b631243974fa3ecf1aab2218dc507c" + } + }, + "signature": "304402207570b4c507ee050f091f80d4079e8a7e3429cdd700c351ccab2af1fb648f48ab022040fb766c2f83345a7b10f5d9018826e0f7c8616a128d94334db26dae520feedc", + "id": "387b49424ab16c709e8e35f2f746a462471606891a83860ce07c7d2591bbc94d", + "senderId": "TkBujuJtTCakEQoAN9a6hfYDxKx3WDsCA9" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02fab06130a8a025dd8fbd989b860c2b7b1ffac06658049d9641c5d945e3bbd770", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_16", + "publicKey": "02fab06130a8a025dd8fbd989b860c2b7b1ffac06658049d9641c5d945e3bbd770" + } + }, + "signature": "304402203f121e31faed3cba48687fe9f143bec276495f20977553be5c49a028e6ee2d3b022077aec991153b3a53fe706adada83add380d3a72f13e159089b71bafdf27227c3", + "id": "dcae97f3b2cde10da11c4f90299f2bf7bd0946ff4d7eba22005f97c0e6797802", + "senderId": "Tffwg6bLnzaeo7W4Dnz7A5TRasApvanwFM" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0346cfba4228ad5febcb23a5fa9bf19aced334b4a23af9c107834afa5317e3858e", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_17", + "publicKey": "0346cfba4228ad5febcb23a5fa9bf19aced334b4a23af9c107834afa5317e3858e" + } + }, + "signature": "3044022036eb0d9a1534f6e7e818a72a03ff5a7e65615eaef438a1c368e51f6ae4c4dba9022073a71375c46fd89af119fbf35644aa77f8f1644d2942fd5dc4b719cf5b03aa37", + "id": "9d7a4533b97b5a3ae7d4044c9956cf671f44718046e587925b1a4f2e5deb2391", + "senderId": "TjoTdyy7sTioU52ZR5RCjPiEgo7KjFGKik" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03aa4035ba72ae8629beaedc46d4849ee4db7ef013289523266085cb3b99732bbd", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_18", + "publicKey": "03aa4035ba72ae8629beaedc46d4849ee4db7ef013289523266085cb3b99732bbd" + } + }, + "signature": "3045022100db03960f68b1f426a7a353a0153efd6928512c103dbad32ff1e54a8da653440c022076e7444abf0b1fee2bd5d333d12f5008445d82c746253c4b4168e57359877797", + "id": "9bdc15e5d0ead66e900ef49c3101de186f78d72eb0144abf3ea1245268daade5", + "senderId": "TwYmgKgCoxsXMvQVwEfxggx1cTiZJ2KNk3" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "034b25ecd0dd9d07726a7722ccd6d2c4a588814423ce96d8ef4daeb94b64ff8ecf", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_19", + "publicKey": "034b25ecd0dd9d07726a7722ccd6d2c4a588814423ce96d8ef4daeb94b64ff8ecf" + } + }, + "signature": "3044022006ad111ca65eecfd0a0f55baacd31f9faf8256c1b98efaf658abe1b4a98d9e1a022078f3a17dce863e728a11fc76f40cddccad5116b01d8782f5f4fff74929f9e1a8", + "id": "bfb5b57e3c1fed19321a138683beca9d99b82c024fc66313f9aa05b58c65709e", + "senderId": "TZMvf2Ha8cw9sBHp2EUCisFGvgoL1vKMZj" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02cdfdd6b031d91c02f9834d011662b6fe8e9b9ce9ff147756df6d400b3930f1ce", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_20", + "publicKey": "02cdfdd6b031d91c02f9834d011662b6fe8e9b9ce9ff147756df6d400b3930f1ce" + } + }, + "signature": "3045022100b1e7d5a4d49c554e8badd4196bf71785e2431accee3b4ab656695f0738a6679802204d367cd5ac8c2b0bb3367b13aeb98d6fbb783e093af57318b6fe377a33100312", + "id": "bfd311494ea614678727bf26b547c93569d08fc2456d546b664d18c239fc1c42", + "senderId": "TauBtZhP8ptvYXQXVBP9QrwxryKRuGh73U" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0360921801de79b895ccbc954bf326d7da7ee3fe0cd9c68c48b31cca4aa4d34d54", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_21", + "publicKey": "0360921801de79b895ccbc954bf326d7da7ee3fe0cd9c68c48b31cca4aa4d34d54" + } + }, + "signature": "3045022100b6171ee464500e76cec53a614120c25997325518c9d01729621266b1d20f358a0220637d9f6205445726c11d63f6db1537683c035c053df8ce459d38a089af6d9577", + "id": "a0d44f80744314453afd512cc87f8487fcb0e927c961ff4721dae43680f6a1b1", + "senderId": "TasXE8Zj8FJjnPCL6uimahcEXrpgyDUTJS" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02fe2d7523666f2a3446476997176f0e3326e6a78e20a566c724159c8e4a5b5226", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_22", + "publicKey": "02fe2d7523666f2a3446476997176f0e3326e6a78e20a566c724159c8e4a5b5226" + } + }, + "signature": "30450221008f75b15e857a6f50e5a64a672b2047703b69f5a9989105401c1ee84e1861179f022049d19070eaa380bd374a5fb7f1a48e755b2eeacace927bc6ad3e93aecccd2618", + "id": "82959c078636fef58039ac0b05442cf22412f4d2223fcbaa90f001f2acbf915d", + "senderId": "TknaYxaACXL5fH2wCWxkAaqfhviCmDQr41" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "037d2f754b2200a732d1516b6b243ac7e12ddb2d86b2d4b88065ca9babe6d54315", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_23", + "publicKey": "037d2f754b2200a732d1516b6b243ac7e12ddb2d86b2d4b88065ca9babe6d54315" + } + }, + "signature": "304402205204afcc3dd68c9ff99712e60af91f229bd9b74152547708aa4669451f45609d022021ced837c057bbbc7b44f08859faeca936ef74023910c51b5e62bae66cbcd061", + "id": "99fb8b17c4b73903a18dba822cf523031861eecc07aa00370911f4ebcd547664", + "senderId": "Tpn2TKoCpEuUfr7hrV5rCqDVRn7UD5jb76" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0315777654dc0aecf549faebe9e65275597a606121c8110a76b7506aa6149d10a6", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_24", + "publicKey": "0315777654dc0aecf549faebe9e65275597a606121c8110a76b7506aa6149d10a6" + } + }, + "signature": "3045022100ba9c6722a5811c39a5b3908679d762c44f2967ee01d990ab9cbd6ef23f26f4c60220010a13e97ae50b5daa43eab14bf8bfd2f369b952f98b2969b40a475d7e1ea36f", + "id": "21cc80bf6e930b3117db22aed65c074f1cded906733dff1dd38570d56e6177bb", + "senderId": "TuVUYRVKxDyFA1xn7J1VYqVBRZvoyzguFP" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03a3f3d3ac32f0dcdfbbeb81ea3228aa15393b1ef9af061ef47f6403b83f9d60b7", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_25", + "publicKey": "03a3f3d3ac32f0dcdfbbeb81ea3228aa15393b1ef9af061ef47f6403b83f9d60b7" + } + }, + "signature": "304402205f084d441feef04e2820062afd96704778e93948f10644efc93d5bf502e4bb56022011cd7814b05bbb897f44859a807f586bd51ae3ff73f3896832bb6c7796da7719", + "id": "1fadac71b02c17a4f61ba95dc3180f9bb624c68911a7a60b836ec30608862a34", + "senderId": "Tu4gRQqBaM8VZu1845B8hz71G7Q2kejsXq" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "022a1ca52dd8ee1771a010b45a4b23b1d6c3288af40c2aaaccc3d98a141752f801", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_26", + "publicKey": "022a1ca52dd8ee1771a010b45a4b23b1d6c3288af40c2aaaccc3d98a141752f801" + } + }, + "signature": "30450221008a889b793979a2d57bded7d3a8f3101a741773e8c584c1d0781441dcf5dbec2002203f5f914b6992cf5a58900e9451d01762d8077e83f8546771f650486092b046bb", + "id": "656f862ab41f289d8d83ad96a8fec1fd842896a44425e9a34813dc8f6084ff22", + "senderId": "TvUjLm5brfbcvkJENGYeBqDQJVsemKw2zs" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03932a0b892bb5dc51a62c6d2b68f37126a5f912f7a54a99a23454f28d11dacaed", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_2", + "publicKey": "03932a0b892bb5dc51a62c6d2b68f37126a5f912f7a54a99a23454f28d11dacaed" + } + }, + "signature": "304502210094c60fb87d454bb5a40c5113b7ca2a1390232c9cb67d7fd76d7648ea0e55649502203e52b83ae7cb51722a8d81dae3fd686c6a5a667762718106170dbe6f405f2326", + "id": "240aa93efed7d5f8ac1c5fcb818a54af8a87525388d56662d5d23e22b5532672", + "senderId": "TZSoC3gMw4fYwj4WTgkLFip1EgikRvA2q2" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "025ac89a3b718f4fefab7067aac753ca586a6b06c1d2c241c8aa99be96e10d2aee", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_28", + "publicKey": "025ac89a3b718f4fefab7067aac753ca586a6b06c1d2c241c8aa99be96e10d2aee" + } + }, + "signature": "3044022073697add5e8aaddd441f9b2e8baacb825d74fc6e49e7e7607ddc43212b97157b02204cf391f598f3adb782eb804a372a2e6b109849e57785aa625d969afbf88e5bd9", + "id": "ec0d9fbd77432e02e19564db04f65365ad91c0ecfea9cef26cf8ad6a84de10a6", + "senderId": "TinbqUhd1YoLfNiidbNgyn1s8KLqu47TMM" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02d31ad95a3084fa8c39416e011ba62188711ba2d31604d68a3e8d938206454d4a", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_29", + "publicKey": "02d31ad95a3084fa8c39416e011ba62188711ba2d31604d68a3e8d938206454d4a" + } + }, + "signature": "304402203c05b3c626176b5017f1102e952434d1f45a419d4694aa72d8c2642e64d91c04022071d894bf2d82a1800b44bfd59326777bd80ed5b4bfa363c3a1508d683cbf605c", + "id": "d186681aa25f2fdb35af0791e9de0eeb021816c694be5aabf0af7026bd8fce26", + "senderId": "TmDnS8edrrasoFa1EpKriGbkn2dbhB3nuL" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02f968cd91e3c753d2140260a3307093ab00fa8991495d649c26079f67e498b351", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_30", + "publicKey": "02f968cd91e3c753d2140260a3307093ab00fa8991495d649c26079f67e498b351" + } + }, + "signature": "3045022100c3999a3a34f4434b23ced7c0fd6d8f4afbc19e0118a321e7e126b1f39566dccc02204c04dacdcc35d745fb6caaa26822f52dd836070df423a0ae29e00a06587cf055", + "id": "8a4a23b25293262208bd9e67b68cceb28c6ea732fe35a5a70ea83b1600db58e1", + "senderId": "ThvR1SCPteBp2PUr6TyntYygnXZiwBhrqE" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03eed408e093e51084e085ec0c180db8e2d1af891caa1523841e0fd0810836855a", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_31", + "publicKey": "03eed408e093e51084e085ec0c180db8e2d1af891caa1523841e0fd0810836855a" + } + }, + "signature": "304402200859e56a889ed5288843ac1e6df19afd0430e8fb75373cd446ae86d61faa8d8002204a515c4a595c5e472b5df8a99280ba51ab6f3b21855190c3654587078b3ec3f3", + "id": "80a916007080db1728e006561dc507eea5bb01335428e8414ff22cea420bb3f3", + "senderId": "TbgMfyBjS58pedqBUUWRj2SLy6T38HyyQV" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "021f2542f9ea637dc691bd1bcb777aede7b3140cca2613a268610d6591dacb9077", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_32", + "publicKey": "021f2542f9ea637dc691bd1bcb777aede7b3140cca2613a268610d6591dacb9077" + } + }, + "signature": "30450221008d340fd953daa88a8e61becb8049651a31a934ed30c3b346d5e243b42b2308830220655da6f3d19664105495a2cde2334d4945a385ffbec6fd5f01b29122475b5ca7", + "id": "dfc7f9aa6b90de834da2eb396d68f1455ddfba62cccad188b13c216a2ea65106", + "senderId": "TdzWtz7abREbfvLmbZUSkqLXiEAeohn928" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0385a9de82cb5eb3335a5db9bd44495bea9903de716a60f93917673fec36d6356e", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_33", + "publicKey": "0385a9de82cb5eb3335a5db9bd44495bea9903de716a60f93917673fec36d6356e" + } + }, + "signature": "3044022012982e0f0db59b9f2aa4834ca76746befd870dadab50874f18d5b54e2b904061022040add08d5a4bf016a1c681e7ef7f7c082d91959fe10395811c2712fe13f0f828", + "id": "7849614c21b6d7b6f7950ff4ccef1e2bd0b8172e0ad948d071a33f18ed6ae1c5", + "senderId": "Te5vKNmyfK3qxb83JFZiJu2GP87iYASnbJ" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0352b2128e8fd2cd348aa9e1c316ff37ebbe6ef0af0570dae8ad590eec3b83fbcd", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_34", + "publicKey": "0352b2128e8fd2cd348aa9e1c316ff37ebbe6ef0af0570dae8ad590eec3b83fbcd" + } + }, + "signature": "3044022067d8501b183ecb5056dffd854e0341fe77c7dbed88094998dc1fa95fd6f3f9f702207b3f09656abf656e9816cddcc97251024fe56b890c7160064a9f5c69597d21ca", + "id": "f4e95a2a876f28bd815c86c9cec95ac1291b8f18a520ed2ef865795b942b401b", + "senderId": "To4gsDcjpPZGpN8Uiy7WMmjvDCRJPvD4Fz" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03e6c05563ce0c602d238419f3646751f7e5cafbf91e0aaa7784c2079589f0dda9", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_35", + "publicKey": "03e6c05563ce0c602d238419f3646751f7e5cafbf91e0aaa7784c2079589f0dda9" + } + }, + "signature": "30440220618d2aae5bccc9b5a90ab7e6960c8021d0290321d071ecf20529d4520451e04502201f18f679cfc792b247ef8f1439ecfb45f44a69e449ff5e7fab68069163016dde", + "id": "6b75a8e3881a020a7c4ac17c26efe579be92e36f64c11632e81348b2e8559d60", + "senderId": "TifRkiqeW8oBpGadCWUSsd5ZYmbHXz9cva" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0361db2e37bb6c08f880042634dc583f87be26feeaa6e9f91066c8ecdf263c0790", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_36", + "publicKey": "0361db2e37bb6c08f880042634dc583f87be26feeaa6e9f91066c8ecdf263c0790" + } + }, + "signature": "3045022100ae0cd49459c1829592eaa7136a4ca2cc62101b415e234a75a39f1aa5a02223df022033fed06ae1a17555170940b79b62084cbfe09f49a79d6a5d1f8188332c9e3fae", + "id": "8b90fa66cd5b4cfd76e31b7b916ac91f99d36e519a9d7f7182e8520b364d9975", + "senderId": "TdDv3tBoXg51J5x8aMKxdkB9VZzQUvEhKe" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "028f8326f4caee3317d138939b47045b871b797f8bae84d6fb9d883c24d98e05da", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_37", + "publicKey": "028f8326f4caee3317d138939b47045b871b797f8bae84d6fb9d883c24d98e05da" + } + }, + "signature": "3045022100c9ba4de49231fef5234bbf17c5a7fdab9291183e58fda2e947afae6e7fb7db770220389964aedb34d89ffd3890dbe98b74b0a7301a521ee71c5d74627490dcfb255b", + "id": "4578dd10cf49cf11d54f81cab6ab87ed99dba5ed3b7f24e99bc22346d44ebc72", + "senderId": "TgoBmiwPQJN9ZPHczb7uxSaRfR3Kg24jkD" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "030efb0f31226529ef084e4a892217c23d4eb26763319e06c8de3723349da99c93", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_38", + "publicKey": "030efb0f31226529ef084e4a892217c23d4eb26763319e06c8de3723349da99c93" + } + }, + "signature": "3044022005b01f54cac78d166dc0745dbdd471cc9fb75500545582006d61635b24e66d1702203413f581df359fe3915620e35e20732eef8b0c8205989faf59a138f6742fe7e7", + "id": "d6cf2c233132214cd4c3590df39c6433e2195471926e505ea1dbc28fdd9e18f4", + "senderId": "TwUqzFqHqss53aCYkM9SfGGw5dkkwR6f5R" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02972e9870b621f53da3a4376a520fa0a58d8c7a9d23a28e7a8c8bcfb5f7cdc7cf", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_39", + "publicKey": "02972e9870b621f53da3a4376a520fa0a58d8c7a9d23a28e7a8c8bcfb5f7cdc7cf" + } + }, + "signature": "304402203260bb9cc8882e542b452e7123b13c07500b7509187e6de166dba77bd9c89da0022044a49a3893fa94a49414a7d9b8adfb8faceaef26a9ab4aaf8582feb039eff545", + "id": "52065260e6ddc97a682c9129504d23f1a3b424c6aa5a8e2f3c3d2265aebc5ed9", + "senderId": "TjhXUUfZAioXX2mhDn2uXon5X6rizusfA9" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "026b629111d2ebf9e0b7395e3873d73edf89da6f2cb2f11fd3cfba4329aa5e3108", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_40", + "publicKey": "026b629111d2ebf9e0b7395e3873d73edf89da6f2cb2f11fd3cfba4329aa5e3108" + } + }, + "signature": "3045022100951feb8e13ea03c86930dbdea4f387b9a66b02263d22854a6ea2db27324ad4a20220580c3ff6fe1a1bc78e160b1befee2567655d07d5f376da2f89f05d941f3f2fe1", + "id": "957dd8891cf96a94f4ad3949d4f4831babdf551a582076dc56cf12a78cb0bf7a", + "senderId": "TsMdvFqVri7kggVo6y2vMjA24sqjXmzaLB" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03377f44259796541a802fdbfb7ac4ec4b3af8c1efd1cbbf9ea03ba96269c17b91", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_41", + "publicKey": "03377f44259796541a802fdbfb7ac4ec4b3af8c1efd1cbbf9ea03ba96269c17b91" + } + }, + "signature": "3045022100faafdc0c1a5d14b38c7bb6b05f12a9529be9f146942ea71ad43cd277f803819d02202d606f62e53c1af4de2b5928ff70b1442154346bb15210d482ec3eb683223501", + "id": "b390b9aba94f1b23d0a51fa2f13fd93238ced34c8f60461c4b4de1deceb52cee", + "senderId": "Txb89vPs1nS3z529PSm5x7VeNLMRdCwY7Z" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0383ad3cd5098a7f4c7c6ef6abb2b119126d4791fca9f99e9bc3608709813936e4", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_42", + "publicKey": "0383ad3cd5098a7f4c7c6ef6abb2b119126d4791fca9f99e9bc3608709813936e4" + } + }, + "signature": "3045022100e00c4947731a0e33b05435fa7593866c8bfc823aa528b275d78d79165cab9c4c02201015abb01e400ee62bab537e4fd8d14fab53ebd739862fee07436215990cebd1", + "id": "5ab9ae6c433898a01d92e9991a8f6209dcab89e6ac6f3e8ecaa92ae48a93289a", + "senderId": "TwQwmDqYT1GWyEudngDwMjUuuUt32zYe6Z" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "027276570adb92d75343bf535cec579be8d01da92327035f319fe718365b16453f", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_43", + "publicKey": "027276570adb92d75343bf535cec579be8d01da92327035f319fe718365b16453f" + } + }, + "signature": "304402206c7fbd7696ef6dacf4de7d7c67c973c40233d37c38eafcf40ac5728c93813a2502202ed6f7e89528988b537b0418e215e35e2d7388934424f6104755976c8a4a83bd", + "id": "af9127949fcbcfb5faffc952bd727b941d70fd3fd0e359feb07f74e0ec83f004", + "senderId": "TkYy3Ekbf2sqjQe48c7A9FJoeVDTmQenyG" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02bb3debdd7fe7095cbff716102963045029742cae2c0f941bbe9e108de6e73fc0", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_44", + "publicKey": "02bb3debdd7fe7095cbff716102963045029742cae2c0f941bbe9e108de6e73fc0" + } + }, + "signature": "3045022100ed2aed8f4bf4b52eca1c40ab6b0949780098e0d7d8cda9c10e4d731c2b99388a022030aa0deb029349b053c3f6916a1585e687a742c1890ac64277c79d742a35fed8", + "id": "c0fa2669bd19fea5355e530002fced074e05c33bba40c48aeede9c08a64afec2", + "senderId": "TiBpS8wnvJEXtFwnhYsSur8HsRLKCReNQN" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0274cec52b8f667430f6228c42baf76027b07c6810da195e10ee593d9ced102fef", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_45", + "publicKey": "0274cec52b8f667430f6228c42baf76027b07c6810da195e10ee593d9ced102fef" + } + }, + "signature": "304402202c2656b3dc4f940bfb2b9ff3ce46f9ed3a65342740b5e19bac9541ba5a8950fa0220049e67d427d68a4a661c0f802d4bc8796f51ae264a4e9dae6e6ad7591faef89f", + "id": "f9ad9672dec1d2c34a76e48481b64c6295b5976f7e732ac44910b8680d5a23d9", + "senderId": "TeysA4yWjE7ZUyYqv6cUDojqBXcirbzJAS" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03b7863b7d0d367fa0248f61249b5c2b735cc16b3e89b9a7ff4de2276484f24cf7", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_46", + "publicKey": "03b7863b7d0d367fa0248f61249b5c2b735cc16b3e89b9a7ff4de2276484f24cf7" + } + }, + "signature": "304402203bb8415c34e636685b86b6d202fce1d44502e6e9bb4e8403a35a8abee6b4b36f0220395f92606b28884622c8cd3626043599dc7717672dcfeaaa0efc09c54f1c0666", + "id": "e36a01bdf4233f967461f0e3669f962c3e56d731a4502dc9b673337f9934865c", + "senderId": "TdMVijcxTqHAgEmf9Sa3SzZySJA9q34vQR" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "022b572fcb957213d14cec435e4d33b2e7cc963bae54e21561e44bbe24621ad5de", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_47", + "publicKey": "022b572fcb957213d14cec435e4d33b2e7cc963bae54e21561e44bbe24621ad5de" + } + }, + "signature": "30440220310f941e711f94410b40c1ab05d498f9338d98524d97abc06944a7243968484602207a5611e6d59aa3e1cf5c040fa7e98ff5886e5c7b4957022c6c7b6decbda8a021", + "id": "00bd46de0d43b005f93ead92fcb03159f9a6da33a7a796d4cf4cab8d85a03988", + "senderId": "Th9SxyEtmGCNCccbRXV78JWrAVCjA69zta" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "034a69632b66e8e0da3dcd0cdb287e2489539c11397466e61cdcbf44e02c784415", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_48", + "publicKey": "034a69632b66e8e0da3dcd0cdb287e2489539c11397466e61cdcbf44e02c784415" + } + }, + "signature": "3045022100c20e4609b0e13285f02675cf9c81b5eec7cd5f42c6f6979aa01f3d4825a9b98002204a555cae2d49e71ea63e56f96cb22fbecea9e090e5b43ccd7fbdc50a569b3238", + "id": "5a05fb4687ac66d53f96d8ea06afc0b8fa2a068f01afcd6b42b1adfd6c559e37", + "senderId": "TvKRbGXGyNqQiTDV9fibeDykRXfgLt4X8h" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02790a5e822e41de7b6c789ec803a33884f8b79b45a2160976f1317fec7f930f7d", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_49", + "publicKey": "02790a5e822e41de7b6c789ec803a33884f8b79b45a2160976f1317fec7f930f7d" + } + }, + "signature": "3045022100fa448b00f8b80a45e2abd7d3a60713e519fc46d2b3f65c3c153b0029574ea77302205bfcdb7ca532b281770890956cfc47dc67694fdaa5140bbf119c890e252d9985", + "id": "130ceef7dde118e23c6604f2be3a6f2887bb6b27c56420542088be2a085e128a", + "senderId": "ToHR4DykL1TGAa641pJdjwm1hL8ZMDs2Jk" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "022d994f8e502bd141026d6b12de97e404853ccc82a68230432d2b4a334194d51d", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_50", + "publicKey": "022d994f8e502bd141026d6b12de97e404853ccc82a68230432d2b4a334194d51d" + } + }, + "signature": "3045022100acf120abcb46190f4d41a74e1b92506be58469204972ab3f2a0c6865ad9e1efe02203c971513d29053797e2bbf46d7e3c7b24a5a0915f14e6e8d207152ed5ed03f6f", + "id": "2527391ee99bf1aee0bfece4bf1cb7358da2a032b78b99c90c9f55f93f392a88", + "senderId": "TrtDQdSdrKRVKdgD6fnZHEzfHYjd5AbAHp" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "035e39d9d9cdfe3fce3fc3e8a4a4b03005f76db28631ac1f2a0477b0225db02136", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_51", + "publicKey": "035e39d9d9cdfe3fce3fc3e8a4a4b03005f76db28631ac1f2a0477b0225db02136" + } + }, + "signature": "304402207758578595ba15deaf7474831cbd9e24cf275d24902bafce92efb6e03363266b02206522414daaa0b61d095c86f1d9e9ea947a76f09a1c3845c02142e83d4cd5b24c", + "id": "17639d34ebee16cc7330a973c1dd52220e6dccfa9b0d21686e82677789597b4b", + "senderId": "TibvPcfN5PxuwLBk7tWDxhspiYHqoSV6wM" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03a6d98fcbd0b2e96e8cc31d47927de194eecefb325359f19822edff1723472e4a", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_1", + "publicKey": "03a6d98fcbd0b2e96e8cc31d47927de194eecefb325359f19822edff1723472e4a" + } + }, + "signature": "30450221008ac1c8d22fb0de28bd2af40b0d0ff4ebbbc115878ecd802e924ad8bbf3bec35402203b829a030f74407d932bddf8a00d007f28f82b2b9711eaa0742d8e10a54ae4a8", + "id": "dd15ea8a7b8f45ab903d34002d0307bce88897d1623f27d007e3a96d7dfde8f1", + "senderId": "Tc9CkGUjToT9Je3ok1HvBRfz1x3pwoWUoF" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02c8f12943ae46be9d0c392a2b988eb6f94d0e77e46eba48fd55a3c418475b5e0c", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_27", + "publicKey": "02c8f12943ae46be9d0c392a2b988eb6f94d0e77e46eba48fd55a3c418475b5e0c" + } + }, + "signature": "3044022036e5ff6c503323fe4f5423767120acf0fd29ae374b60d17f411fe8d880681edf02206dc0d4fd35c7c4bfcd286a0039054b708dd426c2d2084a4f4bfd250238b8d827", + "id": "670749af486bfdb0649ea8783516a857e36274253a9deef523cc95321dfb68e7", + "senderId": "TfwGGBCJrGYVDSgUitxneZa4ZwS6GRYT6u" + } + ], + "height": 1, + "id": "13288495485142943928", + "blockSignature": "3044022012ca141f2f39ca015c26e4b18c8665c3da35a22e749c9d9252a4c7e07bcfb30b0220582dc802af8a6a9ea92703ad0cfd0733672a31f0709ef10f8017a8b3b2ae3421" } diff --git a/packages/crypto/src/networks/devnet/milestones.json b/packages/crypto/src/networks/devnet/milestones.json index 33f0c4bd85..3c73e9304a 100644 --- a/packages/crypto/src/networks/devnet/milestones.json +++ b/packages/crypto/src/networks/devnet/milestones.json @@ -6,10 +6,11 @@ "blocktime": 8, "block": { "version": 0, - "maxTransactions": 50, - "maxPayload": 2097152 + "maxTransactions": 150, + "maxPayload": 6291456, + "idFullSha256": true }, - "epoch": "2017-03-21T13:00:00.000Z", + "epoch": "2018-01-01T00:00:00.000Z", "fees": { "staticFees": { "transfer": 10000000, @@ -23,28 +24,22 @@ "delegateResignation": 0 } }, - "ignoreInvalidSecondSignatureField": true + "vendorFieldLength": 255 }, { - "height": 10800, - "reward": 200000000 + "height": 151200, + "reward": 800000000 }, { - "height": 21600, - "block": { - "maxTransactions": 150, - "maxPayload": 6300000 - } + "height": 8040600, + "reward": 400000000 }, { - "height": 910000, - "block": { - "maxTransactions": 500, - "maxPayload": 21000000 - } + "height": 15930000, + "reward": 200000000 }, { - "height": 950000, - "ignoreInvalidSecondSignatureField": false + "height": 23819400, + "reward": 100000000 } ] diff --git a/packages/crypto/src/networks/devnet/network.json b/packages/crypto/src/networks/devnet/network.json index 6988d83b6a..ecee108f34 100644 --- a/packages/crypto/src/networks/devnet/network.json +++ b/packages/crypto/src/networks/devnet/network.json @@ -1,17 +1,18 @@ { "name": "devnet", - "messagePrefix": "DARK message:\n", + "messagePrefix": "DPRSN message:\n", "bip32": { - "public": 46090600, - "private": 46089520 + "public": 70617039, + "private": 70615956 }, - "pubKeyHash": 30, - "nethash": "2a44f340d76ffc3df204c5f38cd355b7496c9065a1ade2ef92071436bd72e867", - "wif": 170, - "aip20": 1, + "pubKeyHash": 31, + "nethash": "5ceaa37839023de939cfd5702584aaac09e37a904dad49c8d928b6770019a8b5", + "wif": 186, + "slip44": 111, + "aip20": 0, "client": { - "token": "DARK", - "symbol": "DѦ", - "explorer": "https://dexplorer.ark.io" + "token": "DPRSN", + "symbol": "DP", + "explorer": "https://dexplorer.persona.im" } } diff --git a/packages/crypto/src/networks/mainnet/exceptions.json b/packages/crypto/src/networks/mainnet/exceptions.json index 5c63c31763..3451f6439e 100644 --- a/packages/crypto/src/networks/mainnet/exceptions.json +++ b/packages/crypto/src/networks/mainnet/exceptions.json @@ -1,93 +1,6 @@ { - "blocks": ["10370119864814436559"], - "transactions": [ - "608c7aeba0895da4517496590896eb325a0b5d367e1b186b1c07d7651a568b9e", - "43223de192d61a341301cc831a325ffe21d3e99666c023749bd4b562652f6796", - - "2d8ed494d852880eb674c279fad64468bfccadd79d2a63cc69a5ca80116933f5", - "31de62ecd08b12b897d0ab50fb8cb07cff149b8cee7116c17ccb8b3916e2eeb7", - "12be521f2bafb28cc7ada7139d424eaf0e68f7ac20216b5840d68509badebbe8", - "cbd6862966bb1b03ba742397b7e5a88d6eefb393a362ead0d605723b840db2af", - "4f83bc708288044a6f5f2773b90e1456af2ddc50ed8f3b63e960f5f16cac7d73", - - "e54fdacca38409781abdfd273d9ab11bf04c65286311b9c17d2f73829408b561", - "38ef7225c653e9aa9e78434a30403c379318bbde514ccb7f32aff72c067043e6", - "67f9eacdde5e4f8c90f430ea6bca0972984e50d3fc404c0d85c59a2aeecacda7", - "7a31e574c93c61aecc2801b543cbccdd1776e44edfcadbc08439e61cec68a7cb", - "da7590beca2aaa04df430cd30f1ab57fef2867b7afaf2c4aba09a8902864fece", - "a2743b3ed2556a31ab1ac0551124fd507b10d86da95bf3a742744a5e55fbeb40", - "8d150b3ea9b1bc3609e763aad30089f784fa3349369d62eeeb6de62a0d3ec56f", - "834c1cdbb55f78a8c390dfa28b352671a2ee452e69842c5ae0431b909b295ea2", - "1603029ad41e4cdf22a91e7650513b1dceaf5c27a854b9ff6b66f7f9eaa51f0e", - "0977bf68d7349b9c10160c6fe7e533c650bb593514d4289dc99f6b0f9f28c504", - "24714428cbcf94b4c5a02dce5a9ed22019b07327e71873764ec945465efb56f7", - "13e5f2fe5eb5197e50662de599318bac9ae364bf78896de356c40bcc9c6cce71", - "c41555d7dec8612966803135a55550c2fe0753219fc31f6b55e7d20250f33430", - "4bc5378f069c476ca9f7aead1592eed8664a381ec40405a12ed2c5ec1db734e1", - "044bc62e540079946b95d0cb43ac8de4d4ca0f8622871bca2e3748d3be3f7487", - "605b96925202e2726a69e4076494dbcdb790bcf97740580fe19442b3b14537ef", - "09de791caa9c19349b1f085659a21767709984d461024abccbd53bea0018edf2", - "886ccddc38e922d7f931252776178a684c8d170451020f238aefcda5481224c8", - "a53dbba9884500b254833d1b7bd3fc37a89c75afbd09df3c2de1e47a02322a6a", - "9a70c55cc803b3de9beb5e976450620f673f1aec49a71bb88e618ced2a8d1ec1", - "4496c966fb8025297101d12fbc03509d47dc949a722cf11783ad00c71acf127c", - "5290b62ac6ae61e0319e98a7bf5e22264e1e42fa4199bd00ddc9ddf148ea2ed8", - "6774f5fca7be329fba7d49ed88bebea42a5d2580e44e16a21c1c8d3677d7a38b", - "50c9252f5956c693fe04bdd9538ec2cbabfab51df90b2d16e098708a7e30e0d8", - "813fd1b603094159486f0a59f1648e8ada12f0654604b2c3207daa6c934c2752", - "ec952f0edcc10d9ba8a96b3377078995e4761ceb2483e1cb86db0a51f07c8abc", - "874c7b1b9191eed223cd3ceb837bd3b144b25725777fd0ce709983e50d304475", - "978b7e61409d333a1f8a888a0dc64da19d7940aeaad6bd26262ffabe7b46da79", - "ba6856d223f16642d916b33488a3825c61ad5bea92564896febafa44026a28a6", - "8c03b130bb847ab21b59d9da807d6043d33d2f7587134d32fcfa0c460e78b36b", - "500e035ff064f19599d41e98c2725687fb8736367f85080517dbc91bc9d2f25e", - "67456af6f26a14f6f6209fee5e9b65ec517ee6f8035853d26ef4d0196eca369a", - "c9f9fa471a19515e6b57ba4c22c434044041a39ebffca7b10cde8483d1f8e716", - - "4f83bc708288044a6f5f2773b90e1456af2ddc50ed8f3b63e960f5f16cac7d73", - "2c0f79b3689cb1a065aec5494070a165b1183e93aac9f8be6d6c393d71d04979", - "fe3015fefe5b9d6b023c922c00264c92af07635374865cd25f7a4b91188d5c47", - "65aa2caf22785619d1a0f53f2dd56baba47fb02a702ee330161a89c0f148b7b2" - ], - "outlookTable": { - "5139199631254983076": "1000099631254983076", - "4683900276587456793": "1000000276587456793", - "4719273207090574361": "1000073207090574361", - "10008425497949974873": "10000425497949974873", - "3011426208694781338": "1000026208694781338", - "122506651077645039": "100006651077645039", - "5720847785115142568": "1000047785115142568", - "7018402152859193732": "1000002152859193732", - "12530635932931954947": "10000635932931954947", - "7061061305098280027": "1000061305098280027", - "3983271186026110297": "1000071186026110297", - "3546732630357730082": "1000032630357730082", - "14024378732446299587": "10000378732446299587", - "5160516564770509401": "1000016564770509401", - "241883250703033792": "100003250703033792", - "18238049267092652511": "10000049267092652511", - "3824223895435898486": "1000023895435898486", - "4888561739037785996": "1000061739037785996", - "1256478353465481084": "1000078353465481084", - "12598210368652133913": "10000210368652133913", - "17559226088420912749": "10000226088420912749", - "13894975866600060289": "10000975866600060289", - "11710672157782824154": "10000672157782824154", - "5509880884401609373": "1000080884401609373", - "11486353335769396593": "10000353335769396593", - "10147280738049458646": "10000280738049458646", - "5684621525438367021": "1000021525438367021", - "719490120693255848": "100000120693255848", - "7154018532147250826": "1000018532147250826", - "38016207884795383": "10000207884795383", - "8324387831264270399": "1000087831264270399", - "10123661368384267251": "10000661368384267251", - "2222163236406460530": "1000063236406460530", - "5059382813585250340": "1000082813585250340", - "7091362542116598855": "1000062542116598855", - "8225244493039935740": "1000044493039935740" - }, - "transactionIdFixTable": { - "ca764c01dd78f93393b02f7f6c4f0c12ed8e7ca26d3098e91d6e461a238a6b33": "80d75c7b90288246199e4a97ba726bad6639595ef92ad7c2bd14fd31563241ab" - } + "blocks": [], + "transactions": [], + "outlookTable": {}, + "transactionIdFixTable": {} } diff --git a/packages/crypto/src/networks/mainnet/genesisBlock.json b/packages/crypto/src/networks/mainnet/genesisBlock.json index 0398f6cd58..598973c123 100644 --- a/packages/crypto/src/networks/mainnet/genesisBlock.json +++ b/packages/crypto/src/networks/mainnet/genesisBlock.json @@ -1,18176 +1,896 @@ { - "version": 0, - "totalAmount": 12500000000000004, - "totalFee": 0, - "reward": 0, - "payloadHash": "6e84d08bd299ed97c212c886c98a57e36545c8f5d645ca7eeae63a8bd62d8988", - "timestamp": 0, - "numberOfTransactions": 1492, - "payloadLength": 313052, - "previousBlock": null, - "generatorPublicKey": "03a4d147a417376742f9ab78c7c3891574d19376aa62e7bbddceaf12e096e79fe0", - "transactions": [ - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AU9BgcsCBDCkzPyY9EZXqiwukYq4Kor4oX", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ed57f27cabdb01f5398b30e63e3372735ee428e17e95de675c37586b6d1a5c12022062a0040ed189a4adac6c3d105e05180f7c74e8c68ca9912b3c60286c2226f3fa", - "id": "44d9d0a3093232b9368a24af90577741df8340b93732db23b90d44f6590d3e42", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AeLpRK8rFVtBeyBVqBtdQpWDfLzaiNujKr", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022018618cfd5dd1024c0dd7677fdbddcaa6977b57f832eca130583d36480dfa452302202c067556fd93899fb0d18ea28e6f0276a778099cdde3d97d3bb8733dff965a59", - "id": "512f1aa00538b24a3ba55d65519d34cea83d753f5b2cebfd7004d5c0eaa7177a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "ARagsXvdeTHYghaQgJkwbdSkPLZ73qdMkR", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022021e056a123b4a6c30e3f30dd68ff56f4cc1a994222cf27ff5b48434947e45f300220424cbc671a54a019cc655d02b2313a324702908a4a05c86bac4ac83029bb01ef", - "id": "8bb3997878a6a359f1418cf25f31c84f660e5e6897ebd6d07549ff6a4374a44d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AT9xWcPQ8hGYuXZ8aWE57VJFohyX1TTLkH", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fd0ab0bee79152978d8d5835e2d244fa159e4957f48d602c65e35e2383c0d14a022036380dac439784075befef7f7b14734f9ed782e4be5ac7f2f4c49985b08fdce9", - "id": "30cb724924823c689058c25243d1c213b9cdb8c157eff26ee9c89fc1e705fedd", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "ATjjXXcGPTum6wogPVGb9pmimpSo4EDDEv", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202e4a8dbbc11c3628f7aa9ef92825d84cb662a20e0af724d80475bcaf64416007022063e859ef5e9f9dcb294956e14d0680bad69641d1c254ef0ccc733f25b7814573", - "id": "69e5ced4fddc54dc4b688150e8bbb5bd3cb056252708e0d03401819490bc6125", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AW58iWw1ATvzGHu338WqMYvgUhmvMMsRjF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b1a8dfbf6c37a984acb364311e22336c367c5c76741a29dae4d5f894a496738202203e93441406fe1b23ab6d8807179c2cfe9e4c33bfbc455cc30733b5237af35d60", - "id": "62a85a7d3c9c31eafba7b39b102ff4594c5dce17e5c9ed38ef897ae0970ea613", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AFxqVbsVuDfnfyd9ciRAuGq6waR5GqiC5R", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ff14b9189e4f4d6199737c9b9b00d7572b2e9a5e1d1280b0bcb02b51b233668d02207a9df93b66088faf216bdbd60f438974dd5000cd1dee92a5a391294d717fde00", - "id": "c7b221db20709d99cf25d4a5b75aedc2644c963be994f040c9582c8ca84524d5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AJjkVwkhsvsX6dVKhVxpmhRvz4CyXLEvQ3", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100aa43dec0f44ed1ae07fcca1bd4ed5075954cfe0a0bc6ae88f0a2640c9961cd1502205c750b5c957bb92e7dc032bdf01fdd2765cffa5dcdfdaec7201b8a2fd17e56b1", - "id": "647143b65234e1dd78f258cb32ba64008a1d23baea7739c02db82e60ce9147bd", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AMG1Y3LP4kZJMAtoPhsnVkpsMTJ8nnCDr3", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203519061e2b618b44181bee15f4ae561f710c6601995ee5e38149726366b3d14302200d0414c012b86429eae098b474acfa1c9f29ac4ef06d500eda8a9294c1ce14c8", - "id": "02dc2d050528586dbb9ec9dfe8a4118a21bcb3244d14e61a8cf41337925972a0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AVFkEkCmEg7cuCXoVrfvtH6mKz6XC9XnvV", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210088d431420331051cc29ee00cfe3851ac4fed969c9592a81dd0492c0dac83d0ec02200f2efd316f7506abc7c60ad6151b00977206aeacc5a3a538df94fbbb88a1bd70", - "id": "1a3522ccc27e223953d3978b891e4adc219ab41216b06eb956b138c8dd532243", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "ANstQM4LaxfsuKtFMd8vqdGte3CL9s2vMA", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b6e401941a798a09d5179752ac67c39af596f95d28aaded45a79cc8b8689bfa5022079ada29c01231e3fc2ad96050204047a7d0a9b56c56f609532bdb3d92b152e91", - "id": "574fbbd0c1f28cc923b03cd00bc9f6867fe246a645e3b261b6b9b6b041491c13", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AJqbav8MPPAe3zdzo1f471gaciQbx1SRe3", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201e4f4718c13b10300c478ea28fc4a6444f626fcd1fbe6d45f80504abd8f1e5ac022078c0fa5bd5701dc989ee3432a561e659d7a07c22e6a9eb198745d162a3eef958", - "id": "e1d3f456bc81aa88b5b9f790c383384fd24593ab4dd50b308a77a843e0de346a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AbfnTeGFiRM3m8eHcMsrqNvrpUCoCnuSzH", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fb740effa4f878b5afd3bf8202cedaa7bc4c76b5a9ec0e1791824008b08c04ab0220340e130184ea186b9c7acf058b9cb62c77fea9cbd954d5e5f0d883da8136d15c", - "id": "85c540e0e42f449a14541c4ab66a667a64a18ffb1dfc2213a143441c6d21069a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AXArbuCVSiRuYBkzCAajboxCfNiw8AyQX1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022073143336b15ab2a51da194873e14a2e08da329a325e570d3d525c764a8e546b402207055a169c2cf9b1da89bdd40b1e9a19c579865cc19e2f5e868343f594ebd87de", - "id": "0d00b60f1a26a05d52432e517e59a6e42b1597ba16547f25d5b4cc5a4d821c03", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "Aa5rKoVusA5xiyh8Git1tJUWZE48ScbCR8", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ea59db1db3462c4d08ed19920dd694e707181f63dedb48ad62991935f3f5da10022005d5981a84a82f8b7a881e50a07ffac814fa62d1052d43583de50e0ed62498a6", - "id": "bffa21418f3668024901a65e57f484ad45202ac71196e857d30b61c2a300ab6a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AbrgipEvLp1ZHNJ1GcWWAsCLNgSgDG6Lxh", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203e5fc71ee49506af5218a4d6dd3870493081984f16234dc53339e6dd411d774302200293cee5f33cc54c22c766f420a0658107339905c6d6b745e88d49be6154be18", - "id": "77456d6e6fe11bd3fb7ec636e115dd6cd105cfbc97f2c9af37513f9f74737c97", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AWLoVgSScNNB2kecswuhbY9tcrtv9kf2iC", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210089cb088a883d07418a6be847f45d2fdb1c8c09cc506b5ead7df7df3f54cb503202201b323319c4e8ae723c46886fc51ba311a722e25a0ed87a130ca57e9d6e725dd8", - "id": "79c7c34636d6bef31813438d5db5c2b02bd8a7964ec1654a2e24cc24563f5695", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AbyacFxcWS1JsokdRCx8bFsWP8f48XftmS", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bc987030f590b970eade8df22b44f13deb89a652837b3cdbd9274a27b9d97f9802204ad6718c262ab2eba80202503ef5cc9704c13b3ebce7e2e5e80489e783e34540", - "id": "8e40e07b53f46d8b42c90b18383ae19d5af69ff657c13f5f6550890eb65939e3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AYTEu82arYgRyvTgi7dbYwjodV7ignYucz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e30c20254d8518201f0a46ec347352159ae81ab2d1a47d582dfb20bbbfbfdb7c0220300c012e88a107f9268e7593f2adebcf7ab72f2d240aad37b58b139d02067f40", - "id": "23241fac8b9f633b6749f0b7788ffaf9ac8997168c55718d7c20eb688856cfbd", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AYLTbdiYWHvPvJo5Lh42MEVS4Fnepg5vgc", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207c919607f7d4b17b0dd54cd82843f4b4da3f5c5ea92f07a1794f0fd666f065f402207245dbc1d97ed651e14849c84579f11bee9dad72ec7b73dcf0b3d79cb7d68eaa", - "id": "5c78d1808a47333800b9604fc1ab2352bd847ea5fccb9f834825fee74e5aabe0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AGdHR2UZ3MJuaXWejKGoAycztyN8BFQnNG", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f6f1153d93d37483e21cbad1b6c07bebf95a8d13dca45e8c51008a69be736dd602200f81dbda9b257e6c12314bc3a20c5b1c8ba86e0deecde76bd1606f79e836e008", - "id": "fe843047a6d408de46081217cea6f5f197f6c2aba86bbacab00240f147002501", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AVi8PFHr5jFBFGWSissPFDFXUPABuBgxSa", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022002a5daa6a6ac2cba5804e8bdc21769310f76fc6501c61147f9a5d97ff35737ac02202c6886b09e65cd90b3fb5ab5c906bf98512ff823f21cba66ee684c3e55e2058a", - "id": "eba8ca8a4c458bd3e15aa956a15e4b8cdf9336bf7d1050e9ab44bc83482005cb", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AVoTzMjHaaWKDuA26ysXGU681N6Q2PQre6", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203e6a954d0044affbfb9bf8097fb12fd030fa4d7b9f921a797ddbdac7d64d357002202404106e049c35fba4483451e9522ae46e201c916d50bf6971e5ffe7bc1f0155", - "id": "3c8b8785bb4213cb8df30aee1b061fa2ef0a515b59f05abbb0fc7e1c36f2052d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "APXkAdLbLiLJDC9Ls68Y9a5ws3PcgmUDAo", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022022b5f2321cc614962cb4fd9a1eefd128986d39285b59f6e418e459139b07528f022050988e49f7d25d9a44765514d50765a0f43a47b306df56def037be36d9c97d01", - "id": "ab8a92d54c08815a6dcb5561c0dfa0e93b8552eea75e3feac096f5a5e4eff784", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AGZhXHXFUdvd7W4aWs1fYJe6XFUYeSWrd4", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a66c0a231cf6f09d6ba27d7a835a4458edac26f7efce8fe1c6b58f80f88cd00e02207f92221ca0873e10a6415e62cdeb4da836cd16336715cc861784762d387a1749", - "id": "ea59d2aca71b241d063cc41061adab57d97369fe0e9e3bda651ada6c712665d2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "Ad8UiXMZPecuNGwCXZTmF8Mysn1TchVVTf", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100af001967b21b5715f68047911026eb0cef51d14a8e0563e1633b2bf213c191cd02204b85d55c4b13255b488a661dba416f7208237dc12c114040fe54692f76a7fd57", - "id": "b8b63cd15f19ef5671309e8d571482537d88af6226248c2fa0e5bfc3750606ed", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "APQgLh8MaztT1XuWVKb6d4FKM9HMmdmDVB", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206bf4937703d0b2fc171b86ed8596ba5ec58e9282e3cc4ac269192ce670478a4002205cd8f627869c5071fb99db462c47655eafdeb1b991beb75bc48f62a56915c137", - "id": "e1a4a5601e46dca9c9e2a5a67655bf976764efe72e82d2cae07c043e46e29928", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "ANUfH6C3Jjp46pDUxWgeV6WUbWCagaVxM1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100acc09397ff3c0b38214df5bead49d458931197b22e28426826d0173594a75a0702202d22e93e84748b52edfc2e420ddbc6887b9cfae3f7b7b3d7781886f085b9618d", - "id": "e47687f406ffdc59012079854958532ae22512e2021990c0c11cede226c64d58", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AXuVpX3tAewNp5YVWvXWv3etxjrMDgaZdK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100df1255aec444d8ce3ac9cd459669412376eb92d281ec0153c7a525b43b2cd7ca022078e22a0c52eace076baf1d89bfd03771ed1b45dfea105451cb9490371be6908b", - "id": "a9301a31302384e0428be63ca0731b93ebd2a2c18534b2ce771525c4a0c1a708", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AdCa1oGLMRQqygEBGGq1AEtSV44CJ5Kbdo", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220181358f06e1a2f2d43538dd72e7ec33ab64a39c92a512f2cb08e08fa32c3e2cc02203fddae5a4c9545093c9668c95688ec07b9b7ab1be27eb3efdf0c0acc41b2bc61", - "id": "f10ea8612fdb470c754f6ba5271d0b30c52c12d3a89ddffec0151cbc90e8d6fe", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "Actv8ebcwNsbfx8MM5k2AeJidJuLL7PotT", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b2c5014c3967c971f57c995bad2385f5d3cdbef34d43304d17be215984f986de0220080cdd2e5a643d7dc8fa9c299c5456d554a2d46713ce3a8901abd94455dd5929", - "id": "2a992486b31f723a33458ca055b878e51f1c13359cc4bda87509fc124d57c6ec", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "ANeLRbMxkSguPMq9CJeMgr8xZxwZ9mbqbx", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009fddcf0b5071d43e7cfae4f9241011718d324ae16692ee674d668f09434041bd022050fd7c97e4206c2e07ef98032ed731593ec0cd8209a9debec36f39b153b752c5", - "id": "3e6debb349d1059af502804311bcb2cd37368d67b33845f9b50e4ba296d0aadc", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AR5z28tRUnvaBWC14KSBpvNJDgsVCNtagq", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220747d72aa2180e80d190ae4940dbffcad648ab743d551ee70ef221439357aa14a0220334be2dd1a004d188539af503467b37dceab37ec55ae5a035801746fa356412b", - "id": "7b97374ee88b881af118a757167c49ee0ae60b0ce88e377a200b015f87441b19", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AKeDRRgG4TdDo4Z2iCzaBZS2UcvjWKMSrC", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205882a3aa05216dc8495784c9b842e3066f331014c8b55644804deccc10d696e602200fae518daafe7c9599a3e9a4d1ab8388fd3e5751af5e946c00a0d5d7c062f82e", - "id": "0d1a17ff619a2dfd5b1d2abe09600fd1a9ae1f373ebffb70bba1dacf7a15947a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AbkDPAUDsfQJgvHn5g8AisDm9XqLAtFFai", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022016ea5da49920aec7c20e293270fc5aeb99a7a8e6020daee092ed11cf48b01a3002205d10abf6cd111443e4ed7ae84a9f29791c5441cf99dac64af09611d6dff00dca", - "id": "4888da0a60c337f287a6526c9fc86be8bd85460ac1e48aa39b5d2f5c4a4018f9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AZvjdJSG5V4WnNjPaRbDNfLD72j3rYWqbs", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202d089f163508a18c061fc42c70a79583a0081d9afb49dfa36b7f881cf73a48ab022021dfa71c41e3b7303297dcfcf63a180094f0ec58953f52e8b8c424251514f7f6", - "id": "be67562ccb6fc1d6a04791726fbcc111067d5d54076425b91380bc9442b31ea0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AbAeJ4YJU5rxuNtXpDn3E4W5E8UNHyc26a", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022015b2f957cf3fe5f690786bdec5831efe41b59ea82a357bc520500c77ab20d9cf02201ac6773a21d83bf1f0b744576b620838ad984e6954965db199fea922ca5680ec", - "id": "2101c235de152ed1a0bde3f57593362d73149e1cb1e5b4df855718c392ac245c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AY5GwZtG9sFvDzMKAQfAD1Q8EHDh4bW718", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200ad645598de2adfa46f68a9c0b849457306a8744627c9d5a085238bcf51599fa022035823c617d15fc35bf7910a88ca45e47c9b3d2aa6c9146f6c1db2b92de0d3ecb", - "id": "f9aa1c9331948314027579cf07368180d49b452bb747f153c0f25c39328d955c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AP8agRcU4WmsYhC72pBqyfLwaDU6NYKUL6", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207b203b7f10f7fcd1c29379603c765d37d9c1eadb787885211729b63606fe7b3502202862e0cdc7af6f3f2ae8d48ce56b9a76bd38ef3320bb82f21bef96c6d793eee9", - "id": "f6445a905a38bf0454327fc4cd9976bbd971994b48a55906ed3c123efc0f5370", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AaFxHxsjYyYCsZtpQwwYGvYESogJ2SHxe5", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206f4c7616b756110ad060c31c2e4402219f77bd266bbf2c0bdcb4e8969cf59cb4022028355a38d68cf842b88b67ca9d9e261ca5adf017e84b2b5bf04e7adb6062285d", - "id": "4ccaadad7365662dcda0d68a13a18ee08174e2b0ce3638de5d45586cefe1fec7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AHmBqWJgxZfaC2azkyASdrbCxF6csE7Qxx", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220088ea11151b787f4aac1c5bed478a83ed2b31a013942ee76f6c8b5a45dd1fb3d022068d99ce4db49d383fe1987682a459620153e616de651b45f092839b7afa4b29a", - "id": "600b4e8a38b45624d23f8f6a11424cd20a9ed956fa970cd317b126221c2c870b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AZengw5WND4WLC8JKz6xUDFwLz3yCKPpTC", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022001c34abe513df6f732d459e995d5ab3ac6ce064213ba79f502472fbd23d2a41b0220799e96bdbe031aa06aa9fd14cb25b49b71244298cc04f5835fd3c947bcacdb61", - "id": "fca76de221fc1f70b642f30c4e5ea024f5ff886fbbe2f51f4ac7fac4bce94629", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AeLV4NUMsPJW1nvHquBWPFVKCWirs1pshf", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d37946b7e677220e9333aa5d428e8b82a8553d2b3e0c1adbd8551a75112b54ee0220465fdfad076cdde91b5642305ee4c237c4093dcc210b9445c374660b1a11ae40", - "id": "6680f73be6c0d1f96e187440bf3575eed655477e15d8f1da85388453f5c9390a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AJrXd5u3y6FH5HktH2jgkLQHjgD9ZztMtk", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a97bd4beb5444b5c2dea233a7dfc11c5ccb705a88799a7386f188a159477ef56022011875cadccf32228d693ebf24b6a1acbd574b615dabb2763e4a29b75591dc75e", - "id": "0520f4b24257ba8ad735613737ac0f0f2bd593ab5c52edcac746f4bbc8c635e2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "Ad6y8ae35QWkrtiLBpiXRR5Cj2KK2u3EGX", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b6065b0dab807b36139d101154d5e16c145fcd9ee807c06e46ba60e172efedcd022030e641f2c5ee29da508ceab6649593a6cb872d89d4661b3fb2b79f48b20c4ec9", - "id": "04e36364fb8007bfe7f85914c22cbe4bad262c12631ec4f339858d82213adc7c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AQaKx7UU8857b4tJij1jb7aURUzd3GDyKt", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220540f725157e6fa9e1d5a7e495504b5007d9d554ad8f0c275e0e45a04028cf27f0220660a555068c7402672443beffc2bcaef8a85f06f9ed38ab558ede7a21e265bac", - "id": "3dbb56502f32939958b41aeaf84e9cbd4483a85b23ab32f979a3c1f0d683db10", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AGb36aHfdxvqbMqoDCnm6wVkKKtqkE3zAh", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022063d7b93cfedf0daa9255bb1ab9a3955367c5bc8941b386f548fb01db85507bb902200cdde437236daa8fc1b396fd7de9294a9cdcbfdbd22df347bd635bd66581704d", - "id": "1c99e21a5232ccecf47da6b530eba9ceef87f02baa91cb333fcb3b8ca18edccb", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "ARDkQVS4DbJnErrptyWSWdJD8gtaPEyRH8", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c1b92b570f6abf42650414541ddf44693c61835816883bc2de0c4e7997926640022015e109e9f622844d8930a8e497ea2af2fae7089a4a110ff2d3c6fca46b19a70e", - "id": "fda4e22bce9444a6bc7d952c0cab822a5408ff74a19aa8e6d47b61513e061bd3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AW91q3n1QYTn3caBm7KR35zexcJU7PgMtZ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009010a3346835bd198bd011a5a3cac7ef94685c96acd668ee92be386e9b267fe2022078a272a9552454e80ff83dd2a01b3621fce5818d71fbd6430e0848ba85cd2c32", - "id": "52cb2975b2dec5cd21beac470055a254a84169e51b1a72387757a340509a5049", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AYaYHEKaJvLLWX2NgM8VXk15zSDxV8vCgn", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100894832a480b4ce89972badf315ba89a21f9c200cf83dc402dfac475107197852022036158362b58228e69defeeed4035e9491f20e38b8435a4b8afd86718906ec42e", - "id": "1b693cf3a23efe80385fb0f8f2b7e1ec123af4f5b99795a154f7dd1aca505950", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 0, - "fee": 0, - "recipientId": "AGNMmJ5upuU38ucaG3TUsG1ESaQDExSMo4", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e2d35dbf837de046e351c2bdc8a7061edca5af3cf01d8c47ed468225a85f688e02202cd057264c2d6f17a4b0ab9d054de7e6db6349eb1ffa2beecda58960ce80b4d8", - "id": "509e2950f47ac036f4ca1545f3f3e4dee77db3756682175d292558b920948579", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 128537390, - "fee": 0, - "recipientId": "AapqpT6xF7q1Enu44UkL75c76uNuWmRWkB", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ba8eec5c24356ccc89437cfb53440d3db1929fdae0133a4744a4fc61ee1c768502202775dca5d3ac6c2c3b6d5b72e58a975cbdbdfeacf31f26d46486a420fa12bf9b", - "id": "6300b6f026c1e1657f7d7328bcbe58e5e1c6a8e56eb5f1cb1402bd1225786c90", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1470150572, - "fee": 0, - "recipientId": "ARc9Qz4v9wNbneBSNk5s4M1RKXwoMzLiug", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220084a67416a41da67ff634eab70f88443a7f3d531976c84b310a07cb0a6ced39102201f26c0d886785aaac15d5efc79b9545bf8da36ec4b4a491a2d33a937a97bb9d0", - "id": "33c5449bf830f309809f67aee50bafdfbabc3fe284aed0d5a84059baec213843", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1939749374, - "fee": 0, - "recipientId": "AcE7Xh9FTaMB9pUKQgST1BHfXnpmganLAN", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a3515ef9744947b6b3cfcc3c6bbc6ccc05b7c158ecb639b0a46b0c5d38132b3b02202d2c20ebd19a0f60da85961a6dfcdcc905858e11556a0fbe3f37a36ea286703d", - "id": "5359b1e1068625f47432ba3a1ac12fda7411225f9be163d7248c25a92080c496", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2223350287, - "fee": 0, - "recipientId": "AcGWNuiBErRUP9MeXGW3yUQrhQTNKh4keP", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207d9fc26887b0a6ebc7877d0f96cd49fb6f51e09c5f7086c8030740bd19c7f8200220229ac976a89a3f1b061d7f833c916fc76da131629d0373e20f6a10680128c246", - "id": "68e9ebe8e03c06a773223752296acaacc47503411398d75ce9aea8d1809900a1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2635948642, - "fee": 0, - "recipientId": "ASHDLexaM8z53tcrddDC9qNrG48KiykCFj", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220390fd742fd9ad375f06b2dd4be9fd28976facd9ab58a3b497f9c53627c54903e02207a13f2cf4b2e35b512d9f25344d7cbaffa7533a841b444be4131ef3ccbb9a707", - "id": "140dad637fb5e85d9bb48885be2ce2caf63599982325fceef875f5438dba6379", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3319628053, - "fee": 0, - "recipientId": "AKoFbGWi8ZpD1FKLtWbs4PFdR4H5L9mNqj", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fbaf95f5cf4ed909feefc9b0d3d82a27c6281837320174fb9c1cc65c6dfc4b3c0220571f50b5ba6c1b9effff224d5ea08a1dd79753d07487694897f5b053cc7bac56", - "id": "fc4460c527373e2cae1ef706aeb846d0d011f5fe3a15d5b29727ec6d4a65b378", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3500000000, - "fee": 0, - "recipientId": "AcT6rrsUh2T23TXVgfXxXLQKmpRbECs4N4", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ce9a0287b36c323e4dfbd845a2d0bd30cfeadbaaa4314c5055d3f4194d05aa1402207aeba8bf92955383a8bb95ce75628a258c5ce379e3bb482787b66db194bc24c6", - "id": "255952a161258cd10a8e70bb03be46b8005101f05ad0ad453e50bf8b5f1c39c1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3500000000, - "fee": 0, - "recipientId": "AYxobqMdZyUFvvX9o3ihE4iMnZWCzQBhgs", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a8ba213f88717697168ef6647a89ea7d7d1a7bb56c8b82a27f4019cb351f13b502207afb90964461534dff853f2bd8a55e36fe6660de37b0864f65e4220c45cff2b5", - "id": "4a761497352e5687757421e2cad81fd7881c45395fafd33fa5d4266d98d3b40e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3606920711, - "fee": 0, - "recipientId": "AMepHWLBpbShRDbwLcEJhyHRatqGAMDa6r", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a35fee7952af116cb4ec7b4881f9fe5056371287ee26034206d964171c76f05d022053b2d4cf6bd03c89db073a25270ce48fda9b1c98a6cf19136291b912040d8917", - "id": "5cbe722d6e53c668c603db420a577c8a88f2c57c838141d7b0d336ecf4d67541", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4373745201, - "fee": 0, - "recipientId": "AXZCfAqHYyFUpYxYgNyw6W9XVngp25rGkq", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d430485cd384e4a58efddc72ce62e203a5ce80eb29699fb293b7d378521c932d02201e0d8cd73d3c98ca4973fe45ca2d161443f045125c971304864f9dc4e7d47645", - "id": "4f29a835860ea8533c479ecaac35bf14af9c12689a4d40ad8f4b07e55efe9a91", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4410451715, - "fee": 0, - "recipientId": "ATyCrCUvZN7hJfkYWbCXgKfEZp7PB7ez6V", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e90a709462f9654e698f131cee2fe1ae82852e12909909c32bb4ad8883e5e764022065c75a2511725d489a625ce0f4321119c43d2df7831fa748d788c879cea94eca", - "id": "16f6d59d7b48c528332ba917f7710f5af66dcc647a0c129165da7508aa93e826", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4410479871, - "fee": 0, - "recipientId": "ASrrGt6HcDBYe6hTqX2p4cD1AjNfiC3vnv", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f42315460ff894d560857f7b863e22b8e9b14bde5a86ccd34c4189e37e83a8ed022036ad285ab692485715db059896f7d394382dc6ae8386627d6bfde5771a01279e", - "id": "6497901d7e0d33d9ebc7b510100ada1146f9be06e960528d789c4d62f1198855", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5445780417, - "fee": 0, - "recipientId": "APRgb6yUmpvtaCyPmLuSzXPucgPiJ6FRpJ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100cef35ed420e2dc8c5f0de5911474f0b22c0b5f0eebf9cfb453f6832892b47fe502207f52c64e0bcb8fabcfd641a8c0a680d6f6a80ce47df726e276f620131f681c70", - "id": "1ba4319ef107661772377dc6587a13c08493dc0a02499eea9f69de567fce243c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5549484283, - "fee": 0, - "recipientId": "AQqQZPw1ZNhRqmbYfVisYc6muRF1TrUk32", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100aad3e80ae4d92622e819f38872ef85c0edb22300dfbc3fe8ec67ce570c0fa45a022076ce462d4b9cc5ae6340eae28219b1949ca4e5d70eb01fb70c92d759513402b6", - "id": "ddd800bcdce22996cf22036b8de5903e7f5719c5aa9142e4aea0a79b25837df7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5618558679, - "fee": 0, - "recipientId": "AXd5pgP55H9Xd9i6ErnzM3mWpZ8hZpYaCN", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b5860683085463cbb8f828e4749c08b31e1c8a0cb5c5be8166414c7d38da33100220053a0efa7541a6a63cdb752bf3ca7eff289d4314c01c1215ee86dddd27d0c68d", - "id": "12bc04a7470e5929cadeaaa4f1ac786861eaecac4f6792b38b5bd6a8596b6395", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5677245896, - "fee": 0, - "recipientId": "AKURdwRzPFAS4XhPUtBAqKrTrLu9LPqFbj", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fcef07fa5b582bf19686f0df769c110a74e2503fa653475b82f7f5e6faa3be0b0220647b219bd0b2163b6fa363b62bdfef06f2df6006325b878455f806dfd44c09e1", - "id": "2436864a65bb862ba864aaf5e6334bba91a7674a032abd2ad9f2a3895d08309b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 6558699223, - "fee": 0, - "recipientId": "AaNyZo6YitELcMW9SyWXiG1FVTUmrK39Zc", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022078ce2baec0753ba02c05dff2efe59dbb628cd279307c7bd7d884776f2c15f35f022007f3cfe57778813c908496b147ada7c6d3531111cee9fb56053d09099a4b878d", - "id": "64277b25a02735c525a4f11417772288ac3e71f0fd75c8e00d6f40e6a2af8cb6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 6900000000, - "fee": 0, - "recipientId": "AY3adwSjUDSKysWv8Ym4UaGYeqDx9V5a1v", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200cd7794247be234bb63d915a2c777372a89d0762c3c5299178bccec15b6be0ce022048130b001b7d24136435c148838e1caa6b176b98a0fa052314e408f26f81db35", - "id": "644fdeb9e3e870bf0e6558234075ecd9be5addff36f9fec81c28aa7c8ff57a86", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 7350752858, - "fee": 0, - "recipientId": "AY9k6pyVCWzkCNdnfges2yjGJVJyhLdYw3", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220216dd68a632f1f0846cfb628921df1ff06772d478185d05c20986ba09efacc8a02207ea5c3932b37e98b491041ad0169554e3145e06a03088ca5c4e92a6fb184c6d4", - "id": "00cdc908312961a8350276e109985ee2dc325a984d92d8ad64d60dafae5e1ef6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 7350752858, - "fee": 0, - "recipientId": "AV6aU7fieAyQQhMpjAdiUPF8w1SMujpVo5", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f9e222aee99a87a56193fc9661fcb15754e43c41d3a37a1104c063b8e754d37202200c31da497f5e8e84069c9265d816d5ea8c80591836ea68ce94b4cfad308c619a", - "id": "71788c698bc0997546b1c9c2da21cdc2f391a3a295193c61582cb5788571c5c4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 8174205069, - "fee": 0, - "recipientId": "AWhKaR8aPiTxfMaMypQqt2LtfDEUGpB1kz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ce4bac79228cd04ad09239a51a423a4c0e3e0aa48feecb5de9d3ad9402b6678b02205acaba891280ef0fe4c1ea18ed56d6c0693f5a0d31ae9fd00e266e3dddb0c5d5", - "id": "586cf12649c475cf490b6f7ad6bda018032da30e87bd257ebe2b222391e932ff", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 8335753741, - "fee": 0, - "recipientId": "AW1BDJsPkEV6AUfZtk5js9RKqHYfjEnuBH", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009c055053f467f1571cc395463e23f7225580fbf5814d17756a7a421eb812c709022011f5a333a164b685998de4fbd5625c55a35c4a35e31643f8653b74f84d488836", - "id": "dba64b2e7c7f94a2afc083b73fe947c069070b534e442cd5958361ef4b22880d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 8793099579, - "fee": 0, - "recipientId": "AH2QuDHZFX3rkLFkFTBrRshjz9VAaDVu2z", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022075820ff7058679540c808d1fceea1033fef3c80fdbdc1f7409ad40e9ee22249b02207a60b839bb427497387c71965871304b2418afcc7aef71495b3bed16bbb87445", - "id": "fd79cc4dced6ab4862faf2c0b5cde19b3ecda72ac2453558001cee1e5f90af09", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 8930739717, - "fee": 0, - "recipientId": "ATiYQXzewAtwbKtNb8n5uoRpATXvASGu9C", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022043721eada610fccc21dfcc12eca7604854d55ff996a1aee8ef1b6ed6dcfc9c7c02205c1b63a8dc3317a90c41366477b12e6bbb4b26aaf1951546b956b2a019849633", - "id": "4b309d61a46af825c19faec1e116c949e0e4958bed87afe0ef3c81336e269169", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 9285850665, - "fee": 0, - "recipientId": "AL2DsLKiUjXFKBbByaLP1RsdUMeNyj8Pre", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207310e88bc29ed44d42c60cc2bfd8cd0cf41751a7c44a72d47f0ef597a312efc0022074f06ab9ddb0dd66b6095f8324a65d8e57841db18df7569427e3001222bfffd1", - "id": "fc59c5754fa17a47bb34b642ee71b99fff47e1cca8d877788490a6401c1f969b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 10062477006, - "fee": 0, - "recipientId": "AQjGegCDm4ig1o2hAmxBwWYAta74ZvS8f8", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022035956acc21b16b19f446509f805fcda6ec6aa5bb09803e9367e3459b05de6a630220310de9e98092f88787e9ccc503bf67439dcb47e4516eeac015ca5064c6c8230e", - "id": "96db83cae120bcee57cc27436207abb76202272b558e5fd7579fcda5f5f9f61d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 10090126421, - "fee": 0, - "recipientId": "AXhCBxMufzWdxQoGQi3ZPS1SQokqHr7SQ2", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202cda06e19276c0d76680c0fd5177c8e143da4863cd47f2ab4a9f9634abf0b28402205e278ca769cdafefbe74862d94df273382ba379ac0efd90c306d817951d75703", - "id": "f5a2f41b66cb6ddcbc7d68cefea3f5b884f18c2e981eeb8731280dad92d781e9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 10400000000, - "fee": 0, - "recipientId": "AbDSp8xeAfoUR6mauGYE5P3JTDXXnWYmjJ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220684bdff8dbcd3dbee5bd9d98d630db30d6e285f82f06349cd4c85500d4e20fe4022049809890d215e9041fedaa8996dcacf2b59cfa949ba44d5b19bfc0aa3d993f77", - "id": "6bcee0de136de52f7fa27f7641d3fd661bdc17069f874c189b09593936b9511a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 10469786602, - "fee": 0, - "recipientId": "ATQACwzUT92xq8ftk83nm9gc8cDELLk1wt", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203261caf9bd95edc131e4c18f7c0fd985d477314f70b85fea340501934f6968f602202c0c225421471e7ca9ba8e1bf3fa38ee5ab35187ceb5f272c6eaa36b2fcfa127", - "id": "8a8a7657c453a4eceeba12235add5d02a27dcfbd27a82238e586e673ba2d8dc2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 10792241696, - "fee": 0, - "recipientId": "AaD6oZFiPZaWDgAz3NGdjASA4Lcw1n1yFB", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f80771b2256f6e642bb27369d356716861ac83bc0d779870055e53a362bed01402201c5e896aeedc8acbe608ec78f14b87c623069b627d5470d835f07db98dd3394b", - "id": "194ee2091f2dfe779eaa985ae67489ef6f4caef181911050d2d3615292eb9345", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 10860889308, - "fee": 0, - "recipientId": "AQr31AJnzbHCTxtVgbguzDEL5VLHUqpjhb", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ffaa693ebc94e479852ec7d47a90b96f82eebaf2c00b73adfee062ec8f764851022039b50274a148a43a23f7744c30cb00a370ebbd5d39a2e8aff5d91465f3b3a95e", - "id": "177f350bb7801ca83c881fa56256c9f9d63dd8b78d508e67ee74d8455852eae8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 11300031743, - "fee": 0, - "recipientId": "AKgJL6ko3BvdJXKdRDuZ4f9kAdvmNRiq34", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d2012faa0994f60a9e9ab7049c703e223de18e4b89fc71b2204fe73b17af9708022024c5294f0424a68afc1d2975f7d300e35a4440b937f49684ceeeeb64277cc0cd", - "id": "b513d351901d269acf8389af328ed20dd0146542f83b80cb4c3ba8f8a0fb9bce", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 12028504677, - "fee": 0, - "recipientId": "APg3XHNRy3pxHmg9jnbb6iSqq67dEyeup6", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022018b80f68abdc7e6940dca597bb8d24c3a6150c3b6cedfa85f13f3e73aeefb720022047f45325bb01e0d6b9f300a80b0b5b15d5f9f453b646dd84371cc23ed63186b6", - "id": "e19f84fd9f142d50860f6725a79d8c1a14b90e19301ca793ba4a1d13bed8ab22", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 12416758078, - "fee": 0, - "recipientId": "AJZA2GSpqAAjxCBsQS6ASjryGwFvEDz4wY", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022012701d828ce4db33b2f03f4dc42720189cb3e0aa82f48ff8e0284b3a4deaa0b902207a1685bd283e1c51c42002a8041c31ee518307c4b86c7f22c78acdd863cbb4a7", - "id": "3c75b71a126df25c97986d07402e8b153919ed29dd8e86ef7b5d232dc4c27141", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 13626793145, - "fee": 0, - "recipientId": "AHUzDz27ouUBRnBB8MVYEAjaEkjzfYxAZy", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207dccfdaccd0afa9d7fe39da6737f1fa1588cfaa83011f9913e46f9e0bc2d60f5022030b46c49b11c5513d7041963c76394f6a643f60fc9f76af2549ea72758d0d33e", - "id": "ff635c6f6988222c2dfe24ebecd0ce33f5c9310dda198657343c16d9c2c73163", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 13900000000, - "fee": 0, - "recipientId": "Aezpc3WQ487fMYKeoH5CxF3X1BQpG3Z1Tv", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ee21909eedef0a8e7c587263c5a6d8430dc487a1b96ed2d5c662e69c9cc6cc610220341bac16dd02f854fc19f9f3af37489c0b4f4035bf309bc6ef81f22997a3b814", - "id": "06f9cae8f3f46a0f3fe4f1b53efe34cc8c502bd1fbbb2fd15d52d8d0cef3846d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 13900000000, - "fee": 0, - "recipientId": "AUhaVctz4y38gYtk8kAH5iiHsUZJyiV9QK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100cc8a312b243e021212b170071f32ce4174013460e275166c9f17020e769de2c202206b71d09fdc88f4ac32677c19ccaa21408045e1756ac5d7ab4b94352dd21ed038", - "id": "e5a8d56567089b515a7878383ea67dea1690fb4624e8870f566848a28fd2467e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 14407475602, - "fee": 0, - "recipientId": "AN5fzWHTJm6LuJjU2iLqKyS2HGrz7n1awJ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f6203ac00161224464cc3c4ec7df00bf57446cbc9b19d96b4895df4a550bc126022059ec4181988c1f19ae9e790098d17bafffb2668d11b4aa2dd6581abf4516de0a", - "id": "817ab520c018dbf31f24790d8ca23e5f5c1f6e598cf204c58427289dd9c7299a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 14554490659, - "fee": 0, - "recipientId": "AWQzdRCybJwnWsJcB4Ux1ZWCcudmuZj29L", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f61c81d36b176998d3c00b6490c0ecc79726693b04f7f32bdd0522076602e99e02204d8bffead0b7adc26cb5245c5a8de9db29909a4982bc1d371db83e69e005864f", - "id": "397f2509d05348aa49de4a15f4d3adec5379ef12521541ba3388d2c73d45ed35", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 14561654186, - "fee": 0, - "recipientId": "AdBh5du8W41f6JXJxFVmHE5x97cRG9kojq", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b15dbd1e14d3a4ec461cf52edf840144bb0094e09e2a9d1b91f9a9669f6b929f022038ae77df2e975e856a57f656bd04f4fd8b08b11166503de53affdf50da0891f5", - "id": "39d14c1d51ac1509fc9dab4be8b3b40f540c12b11392fc7fb45ed208f0696ca7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 14701505716, - "fee": 0, - "recipientId": "AaoM7ppRsH58K2ABYH1YxSyBKW7z3FrDpY", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022049720a06d232f1232cd8f09e17128721f32bac33a8b210baa322ff6bd813f5520220402aa73d01e414a5953260b79946172ecb53e02326c9baef65c0d6a80e71afe4", - "id": "6d93427a7be1230f2faed5c635893d0f7e376b9bf13fec2ebbd7d6d314bc7e7c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15000000000, - "fee": 0, - "recipientId": "AM16EJe7VJpQtFPkoYLyatgNDJmaoNW1EK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ab3dded9d9a320de89ad11a137ec45cdc6caa558b1ed312c2cfab6a5e73028df022050adffc944953f798ddb791b9f233e1503856f6c16867e06cb91fc18f503685b", - "id": "a9e8e4e49962cffc045abba3b6eaaa77f2955daa6c3b2bb62497f861e26c5b04", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15000000000, - "fee": 0, - "recipientId": "AUPFE3QxTSpq61H8T5pumPcZNBtxbPSCvr", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bc62bb643a1344403854746bbae7bffc44228db69528c485a1bac9b44051160302207628f7a3ca9b230a77dfa13714572f0f28d421356288fce43d695753077173a2", - "id": "c4ced95ee40260f04bb935b7709c4a19c969b77311dcc75537165c30f43793a4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15000000000, - "fee": 0, - "recipientId": "ALeKWmZh3XGjdgSboRyFU5p1k1BQpuriwt", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203abcccd1b15a9857f111db5dfbc65135ae361ff3548a94cdcf9d18f38f1ccc4d022064f4dfbb1a13e532ea2d629ba4a4d5cc85622c18e6f5fea4c5cca9de6e8aeca9", - "id": "110689874c1ab40dd0cdb8af84630a1b89074c4f751e662105017f5e351e1841", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15000000000, - "fee": 0, - "recipientId": "Ac9tpzJ5h72V7rCobsaLxeC2Y4aHmki9EK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200a67c0321177682ad46b56c287807a6c6fb3c68cb35e7fea4325c26b1ff3c3b0022038d12784c4b342b950f43dfc5592d57703c33e331e867a33fc0bf5d6243406e3", - "id": "3293e055b85c9c3a34f2099261032891a326cbd39541a80dab24cc4c5efff8c8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15000000000, - "fee": 0, - "recipientId": "AZ8hMVpGVT1hyHt8nonk5ip8fKFCST7KZ8", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e1d4503779cf4c4e9e90579b2de7eea38a344ae1d1a5be81961309118b13ee7202207b977b82d13e913e0f4f9f37ac713b8deb3ea5b752cd3495af4a00a99c62f853", - "id": "b1d086b1b1bdfb25160c121232d62f2246e0f2125f557f7657439def1a5b473b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15000000000, - "fee": 0, - "recipientId": "AXxBftbe7ARvVnaLTpXyY6bSWSnL1b7iQw", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022057b0ec2903cf6d7741db49ae7ed7e6243e2bda73f878c5ff93e78b9e9941169402201c199cf06a81408eaafa26c8dc6195f243fbfd193076df3ee362e502b555ba8c", - "id": "5555d7f9855671019c47805a4aa14c3054fd4aef01fa161cb7d7e3fc79388b0a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15000000000, - "fee": 0, - "recipientId": "AcB6nKH6ueGiN1ygXurgkdXUVTNGPrAiVA", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b9a86d98d13a708b647d00ca9a28732af3707ed3ea34b173e66a20340666eb9802204de9b88e185c87055b454f146b8472d6513883ff065ebb251c155e59ac56ce28", - "id": "3922852d56b26b389bb1cbcfaf3235d2721a9e65e72185946dcbb46e09de2889", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15000000000, - "fee": 0, - "recipientId": "AZJByMD3mzvS1ux4emnxrJbYAf2mEsS8bu", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e7c70ded41dc8ff70b306e5fe5a01fcbf38057d2a38875e9618deecbd5e1bef40220031687e7a7286f4856e056fb86a600c5fe06cfd05d1373ad451a8e33c3d7ffb6", - "id": "dd619abb201619f8e6361f54aff5d95ab8a45f1b75c866e86a68b4319094bb01", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15000000000, - "fee": 0, - "recipientId": "ARVv9D5LedrdyjyuLPM45ALNowdWZCu9Ww", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022029337e12c30c5a1f3ea0b17a53dcc263bbb88343f140765e5dd5b7661cb7249802206c50a6520a2b47313a614d9d657199b5bd22f6a58c10374bde951078ab66bdf4", - "id": "271e64895989fc542cee02a54399c1e8dfab53f596658a095d7f3818f9d147c6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15000000000, - "fee": 0, - "recipientId": "AJ6Swgm7ABDGZd7vdBuAv3xHL64MGSFZ1d", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220640d9872265a1f6b0649c593e9850bb77023c1122e0483e03924cc44d8421cab022063368396d31392bd0fce5b5aeb2d0a745a4462ee3b47bea7499a8bfdbab3e218", - "id": "b1c51b691bd763cf532b060f04238af305a3965f2755d3b8295c1145c2c78acd", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15000000000, - "fee": 0, - "recipientId": "AbiXRAWJQvtxiEMzVZm45Jj7dTYF6amMx8", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008a0d3a40ba64688ce776449872561a1887d272b885ed9dc77464f5e0e9044ae5022062db52bcbf422b12bebdaffbf4f207f0240b803478764aade7c5440b0efdf413", - "id": "4616809d23f8caeac00a90f33547db0d3c31b6d21dc0c6d1223a2f2b6f8acb9b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15000000000, - "fee": 0, - "recipientId": "AQd294jzG4M7snWPK4chtyrKDTGBN3xU37", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220073c9db4fff5e1b5d59f7b36a29d250ccd0c352d8e30f42d641c0aecb9fcbb780220304f3f1237fbfdd6feb055a4d193a2a24f3d78d36891f86c752059704cb87d84", - "id": "8356eea142b2f8dc858aaf9ab5038dbfc8f71f6a6fae25eb0f9a1e7b0c9c02b8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15000000000, - "fee": 0, - "recipientId": "AJ4WmzahkzX1mzQGQ2ASfXUwVNsTr2nK8c", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100beb99fe85584c1db0e164c49ad211ef99e09b62fecb4ab5bd388b82f3069a3b602206657572320712fa459da101ea422229843f65f7c63cd0f2ec86d49aec3895cb4", - "id": "d3e6dae3d4d3bcad431182bcac5dff365e0ef615c1bc44be29776b9b62eaa847", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15000000000, - "fee": 0, - "recipientId": "ANZyNXsDsx8pCWwodv4VBp5vQotCgFSLpQ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c1b2099ba3d7dcc366b013be5a09fce01599bb707d25c8ca4b8112c1fa65eb1a02203468b56d9ed1247cf21bfa67fd83307eb6c78741f51f24d5722033249193300d", - "id": "eb1c6fbc04b75fdb056432946f90715f02c4a1f3cbba7a1da1e11cf923d9c26f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15000000000, - "fee": 0, - "recipientId": "AGZ466UoZ8rmMwYqCobkJieDn5WnmuNJBT", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220115463a85eedde299e2181968838add9326a9f5f11a066e2de1063372d96d2b102203299c9a0998b75353b474a383f6a8ee5636964ded2265284924a1810dc8a2e6b", - "id": "42362155399528b008cb1945e8271cda6ab8232ac38c1a9f1ba092722162d5ef", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15000000000, - "fee": 0, - "recipientId": "AQK3Ew5GGnHTyrFyt4fZRY6xobNoiWAa9S", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220232b18ce1bb8fec9f105de89b07be45b4851032fd57adb11227c7b408fe4910002204fc98352522425910dedc1a10876da390acdf2737a3efeef36afc0b77c26c114", - "id": "01899d20bf6bb2163d05c2dfb1800fc9abfd8fda8c708b313dd38c44eed60e27", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15000000000, - "fee": 0, - "recipientId": "AKLLCyWEnnsXPLxQ1S91J76WXL2YsgCxo8", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009b8269539e07b9055f9cab99b49218c0535124d99a366cba09a674a31371e82902203b6b6cf090facfbda718b53f181e046bab31040ed1a6bcdc702c519b2fec40df", - "id": "053d16a85becee53a301904de426d478499b1de157a9f15db98f91c4783173f1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15000000000, - "fee": 0, - "recipientId": "AZ36zcRJmdUJ64NvN1M2T9pzv7dLAWtp6U", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008a664c0db5cdf291bb0da5dd05b22a539f8b538286650c2f7dc91def82ecf204022035033583b9bf16e4b0e3db65b4125c567e0591a8c69b5739ee158b55f8c842c9", - "id": "968c02469edccfcc11797a12463cef24aa7ae281aa648854fbff46b6634b5279", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15000000000, - "fee": 0, - "recipientId": "AYhCgzBpHXC9kgRCu9FUyRpjtqvfqNSr4o", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f207cdc66a586a04eb9e7a5c777c4ea37434b73dde96b05a0c7ac666064d69c7022058eb07e5f8adac444c5eb113d25d09da6fa42e9f7a3a5ae778f479c953039610", - "id": "67b0005a47824abec015c2799d99ddb2036bbf932a36cf05db7575fbaee92098", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15155129039, - "fee": 0, - "recipientId": "AN5x6WfeFwtvyqDvpaN6C1MusAvSCzZU45", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f2577c3278f2aae85a9cbce9b581c9c094d2f33b2e8173ff6b4091a12008da9a0220753f0064d21a0b871807911194832c3c255060f3a6dd7730fcd6538de884392b", - "id": "c2f08ecf56771833549e92030bc839761e353fd88330cd7e414f9e5e33869b99", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15155129039, - "fee": 0, - "recipientId": "AcgtP5SQDHp9gjDwe4KPVVE83JLGwWGvUG", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210096dd68b49e599249c0d35342a8375ef543fa62670c6f67602e2415db4b788cb302202cc2edc7b1b318c4c127acc7fdcf34c7a5385d078a683cb6f10593272091a6a0", - "id": "887cc5bf0335496a821123b14efb9b9e989645f90911c1f7fdb9f44924c89f52", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15155129039, - "fee": 0, - "recipientId": "AU61SM9NBrBfiCVyJ3wYMZ73GhvGPcQhkj", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022017fa1b374e87bb1dcc55ed07a0dbd64efa5a148d5b1f3604b6890dc43ff6022402204a0e788d401f1223b27b4c2e0cc5631cbc92d1301827a53bd2d7a7ca411d41dd", - "id": "3dc4b91d426b88fff8d78258c20991afa63c247572ba06ea469ae2a4887a4aa6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15155129039, - "fee": 0, - "recipientId": "AJN82CKCtJ912F93y6mHEmbWS7ny1He3c3", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022042cced4fe76ea745f6e41b70a696a800c2172eaecb24c77845b35355f882dde402200e7a5e5cdcb4411a92bae306df4c01a3ff8ee5d53b6972518dbb1f50f768ee0c", - "id": "445db40bc02d41ea28aba75cc2069cb212793b0c202fe5346feb1ae304288c35", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15458231620, - "fee": 0, - "recipientId": "AebPWjx5BvurHauVa1Qj6nJD1h6h7hrnAm", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100eb671a7f10f305a65bf38632f205cc28f850d304e24d49cdcd19d7ceecd20b830220658bee7489d4555361f2d330fd31f59e146c489070c2522dbfb2009a3ccc5209", - "id": "0a6de5b1e8dfbccd33051fa1bcdba56d8f8ba2beef3c5ff7310620695ed8f7ed", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15912885491, - "fee": 0, - "recipientId": "AZTp75YcToEXwnZU51Meb5etPteCx72DCp", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100831c7fabcbfc8e4f0d7d093d2f80f4dc964bbbb7139d1f438ee539cb3e5266ab02207a4c930391f10a571b7047df2059de1aafc255da9225425cc09f6008e8b93889", - "id": "6b144c56b46f2d418538d2af9ba9890847b3073cb6f0d91c0dd4406402361cb8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15912885491, - "fee": 0, - "recipientId": "ANFaKygQ8EoZFiyvWi2SaRT8WbfwXpW21R", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205d825095fba54447404abf0c7721ebde7ec9d1bafc56bd4673eaa2146dd0c24d022029586e4fd33e94280ce99760eeeb4f12c51e2b068df63cdadc090dc5de09a741", - "id": "74f7c9d800d11facf0e9fd059069e0a05eaddefddc35e684c77f8bc9c6502d45", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15912885491, - "fee": 0, - "recipientId": "AVbpuFDTAyRmt1BRakzWTJyuokTRpBbeqo", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f70e2674210db0cd7ab970fd8b58cb86da605ff850284058d8d6803668950ff902202669e91f8004f9ad85b19312539eb5b1a393d9ed101659980656fca3a7f2b978", - "id": "d828823d3b2287d38edca76dd4e83a88557b2b8da43475cd6197ce16c906834d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 16670641943, - "fee": 0, - "recipientId": "AG31y1h83VJt7MUvv4zu3ergGFqfpUpNkw", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022050e9976c6dade3e5c5e365ff0f4ffb1990ff83e6613015559439c18e62395ab20220083ae7dd6567213a1ea0d762c24af46f8845080bd554f57e675b501c30c11ebb", - "id": "85df60d54bca2146dc642dd2a2bde7ec60c2b00ab7868b3487c3e4cfec742385", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 17079830426, - "fee": 0, - "recipientId": "ANaUP88ggMTde3BhtTXvm8N3UwSLADG54e", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207e0f782747a7020e5f2b0c72ce0bf366bf43ce2b8016c937d376adfd583aadfd02204607186b59392cf5d7899a8f020fb7298d974dadac28f3beebe82cc58b2843d3", - "id": "4a78a7be1bb24b6b44a6d8d20a5be158edb30f7e33028703a01488194439628e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 17300000000, - "fee": 0, - "recipientId": "ARUNkZzsiJa8YECfHbTxiCGv9vrFLZcdKN", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d143d7c5f2d41212209c85a3f762dff3321fbf3e766b6746a9483e5136f62ab402203117b6c3c8fdeb69dd8779319c3652ee0390e3ff8771cab0fcf18512e885fd18", - "id": "ebc1f86a8dfcb7ae369e124628a80298b868939e410a2bb5262c467b43a21b3e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 17300000000, - "fee": 0, - "recipientId": "AepyttoE4qxeaX3Q6tmhvJmhWR4e59UgNN", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220100a5efb6722cb9e96ebd5bc5af40854bc765e7b68c5d42b56d7b92e48c32ecf0220428e3ef95633e89d1b125fbc8c75ffeaf093971b70c553722232e464965ac6ae", - "id": "3b6bb91064213ed8006dfbb57a7aa9fc9b92acd1ff152625bb13c84d4a1c9fdf", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 17300000000, - "fee": 0, - "recipientId": "AK1DoQ2TzXwZFpqRuuLRkuMLtxvnwYA6BN", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206d8e23fd7873b9b3b176c2f81ca68bb8f6a39e86f48e01afd58f1d72b2f370170220160f43117a81dea41d0bd16bca567744b30a0df789d4cdecf1b4969f0de5a16d", - "id": "f77518bfec4286fd33fb36f0c6bfddd86fb51a4394034d80f12616b9c839eca7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 17300000000, - "fee": 0, - "recipientId": "Af2dxbRzquQbsEDdc6YyK38EYoa86bLaS7", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022072d8f98d2aa1dc1ea479aca3d4b3232de3619674c0b484b4f821394ffb91663802201ef857234756b2c4dc090b32375a399c9b9981c616d4106ce35f18b55e7883a2", - "id": "c05281457bb1438b3a8dd85840f6aa8a4e87089f8f41e3243ca146f1ca406836", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 17300000000, - "fee": 0, - "recipientId": "AdgRuyVNXHvKYyqfTqHNN6FL8nQhFuybe9", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203fcc0e870991a56bb59b4d5e3a8be7efa5a07361a34c19802e4b7310bf2a79660220281eee0aa154c8ceb369173ba099b1cf0a46117576dd77d9d30e0bbe7cdc55b3", - "id": "bd02783756ed0a83080748833bd610847e7a54be76c138b07b417bbd09589040", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 17300000000, - "fee": 0, - "recipientId": "AJZcbQZWg6yk2nZvxpuenxNEg5vdNfP7d5", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204f2170f03f42e1a6013ee9ab7f585e420480fc72309e219aa536de5a1eeaeeb602207b5c6838c72cb39c48357df2419b569982eda8275bc4662c066faa7b390dc4cd", - "id": "3742052eb3b87c2e55476c66c85fb7d6f71206ef8c3c94c737a382576f142746", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 17428398395, - "fee": 0, - "recipientId": "APGnKfbcBc7pKjbSWd4dqYbC6zAS9UQn6u", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a9b09e31f0cdae58a7f91e18a967e12436dcbb5dc3e56304e4803d942cbbad7002204d398385e1776bb0ed9f1cf6325cdccfb8bdb64a78287e4d96d18d5754bab1cd", - "id": "f7bbbad0d7ef708f2df7af6116907e8b89d34348d1786d22687f6e680e70b918", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 17428398395, - "fee": 0, - "recipientId": "AMBQecZh541er6XkgwPgZxk7fNLWUDwjXZ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f78352db270ad2e970789ced7fb13b976f20d45905cf4374d9ecb7a7b89a072d0220634c8330932815d3c89aad1f22d74f4305f9a0209279c3b5b5e849a573b763ac", - "id": "849ada089f0422ce85f82cf22cb73ea146af98d1166ecaaab0f772873f854bed", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 17428398395, - "fee": 0, - "recipientId": "AZHYssEeVUGz2WkDiWWkjefoSRcmNtKcxj", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206cbf14e3cb952df65c1688fd79bb6dd657325b21dc9831ea4545617bd15acfe902202e828910f28cc99a91b1e28c8000b53c5389fdec3920c502ab7c2116c11d111c", - "id": "de2af52762d16398e677a58eab98730dd6ea3fa036b148fe8c701da96501938f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 17428398395, - "fee": 0, - "recipientId": "AefDEneWp8sNsesZ4aQkGk91vKWCpMm23n", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022023939f4888ab00f3e2c258fcb8671a70a8b1c25a2eb52d610e1efd91ca5f813902206bf4c893b636139551282d0274f6155df35e46688974c5d9225dc1e3c7ba2230", - "id": "dfe1495aae4a5c62976804ff5ecf1e993416996f4cba9bf1df0ab0795ef5005b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 17428398395, - "fee": 0, - "recipientId": "AciqQathcu91PZkK7wiKqRp61Cddop7YwY", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201b8ecb50cb16def362f572efe3385be176d0ffcac0aefe6f9f378d5e899e445d022062907eb7d5381840cfcd021a1c9283dc4047a61e76df88e9cecd7852ea63e3e4", - "id": "e86a19a03fadada17fa359e563c25ebec320915ce818f28bbdb3dbe7a15545e7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 17432660696, - "fee": 0, - "recipientId": "AJTnGpoECgBAwkcWJFgxNAqq6BNNwQP1hm", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022045f5da273f2e2f227b3d422d418e3cbf3816958282d21131f3ec2844c9cd76920220591b8a380ee924fd2550a1e52a425fe3c7f185448347c119d2f0d515e15135ee", - "id": "aedb2f45e57c01965371534c52adbb99629ba581c788f7597da354fd72a0b885", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 18380568436, - "fee": 0, - "recipientId": "AUWioz7GLb9wrTXGMLVMGa6X4HytaUz1mt", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202dbb34ce6baa94e4996bf50310955636c5082e96db5314804af02e4c446846810220333215217bd86424975be8033217fb1d74b5897ba9cbcac196efa9006765ea6a", - "id": "bc0efb1b00e63694849e719ee3552176f4dbeaeb943910ad34b681b8250c0280", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 18726879545, - "fee": 0, - "recipientId": "AXJ2kuVeuwa2MMtc9XQqe5VBfxKgt8ZAsy", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210097fe86ad5302cdc3a189eb42fc873abb875e7f3c4f47b4b5e581e04be5a37a0902206e8a1fa9cdf78cd7d69d0e12059709f4c2301fc6235585db9dd8d638e01a3a3d", - "id": "a4cbe3e424ff70728cfce9f31324b8115012bc47293d480f62b05228969396dd", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 18826504375, - "fee": 0, - "recipientId": "AaPfmEsY8yCH9UhUdHaWr29UCruPbQC8EK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fc0dabf71ef6e74f376b5c646ceaaeb09f65c47322096e0d130e69463bd0a32f02202b52c16528b3106348834e75e68137fff3908755171c8ef89ae44e480da6d774", - "id": "79e4c62a158b1d95852406021ecc176c2eae643012071320c741c472be24ef25", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 19758347933, - "fee": 0, - "recipientId": "ASWwdLfs6zYsAnQB2MEVq2KVhbtsuuWELk", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100af87163f52e7bd36dd86f728b18360df16d9d9f499acb1709a0e365080ead6730220016e4040fe172f041da4ea90eae432d28f8fe4f49326ff7178f087eeafd52945", - "id": "b60e9a8a4229478192e1bf5df84df85e009a8517b5add54ea0e779550571515f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 20185909534, - "fee": 0, - "recipientId": "AXYVmQKznY2qtzpryr19zTSjTbSkfHd8aH", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022062ddc327d35dc5a02d06d6a91f9303fc340829d731a2ff8c71e868482c99d70402206c4bafa555098c358b0b5e68823b103401ecad4a05a65e5a49176ba0e435d09d", - "id": "60d339709c06fce066b14b06536b0afe9d7d8f0cd346da5763240fa937d2b7e6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 20235049486, - "fee": 0, - "recipientId": "AeR3975uH4kfYydrwgLQgo5pWkmJUww2nm", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204e06a134e0545f41c5f3a6f7f54881ae07c5e7791d23a7c9786c3015fa5fd4a60220422553d947eee3af1faf66e6e80119511651d5a2589d39c34d7cee4daba94ca3", - "id": "8242c9f18a6fa8535be6511f8b75cf6cbbc8da6cb962f8f9a0beeb697a95ecf0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 20301994953, - "fee": 0, - "recipientId": "APSfK5XyawPuumG2me4ovkwDPKCSx257gW", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220789829516e896e6f204699d632176feb9a721d69bc3b72392ed1a1a7b787efea02203f736d9ffcf9ff0b899d7ea648d1b74e1e938a4fe26cf3c8c6f556b0fb4a4bb7", - "id": "e0ea1ebc9da7c1a67fcee0b06203e7df1bc6ad83444f4dfff4fc37ae58f41c2d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 20800000000, - "fee": 0, - "recipientId": "ARwCmALF48ZWiY9cCBWqhCV6PkFbp2Bre9", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210098dcf23f4386de84fc4d75258d22469d2bd67017fea3417396393437d831628b022064427eb68be7fbfb7d034c682b2e5a3c0a740594da40843dafd4fc8fe1593528", - "id": "9c76e2b23ef60016ec4e1e54fae90bdf727172be152125e41fce7a69fb04f73d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 20800000000, - "fee": 0, - "recipientId": "AUGQoqoSkCGdT9gYPNsuFumhksQBDjFLeS", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022071580d7f06e5a33fe8e140208441455af5cd013853c94fd983c457e34685960b02201d7c1134e7c3e408b2dfdd61916e8b1fcbce9fa3e5539ddbb00ec47c424bc510", - "id": "8f444d8c999f8cb2a73f6b48be6f497b0230b4956ce62bc3c2c646367ae79d79", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 20800000000, - "fee": 0, - "recipientId": "AQayPyz6AWMwY8RPTCPeVhxkr8hNuez3Qi", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022045fc1b2a0389840a6e8495f0bab89579c2927936bbdc1237f56b2159ddbb152802207f2216ae4e65f848f96a5da8db61a64c7b0ff16a973783d1a6dc93afbabade13", - "id": "805e65bf89a54b7bf5043f510d74c9bc89a8fbdea7fa741a68bf4427bab5caa6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 20800000000, - "fee": 0, - "recipientId": "AGahFaiYodwt82Qi12rt5j29p2C2MeTLBh", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100897790cf4bd81566f48c2ade97bed334735704f19a67f8f9ca75f53c60756ad30220468cf778cb00ad16e4921c2377a06c2ea762f87ca41fc55e7864fe1cf18b841d", - "id": "133692fccaea85c45398542c5d4c8ac7971d4882764b60daf649f81a3204c49f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 20800000000, - "fee": 0, - "recipientId": "AUPkqi1hRw1Xk95PU9DmKn489PztyNjRYh", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200fd59320b0f0962b330c012ce0ebb55791d59ac26ceefbe0f89b140ffab7e5ea02201ace6578816b4751dc693f4894e04c25ca654e9f962d7f7ea710cf55a759df13", - "id": "faf85b52fd458c9be80059cd065cb328853cd66ebe35bc2f0c76160e271cdbbe", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 20800000000, - "fee": 0, - "recipientId": "APuwwoc1Btsz33vZNtuQDHNznD8G8CHcNY", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206633a9f528e3430e1ac997eb95252c483c94308a97462f9d8d8b6175e04efc9802204cec670befc7b7e2445aea1ad7af8d300a0fdcfe5f8e185df47bf5bf2918b210", - "id": "886bf5c983e9f90993a7fbb5a50064a6c9b32db2408d09561951578a12db73c1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 21622003904, - "fee": 0, - "recipientId": "AavfBaCqpww1LarHdp74ayFp3TSVZ9QHdu", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206329d87b5ab1bf32142a5688d09638ee7b80b79109350e42fca433dd4de0170702206d27e82dedb5ec747f7e16eae5c35408ad6600c0a847c130c615c7cf2875ecac", - "id": "3ac9d7d0173837c2079cfe16d4079d1f960ccbd320ecdfba026819239c4659a2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 21819296752, - "fee": 0, - "recipientId": "ARfsXLuowXfLk3pWNrJprdbn7F25hwMRYi", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207289896d987705b7a0be5edcbe197f61cdf28efe3a0844bcfd70f1f1e36f3bfe02203e49e3d9e01428c39a8e5966851fe94d7b798b4c3c6690be3a098f1ecd16db17", - "id": "ccfc33a398e393b8abcdc28d431fb5a3b4753b15544d58864d7a405b828ce957", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 22277220240, - "fee": 0, - "recipientId": "AGaMNkFFH7n6bXwKbXXEdfuDCN2JC4JMEY", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204e619395ae8cfc6dadcea3a709bb27a014c8836092bfea565ba8fa40c5fcb42002200b46a3a4503971b4238fd849b15a40a927945babd50e7107e205d9fb40bd4110", - "id": "ee656950500341fe56fbcb6b720b120a5e6ad4eb5eb75a2b89651b0526cbb782", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 22426664055, - "fee": 0, - "recipientId": "AYL4yJL3TfEafvWh5abik9mjrNGDS2N5ZT", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b94073a33031731ec6ca84bebbb2e63e45d4d0c509b885684b7315d769b7cb7b02202e0662f659c239b2a69583fde44eb38e8650eaf91a9edfce4e9c602106fb33df", - "id": "b5e89bf8f85d527e891b8ca3ee0f2a2e24a1daf63e50e0ade2d791aa52e03b06", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 22493303746, - "fee": 0, - "recipientId": "Ac4V3AcXoBTb3gDXkBofGd996DySEZ8w68", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bb49539eec3c2ddd506812379d6d9a126f3cb252fce6e918d72920dc389db7f40220135ab2cb8c335ac8e2b7fb563f9b3cf5ed4a3189e4ff9008871b21765def468d", - "id": "f418f20dd6f1c0826b102833e01fee05ac5eccfd3c69e70c6d9694c9885fef2b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 22606906279, - "fee": 0, - "recipientId": "ATksXZghzmn9RkctvKrpwPEctjEFG2s6Fv", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206ca341544ec2f0047a0f1001838d1aa42c0d94b49257034e037258bf09d34a2e022021c6958774ed74bbc8ad0fc4fefcbbf3711bfa0c0b47369fad575001fd3d7df6", - "id": "42e3bda812617277293b0efb08d308dc5494e47c5823d088e1c0520d2082ff57", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 22720508834, - "fee": 0, - "recipientId": "AKy9y4KXZ7XhtWmiodsb3mJSE2HH3H2VtS", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207d201e258c9244f724e94f5764513a16a42dc2aeddd50da09db88b6fac5919350220437202432b51c67ed7538ae1af5ee3445093d5fe85ab500923b23c866a4e12d9", - "id": "54a8922aceaf439f3efc4ebbcddcc60adfff7170351c57fb38a3a23ef2b6e1ee", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 22720508834, - "fee": 0, - "recipientId": "AdQTTtT1HGKsAuGzs1mgqBnwXHtMVCp6MJ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100df406f87bf59816e855ba7749af423879a850e20863ea5bf5e0ff96972aaefce02201a50f1f35dedc824860e5785dbcd6b49eb09510df251b5be71d4ec1fca16e546", - "id": "7f97907d31a72371925ee8ddd94f6c9460f2d7bd837533245c2aa41b9fbcab20", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 23338898720, - "fee": 0, - "recipientId": "AJeeb4cmTjh2oKsSUt6b1Cted554SrkCuE", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201500734913f9fad425d61e07ed00838e1c1e8171020c25dced36849b81be9da402200cf05fe867256dfb7467009a78e47ffc001eb80835413b4c558a465cc08c048b", - "id": "888fe214344f8d288fd0107747d8bcfc1cdd1baf596c75a1aa493b2e086861fe", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 23976819323, - "fee": 0, - "recipientId": "AXtJWNWJctDDFkwuYC3J8CNBGNJorqs5KS", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220678d7e0489942b5faae8fd0cf73542706f700bb4a9ae7a7037f1f13a64b74401022073f32ad4ca5b3a8ecbf843d645876656ff46382057586a85909c8454261d829e", - "id": "d7c0a0b53885f500999c7efde8b6f2dddd186247bf94f0f9b4a1864630d90dc5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 24200000000, - "fee": 0, - "recipientId": "AYet9i4fohv9fYye39z6TVkTkdAeb7g2Gm", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204382461f91941863a5256166d0202c684e4fb1ba39ac43eb7cb432c8b0a90a0002205ad3056fdf794935c28e94bab65673c01e51154624cf695331687f0a3974b420", - "id": "a14fddbb3784cf2bf6491f244fc9830559bc8b3fd355decee085db44c9953b5f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 24200000000, - "fee": 0, - "recipientId": "AS3b7yzfnNX97TLzWm4ez9GiAfkbzs8WTa", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022075bfdbe554fb74d09142042bcde603b6390903a8baa2eae9995193f87bcabad2022048a005a2d88496da6de06cf027ec78de4563267a8de12c0f258580d1e2d44638", - "id": "74b83c00431943cdf0a97d5f801eed6045c1f57e10d0c07c5437954b4b811efe", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 24748325720, - "fee": 0, - "recipientId": "AGUdj5MtJ4HnmcAFtdd8GscPBGwRC4hzYK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008600ceac976e10082007b40015e56d19d6924f888e6e620ddf8ae114bc877b5502207cc0afad875ec422d817fbcd6a067ffce41c013d6d971083b52389ee567726d5", - "id": "a64ef1a8982ad70a13440e5a901066ae8bbab1b1015113bd53a48c14e886d2b9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 24859867470, - "fee": 0, - "recipientId": "AVu5QHc9C727s3wXJACZyvFRc9XJSiHkHs", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ee316c58495cc4729ad60d451d129d7a4f98574e764f8cf5bda95050e1a8c79a02204ba8d962c6128d528dfeebe64156e7ea6aed5b4c98f468265d5886066c350711", - "id": "81e9ca6c942e14621b6c0de8ca5deb8200114cc8dff364ad52e541663095090e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 25052610401, - "fee": 0, - "recipientId": "ANmAMut1Egf7zK4DBHbaki6tv7dzgsEzBt", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205f1aa6c3bc84dc585915658e7e03bdca6b05307a86df928cac71cb523be7510b02206cdad96a72947eab1a24acd55869a8888c12252583e3319ce11ad2de65f64857", - "id": "ea5534e532dfed08d41e0c0668e5b1d3e1aaf70831ff61c6afc65562580a40bc", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 25157514204, - "fee": 0, - "recipientId": "AUncJXmGFQDzHE2XWPWEqitaQg5Azw7Rux", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220354217fb3a9f69b30e0c74b86c3474260e276e4553c0dccbaffa5d2eb1101c5702200b9dfcb85de1773d239fa99288959577030eba8f3908a4cf2611d52c5f5e58dd", - "id": "bb609aaa336872e1a90049e64e08b329ba0e9b1b155b39555660f13e900cc5e6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 25248444979, - "fee": 0, - "recipientId": "AZ5MTXTuHnMtZsgpZJ2zM9B8gWojkpen4A", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100892b7674c9045e60026206aed6c6d8ca9fea571c02fb63a3d852e26de4d9aa1002200f4c29438eb6a7ec035105771370937732a0e076fb1bae458d42f6b864784d0a", - "id": "9e7cc0e06c0f91878772d1f3b3ef00fa270da3087442baaba6e2ecdfb1e788b3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 25763719365, - "fee": 0, - "recipientId": "APxxDqpuPch7PfMZ7611osF5bZYEyx5UMB", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100dafcc214d6f27318979493288f12f50e61a1398bc4276ff9e559dc2d00a2e61b02206b173725cbca509a7d26ae49ab1b063540c8d75843c887ca1e0139029a26d395", - "id": "b838f9fefc43e5780733198956cb88817b6b57ada2f8b9351ddeb38a1b1b4a13", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 25763719365, - "fee": 0, - "recipientId": "AJdxs7TGgcizNuXVMYgd9JaXpuyKe6fF5g", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008402e0f1908c2a7a75474b3262ec6a6b492cb8d63d9ac0fefd5e7935f91cd0bf02202e6225f7b03abe5f580bc1c7d7a2a33070fb139fa22122fa075a70d912fc7749", - "id": "e93052fe6fe08457995307ee8c843d5293f0d62d3a644b4613af01057f7b4182", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 25763719365, - "fee": 0, - "recipientId": "ARjehcSFYaDpcuEnZ1iPquKMnq7i3sW8Yd", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201b094974dd39f06be93b8fc08eac96a942eba37a5ee4aed9b38b4c4d5e85414502203470bb9167f1a5121ae1909a3336caab615fd947a9ba464ef4cdc0ec5efd6182", - "id": "f9b86470f90ca95e6ec3dba7dcf43a638992d501595dc2b311354b01420267f8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 25763719365, - "fee": 0, - "recipientId": "Aar18iMBogeroCZw8S4ELPzeLDuvXoRdH8", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f884dc287f4e68750c622c5cbcfa02efd10ef032e95e3dd5163fe0709f331f7002200b8fb3fb1c0a21467618f265d012a2d0094d62b6b95c5a36e77b1129d295033e", - "id": "7dce050a4abc7ae6ada53d03e78ee7e42774ea2dab60a084cc2f2b2a3f77cd4e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 26000000000, - "fee": 0, - "recipientId": "AXbPyMxJTmQ2iN7qoswFQoxAfZWW3wZj6t", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c343b8c44441d845cd4a56ace540f15ea2445b00a22a350a70f4694052480f2602203110b618b42594b5d8e148f53cf613b55d2eb83685120e40eb9b8f2ca2ee36ed", - "id": "78688e06570c4b2ee4017450f89cf8531cfe1ec31a5422887e84ba2a6a21427e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 26000000000, - "fee": 0, - "recipientId": "AJbQvAY7LCed3KXVa1qxJiySXMKWRrLZ5X", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022045b6f2b658a709eb8b9ec4f9d15839cfe270ee0be5f96e6550a6395e0f887c2c02201194477e971ebaa8b7b8dc4f281f2cf1157960905618602c060b35ccc29d8c0f", - "id": "1604c3e03314a5a83519482df25366aa40aa3bcb3738d7eeb42c177bd1a38b08", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 27559966014, - "fee": 0, - "recipientId": "AXBwNDce45Bw5upoPTup7NUmdhn8NP6D12", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c3d6ccd2349a07f0a94d99292863b4c40fd1fdcb541dd15554131b6f03b5c427022028e5387876964b386a74c01ce79c9435ae71b6690bc65284f6a88b6e313498ad", - "id": "cb5a0d3fd3c4edc2414b8036df50caf75b9b8c25b1bb4b645b368291c8506cda", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 27700000000, - "fee": 0, - "recipientId": "ASXb7xv6zz5N3JVKzhvfLrmZHGGVETBvSw", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ca228022c4c0932b3b548d1c3262894183c12f65c56981b54365360acd175db4022037711473cdbfec5110a4f4467d2f4f847cf8e1424a70bd31ed391c11440e4cbf", - "id": "1d466dc4a218dce9c63f753870cdd645e994c9af33ae0777596bece6fd95bdb5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 27700000000, - "fee": 0, - "recipientId": "AJMMbNCj4gMpfne8rg9QezvdBF9zxPfysh", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202ddc599782c761e60e98b40880fe924a162495a916e30b4fb7e119b5a0ed0a8f02207429cbe9b1e529682b78c2e30e8269673845621d809b9b0393b7386e396e0378", - "id": "a0c287269d928a06bad77dc7366afe8579308331b10f2fc67b0c249edd5652ba", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 27700000000, - "fee": 0, - "recipientId": "ATj3133FZRJqKK49nScR4yZ87S6BAWdtZm", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220690493de80d6ae99cf3108b18cbb9ba0b18d9c283f65d9dcb8158df40623e6df0220376c3e63badab403ed5bfbb298622a9449b8f40aa93f797b4678fb03a0615570", - "id": "7ea9ac0687d6c936dc61a9c6a8c131412cb973869a7073b7a679508d91243917", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 27700000000, - "fee": 0, - "recipientId": "APunbT9dgTkTpqpUAqxHHW1GvQ6pNzaTHL", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bc128ca47b50617210c4efe3991eea82f3cfeeafce7e3ee464acc43c6962fb4b0220434e20c12c72b9e6f1cca4b0f79f5f8023cf054bddbc4e654cd195bed26ad923", - "id": "7fd55eb9e7a1576beb9060840cc704c3db6c2022c904d6ed4f6fd42bd2b94b96", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 27989653191, - "fee": 0, - "recipientId": "APTFQ7bZpw8n13J5v5sbgZ96YpgqbdYsNj", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022045ff469954bab872b82b892d9cc5422ab3284fbcb4976af1fe34abb3882fb18b02204ec503dd4f4491143fcd84a875b6ec8fa7aca68b2e8f6240cf5cefbc281538be", - "id": "6941d8c3cb4a3690680b6af7656f683e3f4d7dc77a8ffed8ca64af9ec501134c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 28721448453, - "fee": 0, - "recipientId": "APjsYt53nwje4wv8sBUJkuJPgHYfkJrvbr", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a5fb78077c9bd963201b4b73f26d1f57f9ff804d0e56080d7d5b11365b97218602205fcb7dd7009b4fc091cb21d8fb32c3b20ba8feee9eb95f19038caebaaa1b3de9", - "id": "653f5e251e1fa91014fc5d5fdb5160f1aab6408bb8e62baeaa69733a9d11b847", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 28730083421, - "fee": 0, - "recipientId": "ANPpsAJ5zUvsFSH89FpiuKn3HSE3F8MrvD", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203d66184dab85d76f32d99f406814204822417081b933227d247eb99aa5ca69420220470e38f361a771362db029ef353e384f002982dc5a40bcfd846c0db84d413576", - "id": "24df83ccb61485f2694a460efbb8ab77b38d0587adc4e48855ba4c496195a2e5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 29339140141, - "fee": 0, - "recipientId": "AWwHvryaqeQv7SsZVysNaG2xHQD65romAU", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022037e16fba790a42b0e56a0e7b353fb740eb499278cc8b9e638209ba06885e27f7022039d8876608f1afc24e7a27400844b4855e6f6b05f7fd2eb45f682760efcd5442", - "id": "54ec066a0475f3d3bb34b431aa58747a246a1e8513c6d295669f0738ad5208e2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 29885914464, - "fee": 0, - "recipientId": "AKWZFvNtkFcyp9qCd1qoaA1XHTCo6yumHz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203dc53e2d991bc80bfe7ef58bb5836b1016fac072bb04dd8ea307c675fb5fa86702201e4ca6d94cd78e647c9f1796bfe6855852a1b08d24033e31861cf4769da707e7", - "id": "882cb0fe98bfb862e7c1b0c89fa209cc1611691a92290ead9bd049b38fc69dfb", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "ANPjQL9f5qA6hiVgntKsdre4hRnt4kVXqb", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220641e29b2546a81aa442e6b13f7c6ca49cb3fd6ea836a3dcff16f9b8ac88b30d602206804d133dd4ab791569c95cb6db82d9e9ae9e8774ebb14be8de6a9ba15308bbf", - "id": "a1e0e700abedc3d7ad878be06f16cd2d565074462d81a731976a57411d1c4d35", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "AbGe4yigaf5FgBd791PKMeRkysFeRdNUJ6", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ac923911201411e738cc3d738b588fcefc6809d1cabe40042fd145624958f32c02201f3f39bdb47e5ed315cb3d88efd53d7f7582370261ec2956d1eadcfa33e1d560", - "id": "99992456df9d1108f11876975230e1f47bbce3defecb092aefcde7d015863426", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "AGWNji5TSq8tXMXK2JftSbUuytzkkUszKX", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202ed1f7fadf1c77665beac7333f23ea2afe3968219944d538a894fd5763f7cd320220637dbe52b04f82ca4f340695b9b77e939d6abefbfc10bd928781abdfe0770b59", - "id": "f11dcbbd114f6d70f1b90c5a54bc4dbabebe788ae0aab1473b9464d48686f0c6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "AZTfaXxi8ZEvtpvZnEbdJZaajFXqCf1B16", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ba7d814fe5391cedaf1eae38c14557e7f7842b0bb618a2043aebf040c10c23dc022002780b3165d68dad87c0dbfe646ca1e11852cc09e8f57443787b3fb4ac636b1a", - "id": "1ac2891f76ab45cce0d5297e9d896deb831ae644968c4b7aacd3c22ac3e2cc80", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "AbEXWz7aphvULaWnTs2LMt8PTnuvFM1tcQ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e5b9e4859b7541fd99124fae2704d5ccb01d9fd424168d931dd8ce7feae9e55b02205faed9d0716e2abea81cf4748db0189c7cf802706087f0570add7399eb51465d", - "id": "53ef0aaa67a4d02eebff7a550715542501d59849dff9ecd83b1be58956403276", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "ATHcSoKCMZzo4KW8ukygbkeJjQ3Hhdm1oC", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100cda20fa0f3a07fdce9d0b17acbd67703863c40311ded26bca2411ab6ea2f03b002200fd03ec6c9c62e33aecec9f704b7bc5a409cb727cbe2b43e7a9ff7ac894b7e85", - "id": "6038460b4fb005389c6df2e4e1513468063fe6ef332af5471e7e47390e09f7ba", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "AKrAqqmZ4SfjPqd9UqtbMoecpHR9tCgmWh", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204d13a7b0c8f6267c5134f40c9c03af1e75eced936834f100607ec8785b58b9fc02205f99a3e0133114f48a42f0eab3e1cf9546b2e71799244e31f12470eef16a5af7", - "id": "4727130dec5138ce4720b7c347fdba334c80d638549992d58e62d8ac6bac5e56", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "AJNdz4hjGwRxrt8CJSBzsqzTHgyBUmEkp9", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e0218eb720635af56f74b4c3f531ab1146764cf082d9c934978c3cbf52a3c65e022041f23226a14f86a8893cee1ef08bbc8521df699ceb6e3925dee5ed947b06d70c", - "id": "9ae2cae32a3895d6fa5f6b7f6b36b21416a6fe14c4782e386d5ebc0831d9985b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "ANWw5vn73Hjh6pLHztyWjYvMiNxYynkbKn", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009a7f5002efea36c366ec5b7afe37b22e12a72d59b2ff43626ecbedeca83a14e802206ea4127d8d15eb80c28905d5047c16128a23ec64e9f25262345de8066e0a31d5", - "id": "3735e0e88737e8ab56057b4383d091ca5f0779177d5aa95da30cf0d8a81aa96f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "AcgKpQ3zVCN4St8seZaob41Brk7zpBbyF4", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220455306c09b4bcf68b3d503c3399d266ccfcd7355014e1edfb600e17ec67e4141022055e7128f5c2caa7b564a08b0dce9492448aaad0e4cd093b603e61d245b27fc28", - "id": "d4c0238a3d04e69ca8e2ee8ec3b8da3a41679ae8d131a1012c2cb300c4a437b7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "AdT9uerGBa6ivvJ75gGJaDYjXyPXfGwJBF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022063f7821d3d906617f9d60474e54259e408ee9f88c724ce5b40455eee8701042202203d0ca800bfc80788ed4888351174f6e1524b7ea63a41f222d770f305f97a35f6", - "id": "aa97fc714c3d8c6b5f5fb76c351b81e4e0fcd263b90597a72e94d55cffc14f2d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "AQ96VLizRnB1RtTb4voazDaMLB3VYKhoiW", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203c248de6577db8045e71f536de61b935a3c6a2dfbf9ab80f92d810a31fa34b9f02202484f612bc171400b2b0471b6f5820be3ba8291031740e2cde5547948bd25f65", - "id": "371d63607d0af6c067a50dfeb58e56737e2636f4b0b0ff98aecaa44507a73f16", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "AZEYQFFZ31ybrZ7gcnt7YtJb11o72HcVCE", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202cad95342229afd4cff162e4f6166020be810f803ae87247e609ac6b476be6650220643568fb6eab880dcef52e35a6ae597da18d849ab7f77e4847216817dbdf2066", - "id": "8d4bcd6bbe8c0d21408c5c956e963c7edbe906e8d51618d15946356ed05f84d3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "AQrgHm3rfWGVXDhoPBSz4vXRYSJnezg5VE", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204abad6d12a702ad5bfe5d86738bb12e5fb77ccd3dc963f385f9fbc60369406f602207e473b2bbcd7176b9d1b8d3ac9429b651c8afa5e4f1394d97114923017734b2f", - "id": "c16763bfed71cf294553f0f3d6b3b0b3ca6d133d73d09c4b918b6e5cee11a895", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "ANBMsW2NRR5QUp8yhQNJc8BW6yrL7iRW8W", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220344f021c3a169186dd6ae0cdb48330b6224da92758de49503fff882d1b71b2c7022048f81b28b9a8ff3c07a1879fb2b3f0f31489f9409061df4cab21bc8403053f34", - "id": "3a359c2cc190a82229e1366b45d4c882bca83958d84063bbdf513d69f8da9bfd", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "AWHBi3iRAFasvWqRPrhBHYXvayzn6VNans", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210095a887589f959790c4c01da0adca893b9f288a8fc6ef32de25d19a4501f2fa770220267683c7e421d85c5d368c0416c0f067110ad2b39085a57b396300df68446827", - "id": "3deed8497e891cd9c8c52cb779542e4e0c14bc1434418558e20cb2d2e7a26f4b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "AKNx6tMWjp3wkCVmMKc6Lf7evoVQaVW2RR", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008a303a51a8eac6a3b47ac696cd9d561133dd3b08392060a20baced5d3d7659e5022032c11371eb5e80d42325caa9933e9cb218c03b17584bc20c8aae6045687c1a45", - "id": "8fe4b99d4943a9107010ec38e159f8208e1242087efa376d7345cfc8798fcf57", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "AX9teDU5aZqYBJUiGXFmkxFoVARFHpSZv1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009c77ca7fd50bbbe8244d6cf87d31fee1502d9cd53bc2719b4977b163601656cc022049cc61fe0848c5cc67bcd401339da0edf6490d59766f1eae6acf071f59d11bff", - "id": "960f6fd34b2aee0ec704559f85cb7889acfc8c5a3b635593d4660a5c4e84077e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "AbB54oR2UpH7Jh9beZbHAMU7i5FAZVqEPL", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205ac4b25ffcee117effac747f983502385778170bf4d9397aa36f0b68b660817502204d73b9266257237d35a1de1afed230c6512c7edb749e92cf978679aa9525e131", - "id": "a841cd49a92d5ecb39a5439a958d9d1d02622188a73b71800b45631d15ed14b9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "AdDxYew7tm1TmxC6ASHJhzS1qgtoVKLEPc", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fa7ad99b7c0c61a861d253948568f93ed985cd832ccc49bd68b5fff69d7d0a3202205d3b4717783ed8db230108f8802644ca9b63e5edc60652d743e3792574cd0a18", - "id": "1eae0aa7ebb5ec9e7e421e704ead21748c4d211d899006b8e0c4df6c0dd54018", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "AXsSBj89cjVAu5Sc9gLiDDwoRhASaVPWpA", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201a00e76784ec8a63958aec45ea6677c6af0bed2c0fc698ddf5a62911459af0ea02200efae75686a8e3615b62fcb698148229837f5ff32c2cbc2d289447124bb2b5bd", - "id": "193e570b80302b146e5c034c941e1817f34979f322c1c8fe47bef69c8b129b94", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "AKUncjgDtuG8pJk7YQrFkwBxujN8SLp1By", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203f9a8eb03c96849a9327c877eae8bc07d3c8e469ef3ecb7bc92f3b10ff76e77d02206b2d2396c5f18be1a68689649acc583c7a827eddf3064d16ebb095a3cb6d4be6", - "id": "4aea1fd822d1a22ae2edefbf779ca2ffb6f8b49a0971e2f43addf9607933d513", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "AVbcaqopSmsUjmXDvW288cGUTYq9YSmWRR", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c26e6789b1f61fc93d15a669de8c5a8dd420f68fb8e2424bb5c40c4a15fd1abd022007007a2055610948879608dbfeb831ffdac2535e26614453bd029b93820c648c", - "id": "de983ec6b437575acf2c16bb9da752f6a901ece688be7bad75e24591baab23c1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "ANhuDMbZ8GPX6MNLNrn1wC13yKTKL4PjL3", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022057f327c74d0539fb43790a2315867fba5f2e8a3f1faa261852c146bcd26e570302206d543cb594a980b408f5bd8fa2c45251e7d4eb005b2e55950cebf36c92c4438a", - "id": "a869df9fc61b6e1693527a3981d465f6f46d96a318a09d9a92c9888145e88a7d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "ALg7ozgLSN9wLUGu94LwMhPhFzyWBxQobR", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a609672480799e9034457b36d6c8f78c1cb253af8a055ac82117e30a3582f96f02207616bc3a6de1cca1762a3dac65dd48022da00e6800c39b44ae53e0d26a322fd1", - "id": "7f7b0295b52a45c399379f18ee56ab09638718fb9346c5bc1963d11cef412afc", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "AcF5LeL5ooyt1wja5Mj52gU8X92TVtG318", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022019f8c0dc34957e29ad1e55901f0ab865b38a7e0e018ed69b8793253a5d207e8002205901d7f3d081664da8469ed8e6bd59b3041ac1a8455f629f5508b50dfc2f62ca", - "id": "dae4406da93d88d9398fb59cdb154503104687b2e3a0754b169d17807a634900", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "AZ3qjcSmk3vacEiV3GdcK3x18hAut72yAE", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100dc74c59b710ef47a0568517dfe242dd8df2bd1305155600e7b0d39113b34a9e602207abe3ef33953e3aee86dd4f1c8909edf6ccbb2a3eb181a0221665dc07e1e0023", - "id": "0d3cb578f886acc03177c1708528692590db30c8ca270b5032b5fcd86ed75033", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "AYdMCtcQjbEAUKWLb7f5NEkkBx9SmTJzcU", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100af1a06c135f43cd0c67c499d1ce310170ead6e61b87f5d673a3d1eee424ae1cb022065b02e0dba32c5f235d9a707cf279a8178d5e5b0baa34572327c9dab785500aa", - "id": "ca2382312af236ea7f5ecec7f860db5ca528b71a4030736eb824db55ea976167", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30000000000, - "fee": 0, - "recipientId": "AGRJC9bwD6npvCuYBZywtho8dBD3RAzikr", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d060d1ae9b22357dd0fb6062abc88eb2ae7e47eebb7af7bc21440495acd3803202207e33d96cfb29e5cc40e45b53eca85b5891ee1b4122ef041e579df50464b8b053", - "id": "40a6b024984183f0809d08d199fb2d7c1742995adea05f1fe51a2fdbbd969842", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30310258076, - "fee": 0, - "recipientId": "AU2s8RgEdGWJYXjSSh8M5zkoJjdguh41Tk", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100be9cd07b90814b2f4cb2a868f08dea6eddc297e79ad7d48c3a1230d078454edb02200171f1277e17ab3978a16fa260903bcad9e66b8667e632cc856d09ea9359a898", - "id": "8d3fe39309daed03ed0e02d4b1808d54a18b71108ad3316898a9109981f0b840", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30644619264, - "fee": 0, - "recipientId": "AQFEMBFnxk8j3ShwNErwoqSK977DidXAAK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100929d3ed3bf40d9aab26360d553f1016f7fad051af511b4111b45ba22571d2b4a02204600751a7a3dd8d0b9e5ae93b6c014facf6191b52a64963a893ce4ad6d74cb36", - "id": "49b13cd9ea10a35ee422e6cca7044ca7a4e4201f397cbb8fd8627d1ef641ca5c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30990774050, - "fee": 0, - "recipientId": "ASVZwCuU1a7qgUKSdiZzRkuRHJpLp9Fp4F", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c352876accf4af2e7395b682bb88e69b896166e3ba25ecb62de62dec09891c5102204bf978db3488674cedc28d875a218309195f3cf7fd2ff1e35b28ed9dadd60995", - "id": "dba6677347d67b9a1030a3f883d978a72c40870414b9dd9a6fafd888195d96cb", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 31371117110, - "fee": 0, - "recipientId": "AXnjrqJujCyYt9rMC5E6NmnbuoN8N8LtR8", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205a2ed6c565adbcea14620c4872181c55dd5df09cfdb0ea96242a6a8a16fd446e022019cd448d1b41aa454ac2cfc476eb4d8d95b3283489fed359d7228d87f97f4192", - "id": "f03293bcc0c837c7d0741081cd123574c51a55f7e58f430aab867047ed0ddf0a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 31808712368, - "fee": 0, - "recipientId": "AWecYiRcWfVFEqdU9Ph6j7F7YwZp2kuowr", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220126963651521701323eb3bc6d78d7bf598d7b12896138f91d0daaaa0cb2a37d4022027009cf91eb481191fb7058204489db792fe49cc2925f2ded59e06f7740f2cfa", - "id": "4b77e88f7746845ee2b045357fb2f1cdc9d23608c50a5587c6414b3a0c4df8a7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 31825770981, - "fee": 0, - "recipientId": "APATpfYkUfDEhUnoq2icXy7ECqSumPRK4C", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d20fa45969cdf4217de7c8977675794023ceb702d0986785fc7d14200a836c2202201db190f80f13322618d3d5c27d07b36f78c0a876428550ba1fe3cc6e5a2be8dd", - "id": "d6dd825e9819c4bfa896bb33b25ccd26c0d6fdf07dd60567c8048b57a6046cb0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 32300000000, - "fee": 0, - "recipientId": "AWcBdMSL6DFzxf4zcbouKnXqT3xnsaxi4c", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a69a449089437fb0878f351b9abb5770b9e84041516b1fa92deb06dc648627a302200b30af92639075735109ddd39d1247a57e9ee7cccba8760f0e18da868f264d84", - "id": "b3971af4cbd2bf84c9766dfd4124993189e2e445bf5c0dff235aa49f4942a2fb", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 32490327633, - "fee": 0, - "recipientId": "AWq1wsJLPzAY4uiVTvH14F14YNZ2PozaUQ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f05a79d7bcd98a58c56630a91eaa25f14e80fd07bc9c70277ded30bf32dcdcf9022067761b7133837da252a7f12c54a5e59596a5bae74292751f4f71722d1d0aa3d6", - "id": "bb073805ed067d4e2efb8882453433f52d0fd6f44235299416f012d73688ec4f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 32583527433, - "fee": 0, - "recipientId": "APruVpipmehKJWeienqh2jbAdVC9o3x2Yn", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c421a27e857b78d81cfd6d12a5b5feb0fea128bfc43a7dd874c014040c91a29f02201df13abe3451a8039bef22d2e801cc09bc6f4d42f85bf4e62caaeb34131a5e7d", - "id": "439c60dc29d33a9d2e800a5b4ba1c1d195f580fbc46c4183cd4dfe1eaaf9dd27", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 32900000000, - "fee": 0, - "recipientId": "ANiKA7nwrdSW432oB4Xufeb4MyyTk9QeLb", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207a79a82616ea4c83b9bbfd52f8a53ea0ee6ea8bcbbad23a0aae1b74e5eb133390220179678e1b59b8e435fd19e68541d890136f85656f31ad1513169df3b947ea481", - "id": "e645add44af4985a0c44d63a7d33d6bbc87d8d0ded51a9074d8796a2af61a720", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 32900000000, - "fee": 0, - "recipientId": "AQvTNyqxVEHLVZfpVFttfMankBSGLyAfWh", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100cfc612e6c035e4ba731bc662abd91c151c0256379c975b2b0b301e4990f99c36022028380c0fcec416d7cae09ff6ad936087ecb43f162e7edb98b6773891003a494e", - "id": "d17aada75d6a2e7566d34ea4d19a4f044e7ec8a77954de872911f5e603d0ce2b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 32900000000, - "fee": 0, - "recipientId": "AYnzEccw2MrcTUT4yXRp7sHQGe6G7x7UkD", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bc272d36bad69aef950ac0551b96dd488bc9a83cb4025fac78c57ca286d42a30022003e275f6f1f923c61d6c3a45141274ca2b32f396d9f15a37ecdf8e6299df9254", - "id": "685f94850350f66b039fd33e550f0c2d1114c9f2fd7b7f0f723f9a8dae08f4be", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 33114472223, - "fee": 0, - "recipientId": "AVzMmzLTmugSxbwqWPqxxSQA4QtXVcz5Le", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220699428272cbe81ce84c5556bdb47e05285e8f2a533c5e1f0fafdfb2ff47fe92802204b0c4c4ae35dedca243f3a322e82bba73d80802f01d8d584ce5abb75154db06a", - "id": "1a1efccbdf78b25765e859d14e2d623afb1fc2acffc1fcd8d2679c0aa1fb697a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 33341283885, - "fee": 0, - "recipientId": "AY5Q9bdBx2NnXsQ4L6Zw5XShNR5gL6ExPu", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220398e0ab17a01dee9508b61e2a42fdccd6f0e26ad76168dd5f78b3bf8ed5d0a5502202fe39e30d85935a83060a11be934999874065d837f18e65ce96041b502a4388b", - "id": "eae73dbe65e5699583a3db3933114222b264c45ef69c6ad26dfa517fbb028665", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 33341283885, - "fee": 0, - "recipientId": "Ac2vcL7D9SLoQKwm5K8G6bbFQWgAXNbN9o", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206826a73682fbfebf2b6e78341e339ca1265bbdc3a2a70ea36edecb3d50adb2d9022064a2a77effebcea2f7e8fa6de6249b1e205e518e0efeca9000826f5a493d46ed", - "id": "16158b459a0c1b2953d98feb4d5b43df41ded0f72f3d8f7b159d47f5fddb1c2e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 33341283885, - "fee": 0, - "recipientId": "AYNtaJsLCbs88Dqv2Cc9SUEhRn3gMi6y7B", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009c00f35dfbde9a28230439c618354b5bb60defa091c8795c455f8a3519dff48c02204dbaa156e94921647d58d271e1e94cbff2fbca92263f225b46fbafbd65aa67b5", - "id": "7c0a5ab8ab2aa880dc4688437645dc9d825e030fdac4df2a25d5a8e97f4b5c70", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 33412512992, - "fee": 0, - "recipientId": "ALa8NaNCRdYYfS5sx81qzvoFEVsA7H4VQw", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ed45f09cf19eb0819dce65b619923909e116fe75588a96d6d2e792857a1a887e022050ce6e62dd65fa58e4bae5d09c191290c669eaecbca5dc38813d1b043f8f565c", - "id": "bab26302a9ea1d99d9ae0a90ed17f44c66f1174dc8ff11f7e7b79ed7b2b49492", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 34492604732, - "fee": 0, - "recipientId": "AUJRMxuAJjUFEbS7sed3otS7K4R89caZx1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009e20c799169e14bee9d76da2ca433b8d98295f8593bcedbced345a3ce189b4e602205fef5c8b8225b20744c385f1bd2994a0a824849e2b65592fd5246d625cb6a28c", - "id": "6cb0b2faa48c09b48dfa6dd0391d3309be705c4704d55f72d8a39347da0cc6cf", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 34600000000, - "fee": 0, - "recipientId": "AHWoDbD86gdtvHfgRyKtxo7jHS92hLAnPL", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022043bac64f1f5a5b986fae5cf88d182ca4698d667a0989fb2b89e731282487021f02206b8d22d28a13bce3d61e513c6aa2ab9ed8aba0370ea38305c86df90796cca026", - "id": "fdb9291d29c3ba236ff008224764227942056a4d28f9c12c7ca7e22b4c4c9f56", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 34600000000, - "fee": 0, - "recipientId": "AbJv6ehN7bETURRPJtTgJ5LfJcmjnVCjjY", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e01a874648848f53e3c58f382b04c4d8372dfe14d5e715a0aafa483c4c1593e0022024d196708b95991b30f9a735aa5bdc951ce80455fd30b32265fb1ae8e20c4376", - "id": "f10183e8291eec17c2308b2731b25e0378a2753ac11024c6b967c04138491900", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 34600000000, - "fee": 0, - "recipientId": "AMGy7FcNBntN6FY9ouWS62dEmiNW5SNDSs", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bc4ceeb3be6f423d167c08e8609e5224a43fe2692675c4cccb7ef35d9b5e286502205b9639e02cabb93e9d1262b8291022e87c8fb26e104ad46f38ba1ee4eecd8b16", - "id": "e72f27cd791fe40da52aa33d4534c6cb4061080e57a1f00ac5c0ca2e0f8e0adb", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 34600000000, - "fee": 0, - "recipientId": "ATgMvSP8FjG7mtDN84NKvxCJkHP63S2EMp", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206556edbb04478bf8492500e284ba73afa570a1ede51add1d0798e70f349e728802202846f2751bc43dd0c0541327e1f20e5e97149b2bb29ebf75d2c17890fdc751d4", - "id": "21d1a6e3205127c1c2008279631cde827bf82ef542a9706f255f0a52b0aea1f3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 34600000000, - "fee": 0, - "recipientId": "AHs1p6BVoscsszE917Zhvd9BPWrQbaUqcq", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a3e2779d7b46931554138df6b0cef64b7f087541eff83c07358e497e136ea23b02200477eb4b805e61433a164c1d0dac248aa2301bb20f774477bcb81c455b5ea709", - "id": "6e804117d3933b6731b936984bcd33a42319d096d5ee8d5784e1686658c72af9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 34600000000, - "fee": 0, - "recipientId": "AHqN9DLFs6S26HcG3zNL93VfofkFSoMXQL", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022058b2c046dc42102008402d2aa1cedd8417f39663048e943227682186c8a5861b022062914b3c529429ed2e0612cf2dbf0e66baebd5ce3ee70679ec723862d8bbbed4", - "id": "d0d692c0b8ea05ba6adf6848c94c642a824840fe2b541e16c7fdc0acb8ab280d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 34600000000, - "fee": 0, - "recipientId": "AaAewYY6LEDiXVmMcsoZTbkvq2X1DQKoCF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fd56b02b4933f9ad0f998c48d4305fc64c047d80fde18883c4c38561d787023702207f6c412361e281154d0868530a6bbe3261040d367087c401b9ad8a7915ff6a17", - "id": "72b77c936dfc9f4f38a825d79ae0360a351753a8aa7e89286edacece4ccd59fb", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 34600000000, - "fee": 0, - "recipientId": "Ac5rmbW5oQnjn51KeQeYPi8k8cB34kZFUY", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210084307422d072f2c09c3ba15404fe9bcd44932e92eb2ed608c60d707b960b69700220186c1250f67c8805c39661d121b8051aa983e0962cb34e046372641830dc7dea", - "id": "7fe1f32c8cb0217b9ef57edc5cf687a7fb07c1b1b82a6e9d6422e1ceec2643ae", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 34600000000, - "fee": 0, - "recipientId": "AR4BUMG1TKCcZi7rWMaNQ6HQkp6wikmknF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f32acf1ce8f09eeef89d9b42c32b76dfc95da77375d96d22eabbaf837a318fbc0220059b6de004ce6fa0a2ce3f41ed053cd0d8f735888707bcb6f5163e6c05e762e3", - "id": "28148ffe3c46ac548cf5ca10fd40575acafc8cbd43f01e97cde9b5d385974b29", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 34600000000, - "fee": 0, - "recipientId": "AZDhZu4Bxs1Y4SgLc24PansSLP6gXZ29to", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f4963f319382de0ba03c3c0856b9a667a00c82a90de308f00581299aa649b30d02205f7023de13f3958320606b1db434cf40c5294759c01a21691070c90bc2b02bcd", - "id": "d7b7d4815d3617e96fc2f47bbd7424a8b841e2dd5acfa42fd96ac52cd91d2e90", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 34600000000, - "fee": 0, - "recipientId": "AVG7PZt7mWxsbHEuJHF8xjqDLCxhPXsH6V", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a6698eead4fc3e21e659f6705356bf1c1cbf11865a735ea83e60eac5488ce1c8022002c23b6bfe049f8a0e9dae2f7eddc50558b51898459bcb2310b4376ef29f1a21", - "id": "e0d87972b7727a514955e01278aa1803bd33f18170554c410d0c851f7c0e7340", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 34600000000, - "fee": 0, - "recipientId": "AWiJZUDjG9KKM5QnKhz1Gnb1C3WQB23Xyc", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220563392e63a4df3c65909808b2a4b69371e082af2e6f0a9d2c3fe8131b87d514902201657b05f6789b16a5cc4f83dacfd0479f222bbfe17ac97374f19238259566c81", - "id": "34aec54141d983219b001b20901745720f3c38d32cfc736b82697e11bd0848aa", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 34600000000, - "fee": 0, - "recipientId": "AXDoHhX3gwTapS24HEU72dgjsc8TFEre1w", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204d7f9490bb14995339dd36f4f36a34fcf37726f1ff6a821731a2e788e62c9e1f02204804a2d78f61b94ca546c21285de091fcb3779c2af7aeb7e62c483556c221849", - "id": "152802a6a0822b0d9082c0bfd4109dce3dde9efb491cbf09d76e071911f62083", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 34600000000, - "fee": 0, - "recipientId": "AQ1n5gCzkyT9DfgJVBEMdApdKsBhXS2ZjE", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206b5c708a8413bb94024010c291633528f6dcf3f86d06112312239bed0e488b5d022068113fcfc5dc3f522005841ea7972f92c75f57ac19f58fd186371d7548ed8ce0", - "id": "b0a303fff3f8d312ee52989778d5473173c0010bfffecf773d89e3db1a3c316c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 34600000000, - "fee": 0, - "recipientId": "AdJ7BZneub7qzyB8qPHg1FS3ViisAfXP55", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204e09b911019131814eff411b87aec770b43f90b15dee330ef901ae18f57178f90220381febfa6e3cb1e869ca682c3efce6c5c07f5002a234475a1724a7a3fbc6788b", - "id": "05e2795800c06da22617f5ecb702c5c178c610a9c1ccc8dfc8a5310f80c169c3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 34600000000, - "fee": 0, - "recipientId": "AXMgtmxcMTMTZkMWJJyZgFNuaP4jco9RZL", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100db75661cda0f02805787729e123ea7fd914448e56b5de0598c187d193a1fdc3002202823a45c4a674e70468185ca667f45fd2769c4a763c915e4e4c0975a672950a7", - "id": "e7f397170f9ec10b9e1605974c37d1ab68794956b6687c03e96ecae5057d12e0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 34600000000, - "fee": 0, - "recipientId": "AQFYCZf3XpzZyjo4BtAQ7PzD72Xhp7y7vb", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d3cc40176b487dafeffa3c068dec44b0e94e079d015811c0866e3eb96a77a2d6022051d678f7e7410d3b27a9c86e2829d31277462b37381eca23febd1d195b21cd1f", - "id": "56806a7b8d18fff61d209e977234bbafe94329804f06ce5dacb626c91e5efb7f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 34600000000, - "fee": 0, - "recipientId": "AZqUtuBczriacfQkM1iWaiobSGX4GN8KwP", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009897cfdf79fd8c69a0508292f557dd46b4ee751be8e158d0bce8338c73fd86b2022044dc1e89fc5eaa374c0703862cddec760a8f692718768b221ca561116af6eccd", - "id": "c9e886dbe8d9344cde99eceec3f927ad6f8ac08525b18daa8d2a72ddc2a0bddb", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 34600000000, - "fee": 0, - "recipientId": "AW8RbZZiYe8FbDGNS77MGWEE16Zu8AxxLz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207bc3c68c391fe49ed8c7f06d35db75a739ab9c8d97cefd2e108f7b9bc5f164bb022040ffe76e6f3bcc39f993df7405860880ece802b28e56336a17b40a91701587ce", - "id": "53f0027d4810bd37571d6a25fe9a212890210ec4ee42f700394ccd8a1672c8a2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 34856796789, - "fee": 0, - "recipientId": "AXk9foYqX2ExUBK9Rtdirj2XTZ2vReCmox", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022049aa0eb602199bde5b2163a99fb098af4687a8ce249923d7007f5155a4219764022076e659cb2877771720de2d695b7df6c981221010217e885f42fa7d0c09d329cd", - "id": "fc2c5586c3e6aa5bc6d08272c06caebce5592b9f9bea2657707c3ced4c008aaa", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 34856796789, - "fee": 0, - "recipientId": "AYJyrRtDNZPw3srKy34buKUFJ8vwCxAuzc", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220125967fe3764625cc68030e8111c0499a0c5102195f94aa38499802de03f267a02206a97bb532d9da550410499a3952ab4a6e01939ab247c17227792e67fc25fba03", - "id": "b4cfd3f8d50c8bec11bf73e8bda2e8c78de932ee460911e0b8ff69c49ed03a08", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 36400000000, - "fee": 0, - "recipientId": "AJtBjGqiABsp5bUduT6YaK9L9E4EVZXhjH", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210087a0a6cfd2e046140baf9899ee9734ebfdfeb5fa86da06d6b354a7035ae3bfd602207de557dfade43b2b1f79b57c19133df0c0c0f6fe5b72f6b34fd0a5b5de987a8f", - "id": "379d4537f2de2f2cd6d9dcdad6c922ff45d538571712ceb2eca4ebfb676d06e7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 36400000000, - "fee": 0, - "recipientId": "ASE9D8yvqKef22qP7gAqKyxYqvNymPPm9a", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100dbdf2e7a976583aa29d4bf6e0d46f3919aa5bf281af7d2a655db076cd73aa1a702203d2a6c322152f63bcfa2db3f535f40b397bdccbd69a13557430bad54c3410cdf", - "id": "0fcf2fa0265d5c4bbdb6a0a0bde602f3342fa59c479af9c761e8032876ca7a34", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 37488839577, - "fee": 0, - "recipientId": "AT2FkCcRYAttXPz47FGpdgURLmza1cDctK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207cc3222d47958a7db0ccf22b6a82724ab14774498c3015c1efe72a4eaa81f0c002204c6d395c17568ee5f5831170da2ed918f5bdb637d2bec9711173b444b09a250b", - "id": "8a905c558371ad7a59243e489fd76d5ca42e522c8a04d3d8908a73445d6ecea9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 37771522137, - "fee": 0, - "recipientId": "ARZQdfzNT9Bd7ym6p7VF4KTjnD1XyoBFdx", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205c90fa2f53194d4d662c53420e4ff1408aac499327e46ab09c5bdb4bcb367eaf02200c71e40ed64aaf058061a224a54d21d1da4719816a4ad90a73b2d86a2795d3f6", - "id": "0f8a2e414407bed22c8866f8f3ad446bd932f24089c15c3122677c7f5ddbf4af", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 38100000000, - "fee": 0, - "recipientId": "AQpo8EbxDh5FA4zATjVRzEFfLcnmUnzMAv", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201de54ef50c50d19a8c86aaaccd7ce55790faf0a58031f608f50929b0ea79e8550220533214087b327632aad01474d1abd56f24793bcddd2366f7cc8346cb75b72985", - "id": "079d07f46a09bde98d55c78ecb6c5205e5209ab32d27c4307dc5448552a25bbb", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 38645579049, - "fee": 0, - "recipientId": "AarTZVSMDRD4k2YPRRs4vNpARZMEa6MtHL", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e437c1b3391179c2c73f309351525aca426a52a050f84cb92463fb37c0f0da65022075660f2e0dfe215911ca518b55954cccd0e184f2d87eb3b7f4500a3e782a7b4b", - "id": "a16879ce73bbfd163115c767c3ffaba4dcf8d1cd2bbdacece77ef15c1a777ab8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 39403335501, - "fee": 0, - "recipientId": "AZdt1o9xiUGNbKtxX4GUjA2Qf8rrepeY8J", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022100c6415f478336f7eb59ada8084bb4656eff9e22a97670afaa4b7aeab9cdd38d40021f62e0546fdb8b200a526c79c67c1240fbd528f6cb332804713109b921b4a458", - "id": "6da4c777b0511056f1d5420621dd8b70b8ca6ab5fdc1a6df342938513e01b2c6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 39470224178, - "fee": 0, - "recipientId": "AV7fCGPQZi7tcKaKYDKf9yGJRiaTpicRTK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e0bbe7320e5972688505a4793e0e9cab39914b53a9ec5fba0ea6e179cd8f94340220737cb6e960f6c2af403f8a80b7a553b6baab74e517f3bf9de245621b1ca9cfac", - "id": "bd2280fa9b6d30ec1530bd9e5601de25f3dd3e0344472a2b4cc7c32818e8037d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 39620933325, - "fee": 0, - "recipientId": "APLS8VURVhTsQ8gYRh69cNbUCETjL89jik", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220548e77cba610645e3b5caa5fe0de930be30662ae976b837252b3fde06bd2f93202201bc93d5dd1710ed48d95cae56f0842df1acbf6da100d4d4d4a56a43b84a504df", - "id": "6bda585f91fe13005e7c2ae9e317e55486f3e6cd0d83d0a64636c409ee8962b3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 39676127823, - "fee": 0, - "recipientId": "APfd2W7pvN7889NjkxygPYuaeAg1XNvKSo", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f99b2ab861887bd456fe83981eb9bf17c655bd9fb3db399c57735001b8913c6102206166bff4926c83511798121ddbbe68e37ce8ef14baf47009c55385110bd470e0", - "id": "46aee861d13cbfa1e058f518e14f1accab43bc628b5aa1e5fcc5f28b38219a03", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 39676127823, - "fee": 0, - "recipientId": "AaLnoAq8B6ZQ9gWMNSc6EHa1PKKkNrPVFV", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b9dd2cd37c9f1838e81325c4ec8d738c9554cd658df1588bdfd568d4ef92e1a9022026e3e132290b7c040220c7a6acd09aa98f4f10cc203f632fe8beadb4f9e1b036", - "id": "d40b29ae4603902272be5e09112e1e5291d49ef8a05ac9e756974c92ae50a677", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 40208679058, - "fee": 0, - "recipientId": "AXVNkc31rXQchNorSwfpweu54jjPCuJkWu", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022078da406531b60613184867d8ab424d2de871bba0651bdfeb0fd90f57c50db48b02205ce247f5c318340534a0ab4abcf37102c09072085b78649b7dbfdc166e79151a", - "id": "d3c4f8396ff1a4ee221b03282321bb8e45ce23efb00d61b8e1f9c76039d2c744", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 41600000000, - "fee": 0, - "recipientId": "Ab2j8GC1y4QtgT7hYv7pA2g1tiiC4W2Hmb", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fab3f4e4d7b764645b4dc54ac55b3856fe95e32f5be4e1bcfa105823a9cb5ed20220053d7c446db556eba9cf85d8e6229360a28dc0cbf46d894481e29269e96ad74e", - "id": "cc6a17753f16a9bc0bfd922b5c39e54e2e8bd6c3daf60f0cb84752af997bcea0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 41600000000, - "fee": 0, - "recipientId": "Abbn712o4ZVx9WVhC3vy36KuW8nRQrKR7i", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022027d1dbc4e47f15a2acbf82f28391d94205ce0ca67fc6bcd88aa0661afb4cae550220717bc9f5525ea5f38d8a17ec4b265b56a58f0caa523071e584d5bc1505b9f525", - "id": "79fc75d4fef939b3967e65fac8103cbdb9dd6b7e60c08323d735611d4f4a1db4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 41600000000, - "fee": 0, - "recipientId": "ANMGxQS3nccdwd4E9VCkUPx7kqCoNgTPBv", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203360d6ca61b6a128e399878bcff373b8ffbec4812df391d118eb972f8fa51cd8022057eadf2624d266fe0a054b103302832345189074c05502f9d9efd4969fb6678a", - "id": "000e2a9426529b65db56224bb9ada512a48395bdc14d22eefcc64ea1342b8e45", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 41600000000, - "fee": 0, - "recipientId": "AWPKFKMqjSFjMzzimsKMgQhyzJF9JBbu6s", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c8f1def0dd762083c23c58977202e0fb138ac22f987ecfc71f915425e3492827022054d427bbadc47b5aba27f3856cb00960a9c07ede09e438ab29928e3ed0096e26", - "id": "617a1168653fd2b8c17ca88d0186949cb8a155ffce2a20727b9940254b82f2e9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 41600000000, - "fee": 0, - "recipientId": "AcQR4ymd1ezhUe82AuXZdWTooroxdt1cWF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207f92c3391e8c66ee21f1f7a12b056e04056603ab6364055295e5bb9eca0862f5022058b6be51c4ae062433934609bf3d0b3f7c8e5aac5af9d72c0ab375abc89a6167", - "id": "948c73321a84c807cd7381d595361e64dfef9b87e957da57ff809f666b76f0e6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 41600000000, - "fee": 0, - "recipientId": "AQ4Bp3zg3oCoS1Kb2grC3DMVtkRCzzgsgz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022057a78d857515ab4028b94cf3bc3cc0bf378213f226fd1160d18c17f298dcd5830220574d2d14ec7972b165c138bbca3f01dd51b34499d2ea5516381572ae3a08ac84", - "id": "4b1cbd8fbd593f29f8fa05b2aaa44c0e0130748b17f09b0c43d65d3e643a4c3c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 41600000000, - "fee": 0, - "recipientId": "AHgivfsrR3CX3xQFoCQmui2DAvUZY2CNyZ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a76da3d4c56cf815cd0fc51160e7a78408c144bfce412d19ee1cc58665e8e44f0220348632438eb2902e6af28bf0a5c074dad45e9060a44b011eae325fa52a70177e", - "id": "dcb2ea9333efa45f3d66d4a3fc489ddc3853f998a902785da39f4ea2ca1520af", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 41600000000, - "fee": 0, - "recipientId": "APcJ3M8vmMQD7gU5ADj2sDgcf1N6FhJ1P8", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b530f23d6cfaf93c26ab19c19b06d4424bde9d28d9a26383603706e15a9c2273022064b7a2879828a23310603e34907761104bb7f3a3df81bfce674ff11ae2fbb664", - "id": "69a05ee826cc2ba50c2c034cb980ae47812567b7307814444b72b8e70f98b43e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 42131258728, - "fee": 0, - "recipientId": "AUHWHwFyrqGvBcNXUjvUwM3hadXg8Ao29t", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bdc6bbfd224056cce2382d92e4a81821720bff470853dbb0afd886afd149cc4e02204b98db8db62174711cd168c11900262a210fbdcf3a9a6f37095e174b54ef5891", - "id": "31ef234dacbdde03bd42a16ec10113e15d07ec0a7b7be5ba53c0cea80667fec8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 42255389394, - "fee": 0, - "recipientId": "Ab71JZB8rZ8kpYVn85Tm5Ra2MPP9dP9zrg", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204adf5271946845e06db2bd764012260c81fe65fd53cc5153d197bfe49ec4c6b6022064637659ab09da617552f820fa155c0c8e927f6da4e64041ce1d1dc982dee5b5", - "id": "160d40dc3bf0931f17ba61708817191e818b5e3342c23de337ae4e90c865a371", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 42416655243, - "fee": 0, - "recipientId": "AQjvy3Lr3AnW7QzeEtDzaL2XvAZGNmA6od", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008d073a33310126298b08ecca7abf4f9af03086b76a62975c13e335e77880ce4d02200e928de4e5726b37f282d9e483ddcb7d4fed27c73cfc0261d8bff9f382606768", - "id": "cb63ced56cddfa2952302fe8878c721bf74dae1ccb014309d602caa2b1cd3787", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 42700000000, - "fee": 0, - "recipientId": "AbKA7AXhr2Hz4N3o4uLgxCVw9q445xygRs", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022061a00c4b115ae2f72d6c7a66d6bbc6bff9843239b65de88a214036c98bf2ecd9022059f612d1060bea12812fda983962c7909793151b0c33b57ea1c0b87cb979e588", - "id": "7e741a32b14ce1d4364ae4f93ba123042d430dbd76be81b0705a69921a07596e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 42700000000, - "fee": 0, - "recipientId": "AKRPP27m6RocDmDwUeSn9FPMZAdq7XZKeb", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204976b03c753c921b6ffcaf08b742e53f3944dd99f7604e9491128c2cd5bb06070220735eec837093c4b48d14dd6d91d02a0433e86ab5156070361245cf7204b7648a", - "id": "5aa56c54f898b59c422a478990d978382aa5ae7168e87afb3a693db7e5c4d897", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 43300000000, - "fee": 0, - "recipientId": "AboW3NE4S2atxnDiB5fE4FNq6ewXQX1jNM", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200bd507090d4afe13d3cfda6d6a3b8cd84d07f501c1d0b4dcab622d836f6f2baa0220597014fae0b01e2f26bd560804a8d2980df2695a0b277945140c2f7f3641d072", - "id": "d8768e339e6bfdc242cf3c395d3b0f11d4b4796977179cabb06c2ec3a758c301", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 43300000000, - "fee": 0, - "recipientId": "AYSgtUb1hE3WTkw7pQ2U1AxYmTXB6hSdAY", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220548744d15a0172cad94e4a2da25632d1074c78de9b5ab01299ce319b7e9554eb0220252400f1bce65a39a0060a64a5cb44aeda15fe33e01ca7b3524f2dd41ef517e5", - "id": "af598dc5b1ec4ca254f60e4abfdddda7a400afd42eca30863b7565899fb73396", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 43300000000, - "fee": 0, - "recipientId": "AJDZqWaKSug4kPadzqKYDzm4MHGxhhUYiE", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210083e24d007dfe080315146299f74d8129fcadabd3feefee65133cb26b4a7205f102202b0378b31f5810c7aeb96d4c1784d1cb098a36df52f8f8e10d16fc41370f7c69", - "id": "1c2e9b68f93e64f431c95d1aa03edfe301d01d5e5061bf303ca3510462f7dec0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 43300000000, - "fee": 0, - "recipientId": "ALDJhQh5YLMeT2TNf1viyoqJFDTdVxBpdr", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205a6741a4f2d76c61a93c300c5dc9542ffe7822c1d2eaf4d63e7f5dacaf7405d40220675355e51b2da2eebad0e1409be2b674415ed903fbda6257dde37e1851ce4fcc", - "id": "4522f2e824f76a2ca38b8f9bcb589d32394eeeeb44521c27a7d29b410d27ba2b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 43300000000, - "fee": 0, - "recipientId": "ANZed5qKqwcaj9Vy1emaYETWUeTVTuZE6q", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022002d24d963c7d96285437680fa90647d6380df7fa3f84fc3b26ebe80d6d5b4ab70220292fd00bed98449c72e246a94a5265324c16bb8357d4d5863cbd9bf95d7ce1c8", - "id": "72ceb8e73f321a870319b090a0840e83bd8257d86500ad1c23fd3df4e9f76288", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 43768613037, - "fee": 0, - "recipientId": "AVpuvWkhYahCaRt3HP27X42W5sLQp1par8", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202bf9aaf14081129fade556087d9c51d31b2ca79f316df943834364dc655e13a502200fee586bf2a8edf195f939b9b850bbec71eea69a04f4bacd592e38dd69c21910", - "id": "4f0a3f9b10798151efb56686d7920dd3447d824d0ff85e805923f6e429f35dbd", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 44117933985, - "fee": 0, - "recipientId": "ARKK5gYqzp8ZBQu6Lb2XCrhyFN7DW2u71y", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100982ecaaa74b2b64bc24eafeaa49951093212380f9784b4cdba3f39b4c6d80e84022054da5701fc8b6222fd56ce68c1c36f125b588e1b4a18fa2512674497c553d807", - "id": "f75cdbe651257ba7454fae6d967686c399a041698c666c25db6f31641a7dc9e3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 44286661880, - "fee": 0, - "recipientId": "AN87Bx6HtTjA6NpajYfTLrqSwKHzx5d5hW", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fd9e1fad2be125b433ad14c77655b8f461db35bdcd68e2cc776c22c887908e46022013e50085edbe22d2feda04815b035ff06fc6a744822150ef8ea7f67c893f0c66", - "id": "9a01c2222033973b17d6014c970e033280053eedac7c1fa9092b2d104a72caa1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 44333481457, - "fee": 0, - "recipientId": "ASbW8T3Xx3bDZQYGincxMhgspZFZNrobJD", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009fcdb37789a54a1ca4134fa8a5931cb5ecff33a6928695d68a903d088fb086b502200e7c474cbb7d1f6245c569d3f2fd7468a7993bde91bccd9802c59ce0d2b56ddf", - "id": "48de47db070de621b582c945d2eee43674ce2ec38b48d7fa80294d20e857cdf8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 44556079374, - "fee": 0, - "recipientId": "ALBVdpLvZofQPLdHungdurd4iF2vHtoFbS", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210098e6b691e6c5c75fe5288d2a5cf0cd0b79694a03e782d9aa982a6df7fe206db202206df4cfb87cc5fe8fc61c5b95c16c947e98671390dbac8b74d5d9acc02fd6513f", - "id": "bb34fd7dc178bbc4ccf41b5ffe7a9b8d8bae117f006006bc710d76f36a83d5f2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 44571697840, - "fee": 0, - "recipientId": "AHXaXhX5ybwNZ7VDkJzJMSD81J6m225xNS", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ef3571ee051193e032dbee17d7b1baf58ea08de031521582416e7965407babe9022055b390e25f42ac5240921d89210f4a9c7176bb922941cc85f92b9dc7e12a1491", - "id": "6834021b254515836168aaaba7bad2ea5ba943f2f6ebab2ed65873342886e147", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 45000000000, - "fee": 0, - "recipientId": "AatvqNHtaZ3bEbegvVZKPoXUebZkNQgJG8", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bdd197b4f1265984c8298d2780746f2e9c3bc375a2e316108fde2334d025e06702201c836e38d63e67910c20791d9fc4813de475b83ef954778ed5a309aa7691ba3f", - "id": "f3fba4cd40e1f99acd6f5fddf5d7fea6758053db2c37e0408976a4294068bc92", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 45000000000, - "fee": 0, - "recipientId": "AYzwRwPZiXuvP33QBjKC8aGyhtbQJad56t", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f87ba4784290c46da18275953842aa98133bc70d7b4e71d8ca12282f6c3aface02200ea514cffdaaed678ee27e9915546bd78735363976da8036347a99353373c981", - "id": "e81faed2cca7cbb2f9d059b2092adee98010bdd0be99cbd5a2fa3dd6cbfa2b50", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 45000000000, - "fee": 0, - "recipientId": "AFrMQ99PEVFX2fF6FHiczGKjXwetkGxYCy", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210089b572936a6429869c1824ff5c093f32519eda2f184c25e488044564fd4f773d0220300200b1497d89248eb06940f0ad30972666dac7971767e1e3d72290a2017457", - "id": "d50bbb062053072e469d59ac609cfa67f1b42db5bf94f50f00002a89253b09bd", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 45000000000, - "fee": 0, - "recipientId": "AZ5xjWNo8XA6xCbWvVeR5gWKF8Yh51ccpY", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201c7336f68570529c6964cb333047edffb7208a41b20946e3f1e33a6fd8c2808102200df8378a745cfebe85881fe2145d8b6dc54bf002e7232e966f10f852f2ac4e92", - "id": "0e671acd9b32bb08daec418f01e9a2b92533928190485a2444ce5ed10798903c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 45000000000, - "fee": 0, - "recipientId": "AUR1TuACuFHerTUB6nkvzfr8LYKw4D1DeA", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205f37144ad23e3cb508c39225b2aa5316c64a64c4dbd8d9f683c931f986862e1302202a6891bc8c03f50f697d5229fe5d9e968c731f54ee2661b4d87c4178be3bc2bc", - "id": "1e0c135240fec675f246e042f8d3677621cf69715a0f8ce8c0480c8c7e276d94", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 45000000000, - "fee": 0, - "recipientId": "AJhTg2R2MWsrzWfn2VMSa7YgthV9ciSAaz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b8c307b1cd5f04e3190470d66972ab3887286424834389efa4c0f14b29d5dbad022053bacfe7cb19002963ce7999a9b324e7b5beee348b827cdd071514c4f3a94fce", - "id": "625634fef958d669ba75d5feedd43da8930ee4f33cc192ae99803f9643d9d44d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 45000000000, - "fee": 0, - "recipientId": "AawK835dZLs9E5C9KXLsFEVmK6re5E2fwp", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f97b2eb6908446bebde6634b3b79d43eab3d425f02660216f1c2c753195d6aab02201c428ab520196041a462194e906afe6bb49ef7e4fd97955dd44bf4b2cb3c498d", - "id": "71ed3ebc697935d79efb1b1b0ec2f45a67a0e784c57fbff3965ce8ac73a8cdf3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 45000000000, - "fee": 0, - "recipientId": "ALyaoK69C8NT8yiKJdx2bPnPpggTkuJdFG", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f69603b3b32ed21e6b90d70b8053aea3c45c1c2e2c204bd5261246b97f43da2f022040756dae02033d7959f870bd1e05145f017aa7f9e8d8bde5ea9db4458eca7e14", - "id": "a38b475f390abd826db23697d8f3a11229ef4ff5e5d9a5486dfc2475cf9b2594", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 45000000000, - "fee": 0, - "recipientId": "AdrWr45gkfS17raTKgACtMwC1oeem29fLn", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022058cdcaabd8201113db1e2babddf080db1958c9072b1039fa245c7dd83e92caa7022070ad9408de0f11f8a39cb43d2533a8b6bf079c7237250288e4eb96dedc3806b1", - "id": "f0865d7b5fbb45687410d8abd0e4a491320fcd2803a80edc40d3a300ae0eb05f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 45000000000, - "fee": 0, - "recipientId": "ATLyQaK3RtKHa6eGCPZRkVrKjEMcKM2NNE", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022005e5bdc9a2eaf86059bc4d631765da8da416e108e6d2e07f2aae071e14fcf87d0220085c0961f9ca20cf2e40278cb27e3cc0de609d080599d4547905dc37bdbc60ed", - "id": "89fe6d2b213226f45a138ea256496d79ad5144353b0f5bd95315c34324674ce4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 45000000000, - "fee": 0, - "recipientId": "AdMty9JNMNJ4eSoUZqwnE1pzRBZQW85Dmr", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201aeb9867943ae98594b74f68a44e2194eda83b52a7c01850f2da145af34554e80220615f683129fc5e3b109067e5bbae547d6c80581c2653b7bedd7d986bc19384c4", - "id": "6550d4dff8ecc93472441bde8d4b87b174719d14b9778f9f25e826cb58d57d08", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 45000000000, - "fee": 0, - "recipientId": "APEkkUCURK2SFA1rXq6nrqYyPVW13rsxtq", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022056e356a79e482348494a82271fa5c6ea1f1ff3857fc2429d248eb5c4ab859734022000b20a70ff281ae78a4ad63af9646c92e11b23a0108c6d646e0b2680f7a8cb53", - "id": "2640992456466adfef85dc33db1f0ad1bf731209a988ce0c3af7b9e2f0a15e36", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 45000000000, - "fee": 0, - "recipientId": "AYiQeJcxPMdGqdvKVHzLQvmAPcgK1JNuwX", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022072936a9c44c3c78e1da607c23346ae71a7dd55294a9f1dccc7ca7b995075b96e022022ceeb6db9237674388251c1acce2a1a6eb5665aeb8a42b9772853ab68284e90", - "id": "e288716799ad69535338745567163e9607bd2500e78f2fc1dd90b4deaa64cf7a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 45000000000, - "fee": 0, - "recipientId": "AQAeWRKR5mR124KMqCH44BciU6otGmbofj", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fb9aabdf5f55bfe910272ad8ce308f03f5db3dafd8c9d2a9007e784e0ba8b12e02203843f7b9e84f7a2a37a23843605dc6e1943b3ec3be0a7330d075024be0338f8c", - "id": "b7a4725339a9517ce667d69b50279c93c53e432ac8029cf1d1990033ce446479", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 45000000000, - "fee": 0, - "recipientId": "AYHJ3GQkHzy9f3ndLg3CsEBvqSrAr9WicB", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a609c9f810820be67db6b2ad25e582721da81ae29b1fbe95acc1e6ec9eae4f5502206cfd243d311190870177ddced286a50f7df71e3ed12bcf2544ccb61184441675", - "id": "6e68a86ad3f141ce115341c4167a0b8c8039671f7b8d593a9007c299a1742ab8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 45000000000, - "fee": 0, - "recipientId": "ATsDqjHX5hGuURrPfnDNmQ3jpB4q2aMcmK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fd13b731e92673d5ae20fbe28c9dafd55b4de120a8b8654b9db323c4ffce89bc02207df49ea18df0e5fd24e72ade2bfc2ea417f53d5a268364eda48a8af850a972fa", - "id": "f6432660a1f9291b952bcc0518abca3f0fc2a48cb553922f94ab82d5127d83de", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 45000000000, - "fee": 0, - "recipientId": "AbWMPtepNQdtZKbNFfkbEvBki3gLxgzpWZ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203fc4e5dcf19aec5c7cd014ec4aade1a177bcf1e8ba4c1ed8ba6636e6091d62fb02201935e26746309781e360a48577ef5ffcd82d41c6540943448d1bdfe44c804b7a", - "id": "1c67042ad2c0ea1436ffca3f18bdb68e192dad5cc1af9506db9187880bfcff58", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 45000000000, - "fee": 0, - "recipientId": "AcA3ke2piEk5tUbLcaaeU25tZckcYaVRo5", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220057351f830067dd2f2a6c554307d64f2cb01c4a41b4bcb56d92bd02353f2a00302205562002faae96f1915237f5ce1574cba8bb17f2cbe4cebcc4eb25f21b39018d7", - "id": "8abc18519bc37580a21b2b5d36bfe15eb4d6f63610fca4db2dee20f6a25417b2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 45000000000, - "fee": 0, - "recipientId": "AWMJezaNhsvRxDPMtEqNdYHt6iAMMCRroQ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206f1e30ed77982601cd7186278023615f0f9c19984ea459629a7d4ebcc4f31e9402207109ecb76a21e88e3dde1c9b1c996195def546b3e6c5affb44838c2fd6342a0e", - "id": "5977367c87cc269564188bb26cd03226dc5d182f7191ec3dc7948f6f21f71d13", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 46006407173, - "fee": 0, - "recipientId": "AMjF2vBKEdDs292kBAsPB2HpAYzCqwUmEC", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b58f5b2879ce4eb45501c18fc605821dc979e3e7efa7f204c85f5ff02b6623b602206f2b4c40f9e7ba54dab669024d3a5a53bbf35ad4bb815c3811a565e6d5325691", - "id": "faa89d3d52bebd59d0abe61d555e187c1b8c98c3446bc566a036a217c6fe5c7a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 46288347832, - "fee": 0, - "recipientId": "ATsdHdM4ZWFUXSmAS1MxvTJn4wEt75fyBu", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100cdad60bffc4d6b6e137f894becf8b7d67d2084a47df750d40590a133e02cd68d02205c99a8012fb42099ea7f26c45181a47e53a02602093a0fda29681ae77dd9e6e6", - "id": "87c1e1fdb88c4e153d3e3e85a767b0e76a29f7da8a88d299b79451abcead9365", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 46416322043, - "fee": 0, - "recipientId": "AZsbKy8LmWUf44H8XqGkbGA3mVdyoNsPc4", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ea15ca497460bb9d9ef34b644d107abf984cb3ca26e6c31b16509d7b9346119002206b732e6eaf0a6d14f43ed5d889726a5998d5a190895d4aeeb355cbc2c48ae2d2", - "id": "8aa8e83047ae4bc31b0775949ec3e9a393888ce39b463d91be26a9e5393fe7fc", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 46947045881, - "fee": 0, - "recipientId": "AG7uDVNphx9PerxNvELAPrHREsnDC4zNyN", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220697f3cf632d4a4e8a3a424be3e15cf7e9b8af40f484dbd862fa32fd0e7bded8e0220061d59d40d3f63cb496b1b1030cdbfc87a9e06e71c63fb9b05764fac08df8dd0", - "id": "e351f47deb7122873b04a91a6051db3812abc8fbe84c907533e6f68a224ad68f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 47400000000, - "fee": 0, - "recipientId": "AGkeeXQM5NjSMk5FwGs4wsVET2TojDJRgh", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210092e1610b6e99829aa9a149a92fb3117ea73ee1f0c7c2b7cea099b55769870de6022017ec1216d66d1c8f7546d9c5e2db23b95f033313d2aa8e1e4f9762cc74036dc4", - "id": "4d07eac2ace1a2cd2579dcd8b0de07d6dde9c3145a2d09ab7580f8ded1531def", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 47400000000, - "fee": 0, - "recipientId": "AHGo9dHkARtM2E2kqb8oVi9VMGZduFNFGT", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b022f6e3c9b494d8c11e12c2014cb078876469c3ce2cf74ce1d2448cb7f813190220012990c9b719f8e80e69108a6e9e0501d7094cf47a0daa1df32f08c2cf29130b", - "id": "143256c98cf7c47331065de6db5e8e328680aa78a35a9b7c4038055a6a416961", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 47400000000, - "fee": 0, - "recipientId": "AVZugZBjYSTPC9ik4rB6RknCnjzWPBdP84", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205d010fa7f4e16cdd80da7f18052eb197794173f661abd51d5b8d3a377a88d853022021f3f75f96a21cd8e3c694b1635a286febb72427c1d7983d52d6dc20a79c3576", - "id": "058cd4a632a73218954a3b9dd18a5b21395ea99bffe9504be2c111e7b5df1628", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 47400000000, - "fee": 0, - "recipientId": "AVp8x3mBHzs7fpvPfpUyeE8WMPdtX5CK63", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c0b5dd428920c1f5148e1282ff66c13b845990b6bb31f497381dd060e8c6c0d50220756750f9565df70437787347c15c60cb5d71677b21c9ce6a7c8bb5e9b786b4df", - "id": "6a3a8a2adc4c7c5cee98d56b69f581b32dddf7da323eddec1bcff013841bded5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 47400000000, - "fee": 0, - "recipientId": "AbgCof5QkxSrpZNJGYsiepZuAEwPxhxT2T", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022063f69a740c7ea719eb624fcb71fee6b497a159b101f0f3b8f06e90791b7f508b0220710ebc92317ab726b2824684d51ae758059efa7c16b7ec78d38d45a4e7d6124e", - "id": "e5458cc7aff8e0bd8e2804a68822a783c48db482295997d53939305186376ea0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 47400000000, - "fee": 0, - "recipientId": "AJZtruVwKAPj1rv3ahcF3Z9GTXGcN9oc7K", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204849a42fc02fb6473c4a79a0c2c9ad9facda13ad294495b9b2786b38745b7f3c022070dbe71e4555efe7fa244a3cca10bf07744bbd0ff280b83676a2a2e188f44825", - "id": "3ee0be7406fb23f3ba38c0eb96698b168c043afeaa6fd918949e0ec7c90cc3d6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 47400000000, - "fee": 0, - "recipientId": "AJgvboi25qWyEgoZrqqWo39kPGaPVbrbki", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203c936c24a2e3490bfa4743b369ed09f12ed8dccefa55836551ba3c666febb58a0220586eac237dc31060f71bd86a893b78a578b76f2970b8f57ff31c31132b9f1008", - "id": "14de30e7e1bfc74127cca71e59bfc74a03b9b2f714837b4812a7e149cfef20d8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 47400000000, - "fee": 0, - "recipientId": "AU5SUAATEwfHjAtQDkGFXVPL8Vh4k6sNtN", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022049a821648ea42ce2c2d1d02c5c97d7a8964060bdf2e4028954528dd843faa00e02203e800a96beedff49f634ed9da667ff419bc8c9c582bccd0297c31006683056a6", - "id": "f41fed485ef003ccbd228edaec88d8087731e0005616a600dcdf94c67e72f0a6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 47400000000, - "fee": 0, - "recipientId": "AT6sBJKYptsn7csAsQSzrKuHeDNv7GkTSv", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200b60f4bf416eef8e104234eaeb92ebe80647bd5afa15953cb446fa06da0b169c02204dad39f650f29de7c6867453c4121a868a2e24b12443d5c7722c4b2465443015", - "id": "17a5a7826eb0676195d33e12b63705cf50846a4db052199cc330156a67607c55", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 48367953807, - "fee": 0, - "recipientId": "APcpfWW8rMcGe9gmiFmZZ6qfeYBtwneDP8", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100879fa90ae6b2d4c759e4ce307f0a1cd87980bce956d431fda9003f00ceffa6a4022054c7765423a5f512d1631d3ffa077d717d943298e5fb8cde08908a0a7c9b8267", - "id": "4a6b80c0f1feff81fd4450558c0da1528879ac2db42362ac7fe3b65f4fa52201", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 48500000000, - "fee": 0, - "recipientId": "ALKpxaTevuYjZYSXvTYav47Nj8jQaWSNqf", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201c5a1709172473523d605e4a21c02f3350df6bd9cc3c0951f20e8e2d13151f95022047b2993d4fe88e1e7d079de95f342202c45813aa0f1a6d17289b71022c03d512", - "id": "fc415685bb6fd5876d5ad3c492ce4696004aefe8995874b6b1f1dd0d5571ec52", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 48500000000, - "fee": 0, - "recipientId": "AH2ZufhZhdDsHWsY6qBbrdp9AwH1rF1kes", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207d9dba6ca6da8f7e49f6d2654d0a02cf36beaee6da12fe5e5470582e4d9ea28102202de0debfc82f03488b867493316c18949aba7ce6788b8e6b583b26f7d9335792", - "id": "22a3ad155d12e9679a0af1d5e8384417828eec2b9fc36cbae2785464b12b372d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 48500000000, - "fee": 0, - "recipientId": "AG2RijifYGo36FXJN2Noxq6hM1JEvX4Xxh", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100cc3629e0cfb25047f615976b40ebaec5d3048f34eec4739e4dfbff8537122a9d022034f893414c942792e86a25e64494c4fa92e72d2252236a2506d26ec1e6d64d41", - "id": "ce7f905a90772e79f6e7b54ecbff92625073fae404abce4af59244ac361b411a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 48500000000, - "fee": 0, - "recipientId": "AZcoh7wHpN3Ex7ijGMrdMRTF77i4sMEgiP", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ce4ea4644dce81416d60ad4de4ce33c7c389b6c6cb53f06cc3bd2c5f60a08e7302200dce4ca71921b485fc37eec0326bceae9c7345af73e16036a195d024c97644e5", - "id": "46016076914aa928d354260f80e77a6cee67c75a0fc754eb8c84c0278c8c5562", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 48500000000, - "fee": 0, - "recipientId": "AYnAUcC8xE7EXKouH467mTCiubcpbgQj1A", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201697eb4f8560bb074f208bc71be81f1308a275967f9af4931066f61c9f56912602204bd8f4577f71c2f5133295cf6d9f9cc54deb0d8c370fd8e1c100cbc8a192ec62", - "id": "37b6ab528f70a015c24f1d9532cee172e1ac7e24167b199de6afe74bec419b9e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 48500000000, - "fee": 0, - "recipientId": "AUYW8cN8SXecVeBLmTZrr4XGPL4MEbGz4o", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206a44108ed5a88f05d1de5932518d0c4f473ff9b85952ec7f168b7f013ccea9ac02205c0cdb66e19f9c4766eeae55d554af8e18954f88c008621eeaf4bbaada341df4", - "id": "9b7ce4722d40f509d67a01d676ea46ab007b7c3250e70cf450c0d4152c861356", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 48500000000, - "fee": 0, - "recipientId": "AHoMsGebVZJMkwd722t3SNQtz8kxbsK2j3", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a6ccd3bc952f8e17fe373625a22692d035ce466241cdb96e5412246c4288bd6602200c55d0122b0e09a7b98df0940538ab3308937637b03640ece423228eaf724964", - "id": "abb9425699d86129a675ebe33f6021ccfe0423fb2ee919030cd44856e29ae0d1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 48500000000, - "fee": 0, - "recipientId": "ARRMnpJsXLabK1nWxQqm3R1WKiY9ZyZxtn", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008b4c4f04e3234f263978b73e510bff42e7eecbccc61bdddc82c906434bd2665f022061480eeade9e85307b62121241c7dfad53168a851d5a3e141c460bac0c78fb80", - "id": "6c644f623c504fd6449dec1ece024fa5026b86d13896bd6466912ae6f8487011", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 48500000000, - "fee": 0, - "recipientId": "AV6ffXNFhte61rxqLoCuWVTs3ufuJ7Z8jW", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200db348a71ec10ef1fe1498daf3c292411ce01e1f524cfbdbee6f2a9e86959ef002204bbbbf2e508f461a134a6acc333ab4c817c9e2c791baca36d1f235a30320b11d", - "id": "cdc3bf330b78fc143c82058b0674465da2b2e840270869d05a42c2d3eac7ac7b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 48500000000, - "fee": 0, - "recipientId": "APhVjG9pJj2YpG3A7rh9gApT2oFf1HHdbA", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009609b469e7ced5253be188cc3ccf069e155894e715ca8ce9b402c024ae62577a02204b6887fa2ff4881d0e539d66ddd8649783bf4aea3cae68aa5bacb0cdd1e5a106", - "id": "bafdf9d4628983cf25b0f411f0c957ebe6565d01dc043d5f79877d6503749b62", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 48500000000, - "fee": 0, - "recipientId": "AVa7ZKfPSMcnZn5b43m9Z17dLYAkKPepSC", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fbc0bd9719f34d1e39ab2c3eba2ae9b20434d4ed9550d745eba0a176c45abfae0220261f68db9cdbbd41ab3df574ffa1a7e5d07d94917bf3455f0348f2a8c86f33a4", - "id": "cae63006ef7dbc6d4bbb8c0f8b2f0c1821292a53c3720046d2f3e5aa9b18df74", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 48500000000, - "fee": 0, - "recipientId": "APJyeTaF5E619fNpruHNDppitroDorHt5a", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203a37ab7e59a3bebbcf9b1d55aa629f5eda60d60330c43ba2ba1c23c43783ac7702202e75368241dbcdf204a3d9e415f8fe755fd8e60c8c45454de3104e9212a9a56a", - "id": "22399be7427eedf993b9bda222c461f4daac136d9ae48988f073bd31d34d9c45", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 48500000000, - "fee": 0, - "recipientId": "AbWDXPoVRcHmUQ3KW4ZWeuD2mrPYfhodtr", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b245491f63892ba07fa18e5c6f0d3dd50c141bf978094934d32aca1efd8c6bbe0220625ad4d01d27ecc58c643353396093c12c9ee5f617e8a7bab48f590b4e0e92d9", - "id": "e7b6647114488172d1c18263e301818829f4ad9185cae8bed3622cfcc2c001d2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 48500000000, - "fee": 0, - "recipientId": "AUeBAB7qtntRpSt6GtXZXcVNeVkhuS2RRm", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206013142ca9cf760ad5f77a3f946e8cd39a254ba8d4b2424489e01897edd3cabc022050457ebaa39e2b84e773ffee063526ead54194a86281dc21492e3fb0188ece93", - "id": "b9d8c7f12cd758e451a70df541351b3f19dd2f2c5516f7a62629e04227d1d4e8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 48500000000, - "fee": 0, - "recipientId": "AMKTHeQDsTHWw3QeSRLAW2kWQnBrfrqKHV", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f6fae8c2f2fe7da5e0690c8b030e7ce4d7f49ed238d182c0bcd554e3c8daf1660220065e1ec76e1db8fc24fc360e04ed3a04782c04e29a9248597aa0974a67825937", - "id": "afaa4c75e302dfef1e9d36c63929decf4dfd7d8d1e8ec8f980bfb6459767ff76", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 48500000000, - "fee": 0, - "recipientId": "AJT7v8APQuYDV3uDJWsAFGBze9XDKpXdNt", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022043653b6aacf91675ba1ea798f0ef37e7fb265811ae4a52130959e57d4d90091f02202b046afa7f108d0d1e2a377246e9aa295531e053c359385c1406eae8ca9dc4ab", - "id": "3917c81dff0713611f02f1427a92b63d26058577ec5f16eade5bc5f313f8861a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 48500000000, - "fee": 0, - "recipientId": "AGuBeT5rVgCETkMTTAUc94Yzx788X9fiqH", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f17d6e2720d978efea176cbc85f9e5c472882df3f6886172c5fc94b8c207b1f5022049fd7c95a425608177ca111b78b6666829d6644a03c324d033de3d003f2a70dc", - "id": "6094c25254da89c6e8d08fb7c97e255448331fd054f3aeb2a7a239f4cab5c43b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 48500000000, - "fee": 0, - "recipientId": "AVvE9v9QyJvBPZK2kgMDwJxDMu1UEYvYRx", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022019dbbb234bdb03d4652206e72bf2294f4d5e18112b86e1e52e29d0419369e55f02202be9a176cec5b00ad3cb40900080100b8c3b15a0827ae5132e4004f22f820895", - "id": "06df438d747a1cad820cde398e0198333f4389c12faeba195c0abb7fd4d92350", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 49717819332, - "fee": 0, - "recipientId": "AKFLHqUXHQSV8qchdjbm1jUYPptVatZyQy", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100929159e14809ac7f07724a097962ed91909fae24bf045e51c74f5f212e2a4654022053ad7d90be5cd253e8fe175267b45453d5af2738f6478d611869ec5e4b124e46", - "id": "c57dfb4a2801c1ad9868dd2e14eca2115928f013ee8713e068ab670171e66abd", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 50011925828, - "fee": 0, - "recipientId": "AYTJNTk88MySw2YRJxb24XQrHr8fYe3SFf", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207a665bd64a6becf19aaaa99468984ce4e5e74cdea92ab4ff565d8d202319ba7c02201aad01d9484f0ec40e02fac399570338ff66cb5b8a84748ba1cffa0bef41fd3a", - "id": "89079a73bd4806bcf928c52bec954e11ec3de186a4caf4d1bc8f21a430373c88", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 51012164344, - "fee": 0, - "recipientId": "ASm2ZsSj9W7jmYCDdLi8Eh9RwLQALkH1T9", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100cecb5a11966ec1d395eb2eead1ebf4d9c41f7a2e65d94bece07d8175fdb20125022062b79536de24c87d93ace4791643d62e2af941cba4aa457b776bc12524cdc0dd", - "id": "199a36b4f6be8326483599431afdc07c424c761826e5dc915103ab677d97112f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 51523164985, - "fee": 0, - "recipientId": "AYXPcxBW4EiZ3hav9w8qPnYCfMC7UiHUFc", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205006932929f7e0fcdfa814063682fa9576cd197d2ba944a52244d8ac573701620220540b8abb5d72775ef6fa039984fe4fb068fcbb11622715947e0bf43b00dcda85", - "id": "5af79acec8a2ca26ae1ef4a4bb4c36c9d756f4627407b9831dc1aefce88d41d1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 51527438731, - "fee": 0, - "recipientId": "AN8fTyWTGzGMWndi86xLuPZ6sx5RqDU3V8", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c86ad1039fbbf4dbf38750f6933eb94de081730aa4bbbab2e69fbf6bffd86994022065cb206c7a6da6a2cc49564fbdbfd861c2d7a43ae574a9152ca743ccef38883a", - "id": "0e875f8909e934d95123c4e39b0dff610726bf4cc793ff32ac3ab87607a869c0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 51527438731, - "fee": 0, - "recipientId": "AcoxZHkmsepyWCs2K77TaAAuKXzYFRhQCF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a556f84c6f3ab557c61a3f103d5a19f967a624bc247905e6433206d17b05979702202bda3ea0fe77b2217dfef8a79921a4169a421451eb08f31350667e8253eacce2", - "id": "baf5b1d34f909d2ef94d82896e8354fb10d59464743e95174fdaf41f5816d986", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 52000000000, - "fee": 0, - "recipientId": "ANx31923yoBut3ToXoPugKPnzgRhRGELPX", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008e91db1c38a78379d51739c8e7c5bba7c7e10423f90d975a83ae8fcd5073e08a022025c8927e171c351598b0ef39f93c2bc2eb2bc86eccd43b5b8c9f6bca681edc97", - "id": "6e86383cbfbcf26c0268180644f7a946325559b282acd1b4166d1edc55271951", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 52000000000, - "fee": 0, - "recipientId": "AN1XZJrPk8cm7Vwf1ZTrKrZtCCLZgkyrJ1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205e39a2dfce53486be7370d92d65ccbf412e079c07e0405d68fecab4f1e17371e0220438de48ce7ef76a2bb2b65bb9e2013eb1790f1c9b26d2037138ba4796315611e", - "id": "6cd2b5fb62f45dfcff3337b52bc1f4750761e88df28c5d02acfecea7cd0164d0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 52000000000, - "fee": 0, - "recipientId": "ATGUpBz7YkxB86G86796U5yTEgyK5u4rJk", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202a418c3135e189187865442f8ffd4cfcd5469eb1858ceb9add3bd0810ea37a4a02204744e68ce981a9d4bbc54c992382dacd14b47458873da4488e3995f206b09db1", - "id": "722201637e0a6c263a9a2094f2ca0adda2d8b69f44a6f15e4d29a53a93cb2985", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 52000000000, - "fee": 0, - "recipientId": "AGyRgpaEFZ2ZnNHL9Kr5bpMGkv5LUWebQb", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207f5ce9d7c0055100d09575738ccde05ba74dfe3db8bdf4d8a30d0bcd6b99592b02205a0f50872db6a29e0f0bab2c687c44077f9be26558b022dd74b3e2275cb6b853", - "id": "33f843ae2198e8310d2055803607fac2d24907bf8f8daa2f7290967e81672e14", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 52000000000, - "fee": 0, - "recipientId": "AT3g4LwnJfZoN4vuFLtMJY9Wxq2uMtLuxT", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022059078b6b0dcb5203a2c3ba3a46bef4d723a63ecd6fd3a2e3ad29f67b50fcac1c02206994e842c8011c3de756e762b831e9b1aa12d8df872d5838ff98d53aeec95e2d", - "id": "f5ed89f77f37b64aae398ba0e89fb520081aac2ef2ffb2aec87c42bf29a719a4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 52285195184, - "fee": 0, - "recipientId": "AdJLdk5Vkejk95ah4j3ZRcEWu34E1Q7wVN", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022056d42be934431202be893e9d5e6d640d2d9f5acab7aac3bc40017654ee6688a902201f407d488855a6208aeaf3068f109a0b5de4abdfc4233e8bfbbb89ca3e33847d", - "id": "4912aebc2a0e8081a4761cdf85238d17a27277193389698d25200c3259116728", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 52325513889, - "fee": 0, - "recipientId": "AJE5ptbRLg4SGn7DQcaLChpuzjEo66WTDg", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fa8e197adcd0a62c4e79017cea097a9e22a2301be0ab22fb3b5466846bb78575022044a063dd5987a6e91f6ebc4858754ca6f7e7fefcf53bbd3e1e08e20c3f7e14db", - "id": "58aee6b0df8cea2a4fb9ec25402b3440a8e5076890a8cba8a9ccef0ca5b52148", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 52512522119, - "fee": 0, - "recipientId": "AR57nJGjbRknsTMoDJVPnKjeH7H8pQ4J3e", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fb7872a819b54bbc0b30dcf4d8c3e413c84278d631628dca3a6805ec1231e210022002c76cc1e9b7672ad3f0a1db859e4b36564f7f815e494c5f9b1d601b6081e8db", - "id": "3f54c13b225a0c71692660cc5cc0910fedd163ff1aada08cff0b6216df414f4f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 54692496501, - "fee": 0, - "recipientId": "AH1HPESny9sMqiJz1ySzoonvcgVLPNTVY8", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204b168552ce3f30befaf9a566986b6c17f67471fa0cc2ea308acf7e580586d58f022017a5a0173699a7a5440f82dabea25270637d7167372d9aa71ec62b09e4c1860e", - "id": "b1ac5258577f86365c9c532150696897e0f5b304fc755657e4166b7f784adf79", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 56071540498, - "fee": 0, - "recipientId": "AY4gFPmvcG2GfTcVE25mqeDmnDQMPcWndc", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203b1848de0b3be4cccfef6ff105581675c01006ec69da4492815ed63c194338f30220377f301d3fffb2a88f7ed13d42e2231cc5984f63775f082e3c85e02b37c1743d", - "id": "e1154407af38c6fbb875feb1badaeb5cd8262821b3b1b1a441cf506aaec41d4c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 57049922288, - "fee": 0, - "recipientId": "AQbcTN3TDRc57j6fpwAvLjxsc9FeAJThp5", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100dbc5ba06661abfcadade917512e640a45ba57ff29ca7fd7ef728405e87f8f0cf022016d0bd3ef6c0a88d161096520dcf4673dc2cb8fc96ca7e61095ea5e67465c8b5", - "id": "3fd968cf660e948f9fe340334684579db327ab1d6281ee05a840dd1deb0e4b7f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 57589490347, - "fee": 0, - "recipientId": "AZDQwckNFvu5RL2gP6x5SfNFtzSk91tPTQ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e3824a2385b1f4133877047b7d5e702d269c82e64bf96196f526a063047d97120220327dfbafefca990f871ed25bcbad0bb7308279930468676e84c54d701890ee15", - "id": "d1d7712844a3898546271abe9b6c0d139b3d322f436d26d7805caa0a34e1f8e3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 58900000000, - "fee": 0, - "recipientId": "AZe1BULQ52K9W8Pm2oz7WnS8EFsHSmtcGa", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100cef7f753f6cbce6fc6513cbbeffb4fed56e32f0f6632d87f4471d59841a8af2c022036f7202c8e437be380ecedf8f1930d38fada608900e93d33334ea8815c4ada2b", - "id": "00acf4f97b4d6baf38f7a16619a7bfb62d93a96cd30f5bb081fa15099dbaadfa", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 60000000000, - "fee": 0, - "recipientId": "AFuQS4a6wysBSmZJpMXq4xDNAetJJBAFsQ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d8865a56a63f070cbf21dc8e48685ecd862077ee52ccd2c8cd8f40be72c3c4d3022057b11b12e968dd5e87df9da6928c6028a131b59bda09ee07a204dfbb9a055844", - "id": "d3ac3ea41460fd549c79fe3badd7a80ba4d467869b62b6b4bb342d8db1570efe", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 60000000000, - "fee": 0, - "recipientId": "AeWauSzsoZ2FXZwP7GFX92pPLyJMSkSKph", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204531943ac76998e7bb5987455c901b6174d603114ebf915775590c8315653896022001eaa07b36c01e93b07c66aa139cf299ebf25ae16431eb85611dfb9cea9c09a1", - "id": "c6719c892816ee2bdd2c113614cbbd92a59de7b54b3a4c17de68c0da22256d3e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 60000000000, - "fee": 0, - "recipientId": "AMmqT7w7oXLazLwQcSV7WHnYx9S7KSXixq", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200e9b8f5aa7d2471eb1981ce5482230a2c2fd1bbcc6db017f6a1ddee9e4e90eb202201ec640c890050eac7108f74d40f010d1e7d5f9bb05f2fd17eb5d8fe385080342", - "id": "7d269a6a5a9c507c1637707448d5fa61324f9394471fde06b0b53bbd51b0cac3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 60000000000, - "fee": 0, - "recipientId": "AXdGv5eEBNGK5UTcRaFHbW3FwEiXifcegt", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fd07c0502e712b804344fdc118b52e2bd1f9082c52aaa6a87141f4e46d42107c0220718a1a7d58f887e544c92404b49824154352139fbe33eddb5a7dd5e4ef88d7ad", - "id": "f0425c83dd9d9eaae6158673566fc41a1d4dce9304fc82703c97463b4f93db7c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 60000000000, - "fee": 0, - "recipientId": "AYA5LBnUiYXCraG1jz6XxGe1FbViiJbj7n", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022036479d87611d110554a7fc93c08cc78094a769674379c3630c8a77498626c1b302203712a96621cb8133a43789b7f4c64dc6dec8de903de7c7275582c1fba30007ba", - "id": "2b65f2d5ba1c79f39c61a99642af209df57514ea158bcdd790c637d6287175bd", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 60000000000, - "fee": 0, - "recipientId": "AVf5Nefkn7H2m5ccTY2Gss783w4B2XHrZH", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b3ed017b9c924a513e4175d1ae026e1f6e0cf5062d8abf944a1d456c9b99a3e902206e81a45c621846a3b3975fdbc63e842926e2f0736a8cab98e382fb2027309e09", - "id": "afc6dc49249582cb59fd0ef44d5ef26d40d7a4faf6cec4edea6410396511a57a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 60000000000, - "fee": 0, - "recipientId": "AZmiLVom6XmmQjch8mPaZjzbiZt7xEJ4Ux", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022001981eb483fc7c992ee3b6491fee84e92940df9fe96655b36a451771563a94f002201d0494084a12cf4de2441081cae004067d1d43f802c7c661bd75d0c8882a6d9b", - "id": "c177aef927d06946f2c80cdb33855fc233d0b20a5bc0b70a72d3e1f6076e4e4c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 60000000000, - "fee": 0, - "recipientId": "AUs83fWgyxfn3vFcpr5DLhiNeCrRcrY8YL", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022025c7b0874fc7eba0e23e003d5172490a564e3eed0a504861ce0346e2782a28400220165dc08dcf6720694b67de43258f127630a4d4cddbec8a2f66ec1ddf853d454c", - "id": "6e6193eb69cb0ba573c5545757078fab69cd407c3ab4499ab75985fbf378634b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 60000000000, - "fee": 0, - "recipientId": "Ac2vFD8sqzixk7MaK7JwEYoe2QXEPna1e3", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ee6a0aead5de861b29caaf1c876304bd9792e917a5ceeb1164b69aee6293727002201edad412f00a6efa6148a6a62d7148466f4d24ef343e406b5291688a4270b92b", - "id": "ab0da3b48e067dfbdd94c6bbcac4f3e468946a508735dae2c7cae0ce051e2087", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 60000000000, - "fee": 0, - "recipientId": "AbJSiFHgiy1Gc9dnpnSUgRQVu7cmKUTJFo", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022007df858f6b336a855e06e11bde5f75b4584aa8941a8d61ed54b91ab8a76670ac02205fd2ca83ba0694a6a5ff2e552330616ac9896c319a319dc9998f5c6b57edda65", - "id": "6cbc50accd6cdccae73f205c0bddb4c944bdba76323c5100ba6331f89d16f952", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 60000000000, - "fee": 0, - "recipientId": "AaSvUFxNvGZcvxk9ncd7wnqtMw69qTF2NG", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200ecadd5dc516c178c2d80b3b52caf05720883e07c0b28bef1e0a20f27c0607900220178af5fb06c3327e6b5c6767c848bb2be60e311ed477bd64a56caa6e66dfa13b", - "id": "d1bc92a487e9202d245e42abfb7e37f60e08d0a78a3126624dd0e0783c2a9238", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 60000000000, - "fee": 0, - "recipientId": "ANFpv6FWVyodGiMNwfqBvfNo3tt59q2uwW", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022064edc033b3ff422b695196c4a6d3a0bb6b321e467a2771632816d92ad05af9ca02203afca6079f18210a50218dc05956311a77b04c9b5c9f7c960f0f796c436c92fd", - "id": "a3429a6656e132eed4dbb74b2ed17238caf2790187166ecf4af877aa2a7087f5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 60000000000, - "fee": 0, - "recipientId": "Ab9vTWogfBcwA3d9BVdHLZ7RVasLqZ7qwg", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100923954a62e4ab7684af8694637e66839fcff5fc11d54de68b4fa82f7974b313202202d89f173b1c62ab26c6f9210a8cf5b7d12538124c7366c6c1914f8990bdcb272", - "id": "6cb18de69128b7a4c91bfce46d17227f1103f50f0061a9228e2488c58d26d18a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 60000000000, - "fee": 0, - "recipientId": "AGonR6i4nHcvrHN95rsmg25YTgqRm7TBox", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f906a1d22b886f4198c8e6e8729d55f15fbd10761b445f92a2b7748ad4ed0295022018b9fe3b0e95e59d612119d349472090f1a78be9057c754dc17a0c23610247cb", - "id": "4c650df3dca440c5d3f2f17f89fe1000e114a60df15a51190134eac8feb777e0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 60000000000, - "fee": 0, - "recipientId": "AZp6btpTxvxPCWdsJGUqZJCrZMYrS6cCk7", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c8211a6578d9f11108a1fd950260f2e30c15c854c5cbe33af0d6ee329a81bfef02201fa7a8226fee5217097353f9b00c39cd6901e8cd2471cd3d3a34daa56958dc5e", - "id": "9ef08f0bd8beda3e82d6c39147005bdf9590f6df478e87716c5c69942f6e34f5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 60000000000, - "fee": 0, - "recipientId": "AQT6VvbyD8BaVSBtkq2rQJf7ZFxpQMrGFF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f989f104e83e5091791929078b408a0e6c56d86bb8c9caed517293bb377a92aa02201bd438311e99fac1e31bd4c031ebcb9fe7f05de8a330713be4bd25f5effc2e0e", - "id": "30e10c01d2e0a376ce6deb42cedf146f8a849f9ead0170286e7ae6f5ec14af92", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 60000000000, - "fee": 0, - "recipientId": "AJRzekFXBecN23nD3H17GCvU5ykTqZ9Qu1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100cf8ca12989b34d74df49b7f7db16c5d29d5c94e387890a6dc714628ec737f7c802207eda53f3c1d3f43b62598b1b6efc8de7a4b46ffc069750a90ef294b5a5d49fe8", - "id": "167ef310bcb21341cc2b7c32b1ded135a3223f5322eb438751a5081fd8a61c1e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 60000000000, - "fee": 0, - "recipientId": "ANVX8UQEt8MkJokm35rGPiQbkMD6Kc7KSt", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220723a3f4869ece9ec362b11c309a824ef17ff8287d3a6e2f1046db473bcc0faaa02201618ea49f81bf73c60ee427956afaa75826bf94788d73319c8810ef2c4538427", - "id": "b3734997009c33befcf1d9d3aad7917aed5eed0c4c7de9730ecba54a72db099c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 60600000000, - "fee": 0, - "recipientId": "AVFpsz7LiwhRJmCAq63uMCFBXhXYaMNAq8", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206c018a247cc32e4807e6fdc62fc83cab278d50ec7a4f0f67e26094387793cf7b022014782a31c92672ce921fe86b3b8cf36452e40b360830f47459c3fd8b26cbe5c5", - "id": "072dafcb1b3e774663b9c7be4c1c2054f7c9357e3708b5d61dde8104101026de", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 60600000000, - "fee": 0, - "recipientId": "AZ7M9chTUSjuxSUkWVDyQhAbEDVXbP2ZvA", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207ce6a75041112fe7cc42d42ee0e35cddbcae0f33ba051c425b5d3afc235b691e02206092e7c04965993b38e860019c498968baef52f05160e492c5bcfda86a868e33", - "id": "d728112b0c69a9c9f5a3c1800347aa8f87de5ba5407fb16fed5e1079f9a48ac2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 60600000000, - "fee": 0, - "recipientId": "ALMaBHtP4Ki4epwJut68XMDxYVRXTwhPuU", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022050f867d1d89a62e65a88c9861c6fa93971ea08bd0564d9864a51517244196866022032ba4ba30dcf55cd08834085ddcdd425eceb7cc784f4459e62c11e70f29eed21", - "id": "996aade098c28e002949de9eba9dd663f3d9a29a8042dc516142bd56c91f1b8a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 60600000000, - "fee": 0, - "recipientId": "Aapmg3eAWSv4bWzPYMNUWMx2cULimBsKDT", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a76f37bb0e4c4eb508597231f437289107f7f9fd266c31c653c2e10c45d59cfd02202bcf4f912212e7d409bf2a425d73fd9697836a63019a064e6ce2ce8f174987d4", - "id": "544531e7295c7333e4e770a010b73b7584d06751a7d05388f3a523080299f929", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 60600000000, - "fee": 0, - "recipientId": "AZHUEDRF7B1wHT4qcg66t6f6AZpmAwnQzP", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022037ff7edd4d48a58cb2d3aca7cf2b4a6f047b5e0ecf930c6624deec7e6e7ca22102202a038b5e27f88d596acc94687529c4700fffa41e907a34118e4867c012083b2f", - "id": "ce8ac13ef4d89b3c3e9cb359a4e5ecd8ece85313dd6ed08e247995fa12ae92af", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 60600000000, - "fee": 0, - "recipientId": "ALMjKKEK2z3hLUY3XubSxkAhzdpU5i8jsL", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203589acf0d84cf3f231ae931522af87cd17ba02138f1b5fbaf71b1ee6f530451702204881f4bef7736594e5cb28e5fdac8bfadb467fc853c58ca7f59e55e7a7b121bf", - "id": "2988196f5d08ebe98c6bb9de0830e0675c85fbafb60a2ab7168a633360c112d1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 60620516154, - "fee": 0, - "recipientId": "AWFzsKF4dALGbqzQgR81VbFBDdTSUJCBNU", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210083abf4d927d402f0aec00e8c37e661e8be625b8ba47b3fe3d604500c358f88d30220645351b976be790dfeec71082328756d3610e33e57cb8b396657c69fff96feb5", - "id": "a68385459023327d26fa0e745c0891a34789bc3db0392294cee99cf1ddc1b190", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 62300000000, - "fee": 0, - "recipientId": "AGikMwhSBsgXuwCUZsv3EAkxeYSsHCRany", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022014e9a28ad7815e15bef49340133ef40a0ec92d8dfa80c125eb0c26e3d47ca96a02204a8ee6d3f847fadd1ccbf50e54d4a1f220ad28125f82cc1e653069761e953331", - "id": "71a75c1380f47a79f47847fcf615e733c9b8e4d44511490729cbc2cf7cb04b3a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 62614203690, - "fee": 0, - "recipientId": "Acu59K3HbqucEtsNMMLzogU4tFJxmuJkCc", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202448a56483a3ce4580734e2734c1cda29fec792689854f77a3bd2aeeb4b395be02206b17cca51c241b725a9f41d7a7d15e308f0f94f33480fbdea4f4ca2c99d01482", - "id": "d71afbccce8620d94b04bc74c40038eca31034383a296570c0b3181b00433bd0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 62821647096, - "fee": 0, - "recipientId": "AGg3tZ3u4gE8mSM68gmeEiQnjARwUpT2n9", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022011162de5da032f0a5e15596ee983ac470d62876eb2e8348cd015eccfa0b9831102207c6b60bae672fd6e593b3f5040733dde21c7ed75741a85faff759d97f8a5e27b", - "id": "ec44a065625a762aed0e093f485104dd5cca3e4d73fae79d6ead07ee6bc4704a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 63651541963, - "fee": 0, - "recipientId": "AQEsgEhmEBzGLwtXeDzYacpgHFdmZzgxBD", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210099bf9fcf91b0efffd75888770663c351f6e8158fbaec8ea99fe43382e31ad5230220575000d242528aa103ac912daf6117cb4ffabdd867fccdcf847b46d6e46c0a12", - "id": "130c08efd433f91433f3170cd7bc85b484d2c3e3c53134c0d955932726bc5338", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 64100000000, - "fee": 0, - "recipientId": "AL3BcBwg6R2PZ9pmnYe6wzeY9xr2E4Rhdi", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220684f309ec0d8e34d82365c29e7ea21a08e4d044bf5a96e17f692ea4320ad12210220155cd1bce15cfb31d60d097fadfd6aec6bdd14f65c079039d9432ec9c7ab91ce", - "id": "22372757608284b2b88a6c068c4dc994ca37684d765c14f5be0bdb48ad50e968", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 64100000000, - "fee": 0, - "recipientId": "AbpU1DmufxiTYMDQrbKjeKumxoVPwKyczD", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202f40051c81e2bf95bff6cc78fcf9cab2b999d48ebd9b6524b593c95441902d9e0220347c380e7fbbdbe5dd4ea8492a5deb17835b48ac22f388c2b519fd4103fff0fc", - "id": "8726a184e737d57567c17b077a424fb7ac9fe5b0cc9acf509e3af87baf5167e4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 64700000000, - "fee": 0, - "recipientId": "AR51dQ3b2iPD7kxY6Jr2f8nvG9V1Tp4bXn", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e701b0a5ae50d35cc985776be08a2af2a1189df4a7dd2bbaaf7b182ff5fe5ebd0220123def90996bfddd4c0769746130d4e13a8150d75e32770b6fc2024cad56e80a", - "id": "9f890472facb44d3f0fc91cc3100f33ff80a0ca419d6654479c0ad2e1867401e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 66023125672, - "fee": 0, - "recipientId": "ALMVkKVthbGLSyCAu99UXPTcJUCgycJCio", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204c190c4ae5a7885bdefb89112f08f782934fcd60fa2fcc0ffb5a57fc25589f0702206c086bd249bceabc559d1bce46a35687963c7af245e982b83bcb4679d27f048b", - "id": "f0db655b4c6a0cce96585893a9ae0b5834629b471c8a10bae2d03ba7b59a86a2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 66682567770, - "fee": 0, - "recipientId": "ALVWWv96ePvZpsr2LJiYkgczjp5HFh2vS6", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d4223e4d6d86f362b5d9bbcf4887e3b4a7905504509d7cba066761e5ab4f2eee022053f2f8f1e12ea2c979c1ff532d2529577ee1d5128e4863e4b88081fe4223074f", - "id": "e49671791471089e19114260ecf0d0410571a828831fc693900129992cbdcd32", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 66985670351, - "fee": 0, - "recipientId": "AK5aMpBxSFkveYCPLK7655BNCYxE2oDWTj", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022030059d3cb8c99259e250803496094fcbdf5c644a54be5de71ed67579c50dfe7c0220551dc20a9ea8fab2756d945ba96b6df226f786f1517ba500808990535517540a", - "id": "1ab441b67ca4f041fd0b4de0ee084f47808fc41327afbf0db652ec6e8eb0293d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 67500000000, - "fee": 0, - "recipientId": "AZiCsJNur5AHuBmzXFm8hrvzMtxaXeBmTP", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100819f9e25f210711c95d4f5bfda34f90c4cac0d2ee7fc788867059cc8437153a302202f94cd63aa4e59b051dc69a1acf46cd905be00b8547d377f705f6f63b3a61119", - "id": "373da2f2c548fb4f41d36f7b39aed883804bc453846bf42ad223c7dc22f65903", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 67922251265, - "fee": 0, - "recipientId": "AaVsZhcJWbpsrwHFYQ6Ms7dwRamLQGbiEQ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fadc2d82b1e12c7ade65115d3d2a80c59be0570cdcecba6b6d46500de0b1723f022027b16d7f88841422575354d423d0cde46edd1ff3dbfe77cec29aa3cce7ddb61b", - "id": "b6cebe893f177fac2b32bb805b3a8b6ef79dcf42d7e7e1e367da46fec3c6e659", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 68047923959, - "fee": 0, - "recipientId": "AcfGpnqyxHTTCg9qGGDNoJZxp9gP3RZJEs", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220056ebdfcf97cd26c4f1ddb8d2dc2eda872aa41c72122dbf4b1fb7dfe6a13a566022066e8ea897d1845fe11262d60550d930d4ec0be1408c874ae05b6705838499ee4", - "id": "636db58a8c9e586a4eb157c7b2be73af3edae8b5ff48248933970e06cc14ed67", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 69300000000, - "fee": 0, - "recipientId": "AdReKteT3KZV3nxYeaUwbxuvNu4Tbux8QV", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220045e674e2020858cb6db65016b0721094b18d69d5c40b21e420eaca23c7e5e530220581f3f511f5ac357d3dd730ebf0e6084d62a9aeb356d1317463799444ed3a327", - "id": "194bef1b8604a0d86e0dce346149d9a67dec990def05321fa4ca2f5d857a3b45", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 69300000000, - "fee": 0, - "recipientId": "ATBhoLR6mCACu9T9iHcvPmXBbeabR9o2Af", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fe79b98c6ac6eea23af36c1cbea5f5ce104ab7e8cadad9905bc86c36555e552f022050dc6c3a5334e5efaed6f072a584319c51dfda211522d9424561e0d48bf5d7d7", - "id": "bc5c5cbfbacf7ca885544dd956d82dd4a99d3d9a9b922124556711c35aeaef1f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 69300000000, - "fee": 0, - "recipientId": "AULwd36ELPQHp3MBCVjQPGsG5vLbWDwsX5", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220730f890799382a99a6082648a97e89117ee5ea2cf79405ddbf726c8732a48a9a02204791eb8cad7f9b987dbd4865a507c87d446b502f4faa5b469ef938be043e034f", - "id": "8de5f8c57d06f1a26d57f2b30e1a6de70c1f4181b5c7a5ac46d4d77d4aa0d1d0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 69300000000, - "fee": 0, - "recipientId": "ASWtE77ekytMEB82Snx3iZeyECBwcyYAGU", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009e5a8e6261a7c50f8cc6afeaaf51cf433a55a2ef85545823811c2bbca752046402204d879e4ff9709ac18288c75ff6b2d0bf210d4466a7c46d82d688727b3a49eaa9", - "id": "a0d0bd4e4470ad7708a1b6a8d8c26c5ac9de239b8e15a581cf2a29699648f944", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 69300000000, - "fee": 0, - "recipientId": "AMWKeZaKAZgtvX3McGVZGVCjR519mjdxod", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e910ea19a113d2fe010320fba9848358135d3989f92c328a59d9611e1382a2c902205e83c177d11dd68147d97a9536fb8657b18121c69e3257034a101c7c928d4cc9", - "id": "622e1654011fa90a79be805a43dcd875b0d649c5e5d455285c2103430f5329f7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 69300000000, - "fee": 0, - "recipientId": "AMP3grMYVWSxx5g7KdRf84PjvT3f9NDiZ4", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100acbdeee8bdd4dc28809d30dfeff20965f48211675bddf7bed7a105a6e04092e202202c61fa35cc87ab1b02a8530b0021a8d05d2d20f34520637c9e17f60e603ac2b1", - "id": "9ade4fb6bd9dec55103ea68751040bb6cadbdb7f7518442327d72599dff0ec36", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 69300000000, - "fee": 0, - "recipientId": "APyUPnRwawq7LHHQbuLVwEVvviUS9gYvqf", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210082e9e6dc9eccb3d40725cd5434128ebb94f56d47bb864d879a79cf6a6d7d92e6022020e76a058819f2d1afb709d1262835bd3571112422f885d1bb5dab06900a41dc", - "id": "961478ef66c79ff2bfe8f9c1f7f530c1111e998ab0f36386ae9b5d6a27fe2875", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 69300000000, - "fee": 0, - "recipientId": "AaygtE13tPoD7HTNBJiJQ2McDRxVaWdJYw", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ef82c8eb76122d5557befd50e52d7c297ba3ec741cfc050fee3a1ca10d3bd01a02205b199789061011b890a09029642750b784f53a174082d8e113a43083fce2cd24", - "id": "5291bc37168adc5497986964e7f44a553aa5007143cb056be070857b285cdb0a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 69300000000, - "fee": 0, - "recipientId": "AamfrghtYBbWkQtRV7vohTfrJCPv8Sg15f", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220757f338c7cfd2ba9342a5246b2c20ab46c2fab13d8b8475f748ae596d721afd702207dd4f45e4a0562765392b4041a812f3caba35985ec8b0a27f59af91063ca0ff4", - "id": "49f82d3d4360a92c509afd96a16e76adb8cee98b82b17c9ef0d8403a7d933b8b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 71253891180, - "fee": 0, - "recipientId": "AHcjnPJA9tdupQcjcJMLtD6nnWJ9e6KFQs", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009ac53ee5d6f50ae5eb6021aae45b59f77ff49b259a42bb7b072d1c9506c0063602203238177cd22e6d418db707699b4051639c4c530283d13a02c8c9ebef4fe9b4e9", - "id": "04069fc9bc169922314401d138568a956bfe70d8f3658a5fc456c33ca10ad67f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 71429604399, - "fee": 0, - "recipientId": "AYFJQE3EPeSkuxEa4PubKaS4eVitUtZkiR", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200add47cd5990fcd49c559868724063fc58cbdffe8c79aa958a7ecbc75a603bf7022017f022de0655cd62e383d4b7d9d4a4a074b8222ae7c1bd6cf26716c1292bb3ad", - "id": "06ed48da4f8bfc579c4ef2298b43745eb77529889ece543daaaf68ff2bdbb9b3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 71590501751, - "fee": 0, - "recipientId": "AMP1K45yauAQU1c2NqwuUu6ekCmBqtaPoP", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022014db9f5d0e46fd745c970ca39b54ce13f245ddff480e7abd19b318c262f3b951022062ca17464681bdfc5381876ebeb66740f14ea227007c34a4dfb1fe0946c6e2ce", - "id": "34598c7779c1aff18a82cfb53f52f7cd9baf51bf2fd9eb2d8088f38e9de615e2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 71600000000, - "fee": 0, - "recipientId": "AQRhAZwfV8rwCvsb4WM1bVTbenugJn3c8r", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206b2f68e67bc4218888b389cadfca07d954a4388635974d1c4872819d9fb4305402201dfaa7ca2e83c0694ab80505ac5282ca28ae45970161acb774b38d87efaaff22", - "id": "e9a1f1079b09dbc72a80b56de6c65f7414455d7d569c6ef55540286c6e6cebce", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 72653688612, - "fee": 0, - "recipientId": "ATBxkNfkzM8NBxdCsJ44RdABoATkD98qdR", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ca29827b5530b9846ee5a9084d75a067fbd9a932073e731af57c460bc403b5210220487342cde09bc5c339223b575a8a4c325098202707c68d09e0ed48bffdf72f1e", - "id": "11ecc7e569168d579cb6273ce6d27f58f29fd4db7c2897082d6e49cc6a9bbf8d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 72700000000, - "fee": 0, - "recipientId": "AW5d3EEDJBQnKFHkPJnKPTinSivnBzUYkB", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204e68f134e251d796c0c4044c61881a7e49f260d17aef7fd2f4d54010d9fdf52d02203dd02402d012f4d91946dcb449f95d61d874e02ca02ef4bc2c36f7d4eb5b4547", - "id": "f38d068010566f2d81f972525779b84f6e8693880ef6251b6a63153a2e98d9ad", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 72700000000, - "fee": 0, - "recipientId": "AMYvEFsHMcctwEmRrghWE46Jj9kWbFPA3A", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a593780b9925611723664234d85ffde90f314afb68d45e4a0fccb00d40c1bef302200ddaf8f1677d988f24302a388e71d069149437822d9c7d2330fa037f96770110", - "id": "b1d8f7741c54407d505bcefc467f1daab79772ff77e71bbd0ef7f8708ae43582", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 72700000000, - "fee": 0, - "recipientId": "AR44YGUBeJXZDQcbUebK7Ds8fVHy7DHjgW", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207ac0a6b2a5468d60e9f0290e5ceca35b896db702038309ffae2a410ba25f2b6902205d3643b2569c0b185f2bce2a1539f5a1cb92438a1e6efe3512080712fbf4774b", - "id": "1bb883cf35619f46d4839ebaff381c9ae4e00c3d4cbe981acd00bd98a1c4ab91", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 72700000000, - "fee": 0, - "recipientId": "AM3F4yHDLbzGGqJ9uMCjnPCv8xzoJ872sp", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d6ac3a85f45bd763fa1fa8850dc8fc12978155982c3dbc250a5514d2e4f6081502200d08e59cbb1dd6bd22a7ca8375ae7bde3dc2499367482483b84b5dbf2df4a0f7", - "id": "e46dc48c6353759f1bfd73f83112c808d8a2ebbb687fcd177002f7facc3da944", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 73796297519, - "fee": 0, - "recipientId": "AGcoFdESM7NdWm1HodtJyDU32BHyShDqat", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022071e03ef704b56aef2dc368d7ca2642275dba4b81eb085aef6b8c1fe48307006602200a4354795dd801e4752ca441187d98f25609ac9b58a0ab79d2bdad3659db6ff8", - "id": "71e2650406959057c6503cf336a99c7be97b592eb47e383b03a334bfc3cd5bcd", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 74014667709, - "fee": 0, - "recipientId": "ATkzTWGZPHNhEKCToyULmtTaxSJYNiZRYz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210096a5bada156e5b921023bc5d2e79b9cb6058c2a6b722674c3bc04ff45e2c2d0f02202a804f965b55887303c2f15ef22bdfa0f70639141ab4db6b982832b76e5111d8", - "id": "ebab1e0dc5d5a02577ba83b3bcb0641722dfd2f08acde9ff14e7f4d5c5415ecf", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 74124099984, - "fee": 0, - "recipientId": "APpwmia2SFKQmhNZq9zxav8aidP5vBBptx", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a4a58e349be3f5d8da0b00d71bc150261f2538a2031e06fc8dcf0df8afcb6865022003308a299af829c6bcad2f9bd36c2648d96eb3a3bb8b0c33e687b33099cd1fb3", - "id": "0dc2b86a4ea26f04660150949d00091900028bb6fef863392f2bb3110e77b498", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 75000000000, - "fee": 0, - "recipientId": "AWshU1zde3iSo4691GzJ1ibHDCqA8n7mGW", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207a0ed602d07dca8bbf2b4d9458a0dc70fd92c192c2bd05cc455dab5977be3d48022044f872dff762af785e654198f8342b6083e66e9c0181a16c3c21a1f2423b982a", - "id": "2df19ff6e35c5a76a99a062d893432c27fbc7e98e6d4df4ded66109311b58744", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 75000000000, - "fee": 0, - "recipientId": "AVWUFjyvKxXZLYexXDWLiJLuLXZ49Ymb5f", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210093a55677a758062eeb56a99d5994dbd886a2bdd33f76287fb9dee7ce6f25322302202ce4a80a24aa3b75d0b8dce030b04985f3b625252ee8411223735ed013535f1a", - "id": "40dc85e19386d391e0da2c9d50cec7dbdef55e9777f8e0952e26ee7c9a2523e8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 75000000000, - "fee": 0, - "recipientId": "AL9X3YsJksdtUqAMS2yojWG3rThNH3rXqc", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022007d901327f63e75971e3a5a023ff97c2513cea96dbd4f4d2969fc33fcecf3c8d0220606584131bc5a786e15e7a7fb7e8dd752cffb73c578b5933a62d6ea6072c4ad0", - "id": "9d0d26d7fe267421fc7db3b7cdeb2b877696d056dfdbbd4dbc4fffbd052c5584", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 75000000000, - "fee": 0, - "recipientId": "ATE6aerp28bsHn4g9uhKk4ncJiGtKpuSNe", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204faead30048606f6b057ddbd4ee76da47657a853cb8b644e588c29d375c9f2ac02207785f0ecd77c1af9f8f0f68c5cc3d344e4176fbf911037ec8df681e72f5d5f09", - "id": "7f2a17f9a14b1c84cf539431573a00a63d9059051a58ee50a3ae575effc08bd5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 75000000000, - "fee": 0, - "recipientId": "ARc6St2Vj9T6PU8JPMtUn7RBb3DuM9uE8p", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100de6a748d2991d40d71bbdaa2de5cfd9cfef49c4bb9965174e0bdccc58cd7e0f5022027ed0e384fdaa03164d3e0f317f475d6f7728e1747afe8ba829a47edd3f39b59", - "id": "ff8a36e128dfadbdfd48159c0b65c608702425d36f3dbbe0db0657a80cae66de", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 75000000000, - "fee": 0, - "recipientId": "AHfcau8pKsQVGWzFCWSXDyoXGGkTKvuXsK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b1a48ab2f689a43aeb5e2b8d59f6e777ef945685301e64dcee3325bb9b3ef46202204b125c359a5d00bc54f0c0d0d3580a9841c73cf8e2ae19fe3e9666587a83a143", - "id": "64e0e887e60dfe4cf3c0a0a00ce3c89c5dbf6d76607e6c1bec2c8e65611492d6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 75000000000, - "fee": 0, - "recipientId": "AaSGhysJYBesZiFSf4zHj986A11DRvYtca", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ed44ab5a22710c41eaf06885e3b3801bc9234987085adae32f7277d1c31dbd51022022a855e38b1e57ad064a4004f37597ed6d31deaa6378e06dcbee180075ac0fbc", - "id": "c2742c309400d0bc0e4cd6b4a183cf9c58d2a8d00ad67564e6243e5019ce79a4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 75000000000, - "fee": 0, - "recipientId": "AS3gXToMKTMydG7jNvxUMNxygvUc7okXXw", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d8b5a57241bd6982060e2a5260a6d6a9ad076c10e9a6b863503686b5b1d9ff0002200933f2e858f279ac24011d5008fe4a6ab99efab3ec795954a5b86c121fc09a90", - "id": "27fb7f36d8e0e19ff8caf5f74033b5912eec9611c439561f1d3edc37537c569a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 75000000000, - "fee": 0, - "recipientId": "ATS6jWiztxiZw8EFr2VU158ZiJev6bYfiF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c434b9605d6bebe5950ae910388235326b701d218a72f630a523386a3d085fac02203ba1a9016a6738f0d6078d89d7e481aef5fb7e1c698262a345453f8978211ef0", - "id": "e78b60e89f9757af6f2193efd826c7f4e43cf55e992abb49a49fc6efd8488ffb", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 75435988442, - "fee": 0, - "recipientId": "AHZ3K294SDAyoNfoBX7ofAvxNr3u6bt2Tb", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b700ec2b35d2a4a0a36802e503a75794557148e37121ea7983bd3bfad456d19202204fc34d8b6fcfd513b1e7d74dbcde9963eb606b234897f1d8fcd1024d236a7118", - "id": "c331e66c718f09ab4d637cd139a5e0a9620b0aa268b8548b51f82113a6f91961", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 75912885491, - "fee": 0, - "recipientId": "AL8QkKgRswzkHyPfaYuknd8F98onyYfsw7", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b2b7083ee6801b455e14c24c1f739e1f19f39d429054e86c77475d82c54be2470220128182d6a3c7b36eb456a5e2438b9cf7579b981feef78a31fd71e63200ca1959", - "id": "9f74b82aa9272226bff1637d566a3e3ce213957f053eb6fb167ddc9d1b8833d2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 76800000000, - "fee": 0, - "recipientId": "AcHCwqRbb4vneRguSP6SXqFq9RzUt8CCto", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203e95f92a3a0ff1cca53e0acfd410a6204074cc52135671a1cb1ab193e7e42b1402202cb648ac7bcefe13152483a85f5cb54a1f2dcbbbdef70fda2e9744d210692614", - "id": "1758ed13f7ab2c65bf54bebfff06763f6955bec8836c1bb84b9277960aaf3cf3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 77291158098, - "fee": 0, - "recipientId": "ALikhBgeXghUsmNXXwXD37SPJJKP9YKFQZ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022037329d395aa796dec8db0f41eebeec510fdce6cbcbd5bf429bd308392def4c6102200f20bb5f7d5849d26ee324ebe881ac7fabbf3c4e7fe5e93605c0f7615810f50e", - "id": "144e1c573644f0461ed333bd72feca9b94c7233763ef21f196cc869937bb5de7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 77900000000, - "fee": 0, - "recipientId": "AG755Z2rSmeXi4nXc2Dp5xjEnV3h9DSePY", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022072dad4689c20a7daec81141e12e12b48ccd71072cd2c6350321523515830f97302201e442d4448f5a0fb02e73deff73b0f798262beeae00d47c8f8a4ad94e041a4a3", - "id": "50259feb79aaba9640ac99629d6dd7cc84a43c14529be5549267c3fa219e071e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 77900000000, - "fee": 0, - "recipientId": "AX6JKGAGAURGfwYoy8r6BqCsiHibZQFtSH", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100894bcf3faf2ced40b12da116be0cebcccbdc17d822fecf2d52be9dad273dda8102207de8bca9554c27b1a37e7679c1b8ea4bd75b1b47697b9535789d5386f2a69289", - "id": "c6a38a970ca5a17e2632b958bf0f035c4760e2320fe5991c19dcce0fbc2ab0b8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 77900000000, - "fee": 0, - "recipientId": "APkL2ZzBb5Smjhv8f9ugVgS8iRJP48pZEs", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009f0a965cdca67e25a459cae3674e261d235b631393a521c95118260a50ef817b0220667fc3de471fea7ebe7d92776f8d0358bf1cb6e480c1964f45d104ee9ebc815c", - "id": "b11d02a6be882a82b57e34d0876feb673843ef6baafba06421adffbf34512108", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 77900000000, - "fee": 0, - "recipientId": "AaSzFFxYD6brxjKMaz6PsLM8Mgy611VWXU", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203968e6322e183b266a578d5f6599b707c74b8d35dfd2722b1e43abe8850434af02207e4a7783ba60cd39d8503769f886d7f699b673cc1811879f37c97e000aee180a", - "id": "8a51fa7f53ee7372961dbbc9dae868c1111748d769488e36cb21c575ef3e9475", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 78500000000, - "fee": 0, - "recipientId": "AavMxN9AF7pJ1yV2qmWjYeihGZN4QBVSTo", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f4b220829fbee7eaf190752898c3bc55359ab757413f2acb3d8f3f3c154e9206022076632671be2f123a47cc6b18b1f4ce8c5fa6697247a7b4a4da6e87e9c3810585", - "id": "becf405f252fa1c2c16031dc31feb20317838c2995476064f11fae9e7b3f23db", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 78806671001, - "fee": 0, - "recipientId": "AHTeBUJSraRKMDkpYmE2kzCTdEMTw2iS8v", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d1db2ff69f14e7066d9e3a8e9d9325f9c9c840ca2f3cb4499b72d615cbc47763022067e19df7ea14b245626624b76b408827093fe02036142966ad7c91c2a847bd83", - "id": "98e5b97afe377508e7c517db5d694167a9380169c78903aa92c557d673b7895c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 79513639585, - "fee": 0, - "recipientId": "ANg37TzJQgskwThhQ526R6EukJff7jWXZD", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e68c3dee5a9eb77645c958a81f8b3684485ab7ef7108eb29c95239d618ae6cbb0220779e760390ec7ef1700cc84b80361dc80c23df1bdc8fae6e2686e20f489342df", - "id": "e6fc80f568069d7ab3bb599d99e81f02ee2e8d85b449e37b2456f996f729f727", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 80575550021, - "fee": 0, - "recipientId": "AUwoiLFPxQimPnE8Bnp7Z9GVLGwNMn777R", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204d6e44f453221389ffcc58e8bc98a9a2691483f2e0713c1c0ebb38ca87236593022041a360781403cb8f434b221177060f206b3bc7bb184e011eb1e5066f1ea8d70f", - "id": "75dd1bd25d4413d6d4d5c2d9853d9c03283a2606365b1f0458d1f05d2923d4e2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 80648439946, - "fee": 0, - "recipientId": "AWojAkunWdVit59t4Pr29yGWAdhCrMzeNf", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022030ee47eac2cb2b80f4e22f407d8eee4c29733efd0e11daf7a3937bee384e44be02205695c43c6ed886a5db961b87b6008ff171ebc5add97e0318680528940ed0f007", - "id": "52f8f83b4a2346b02e454ecb7dbd9fac6a8d4c3f2596eccc5cc5c1444844a0e1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 80880602287, - "fee": 0, - "recipientId": "AdTfUqtiB3NJuhhw2T2xPYHiMJYGJLFXBT", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e5957017f40d2b9a0a7f10722697b00e49c16e791e1debccb1c2a072e3d063c202205c19ad3b770b138245321ff7dc299ed5f411f3d9fe727f22b54d1165f0447b8a", - "id": "f8900c94b8fd4e9c80d07fccee69881edfd4c525f7d624f076522bf677c50c56", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 81355763706, - "fee": 0, - "recipientId": "AP2TBQe9NmoirGTja8cbSuH6158GFprhoS", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206a6476bd2d646155d0bab7dc4f952a8856c7789a6512718ae6d8b7736ae1ba1802205fc63cac9eb495ef7e2426a64668b771a694826c943023891f0f46f5ea8d4741", - "id": "ff059b0e5477e3417d2e3ed156d6cc48dcd62cbd7ab13e30c5120f80900d8e73", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 81837696809, - "fee": 0, - "recipientId": "Ab5x44ac1h97exN5QJsXY81WfMTAJx9YyW", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022014c66256aef1f3094558a0890efc5a0f22a25dbcc1ff52c6f618a1ba519eb5cf02207a79ccc6cc6e562604c2d8e11eb041c27ff824a2682de9f399906e5360bfabc5", - "id": "4caf5c2aa360f7f94a872c50e41028b7c83540dc65ed63fe8c982fcf0472c0b8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 81837696809, - "fee": 0, - "recipientId": "ATpmYfefxVXdB6raUbujbqqj3bx5kVoDfB", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022073d07498a3e0ff75e0786796c973e8700139fe9222319fed425e96b03c78dad5022012fbc2aa62a479c88197a3656ec4488756356cb4d568ecf65fce5e8a15f5c5c0", - "id": "5e3146ffca3c3c29e76ffe1bbf28dacecf88566d393117d870feee892111cda9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 83100000000, - "fee": 0, - "recipientId": "Acp2FNSe4DwXdMvJxBn6iLGGMZnvmtaR7X", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100cc65894c33488bd85d10eb2104a142710a36d8f4899c8198dd50db51ba7bc6c10220527d3192c1357d324ffb84a70fd02b848328fef04d26537255c4f8790283f138", - "id": "d584d07fac0668f426731192de3967134dc574c6fedf27d98118df0c796c2dc7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 83353209713, - "fee": 0, - "recipientId": "AJHws2iLF46S5j8JYEwSHnSvcJsfGmXuY1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207934233bc6857a2ec4440fb1d3d80c30f3e71b86c59438fedc0cb8f773e6d15b02202f6915682768af0db3aaaeca4e8154acdda3446ba816d4086636af89ed8726b3", - "id": "f16ba7a15ee70202fde2f172673374204a3beab555954cb8edb829214d4d1f9c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 83989725133, - "fee": 0, - "recipientId": "APwVRmToNQFy3vq4wiEHfi8F1vYfwGXMxe", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f04bd60b6b64559d86587bf3c83f6407c20b0ba82b91db0d79a284222d5ccabb02204bac2bdc4e1b328c668aa33cac6f435b666cd6e3f907b91463b3f5b03678d799", - "id": "6cc3b4a346d67a46d101749766f64a6332dae291d148b969d042cc8d178085e4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 84900000000, - "fee": 0, - "recipientId": "ALaNWEzy9zhCVGrJw28wSV91ES1fhWZpQb", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c27e16af3bac0ad147ffd8a884565fced7a6a609fea72d2b3e23a54cbf83e77b02205a8ad93c4da3473bec4315ea1917af5734413171b22df8d1f1cc14b1f0071f0f", - "id": "d7b32ba893728ea38b48d632302f22e87dab2c04a01cfd809413b7991afbc70a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 84900000000, - "fee": 0, - "recipientId": "AGNzHhBmg7JkSgae3DKbhj45cJvd3at1ht", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f0dfcdf262541ad079d15430a8057ca753ae9ad4030811913110356b177afc19022031c0156405f2ca3262c121a648242b65f40e6b9e02f981af090bf5fd83d77234", - "id": "d3173641dc8035ce4e2bcfb9d20ad323b3b525d9cab25a86f8c3ecd6cddb1c07", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 86600000000, - "fee": 0, - "recipientId": "AbcfAjLyDM9xKnnKyPq1esgwrpPffzuhpK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100868c42ee9b8707f4c5fe317090e9d4bf3ee8fa023fef5a2dd1ab6e5613b58c5d02201898a17c6bc802c3bf03522db36e4d23a4fd74e780e83aac72043f51dde78074", - "id": "8c0b5fb020e1c904661cc97dddcb5ab0a946d6b4f8e74e4ec22dd225ecbc29ce", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 86600000000, - "fee": 0, - "recipientId": "AKFXsYK3u5Yys3gaQVtQnARgQYr7K2qi7Y", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202298b4575ccaa0e90fc62851eeb490411cf463a7f81205b530d9abba7151376502205b1834247d0e24b428d211069e86d0fe7498dce5545c21fcbc669a04738440f9", - "id": "83b38ef3992d3ab5884eb9b7bdfd37d7f937134f238e0810aa807ec6c78bbd34", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 86600000000, - "fee": 0, - "recipientId": "AHiUknBbPYinqfG6pGkgJnLWXyH5SdiAwu", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022068147f79aeecf424c969d66138b8cfeb1a543bc833d0c1dcaea2b361ca67088202205af3b0111769ab1ad9a03f98c25b3df6db75e352c973cd6ac2a5adb9a528af23", - "id": "8fcf8c21aaa30f9a9cbe2e8f17ec58e811e52a7cb71ddd76a824560dd5ed26c2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 86600000000, - "fee": 0, - "recipientId": "AGpULTAeC1WWDY8PFZjkzrbhwUqnksCTKA", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ef2a3557376455e0d25cf3c951db49394f7b211503b42b57a239be0eb76b41a402203406584ba5c6b3f1c791f1bd54781c27cdc2e4f794b1c187e3ba05d8710559bf", - "id": "7c09fc8b85c9a9b231aaa24e4ba3149f2da51dba3693bdda7ef1dc584b6e2094", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 86600000000, - "fee": 0, - "recipientId": "AWhFiapndC1wvpb8o2cxWak84oFu4NBRMo", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009a5bed09102a91a9355bec42813eb86645d93ef79c7eb0b26db2bd155008678902207b8ce7fb53a59078b53581a81ded88b791737d1caa5554c16a05a9c4ce15034d", - "id": "0288e9d30d96bde32799db384292c36aef36840fe738d86431cf001180e171c1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 86600000000, - "fee": 0, - "recipientId": "AJUmYRUYf6hksjU2eWExtQZuxf7ZCACSzt", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100dfea58e5ce12a20be120decac2941a279eef12189d7a5491856278c7d18edd8e02202aba95df77908c8f46353fb1c4bdbbb1d68ad510dfdb082fb7c7f74e3f4f573f", - "id": "eba13a92a631ea574f1e478f8d5612d3af6ce7f6d610b669a7b46b30d878cc7d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 86600000000, - "fee": 0, - "recipientId": "AY87NwPwRs4tYVYHznKKoqDWLqCUHrErYg", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210098df44bd72c80c3ee825909494f2a973484d1b9d806e8b2d6359fd89afe5714b022002b29eea85c6bac9267d37d0ddc0f74a89c5833cc15f5b8cb5afc98355b6eb4a", - "id": "74d30f27ca09703402c3f09089ee5c4cb8548625974a0416f5efcff722984606", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 86600000000, - "fee": 0, - "recipientId": "ASmTzH5eR9V2nkrU7E8J7G8Mu8RTiQstJe", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008f15851112cd2825da273a761843bfe8d772c88da8e8108835f18758758e3bb7022040497db4055ffa5c14c2a2f3fb000cdbeddd5a8fab8a9066e55b2beddb34a790", - "id": "850b70f669c62b31665190b27391ec3dff00222e4e86757e04bb35cc01076357", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 86600000000, - "fee": 0, - "recipientId": "AQqd4UCBGeKi4kJZyjoqPB62W7Gqqi41YL", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022065e858357c4f598f1cf4c26e20e52b245fe92c2c58fb66c986106800e106d02e0220071dfa1c61fac54e79288d6b59f7b15c7271269e7cfb4c93a76bd0a7c6858011", - "id": "dc1dcfabdef49122d646d50d52fe9b558803df411c765ccf04fc4d86cc084d9e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 87141991973, - "fee": 0, - "recipientId": "AR8hYbKPJYajTNZ9pnpHjWrm7CFqSFaGHL", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c68c155894dc39eff0136d6aa9d7046964f2abbff63548e669298b937a2554dd0220669383cedd736fc3524a286ca3480001e17e6fdabe616b0290d151bed94361b5", - "id": "1697ab9993ff077aecedf05e5897d1366435f757709d0d4b8ba163cfbe64192f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 88300000000, - "fee": 0, - "recipientId": "Acd1WttVcq32Xv7agoi1KnoQZ3uhS3xx5D", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a6e73937fdb2867650c86e5ab5afe66407d79f38ccd10e9af6e6b9210852bd96022064e9f36604a80e8ebd292143d6dbb4dcde5fa48567c1389ab448a87b57a7edd3", - "id": "129f59b0d566a5081114b4a4a287e8d352baa05b6ecdf8efff73fd0661d92da5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 89405066332, - "fee": 0, - "recipientId": "AGzvuwhRjpXsCiF1acJxFnnXgNrMd8ekWe", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205dd8167fcd2a31bff75cb1a6a98adc1490bdd1c8a073929aa05bb56ef3cd199b02202d18b11bbb36c91fde4ae756b4cf7dba8197433c8723835ba91b1f4424a40a32", - "id": "87618b8944a3032bb912cbece834559d1f043e7d7255705c6abda7bda031f445", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 89514072770, - "fee": 0, - "recipientId": "Acn1izrpJYNCFrqFwFQhKXgXHHyxpGAVTw", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207a5fdefe87c639533e3a5ab01f52624f4d1b468ab88eedab0e523cac50513e1e0220388fe25747ea2764ca18cecd4ef8c3ddfdfd2a689effe830b22f336d1db292be", - "id": "2eb0bcffeb0067aa69f8438afdf76f91969faed45f26cd0c641fc0b11695145a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 90000000000, - "fee": 0, - "recipientId": "AG1bTaALP6A4Lh7Z3pDDehv8saCFpf3RZQ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204f0e2ff8ab6eb771ce67474134ca7a8b59f5d0fb421730fdd306803a548f867d022029256ce0d0c018660a1c3a9f38a7c627a07247a73b2058ae52a000fc06781267", - "id": "591c06ac993f8d1454caf4553330213b9fa246c7b8e761077768907b7f136dd4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 90000000000, - "fee": 0, - "recipientId": "Aaq9zxgsq5heY5uGvHWpUanFsLp1aFYtzs", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c537c102f6d3e77742203961dd7718f4441db6f9c73e6dc17c139c7639707592022049a206a019ec362981d3377b6172d499de98a4e603b2c6b6772e676f3161ce7d", - "id": "c66078f962373fe9663a4a89460d2cb8694aee100ef510c511376b64a6129d14", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 90000000000, - "fee": 0, - "recipientId": "APdt2CLhn79TsuFb148bXN4yRWDtxXByi9", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100be4c6dd3e6266e8f3c488f7a70e43547b289a8a838af519fc0e3718270133a7d02202b438a20931ed53c67c4122f3bc8543c5f51fbfdd918e87e2da4d0aaa9becaca", - "id": "fd445aafceecef71b3bf57c4f047fda3759aaa279f5bd7108a5ef27538b11002", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 90000000000, - "fee": 0, - "recipientId": "AcAs2VGLLA3CTwB4zzHBnbzrUpFU94qpo4", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c21d6ea2c66bc26ddbcb250dcfe904c10684eaf8a30aef233aec5b64d3ec9687022011396be43ba72cd7cf2f5001e619fc4856d972745d012194b3d951834562f802", - "id": "0963c73ce61593cb65e2faaba4c803783a37198d3b93ea54323d3d52615d45b7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 90000000000, - "fee": 0, - "recipientId": "AHvDiW6VVXRHhGLkMpuR8ZX5auBZPHK4AB", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e333d51da7708efc3ab80ad3e510482ddfd87f2193b1d61939d38d5092fc5be402207bce382e3990b99c53e0c8e5dc3af4f948a7f511e514684e0f59c37aa0ec54a2", - "id": "f2d908530b25ea4f3cbce6547e4aaf248e18fe149f190d598f775fe26458e60e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 90100000000, - "fee": 0, - "recipientId": "AJ43cE4rT2RoUAv3LfkDzYYG6podChvrUQ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f4b85a3222ca9638fc1b8c5a64e4e23aec2cc4ccd8d957cd1ad0747cd430fe910220716428e2f6d7289bea53eb029006d1a1472cdf4661c0e55eb9b04ebcfde5958f", - "id": "a533655b53cfeff54fa341c2d9cb376e71c70f2b7625f798a66e6e1e0ca1c505", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 90882035337, - "fee": 0, - "recipientId": "ARQiX23neMcAX7SPfyauwbixNhfWF9DF7c", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022018c324014c8bdd445bd2025fe527f52cc668886705b068837b9d86cc079ff215022075646fa0b7eee5be5355c1630eacc5bf4b98b1653db006d1cbfd41e07c156218", - "id": "4111147e598f03fce985dfef561a5205500865a7adaf07ebef15a8035b070726", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 91944813749, - "fee": 0, - "recipientId": "AZRqkB1K4ybcsZXL5ZV6sYfcqYD2bRYwzW", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220794d5cfc737927e6e6e2a628298466bda26bebacc349fa3b75cae81c636bdfcb0220463b4ef0055b4bad1150add80f192999486d91c4ccfdb07da77bc9100844271c", - "id": "8e3e472a029d3c34d9360a92e08f71ecadbe5c2290fcd295d691b35444510626", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 92749389717, - "fee": 0, - "recipientId": "AXiaU4ksGV7AZHLSPPMM5Ey1wbEwEXoV2m", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203436a912a9c53191957842d0d04401a5b33b3255bafecea63ffb703fe36bc4a402204bdd3dc39ea956d43a909b9e5ad8c71909bb5291238ef2b0ff7bfea8cecce5c1", - "id": "6d4a7e3b265f562969594ee5ada3abf8a1a3028a324803a7d8566b294ace73f6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 93006402914, - "fee": 0, - "recipientId": "AXZLHoMLqpEVAfeP1qcdtprba9GBoK8rU1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022074f4c6f3bc573b7e6ad33da49b2f429d8bd93d2947c8befb2eef024e24b885ff022035902bcc93a8f39dad93fa36d0c378378517d50ed2bfe1b910d8b6bf2712b3c6", - "id": "69adc07393e5f7e41408f665571ccdf56ed41e8414002df3a5ee78f83d4e51a3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 94906857132, - "fee": 0, - "recipientId": "AGwRjNYG47ycvkzKjM5f6QrzcHoU8MNRHT", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f31598c11e21ad6c472aca50ae8c9e762bf7b9dfb720bf81e4edeb0c94a0863302207172d053daf3cca142027b29c62cf8ad23f2bcfe856d9ae9bee82f2398bfcd38", - "id": "c311c2d17e5882af306b820209a4cccc28f382dc7861e8e59d39b7a5e023502e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 95300000000, - "fee": 0, - "recipientId": "AY26UuQoCQTQA3Yaqf5Khwf1dWK79AkfuQ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205a0874f7f7aef5aa5eb650196cf1ee1739a3c0d3be23fcefb43cb63efee96f3602203aa0715dd7b00a84926f6e7081840adc97f9dc8dff542373b614bb9a20912074", - "id": "493a1e157bbab8a11059fd93f54cb3015076628ab11b09a1f6768b233d8c1454", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 95300000000, - "fee": 0, - "recipientId": "AFwj9n5ARhWX6oWNbTEeR9bbX3o7xBikXD", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220483099ced6fd44669d578eab70d4630660597f0df1a0fb734a62c72cb2581cf202206636e3f8066fc4857d558eeb5a3502b3aa2dc25b2f51f3d44aba967650479988", - "id": "a3273ca38ac6a49f276e658b11155fcd151f5745ce220bbf63373cc8971d784b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 95300000000, - "fee": 0, - "recipientId": "AU54w4okTRtFbFa19MC79VBL9B3QW45trR", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022055aefacb9cb4500f4573d6ab8e50e724970b2640b8a01cf071c4f0bd7076ca6a0220474772c7c8f44a38f98a1fd4be1cdd65120bbd3c2404c91f21fe5331f2f429c1", - "id": "de221e24f05801bb0c5681342127472e05e716852392d8d60733811779a0a142", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 95300000000, - "fee": 0, - "recipientId": "Adap1889XDiKC1bMLuLrSKzsabXmTuBXjR", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220083115c84effaa090c3b79b7e7036a9bf2d9dc8e5818aad77446445f17ac8e41022016a2dc3addd3d82605c5b20e27f89019dba0af6bb91cbb609f4f3be89f463b4b", - "id": "97aaec895cc684d562baab61c97def9a651cddd63b1b8fdea8ca846a86eb595e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 95559787156, - "fee": 0, - "recipientId": "AayAdq2K47PQiS7jMknrMwmQZujeEp5Fic", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200993e9038199932d4f9ea36724f89ac970875ce335ec578abd5792c24c4cce170220048ac8441077ce7e8eebb2fb0d22d4216f0ada9a0f8a3bff22e713f2d20b2875", - "id": "28103dfb6b5efe7dd7f5f880df2b3b910a96c4ba733c7bec80040aaf73b9a754", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 95612193593, - "fee": 0, - "recipientId": "AWgoLa9CgxHtUPAHqFvMpxvvFFK2iyBxUK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b71039017534404cadacb04535b1da38415411d5a2f6f5b511131c6a7d48843e02203504cabf89ca95390944620ed9398261da2a0a825e38d26ed5ce0c94bf58469c", - "id": "2a4c8eb73ba5fda2120f2ab91c7806ac9b603ca1a2b2d8301d676fda113ac29c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 97000000000, - "fee": 0, - "recipientId": "AbD1qLeYWasvZH4PX4j2h9bXFXJxAdhAuy", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203d888caf166966ca8a3559a40b6e8a7ac840048ef9469dc143bb1f9be26acbf302201d256b315dcbca537eaddc86c56850354726e67148088e38ea6763c8f774e2eb", - "id": "0725b2165c79199d1cb3087ce9f6989d0781c38bb4fd3bb054e9f42cf577532d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 97000000000, - "fee": 0, - "recipientId": "AWffb5DY3ceB9DcHknBsLSmZ7bQCX9BgZa", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200a3142e959218f0b3b7daf341222c3d2ceb5be21817e5c97bac96f964823faa70220086698050a3d8030bb6a1f01d4000297561b0a6204d58f8e4795b58a6b1448bc", - "id": "50f6375cdb90d9bf764f1544c5c85daaac335148e7c5b9706fad6564711eb832", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 97000000000, - "fee": 0, - "recipientId": "AJyxXb67KmdzRibbRSp55tC1NHcSXzNQmn", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201bc4e3f5e8bae4e7bd81da4e8d4d3e8a1f9c607c36e39e442235b133e3c350dc02200cdab848881bcfd15adf3f38afba7582753e9824cded8a96c35de855ed394c6d", - "id": "41cff0cb50f6902d42cc455467415829d66bc857002b904b63b959fdcf028215", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 97000000000, - "fee": 0, - "recipientId": "AYo5WNLUCNSRKkvxxMs5hHCNxme7xQ12ZK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205958c081b5336400e4a7a834a33cb8b5b05b81cfd7fd435b6f2c2109850db75d02205ac3c51a116db91e925e26da94efb2143e3e457669ebed1bd7464c9b1306099b", - "id": "ec651028a9b2b6924dd11472761295fad83f3738d2b1ca5867b32a867e17d946", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 97000000000, - "fee": 0, - "recipientId": "AZFKF8mKcoFVz6nG2zvbFxQXfumsXK5a2J", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ffabd89a120c2ed7c03c125c1da112043f5dd98cc99f205ae87488f422278b87022054bc18f5d6c732e8a2132086bb9961d9b3313bbfde4f428cca2f61db934275cf", - "id": "6dc4ec6abe9f5b39e3624841432e9c9af338edc2d4214468029d204d15bd50af", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 97000000000, - "fee": 0, - "recipientId": "Acye9MMv7bbhsBoN1T7TuiCKwPFprnoJgk", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f2dcee2429c1d6ed797c1d8825890474a4770f5f18774575308946bafb3b4c2502201ad6bd91169df28bb2fa6c827e8cecf0696d87136de193bcacae2068d0af83e4", - "id": "ddd07d35e2e0d2b1eb9d1fe7297051c22da9762283b767b6f8377d12959328fa", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 97000000000, - "fee": 0, - "recipientId": "ARWYVBZxnuxnp8JQp96fSZHZEw3Ea2Zd3X", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022007e6b2cbc4a6bf18e2b310325e1d7a56e0d455cd9afc6b7bffe419a9e70b710b02205faf2e91e740f331c04ca133da2e6408fa7f3ae4e5328b8ed6befabc8f7adef5", - "id": "52d56ac56c6e2363f94d1a953a908ba3d841bd193c4af65799f652044c66bde2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 97000000000, - "fee": 0, - "recipientId": "AdPV4RprkYHnm1YajzZYcQ7QjVLwyP9HrS", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203827a7141256877ba780e3b36875bb9af64f7f4fb3d9bbd4e9705d25dada25a7022027a1c9324c162cdc510aa1130099435d7d5569737249c3458f496577482a297b", - "id": "7cd4765b692240de26ec4341109869564754a541571ac191a1ecc960220cdc6f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 97000000000, - "fee": 0, - "recipientId": "AGNE5beBRiKtzAGxCGHPUEzSTbYNmB4x65", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220639fd4f4f25e365b6cdb6ba4cd631a93cd826376a07ad941aff9c83eacb438b3022060a52e0cfa6f7b4b0048d30deff592b185a7132439031c9f84aa5eab945413e6", - "id": "aff7235b112fbaeb9bd31ed8ddd827a95d735d74cfdbec373727031a6e752d07", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 97297237832, - "fee": 0, - "recipientId": "AWxt9gGVbxQ7cX7o9DBniXLA3FNnAzo5Yc", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100dda67cc7adf9bf59523c49e3862a569502c02b69d4183681ba73fca12720e14f02202744c8bed417d5bb28499eb7bd3d353041c32d86247d9152090925395642f2eb", - "id": "08977af886121fdadc51a53156c4d920998d236560d5ba5518eae27b0cb75460", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 97764047435, - "fee": 0, - "recipientId": "AXduXps2TeHn7AvTiJW6m9qqNCtrPAXD3H", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022033d10a0876ec4a4dc66e809ab4a852ad48cc688c68891ca5934b78550420e7ae02201798c11a5dcd1c21f873f0bb299a86d363895ea0e6d4e7a1137327498fa69256", - "id": "9d74cfa57b58e0470fdb88a9eb944096e1c749a4196820575b99e948c125caca", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 98045028617, - "fee": 0, - "recipientId": "AWYjUa8Y5UdaZKtit3Va899xqZDdXxsRaS", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c60266fade798d0a234c8e8d8fbdfe5f73fb015a29f2497acf95e78f1fb5e192022034c1a2debfaaf6068d46e70e42eb606082438aa53eeb75ae1d84f84b6cde0353", - "id": "828319edbb770b68aa5aa91afd76c02f76308f17f4a1c36523fb2817d1bc1e28", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 98100000000, - "fee": 0, - "recipientId": "AUzivjaUXHG8ivXWuj6evpdL2TjszQmVWs", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022008f1b85ead0a3a3328f43200c0c532a2a539715fbf4856e084dea1e7498a096402201e0daee01c06927dde071f5f5cc7e6fe1eeab7291cf4f7a0f330833c535d2deb", - "id": "fa1085f0ac80d410ef9530aba0af26ae0d652677f5e90195bf2184be93dfdf71", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 99262036541, - "fee": 0, - "recipientId": "ANGen8SGYqiBR8TFgtawL57qxbNZRN1hrt", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207557798214dc6bd35d8dc197714dc93287dbd45edd7ed832991635e5f20cf9a202207ca660d57db42578e31b0622648439ddf0219a862579b1c933d5987b76ce53c5", - "id": "f46f63e2877adbb32ffac6af90c5cbc79dc19ca5592a0579d395739d249a22a1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 100000000000, - "fee": 0, - "recipientId": "APSNQNbPQabUiMqxLxjhEyTkeh27nWQDu8", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009b295629afb30454c67ebce29203ec8b3f684f194fddb167bc4ef9d3739a300902202da7e99a48430663805db9cef9cc32915ad694ee75b5573a5be7d7d7d688afa4", - "id": "423abfb5a05784af1225894bc22e224dac42d421e90bb4227c945c35ccb9aa54", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 100000000000, - "fee": 0, - "recipientId": "Acj8SdBVCKnaCUFAydJ2Ggk6E7cfiRP1kJ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009d471ec487447ee43e43ecca799d7d1eab2528d75a1f98a35d9f1c777ebbfcf5022020d40ea9d1d3903bf5ee4e2f67f97b7d62fdfeba622ca8e72e67622c9dbdf8ee", - "id": "0977be517acbf17a25ebbe3c2568a91667b5bbc05c6deea85d61b2f1154440b9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 100000000000, - "fee": 0, - "recipientId": "ATz8duPjUnEL5oXZeD59rLCmtvHEcbui6i", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200de765be6c4d07836d73dc78eafed1a80fd2e91e3217e7bd104b6b084b09d78802207b244d85ea74fb2248bbd4c265a9e88a5b6a55ac6126f1545f839d2c41204f95", - "id": "106903e0f03337c08dc2d9af7c17263b53376474f56134b76357113a4e92b3e9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 100000000000, - "fee": 0, - "recipientId": "AUxTza9YeaMXKqgU2FXp6mr6kGyRNFACRF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220697444d31def970f5a73bafad9d3c0865cfa82a8f200d95913a6953314b7b49102207f70b0f71f2177add874eafa7ddbac1c3f5162f1f410320b06cd9f312fa5cddb", - "id": "e2b78d5f015620390fc6dc0bb39dfe1473461281c1f2de2dc47668959cb705fe", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 100000000000, - "fee": 0, - "recipientId": "AdbbGesHxgSbb5S9BFVvvmgrLbz6vMwtti", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ff6e1f7efbd6cf34bd7e27dde486d29e6429d28f0407a2cb86b9d3b2430cf22802200f64f32c92f87a8591e674426c34e43534c3afae30c62f08b6492f53554e99f3", - "id": "900cc87a9f10d54378456a777b72168fa9a8e44d256d8c46c362a1e068b8a3ec", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 100000000000, - "fee": 0, - "recipientId": "ARRdKZqLoEz3BqkspqPFnjDMXUWHiK7rUf", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008ac2f93dca4a6e81886d0cd48cd8d05fe612364e9382f45eadce42e3051cb0da0220349b5ef16d0dd502d887fbd2caa4d666cf0080f55a6c8bb92b99819e15cb148b", - "id": "48bdcc189c2fe7e1796fe78cb49f18b99187feae93e80d3e014d30101c067f84", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 100000000000, - "fee": 0, - "recipientId": "AM14wbgpGVFcR6EeyFrZ98iBSGQdKGwoNR", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f56b28811f377035b6f5ef6597f622a8f45d0ab7ba46d9cf8b73367dc2ba72c4022021bc53a68673a43fbc49c98b19708527f61269f16bfa15571a74b468dde373e2", - "id": "1c0411c1f4e0cccf85034f50469939ef994740903f65ec5c51d3ea04a0d776d8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 100000000000, - "fee": 0, - "recipientId": "APwycJtjaT8LaXEwN6DmyanzBWMGLCDffz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022030e5363191d9681297a0b85fda62a95ed499f3b6ec18f7dfe115371456367bb5022018807046a5e802ed222f7b2f87e583942782a7a3f3241592f9d7bed5d5d88784", - "id": "f0825d84e64b56bf8f388530cd18cdd4909be54d364ff9703db24cdacece080b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 100000000000, - "fee": 0, - "recipientId": "AFwTR2Q1TyWgpYvnUD5AKZ3KtEhTZnjSen", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220767d49d3c2497cac443b78c806287b68deffea0b7bbbeb171335997b40a10d7c02201a7e30b1d7ac9115d98589b3b54c16e19e7466b8ccc4394b488cb2637315af0e", - "id": "0dd5220da9ca8ff958bbc8a4040a0502d8ee757ddde806625c00f4a7cdcdddda", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 100000000000, - "fee": 0, - "recipientId": "AWWbW4iYXRvXzXAG4DVNw13kxGyZXkXJZe", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e00353541efedb21d3bd42e33963056ec65f0a79880f8a3a5f991103d8f64b3d02202f324a1de8c96fa2fbc41d9c3cd544e3e25bd84de68013c38696115a497a12cb", - "id": "3fe581b3aa87b6cffddcb98d27f62ef83138e2e78fcf3feea8a44db4575f89d1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 100000000000, - "fee": 0, - "recipientId": "AZcPTCo5A8M36wCFvDvPz3H2cBBJo9p8iq", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100cc27cbdedfcb771e9d222bb6cb13a71d7aec8e36fd433ea04942633c5d5e84a20220564dacdf36c5d0abaacfb62c445c8fd1d046a794339f38a05b8f40ab1d338d2c", - "id": "7682c2fa3c74fc40d917290a665ca4412fe74b737cd464c908850ee631af679e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 100000000000, - "fee": 0, - "recipientId": "ARoVFy8w9CsSBazrXnigzRZVrrsVhUzfLs", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022072790cfc15458125cf1fe54e85d58955797d3f4409f99fe74d3405742fa72bda022013075f4dd0ec2054b42eabc9d41d4cbec07d885bf1650f75ef3936826dc2d1b4", - "id": "f20634bad0858d0e9b1fae15b9608e8b6c0eb8715adba0977e0f17cc20dbee99", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 100000000000, - "fee": 0, - "recipientId": "AaerL99dZfwZL7yghV9k8Ri5Hhpi9YPsYp", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e67003e1c964db17bf2bbb00e6a4c60ca12e46753d36785b13041a136bef4ebf02207846bf567a2bd72bee3289a321e58da645d3a64dd9030d0f52028de50efac7b8", - "id": "8aae4664984ac30d28b171541c9a41dccabe558089b073d8236171c40456c4a0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 100000000000, - "fee": 0, - "recipientId": "AYA1abqExAqcWxP4qHaMNN6MdwSVAzz23e", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205e46b3935995f1edd2797f8a4ecd5181cf16893d4f955747824e0cbd7effae40022012f11701804587a5c2d9bdd8295fbf67524bce5a26ac6758bb7d43ba6975ecd0", - "id": "fb4d5ab6087839d6bea3c4b93d91a5654cb556023c0c2648e3ddc47268b29447", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 100023851656, - "fee": 0, - "recipientId": "AG825v123YvZQuL4vbe7wNxvdyTewt9gGm", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009d37379a461696e7b42faf48419ac770ead1768095ff38997eee98fc01bccc3802203272ec96815df48dc692ff686952e04053d60f14c0cddd821f0bfca775034b27", - "id": "aca358d024faa5f4fa68b7943ccace6bbc5a9ba2bfcc614445b7a1b4e96be417", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 100749519793, - "fee": 0, - "recipientId": "AStWDeuQ3NTP619nCiyxbYbnyHTnR6cxb3", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022046f3e87a0851d6feaae6224127051c783d08efc892aa52118f89dab6f11f02310220030a37ce70dcc5b4554397934d67ea888f49c7ad825a337a5540f8fae99cca44", - "id": "b92dcea5e20cb9348374898f686f4d2e2b6ecfaceefd6a0ca637bc61c6a2bf46", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 100933159396, - "fee": 0, - "recipientId": "ANV8jYBCQdPscN31ES2NDFQ5Xe87zUrFzX", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022000ac1e8b3e471505f8476f98ea31920128876a804d22ee446af4ddf210442cfd02203032930c4d6d6646d3d4140ff89c48b4e58374770f6be9169d04c1099a39d216", - "id": "c57cb34e723d065e12f74b33c2ba5bcde69763ef191505ba9cb38af0a769360c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 101680182605, - "fee": 0, - "recipientId": "AUQroQ4if3dKyFwgv6ESDM8MHyJDe8MVLP", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022025f1efed2ab491fe87764de2cca46aeebe3e31e558210001798d86c831dd69fc022059dd3257cbb318ab88d7f21baa2cf56a975582d578a3c67cc188ce46793a2d2b", - "id": "f1d975e6feb911d060c7ee86cebf9f56ea0d3001d8d1e9813d24c0fd1791aeb4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 101830636768, - "fee": 0, - "recipientId": "AR58rt33ihFKG56DXMVdKoidzNm4C8kCnF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a8e1e2102f75026fdf1cdf3bd8a737a673315d48ccf8b28730235ac9f78dd2ed02205d5a50aefab9c7817d6769a64c83b19b96df69769a735a3051c9888eb4844ce7", - "id": "84ada517a25f66b23c3127830b7d77948fab1c1fd2e643f4166384c1a6af9bfe", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 102744627447, - "fee": 0, - "recipientId": "AY7JH5b3GdHv1ZcbFXW3c19ovA5k3jYtDc", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100dc91d5af8df4ce928414162c63d64452bc1a05ecf20fca159cc374deab7401ea0220749733c55f0532b454f3d401d41bc3bb79fec7fef8b439f54c34c17fd5865a1d", - "id": "edbb728bb5f8ccf9384ccd41be5689507179d05d60f72188c419d5163d76b6cd", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 103054877463, - "fee": 0, - "recipientId": "AHRjCLd18MVx4MevXGsygteD41pjjEmae5", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220223d32f9daa36a1d0c96c35d6825922ac18fd23cf9e69f640f707c27a49f9a4802206e4132f40a65ceabff49666671a7f8d1d22848e06ff91c31a42cb6cc38f0d9b9", - "id": "63a23da52548b2b0f932340589409d453f3d46656f78dea4d8688d926013f84d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 103054877463, - "fee": 0, - "recipientId": "AYwziM1FFZdiYJFpLjSDBQUnJD5gQG3T6U", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ef3d526e72b7021dfdaf9b7706c88ad8bd985f453754a116254d13b888e127140220259eebf0c8aa21adee9b573ceb4bc1b4597c72cf6a18cf1617cf645eb169dd4d", - "id": "915baba7089f762e6ff2398ad391de54f7285f0b155a277d88e5a9bd049139b8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 103054877463, - "fee": 0, - "recipientId": "AFygDabDnSBm2M1iDWofGNHPa6ZeCEeqj4", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009fb6a8eeca7df3b18fa9d1db1a1a4c1c63fb36dc5f71396c9f6d21070e7fe8d90220426ff00438c59643cf1ccd01575832f0a375d1217c43cd816538f3968c643fb1", - "id": "886d32c7624d952ff3db788984073b74e5dc6bc027ef926d1139b0a512dc3f2b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 103054877463, - "fee": 0, - "recipientId": "AMimZDyN8vdDp96CHVLtWkhtvmERvTmnnT", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fd942aca86527ff6011d230a0ad9626468d361b91f44ad22e32c0774627b0cf90220410fe8a28dec6354408b5da8bb3b77f5f5a4e2b491dabf5177876a8b29928c97", - "id": "b7f7c997bc93d99f92b458affba8081b15e2349f35176e2100ad38bd859bcdf0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 103054877463, - "fee": 0, - "recipientId": "APdPdgkVCNJqQz1xP4qqzRY6K2tjybdH6V", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bdb4e44659fffbe283616db6d45315d3abd1e4e559bbf323629b9efc4ed81b080220069b6e53fd81cea49c8136ba9ae23e00258ab6a2d30130d1b31784a21b669a96", - "id": "ffaa043601b679834ce66deff3b7ff00046acd8bb3f573ac6610165ef087879a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 103900000000, - "fee": 0, - "recipientId": "AZiQBaR5TwSH34NXrdrvErMX3aiYZWFxo8", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c87c4c23d0a4432c99df14afd1c88bdda73f3a156ad89a247ea662080b7214dd022055506cba670851b036bb39e638986879199b28312fdf356a685bdd1b17003708", - "id": "146b10f76eab1b6783af1c108c35df9bafa3c6ba25b72ac6597d9fc57c7cb215", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 103900000000, - "fee": 0, - "recipientId": "AZo1U1VRipZ4aWx8LYfEXmyEBcFTs2St36", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022040317a8ac4fe1308c10fdb2ccf695db0d4374cc57ce5b012e7d37766a7619ec70220014d508f6dda12b5b9ff095e6d53fa77bdaeeb04713de273725d09b55ca22251", - "id": "549b4107541aaa18058574d381323196285df6645476b9dd4e4f791ea4c49106", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 103900000000, - "fee": 0, - "recipientId": "Af4CtTPXJ4Wkgrsw1zsvmCoEUS1AksAyEk", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203e6c4eb1fa7b5311f04bc7cdc38445b9b737592b9eaf0df143998cf5ce66eeec02204ee48dfa39a93b9afb978afdb4b2de537dbbeaf1c82e31059d344be867378cbd", - "id": "3fe8bb50ef8e7f6346f7447e934e9cf8e51d981ab151698ac258cdf03c72b8ac", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 103900000000, - "fee": 0, - "recipientId": "AV1MipTPz9KkjJKqSfagNiQkpCDACG6A45", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009c3986801d041cc233bbd394cac166ddcb2c55482aca48a85a59c130c55cd8d50220164eb74f9ef23f5a6307756fe57ff385258a9fbdeecb5525802f79cf5dee8956", - "id": "54fc495250ae8a95f231553ba952f8ff502aba015ad586f03f68bd6fceaa7606", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 103900000000, - "fee": 0, - "recipientId": "AdFF3NRiwLpVuypouVth7HKk5Ca4xejtmq", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100efce8d12b523214f79402697597729647211ba94ef27ae2743af9d6ff0985da602202b75b8e0a0a655a8863b39940726b51b31566f98ddb51b2b17735a7970596edf", - "id": "436256751f2e97b12d0a99f725a71aaa4b4ace4644b4087298592e0a5cafda7d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 104570390367, - "fee": 0, - "recipientId": "AXowRmmMSAB7GFCy8XPpNLu7E23B7DmkPa", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e0b952d2bfc38186a4ca9ca51607f44ef64a3a0e58836171f1668f0f47fc10b5022071d9e89774682c5cb84897d5c406aab779527e759ddfac473c900add865e420c", - "id": "ef7a402d8c58133af24b24b5ec33821e0b443e680144a20b9ae83ac85ea2d16b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 104811009865, - "fee": 0, - "recipientId": "AJNitjA6dbbiz5vDaQkaKSXaNiktVFmopp", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100aef19847d686fa0e802b7eccdf3e302fd9bc3aacc87f6ed76ee2ef0d5abd0c570220248a00f81756031ad2b87a6bb367e7e6cedc11b4412ee3998db66972edd35166", - "id": "1d956208a3979f3224a4dd347109102afae6463b060bd94957440442904988fd", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 105000000000, - "fee": 0, - "recipientId": "ALrkZgaD6BBZCLx1FaUvcFRL9BkTsjXa6P", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100eb10d7c5fd4b9c27aae44cccefec7315600dbabf3cbb180fc66e996ea2856783022037c60b02b33ac06604cc39077d11372ade6e1a66aa89f99c43b6fd0f0fcd8ee3", - "id": "1d10aadc7d67af4ea389a2f0163fc174f261d71ca6c80c2b326b674858948ed2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 105000000000, - "fee": 0, - "recipientId": "AZnonaWAZjaGFbku3DLoeGwgg8PCy41sMj", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210091235be3645f78fa9d77d45882767b7280e779f3532defcdf0b71eeebc73bf63022050c9631aedf3c42231ed1fb9f385763c370ff1c9faa6c14ce07d14500fab3d0f", - "id": "a65fe81748277af637a844a6a21b8cd57f4d1dcbe6aa58a553a94cfcf0ee2650", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 105000000000, - "fee": 0, - "recipientId": "AbCoHPSKn16NSKL58h81seENSohU6xAT9w", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204ee2fa79681c3c86b4107e239596087b33a0fb6108e2bfe286598618945c859902203a32f45dd2538f1e49eea075fbda05db26f5c9cfb2b3d1f4aa85117bf4a92149", - "id": "306de8162df8007e47f95095c1386c4f838d32bd6072ea1e740e27720649058d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 105000000000, - "fee": 0, - "recipientId": "AMng2ZJC3Gh2ykVUAGUeKB2q16WnaqK9hF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204d0818688736ad3bb25e21e4af2716b2038a3691e2cc7f27bda2df761df4c1c802201c2fd04143b52f2b5e5bc1d3f8c17cb038b90eebd5282ac3877012a874f8fa95", - "id": "1a64cc2367fc2d05a9fc862cbb99be77166117d5ba2ec25907126561b2d9e1ed", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 105381567713, - "fee": 0, - "recipientId": "ANzYwcwGfMg15SgWMkGuYPxjePuXLtvuEG", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220750181471cd14e07434ff8f59022127b7ebb36b643aaa1851142144fd6d0bfaf0220268280752dd52b66eda35bb6384e6148135461ae865154b175b36162860d71d6", - "id": "e4f2db2efffd43d8a99a2df742cc93ba40d3e97cc4e9cb421f9ad3c85a1d5e19", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 105714987355, - "fee": 0, - "recipientId": "AcJFXLL7uS4i8x6Jg2hZQftgirzmFt2X1t", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200c0d4ba6f603d8dc3b535fb8b442fd1c92c2d7deca82c1aff622981874563b0b022043f3c8a59e042b847b1359f1a28a34b44fccaa042c41947a1565ec2d7e719167", - "id": "a85f33a959089b825b8453138de87a63137c1fc61ab1038f3e8202d761133c31", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 106221504166, - "fee": 0, - "recipientId": "APk1d1oci5TiANq2XcdQjy8C8iLwmtTf1G", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206afbd9bc04c90409a31228c58e89f493ce384d7dd97a0b97412a4725d7358ccc022067b1f28540632ad989bd7458fdcd23e5897615e9d9017289850277034114888a", - "id": "b9cf7363f5730e0b1ac4ce9114ef5eee22797016bb718354940fcb53cab874b2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 107239514002, - "fee": 0, - "recipientId": "AGLvTbXCtBAdCD7bvAi8PBSSvRexEv1upC", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200ddf53eb746d313d24df80c2edb7a3d01b533a9691d05d4c863afe390ee5a82402204de5576afd6f9a54ad7f106cdc71691acbe16e206914e9da123709f9ab78ef85", - "id": "84b3f5981b62693819aaed660e3eed3e5f13042449234ab73a5870e760a45c7d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 107692346948, - "fee": 0, - "recipientId": "AR8jXLCoRWnnkaD78YxajVcFsTv7eVutRb", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022077094180476f993bcc0da365d9f9667c8724c5d07402689ece5f870af7e63b2f02200faa17f80991d65bc3e8a2593eb2ccbd7b12fda29dc3fb856ae0d222bd56113d", - "id": "dd832724781a028d0427b13d14813c82d8e949fa488e9885c872f1d7e6176642", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 109100000000, - "fee": 0, - "recipientId": "Ae8ZPCEgJEUMkR8oD1Pp9eDocsGE2MRtiZ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022040ea35b1906200719f7b79139f0bc3dcfd2d1f34b19c3e75a4fdd1e3ca84b60b022008522e9eb7dde5ec65fecddb318added37afed341c66d4c3f938cdb539bab0c3", - "id": "ae1127f0c65bc53762bb7b5e79c3dd2f0da85fb201afc5bb1577b1fcf371c31c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 109100000000, - "fee": 0, - "recipientId": "ASoz5DXAEBaQy8H5KrYWH6bzYkJ3zHVSsx", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b4b63ea88288f994776b80c78ecb41793cf70fbf72e3038acf73099c5677be9d02200358cad66ae2d98a08c43be0c73fe88a6ee869be49d0012e394db3d14659c43c", - "id": "f2d28d17f885481487547831f316bcf701a1be0d2263403740dc0516293384c3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 109100000000, - "fee": 0, - "recipientId": "AaPUG2PrNDy6UVUBkSwPuKvKh2DSZdnDqe", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008d7f47c01e28bec5afbf49a5cbfd68f4a911a4e840b624e13abfa4209c2e67c102201fcb385bc4fd9b55c687e5a253b2f181a658b7fc462d8830e63deee0c82967d7", - "id": "0974483b28113dc2ba87c4e8caa564f71b5a4dd10167252e66124b02fbddd7d9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 109100000000, - "fee": 0, - "recipientId": "Aek4aTvvdA3hUVUjhgkDtPQF5E2iBuAVP5", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a05c9fc68f17c57f5adfc88c3bef13e616149a53b65a6a97c95d8f750a3251ad02203cd2aeed88591b244df1c05342d79fc7a5c0da989d5b024bb4c72582b28333ea", - "id": "cb54ba7a6a747c5d20e66648e3a7d12c6e5193d0ec1daba2dbae6b8bda726165", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 110800000000, - "fee": 0, - "recipientId": "AL32Ure9XiVh6emxgHdLQPRbLbr5otNqDc", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b8d1cd4c228ea834d371edd3ee9c59053d831c124edc7588cb358aaf0639f93102200cbfb5d6f0cb5498709897a24af743cc1a80a66a948d411c9d71ef18394872ee", - "id": "4683c51754c198ccaefd949f1becfa8dc36383bdf4f43b9a5ac3df0e848cbb01", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 111541749725, - "fee": 0, - "recipientId": "Af3iDQ3qYG53LHVXgGbQFXkffa2pyjdEFj", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c57f7ddf68a0d08feb91bc9d67e9b246bdff6d8fc3dc07e3524cde453ad60bb9022007a08e49417b03c201aae30e9c6b12360c1e5e92b7ea5e72cc3b257d48578f54", - "id": "df3260c10be2b53c385cbe025e84be776d6f9cc5bbced87a110ba3fee4ada362", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 112600000000, - "fee": 0, - "recipientId": "AU1k68nmfcpAkTkday2TZFoKuttD6eh7JL", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3043022004abba6942eb051d80c8faea823133159c8207251f82bae328b0227b77f92097021f1580e0ff6214cbec6d97059469e5f498b39c25873c889870d0450b1b892be5", - "id": "c4b480f9254eb4290b7acc61e2e85bd2131785e482f6e617b8c3a6084773760b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 112600000000, - "fee": 0, - "recipientId": "AS3SgCu4BQ4VxfdJyu4HKNVHZGTQBP6bTB", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bd132234f55b6c486d34b3fb5b1b52558d45938ffbe0a02e2ee06ba9fe7673e802200f61ad1ae7b20468c496c0526bf726546eae7907387f72f85245e0c7fd9888a5", - "id": "66b7e7ae47d5663ceef394c8cec8ddd1851bb4034c5ad133c1b4f6d2b18b43a1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 112600000000, - "fee": 0, - "recipientId": "AWepJMLX9wD9nm8RqagQasNfypVX8n2wLF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201dbf3a7b09b8a4e71db49504e3cd5bf8864a877218bffab4dbce5a50b199cd1e0220452ebc9b95206fea3629f11abc6a40a6be3c0738bf496ab2215c7033bba185a3", - "id": "a94b2af091d991840007d2644ece123f0f42d18276237f163935d2df2e075685", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 112600000000, - "fee": 0, - "recipientId": "AXBWn3Kf2yEy16mYKrwnTQsRFL3geunAxh", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220663a6cd1bef053f3376b19778d7256d08a34a8d8177e9fc36b3460cacea75c4602205b50dbdf88972c8868d15934c2a466f7cc6b82234002cd08391ffd13d173107b", - "id": "068b774d4990385eca9fb6e1ac6726cf6eaf99fd3687b35271fe407b33e9a784", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 112600000000, - "fee": 0, - "recipientId": "AYtpgFgBb7FJL88VBShq1GtBLoGnYBzLe2", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201d96f2acded9f3d609618ce34f387c2bc46402e7e14c7bca2592aae1e00562800220593d7ee4b3f97caff848c38b497140312855d9df2f007b8432c69b43182462e8", - "id": "df619578c2c85a4e2a3f9f41c7afd5992439c5a60f8d17eb3ec7a9ce8b23c96a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 114624967068, - "fee": 0, - "recipientId": "APR5muHZocUvsHqfXt3Y3QDuYLHHgiRnov", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fa7873fc955e0c2de2a6591ecf38fd0252b4b25f83c92b6eca220dca2c593e7b022007b86133cb60ece69909502ae21d7a5a0fffba04ae88522ffe15bebeba2882b0", - "id": "877ce4b295db21844c7dfdbaaa08571c2a376400d60cdabc9af1eac7fa2b591e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 115000000000, - "fee": 0, - "recipientId": "AQVCqqgTRUDR5NMaBBDNxgdpwxkZbtqGxs", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202927f829fea3a19b90b2144407b9a268daa489ab146c08106be90c4273220b960220220f2de203a334b320f9852c527e55b8f595d3175e6b78c2650c27487bcde63d", - "id": "0674ef83667bbf5c4dddc21c122777e76af7abb6ff73337b7c1f87231573e3ea", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 116000000000, - "fee": 0, - "recipientId": "AWEHZFuvK2sucehMNho7bFgs5Kct9MX9cM", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022023e9e21d3126edf7419081dc3d8ca781cf08b4f7a6250536859c4161d848799802207e4a553cc1b629c4727b4c3fef627e8fb592e839c80997dbfe631a08313f84b9", - "id": "aaacbb16a6a80d35c49796dd24c5ec944fa8e78323f7df908fee895a651b59a1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 116329005231, - "fee": 0, - "recipientId": "AMuZTwvJTd24XdckcZAi7Qiy3pMoitZLHV", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220523323f28caa94c93362d327129e8d3c4088f9c577a77e9c905464c5ebd04b1b0220705fe7ff0d17f52e3370d10be916af09ef1883614c76eec9899adca018888445", - "id": "7301a48380a2745cfefa1cdf1a2ecc9edbf70e619ef97440092bf80e8c392440", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 116897017953, - "fee": 0, - "recipientId": "AdFmnvX2noJxbAqXZvKKCRV8xewsq2L8mU", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a61b9e09531ab70652d5a188994c291247b3be964960448ba1fd0d21668d7f2302204d856caa8fa3ff06e8b91c69f5dcabc7c60e57d8b342205ce03233682b109c06", - "id": "55242c764f2a4cd791545e8b71cf09b205b99663760874500a814a942070939e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 118387830724, - "fee": 0, - "recipientId": "AGsD1iKAnYqa7q41f987DzMS9kVmuvMB4p", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b982e36000e67d68c16636c2f869d138f69612e2a2452584831d807bfd7e2b69022061eebe290b68359322fda287642ca41864a581bf666b74f998796b7abf418c54", - "id": "9c19c864bedeae508eadfb014a1716c8f6af01bffa05bde44a35e9400fb441ac", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 120000000000, - "fee": 0, - "recipientId": "AbToFt8MRXR3BmKcC6EpnEPWaNUMQzr1hz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d94cf00a4b6aef15f3243dcb3d68afbad70558b3928f096622eba887da94af4e02202d306ded93a5b9e21cfa7ed9db16dae8b78ce51586f4c6be5dd0ff69c379fdcc", - "id": "fa35c7b607c02653d4c3856476100bdb65b0af57df0864b0173da3fd370d1bf7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 120000000000, - "fee": 0, - "recipientId": "AHWWoXDCa9RSeB5d9BCS2krPh6TzxUa9b5", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100af2fbcda194d173772c91a90ecea2ba0ae32aa24dd7a119a5cec4b92dd348a93022073dace8e3c3fb62051bccda308c1e80650e39ddf6db74cfbb1ff554fe5dfe773", - "id": "d96cef527759049343a6ceb34e973a6e873cd89ffdc0750dc94a9b8b11602c38", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 120000000000, - "fee": 0, - "recipientId": "ATqYMZ7zmQy5f3DFrPKaMZjiPVpGAjCWae", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220176a3dafb595afac283608fecb966d35be5f4ea746ee54a61cc837732b368f3d02201df396e59e4947dac9c31c0f6e3596fa366b3d6a1b537a04a3d39bd5b2705911", - "id": "942882235103abaf544d21ea54b7cd466fb9f0eccf13db858af4cf254444cc0f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 120100000000, - "fee": 0, - "recipientId": "AeEasb3UFnht7FJiYqFi7csnAavnTwxUHG", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30430220608fd2db9a7ab9160e8071694506c793a63ef0b3a70970488358a661e80e11eb021f6ac85f5fb747484f5cfc189b8cb75e2ae8a013b9f9f2f37b96ccd1a5e21f01", - "id": "5b51d62552fa2d44563a09c0e6b1e30610ace3fb9721fa24499cc19d6252f8a3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 121200000000, - "fee": 0, - "recipientId": "AR979mGe91DMX9dUPsKvEHhru5hc2aMqn2", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d97b547f60feb499b52f9b613366eeaba66f8f2675d7e1faba52dee39e3142810220617a00281f0c066eba13a80da45ace50b6d94766a50d0b975563c2e1617cb1cf", - "id": "03676f15ae81acf829753f2b3ac30cee6d4749a508ae32cf104124b9a4f612c6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 121200000000, - "fee": 0, - "recipientId": "Ad6npQratXMq87v27PgmpKfK9aMvmWTjSs", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022065eeb3aeec85e090ec09e772ed5d036d0e7521f09db0310e75622c0bb8e96c87022061e5b3ed0e12e3077522abcc5d15cb07a2d261d6fec0e59622700ec4466c89ec", - "id": "1e2ea66e2f12b91bb85d384b483f8a81c349adab4dfd2d20fb1d26346e4ceed3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 121200000000, - "fee": 0, - "recipientId": "AMeyqtTonCYWt1mPJwd1K2FpnHcPjCVznH", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b0d13c6cfd44a8e186e659c23a2af522cec69a41b8e28ea6a6131bd12795216402200a0a212cda71ddcc26629b53d7f1d6e455b99e8fe4bc8f4e3ddc29839316a82d", - "id": "2f27ad44ec7b4c1b612fb27a23a4d77e9309461f72c5613c1ccf60db7601e833", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 121200000000, - "fee": 0, - "recipientId": "ALvhge1cucXrjdpnMozCDaWM3C6gm2Xm33", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022059d6b72bf48d119e8e18924861ccd754aee7e71d7d3010daeba835d2b33daf8e02203f0dfc75e84aba60c389eeaa7b8cb704c5768e58d0c3e066004ca01b2015120e", - "id": "b46a24fae79c4b7b18b368b357785eed4efa908b0170fc0c955365d9a8764b31", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 121200000000, - "fee": 0, - "recipientId": "Ac427bp1DynWsNGXCCYwWXseyUCYMWr2CA", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100df366d759e02b5345ca59b52de2bcab1906b78729c65ded10fe89c6e0daed4c602205821d77a90716e943ddf58c31392f902c7777e3dbe69bdec5198bb2796c17a4b", - "id": "f5c95f62953e9c219850a8ae44c468d3a61fabcaac19446d99aefdf72714f32e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 121200000000, - "fee": 0, - "recipientId": "ALHBQuNjrFx3i3PgCz1QdkWgAEwX8zQ7zy", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200c9fa69d5adfc7f6c1a6dda8dbcaed58135738eb7e20975bf457cf288a4412fc02207be43c5bbf745b851bec3b076291f3800c54388da527b24853363b0c8144f0da", - "id": "190f5a757ecd8332701afd35805f0ab1ade99cd1aa3a9169b0fb9f48148e6cae", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 121200000000, - "fee": 0, - "recipientId": "ASn5uzMgJxu2bu54HGiKE8D64ZpkhXrPcX", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bfc41e8af18b4505ef221990a17ae16c2be5e9ad554785523fbcbfd5393f006f022017978893e82ae7232da47b6ef37334bfa255c94cc19e9dcc8de51659b4bc47b9", - "id": "2e80037c72f52d9caa96bc27fa934e662be2497852c6162a50ea01b2487c5b4f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 121200000000, - "fee": 0, - "recipientId": "AYmpwBT59ZpMKJCUcsKxjCUBYfGb3i4r7P", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200b5a383750451bbfd892a9c444c41a029efe9fdddb40acf604851b6cd623ba320220129abddee1f38206fe9b688715471242f10ddfe99fff3717bbb0e17b5a43c071", - "id": "388d78941ad3978004c95a6507737c08e6b2840f1de9e054c215a1c800827c16", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 121200000000, - "fee": 0, - "recipientId": "APaWSMFzp9gNr2RRqwdGzMdKrf5rwa3dNV", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c69b404bda50b119d33763cdb23e3b69bdcc387668458dd33253955247763c5802204fc1cd9a805cca13f1c62bc706393a2f2e839a696afadcf528ca93177aaba237", - "id": "1b24b82d490655834aeacee9d6af2ec815bead657ff6405bd6cb29ab19faf737", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 121200000000, - "fee": 0, - "recipientId": "ANo6dapr6rCMDTKjwRzZgWUtQgbbQ1Nnz4", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fd106e25de0bd3b3c1e6cd3dd1f6b49968e482fb015f00556ed60a3853c2d576022050e02fdee6b72fa4c6027c17f8485e395391b9a342a6e212b064c0cc27a47aec", - "id": "96834d445ffb6224915b81236804f1a6f4e5231553b4019d5b9a9806afa632ed", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 121200000000, - "fee": 0, - "recipientId": "AWA6TybTgFiesGDtCasa68Jt15QP2Auvju", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203bc67263cf58ec7b90c524dd694b06184f705dc7a206b1e1a1a8fa1dd29cbaf1022049410d87332f9108ec487dcc2d4fcaf52ed36867b05e4b4f14deb94006e94802", - "id": "176a70f81ae2c02db9e422651bfb9fe7430639d86144b94a4f28585533403cca", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 121200000000, - "fee": 0, - "recipientId": "Ac97FP6Zt5mUcz8hRWRy8QgNRsPCxs7BaL", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202d1d32121b5e7ec0fcabcf04ddf8da303b4e3b5ffb55ab9ec037509f6d4df29902201526e223d63a19013b7e44fe44879ecaea07baf2c45ddf13c1234ed9c5163475", - "id": "7b50f54fb15fb3cb7c7c2e5b1ca1d241c37d3c10db990db87534ce366ea94d3f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 121200000000, - "fee": 0, - "recipientId": "AJEX11r4ECKF3r14KxNMwzLT29omPCtGHA", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022035208eb90354b15269c5b1a97197ef357799111600bbd313afacce965838eae10220036a346f41e67380824086cbda241e058dab979994c171be6b9f633ef4c0300a", - "id": "35242f8882db3e61b508761316879c32fcee9a5677bbbf23c76c731cd68b2cd8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 121200000000, - "fee": 0, - "recipientId": "AQ8PPGdBYLGPAiUjpo64YAWCyPzsUEEBvM", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022025cbb2d13fbc38c3de8cd8f134c41eccee79f133e78c83091888e4e1460f998402201abaf01c324594b4a3e2ebacf46c80ae6609ccb8dbc915304d0bb907a08a7869", - "id": "0889d189eb144fca050638e158ba32932733b39352998d2cc9307b4cfd5ce2fb", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 121200000000, - "fee": 0, - "recipientId": "AKhaX5DPoz59va7cMYhcUsBEGLw73S7524", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205c29cd985cc57d5dfb9cc47619030cc11860ade66201f73db301a411dcf8769802205329981e063b8f671cf6584bdf65f6d9ee12d304d4b167399d913e1e3fa3a8db", - "id": "2edd2baac328384231be9165095f0553378679aea26eba5b149e3a54303f5fae", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 121200000000, - "fee": 0, - "recipientId": "AN3u3wzJPUEbKkjRTW7JGnZwHj2U8ir1s6", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e2cb4fb8a510d122ff54873ae69f3ed0e05e5218f16caf3e10c719a46c1f493e02201c2a6640e6904f68a9ef5375bf4d3e0a568a558a088d68087071f09f34206d61", - "id": "85d4a553ed41bc54fd93c81ec5021a23088be6f50aba0055270e382acefd98cf", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 121200000000, - "fee": 0, - "recipientId": "AVL35iLjeDYJFrV3YK5kPHHijJcHbzGqv7", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200acc365ced6b1cbff5d6d586f373ca5f9a193a20de02ed784c2ed0969bd33a27022033ed50bc892f08d837f3e38ec8c3d4b2cff4ad2ba148712667870d7615047b80", - "id": "122ae3a92b89b6c5cb1234075b7e5415c0b284c4ccdb66a480cdc47a6f45591b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 121200000000, - "fee": 0, - "recipientId": "AYxAEgAvXoifkzMsN9P8nmDGaErUnGz6vE", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022079f8b805e20edaa4f0ee8e1a1136ced571a1a964c49f678ea4ac945c77aa40ad02205f03efae06c98fe37f8beb0e730a0f0b6cb5473c70fe0f5f03a33e71699b4816", - "id": "9d1bb9a20498867f18e4cd28d56b1b6d0330c78da178d3ec2a9ea776d657300f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 121482157847, - "fee": 0, - "recipientId": "AXdk1YaWraKLT1he2N3LW4aBaZLyAiyiMW", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022036f7275a99c91e73aceb5d904bdd1aff19007fe45aa3c1d926170e9883134730022074df6932914e893362a8f6473bf32e1eca016efe823a02bb9fa3d9ab7f64c6a0", - "id": "6676dc925e0257740d79d42f4963e30cf1451dfe23c282732f9b2a0917e7fda0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 122958047810, - "fee": 0, - "recipientId": "ALHwE12mF1DbBNZCTxcPRB6x1om2bRnQxG", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201484a2b3a9bf798e204b01eeed2d235e3dfb11f9710bcaa5c78eb984c5a4cb2102200cdb0f5f44d445cd495f20b75707927abb3115b84fa1c95e12879f36e5d962c2", - "id": "50e3e17e80a61446fb6a9dd016d3ecd76d22014721416802fbb8ec212f732255", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 123284431502, - "fee": 0, - "recipientId": "APMwQmexasWJwpAyLZoDFYWFSGUamtNK1J", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220310d6c899dba99be0221deed8077220becb9da79118276ec3cab9226988e9b72022048bb5ea38ced208e1cb6d065248a390ecafcfc6aefd19b8f439c515132768603", - "id": "817ab6d80dd709d55baf1d885b47d8f83dbfe0009f8777dff77500745797fa3a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 123492648017, - "fee": 0, - "recipientId": "Ac33gzmvjrrbo75WJ6WVuuMYchi2oSG2cZ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022011862523ab035e2600fc17663721256caeb4b54de512cbb7f22064bfeb6f306b0220586576a37ee87ce14575e8e440aae35c6b370bf8ebbb3d220337395e4da3a127", - "id": "fc0ba615203b330acb7946235f7f4bf01739cf6847e07be0d23d5218ac106d03", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 123626298069, - "fee": 0, - "recipientId": "AePe8p3H3wqiUUJ7N63aR4tSiSkxnA8Li9", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022023c78ea21b06e95bbe80641f27a39ded89ec17a2ee2c8f5be89135ac4098a464022058ee31b7830ed287622216e2c47c34c6fb1929b72917b3f95de4c3e1cab45083", - "id": "5776ccefa14dca3370b26abed21365f1ffe65228a6bf5b25631cd39c461d14ca", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 124700000000, - "fee": 0, - "recipientId": "ALL3T4hBL5Hy6N1ekevdHuK7c8SCoiKmSy", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100da7c3cc5cd62b428639c066eaf88fcf3fcd60b69b3a6988d1059868aced0a0b602204a2f5da3d7958c04d3edfd60bed8ce37a862ca10238c5175d49f8d994f4d0462", - "id": "b44e7fe2d2a0491576054ce4b6df112eb422691517c896ec9000e642d057eee6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 124700000000, - "fee": 0, - "recipientId": "AcM8Mm1xZLsaQ3C1UrpKBGETTWL42n74o3", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022051d19bcc61978325c50456ad68fcba62f75356d48611b6922b7b3386ecd1febe02204e16018952ec4bd29530eca80eb7d1485b279595524a69c77a199b7795bf52b5", - "id": "dd34a901ab4d46ed7f9e4b0d9e4ef54e36e412d1e0169c63fd5e6479c9f49eac", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 124700000000, - "fee": 0, - "recipientId": "AG9kM2jLaRKeTdRiDBnmHKFvEodwxkyns7", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ed04ff01b76d87858b06a9b4ac1fbbf452f237f3c89291ccc2dabf2ca2eae7650220307fbb2d586cf040baedd8a96b0745e7c5bff914ec9a36c6a9ecc1db28edb430", - "id": "f69027b2324e43c68a23e9a62a24689315571342b63651380d620e0d747cd561", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 124700000000, - "fee": 0, - "recipientId": "AbJe8Q4PeWXGd2hurMguwVe3BLaDHfWRcR", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206c292d5bc621a09520136d3763e2ea99994c92164f083317ac7d4d84ad67c9a7022064fce43058f8d19cdca3d633fcc34429086211c6947d4deedec92a96f5d72e7e", - "id": "d6b27e61b96f4a4e503454f052fbf313559f9317694aaf0e3b17f9c12a84ffcc", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 124700000000, - "fee": 0, - "recipientId": "AKm4VdRbpqQRxNUatCuHz6SaRGTatMGUsD", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100dec71fb87031a989bc6dc3f048ee308dbc97ec349a45861efae3459b04424b2d02207a5e5f4a089bd56368ce0cf6a83db247e9a8b215c0aed867dc83141ddd81894d", - "id": "f3cc88247afdfd0a8cab61e339840363c7353287cb76043acaa613f1ee5e8555", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 125174485431, - "fee": 0, - "recipientId": "AUYnvSaH7v8kU891WuBGDA8XraJnqfPft7", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210086f778878fc1586963bd8994419927a9a1f6c88f899c5f4f19840b2c42dc559c02207bde1d3bcc0f1faf3143b3845f6671a90792bb2e3ea1b041ac9d80a5daa02615", - "id": "2801d176b91ba63de86b0332a26346bc1e4c7a8a3a967473e18c09e559965121", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 129540204214, - "fee": 0, - "recipientId": "AbnX6Wyoz3d1LLBdY4pkYmRXvkv7qq57Nw", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200d2330b9edc3faf556ae66e1c689237bb630dea023ccaf3037dd706e7a9e34b702203ff844c9710095e75fab94dee0d6f15a2c233a4c05e37a593008ff36bffbbea4", - "id": "0a1e802ba4d8543ca30911f77fc551ef666486b36678ef2132ecbfee416331e9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 130000000000, - "fee": 0, - "recipientId": "AYxWmuZJwzYkjUarmquFoHjG64ityMAwTm", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a55652d4a247e1e536a05ac2233c5cedcbeccf6e8b8e701af2bdb07b42a72fee022058737e0837e904be3c969f81e9622890bf7593604e8ef6ee19aa592ecaed0088", - "id": "3efe48cfa53baaacd2e25059d104d656493d9fd13eb2e8acb508db55db7417dd", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 130000000000, - "fee": 0, - "recipientId": "AFyuiPD8r62cdnrrAb8PHdiom3ZgUZsFQb", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009f3ec0907177655f8d54eb4eba7f2c96a3e86f239a914401174b45319c482c99022051c830003a1111109f2f726fb888107ec30c5faca8f54a34a078c4831e62ca0b", - "id": "7a58e80d25d0d063bfa357729ce25b66b833107b74beb953899a4724e981d47b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 130884887397, - "fee": 0, - "recipientId": "AGDxH1FYXf3XrL6nJ9qhwmGeri3M8hg7dM", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201cbd29a31f62bb30c95fda9d78c8d860fcac3939f39fb7b5638257d429bbc69702206cec558ca827f47711b6b0685b37a571487840d013dae31e95dcaccc31c0d82d", - "id": "218113406ee93b29cc9cd8ebd40d51df6eb6b37dab860641087fb2990dda7d88", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 132401763390, - "fee": 0, - "recipientId": "AdFxLwoJ8JVWW3zpPWkvcpTjbVtawPhh1L", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202cd5cd7114a86e5ea04746d2a44b1daf598749885f03cb8b69dbcb95e8f7591e0220062c9951b5f1476594c34f7bf9a47bb637ea83e843e46bc070ef8f80fd20112b", - "id": "21589db4eafcfe26a65312b727f705ba20dd4403d8ee7fc09dfe4722005251a9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 133300000000, - "fee": 0, - "recipientId": "AVzze8R7jrAugNCcUDpS6gvtH3jLTBApfK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e575b4d782d64bfd7b3a5208a9b0b36baae254418f704ff71a519be9e33f4504022011766b87c099d21616072efcb5916ae72965886276837b08e8737ec00d20f691", - "id": "f0d6338a6ae19444416bd44a0a194c4dd6043950bd0488187c2ae522f0c9cec5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 133300000000, - "fee": 0, - "recipientId": "AHiw6LhXUDksaQkQ5YEys4bR3KQUpShuhK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220336d5cd425f12d9aba380a3112d53d3d7c8f64adb9ecef94f69e6b42afec87db0220521a58fd1b62eb89abaf64b5ba8e79493ff4b146fb9877ade991119b4d5096f1", - "id": "01968fa393539a33b61a61e9b27f6c666dfc9a75618c5515d4fc0ae3e5e2f7bc", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 133400000000, - "fee": 0, - "recipientId": "Ac9Z3Yq7xJeE2Vt5QcRPDmuaTS1FtypjyC", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c041ef867527f600f1c23510313ea3765322127147262b2137a7fd6ff8e8ea6002205d20cf3486dc1e2604b447f5cf48b4f1463c0fb8e2a5136d4ef43e5017355bbe", - "id": "fd69e79e125889c7e18e4ee6f3b9b6a6833185dc560cb07a24f76523911d9e87", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 133400000000, - "fee": 0, - "recipientId": "AeWazLDW2wcTR5tDFoThm96tZFVKvyg3Xw", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210094396bc42de431678812ed874f45e95dca0c9a28a3e67a48530f271fe1366c6202201aab946ee2c25becde260943d413a6b70e800ab2ec02bacfe6cff415c439a7eb", - "id": "99cbdf7999737e6e3ab6f3db168106e630e99e78c7c7f6610acc9cb77d9b74f1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 133400000000, - "fee": 0, - "recipientId": "AYKHBz1zZ1dQjpfBu7jJforx7wqX3MSw45", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022066f06afd81dd1ae22a41c79ae0f8592333f9a1f40d0dc09a91fc81556179ef6802201c0fb882cd5d81ef77ba6b1bca9ce4a52143fe2cfe9c06d0c4d1b6737d27b42c", - "id": "ac75980be3c16d88e18006a739c0405ba62ff7e4573297509c22799f36648090", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 133400000000, - "fee": 0, - "recipientId": "AGytj4HN26YqbqoYy8bVyAkHr5DBKqck5S", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022000cb76109a01edf4df270e3a942744877f14f08c462e21f48a97e8b401d7e0e00220497ef0efaa27f2b0f98ded7c797f897411629d2500eb9541e8450e2ec4a85b75", - "id": "dcb55ec4448b657e784ffce0a845657369bdcd31ea3118c6852f3a267858dbf5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 133400000000, - "fee": 0, - "recipientId": "AKv5bQtqcrS1NwWM744ApRXLGzxq8th83Q", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022000f10c1b56695f77a65301a9c63d16f2c6a984a921b4c43a1087e41362db454502203ee4bb6a6f6fec5cd685966c65e032b9dff460102e35423f16d677a98d4d9a77", - "id": "da79fa8dda0f553b25b8cf289fa917a8f2b3154d16754749c0beb2c0fd541909", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 133400000000, - "fee": 0, - "recipientId": "AW3MKtfA8qu3WyuWf3WzZsXyNJj4T9t9d9", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009e8e61957950103cb3dfc4dad5e18efe80e1f1e4974d1c1c079122ec080087cf0220209c4741057ef4819de778e3353b58a0a9fbd45013775606e3eaf16f91b4c22e", - "id": "880ffeb2284de4b16dd38499fc6fd15320071d08a0d19b1837a1973284b44795", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 133400000000, - "fee": 0, - "recipientId": "Abe1tPmDJ7a34syYde7XCkdUfAGjPmhNyD", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206ee7dc450b5b1e60271ca2172e1e1c26b23d6b9e163bb8ef819e4d7a2cc3937402200b5b7687c82a928ce9856cc9d452ca71e762053135ebd791bf09e1ecf2f1d94f", - "id": "436a0eb389e7ebb2ffb7215e10fcfb90b707f9a3fe08b46b4a08e53919106bfa", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 133400000000, - "fee": 0, - "recipientId": "AaCJwF1eVDGA3Jy6MmQ8emMB5LKkPFhqPF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c319ac615e8d54a36c919e40e7a6a77e534abbd952abded7533e11a1bcfbac2902203e9a945df2c06505823ea920839528d37fe946ec8cba4c75e1c2bc035d1e0624", - "id": "65451621e425a1118c592029a9b6f47f4635937da9640edbbe2aa7e1531b032e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 133400000000, - "fee": 0, - "recipientId": "AXYh1dUhBWumWF3Saj7F8UH46TqKoUR4oq", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210099fbf3734fcf70371ee259534f82e2ef84a37b9000b254f7bef53e812afee3a202201c0aed05b097804fc2e6103c996b323cd0d1a634d2bf08560d1473cb19c98583", - "id": "0e8d05444ae15dbb7cbfaf6ea4d03733dfdcad333267f9e7e7ed18f5975bc168", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 133400000000, - "fee": 0, - "recipientId": "AFzhTRcF1mdD8bswGM4kjyiAbvHFZ3utDf", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a40e1a906abb1198a9e6749f139169488103f6a089384414c228a375c3697c28022045e6829b51d7f3f0ffb9e6f27132aabb8c13c5612d9cf98b2bc65736d3bb7d6a", - "id": "53b7a5d15d2e6c5d4b977280ec050e8bde87040748e5b25901459f139c9afe46", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 133400000000, - "fee": 0, - "recipientId": "AHj3b7vTcrvS81U3xpWT62G6C4jVBpXB9z", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a226e5b1d830f56939fd2b860c004c54e6f49260ff1400d21509c2e1f6c7404d02201997e86e6cb516c88bf6ffc7ee6cafb855783954fb8e131bd65561fbbea8246b", - "id": "7bf54e1e48b45d11b86e06636fbc78f412d46ae1e71b2dd24dc3474aa6290dee", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 133400000000, - "fee": 0, - "recipientId": "AUjtjkgdiyEpuByP1uajKuT2HSeb68iq2K", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220415974bfe952d18dc3a26a966b9d7c63993568f834a3bed5c4bd08784331230b022029b654a8a4b0da40821d6a63dcf40fb325b58579041008ce1593aa8f1b785d09", - "id": "cc3fd2a9da7d622e24a186044bbe5479ce94cf75be168d9e2ce5a950853648b1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 134701272086, - "fee": 0, - "recipientId": "AGyWeMScLefasU8tiewWRkHBeqvSeaAGii", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e75d0bf3d62eea3631a95d6bf61055b0006c0ca0f92007b3b178e4ae82bcf5de022065d1c1c26883926792929c9945fd6fe6cd1054ca716113d146598c669f8b7db9", - "id": "2b80ef415bf5e28b1cc9b5fdd407c3005c498e6ed0d779defb5386f12a145b05", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 135000000000, - "fee": 0, - "recipientId": "ANZr3b2hPsGwTtVgNxpvMkyZZ6Vg9UZkrx", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220774c16ee61fb32555f68f77aa42d26f4df6689af47415876105fcc44049d00f0022026855b61fc8975aa07b518e0b4043f63f6e4e10936b25266754dd50f4d63c014", - "id": "70a85c890f419243809d0fefe37b7e6a03f73d859c8cda6f59cd34d1b7d67a5d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 135100000000, - "fee": 0, - "recipientId": "AHY7L1v8LzmNke19ArEgzN3tocigVaAzLX", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204ea6f57c6dad7869ddae0d8bd270a4f3ac278bf5887bdcf587ff4f083013e322022032a61c4901116e5714560a5ade681f74b41cb9947426384ac2db55df4004131c", - "id": "a5bb75d5aa8aad7955555060d8d43eaf3ddf39c0b7e43235466870752a8fc283", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 137900000000, - "fee": 0, - "recipientId": "ANAL6hHrK34Jy7v3YWGodsJcb3Egau8UcW", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d12c766b40589e3c976e03c5a0ed64a8e44c32d3f0b1366051dd37bd9c6043520220455b383091894c83178c0ff07a45108abf5d442fa44d2dee6adc8b8cf884e018", - "id": "95fb3162c8f7beb2ab5f6fb61666094226db627b460f0d5ed45a76483064e6d0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 138600000000, - "fee": 0, - "recipientId": "AUArTLyiUVrf9h8N5fM4gtv3yeaT7sD7pt", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e4f07b7c14b4136b62ba30d4dbb23ef7c34d0b5aa574b528fc71975134700fee022018350059d6011c7c4310aae232b683b8bdfec4f366cb952c8a71573080eeeb73", - "id": "cce0cd6d09ba3eddc5acde554e079cda7dd1a370a2ad2136bee3da660a7bb6b0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 140300000000, - "fee": 0, - "recipientId": "AKbaSMZ36bgW2jL63Z2RYe221JvEKBJRpK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201e1e2cec556612a736da0487ae900c979093566cb9d461ee5f97c1cb8913521d022059f3e13018c3779000de42972f514a49cd359f1681a8f389d203483c95defd9e", - "id": "77de4c98b0eb2024ba7df54f734d9e0d86efe0250eb4c38921be96039a92273d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 140332554565, - "fee": 0, - "recipientId": "AS4JC75Lv8avQHSDT8aRu4qWLLzy7B8YnJ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220220c567e49b35270e9ad3f4618ce46a1d9b86d8bac6f51f77a561414b1c589b10220287e615bbcf9366ae1cc249f5652cffe72e61bc76dd6221bfb0b9751b359ed63", - "id": "b45e18990b9f754737ea2442fb4ce19f020f9ff1ac5bc083d69e5e24810d0e34", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 140332554565, - "fee": 0, - "recipientId": "AU1Bz7CJhiNYneNYEuBCXPY9WQBwCxCFSQ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022030a79416ae581e874e3f85ad8fac4d2c0ac39d7287c92e807452122a4b18977702205b3d6fc1ccddb6741330366926fd70ed89f160eef85bd2dc161e73984bf7c1e7", - "id": "627b1183a9f109a94542d761904f247ee43527810bb5b32c70c6089121223f1b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 141913175258, - "fee": 0, - "recipientId": "AWSYgoRKRpXfXbA3WpJ8eegTky4SdT6dYi", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d8aa4af8d9f52b9b1bac3b38217220c0aee778de34fa652dbc9d2df182540c840220687ddcebd64e8b0b02aa4e18ba738aba219e0a66157d373e840ffdcb4e3d5093", - "id": "fe7f90ba3d98988a846a2f943017bd03c475625ec021bdf13799f8cf7ed70b89", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 142400383746, - "fee": 0, - "recipientId": "AHomeJ2mZdS2HdBnfpznUGWKMdnGE2ZyBi", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ebc13c20a25aebab60d043e3983faeac70aea83008e8531afcd3b3aa986395e002207e37320b8a5085c0fcabdc8da57c7f67482b46847251beb21ced9096858d67a2", - "id": "73f0d8477a0a9691839f121b8a28b86f9c3a0283e60b5dcb57a74256ddc19c1e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 142597922946, - "fee": 0, - "recipientId": "AbCmk7JqsA2oNHjSxs33hwEwm7i9ZGfNTc", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ff68a3af0a8400a3f5bc2fa933df079aeae3cd8a33df0e6d06883b8bae604f3902200618c92f102ae967f90b5ab0bf2f17d561501fa6b0e42069cf958f58f10131b9", - "id": "1b8a6a9805b7c3a9185eb8692d93bf0130f0c090decfab0f25f33748465c34fd", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 143671138618, - "fee": 0, - "recipientId": "AKpcMURctYwUa7NFWwQbHMPShAmGvZxTDk", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203de99b3f8f7fb93625f52d07e75e9735674757e5728a0833e282e21887c399ff0220665f3516be9e94ea507964890dd5d1de7d0829143401ef78439503742444cb45", - "id": "ef58d87117fe3cfb73ed87681ef9df9144ef889a2027ad85f341809548a7d69b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 144747007068, - "fee": 0, - "recipientId": "ATw5aBDeZzzxdz1VhJH7zjYUS5c5m3wWcz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022016e3810651bdd50d22b4240aaeaed98bc01b383990a18711671e27fc1c2bfe35022027628ae55345065e5f4e21b462408a9845cfe2b3d9666506017fda87ac6f7e07", - "id": "d068d9b75cf07209d53076886c817de5d449938fe3fcafd31828ea400f4fd44e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 145130623474, - "fee": 0, - "recipientId": "AeReN9UHpbqNqA1zRP3My3iQYYw4vBQ85h", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022076fa46b96abcb0ac7b0b0b9b5c8870269edaa7b4a3ae4fe75585837c73c1194d022004e5e214749129ecef9fcbafd06325ec4645816366431199253fe349af14bf86", - "id": "a784b1ea1393c00ac0c8e58fa19b9e137f1e8611a4e8f81f65660dd36b4f3882", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 145500000000, - "fee": 0, - "recipientId": "AVgLdwCZLBUVRr84tPzwgCPLcdDRRTvkMd", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206d398c5a3c63f66e37216a297e442d8eb490c67bf99cc0a98c5680d9ddbe2933022048170d0c7dbe5ef0a30baee4c669579c1c414fc12cca94af65adcd40e90e2d39", - "id": "0d36bc9b909f51dd5bed75e5022f632bdf8707268e74aba53f8d7400849b49a4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 145500000000, - "fee": 0, - "recipientId": "AVroDA7WnnpRhsAy9187e7mRs3c9p5ADoF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201e8248c25c74669b3074a5efbdab29b715d4d796bf1b139917634cd27b72b1280220184b4d05880cabc910f372c43c13fe5f3db2540174993467dadd0bcab4c84873", - "id": "6277e6f92b5b75671e07fd26b28a16a82038447cd9ae5dbc4a8a525ea2969204", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 145500000000, - "fee": 0, - "recipientId": "AdQNZMDMYsqUhuZ3F5Yy5FvUsdRDSupxEP", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009b106d35d40c34def24a3af078288ce38a5c42374086bf13c1d43a354abd4ece02203797da879995e88f71e7a0d04b65ab17f80d073d0cd015bec58f1f4a5bac25a6", - "id": "a76db685b5fc59e38d3e25f7c43cadca9f35dbb079c8b5b73bc8da3066b501cc", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 145500000000, - "fee": 0, - "recipientId": "AbWS5yJ1gwx1GhpaTYDQvJSy3bCFbA2vpo", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204e1ff4b27c8fb80dc2427f493822b570600fab317cec5a5cb53a58e10769b6b202206a7dc2cdb7785ae00ab6240db3da4abcff95042a33f5c620e42af976925f2c73", - "id": "560771be8dc4d0c21922a7a57b7fb66a9b20803ac8051150cadb54816086eff4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 145500000000, - "fee": 0, - "recipientId": "AXfFia87jEauZWU6DJHRbECKk44EXSQmbY", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d1ff7a6ec56fbd8fa592c687ba088f198d0752abc667ef957dd2c2e40d9589c70220218d36c822d70d25b1fe3c0ecc1efe85671b09d16765a93cd12c20b45f1fa6df", - "id": "63d81828f4c3e1455eb6a1aa99ef36b241b5fa9dd7c5cf86c92dc8781df88b92", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 145500000000, - "fee": 0, - "recipientId": "AJCWEe6hRQRiCC1gLdS2TUSbEwUPHxMJcn", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205b1550775c4cf879ec7500f8eb3880a3ec9173db41a7e8e249db14dad79a0d9902200cd06919a283c00dca6cbc647f1f09398dc377c8a4819db6ec20458c55a4affa", - "id": "ec03b808b1f705553a333f882008acb5330f3fbfb87eeb64b7879f4e3e9e013c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 145500000000, - "fee": 0, - "recipientId": "AM3Tdmm7PajytJqccZvjBmK2aNzfkmVNay", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100cb1a88bc254deed2de2aaaa7165c62d8ac24599cb716cf294fa7650d731c7cb8022040c5516b3945d87eb5bc4101ad5fd8e28c04a86a2475617d43611793c2e7619f", - "id": "58ea9d0e7a38ac86f105406e615eb08a288053f6be10db870a126b4d5b1a7558", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 145500000000, - "fee": 0, - "recipientId": "AY1tE8UrGCykVngKVmmjZfes21arcLLyzn", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d0b69b396392afd07b737fc4aab7262c5e693a61b4869afe53696e1d72c159d502200de0cb71068602b2552baec25e1d3421c4d59526c01a99bc3475abec5f63bd1c", - "id": "2f2c0ef7895193de3e7405dfda9ded41aebf2aa0143a3870f879800ff3af4a6a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 145500000000, - "fee": 0, - "recipientId": "APAFtJderG8gJDUr6avxr4thm5a2hrYk6q", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008d43e939b9b3789b1673ff939519fbcdbfcb6746688d1309318b2bd12f839c8e022009e186c21fb9d2963e2c01d863058aee90c9eed7349d3243c900d606cd7f5163", - "id": "d9e039153b8d06ab7b1e3fe64eca9bb917bd7a7c848eeaeda9f8d2b28d24d5e4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 145500000000, - "fee": 0, - "recipientId": "AMFSfB5sy97Dr17fqAF64G93pxkSn6mGqH", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022005a7f2529d9264b887d323228132518980dc91ea2d0cd5bfed098b8f05b31acd0220676c8d86fe6be5a7fc90ef3ac2db26674377c33188b694e5106c36850fcdd5d3", - "id": "1f7ff73bd0c0b6c42a73499dc8948ffc23a5affa758ca17425d60d03f0e3c412", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 145500000000, - "fee": 0, - "recipientId": "AQ5kzJMSeEEr14YkwoTTGpjj8odwQj121R", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ba6df9e0ac6c1db0a889bcd3c30460b2d5460ddffb7867652dd4e0fb296d9bb70220039f2b0799cf13b3f9e5f4f950afb53c12c72ae1e6ccc15b6c86aaba0febd322", - "id": "39afbf86f83389014c2d00c1b56c215278a7d12f631a10e24615b4c69aab3dac", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 146600000000, - "fee": 0, - "recipientId": "AY3P1mc5878sCMkCFmJdEBhMXjzZivm4rd", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c563a228728d67c90cc13501efe0dafd97919ecec25afc4e56c48c9289f0d85a02205c91587eb8001188c7e291e0c851feed13f6960d1828b7fdc6303fa49eb08b41", - "id": "a1ab69f665a37b685d9219b051428f1e9930a4e8bd908623085b1cc5a85a7a37", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 146647519521, - "fee": 0, - "recipientId": "AVwxCqG9GamW79Xb96S7ipWUpxLgR3shoe", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009d426ec91319147e8ce9d95e7f4a2bb2ef20ac1a93c4b9a79e0a45b555aac9840220470cc4cd8993fe92f3e41ac71027ea68d6dc5e873a4f5f4042d2f6e03fea61f2", - "id": "6b19fd28e4fac7c60537e63d10d0bfeb1ac28eb768db7c05a0eacb22ed0c945a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 146868042106, - "fee": 0, - "recipientId": "AUALn8exeu8kXARs5HVxay2r8pmSmxMVdq", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022038a92d978af95b79d68342d7da3d86e0de7ec10fe793b87e15e889f962f78bfd02207095fdc539244f346eb46a50d00a5a692b0687ff1d6d4597ebc7f2f92c7b3aeb", - "id": "d3d03e03d5bba603521e8be851fbb870e25dee280cf5a1f6fc7a025d84314a10", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 146868042106, - "fee": 0, - "recipientId": "AUDTxFyejQZcR5bNSXBkoQocuoquYfChG9", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c460859d8d9d4a18b8c336b5345adf8023234c90a0d2f86836a39ac3b1baacc9022068265214028204e47325d2f157d27c6ac20c0f43276d6df706d4492687b13e23", - "id": "04f2135cee1a37d6854cd0d10c8db0bcc83fd8176833428f8e5cb4b891aeaee5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 146868042106, - "fee": 0, - "recipientId": "ATxUGRxnCNHpafcsTQAimxEygTJEb3syCy", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008b9f09ada3004e0b966f2a208bb2c938b797b1973e1e8c2b53b7ad4b0860939f02206f08c4caaf1ed0102430c9b2f382f58e6e0337ef8c11ec6ca0894263b99086e1", - "id": "7c156161c977de562a5613d8b6b955144a56ebdd9efceee9e8981ea639e144fa", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 147015057164, - "fee": 0, - "recipientId": "AJeAvhsxGTJQKtd7RHBfmNtCy9fPUZYNVV", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100eba3c97cbd1002056427991b7c33c21336783809f814936183ff030378ed3c5002203c6dd00bfab286353f3565d3bc7be98975a9c8f412c409bf182892043e350719", - "id": "4511231384b91d494dd617db65cf90e99d7be45a761010761b39b2e07cf0a896", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 147015057164, - "fee": 0, - "recipientId": "AeyAvcAptKRxirAwuyLei1YLUMZap6RSCe", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e30b9369318e1cad7ec287448f11e9df997d10d4ccfa1cb14c5bc16f042969550220716c4d0772ffa77db00d076cea094ea196e1bad98aab377b20e87aea6e7b8b34", - "id": "a033da0ff01712d4b8e724846bc0f524a3931a0e268cbe798a006361a6b5a2e2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 147015057164, - "fee": 0, - "recipientId": "ASHqJXYyyKCVuhjs761cz5bLm6wy8nbSgn", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203c354c4d829f755371122ce7249619b2df5a86e74830f1809e977fb80875e5610220713491f7571edf18f009dd7d0a1d4ddd94ba8c53fdc7a1e53596dcbeaff40315", - "id": "86c30a20741f5fc1738e4f0180b64b585f0adb966d414ac45316a879fd5e7fe2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 147165816371, - "fee": 0, - "recipientId": "AVpQfwASwF3WSC5CM7HErLYvYBkpzkTmUu", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220030a63d6cd68e0fcac0e2eca89c14eb6a8832d52efa56e6d1a5b772805d1ea480220091c1fc5904423e14a6137f02d153573765905e76d1c4db66e945ec7851763b7", - "id": "93ea7b1a7e4a623e75cb4245a2ff78894c9f63c9ae4438a2b614350b9f65f1cd", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 147695811297, - "fee": 0, - "recipientId": "AevPmhwSCevsJkXhDcKvaRNQwnG32ygbC5", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d748dcbdd417e023f4e3976a7625d8c7f2ad45644e768bc5923210b70b8e535102203ecf2b3f03c5caabde8ab41047155bb14ef36073bcab9a76b1fde86baa8eb21b", - "id": "6cf7722a4793a9f5134e80853eb954b8772ec20f9e7732b109c7489b722f7c0e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 147792818385, - "fee": 0, - "recipientId": "AYdQPyMPRyP2jpp813PpvKn18rew13EJTk", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c946a710663b006a6065a032f29ae1eb13ecb2864ee79e7443e795a7d566714d02207dde19f663673b881bb01ddcffdc1bb88481b42c7b78058ef722238c3acea288", - "id": "5ced9e0a7ae04dbe285c3087898108f56f6ac791e3650a11c9b6d05c3279f0e8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 147794072165, - "fee": 0, - "recipientId": "Ab2sMHQxXcEFT347HyvsSdXqTwBWcYkiNo", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008f73302141cdd6646a1b0c2be14aeb4e40bed6eeea5ae720ba24d482f45ce1fe02203213e8ed9fd98004e859de842345e8a3856b9a1920608fa050211ea76dfd7cf3", - "id": "ef80b356e77a8b3e18c21b11b6ac09b3f89b7b99f33aa12fa53b4d2ee3f380b5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 148338192678, - "fee": 0, - "recipientId": "AVS2aoVuzEnY74iWsgzfzmMNQfSRc5KTzq", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c6b3c68949296e1794246ebbc64bd06f9f5d099c5a4498030442a6eb8a78d6f202205f0be315dfac6b7647a33934a04df6c40a2e40dd8a17303f0b20bb09c6bec6b9", - "id": "e38e2053c5110aed17e4a7654650a3061ca5441c3a5fd8d159e2140b20d26f33", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 148900000000, - "fee": 0, - "recipientId": "AeNApTdu2Kf7vphV4H2ZTbopitfEchc2eb", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201255ca8ef4fb94fb2a0f9eb089f9f1d48971adcc9bd153835e7df739109fd6d7022022a497a0b8ebc793d25e3348fd884f32cea85189bdae2f66799a3818c15e6936", - "id": "5e76229c1016f43b961ab11b51d4be7598434be225f68ca4347df5106c68b0b5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 149442810367, - "fee": 0, - "recipientId": "Ae9cyMJ32RatQDd1iPd5pftvBjFpRyghhJ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210099179595efa579206006e9b6689f62d222f67a4ce4565508d7dea57e3325c917022009425c7807f05f46406a05f02a8cd70dbb6cd20f7ee172749a54eb21f9fdfab5", - "id": "578b56b6076f8b418eb1df12980143f0ff841890cbdf447b7d1d5320785cd0fc", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 150899497250, - "fee": 0, - "recipientId": "AZGVDCCTnVz5zye3hsckws1AZkgnUdEwLj", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022056e8422f6cc77310114d7e7aab0eba13fbca2cc242a04594af17c8938d7a55c302205d8a258dcb617f878e4b25c84f08d69f77c5575e7c6840028059706635f5bd5e", - "id": "c543394aca3a02fea18e06f89d12e6f170269787be922539139bf72ba173caa8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 151036016000, - "fee": 0, - "recipientId": "APuhkVvG7u6itm8bUEfqDBpw8vTqTZMpdE", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008db754d788749079dadc57c9bec350c02487e095df746ae3e196c566b62edfa50220089ecf52f504b54a6d5ec5da34b0eef3bc5f151d8a65598c07da8cf61dc2524f", - "id": "2a5b77ad3a7559a3b066e6845f41c73b368032bf9c125825f6ecfe7f84a48f78", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 151200000000, - "fee": 0, - "recipientId": "AVJ6FhpMSHC7sywQDJujkwM9yv75tyzjrj", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202a8ffe7a80afccaff07d8d972d11e8af2396f9912171e3fd7ee869a30262739b02205b9785b72fd0ff1d1028c44d29d9423cf3a22a3e5c6514f86697742777868398", - "id": "e1e3ac19ac46f91d4be3977a0e95de5cb136219a984b03b1782243d0b5f2757d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 152400000000, - "fee": 0, - "recipientId": "AKMDyKrWkSbpU3q6oTxuoS7nr6G5US5Ppm", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b12f5c4a7c7e0366f303803b62486f0d6685eb60a873fdb9f15d508c8796af2f02207d28afc1bda7c2af2c650a7cdafcdd6111f950cc10d403fa95132e0bf5669661", - "id": "188f9c090bca5ec2214a0c41ef741aa5b7f3a947cd2cc6ffd51375c837bcf9b7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 152400000000, - "fee": 0, - "recipientId": "AR5yuVPejTDoeHhfbAAYBMCoMPWfT64wS5", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207530f550343588e92edebc0c239f9d9215362cdba07fc9fb0a9a0ab5a3185baf02203a151c01fd5ee65b3b90915d6bc0d49a756a687e6e409ae21dc64f18cdf61c8f", - "id": "b5b6f86c2a8dcba704275301e6f78c80fc63a7c3f1e6f80a11e4c1f35e738e2d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 153500000000, - "fee": 0, - "recipientId": "ALhmTEX4c8Cu6j3LmvMy9QRScP4qd5uS1X", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009582b66e2d7e23ee2cd95f4a35683e67370dd60f84bdd4bf13a70b0e950255ce022027d4f0e29103bbe2c7626378b5d92f9976ef5637d4e30201f41762f6b58abcfd", - "id": "d337a4d55a6f848c4b56a1d8b4267ec04379b7ca49d51c4ff12bb4c91dfe07eb", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 153600000000, - "fee": 0, - "recipientId": "APL1n3DRcNdNvtGEAVXrZeF8aE3kSje39B", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022026c5e59f6f0389d02d8303807aa6b56a1677759397bf1131fa923da22cc0ff3102200c206cdad36b67bbfafcdc42bc5af3a43865c4dbcde49216cd6acffb1193a86c", - "id": "f855d660cd350500dd7c352d9e1e298f7faa93fe3b99d54fc5179caa3233e9f3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 154067041808, - "fee": 0, - "recipientId": "ANe5pi2srBMSp2Hsdi4dvEQkmRw6QRufWE", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b6021389172baf3c7416f95ce39a517a755f44e213c7fc174dc47c2a9f67aa14022002f08e8c6e0109219b258fec7dfbe25e18c5d563a99cb751219955a68ada0e0a", - "id": "300260d69932d0fee55a854007a7c8e16da8ba9a531fef306df009c500e42901", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 157600000000, - "fee": 0, - "recipientId": "ALUZm656LEa4TqfwdtkjP3XFidv8hdFG9y", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220564681998c47b9747919eec5e8d497815a14002b497a43789007d5fcfd1fa37d02205b6097bfe9e3c44dd8a7bd9d610a55d72c674cca6f89e29b37847ce57eb06ec4", - "id": "2c1eb9434e82279b5ba0aafea3b7813e4f024f3386126e65cbbdcaac3e1de27e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 157600000000, - "fee": 0, - "recipientId": "AWtwaGELZxRTxu6znTh3JaxMJWyUf5yg2b", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a87d31acee7834af08c83ca70917065c1a5a413a74d7c4443e316f24a8526ada022077ddd8c1add111bba68f01695dc922f680b83fa3db1edf0e9750a0e041a3c5d0", - "id": "b724ce6e1fd1bc009fd05da5523c735a21300fbe1a4fc1da52cf2c6ab9d66ba6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 157600000000, - "fee": 0, - "recipientId": "AH8dy4DPGzSnyMmfXmJrtyWhUkZoqsscWC", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022000f7c8a6f8a8b0bdda54992f15a566dd1c9c9d5d48f03b0205104ccef6397312022052ba2904b07535406df11778a86256ea7893b6e41c91a7889aa4e4f4e93ce7fa", - "id": "a7d0234ea7c0595d44d473619c7464781cfbd6b3be2edc583b624cb9210ae7f4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 157600000000, - "fee": 0, - "recipientId": "APovNoMU6T9i1yvfXczPauvFA79FFPP3Ns", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e8a6834b4380b17f340853bda75c96ee9c553ba2b570402c28261504ac6f11e8022072c7c130180e042c97a660bf1f6f2b17625dc24acc16e03c4b54bfc767ba4e02", - "id": "86f6394cec011bda87c30b080e27320137e936adb8e67a9f9fde6afc84582c7e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 157613342003, - "fee": 0, - "recipientId": "AZd59X8LMYSdKgz6zz64jKxhs6YHUcE2Dc", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022021513ab40baf4bd11223c976ec339be0374e8a96ee60c90e499202e2e14916150220229b9355d285561da1b1cb33ad210df305e961ac485e3ce51db39faeac96475a", - "id": "a3b4066f32a764dd0cc8c357a401e91c30be8b7913851aaf8815c61f1fde18ed", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 159675898944, - "fee": 0, - "recipientId": "Ae4moTG7YCQ4yvzW7NV86aZVA1SL5jZGUy", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220799233668ec57eb2ca1b0c18b433e788ef25df88edd97bdfbc5919e1184efdb3022074a3d4fac5a531fee1ba3a6c6378d5fd080acd163e8e7f983969b1059376e9b1", - "id": "2d900441744993dfaa79989178ee5ed78cbf8dc5a67ed945b2558b0531c23653", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 160600000000, - "fee": 0, - "recipientId": "AeR1SNtu15o3UocwDDh8kBeov2RWeVYzve", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c3ca5aaa9b76d2d4f4916b9268fdedd5f8c75427bb980e7437e5e50e4028560d02207f145bba9763452fd5cf86013c35ff8a71aaeebd0966e2a6e218e7dcfbd43dda", - "id": "4f732a783e8fd503af9584c2116f4d2d9cdb0763c1629f202646dbaa79445be3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 161600000000, - "fee": 0, - "recipientId": "AP8RQXpVHCYEjNaQzHPZLYEARWeMzYAnUH", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100dea089096a8ef631cd271361fd564a626f541409e3e166082890f5c3d39a5db1022057eaa7c8ca2fc37cac5129c8994f26b231dada2607cf7f3e026bc5b22c9ab89d", - "id": "99aad499192c22c4bcbe71b8c133bef61eb16efb59b9281323e04c6c4b925860", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 161657213879, - "fee": 0, - "recipientId": "AdLtfyiktPxNa8Wf3uEgGiPHHRvBwb6BVS", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022074316c70b4b377bf7ede11dfd7e246e947479c62c6343c8370db0f0f5ba9e19702202487a7a8e86ee4970e3bfa3c42edebfab845b42f98c454db3bd0310c085acb07", - "id": "70a2af7bb1dc6807071d4c2ecc193a16ea4adaa872ff14de76d40e5d07c3498c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 162022981403, - "fee": 0, - "recipientId": "AW6FnNuwQGBcJ9nujbPDx7CBjukP2tK8KN", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c29213cead6a57f15622d66bb90dbfd8e9f46bbdbc186c752113d264e930486a022006f20912f179934ca1f2f94e0fd43ba5b415666d9949c11f0092de3553f648e7", - "id": "5c75d116edc4dcc35d0ba18d7ad56aa45dac0d9404fbf9f52e857fe8e8f34406", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 166527964751, - "fee": 0, - "recipientId": "ARkk3RAAeZSrA6Q2NiQYi1F5J27yW8Fz66", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220297e1206fd771d82406755d928bc86097eb8126f245e500f9c3f425d5b30fb1f02204b2258bcb30b1a11cde8cd76c81ee20482f870715cfb861c62c1512bc2737344", - "id": "4f3aa1ae966a9f4ea2bffb9e340bd522f3ee33479be4fea1f63d5a7b5eb47c0b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 168221932329, - "fee": 0, - "recipientId": "AN7y5f2nixsdifEkZTcgpnXWBzpiuCkcQQ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e460a99b932fe2c9b28720352ea943683be7120993e9dc4bd82bb336167c597502205a46ceac87e8dd5179eb40194229be169446ccdf085089e657b200089bcec557", - "id": "6bd4850be884fccb495c347ca9a9ee5b46be31fc1a0404c03cec0608912be6e6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 168973133925, - "fee": 0, - "recipientId": "AQ6ziotXeWnocAnwzz7V2idW8uxz5V1pka", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bfeb6d49f03a89820a6dd1e82d238aca795866ca245be3f43e869dd1ae5bf9f20220580a0224b2b99b38564cee5b3470f88aa7c7106068e2bde76fd8ad1d8ec0ed98", - "id": "81e4e95e91247b73cd9e2dc3be2a84efb9fb428f04b184f63df43d3c1a9de40d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 169700000000, - "fee": 0, - "recipientId": "AKDpBAVgjd4cYXd7dP6khrK2Q5GyuyQfoi", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202619a5fef5f87ca49dadda59139f17bc7f388f09340914c5e0b997c1f0492b9d022031a12aabd0dca56193409dbf39fa3e9ee332d3e47fa4656252e161ea90563c6c", - "id": "6c86bd9ad81ff5305fa35d8eec25872292bf417ed456a801bb2301cfd9d5e79d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 169700000000, - "fee": 0, - "recipientId": "Ac6zKra111cp2QccMk6zDNYwyWrYsjY4zo", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203677d3e5102ea36a2a7693d8a47c86b3f182aaf7c4c8cb4118efb17eed87b6f002206a17cdfc67917a0c18faedec37baec1be75245eaccb45dc7fdf8ce4cf49b1e5a", - "id": "df692fe1270c264c2cad4bee6383428d2038d6b82d29b805d367690097136575", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 169700000000, - "fee": 0, - "recipientId": "AZbfRSargxVS9qk4cBB2BfVCiqir8bsrKS", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bba6694b9e169a8b77a512b9c09079d99586551db4f5848a059b18d7500ec4e60220438d0b0f1d75a0621a6a06375ba8a3f75dfcc542d7d13f08175f9878b5feb409", - "id": "c0ca1a1626b404ec63d0a50fff867e2e9f9a1edaa744840c39a58c979862e33e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 169700000000, - "fee": 0, - "recipientId": "ANeUUTh74UcDqZhgxEwMe1SX2CP44MkfZX", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022072f2156f00911ae5b22ed9bb3701c89ee4ac9ede3f3b4451911e1e284e21f3ec022001918a7e52c65cf762ace735e980547e60abc3d00df59cf16770faaedc05e1a6", - "id": "b119402b26404e9bbba26df0e59cfbf8d50d41d28d428202fb0b293e19c30830", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 169700000000, - "fee": 0, - "recipientId": "AdVL1oAWGLDP5vKax1kfA9izMrZGkaStAj", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206d1a108b0dbbdb2c78252fc7ad9a91ebd9a525fc5d6a0c13f94d64a1e6f8ffe002203c04d2e0f607398933e30df73e93a40db9ddc41abef1980e954654a6bfdbce12", - "id": "ffce74f355622c5a278ee320d0d9eae789d9a8a81440815bd919179a5a0b2633", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 169700000000, - "fee": 0, - "recipientId": "AFpTaoXGqbwVG2HhaWJK5Vky8QMXCGNxAh", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206dd92556e443b03c0d88cbb88f562223e8def85b3c4e5ac40cefd0dc958ae157022012b06f7fbd4551ada6d470d25bfcd38cf8cc711c9af94ac56e4415f4f099f528", - "id": "ea0ed0b1bcb706d1e0b05b818a91691221010f7fece97113227de6a2851dac02", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 169700000000, - "fee": 0, - "recipientId": "ATzVyx4Et7ie5KLrjEUm4iULw9Lm77vgkt", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f42364a66c031a8584365c68b926931d1ffcd6edfa8a4a859a9e07ff248a507902201731c3bddd83d1f64f8514f5914b0bba1f385c6ca410241a0c313c5b8b614b1b", - "id": "6aec2e00bf4ff94b4374f83df59e85d0b6a5d05fa5520b021d02c74e9b582e26", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 169700000000, - "fee": 0, - "recipientId": "AUeaN1Nvksrkynuxe2KFQcDnE6U8dHFPdD", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206facc2f5e57cb6ffe15840f80384f3d386125e186234a6825accb2c8fae4dc940220456e79a2aa1c1302dd8d9437d815582b5cf6e2dcf9e7bb18f5036a1a1c77aa80", - "id": "965f90cc474b98cae2de4369455e85d1d10b71d5f6218bf8029eacbd8ac70324", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 169700000000, - "fee": 0, - "recipientId": "AbFmTF7BXLzyHYPsbzhH73fcUSzYrLcZeL", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022025a0f243b8cedb3282616eac1cd44a0f15f0d908c75cbe5cc9b0be8b46c4e72d02207463d2e7f585be69268fcdb655d3d23c6f3b50c376b2683d7e2f86cc7a5e1aed", - "id": "c1d0407270d31a4e5a569d340e0e71f3e7771dee306d0c229c2c4badced51dee", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 169700000000, - "fee": 0, - "recipientId": "AXvyLRRSRYxX8HsQHwLgDJNwwxQ6MPyuhz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a149e9edfd55fbd717c71c7150b9546a47072537b1021c7fc4e23703b4322c93022033f23dbec845e796d00102b8570fabea477c75c88b574611bc46e3aa94afdd73", - "id": "d7fccd7b5b4da68f0deaad41f9f132f5891a82295259510d2a4c8f5c1f2fb22e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 169938227241, - "fee": 0, - "recipientId": "AKLvGM1T9FCVstMPjWTC9ytw5UjyZJ9xMT", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d361a2adb15f3e5b20238eb382fedf6a502ce30044b065b9169f4bd223d30fb902205e6004454fb61b7562e86ff5dec410db6765e29128e23285a6dd2b34fd437791", - "id": "a59e761db39ffcf16d170da0745cb36b5fa9d13c103592cfc28e1083ceacd886", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 170449652084, - "fee": 0, - "recipientId": "ALwMf5fAPMJhW3iwjxMydBBNr8EgZcTkrr", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200d97423751ff5c67b4a4bf29412b80ee84ec48c34a9412addeba4bc0a6278372022010398454bef83c100bfcbcd6920bef23951cc1534611f143c9dfc07a247d60e5", - "id": "d0dc0ac9dcbaea7ddeabf7fc94171d286fccc49fd07668e417b672e96b49f362", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 172000000000, - "fee": 0, - "recipientId": "AbpQwBzBGHQa2NdEpPbg8p1N5j1u9KUMkL", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203599ccae223b04921b15fb6dbf2282c0847656785d0fc12874e739520462d8b602207d431393872a4a6c5e9a7014e99c5d5dc6ce17ed2d21745661b36c34096a96c8", - "id": "245622a915732fb38c48597bce7bb1b91a4ae08bfc629ab7108f13f17804a962", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 172479663865, - "fee": 0, - "recipientId": "Adu9KbxhD6etHEMnjkf6n7diLWdjEGzwiw", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220702ff0e19a0dcc3fb0e62185a5cf1c094b6b67e3be700252e02ece19e3a53e3502205f45c8b0ab6d45cacc2572799244c5304b832f068e0cee10dbfefc14384e98d8", - "id": "3e2a032b3ea825987a4f374298f036806e3d032d7047ffb77f3c74c62d1bb8e9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 172700000000, - "fee": 0, - "recipientId": "AT5ziEWMZjWzBjjKbgFVwa9XsXvgLF7E6x", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203072cc3eb52d630364deff654fedbc54d5a05feaac0e34e0b56acdd843c5d57502201e8f8c94205b700683610b373a3014b04fc0c064ecf7e48115febe1dd26e6c07", - "id": "760939c2cc34d8b911d94e7c5d33e577b4b521695ef1283793e47e9550cad9fb", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 174283983946, - "fee": 0, - "recipientId": "AUnkYxRkpMJZ9jKzRQMN9p5VGDmYhk6bZQ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bf56948136cd8aabd99228cfe5b668e3c31ee9ba51904413885a0c28de5785b00220529937211a9e16896a45ce426309e06db74d3cf0014316d6472088d843ccb6ab", - "id": "1440feb6aeb77c3ceb720c4cc1aff6c3ca840ad482b13ca6fc44943f26bd41d1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 174283983946, - "fee": 0, - "recipientId": "AYP5kYDXLi1c6wrL6AbUX3S2JUEZPVLyug", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220564b80aa83110cfcf4c05976c11cb0bf022aea5539ccfc7285e6bb7024ea92a6022054cd14b6b69a9a7aced496951201a2b39b665282a053686b6095aa8f9af74be9", - "id": "2350844dac54c1d2b56608babd879cd5d1b986f4d0c3b32f587e0d270f9faac5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 174996809637, - "fee": 0, - "recipientId": "AZ8ijDkmLBLemmH1AVPQZz1ucfK7hmpLaG", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e3e8f38a1b850c94c87b33bb095221210280b1f9da1d2ec58669063f7b9302c902206b3b20dc5e9fac39ab48f418d4119751c52692aa353361c20839b564ae2d86d0", - "id": "af897d4026d8b58fc97ad3e0a4f555c1ec2d38954a590611780229514b07aac6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 175000000000, - "fee": 0, - "recipientId": "AQyWsH3NpyMMepkGbMJmqBqZehhQoDnzgu", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a9dadb4e5df8e5b324a66eca8e5a2abbe608886ceb3058c3b28d2c4eaaff255602205bd59390dd1b2b0019a5d928913d9379cad4adebd2b686d61b91733acc1629e9", - "id": "169028e76183f099c5c6937c4b0f33d085a4dc67b0cfdd5e70ef985a7640cc46", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 175000000000, - "fee": 0, - "recipientId": "ARaVs5XoWqrTdQrfXSSr2PTFWpz8ex6Tpd", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3043021f2f2ee4045f6a3a4072212a905fb674ac22021fbe4f627ab06254c9d8996a6302206011608f1e33f52c6e310bb6f5990fb97b40e3621266d832b5ff3336998c75d8", - "id": "b29913407f911e50a89ee33658efe0bdd856c17654f022df7dff3ba66037456a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 175000000000, - "fee": 0, - "recipientId": "AaAYd5qhdUN5Suq6wnJyLFgvVMbz7tAXhe", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100dbe0cac3b1a67b51c05c4dcf60cdd40898c0d797c9e82ead41c3d253ff5936aa02206a1367f167e42d0272eb4a2aacde2f6392b845324131d83e4060743e981a2982", - "id": "8c1d279a9c3e6fa01cdd41be4de0389ca0628a81e9f54738b3521a38cad15199", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 176418068596, - "fee": 0, - "recipientId": "ALemeBL1Rt6JgL6Z8CJysKDckba33SxDJg", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210087e1ac43a27da39206017ddb7b7d65c9104eb079587244283e27156769a6669a022002838c9763c2b4c53926175830c64dde70709773dd9ec5fc82880f8afcc57ac4", - "id": "d1e866be9ca942556ce9e7b7e86fc181cb4b2507a254e213837cc9549af568ea", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 178300000000, - "fee": 0, - "recipientId": "AK7bbYXM4Gkyscxp9sVecVn12z3oniuEfm", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204c1db106bfbc3cfb05d07cd96f73e94a858a3b48d98e6ecd569c70e6bd381b0d02202db38c2a3f4796f90719ac11132e0bccbf90f3c4df55a423a8102cdc2617dbf6", - "id": "dcfa805aa6fa5e3b48e2977f881e0b5e958fcd6d8f5e954d2ffcc19ec604b548", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 178300000000, - "fee": 0, - "recipientId": "AeBnLajb2S3ggu919PuDkcHKvQUyHogQ8e", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e22d2b3fa99c81cc47c253d98cf068c6e6d4392cea6b98046a6f9bf96f08cc2902206b95a356720f6ad9a1fc35a0ad4a20dd52ff5554e917b83a47323564f6a59eea", - "id": "b7f38f5e6054fac0d79bfad045c9d6320b25c5daafc7981cf20e97a86d0c960c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 178500479084, - "fee": 0, - "recipientId": "AQdLeqJwgo6Tq6K9ydgN79wyaFt5gzKFde", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fa25b2247f33f524a63be9001bc7bdcc5bec69b58672a2716c818bd6628e2dde02203b4fe9e4e3ee61142c081368f69b7aa585bff6a006d89be99f11c6812f8adefd", - "id": "f31cb0587377be839be6a8c831a618f305cf2aa81524fa8b7db848d1a890ff0a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 178900000000, - "fee": 0, - "recipientId": "AbZg4v2npAwLZfgjLAX3fj6ygRRXiT4uJ7", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205069080158b4cdea2222990a79074e3b604f8fa3c9fe3c64a6ef43c28e17235502200ae19cfc468e9a6be38f7d32418a8fdbd9645926cdbc41f69983b814a2370268", - "id": "741fa2cda9837f82593d70443034a0fffcce8513f05a6808230b4dbc222a517d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 180659436535, - "fee": 0, - "recipientId": "Ac9dEA2bwQW99JBREyPDyhEotZ7YQRH6ra", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ca9bd24ddc2d0da487f016603fcbec17be8c090360e57b8e8cfc33609bf72d080220033a502c201ddc296b9ffe5a6c8a439a56ae3b3e7c21907467f22b5c84afec74", - "id": "d777a58efef6dd5addf4b41c7d606d5087d2c04da46cc3de40df6b253179322c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 180700000000, - "fee": 0, - "recipientId": "AN1yL23GyoHGo9sa9k34ivBDsbJ9xNTso4", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202c19e9171610eea55add6b8ce88e6dd2df3cebcb2c7408750e2d439462053b9502202a0ac408178498ac59f3efef9fbe11595c6354e192d93384b40016ce4032d73b", - "id": "9baa8ef575418b7c21c114677123bdf2994083fdb026f71f3160e42749a3d896", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 180700000000, - "fee": 0, - "recipientId": "AQvopcUU3ynU28hy9qrgjNgAfNJUXxxh6d", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f192ccea6e18a7c7085772ac499c17747e47086e3e3f8091062a5024f6e1d77402207312fda88382fe4897d0f36660b431290e6cc28d09da713929cffaf9f6a49924", - "id": "bdb82c07d56940e5aa3ed4738521380d13c46dfcc2c41a08a552870b9b2f26c3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 181302136380, - "fee": 0, - "recipientId": "APytrs52Z9zEQFbTTsVseTAc2ah2w3Rz6E", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206333d9530c4bb019d2fa8505536bba29d34255860b173bbce1f3c04dd5dfaae502202f71f8001cd13550442493273dfb28a76be302bb4958fa2c00c1da0752c5584b", - "id": "c8e21ed6c8a7b2de81d217a948f55842dd2d9f3ee5c5355e333357d7345a0276", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 181861548464, - "fee": 0, - "recipientId": "AK3N2csioKfWiKcdFiM5Jz8bh6pQurd8B6", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205db6e528d94b20b8a25c31994a01ed5b2735bfa1bc5cf2e8a44d44b666ca11b8022056eccb3b38df08cd32a6d10abd316caf075582233a4e3f33cb73dbd143ee2905", - "id": "70ade5cda886880e03b60be04919c3623a511b655e98dca632b59c49c861a285", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 183741686918, - "fee": 0, - "recipientId": "Ad5ZbazVtWzjZJ7PPep5xcd6BrKXujAVRs", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022046b4f82debedc188166749d7eac5367ea18d2c56ec136943dfea7081d2a52af402207f6b27161c1b32de2738e9d55225e411d74872db44109b4ebd740abf1f3751d8", - "id": "d7f3e67bcfcf0e9be5368f8c3a0d23f48271465c61a3d345054c5be9aa01249d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 187700000000, - "fee": 0, - "recipientId": "APaeLc9yFj2Kr4c6TzDvSEhtv9yuyYt15r", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201f2ec1b7426bca1a40df431b64b458e41c3da447c9110f04752b17f27e19922802200f450da9a7053e18de7d92f27b373bbeb5e8d83d9ab97e521e7b2b97d70a56bc", - "id": "2d55f798fc91fdbbf8e8cadb68372e3562b7c14b5c8cc9c15e9835c442f37133", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 187862979565, - "fee": 0, - "recipientId": "AeK3nfKry9sXXTKdf1TpGxukzwjEbFsYzB", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022049d80b6b0f5e5592a925a105fa49d3824cf6ade3e1f82c6e08458fc6d1b4e1ea0220084e937408e7f66e690d2170723464652d7fc5e49b5a50d46edd4daeec028806", - "id": "90e496cfb4d6231cf2e764ba08e6d236086ee2b0126d754e96e1f046990b4741", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 189637269418, - "fee": 0, - "recipientId": "AURDY45qCCokzgcLMHFgMQedxx3fAAvrmS", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022070499c504fec7c112ea30ad8064083d50c86fc7bab9c35b906d3f5dc574a63c50220295bfade3e5b109dbd370a713f8166eede8aa42754b532dcfebb2e1208ee064f", - "id": "a344917af836c95bd810bbdb119bfa4505d53e314f7d8064b947a95765acc668", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 190000000000, - "fee": 0, - "recipientId": "AbQgQntyhfSarnTh8cFN7o5voAyDvbyZFa", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bfd6cec5bf192f270babc3b0ba8435d592bdd369e3624c9c6c3b1a01b5f2b6820220191aa370fb41dc7e0f1c4ac848290419702e467f73dba7eb5e63f31028585fbe", - "id": "5174fb0eb29a8303387005b4518977b8386e811e228754863418ea5dae98953a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 191010602651, - "fee": 0, - "recipientId": "AQXAihgHkyTH2GaA5q7txsuGN8Wm1fRCsh", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f63d5da7c7ccdabaeee4c6d4d7a6a280feb33e4c0309291d9d32e689be6d83b6022013a07a743b78fb4a3fdc0c405279a72a1514baebb9dd166b9364c46eb934c3f5", - "id": "0b931be5bc7475ba3080a14a864bbde1f78e8dc0e9097c7d6541ac32990f55fc", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 192343079840, - "fee": 0, - "recipientId": "AJVL5Lh8fjMSrzDMTXxsBt8bjjEfMp6eFb", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bf093f56441607b038305a26408c77428d3f41f0be77f1ad8a990b8837902eb702203c3badb9b7c845cd4c84261403de3ea42e644f11cc5536878828ff3536e91c0c", - "id": "e47fbbeb7f8b522d4a60ae7731df51df96a687adbea76a3d216c8671106e9c81", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 194167513244, - "fee": 0, - "recipientId": "AR8J56ZfnBCzB5Lvx5VyMbjKnfvN1ue8KT", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fa1e878c673805908d682901f13bf1f0ce133822ba183e9c00425bcc1d43e8bd022043c553f199195ec42da2c5ffead7abcf0aa9046dcb8a5009f0b04d64f3611c8a", - "id": "2da708e273f79eae2ccfc37fb5ce48fa46f664f90715e7109b4b03d4f336820a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 195295588810, - "fee": 0, - "recipientId": "ARRpaRQ3t4LQdiJSN2ZMsuShCn4RqV6uq3", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202b4e543a3ac721ced2a4cde390f1b7e31d5efdc3d036d9e6a525c5a093ca7768022030c034a23ef72c1f335fb32fcc0e068ad14ddb0973302f8895f46060acac864c", - "id": "1cafd9c19b66bf9168a643075f84b1a78d80c909e2d6fcd13ebe51335ce45d83", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 196578368156, - "fee": 0, - "recipientId": "AXnet9aeWZ6SUKqHqHaZfCZNuTvQcdS9YR", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fde0862fac2154078b283cf6d5d95e9913b5b19ba708cc17f6287832216e54e502201f71dce1c39e215b9d5a3c971ff5a6e8ebbfa6fb79a7135ceddd490216fed391", - "id": "4a3e46a8378334a550bd54172491a19a8832ff1c4e909b68f54a9acd22fa9e0f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 197344240463, - "fee": 0, - "recipientId": "AV9rVNEKtxumKAuw7ZRui27Xqm5oDQTcGu", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fdadaef2d3b1c8eab520b7a5738db35eb35c224e2fdd0d215911454d74b09da302207d468b503cea3e7106c55f2b4e3081036c6ee71b0d22f3c7ef7c6ace38c2c1c5", - "id": "7a263c4f547c159bfeea9542f083060435453a72ef8251375930a6ce50788e35", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 199700000000, - "fee": 0, - "recipientId": "AGJDGWF7ZdcFg3py7fg5DXVLv7u835YEu4", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200d2557315c3ed28f6e9006adff24c8c7653482ae2c5a46536dffcd2de17187c7022021bb54089176340c71544b27388cd2836274b5fa4578af87a8bcebf8509160de", - "id": "73ee85253599b0a5c6795e002a0369b02caafe3d471e3ae50bf06257d9242ea6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 200000000000, - "fee": 0, - "recipientId": "AVQp4XzGX7xezNvAs7zK9THH52sHQ5cVMF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f4497eb3cf8dab8dcc034e86323ed133378045e5be031e8a0aa7e146df51185102205b4e2fd28d73d1c6b0363163f3d43f455e909c5d1e91ab351f1c54a0c1180c56", - "id": "ab4b8af4e3bba9782ddd6e1cf423572c08018b97c1edb458ead90e71d84196ef", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 201600000000, - "fee": 0, - "recipientId": "Aa5mSdp95Q3UYCfq14hyqoFAb17nbvwtce", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a859f753c037cf28dd0adf78f00b25edfb1824a361dacaa6e638c1a026072f4002202f3fc4d70a9a783cb18cdf15cbcb44152c33c4b1bd3258a8d40b080fb677b93c", - "id": "f8f09ffcd58da0f41474741a12846a993315794679c3689f683e2caef0222616", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 202607842389, - "fee": 0, - "recipientId": "AZ1X4tRmPioTYsF7M9G6LptiwUC3FiTcSR", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d687ac1127083d06605b4f903c9052cc4d0d4b32a6721e2bf5802d7363dba00e022072fa83d6653c638db9f2f011a61c7277e3feda10551d6455be11f7ea8282e625", - "id": "9fbf5160fb1d3e8c0b218e2332a1fd4ec0bac96c346fafcf8430cad2aa7dfbea", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 202700000000, - "fee": 0, - "recipientId": "Ab9yCa4xAXYPMhDVQn4pq9sPaNPf87oPn6", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203b4ad209e97f90c9042d145d904fb43b7415dee167bc23704e29d1b1e6f57a7602207cd6aa93c337139424cc160791262310216b131fe347e6261f29c30fc470991a", - "id": "f553a38a56e7591467c16af7ad8b3b33661f70cc6e7ef38b7193ca30d7433e80", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 203859765516, - "fee": 0, - "recipientId": "AdAVt1aKEhoTSpoThdzoBc551Pd8TvcnTK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200ee9a0e9d21b09cf8456a7af712aff5576bbb7487afec2277c6ce857ea32044202202c7c723a32c2eb349c27cc9b53aba8c41eee9c668e606e8d1f39a4f4c51725cc", - "id": "fc7712497be31254a03197aa586d121307aee0db828ea22ae5f64efafd4a7d2d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 205000000000, - "fee": 0, - "recipientId": "ARpbHrGteAtmcuq4ogVhDHGYa5bNX43vuc", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100be97fc5e92f8dbe342499e2dfeb861e2a3034d90db0ed46263c492c02821842602203c072ccfcfc88cf36e9e2bb05f4bb6bad6055c6b5f409d46a686e2594d92b2f7", - "id": "3fa38602a65a4cbd8289112ad9d2dcc38682688422da1399192e9becb3713419", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 206040822146, - "fee": 0, - "recipientId": "ANiKFTez41cCVTDCeQMGDah3CZcQE3QtqM", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b2d3c8ff809e06ce7f18e8578c098a05f17ef5725f5493455fbdafb0a18c64d50220278ebca469d1ec78d436a6876bf69caf7c43b61de58bf725768e20e5467b214f", - "id": "fb110b753cecae568ea684ea89219ecc435e4c0b36c4732f39429b96c4324d42", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 210000000000, - "fee": 0, - "recipientId": "AMEiCa1gAAMQkkNBLq6U8XU6eojD9NDLhK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100858d68292d61d3ee9765bd761c669b387362768926892d13e20de085dab00b34022063217ca008673f5d910d52e76172ae3d5cd89c5397722555fa02ef904046b3f0", - "id": "5e160385a766397e2e8073acf3c6e61ebc1e5531c051511e2305e1f9a99c9074", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 210742589495, - "fee": 0, - "recipientId": "ASa6Ptm7PRBFEabEA3xSAaTzkXAATsgWYH", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201f7e5d2a2b1eb413415147c5b0531be4acc9657a258b0642674a9222c9e111aa02200a6b12557e8b15c93363cbd2f16c5da5b347808b81ff0787b509a5df402bdcb2", - "id": "7e416a3161f32a1c8fc7a50ddaee0005af24af5d71634ed412a850b272773300", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 213596124973, - "fee": 0, - "recipientId": "AW2WdVA4pT25G6Mg4Y8v4zVzwMqf4aSzVY", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f483231b8fa37f29b442b23f5cde12bd0a3c5aa9685b0474c9d8b49e29bba09202202909c7cfd49d450ac8230e0830b501c30d4bde12daf911c0123c2986a6cd940b", - "id": "c40a53464e01b2d155f20f7bee18aeff16912e6d0bf62ce7af98242c7d17129c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 214366548708, - "fee": 0, - "recipientId": "AY4zDyuA2GRwYA19o9MfJWHkYX5y284Qef", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220433e6ed95528685139036657dda5bd0d00835bf887ff48b50f2ab8fee55c862f0220454511b151d642bf325c1b76e4f87c10e2edcd6888db2ae93b80dc002c64fc26", - "id": "d11284b0b9371d544a9164d028d01780bd65f8c855c147788bf9863670756898", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 218074902818, - "fee": 0, - "recipientId": "AcGLR6W5eVw7Z4nUNxAKAsPbff7W3g27LL", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022059ae53f97ead4bcffcd1918d7441d2af25e3e82b8177543cf2ed491506ea4452022040e8491b7acda16b0cbe26376c7c363c31e007fe6d530d0a7b59a2e0fa8bd2c2", - "id": "492487dd0ac90a5e56db964bf01399a2e68a6ae90ae7df983d28bb7a016fd3ce", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 218233858158, - "fee": 0, - "recipientId": "Ady9TkKQPteVBFnEgsB1jReC31ChZPQTrM", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022044026bad7aac5170ffb87b2fab2d9e042093d05707d14e8cecb33de925fd04b70220181f5fdf565a57905911e0de52b99de3fb686768d269bd825e660a7136e88baf", - "id": "fe4a120e7ab4c9029ce22ff565bb6a3b1c94b271d356fdfb5ebff146b521e983", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 220000000000, - "fee": 0, - "recipientId": "Ac4TAJqrvBq4ts5VZBhHdH1NG9t12T5iG3", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207afeefd18cfef28ca2d575ce09186d5e73dc9b26164ce2f236b3191c0f96279202202716438a52ea4a50fc87bcf18b2053506f19cbc8063c6115d878c5f6392effbe", - "id": "97b34b6492aacfd46d6cc9abc808fe0a24753ddb97bbf6f6fecb926097faf36b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 220522585745, - "fee": 0, - "recipientId": "AVQxWb8vskpGUtBHGyWtZSdM9gGK4oWGAr", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022029c9ca7067178a57095b53e99b9ef5bf510797b677eff8270dd269c44d52af190220773c8cd4647f9bd75fa8d67164990b3dd5c4e872564438d5125be7c08224858c", - "id": "8bf8a19bc12cd56778d9ceed54b0f85bf35d93074acfc79360c845b3afc49701", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 223213787338, - "fee": 0, - "recipientId": "AdYX3f5TQrDG1E59vtT67yfUkePMA4kdLs", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ca26254fdba9aff2aac672c8f81d6501c7ed05f2310408211593616c79dd1d7f02205601d25d8967f49a12d32334a65c2a5135bffa6b00ae0b8409c20012db15c977", - "id": "01729bd3f0c4cf81b3c5ee92c11104e3ba3bdaf5ca1cff75468b1909b0702c0d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 223400000000, - "fee": 0, - "recipientId": "AW1Hn4dANHJAbEYZeEjY72kHziwS82vgnU", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206554e7abc84d48a0446916b7dedfe2ca80b81ee68fac6a1306e20f21fff13277022069ce2552631634c4ea0d8715b01b78b2a6a5ae0ae27adedee6fa6e943845a33d", - "id": "489622fac2962052e7aebeee1e4a4b2474432b88164ee70863fc4597ccb0f27e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 224600000000, - "fee": 0, - "recipientId": "ASHHaaJMGmaBdgYzLBnCf3QRzmBfiCLwCZ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f52a3a95b11ff46babb1a698a31a2c15543cd1e4edc0222b7f6593bf9e1d4a8402207aca0fc68e3096119766e390f2d20d2e48d308520f2700a4a1a8400fc9472407", - "id": "83ac0a39f01b70bd420881285a9cc7b4a99fe52f1329da58561a10bb7f933c4e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 225135109888, - "fee": 0, - "recipientId": "AShwN32PRrdnpqBNruVJBba54zvBxQT1fZ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205855eb9048241c2c794ceb57f89f4cfe6c6e7db7b75819eb2d6b613ddf830f3102205e55a04f4d782a61e969ad9cfcb8926990001650282849122773fcc25efd49a8", - "id": "83093ca92b08910d1701846610d091403f0caa583c539c555c676b6a31fc62e0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 225186625152, - "fee": 0, - "recipientId": "AU8vV51b4PJZb9fWwHgVUrgz4Qq9ybsM6o", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202ceb7829288662087ca7db79218ddb0d576cbcd112fc065bc5121d2726f7195c02202deb812e71c09a4e21095b881ba24d55a7cc135cc9dd739e66b22149d669269b", - "id": "2cac15432180f5f1a0459c96a42fa93dad5626ac6f3abba1b79116c9c785734e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 225944551562, - "fee": 0, - "recipientId": "AMw99hBqtW5JzU69NFnqdHmP7hezSHEJwd", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202ecfd14378b25946c8f865df0a638300d26e6f82c4c557e47acc3ae5fdee98590220047e32bd8b1cefe6ac337acee1671d0c83b6c4966bab9de30615c36f5b78afdb", - "id": "c62a1fc478ea0e2bf3e784ed5a79e7073e62911ee9d078dbbbe888fbdb69f4ca", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 226088941627, - "fee": 0, - "recipientId": "AYijkoBCzzshgn3N89mw6tVHfFVDEoHhTj", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008a560bec818d7a518488d631663ef9db1051402d2e9f24b7e45ed008de328adb0220783be0fe7394775314a1ac063e286a47d8f0d5a03bedef6fba927a08f16991ee", - "id": "b5ee7b65892cbd18ce165ec3039e9f103163b4775c19774223c7a039706c7be1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 226432949161, - "fee": 0, - "recipientId": "AQg79cKKYsHB27WksW9k5Zj4gfCgd6y334", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201b6566d5b7fef4e562a28df76b1cf240b1ab6d8e404fceeb6a73e8eeac2c410e02206fc6d1f95cc2f73268bc8c8a16c018f1e3d8841b5d1689a0ab9a994eca88c35c", - "id": "7f3c2b6326ae39f356f638e7ae2a62c6743f9a0ef5d96bf5579d4ab633945d3e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 226920728780, - "fee": 0, - "recipientId": "ASvcE2iGk6NjtGzsCdhtyEmYTZq8vzA4ro", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d491f759be71c566db0c613b002abe362d5773c394c4c334b2be2c594339cf1202207af821eab08a548cf1ac494066c9f5c44f1e012a2f802388cda6dd1d3e929136", - "id": "d5f945780826ed328ed99545019e5a71b6758320802accc0e27063a882bc410a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 227205088343, - "fee": 0, - "recipientId": "AHAD2pRZH6sqxHbhGYjAWWBbrUryGgLXNb", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fda316ddaeb730b7d40cef2c6b67b1ab4309013e1fc2dfad9c7338cdbddd99d8022068dbecf8adbe1f3772ec3ada57f712d621c6dbc05586a7d07657f515ceaf8301", - "id": "c90e06437de6b21fefbc7ad2740019ed8a21d3d2f8cf4dec41c9ca50fecf9878", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 227326935581, - "fee": 0, - "recipientId": "AMzGgQHh5MHzFzpMV2RnP2ZEaDxh648gYr", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022100df2e8ea17c584cd5c2b98d8f482073b0283a0e08dfff592d802b136397f482ae021f3b7a55fa8f7e6cf81dc6366730081f9350c929a64c00d57b278e8e927a6639", - "id": "4ff0f93c6049358eabbc1fc17d2085b198fb2f853898c31e4e86897921bf0739", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 227395512019, - "fee": 0, - "recipientId": "AadNd7r4Sxm8nRqRXauh8e93JieVxHr6SE", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100cb986908cda1d4cde6fc3d3d1d677cd884b44cae1f8baf1ff29c60ce9ec230f302206c17455c3a4a0cbac75aaf7827e3a2b6e04904114b580e6b2e34e1830824afe0", - "id": "8198e7da0ccb9bdddc19850d2b12b7f142e5bae867e699865c6e07f380249de3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 228227511241, - "fee": 0, - "recipientId": "ASyPsMFeWujywouFojtZHAmk8MhB3ZYMyr", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008f58884d3e390ffce7e7dd713c9f3c0522a03f995ab5e78310ad81e490dcb80c02206ef0c5569957702d9260aa8b1ddfe7460737fbae69f7685cad4615e497b027e0", - "id": "76cb4cb4da799f5652caa816f58da0851a83281f7412938263b9d93535026bff", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 229694826743, - "fee": 0, - "recipientId": "AQejgdtXnkygMaN4iYGsceURNvrnGLN96D", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022043a8989908833bb50b5a74aa77ba1172f4126ba24fcfbb7502a8b3adbf7f9bf602205227d5352082b42dc8ff3a6971d5c04cae58f7c84f44b9804a42d7d424e076fd", - "id": "5f4e06198e65476fd625fe07b5876e9e9fc27cec66fe4e48faef7c89bd79fd57", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 231352203905, - "fee": 0, - "recipientId": "ALr1QRnTAnsQXCKVoc5nHjA78uVSDNGArL", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202b798a36b794fa8ec3a75ad0edea4d6376cd39df6c0d263025c4b09b606acad80220371266d010a2cfd2a22e5e46322647528cca9eec61f6e17aedbb64ee19854b3a", - "id": "cefa0126ac7ae644b539f9004f2cd71b5e4520af416d93e4541c5ca8a84f75ce", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 232283790318, - "fee": 0, - "recipientId": "AeZPqVdexqu7VGES4UMMGdnySdKTRapfPi", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022015972edfca96fef8811354524dad92eb10b6f891d80fd0daed0b692e60f65b2b022052ca9bcd9a6963826ca5ed308c0268c94a0136d236245c522859091d405d9599", - "id": "e36e0129a20398b9ec6d8190e2ef349e4e3ae61e0e1a09085f153523f57bc3a8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 234568404307, - "fee": 0, - "recipientId": "ATAva6NSSHp4tfH7N9toWikSqd6YYKBBLk", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c2dd0fc577d306894f4464c70fe39c83e85c80f31003102a5ed878f6095ec21702202c64b5a075465f7c3014c9255ce0cbdf593b2158d6cdbd8e91f1b44ce9e3803d", - "id": "7d7fe67913bdd7317ac7b930a9cb90e7649f05125ebdf1e8691e4ed32a482af9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 235000000000, - "fee": 0, - "recipientId": "ANMT9kqisqY2KKLx3VMokjLCynqMxQGUaS", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a8e13b798bd778badc34d9280e1d2ab9414c813f938384c0a86550e826cd8b8402205950252bc1efb41ff5b354dbff2ca5a8ebd5d4d815d9b5b3c39f2c787b881a48", - "id": "14b33a1754dd1dd5f243301dd176910255ab19ddaab5f6655f18790b9d2d8331", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 235931714746, - "fee": 0, - "recipientId": "ANpWLmDRAqDejcZ7GCdcKChmRGoMZCNJS6", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b99bd138ec399c08edd91e0219e9bd049ab7a80e32c813501d80442f436d2d19022056341b7bf689ddaf371c88ade20a7952815bdd784dc2ae64d30b2ad311e79e39", - "id": "6953ea52c0b42bd950b6a53eb1e0b4e6af5c13d0836a30288726a0cbbeb34f56", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 235942815916, - "fee": 0, - "recipientId": "AcqrcF2KJgjenxBxXoK5vtWo5ELqwfkbhX", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210094dd527f0c312b8ec349fa10aad1c7dfea7cd7dcc9c4c9196fc5a571c05d85cd0220595c9587b8bdefd34389e98cd78ef8b4bd9f1912700f0d3d713afbff6f5280f0", - "id": "6572a91980977673ed2d3c74edea33493e87d2855e060e3ee17a015e35331159", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 237605753191, - "fee": 0, - "recipientId": "APNyAxuJpws9bQ9agQN9J1hKE2kMoBpv1c", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ec9cc4a71bdfe43a491208b81c164c1a7dba990eaa9232235a6f26eac2bac7b002202ace026d4bceaab43df36c3a7dc38d859e00337e0a278550fc11d3f7ec2455d9", - "id": "45c45d469ad237b9eb967bf033998866e4671c955498a39bcc4ff652d7366e69", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 237874783089, - "fee": 0, - "recipientId": "AUn658PgseDk5iLkcAHwEMAYy7SLz7aByk", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022010c2a04ea0859438a7c8baec0c633a993afb4f2fa74b09fd3cdc36bd2a74cc36022074a2b5210fa27262bb40d9a5d6eaafcdd7791bb62bc678c3dec55d8a0e196b2d", - "id": "5809e7da60df4b7f9c8f359c859c43fae0d319b9ba0ba7e8dc2228d96cc8f651", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 238708836916, - "fee": 0, - "recipientId": "AS62XXxC49oGLwGiaFyMXCxCamuATfHSHo", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206e893e194882b8be5aa96582595ca69b55ea0ea9a7799789d0e3a79d003ea77c022034edefdf0793575f1f2139b925e7cd1e87c831ef0e2a11f923440ccbb62aec8e", - "id": "745488ac65b21596e11eb41d380bd595a4270fb691819b5c0437e3b9b0f5f52d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 239265443070, - "fee": 0, - "recipientId": "AUdHvFwv2fF2DBpYqbnh8fakj2Wc1HFDW9", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203fadb40ba73bed4b275b2dcf2d205b496404c65e896468fdb66946792fad929902201ce6b87ca0ca7013dc22177e6915c06fddabeaba1f818098b63cc0918c33c9cc", - "id": "7d94f1bdecf28119468bad192f7ed5ef12f67800246f071a312bd23e8d8f2d9e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 240950563055, - "fee": 0, - "recipientId": "ALE7xUp2tVw4XqoA9gK1d2UADbZ5XbNPv4", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f2d3dbadcc28cb3aaa43b29caa35493ed229e7feb032985420d6cdae580e8355022005ad1413bc7dde581e08d60a2faa4faabcb8cb1d4e822cbe23f47922e737d7de", - "id": "8b057bffb3bfa183445d801a220a07a2c66e7e4e1aadc440265615fec4336442", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 245331115580, - "fee": 0, - "recipientId": "ALRhEXxjx8RxjBMbFJMZfRPVKYgTeABue1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c10b2b4f807ffc5f82391479dfbe6d1e455e16f6adb0c150bc92200c8fcbd9a30220033b3a351010143a22272ca5b48d73180b209b9d9f2367fff8ccd5dd899e642c", - "id": "09dd0da5b96f36e27e7d67779d3cdf266ccc2efdbe5c9ab88c20b5082942d4b7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 245368130406, - "fee": 0, - "recipientId": "AR8zWTBs1pw55kwS3Wq58uvhhwPjtTYNyr", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220076a552745173a93750265a34cb80633d8d613dd72565420ccabe9d56f27c44002200509f4e62d51dd15d11fada0edf71e02af8397fe5e5bad521bf28bf9acf80655", - "id": "39c6bf781edd4da283148bb6f34d1152cee2fc98cec7464932a82bf380df151c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 250593911397, - "fee": 0, - "recipientId": "AGnxrUgmVmqqRNyo8nVVnFuSwiABeiMjXp", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a01fa1cd2c20a5012c88ed1a0756a1f643a62c78fcfc4f69a7c983e76932ebd202205ecc72be31c8732feee7ed2f79ada18b52b9d773f26fda89825d5364ffb0653a", - "id": "abe6f922e8ab5509fb29fbe11356a182a5b7feb4e91d0ffabe3969708b5d221c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 253760909613, - "fee": 0, - "recipientId": "AchFpiHbXRyCxc1ZY6z638QtmJnDi7hozK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008e32159083784acaf47a4a5bc2fae2f25b42f27ccfd4d1f0bb2e6263f902ae8b0220068d23c7a19fcf30456fe8117678b6f16d14765256e034764f7be575c53579a3", - "id": "99b8b895b70466c7984edfdf0f05dc35ad4b52b3a620bed624cb11fd68a588c0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 254454616560, - "fee": 0, - "recipientId": "AXt9rWvbGupykENfpxW88mnD9zdxTDvWXQ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022071ecea689811557ce917a64653771c2660ff29f9a5a903405a74f5f7e6884a0c022034acca7a4460d555b186d371be5c90de69ee6cd2e562b874fa37323dc9b7479a", - "id": "189b47b1a4ef03306276ad9e7afa6de0a26711fbdfb2bcb6527f679ca1afc192", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 254606167851, - "fee": 0, - "recipientId": "AaPyrCLTj12R3oc3iW2mWAwFBy6iu6VbwB", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b4927d09c6bbbc5e300e8e12107822350358939d92916affee3f5bd8147bf845022052635047282fc85c9fb4f115bd03f9fdfe42636668f1da00073827ee72835b4a", - "id": "bb4f8ad248ea6a382f7b492587bd4ac14bd1f06554261b9e2d187aac784930a6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 254687439215, - "fee": 0, - "recipientId": "ASddgAZdbm4jQAWaT1q6Y2zQWRqEkJFEX9", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e7ba05a49b3b569d7f55ba5dc0309db7d775c5f1b5ad068f426cecf7e6973bfd022073aa72abef5c92119acbd30f5434b066d2e9c3f68d0a8d44f61c6ac60d76d9a3", - "id": "3aed748d0979d80a807df6e530f480a05485d147925f971b26e7c7826e854fe0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 257637193659, - "fee": 0, - "recipientId": "ASsSVAcmoUukQd71zZdmXu6nXzmG3gsKVk", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100823c1f1dd6b34885aaa0e0ad4fb4ce4362343dd5a328bbf0d670261a6b90d4a802205a54410b7ec114c84c47ca4de5390e78411fb580f966a790266ef570425ee935", - "id": "00dc859d0b95f33615098b9026cecae7a86f1aa61dc2402725bf41ed589fb563", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 257700000000, - "fee": 0, - "recipientId": "AdeWwxiobp9H6WiabttYQp5H3kkNrHXQ4h", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022052cfe09b5968829e0742892f8bf957072e13528e293879aaecd0c0c5c37273b5022021eb5fb8c940be0b569c6c90921ce4a5e6e20df8662d50d94f05b03429317e66", - "id": "b55d6a965744191be648d34aed3eafea6346102c9c078ac7e6ad16b3aa9528da", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 258336704622, - "fee": 0, - "recipientId": "AZeA4DJAW5qBbSdGavdC1CikwiW5usc19G", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3043021f791bd82b7e0414daa7a8a9567d935795438cf7e23c4ce95ec0c634ddf8bea102205eda1bf6bbb5f7f374460fefca7e8001e4a37d1d2615e776c347a3094ecb88a7", - "id": "5ab82d277db94a82df64931ca7c39d4708fe488152263dd47abc95f889fea4c3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 263400000000, - "fee": 0, - "recipientId": "AQ1qa5TMsSF4RWU6fBZCQCFxcEpzmtkGEM", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204fea12280fe69879e6dcd4eb86c2590293a9647627db7709ac6848690e3d9c3602201fd4810e4209edae71a9dcbd3b58daa95ee072bff610b035a8c642770d70de4b", - "id": "0985c36709c6452d9520cc9a2742c2388949c62c811f51add27bb59e0092f144", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 263517383725, - "fee": 0, - "recipientId": "AVLiBtEcZGEyUzME7mkjwn9whygssd6iio", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210083285814a6b94481dfac21296003cc4f343d4d7c8bc7a7a0e5345d4aa48eac1902207915c6bedd8a3a10812da78de0368acae101cbe093685d18fdcddc98b76053d7", - "id": "af50dd79f9d422fcbd301e204ba6a1e27f7b52bad2ef1183ff20e434049c2ea3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 264706758253, - "fee": 0, - "recipientId": "ANTb8pDUmSQHBQK47P3JSU5pTWYDnAuGGW", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204175f6fc6e5b7e7b453dc121a8af4df6dfc4c55028d11c3ddf71b9d81f4e7e0402207723e4e6a7ca838f645fbab78af16c493d384a06e70b32909fc4cf915817d5e3", - "id": "0ed931759cb9de29db6d266868b504864389c2d1cf3a81f5d4a5efa54ae82a7a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 266600000000, - "fee": 0, - "recipientId": "AYpgKvx4aBst2hD5gaRYv21FtiELyaehMa", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210095cfda172969672c538a02e9befc1343f837d4fb1f3bb9570312f03e2dbfafd602202297f4fd1f28d1c63614a75d47c4cbf3f60ec5f77eadf12769d4de7a39290277", - "id": "2ad61362f35c9510c43e14c3968ae3ab8a1f0b1ebe69dbba2b0bdb094805d55a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 266600000000, - "fee": 0, - "recipientId": "AFsy81B8AvNKkxpneNm4WaPXsXezYLRVwV", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220632892e9de2b0f3e915e2af3a869e834b065a33d239888e4d9cc86068e9a8d9202201cf798edd4f1785b50425d3cf0e85b2cf41117479e4d0d354a00908ac809d711", - "id": "2e7d5d559b6bac5a49af20ad2e745daff3651fcd782d3a681e1935c2fec8793a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 267156888910, - "fee": 0, - "recipientId": "AWxBnbH6FrttJsempdhhoNqzzdynnue2Kr", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204ba5295df2c4e3d4b1c098c7ecdedc98ec316f115399ecca37ac08606010a4bc022057819841af1ce0f3c979c9b3ef0f6de3493e21b10040dab878cf753849c25b0a", - "id": "0caf79acb11be4d8712ca28485afcee25efc113aea6eee515d490a9235608e28", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 268215057163, - "fee": 0, - "recipientId": "AKU81Sra3nx1y9FcCJYxuuHQjZ81rjHavg", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022020e417bdf510dbae5780ddc943a9b2fbdc1a79d2225a3e491a223f3e6a8fda2f022021b6e95132010bfd534af5d9404295e9ab497f6f00798ee15d7a6b5ef49257b1", - "id": "fc18ebde76c5d15ef00d628b4e987f77c4ec85d4fe24dfb56d7c3a20fd8abe27", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 269700000000, - "fee": 0, - "recipientId": "AXHDNRmJ36baawXLcS8ipDHgrunCeRN471", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220580727096e40273d96f9f2c52109eb4810c2cc10797bc59228f1abb8fb42af22022062043d9bef15d4e1239a42735bc3cccf08a15c6b1e51058d3c0ea590418e269e", - "id": "fe60d828232ea75e7df9a6008cb7b1b3ad2e5d8e72f638350f6f40420b36bec5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 271217304301, - "fee": 0, - "recipientId": "AegyGSE9AKWNiYeiBPA7p7f5MDBbsQobNw", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220587cb825e6b40631e4b8262b05b170e219c6a42c723ba1fed4f6cc6b472474120220784a7ff6e72b544cd8807dfdabb9f00f3782322bef851bba6995ff4b2b826710", - "id": "fb0dfdcec943df0624049563fc6c04fdbdfe413bbeef620e7a27ac06af71f0bf", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 273312359613, - "fee": 0, - "recipientId": "AdG4rV1nutJ2VmGfKDvH1usesnFgxdokd1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220367368621c5adcf56ee4cda123934cc5fc9ac54332b0bcaeaa0b413a0d65a71402201971cf564a643d39c915a8328da5d3cfa36901b373ad69b8a72a21345946948a", - "id": "5a71a47bab2f7cab9e01e4694cfbfb67b77c411d4488555101f187dc88ca3372", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 275823348504, - "fee": 0, - "recipientId": "AQHWx36dxxzBjq7t4HD7sCMtBMBSjCBceS", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022020167d72b49f9c4b420066171449eedbd8313fcd5384f06881d19c1648c1b4d902205d0d2bbf805276426751a1a45daf52516d058a1776f43963c583cece69fe20c6", - "id": "a5b96fda073ea900e23c4c85cee5b79f0ebead350d4353d1697874abeb377ec4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 275919189541, - "fee": 0, - "recipientId": "AdmQcPaXYhe7cJcD9C1Ghk6BKbfS9VqFQq", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008d577572afc4ed0362d6ec5c4a5c9b31abba6d2267c51a69c7d5f7349f8760b902203e73555f377d34d71c77786defe682711866f02f4be29787552bc25e1a277082", - "id": "99e450025bb676e306e237e05dde8a6f2d49913794ed927d9699c78a5e363409", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 279247094098, - "fee": 0, - "recipientId": "AT5ANRumUi7WBhCSVs35yfkbUg186x96fe", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100967b5bfbba242e8efe50f90f6880afe77a1e065e471a7885060af61baed6736d02202dfc5b008abb3466e78cc24315b51af9058ed24e2f77eb6e57c80b3a85ce250e", - "id": "ca2358076d0c35255bc017cc7a85023f41551867e9ef28287ff6d5da2d50833e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 280000000000, - "fee": 0, - "recipientId": "AQN9znPRTre9TCbGYmriMAc8Z2m8H8xrL1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022032247fec35acb98d299336331d44cd915e19b15857c36ed9f5bbe6dca7c2d9ea0220350197b5c1a25d9e0dad1d00d223596aff07e8a0da480661a19e27342a26dee3", - "id": "fa5d6719a86996e3661842604a1448e78e5b9d6a91e6c137a4ca6af765b9ca19", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 280803102800, - "fee": 0, - "recipientId": "ANfCguzStf32kWL6HueDisbWajTw8acRWs", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022014dc24aa9c13fa450a824e24a25ffb06005807c86b04aad84c4fd502e257647f02206479aaebb822b9c1e0ebc8d28acb759c7e5b09a3eaaadd48409dcca0c7045c0c", - "id": "08fc563e818b09004b5ce2e4c7c5eed2890146f62deebcafec6d5a2e4ad7f0bd", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 280988036090, - "fee": 0, - "recipientId": "AQypxoNmusR1UsP1u69SFbjb3R9YH7mszw", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d63d05d551bd6c9610dad7ecec2332de5675540eaa9c7989a00b4622d238ded3022071a021dbd0a3e69cf9cdc1ac0d949f803cb5137e08ddf476fe606a774f6f1127", - "id": "52cb8cbb5a802ca5d4f20438a0cf60ce155421404f9b8184952571a2382745c4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 281336687456, - "fee": 0, - "recipientId": "AZx5ahPCDgGR7tdGeZUTv6oV4vfmKvPeJs", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201f040daf2812c5bca3ca29ea83bf0d24212707eec898547a76d6f00a114d1c330220179b38aa34cde2de80c519e0ac5beb23be3e14389d5e12b530fcc8769779f9ea", - "id": "b3e6422692e6b305050f7dcc3d23c3ea9efead66f14327c7ed41f02e1471039f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 289471840817, - "fee": 0, - "recipientId": "ALApc6GgN5sAA5MdRfws9MS3PxcP14Vk3i", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220284ba45d70676f0e34c9f8401d06c0d5807a6e84d021ebb52266730491ac5c0d022014d14816d1e86ca5568ea8c78e54def350ab3817f4fefe4404362042cdd12ef6", - "id": "899123718c97ccfc2af2a5bc4b50d97012d46c2d1403c60b51d54ae30e3e7da0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 290500000000, - "fee": 0, - "recipientId": "AVd1HMGBkguKeBduZoN7JD25mVv91G3diR", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220623a9ce997e94680b7fda4412921890ddb91de2dc833c76331026df41d4bc93f0220159e4f175d888472f3eb4318344b7fbde4140cac5b43dade415445a5c848c24e", - "id": "b2fd759ed4885ab57848dcb86ccc2026f47bae0cac4fe119183550435dc21d90", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 293042430719, - "fee": 0, - "recipientId": "ATdJXNdCCBdPaD9pTzZE1uHubNd4EixaoA", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220333ccf40d7686d7ede92e901701f2e4fb65206b40bf1f71abc85815e6eecce1e02204c30a9f447bb751fa78c08ac0eea0dd9bf22950cef4a8ae85ae101970a963362", - "id": "69d303c8dbbec71c451201ea88e64455da3260bcf9eb866a784b13128ae3e6d6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 294030114327, - "fee": 0, - "recipientId": "AaobgufVyjG5QYYUCHfdD62yqfKnheoiK7", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207badea2403c148d9573e4b8551e91ff258250cc339647cfc7c09492c64d57602022058a84d0ca29a3e2a9d469bb5e31460c3fc55a968a296337dda894b1f324a3406", - "id": "e1fc93b662aa17857ec8282b16af5accb50862272ac7e804c9a599072e13a7b4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 297800000000, - "fee": 0, - "recipientId": "AavHyFGe2JQE9ZwkWVHZeZF4otbj8q8aKp", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210092332a9a11c56d75f25b407f22b3b910d469d888b30ddeb12ab843b37f5eccb2022008048a684ee0765e703fff4452d81e85eee5060c7730ce7959e66ca4c442414d", - "id": "e908984cbe697b713493d9e3f6a8e928932c5352d5f3eb262d7840cc04e8741e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 301300000000, - "fee": 0, - "recipientId": "AMwtequWhiTWPMH7R4PUf6wzEUVoQf41sE", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220284be66d3840c7c5a748d244c17b9255e2977142e43263139e36d89e1b204d9502206d9ebc18bd3f19ce852d2b4e8371baed5853cfe7a57d550fb89332f3cebed29d", - "id": "caa7b788cd2a9c62b6f83c5e549fdfecaafd76dc531f1f29689cf3490bcbdbc2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 301947752203, - "fee": 0, - "recipientId": "ATFJPWAy75j6jXHzYPCG1JbWYMm6XvAdiK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b5fb6d53b4dd98f55f120f556a6bb569291942b574a0977ffb82009a8422d8cd02205203575ca53fc5636a40e6f931e429ff63e2bb55931177732a04a0ce0431d431", - "id": "6f0fd1a8e7236b0e2ea0353de6aace7a1a9e52b2268a2e21a7d70ba50df685b8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 302851017757, - "fee": 0, - "recipientId": "AGrNFNUrFmtbDEtGmdmcXY4PmdcGt52xtD", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207085b431cd0a8ef985dc12b3b01b7e45ad8b3c0aade162cc21054fda777114b202205df8eb5b1e36a571083b0c89af506f917d5bb80859852a0435fa7af5ecef1994", - "id": "640cabc266bc124bb7575b0d0d54826bb4298f6dcf86728552a7c85cfb9d5c42", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 304323259134, - "fee": 0, - "recipientId": "AcmxYd8QHTF89awqRBCtJFsC1aPvwfnLqx", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c4a2d7ce45f32daeefaadb4b12d0f3022f207f68019a82d76fa41fae5f50461d022015bed2ebd307241f334322ac81eacac8b517a6b4942ee9dfa866ccc85cd3c4be", - "id": "88585b0976182c15633b99af07e08efd91f2e2ade96f6bcac9a87289b7b2f7bb", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 304700000000, - "fee": 0, - "recipientId": "AcjeSyWdbJbP7Y4xXZpPuGLuNtTL1EVmKx", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022040412540fd1740a5bf25c9edee8e55b8697f4862b476d93c57b7397b5265c14e0220394c3e19b2a9ffff36ff404e4ac60a77b695cd3641bdb119a0fe2d9a74ca5b19", - "id": "09b69e99fa28ffce86e2333899a9053e7cb915d4a6eef962c9b19fce83c42229", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 308731620043, - "fee": 0, - "recipientId": "ATXdja19fpLfnjPbpuusYqtSs2xGK1yYy7", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f16422eb334f756c50c1af8e4df9897a0341dd5b222fb7e24838978006d1b0b802206eb59fb05242d6208812e4601aeb2a0a64408ac03aa42ccfd6e4d2176cab3821", - "id": "71036c6537b211efc0da3ded963361c03c47b5002a0729cb8bd94c9171514900", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 309164632390, - "fee": 0, - "recipientId": "ARftmK1C8xzd6QJk4xNvvnLUHNNHr5oWD2", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d9ae4512d715c87e98370acc74818cb15f4a3575eadbe764e15a6aa4beff9ec10220042de90d3d51992d850581e2f717c40d7e00d85a07d5418856b8b4d28a6da8ed", - "id": "aeff6d588a242455a1226b253a08412327b1936cb47d911cffc5b0e1d4e01876", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 313315057164, - "fee": 0, - "recipientId": "AVziGRo9W9A4ngqrkof2wpHyrDDeBDiKiN", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022077e882d387450d847cc5ccf18042ef7c0c9a15b9f01ed523ab2dd90f3debec66022000b0525cdb09ce49c7ffb1588002e56e20f95798e2ed979e7f34cfb7c95aea6c", - "id": "c9fc04b7d64a5f6cc60fade0d91d344ecd925187f0e2b5591cb1e0748c293508", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 315100000000, - "fee": 0, - "recipientId": "ANPQroio7mrAH6hSrETf9nhoBvhz9UfrnY", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022004011cfce82a2b867330e93ac537230402fae45ebd6c28029c20a9a179fd588e02203c1d4518c47a0f86f33d93ddd32f6f97d297c74ce4a3a22464695aa14e144311", - "id": "b50c52e231192da85a6249b53545fcc91f17e03db263ba9ba9830f6e2198a36b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 323400000000, - "fee": 0, - "recipientId": "AbnhZAGRAWZQNfj3NSwWacXCcXsoDg1UDZ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022043e245c68837fc34dbf6a6f1806a5ded6a173f97a935aed9fe532ec5df5a045402202f5a8bb2ae72c7995e2fcaf1acbb9615b2d0f3567b2bd9db01af6e02c33c1a96", - "id": "5475983134ca085d94e93c8ad7847d8ec2d124c0606e7f65dc7360582927e28b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 333412838852, - "fee": 0, - "recipientId": "AHFC64BTXc4kKdmn2jw8Tb8487uxsbK65j", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f1d1ec1e2e7af629ab53f68441a83f4107ff97e7d21ae0db068cff630a2ad887022030152a4a06f8a0d390d7957995be0a5134f1fbf58693f4ccfb4815abefd9ba45", - "id": "178b974b999420ffc3f16c4b96a2886660174655e77f83e1277d1cc139c42445", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 334419127093, - "fee": 0, - "recipientId": "AbEtCb6JAawzHj3iDF1CnEBWpjdShoRes7", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100934d8a30fa7fad31b7e59d5befa251d41a9b476f381394569b6c670388b88ea702201789741ebd7614767df9f43b73a8ebc1ff546e9747ffd22cb1c2f7060891622f", - "id": "5889c1d56044d500f6abd85ba7e02e2fd69dae5740a88a6321acbf5620f0ed6b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 338228944448, - "fee": 0, - "recipientId": "ANm2X2bmWfVavS68Wf6zmisgvyKzmsYWEk", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100dad3e59166de998a91e9439ee044e318dd82040d8c20ab971d6ba12fe5057384022048f7c14d431001b88b2ce5abecba8fec190d38a1dad0886b1213c2bfe649b96f", - "id": "3255e17ccac83b9df7d9d34bd09ae2a6b6664423c632046b5ace6267e591fa0f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 338641901001, - "fee": 0, - "recipientId": "AR9nWb2Y4rZzrsCTpHCDBsjeTB2cSqxgLr", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100842b25c4dcaa3f85e3cffcb5412650e3d16f65600c6f76b3ffda4d3064649ce802204b6e0ff006c7045ec16bc1f2ddec0c778a192bf5f65203f1c9330dbd25e0317e", - "id": "11d040dd27672f77a6dd8088f2a40c0d095d714ff505582422eb9c280f65ca7f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 339243683819, - "fee": 0, - "recipientId": "APkQyY3hE8TTfDdYFxVX9BXfAhKvgLMFFC", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008d5444e7ab774e40da05ae30917882abef70cd94d28114658515c122bd33b32c022022e8c524d76b82f9241dcb8d49759dcb8e11efdbb2bc8f41ad1340b0ace8ef3e", - "id": "05c45393976a5e5daa2ad7386d12587d3f8096e6e99875b8aad24b6b87fec4c2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 340662911557, - "fee": 0, - "recipientId": "ATynVRNrhhRBWx9xLuwzqRMatJEebrNag5", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ab90a2e21ac0cb5269f1e3f662e29d54fba5e57b867fbe8c716a2212c9c2fa06022032daff32ee5c0167d8f1716462077882e411473116cbd3a23289832ddc414a6d", - "id": "863339c27a73d2b35fb206638b23c1048f35854c4d8ef4e0d2c2c9f664a38be3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 340892990675, - "fee": 0, - "recipientId": "AdDGwzuSnwbTgS5zCn9G2tjgbRSVwtwXgq", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205c4da9c7896032a24fb33f087f28cbd4ee68a1af5f242ad4cc246ce189c57fc70220754cda9f04dddd1db8a420374a314b3398d2b20c8a13ca69490c9c48af950917", - "id": "6b0fd1ad749e8923efea2a678bd5fa503cad05c1290b85d87336a46641148a1d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 341600000000, - "fee": 0, - "recipientId": "AZXZtF8s1rtY2x7qZ4fup9iXsGdwLgCy5o", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022029cb43b5e7381c8692a5e474af506b4443735f56198d2c03d8bac38edbfc46de0220736670e57d73f4d8ee09c27ddf435b1ddbe206fde50b3cf2362115b11ea8da9f", - "id": "79bd2a6949472941f09c7872534dd18cc5cc7d55d8ce9276f0859cc872f1f56f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 342836625893, - "fee": 0, - "recipientId": "AbSRLLJkexPZCWdhznU1YnBs6UmsvAsg8R", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220615218597c85309c9eb5db6cd1d0657998553494a8ceb6140102d9d6955bcd4f02205264ce22077d58684a39bec2dd60d2b90780ebcef424fa29720244d19907dc6b", - "id": "ebce164ccef6fb06922b26611a1d4c755ac5393812f1b6e6725f5a09eacf9607", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 348484491500, - "fee": 0, - "recipientId": "AVv22GuhDy6QAq5EbWmvqPpyNK327dDKpX", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210091c2b8ace2dc7f2d4bbda72cb5d52cae4b6413f823dfb4722d7a6b78d93d07df022046b2f87bdc34fb337175fbaa95d17ecd967a2b3bf2e158759b50edc1be0ab51c", - "id": "74c1b29f70e726c862c8db61964d259aabdc5fc1e5ec4a14caae3338c474b092", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 349954995610, - "fee": 0, - "recipientId": "AZKSX4x6Wrun1sKFcm5tUMQTyEDKHSGvEt", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204db55e406f565efc50329b609813b5361fa8829413c65dfbdb81eb62d45d4dc702206eef0b82a59c4ae156d53b5174d667b2239934b2f4ce6e3a433ffcf133dfac8d", - "id": "b4f3fff5b12a05df38f253328d0212e80e1b86f9f783d1845676a395fb46b72a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 350000000000, - "fee": 0, - "recipientId": "AXUuekRSD7Z8aAqRz5oEra8tjAhn5Jvvrr", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100875b2ed22d8bc7cef1683d8aa8b5f5bf4696859e2770afc120fc5fb9fcaea1c702202771caf3c8ca619fa15f339dfe2c1106d066804689421836b19d9a5d1fdb5cd9", - "id": "15a1e1313a9a1df3686417a2eed1cae978664b8413e79fea6b38925a286b4411", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 350000000000, - "fee": 0, - "recipientId": "AWnx5rbjMA3BNCXQ2JDXw9jjtdJtqG9gvU", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220723a306d9e64fb999de916be02dd0a12c45a33da542fb93eb5be5a73a39c7df0022063a52580d99354654e80fcd43440c0df1dd932c939eb672308bfa4ea705ff901", - "id": "6a717fdc8c6b1052bc7f70a2a18ffa4bf14f78b8d252825c90424d920f13a24f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 350918258935, - "fee": 0, - "recipientId": "AGUqJi6qb2E4F51wdxxdtomHGdTgDMYhkm", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a2ed646787d44f8f362242ce96426f32fa938967c409f3d1f748b713aab0a2c9022019adfe63d28f5b634ef60f843021e42514baa766c465a45840d73bedc00001f5", - "id": "a4b4f4cb2902f4d9a0489fe35641c04bf8911b064d9cd29b80cb0a1274ed64d5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 351177075272, - "fee": 0, - "recipientId": "AMQLH7k26GetQAX3q4shwWaDooL9cDcW9F", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e6ec5c6fcc7b88f4325f2e449d139e6889e8eea625463b9cb120b8921c26a902022049986b8ca2d95d0f3b461601b3411cc7e694271a2496ce84088899242a9212d4", - "id": "6f0898653b2de3e94b56625e6d6248bfe50f2e120b0a6425a311f6a47e7a3b26", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 352101061907, - "fee": 0, - "recipientId": "AKGkMr8fTn7d5Wcu3AAyjbVM338JWubiHm", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207b22f8fc7e97f5669baeefa2e72d9d30dce95e4ece077da817c391460050de28022045ecc963840f840ebf144a32ca3f47827a3f8665ccca7653a718d273a29c6a52", - "id": "35b48f395bb80488b5b9542206cac08dc7bb6e1c4ac2f07fceeaa028a615b2df", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 353468821454, - "fee": 0, - "recipientId": "ALkHY1xQK74faXu6C3Pejk1xSuAFqCFdhz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022077fe72a93d8d27611229b8f74ec4dcdd4fce7c4c3379d44237814ed7e5b81bf00220679f8baa1992972284470f22d085907bcaf41b5cbdeaf9ae886be71187e38c0a", - "id": "ac919bd687999aa984250568b6c43e8cedd7d1a639aed32a816f8e1588cd901a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 356573668788, - "fee": 0, - "recipientId": "AaEfJQu8y267hp5fQSyJtzR4iJSor7q5xH", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f101ca42eae873277eae594351e634addb1ecd245c28fab5a3b6ad97add0800f02204a262f763608d06ae684f5147b1ccf82bc9538bd82937de72cd1d8593c9e9804", - "id": "51f08208e82c1ea12099b608bc6cb3fdec272ae383c10cc18cca09d3a6906ad9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 357292915405, - "fee": 0, - "recipientId": "Ab7CQLYGsWUEwHRY86hEPRXaABtMRfj4vp", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022048b11cb528691c8397aa0c8b01d4824ab9ab37b2ffbfd5c93ea7632be5414abf02205d868c12d097942225f488ae41bb9a0b83fb7e12fe163eabbec26a50a5751d17", - "id": "cbb6e663289d0af21a30738d8456ad4bf347fc60052eca55937598ddcd1f8127", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 357640325222, - "fee": 0, - "recipientId": "AQzZvHDiVUfrPTs3PhNi6xXaCmWwPJ9rW3", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c6744c0afeb8b450b915b4131c6a5318073f20cc7828f31f05b5fe7a51d40413022075e0848a80f03df23205e31438c4f7fdb16aa36060e87838f5ea4098ae5e92c5", - "id": "aae37ca7a805749e82bc2ccb0f812c0c80c5731d4b0258a6a44b394985f4e213", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 359722188329, - "fee": 0, - "recipientId": "AH45YgpupuJgJaKfNqpxrbusmyxxzAsSiZ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205685e59ae6d20fe9242cf689a3a6d7741215b7d473405716b61eaa15c4ae156f02201251a4a0cb27f4f5501c5b5feb891c730a795620d7092d7906b4d853e830ffaf", - "id": "bcf73615f4b48a7ea0a5570da0b75b6edbe053808301a5d3c0465da8a1c8a992", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 361053804649, - "fee": 0, - "recipientId": "ALGxVP1rkTWWUpEZyhTvzT8YJprKADx6jA", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100850f4683c8641e83b64d7076b43922e70569769b224813ab0513c88e51470eb302200ba35068526155c1413020bf4c18f2b4b8cc32f5e16877a9e10aa32dae1907cd", - "id": "afd3ee65410f9d76132c8cecbc796bfd54a530327ba33af33dc0e9a07760c1fd", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 363315727061, - "fee": 0, - "recipientId": "AMQ8ezFU9gvYfyxxk7VETnXmK6kpt1Looc", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201689314194023cd9e64eef30f0051279da6253ab7b487de47d5ce3b81487f2830220148ffa133c7bca9cf6d1d743105be8b3912af8426d7e0309b969c2e2d6334674", - "id": "a9bcd03694c114a041220271fd5f01274c7ae365a6b0f85d89b364a104a20e29", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 364303311651, - "fee": 0, - "recipientId": "Aeg4n5zYvShKUmhuXe7CmzvEJqFBTv3jVs", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100899497a5fba8b821e22acd35d5027771d09baf69899acf626c47d63ee4ac74fe0220481eab02c6e999522954a7da743125b5162b56620a93d6132195675464f64c8d", - "id": "e7ac9b3742d019488f8d93c3418e19fa8bf556bc232ff0af88cc300e631329ca", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 364467189703, - "fee": 0, - "recipientId": "AUSKfmvgiPu3LSAfY6Kfyo4zR1VgL2twcL", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022055af734911a3bfe58f44351b3ae7c15653ea3738911cb6d9872c62471eb3716d022032d62b6716655c8a6e9109fada3a82a3b4a20809ab6c0166ee79327529e92ac0", - "id": "635946b171c00b3f9c0983e4a43582187bd2a3016349a6760732e434bdfcd697", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 365586352150, - "fee": 0, - "recipientId": "ANV7tExbU5ZgSDJGYyDtDmfg3Z8SVZ6rvf", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220493d2343b10c305a73614abd14c038561f4ae878c6d9957a171c0616f149187f022042548385875006c2cb45faaf5322f455b178d256075a007913ac11be84fd68b7", - "id": "ebba2ab5afa6f21355ca4f77d4b79e55d08b88c0b8b35afb69749ac61f0b031d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 366287950968, - "fee": 0, - "recipientId": "AcM4tvSTkSHsvjLM2MtBEkMX53ErPpFEbo", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207f9c913da5d2500b202003576ef1c2f478ee3218ac7d90ec443b94b64e96012f0220537e1b8af0d427a41400ba6008a463d00006c5f722bbc6b65e1d83d524863ec4", - "id": "a6fa7609921c11ea4658cea3ee0626f1a87d0ae21324780ce17721ed0d21f539", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 369653766955, - "fee": 0, - "recipientId": "AMfKrnGPBwckMBZff3rR4wFGfeztHUXEXk", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202e4e5c658ba2857c9b0d56b5babd7bcb848bece7b38820f4407827132032d59d0220713d1203f84e049caebf326b1eb3424b91643f407ee34494d1614ecc7b06c7dd", - "id": "ac8c772db29278a0bd50fa5e4b0a016fa8bb80b35b20bbb538ead6f53218cfff", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 373149442409, - "fee": 0, - "recipientId": "AVHXn563x7mzNDVN32MCf8Jngc7UKgGoY2", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022009b8fc1be1c3fde84bf3099e79c2356bea23ae042a0f76bb1d07b34fd4e90b410220704e5c4ced7376c4c7135004c0f07ae545ea1b5a0e72355caf0aae0150c865af", - "id": "36ab40ef3b10937488dd8593e7726d4c965a5baa8b2839f06620cd6392c146aa", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 373746668966, - "fee": 0, - "recipientId": "AUc2gugBPs87YHajno9jVS2niPspZLydtb", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d83d24ca3f90fbf5791169619436bc87c015f58140ea0c72a4c68df3996ea50c02201a061ded755126c92713e464bdcfd652cd4f7530aa1c5a56f1c56e366b388236", - "id": "f46f2c84bfb139e6bd22eaafd4b8c18a0547635dc798b91a3bc40567ddedb41a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 373774406219, - "fee": 0, - "recipientId": "AasYFcLgSztcGK66vSat1rgHSpFSxbgMML", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100dd07f53bb0e76df7d4dae83990242fb5db90a9c53c1d93855620b03eacf5d95902207957e7389be539c14ff9a17b9b0cd4418ebb14e80a74e3e56b555062e209e333", - "id": "19b48281b3104b5c29a0858d05fb08e8c324437fbeba15fdf68756842b3434e2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 374988724940, - "fee": 0, - "recipientId": "AbNopZKDikntAR9jguVZBChPeVmrp363ey", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220238b5a7da45bc180902e8b2c49d8552306a721f6a7f7f435eea329e0fb6952af022028e04e87bb64fb457a3cde60771481e71aec7fc8c2522cb05918e6677242ef67", - "id": "3e5fa869674d84582bf5a2cd3a2d18a52af64b80c188ddc9b15d6dc4b129c67a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 395360944311, - "fee": 0, - "recipientId": "Abk1pu25HhKdUVPwwkGqp1zqhYMTHEU2bR", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008e3454257b6c3f1b6bcaf812b5f3551ecaf4cf7118fca5367d4cdd4155208650022038add348b1a48125de8a37d8724e497e140b69234cff0a3923e70fc305c8d58a", - "id": "e0aec5537bfbb10983ad3268b4059a8b08fecd76a16db4a871d76e3f9d82c6a8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 396243735944, - "fee": 0, - "recipientId": "AFrHJuVb2HT4c4tWqYEvWgiY6CuymhkNFs", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ef46b5c739f79696c0b0a9a8f735cec91a0dc546accc233ff32f7e5cbf38b40902200dfdc24247a4f60f3e791fba193d9e9d16d83131184028e248a50ea4cb2a7272", - "id": "f2205ba50118bfce10259a754db70528268ee9112d16da6e2bd7dac30d06418c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 396752660012, - "fee": 0, - "recipientId": "AdWC72U5LSwghdenW8DKXWHYuULfUDrCP3", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008a55e63ad2b9b3d0baa2b6a2f80606f38d02abaac071387077ccba86f315a80c022013c555cafa7277020701df0004f2b695e34253ed7cbcd6f2a9e0e66593390577", - "id": "61c5f683b3a21404863121a1833c43e94f7f97acfc4100198f24e8dce6e5fd7a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 398860671270, - "fee": 0, - "recipientId": "AHV54m6LqZG8A1YdPKzvJdBfdoV48VzNmt", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220281ea4f34930789c6890fcae28d56ae12d1877038a112355f45a8586a7f7108a02204b0ceb0a989c686b91d84d42cbb5c8de0657c2988d9acfda7911f6fb37a4d0d4", - "id": "8aa2f2aec071acb8418bea239baeabcf9e22a4ec3059777df5f2f379f0dde30d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 399900000000, - "fee": 0, - "recipientId": "AMDpfwcmvCFW1jZYJz5Vk16pytx8zwqf4M", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202512aac371fad98ace991a2c0b3c736cb12b18da8206f5308dab02b71043c1df022031d2c0fee93eadf08de6f04abbd6795f15467d0d9264bd22c67bcaab93a40307", - "id": "9147058ff25475d0975386208f9d0d4cc8c60bbdf77737a09dde4bc168638db9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 406030861383, - "fee": 0, - "recipientId": "Ad8LXbyPdTSYMHvcCZiQzaUtsd8VkLRdUk", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201df9dcb7dfcd79218826fc4777d0216f5020377bf2c1ab94e3818f17a03a6ec3022052324948abd06924e123c5e5042f9b388bcfd0f127b962f4450b166207f6b4d8", - "id": "7dd69f0c7424458dc39d3500fda4e8b1e5faebb079d6917f908c88192e03602c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 410054755558, - "fee": 0, - "recipientId": "AejLSizWdJowzJq3o2DZstQmyZNdqGGzJK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b53a8ca2840af4fafdfd607ba7a2673d1a3a70c4c3001cdfd03998e1d85049bb02206d11131b4410a8214be0bb9035664caa24e5ac6788ad7201442e151ca4e75b2e", - "id": "84fd6c750dbc8697bf563b042b5590d7d543e0f14b07cc3af1de3a89ceeca224", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 411430774232, - "fee": 0, - "recipientId": "AQwJBgy1TXokikH7XvqpyWtgAexhvnEX4e", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022005de47c592104b12833c0de59912e4f98001ab5e263af9d085828237f378a73f022074efe6b3260b7fc941691a426dd73e567eae101b680e360652e9c1539dc26c1e", - "id": "4594cf80dd9c58f780619f1c5b07ded414855e984bfe92f9c8b5e253703eeac4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 414435446144, - "fee": 0, - "recipientId": "ALcuF8EWQZo6wHdhEab3SPG6NNnUhxmtrS", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100be8dacfc1c3bf51ff37c4db4df096642aee830e9c54b03da3a2cb1093150099a02206694dc3b9cfb53ef9c9af478bb5663979bcf740cedd5c36dc892ae50ac2b5c38", - "id": "3683470aee128d9e3198d1367815a49543180de9e5b6a354c6cace53386b998f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 414495122050, - "fee": 0, - "recipientId": "ASCzbyDxfQipaZCjC6VR5hbGG9YYgsaAwd", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b9aef4b9f36b7c0fefd1a5fef061047e327f54935080c118cbf913f607e42d1c02206edd5cbb8feae8309a3ea4e53ce4a86f9d91a084d453cfb51dcbff13522ce002", - "id": "198ee58a22c79eb720667aa9d17e249be4e2eb3114c7206ab0a73b7c9127ccfb", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 421404948418, - "fee": 0, - "recipientId": "AcZ8qLedqB3pqDv59QMLD5zLCMLEPsZiyA", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203294398344f0e3863dcb9709bf6bf915ff4c678a681edf48f03ed605ef990aeb0220247299f0e0a0abf1595d9801220e2be6ae9baffd4611aa62683697dbfb051f92", - "id": "83fbac69f11ce4a4d5993f8b7c60a4d1da9738f85e0d2e6905d49f41f62f22ab", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 422404168409, - "fee": 0, - "recipientId": "ALEAsaG5tcombmn4ewk6CtCZcT8jxS5LfA", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022044a875f285cb908df1025a9246529b88c88320c3d2c5b212721a67afba69304e022035b80eec3f0b523df1d8b235f9c9ad05c569e33f6ed4b7d72127ac88a8e04a51", - "id": "f01d3a9212807fcad775b20db523002d72db87387a2c0c0e6d6e43a96f76e10c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 425000000000, - "fee": 0, - "recipientId": "AaFVVK4bHWAULm3enE5jFuW8w1tGDVAtPP", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e4c2fc79977dd2c8202b0b9b710c1d345266b58a583911b8d7dab80cf6a9efa8022043b02b4273d62a6bfb357060314f977c8688fa2c9fd9655f1225d76e5f210598", - "id": "a9d807156e0201c2e6b62cbcd86df7e744c113443547394481215613463aa517", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 428842921747, - "fee": 0, - "recipientId": "ARTND7EwNVQyRRcHmxRMXD4vDxMsuvWHe2", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206d2758d81dfb99fe616979b5241ddc7054c2ba0661db8534b27f5a15a6e42f52022075af3ca1a436a580397f182ffe8dc07734eb4b2a67bda747135546f8fd3462f1", - "id": "30cc3a106ccd48d7a88c1f6d948d5701670c7541ea85d7472bc0a8e66899a9a6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 429330120346, - "fee": 0, - "recipientId": "AUnowRvHGnZnpPYWQhjnP5GRh8waH9oaoW", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220396919ca6596b28199114e6bed458f8b3fcd53d1bc01b9c55a3e8c393f2694480220072f26bdc63d2ca9bd7c3c4a4ad1e246676055338077894de505da12f2b81a8d", - "id": "b673418ffcaa8a343b85cc02856e8d1aa2167c9e80228b738ac3d9487a56adf1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 429900000000, - "fee": 0, - "recipientId": "AVtEhnUNTLMG1ABGW8MxoprpQpcnjVMMs5", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a4775a3edb88c12d3117057cb71c556f460bada4d578d14d52f6f16f52dbe1e502206f0793cf3cba1bbfb17195e79343b6652414cd85a1ad47a6e76819f58a41e794", - "id": "91d608f9137abb9d4f30446786eca2f74cd9173b0cf9e47f3a917b400bea2d78", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 430758143690, - "fee": 0, - "recipientId": "AcsmQm5EH94A8bmxVhBX5azqRZ5HxSFurH", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ffcdd344a83d815b35399377593e907593848c590e4f6b690f90c8d87970f7d20220098ae996ee30c244bfbe04fea6f0940488dace06119d2af396b7d08dd925a328", - "id": "9693698248b30da4d32f3bb74a94185545a22511a32789b6e7c6a62c22300d15", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 431169218840, - "fee": 0, - "recipientId": "AaNpyjWJC1SzuYNtPd5eJrfdNGD6syTzLe", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a19b4cae750b166dc50c3e22bd12dfc5f490b58cd13c139412b61dc7acc7149502200418a034b9a2b6fa26b12276e5bba9f79a15ddf3307b51ac1d3b35316087a080", - "id": "074cbdd2b8b306c0883d1e6d0a468376c98f00274da7467af05095edb852a044", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 433300000000, - "fee": 0, - "recipientId": "AS31C6KTNhEc6P3pHgpZYg6L6xduuqvSXW", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200369f8d253aed3996199457e932c7f1b95d12cfbac50abf255e04a331eb6b30302200c9643963f4b856e9ddb9572797944164f4a2991e2073ddd0a9c0853da0b969a", - "id": "bed6b1d63572f48b674a38739a4f33608fffd9267989b21144f3fa1de1f6d39f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 434358663505, - "fee": 0, - "recipientId": "AMKqjbsJKvsiRyzBwc3htV5V2sqrwkXpzK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022026566e29b8b649d199aa887ab842f5b815df66865cc04f5e1d3a26378c3a39450220284f908cca59b861fcb512612c0e66a0ddd855d4472d0fe6a96d113963dd0ed5", - "id": "73c577979ee440ffcda796ac338ecc04d7a462b69c17336ddcbf05dafeff9e9d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 441082175858, - "fee": 0, - "recipientId": "AJCDjQNpuuEN2byRdGY9xf2qRBCeASsCv9", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203e7de0ac5c2dca5bb3b367f2006e33648f0b99fe5321af1cf06923f5638376e702207b223a41226368a7fbf51a26ede9ce708c86dadf563446aadc82eee78a142f29", - "id": "295bc81dc9d3e434b8b4cd76bf32878432742128e94b10f133b6f8596e4ae65e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 442296547242, - "fee": 0, - "recipientId": "AeeJpSnh6sHMH9QFnaS8iNQUxHX5kxZwa1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205a6753a13b5dba6748e93d9ff8c9e0b243916edebc82d50f26dfa4023c5a1c97022015cff0083bd455c998ba99ad3836fe8f569dea1b16e46f3cb0a1789a9015abc1", - "id": "db2bd8f694e5d5d7193469bc9af661d54ccdc1bf5f8dd1672648edabcec5585a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 444900000000, - "fee": 0, - "recipientId": "AYBuKPH9GghfWpVeoXmHheJ3hFEHHsPLL6", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022076e35e14f992269d3388839ceb60bf32ec1b02a0ef6e63ddab97fce76cebe95702203cc902cf0c383317ee84a5b6fe7ad1a5d0e507d3dfb9a5e1cb6b72bed9c2e013", - "id": "05b8f0519ee71468c7b25ee5d7e80360de95b6e2076886f3c1407f0b1b2c9f35", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 454296574143, - "fee": 0, - "recipientId": "ANdCnCPbZcMmb5DcuxKiC4Cun8XfNpk9LH", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e4072324402b907461e4bcfcf4b064dad72e9099acd171f27d208a9a018c2c4d022011b20091c75c4dc05d32e8c40b202c910a32a6fd18da1b531ddb4f5dc677a0f5", - "id": "9c35f16fe5a213937cb9d260efb89e114bc59310f3843280f6f858626257cbe0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 454410176686, - "fee": 0, - "recipientId": "ASDxEzhr4QLiKvE8gx8NyBNpNJBiY93bBN", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100be0f72e16ac306667849e550bfa53a55f77446ba1b3e85190a28c789d80e2dda022021706b1b73b64cd7d98e85c79e9787cae57efdaddf31c60e9d13c6530432d09a", - "id": "68038c71701292ed7e30371c8f58c74de6926a9eecd9833f205e85817b4880c7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 458329154329, - "fee": 0, - "recipientId": "Af4KkTdudJhshRqyQknHmieMTzgN1baKGk", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200d98f84a03352a7404824fbf2565597e3407f554602a05db8c9e94842121de5a02201500a98f7224e8e716c2719d8981995c6a9cb0c38b456842b8b219c6034819b4", - "id": "13437129b6e18a12d3acdb01c9e113c21bdd85e2058aa56a460af7f3c8423501", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 461698156433, - "fee": 0, - "recipientId": "AJJ7HZHTZGmkLjGLBiWJ2ni47EVVmsPAcW", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100eb4bc75f85eb983bd0b2a1d73ebbaf29c0163ba020b3fb85781ce3c1a97d6a49022071bb413f07e6c7b13f6f8c82dc9d023da2ef6f262883aadbaae6def1dbe5cc1d", - "id": "2e52fe4e3801a2a567ab73693da5448d5f0bfde55a2f0d6f4052075820ecae65", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 462363719366, - "fee": 0, - "recipientId": "AVsMnuEh8yCdQjAtc81NZzRsxJJDVjf9Ka", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ffd9156e78730c609974cc6a31a597d827cde890c7e5de46bc54cb10d7b1d51d02200303b539a531b513eb059a15ed6a5b08f9299e634348e3228ba323cee42d16bb", - "id": "5ce506c37e536cf8d7da0c3d2208bf27e461c5b261b9eae156808812fa14a6ee", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 462789386912, - "fee": 0, - "recipientId": "AVmGqru9A4Zpn3kd7goY1kJ3h37nAaxkN1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022039e8e2eff6ee9b7df8d20d070143f244f3a29b4c673932576e2d1d514d31d7af02202a79c76be0c59c0d27af4a7c5e45da0ac00944eaee1eb9aaffb3768eb3986095", - "id": "a3875b9ae99d76e48778e0b37392498715806214a2cfca46307c828e0d160ba5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 464492402483, - "fee": 0, - "recipientId": "Acb9QU2CSwSXNcbZARBwyqBSSsQRubqjHq", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202e6eaaf793a04b8fd064315af7d6cc5b911f86faf21346fa2a6e013bdb2191d3022038a043a4914bc834a47c6c903cddf4dba4eef84885201b9c4a637cfdd9552f55", - "id": "7f81ee0bb3fe048921ccb934a4476c15a339019cd8bd3de40685062281d9373b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 466008654301, - "fee": 0, - "recipientId": "AQ2bpZffieDKR6TFatkS3vZwDLyvDpA6Jk", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220516e15a8cd6382b7e0b93ee04ce1ceff0621dbb17ec2f79158a1dfce7d3829e902201383e9cbe11f387f87cd8e1701a0d974c61ea92f75090620065125f1b283ceaa", - "id": "838d54e1f7681e910380bc08a02aefa3bd47d347c10c9f17030cc1bbdbc9067a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 468845108203, - "fee": 0, - "recipientId": "AbBz7xmNnHkkdww8fBK7x4EcEi26V5sqr6", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009364c19fed0bfc1c4566e19fe01e83bace4233e7f7bbb25ab08971ef5c2e00500220118bc321de72db6de795781e1ccfe1c587b89362be1a8f9ff43d9ec1e2411633", - "id": "de57689682daa61b89193fc451040ae47b6335ef07f363e86bb37ff0b28f75d1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 477386564720, - "fee": 0, - "recipientId": "AT3dyR6SqoMafVWKsMvL9h3iBvsU2Q7S63", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b8e109b247b4d83739b36d32ad9e54f11979bbb54ec0629da72f65274e468d7302203b1370fce9d9e950120e102ebce751b2381d2319353e5c0313c6c84e5f24fff6", - "id": "8ec9dac6224398952f9c801261fa4a901ec3e823bc47301201c8e1d377b77bd1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 477386564720, - "fee": 0, - "recipientId": "Aeb22zZXfmhNvEjUH6H3ZT64fJCEqR62iJ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009fea8a4720368c5228f554d16026c0286bc036d49f5de51abfa2689d27e54346022061f2f0b0165159567388103419fce40a4b296079369bff483df251fcb9427764", - "id": "a47039058f275732366634483cc82c01a7ceddcfb6fe448f4e60eab614ae0e64", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 477600000000, - "fee": 0, - "recipientId": "AWhmJrkMyrJsdGjVaz2LoqAboibJFdpeKg", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202f3417cab05063c88942c5011311b5d19f7c05bbffc67e6ae090388803a8639502200a05fb48af403296455264ca2a3a3f6d4b1cc89706877be3335fc5c490c88692", - "id": "b6c19da13c34a12fb2a06d6a081b172074f07641024f3021b2105864e31faa49", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 486438883282, - "fee": 0, - "recipientId": "ANVReFpTPz83WVcVZocHwuNwJLuGW8Hg5n", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bc6894303b0552ad18f66494029308976efe651de9f707a0187059e33106431302203f70460a4113ba5b089efc2d85e885832a349a9860bd05563c55f5d5923bbca5", - "id": "2644a2b4e0c87b2b0fb23f8da21f0c085ea8aae031436e846d45d77699ad1c28", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 486986935045, - "fee": 0, - "recipientId": "APoqq5cehPy8xdqgY5nPMFtupDK8HcCWFD", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e1ac0a3e424f949cdafbabed541a5fe658eacf781e059b22460f1ccceeac7554022050119d98a70c087621994890f55d3d8a79e889b02cc186f8be1ef04ca4ede1fc", - "id": "0bb8b3edd973b8b51a57550b49252f708528319de548f0b89a77aaf8f567a685", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 499452432600, - "fee": 0, - "recipientId": "AaVHPBJGnHGAyDsXqcXMkgnTXrP2VA8zXd", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202040b29b5ae655905a4be13aa5160fcc682c0c3df0768ef4c15142e0b6d79f45022070a5b44807bfe60f84455ad05957fdee06cf677a99279b25c33eab56b33bba80", - "id": "a8a4acb7cd42fb523baa102fa6dfe72371a2686c99d18e8f26d83cd65ce69e62", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 506413677424, - "fee": 0, - "recipientId": "AViKTbBER3QdyjPYvu6aEikJG6jyMZeBMH", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220575b7844c2c7dfe981396bdba8d577ed88738a16e4307c522077255c5db8225502206ff9da12d3b9a67eebfb1234918c19e92b1d94f75983c5ffc8b5598d0992a40d", - "id": "a424e155f169f58f922458c89352fc80a71a214a9b7f5d7edf53b833b7e9bb3c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 507369287698, - "fee": 0, - "recipientId": "AN4SVH6wMd1J2UinEszD1m6rNd2Qf67s6p", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202fc2e97188ea29acbbb5cb669a91834399df4e95258f843e5f508b2ae92bdbaa02201503b9313688be4f868f27960bd4ec9d3e26b49d09455e7261f811a8f4bb9f64", - "id": "362dce6072bbf7ddabf9cefee0b27fb43eae636f5d4a0d2f777319764ae643b9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 509141981427, - "fee": 0, - "recipientId": "Abjr6LJqKumLBkGwWdCmqQU6ySWNTvYs83", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210088070a6fcbbe069be1a6a7b9de2ac4baf72f01078d69e8b3203bc85bc87e68e802200993087e55d13928a55f3a543cb91e780fe082d1a571da71ad6e4edd4ae63af3", - "id": "980d85c7519a5bf12e66c830e13372ce93d7918dea83f61bf88aaf57e5d44e6f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 515274387317, - "fee": 0, - "recipientId": "AKsTx2HPyBzhbNiEuUTjaBefkXsbWBmAzj", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206799aa5ca25023037fc2046c9976c29eee3253c6110d6d68da3b38a011f85ae20220498f5755684e9f2525f2080ebbec2c1306b18dbcbb1ef40d9006f17b3e12c30b", - "id": "0b85faa2155edb7516010b7b78fab8170527f6f51a144eda464f9bf148a2997f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 516873898330, - "fee": 0, - "recipientId": "AWP1xqsXMMgBDfRpvJZum4fywFLtRvT5Uz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205a98023efaa4e52f9936bfc80e4f8d4230a2c907a7723a7766a7ec0eed9992fc02202266a5cb028f371ab13c03ac11ec40a711c0473834a595da18591c2293622f15", - "id": "bbf1e033954f8086c4ba041a94635ae3ed8e0a34fdb0ecb699aa1a62edf9dac7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 522000000000, - "fee": 0, - "recipientId": "AZydcTLh6qTuJzSkt2i7j7neaaMxN57bqL", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022049014a7b3c815b9989d7c4da1275d9381d5ea1bf94fa0197e2f1cce3093f9001022018b6744495c6da6cec8820e4d28edac409e2f4a9383ea42f918a792c89fda18d", - "id": "253a6707bd401ebd1b7d1465bf5857db4aba9e8ad438b8d97d121a7bf3a6e7c5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 522851951837, - "fee": 0, - "recipientId": "AZ1BHFwiQLgXfnbiW8poyzX44z9fg6R7Pu", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e6ea691fcf6ceb3735eeca8e83cd6be8664b9c42bd31aa0317fda910b154e65002206556f64d1821aec7395eadb7d2aae1a5a95e7a063b62ca6dcb6176d3ae8190df", - "id": "a01d46e24b46904aa9a2163d3950c53af19460ac839238d03c7304ac83b33820", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 526711619581, - "fee": 0, - "recipientId": "AUsWkvkAVgvaFhj9VTX4U96FyGFh5KbdZU", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f4e1b32ed7f1c7831444c0b2452e8d329dc9a0a439da676a5a56d0ef2b8c266902201bf1c1a13f4ecd6289b91e866e73b727ce735baa4128f0825acbd976e147ea32", - "id": "c22131de3dff92e746993d7a01636fa1ca2dd20e01d4db0708271aae6a63bb49", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 528376108643, - "fee": 0, - "recipientId": "AUuTNzGAj9F1aNpcKDJfuDzHYj2ndYLihD", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200ffbe2910b4d88d818dbc798913af9ddc106e423242640cc712e0d55c1a4a1d602206262f13fc3fd933e9f92f1455ebe280d33727501dfca60f8dc1bf85bfeb51ce0", - "id": "b487b250cc3f98368b60ec6c5fabd510e1a8483cc03bcd74d4931ef792080712", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 531321344927, - "fee": 0, - "recipientId": "ARtGz4V9e3wzW3iRtpYo4DbREDAvCwAg2k", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203776cd4346a82b61c46c2ae9fe7e7b5e412b84727183646992b4764249881c59022025e092b1d5180fb4cf23717ba0de4444e52067414da0bf474752763cdbefb8c2", - "id": "fe48867e9156b1ada766c579bce19e3d01f92164d9c265902b7a6c20159d520e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 536606958259, - "fee": 0, - "recipientId": "AWFff1CkyqJWtugHuYYE8WWzBP8w9qwuhL", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220024c4fee1e8ac337e575ed098a4ecfe070ada692085295894459b7b7a3b86c37022034f2e22c2438ebf949955ee0e865b528a781da86b942321eb3725178a2f8173c", - "id": "f1fcd6c5c6b3c83d0bd5accfcb929061d8804a863015cb094100ed09c90b0076", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 538220305472, - "fee": 0, - "recipientId": "AMF2nM6FEMBBfv7qWc9tW1TWv93dxWbXjn", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220315cf7c033bfeda43ab267511e88fbe392380aede5ab0087e7295bc4b20b0abd02202524f858f7d42cc1c5fa6866221d7463abc1b94d0185067ff0b3dd57a182984e", - "id": "8e26b02745f1f5ced11fa24895b0bd773a2db5370126c1e92235f1b123570548", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 539888138573, - "fee": 0, - "recipientId": "AUoii6QU9zwWhRJzLZMF71E6dffv7c9f1P", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022045f6e18076dffb721463d1e34eaf47a3bdd0d1d0e87d19d9e0a2ece49c9ad71f02206a0b723c4937c7861579b098763ddb8c533a7308dced9baf55d943b17e8b6237", - "id": "b56baa4d48dda93fd85fcfed035b4ee474f64469e28eede0b2369b4ffe17a542", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 540008433875, - "fee": 0, - "recipientId": "AKpuB5YvWrEzgxGqs9RSGr9oT9WQLCyxaF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022066b1533fde6b296e7cc40d4764676bed7e9e088489e2482d9d7151f845f1c7c3022041502f6705fed4575514296bb157a9c5ad601758992acb22be436badfbb66f21", - "id": "85882091ee2b032360bfc72a9502434cda2fb938f5362225d68c4f77c3a47600", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 540816219226, - "fee": 0, - "recipientId": "AGGeFX427aMiLYXk3n6u1c4hup1X2STgdV", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220276e0d0e57371d2e5d6d9dcdcc8f9f1267bc27f640e95eb88b8024716e019743022034bd26c909f2653ca8af6298aca77609e8eb367e16f4b4507ad280beeae51699", - "id": "d966f146fa457ac9906b6b928bff9807416e8506483774c8948c0398c0481435", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 552260891955, - "fee": 0, - "recipientId": "AQrYk1AbtQ2brZ3dvsbHh8rdErYGJb68pi", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a1ad97eeb99f64d9eae5da29ec69a174058788941b7d7d43348e0e84a8d04cc902202520b1fc1ffc869ab83da821886b904d1e94193dd33210b5fa6cf38a1c9ea9bd", - "id": "ccf83a1e1d918e27052860875f0abf5e55dc14f6933c432eab13264919a60d6a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 553060575959, - "fee": 0, - "recipientId": "ARMwrHG3miRYdVZsGYmeXKQULB6GwMyyad", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200110ca9f26cf5a36a36e2b035b932b98df256209b95465a57eaaf8c2b19c05b6022062f55565484251018cd6e30fe19ae6387d5bff36836dc7b09fcfb5c350efa4c4", - "id": "f8aecfe8e66cf62d917c2c231855f9d4a7a479e918464658a98d4cf5dc03edc7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 559276526635, - "fee": 0, - "recipientId": "AUbZ5CjroZETpDU9rmQtJdVU1ZSBHhxWNk", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200c8f7e1cf87873f2035f917f692cb58ffb2e8a5ecd68a13f25e8ad08b3309eb002201b7d7b59ee7e63221343a44e4a04642118e38acd08ca658ec01bf620040f7ec9", - "id": "d94bfd69197aa074ae9ede3c8abf9fe3a67b49bf498d3c620c364197be028b16", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 564299912400, - "fee": 0, - "recipientId": "AcMseyJzDq5Rsh2RYqJsGXYncjQinXDtvX", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a5edcdabd03e64a2e29f5bbf6427db67691f39b54ac517fe46fcf26b3515db9a022074d62771c4396e6252d56581e6413ed4b24891272bc7c8fb2a3ae01d2b7d6161", - "id": "a2f8f856798ed3ea34daac7d04f99409ceb4b58e897f8a4a8f05e1706926c64c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 566286551662, - "fee": 0, - "recipientId": "AQFkoubX26ch8tFBy7dm9yG98LLqV7cMCo", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206e638a8e03710eb44c0d7e8ac09b1b38366156bc8c46e1a0ee2f88424b86c5b40220247f979fe46dce7faff11bff26d3fd1bcd510747dc857b9cce2c354fc8d31043", - "id": "392e37377917bbe974e3c8ce837d0ecf52f41f1bbb7ec2e0056d0b09fd44620a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 567899118315, - "fee": 0, - "recipientId": "AYMj9jGadgkv3uWMpRr9f8P9poV9HPqr8c", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100937f17e1cd7f010b860f1f87e21802ee7c72e826564e9eea6b942b862e56f0ef02201673df293e24e84c2528bc70bca4ebfa5f73102beb49bbf999ec169527e2a3e7", - "id": "efb581f15c255041206ec351d3ee4f0b20792e6f99b29da27f9f588d1a5fb34a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 567899118315, - "fee": 0, - "recipientId": "AGpjXT2iG15ACjpwGGnZuZ1rcEB8WuwLGn", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b12dd8dfdfad12e59a4bd1b13e589a882674f1d9ddfd7f91286e10def66a8e9e022054bd93d1ea02976eb99f7d2b12c3df7b4d10db3cd77bc02810332c24a2e26227", - "id": "4aa30ef712b9518114abbcfbcbc962414af90b4618d22288fcc6c9b8cedf4453", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 568012720859, - "fee": 0, - "recipientId": "AGM85ysEstriQdnff6hLY96CNegLmfsTNx", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009b5a8d8225cc5c573f92f1f007c19ee1a702093e08dde891385ed3c19f00258902205e1088df5448c3a8aed0cc80a51b16db6759b80cdcfc318ad0cf70bb1eb19ca3", - "id": "32da511a73a5b2077d70a3299332790197c37351f0f6a10178cf1f777126e6f2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 568126323403, - "fee": 0, - "recipientId": "AWkSJ6mwXFC75PCU2Jq35ddXHyDAWETy8d", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bcb4517f522f1c416d6ff3a5112dd22b15de6ac76fa7288c3e53859265b71208022046f9365a91793e947578c86fc7ff612c6f34d468dd6ea61f35b7b4467ca7f87e", - "id": "6de463003b0a10c7372a8ba2490533ae3a485d916b93986d2049980bf3771e8d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 572950085963, - "fee": 0, - "recipientId": "AXQXmHt4c1EcpxFAqkC9Cy7J1ui7FJaTUe", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210081b78d77da50f2b94713bb862662883a4e0dbe744ff6d41e14c5448dec5f99ef02203d4ec23476ed4adebfde4e7043cb0fdf6f7a07800d0bc350c4509baa698eb45a", - "id": "2521232eaf12c8a956b5b0177a86b430e4557aa782be3719e3220a6ae1ef77fc", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 584622281689, - "fee": 0, - "recipientId": "AXVz1XQf4pdPdXgyyU4VCFjsRRV3i9VrbW", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220296a2e02be62e0a620e5b5b166757a449fe5c45829a0d41efad153710f94d065022075f637f41da115284df9e0a3782e73db04e044935a60a90715555949add9a470", - "id": "1f4180750e1f7c3f5c48fcb94104ab167e677eac601f7414f675c984e8dced44", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 585926998753, - "fee": 0, - "recipientId": "AeWy7BY7i384uzceaAbtxT7k7N1RkQeVK1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220296d7c9a6a71dc7d9803a3c95c67f69fc0aef05e2309dc3038611d28a712f8d5022050fdf931fddfbdfff9a36c4a554a7198c3ac011d462580f103b419b6f9a996db", - "id": "f8ac8625237e4d9d263547e716bbc276f536c7dc5612a9b102195f76744e0d78", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 587913213597, - "fee": 0, - "recipientId": "AKEyZbWJ6qKChGPXrq41cHZk4Jz8xHTqh8", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100993c7c4f1c17412bc3f44f64181821c60a5532272d1f0436aaabda139274900f022010e72f7d9343ead3cc02fba0e111674f31c2cf573994415adeab4d664acbdcbb", - "id": "8691098d97c62a3d45c08b8193b7cdd5416518973eb699d713bd826f5b978fea", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 589063195094, - "fee": 0, - "recipientId": "AeZ5u74dBxufywKh6eKBafLKBnWBX7F39G", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203f7a428270130c3cb5cc77cf307f8f87e3e99da03bd9c673805de883aff6b4f7022062a643143c9be2a68a3c49ba308a96d89920130f4e28bc688a687b05e5554ceb", - "id": "7ef826360e319242f436e15599caff7ab705747e617cbb8a2e0865b3821afe7c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 592519033621, - "fee": 0, - "recipientId": "AJQkUYcMuHxLho6Ag8yLiZjTcjhMXZ9aHB", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c2f173878f73f99e2a044fcca1fe6d6e5f116c30a8153f0ede31f3c1963b19500220081480f6aba6fe8568d84a6ecb26c70a8b6e418c60aad5386148455d9d361b0c", - "id": "af49856b1c3f6a712330fced86bbb90b577150af1568b8fc64b024eee487daa4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 597999101241, - "fee": 0, - "recipientId": "AGoFy5eoJDS7wWbNpAwyDQbwGcWHF4iAe6", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e0a578e2efa657e551cb1ec1fc7330b08ec8aefcbf4458f720d7ece9b04b655a0220065c046cf75908b41f1d540e33450ecc4506f838818d97240d3c256562bf1daa", - "id": "50a23397ce1b9f9af4bdae64c7658e7a98125d17e803340d310535c11aef57b1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 601400000000, - "fee": 0, - "recipientId": "AYqXPht63UU1HKGMYSTgmgPoZj33zKWAGV", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201d214d26ae57304653dbf4deff3a2f4c2b5c0faa6451821a4aaf0851a66f77070220563a930f4833d36732cc2845a318e4d59cf77c2c7808eab9fa80440b6649c3c7", - "id": "545a75b3f45b8d3ceb447f8a573ad527853ea7cbfcce54c2ded8e8add84b1574", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 603323064588, - "fee": 0, - "recipientId": "APue7kzQNznGLbD361AwVKyfeqk66Gp8pb", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202105a9341411c738f541af56b48370e5c670710f26375dc77c968e3a3d0306ab02204218af14c3f11038101be19628a0d34d7be466f0dbfc41518440498e23742b57", - "id": "ca525d14c00825ad6f0b1f41b8832ab154464f55adbefcdfcaa9158f195633cd", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 604220262324, - "fee": 0, - "recipientId": "AamyoMmKy6vucKTsu13jYFnP4GeBy1RMum", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022032a24879fdefdebcb46b671a4dadf43e6f84af8b4d58646044a13d2dffc19720022052301e1eedcbb6116cc327c27ecf021e0e69345fc8b64d8864de1799356cb052", - "id": "5fc2c74fe023282f5b142ede55e47f0b218daed782c307d3f516b1707afd89ea", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 636515419627, - "fee": 0, - "recipientId": "ALPyErixj2dXaoQf2aFKR5qRWrpQNSpVzW", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210088327da183772be9161d481fc69fdfeee11f629481d8b564146f45709347775e02207bdac01a99707e5fd59cfdaaaf9aff15b0171bc5c4fd2a8a97695c5adae6e49f", - "id": "783b96a592c50b102e9e73a1d69279352e842cdd8dc7b967b2728f6f15031ac1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 639468721143, - "fee": 0, - "recipientId": "AWTTo6v3d5AwpVu7Z4zMTdzGt7nStpphgS", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022046efb627b31d7f8834c4cb53547c86b346cc0a0c52b00d8bed26910909cb759e02206c6e2c5b6d5fdbe74e6a05006fcc9f2c1c0b2e3a488dc8f9baae9c1e4f9f2538", - "id": "b5be81aec8875f631918ea7e3471074c1ead97de2eb760b6367d43360f3d7917", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 649700000000, - "fee": 0, - "recipientId": "ARRLBH2goVLrow9EgBstBpS13mjmKzYwNA", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210091648b7427049df79b49484ca0a5881a3b870354a1e74c38e56b83849d49fa43022010008dc0dac24a564eb2d82d51353602ff7d7cc0b01be101aba4e6bd8a3d5bb1", - "id": "f7d43309c24a58b257df1477766aafee1d1f4a41d4ee45a38fb874fa62517e29", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 650982673120, - "fee": 0, - "recipientId": "AeUoiRMhcgcw1BozjW2yesuRNsCaq4WzAt", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e7623c82f69ca00ef2b0f988cdf38f53ef71067be47637c7db4da7f010a33ac60220429410215eaf06057c4f8c7d1da490340b0147a3d72be9ab9b1f4e136451cf98", - "id": "f3c675a4dab04c4a3f7437bf4264e97402d36f43bcfe84f5c2a874c089509cf4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 655486679871, - "fee": 0, - "recipientId": "AP7kuQYdDVNaxFpBy1RRZCtFJxEfjyafk8", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220157e94b4569a8eb2cf78c543010cfabddffd8fddf12b800c5ae82a6a90605e95022041d4a41672b6f85bb8c2171a033906eea7043a35f377f2c1e07ae09c07906f53", - "id": "8e4a229041acf3eb033f4215adc26faf8bcdfdd1a8f351b1f97bfb96972c48ae", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 667854242368, - "fee": 0, - "recipientId": "AXyLBiVC4v1vyXUwVCZywuGPgzGE6fwjfr", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220117105ef4a93b506305144a883d16778f4d15fb5cc7cdc4624770e1e0976c1e802201ef7dfaf16ccfa96f2e408f78f7026377a65a59d1289608c47fd6c96f9df8730", - "id": "dd2a692bf311ad077ab9e0eb14d321fd4be930012b9ee7439cee939330063ce5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 668477464923, - "fee": 0, - "recipientId": "ARanutQKuQepLjbaHuTGh3yvFVzDbuoWj6", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207bf98de6ba2b128320e753ee96a3740084cdb6aaa13b3452ea9c6dd3afbdb6ec02203a9c6e40cf99cc6694a504c913a3eda844cdcd0e89b594903d27774a78b96ce5", - "id": "a428541fc5455a8e86b42088317a82ec539ec88ee98e2b7016a8aed336046fe0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 669856703512, - "fee": 0, - "recipientId": "ASBZf3gbf2VLdkaFp8rkVRGjnCm89HRfEP", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100dfa8b07f91d5ac3309d0302898299f4695480d247e75cea9c316aead4eda1332022039a19ddb10f1b4b4471a6ae3af7cc70cc9a2fc5ba9f88da5cb14e25c1cef12a1", - "id": "3a0b61cf0227f3a14b973e4d991b3c892ca9c4b40f48c6b48dc0e12a70939126", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 683962013271, - "fee": 0, - "recipientId": "AM6uMWQkzps2vJqMMMMyEuZLNa8a9HetfJ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100aed03caf6374a94b18218b1d6557cc6b24336e14a14932f1c3de017d61d969e3022040ae6f228c81d7ef90a5cd59d57844e7e712c981cfde43b4f7bf5fc590550995", - "id": "1ed84533e28f9b77cf94331c50903a74d10f648d4bb290eff722fafd5ad726b7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 685060228654, - "fee": 0, - "recipientId": "AMwuubQrccxV551wFW5oTmKWZhvbojTizD", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204618ffb59c02a2106b27c71d14148d1932968b0c31490664911c343e96e5316c0220313e2e7f2387c05e242f162fbd94bbfaa3c991aadd853ebbe19c994095cc2464", - "id": "07c8587383bcb1fa6cc35ac3517ddbb3ccbaee82084ed71682c81a53db7b7995", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 688309516953, - "fee": 0, - "recipientId": "AS3HG6pG4cgtJBHiai6DrZfMdpUyTFPkB1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022019ddc6fe655b187a051a3d7a0575c8f3fe2b6ddaf74c69c8c6dc84a05e67d20b0220568dc2da3b1d3dbd1a712a4dc8e492500b475a08200bc70f62f73559a10d80ed", - "id": "0e1e914e5931d52145327aecf15e302d44dd38bf5c87756af8cb22a0cc1b8177", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 690823753611, - "fee": 0, - "recipientId": "AL5qEAPm6HBApbNTqVE3awEgzr65w8JZA1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e7ab0f12b99f73cdec3b34ee4b52d3987aaef73fab54b7aada17d807428cd0730220383d3191195bd2819afd54dc40bdc165c9271a201ecf57444c6ccea480800d1e", - "id": "cb4091df5629df4208f1132ad20d8beedac84714e5c73e52e8dd025a2d571278", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 690970768669, - "fee": 0, - "recipientId": "AMRtZ94wfCYmF9eAgK8345Hy9unPa1GKWE", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022058163e796f8fac61be71bd7493ca8f729067086b0a850bf233a0f719ff7a4d8f0220126c32a7a178013b1c955dafa8ffc3ff7c785a41f9ed089fe0bdd5283ee2bbe4", - "id": "711bdc5c2a1da2fe4c488397f938dfd9569779581ad321f8f1471b2bc363e195", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 703958360199, - "fee": 0, - "recipientId": "ASCegDxvnqHPGYrUUS3YoX3fMC5B6WqYGx", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fbcb790ad306e8b67dad036b7b02185dd3f40e92cde91c942ebf65976ecd98b6022033e4e5eed3dc65ed5264efff8a3f581107557fe0033fc6ef440e3a1622e4500d", - "id": "15c7116a20f1740fd4fa8c6e93c23b5149be45117d14c9c38c36684517914c32", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 706898356567, - "fee": 0, - "recipientId": "AYSpB9rSgnR9Cp9siWsdPct9dKKxbhBnnL", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022000cf165386c43ac8d18e3304b5eb7d7a58bf1eccdffdefd3f5af84fceb94846c02202cfa34b89f205b55f7a8c90ba72bdb8698ad1abc18a87dd650fd41c348592f53", - "id": "be6736d3f90b290d9761ab4976c11397d23d0300cbb09a3d24f818c850c9fe68", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 716079847081, - "fee": 0, - "recipientId": "AGBi3EnW12LZUDcyybsqxbeqLzdxzxTsEV", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220524fee405d41e04fedacc0e38c031c26ffbbb70cb273e53106382add945918430220549f961152cdbb902d342a57dff174cc6cd7d6b689d5aa49eac318311784b4a1", - "id": "a5a5a47040cb7f878d89cbd7b9e2d4da7f17b686f9f031b9f4ab8485373a467b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 726541178342, - "fee": 0, - "recipientId": "ANkCc7vjQS8g1jo5pKEvYKvrVbPTVmZxx8", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220082613e1f512c2c74044df74ccfa0018f5066c27c1027911669e6a393094f22702204cc71514d9c0c72430d6b24dfa6d8e0ddc3e0d9e9877e5a45af7b66e06c77141", - "id": "bf09ad805c4fbe75d9e1dc6d17b319edf13885f5455094ce30e44c144a496ed2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 727897828829, - "fee": 0, - "recipientId": "AeX8N5GFDN6ZBqf3u3kXNxVHBYsz2jLg8v", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200198881de2264fc3c1921b214379e16bad210f1e3d62b289ec0714e4c47f51b8022013d911c9f57820450630bcb87670ea779e7ffed6bfd700f7be55fdbebfdef0c8", - "id": "83613475e1867eb2a82ecbb92db7506a2e50c0336514c42e48dd3868d5aeb6cc", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 732204904378, - "fee": 0, - "recipientId": "AbzyK6ESfRUtaVWgiw79Qjx23QpEpBQdDY", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022065140a29fa740ed9049d50e848a48398fc0976992611867e87e7fe0afc72e13402202bfab985f83e5ea588c2a15210e1f6751b263eae649c2eccaddffc07746a0a69", - "id": "03dc5b6869f88c387a1e29bc05e963c99e88a3861a8e6b215ea0c87852df252b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 733825657832, - "fee": 0, - "recipientId": "AX2fhE4ybwdvcWwnJKpcwsU2sHZAQqF9h6", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022014e54a8d9f603cd2ffb1b589b11e26c1f74d0aa0c3c457b67fdf614ba46d1b1802200ecc6519cfb098463a51a3a4c8791d8cdfd7d0ba1a7841402c1c61c37df556a0", - "id": "57a89b3732488bdcab397c8c52e9cf56cfc18d316659388d49bd59b9b104be11", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 734634240646, - "fee": 0, - "recipientId": "AJdkWn2xq9mfJ1XbsvMmGWgzg1qXZcpaVr", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022020e7d6b80e23a91b7ac3cf255416b0cd1ee4b8c99e7905f3f074e0389d41658002206e2056d8cd76a1017caf5657c0b2028f85e9769b8dffc3b55667f34e00b10187", - "id": "a6c0f4e9a9df5bd241fbddad25bb116624637bfc4aa4938c2167894d3b422d31", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 734928270761, - "fee": 0, - "recipientId": "AKBTUeWQRdFCE3Cs2tKVXdSSxFnm5m4dZL", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022056a156e3715c7e280dc5ada73fec5140c0d7d58a36a9d5c2d191e141558cf22b02206f1a3b08eb3f4ac221438eb716232c205e5574753ddfe10c215dfab5961af2b4", - "id": "3fee18fd82e1b26fff178afc44338454f947dd5cbfcfb9b26e95786d7a852a36", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 734928270761, - "fee": 0, - "recipientId": "AYcACtdVz7kyrUu1RyfyYmnSxd4TKYCiAH", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009173fbf35ca5887ba7e479349557a88564c45c0f9a2c84555ae04839fc96124d02206c2654536f9ff7f3192f2aa9ef23e612f4488263d77c342b81d0ff9638b59f93", - "id": "50743bc18dbf1b0b06b0b85da7831be155da905b66cb4c55061b2c87329b6857", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 734928270761, - "fee": 0, - "recipientId": "AJPBdGiVCiYb2Zo4ZWs4wQwGpiJxuPT6uC", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022001bc153ee897c9a8088bd7d83db9b0beba950a777c79a3e19168a280c9a58929022028538f8aa4e9932b2a779622b99bdfedc92e16cc05aca8e835ed647a41419872", - "id": "aa2748fd0d2f735f8937046030459798b03ff7ace43ab7a6858a1d63cf38965d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 735075285817, - "fee": 0, - "recipientId": "AXe51e9NEuXQPFhY6faMfWJNAru1ccTJ3a", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022008f25a62cf8b062f8264aabdbdfd46a3b7d8ac5086031e0465afb0f9e30403870220207ae7d8f21ef03284ef94bcfa9d0d370dbc4e5b795b183decdd446f89b26fea", - "id": "0dbe3c763cdab7222f45d7062cf94bdd72a5ed8895fa55b268e2bc7626198c22", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 735075285817, - "fee": 0, - "recipientId": "AHPpn51yRx8UnegXydxpXiivh9xnuyshH9", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203f1447733e52b6d8529e108e9a59259656da14f448adbeccd11cdf3770afed200220236c460323b60286ee1667c51bf1d8a61e7c7ccc88c90195241db585e886ee3f", - "id": "8e61c93cec0bc73a53710fd0d35fa0d4dff90a240513d20964782de96f256a86", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 735075285818, - "fee": 0, - "recipientId": "AKST4cpT1s7nnoP9fVYvyWSEvnKLz7vT7U", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205559fddbf5dc0f93eef3713edaad47980b79f81dea309ec15dccfd4e04fb0d76022018c9a630b655ca36b9865b304ac7a256fce6282779092790079d6dc78fc4cbe3", - "id": "6ca20d3c6119bc42cf161f48f4d82c73f005d8e9e5f602df32fa5cc0bcefa9ee", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 735075285818, - "fee": 0, - "recipientId": "AGN9gWA67X1BxSdVNjDwuQrpJKr6waamAs", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bbb6b489d4f1f55424f74280ab00615213d7c008718567eed1b849a66c9c0ce0022008129948f42b4be5837584c37bcc415d343c336d7a6988f1bc7ef69a698c188b", - "id": "031a5500249e80688c627180ecc8d3cb9cfa38c5ed42ea10d2624ecae5342fc9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 735075285818, - "fee": 0, - "recipientId": "ATHa3HMBEGDM4BQ3VcH1rAY6FdviPyDg32", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100948cf4bd55210d34820da658b900d4d4dfc4f2b538b9f8281189abf139fec2070220749e0a1a746c346b324aa393796276ea95f449dd11058d1857bba366fd228977", - "id": "ac676439da598343cd82aeaad1f306e6d1fa15d8db51bfaa967de14572af508f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 735075285820, - "fee": 0, - "recipientId": "AdezHrqbRQdooEMbLf5HEQjStMcWtKaUrs", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a0265aa6bcc52dd3ce524eb0902b08a00139f01e8078412acb0d48021bd54100022061fd1f5fd3c0abfdbf11fe56932ed39ae9b25d468f9e9d2fe00a96b96b60e3ce", - "id": "e9d8c9795d3f32cbbd22b6811801dea2d4b99a3b5dd66013d8e1bf75a12fa573", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 743982782620, - "fee": 0, - "recipientId": "AMefnVvdW3xQzRnfy3RqzjpEjUayGp62cz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022046a419c537be06463ceef560a05950552f61b5429f7e2b4a2aa09ea2ad1d3d0f022038881350fc45a81a7c1c0ad407f258b100b87acb496b8ec6e6e62958468c55dc", - "id": "edd887ebf23a14ee72232666b61754f7835b4cfa4034f7294fc9bd9a30ac1f10", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 758597694964, - "fee": 0, - "recipientId": "Ae9YVNX8GAVUiVH4wA3PqeSMGTDTDeUfeC", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022009cc850c616124e30c7a173d679234436dacba9fe466a501b2f645f01a59944d0220042cc6acd2cc25538201006a7a1df425d3ffce08a4408b1f5fdeb73d27d7cfa6", - "id": "ae6f8253bd2b564690008ff3ada0199ae1389457eba14431897332e6588e111a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 762146976638, - "fee": 0, - "recipientId": "AdScYdTwx5YAJSZNXhFfCXGqNpUZfgUAun", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207aecd31e853d981b83f6b4381a7e2d6152afcb06e08f7b001ad88200aee9d33f022018b9af36be0290056ded2c835c80902c824826ec0955e53cea50f4f153fc62e2", - "id": "62c9ac8c0b9bc3590849da10c55d14ed79af7a11aed9c88d4820c043655d8d61", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 764781255703, - "fee": 0, - "recipientId": "AazxWnociyHVd8hBNgCxPqAepYe52psryF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207fa864ffe01d95a14e23efbb66ea9a22ec47b0cecdb564c8842a6c0f0cceadf4022054ff2c650fbd053bf2b022422e49977f6bc4fe147834dca0198d5c87f39169b7", - "id": "3684f2c7ebac624519c3e3fa1751718e07c66bb695649cf049238506d3866e15", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 767271583336, - "fee": 0, - "recipientId": "AKBgENszA84HHwQg1eKG7t5KnU17WLUkDN", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022015ab4dce082428342f964b9372d37588cfb5be5f916c8c936eca4d7a4457ad440220632c804557c94797efb8d041f94561b3126d0ce2105f1f51f038b49ebe387a8e", - "id": "a7d10521629488ad012d27edb9083ad6dead0b3a401089fe34401af2da0a847d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 772911580976, - "fee": 0, - "recipientId": "Aai89ucFnPch8ZHFvt5NJ6qBZKACJVYTeP", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204335c3aa664c18916e6ca785fd3c89f76b790e77511df34ffdaf5094322dc0a002204506ffc0e8c85e5a68417b78c3c231ce5c48a8a1dc417f1962bf65d6a1b5da7d", - "id": "773b21c1fca3bd52438fb17f97403e5f26aeb20693c591ee9d4e9e3ffa748be1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 772911580976, - "fee": 0, - "recipientId": "AcBCaUbM8cS4sp6s1uXELKv3CESzCD5XX8", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e01f0a0d0194e905f33d5189eb1f46da9ec86cd2520af288d3a88bdb3096d54202200f1285e4b0a6b4d3036e1fb85637725532a39e4cdffd51c324af87d4d30d0a3e", - "id": "2465e6d3f6b17da590b6f53136e4af7f893b9178bdfd0f7b5cee9f7c8ef816f5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 780058983676, - "fee": 0, - "recipientId": "AcuUUQ4hUm4wfob8z2dy2fgzVjMBYBL7CE", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022015bf2469739a014eb0002e02a27cb9873058c6ce26bcc30b491dd1c2a783a285022027942002fec0c30c3f73859f03d0b22d6e6b9aa8f2b4f996fe440ba00920e643", - "id": "1efca8a1ea9b00029fc970c5680d14f2aaff245fa0757ad0f1853d2c2b2e7053", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 783225207027, - "fee": 0, - "recipientId": "Abj2DUoYZAET4WYB73sTyr6udaGA24QSaF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207d994f0790b2279a417323e51fabe58f9406f3306b21846d986a3f7146bb2dc40220246635f1f8a5d028f72d6117a7a743410fc596e89722121f94f8643dde175495", - "id": "cafe13900519e38c9e814edf229a8a07f9f521ecedfeb8f2812d9c8a94aa7f4f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 792264143054, - "fee": 0, - "recipientId": "AH6gRrXAUiWeyi79vjBvysTKJHCny88jpo", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bfabfef46710cdcc59a2223e554e3e7cc416890cb9d2aa3ea408724bdd11d08a02205aed7c99e38a0b4ae8991c7daf53da01ba8652434d589aab0122d7c043dff201", - "id": "26c101b6192346f37ee639f28da5b53ff81ecb7ac8802389ea6ae9cc9571fc2d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 795355926543, - "fee": 0, - "recipientId": "AL9EsuL167UcKpjH2Cco6zqiFena8LoURK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220601bcb86301d0406afb27b864ff246c6bbfb8e6668be9bdd87dde634cd44b1f4022070ecc8458fed79816df9f30790a903d99ce41bd765cfc92421cf6070c7e6961a", - "id": "b745abc6ac90e5123dd8235aa5b19cf96e2419a8b7710ba03828a33dc796fb11", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 800000000000, - "fee": 0, - "recipientId": "ALgKXQivLiRjcmchi1vaRBvZDPpYcMNv3K", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009ad6dfefc8a08f6337c0fb9283a07fca5b24dda8145dbb06644284a7f91a0aba0220730b2b1411070cc0f9cb67b09d8d4c897a2dd7fdfd49523ab3b9d4f759c9803c", - "id": "fde379bb0f8057769553fbf9c7e49b62c37f33999696b0a426d6df0f1198d7e9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 800807608813, - "fee": 0, - "recipientId": "ASNbbDUUV537CWVyJhW4nEBaoqphsfdGoF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205c5f5fbe87eb844f435035cee3b081a0594c30581165ab0c5ee5eede125c99b702202db7822920671f90a6bc5ef5056b6c91599f7f48b1441ce679e8aa6b0ca53e19", - "id": "0d34c831223debbc92140029644e84dab7664c4af7cb70acd1271b023df5777d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 807327846398, - "fee": 0, - "recipientId": "AaJkUNWfyrzaF8VeoujbCUB96bncRBxJwb", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204b973689c3730f93177aae4292752724a4ae4f313a98f70b8602a63d2fcd7898022061fc16b87f2e06b4f2adb56ea4e15de9ee8858b079b7783c749575c6fc8e647c", - "id": "784dabe70b09163d177db73a00dcdc56ec59620d25ea31e271fe15a479c31f35", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 817711112949, - "fee": 0, - "recipientId": "AY25aryyTvvZUjDtbG53zbpigrenCBXbEd", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e23b475b7f57825c23c18dd4adc239468a8d185f43f5fc35b815f8d9bc04d03d02205e27da5cec444f1fa6941ac1b28c6536b5c4e56c9908099aeedea3af4c8943bc", - "id": "50fd6a37dba73576473f5d47c3a08ae07e39b1c5451d50fdcf5e9d3c1469a521", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 817938318037, - "fee": 0, - "recipientId": "AGTFXFtkqYmcG6wZowdinGwb9K56rWbSjH", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c8908d3537c932ca3d4de3b28d09da5561604463bd531024a992be58de1802ae02202b07302aeb41d0a3fe01a7a519a0b19e14d726918dac560e79d575e5c68412f9", - "id": "9352eb84bca23a8932cba246fca96482c36b3c55cebf9545ab3260968c223b04", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 828092442164, - "fee": 0, - "recipientId": "AHNQc1eUKgSJWr7BiopwP3fCLm72NRGFnu", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022078d3cd96dfe78a40223df2d36967e46708c8ed21dd1e098e5994c9832b2bef1602200ac6d2c0ba0763740046aae684d542a3332ceaa98cd2d62d0217a75c57411e54", - "id": "190407bed322d0dc2626669ba6de2627c6ed18f86d158bdc4e3d24e4e49bbcdf", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 833532097131, - "fee": 0, - "recipientId": "AHSytS1gD6vMjHtcpkQ86V3tTLVbVy4UV7", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a35919ee25620b1cbf5f00d83d28329563805282f1de3c6a14c3f3718d5fb671022003b1c6755cc0d0f7451fc3c16e41cf2b5a771c4596063a2a3458544bf927dfd4", - "id": "c36f7006aa4ada2eda35951c1d8e88194598959783880832ebfe42357baa5661", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 838975285818, - "fee": 0, - "recipientId": "Accz8foPxAgb53QZq55r2J3MHyhFJhWjyN", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d38fbd8b4e808a943dc3ed39967191c50ab52d5049bcb95f8c7896232ad9a25102204ce41e68a375a7a77d12ac92c4eb5a32a87dd2646d481b4dd6a4fd9b28e0a8f4", - "id": "677b8dd3b694d5ca4f4a2d890d99038f0d3529372ca65f21ad0669eb2a703d4b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 843238131545, - "fee": 0, - "recipientId": "AVWJBw2eQiWU9dhYYiPZk6YDKgT8zWuJpr", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100908c7959593e47247fcc045d6c5d14798668266960532130f43c8f03ee9e4e3802205da61bf5c85eaafea399d9a0f6ba0002baa6f011fc6c10d0367197768d5fc7f1", - "id": "442e380e1881f46f13924593a158c58ab704e76686258a9e1b33b645c9f1913e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 861005612452, - "fee": 0, - "recipientId": "APLNgrLNdVJUcAg8n79DRo5d8krXTS9jBP", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210082d6450fb65d0734f8c6d7d7ddab2438055b37ba420a3283b73b7dd236cc855602203663d2b76b2c2e681e78f016bf74a300587259281d73be10a8ae1841db720bbd", - "id": "53462cdbd50f84cc911007ef46be4622b31fecc18415d27f7937b5c36a9c1c38", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 867237104113, - "fee": 0, - "recipientId": "ANnnZMq2TBZhCoQkrXtsuhKYVvX7ii5RKG", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220054a3b0e8eb109670e0fb452e1e58b8b82048aaa531c4238a011ade0f3c7662d0220711ad6eeeaf8888a8774cd572606e7ba0613d737d3255367a96542b95181d5b6", - "id": "e6c1a70720897231182fbac0ff131552e938891ca01be2fcd96afdf3ce2079b1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 868945860370, - "fee": 0, - "recipientId": "AdWni6sVGmYL1iWEeVAt5erYacKrNE6CHY", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022042ef637e7595b772dc0f2c6edd1d1cdf05decd33f67e595c94abc1357baab2c702204e8bd0a77224dd1bf329ead41f7d03ab79b61f3bbb4a607d9b1ec06978fe59c3", - "id": "93ea865c088bdf14d07332d702448dc31dceeed8f265334e3df8b859f06a048b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 871419919728, - "fee": 0, - "recipientId": "ALBGoFokLTX1VeswH5LRBtk3uEUitoYy5U", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022036757f788fe0baf560512809ee624ea8efa84ec50dc22f8c8d14bcf6daaae4c7022011760ffdfd5f5c283ae6561b017036027b62b70a1281bcfb7cf9fee718400800", - "id": "75dd4fc72a01bbd3b6faaf4adf4fdea8c88bb2a6708d15f124005079cb9cfc2f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 875407840383, - "fee": 0, - "recipientId": "AStpm2Vziqqgtm4Tc3xw8poVh9XUBGpode", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206fd3e896284765a4a81713111ed9bfac25e977e14b70b2db0febea67d8e14893022055d0b6b689b94312aa2f360f5930c43341499b6551f74cee2d2469527f5699e8", - "id": "85f1956ed442417404c08f274e8b70f65b139a35ce77b85bea78f9b913e4e4f6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 877995100899, - "fee": 0, - "recipientId": "AYGjRAJcBQokKnpxnEWvxsLYohpa3R22n9", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220563f3bacdff499019a348d4c9feb8c965c3aa80385328d1ae7fe2d1abd7b133a022078402ff3128df4b1bd885b3abf5d79c3318d6255ef03f6aa2c4148b323569a7f", - "id": "92ab5e8f2f16a9e0b3e8a127101142184a3f2da044a47bc9ff72da0afdd5c0aa", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 879811609594, - "fee": 0, - "recipientId": "AN3odZjMnK4NY6oHHG5hKPySNbH6DvfcyA", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022076f3867eb0610cfb8c5ce5be956ac5b2c37561fdd58525db87c5c89e26af7c0002203635be4d862d33fdca979630ae2f08f43a8375f35632af2ba59b148045bf1f24", - "id": "ad3ab65ab8c782d704ebcb4f1ad69d54a225ca2e669aaaac22724b946b244508", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 881943327924, - "fee": 0, - "recipientId": "AdEnEMDqFd9FGjuTAdcLgVeSYB4Nroj5qR", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022022e4869dc0dba943de4d58bbad7c4252e5a7e29a4cc415940ec3ec947b4f427d02204dd4c32404556aa475eb6eee346ca5b408e6541d7078cb03af4c8acf72ce87b2", - "id": "7deba86a243c8bcf2c222986ba4fed01192817061e1721c1d42e55b04678c85d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 882010152950, - "fee": 0, - "recipientId": "AUNsfAkq6Pcqeew41Qb7fMMTtsjKMfLuBU", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203abb628561fafe13dcce54fb723168690a9a9e766bd4ec3a250a5b496abdfb33022017a450e3a2f31be397e2696b79535dc956c2faa59ed503ac81d57fd8c3078c23", - "id": "55dd0e380d2c6b884a719461ceb712f3c6ee0518841ba10f81f1f3c3cd044907", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 882090342981, - "fee": 0, - "recipientId": "AYxtKAvHGgKmjAyszFLeeUdrBrcx2T4HMj", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202dbbb29509d4944ae73a2957f94bc821480f99552f77b7b2576b63703832fe9902207c65af1e634a4f3f2140146658865b577d8fd0ab88563d943ac6ab9f5c2082d8", - "id": "ae02e0d07ada325ac33ee462c57bbfe4b175943bad966e7930af9b51a6a83888", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 882090342981, - "fee": 0, - "recipientId": "AQ9dsnRjod2zb1jYmQouSeqMECCjczHTe4", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204ca7df8131d2dbae7148bb7d600fd2ae9504838ae6e08a36ce5038898dface6302203016472420df3880b64e341b28ae44d9ba50a218a1be088692613fa69ab95245", - "id": "ccce88fed70924e2c19f5ce8ccde69f7c74377cbb3f698fb678dd6455cb74ae6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 895174683068, - "fee": 0, - "recipientId": "AX2ZAQGv5aZfuB7uEMrKVBJixqGjaD7QTk", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204000a219545ece7dfcabaf8d2bec29a6ac6d360046298e81f1fa847dc260bec9022004193aacae42218212effa3649334b8e5e865ca8018f4cc10a704e0bf464aac0", - "id": "fe23b603d883fcb3e0f2dcbd203a338e6402e95abd9c2d32ff36352b707ce12c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 895341745634, - "fee": 0, - "recipientId": "AKgTzN7RNMLvv4S8QzRHqvtP5Kx4KFeufC", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210086703eb4f783d915efde3bc0521c44c2c1fa7f09ef0fa0cce1b83c0567e55ea102203402af173407f63130b753f8e503acf6ab9272d4cf540ca2da5f76c5da2704a8", - "id": "e61c6ec80612d5006059f9551b9f34e928aeb6424694f773c56500bf2037e776", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 896791848697, - "fee": 0, - "recipientId": "ARyJArFNkcQUDDpQdLGL47BF4ZVXJ3y3fm", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022053fde5e97462ffe6d69adc37b4923118269fd9d858ecbca3b7ec8f9fd88e1cde022047da7a3a640d20856f1f267e2f06178d1ffefee2177e8a15b2957f24f83cfa0b", - "id": "45eed794987ba92e4d579a7aa21bf890bda28e3028a3139be7a4bae6ef232ac3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 900000000000, - "fee": 0, - "recipientId": "AKJn3FRweuCdDcNkjSGH36vXb5M8ZBjbcT", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009e08602bf0177bd5ff9b95c0f173333563073c193eec6574a25b8a4c7453a15b0220778701ddbc1a30c3b0366dd7d7ad654b0066e4db8f8a884a668425c95dc57028", - "id": "5d3beece4057da03840834c3035f4b580872492de87afacbe44d540ed75453be", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 903190511125, - "fee": 0, - "recipientId": "ANN62YesPBDVEsW12iTwfwRaxhgTQGC6B8", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100cd15bb91ab56975fb578938b0ba8ec406c684015cc4a84386d93a1fd5660ab18022017b0b3c53b8d834a2a830676ccfcf346d21610f55e96c25eec771585d6993c87", - "id": "f7b3f239d399a69d954994d12753be50a09d71241516f3bc49dcc6fcdefb7d44", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 912865546892, - "fee": 0, - "recipientId": "ARuGpfJJNMi3BFFbsgS5BeHU2c5ASxcE3x", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008d232047da315e33fe38b625c3bebad7400b03cbbd0b046aa0c38e972d5843f20220261e30aa301f0f4956d1f95cf3e4ffc50f0a6d6dcb2f0175e6e589704eb88023", - "id": "e2c3a809125902671f2e0242d8cbafaed3fcbe90c659a782e9de638bca44ef44", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 913029661747, - "fee": 0, - "recipientId": "ASycZbKLnRNbYdYgek8YJQzvyNFwjcQGAt", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b27e1b308e009e6cdb6ebbe5b5ad0b81140bfb107e13ee7b044cf93d3152fd8802207b01d59ad8c5f909c37114ac86e763ad66c5398601d99fb1338e5944cd8810fb", - "id": "0a2f7813b32d7506cd8f633d7938b81c89d8aea083e830c65790ebaf4c6e7795", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 916154978619, - "fee": 0, - "recipientId": "APepu8ruATApkWtKxJhiU5CPYcFMfH5tfu", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100dba1440e8f6a7a3d4796995d180099e8abef17fff1f9d1c5d8c69cb3132cd1c10220351bb3f699b4dd023ad2ab75172a2c2f4d0854fd554ae437d11b3bd573f8aef8", - "id": "92f849292f6fc6ccb43b4f0feb73d9a875024a8aa9b32e4b4023936a71e2ce95", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 920325036170, - "fee": 0, - "recipientId": "AaDbKSFY6xA2UJgeBSE8r6n8wX65Py1feQ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ded1448f1d4b184b39b787c38e550308b2e458d19ffd23b403c8d8c836cbdce1022002d021095c60fbe111946539cbefa09e5202a7261d44df441cd1996c01e3e2d8", - "id": "879e49379ae26a5f8120f6a4535400fb01357eee8b4aae9322d51e149fb9a227", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 921784408416, - "fee": 0, - "recipientId": "AJGxTV9yTgqZWNiXifF9JM8U2vdPssLyEz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201d722c68e0a469d9bfc0a67bf0de5c63c4a969b2942c30cbab883fccff7e733e02202d9cf49d2fee94fb552e356cc09291a1a0cefd9fa5ca98af311a951433c6b3f0", - "id": "8e0a8082e102ee10405d32ba82988839770f5f8a4736b556ca64ea6b674d90a0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 922643224501, - "fee": 0, - "recipientId": "AbsrMsgKck1shqR8NwDm1ntDApWxGSZW3e", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009cd6e7355be59b5624201572983708b730e673c00b779c20273d94d4c34e9e4802202685f9d0798d3e167b787234885618a79d7f9967adc21786c7b127edaab39687", - "id": "59d8c7f34718a9845f8c670513b377e70b9f988c86007aad583f6318680be534", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 928155414454, - "fee": 0, - "recipientId": "Aaj14mdqcmAtZH7LnQqdWT3qf9BFmyR7hG", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d10065682321cd83ecf696a90d06022a557d0e6f64864be26aca6d3e7958348d02201eb4018574d93c83eb706107bf2108a5f48529782fc2b03a4c55482f49cc4ba7", - "id": "65e3b5466ee3e56a4646686a8bed5a279368eea489d49ef0e093d3c6848a8ddf", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 932790490195, - "fee": 0, - "recipientId": "AdQDEejPjoMpLg8cMDPJV7tyG2zLQmuDjg", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e668f456c6c608cae80c8c429f20cd6b38690f069c232a8d8ecb57def6986579022015f6758e04f379ff651fa51c06ae5b9f64b48203dead52a732f08d88fd5c7ac0", - "id": "f14cf4caeb5874d8bd03184939da80ee11ff146580438247ecb81af6dc116176", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 939660273449, - "fee": 0, - "recipientId": "AdGeDcDSCeYBfS2DA5Dd9VZ5SwvgmAgCz4", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100de0fa78884cbade7879b174697493b0d54e6810a8a5b1e9675b04d5a98668fe30220520492f5cf1c91d22bfe2d63edec163d25a09ab5c74338f20f3dc1ccb3a80e0e", - "id": "e11a97c65477cb5de932daf09eb0f377e14cb233813ddb680ffc030661dcd44b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 940453697923, - "fee": 0, - "recipientId": "AbfshU5wgXcrhfWNDjrkrvN52y3EScpg5V", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206cc11c0bf89c528f9efc84688be77e19339a847328790b7cdd407bfbbebfcdf8022057b0752aab9c9592f2387078df66d2ac5e40e49928df72a53b769b942cc40c1b", - "id": "1442d74ed4130eb43a84c53baa8ae6db3fc088d2ac900423f8b69953aa61a84b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 944039590123, - "fee": 0, - "recipientId": "AKj53MkBzDfkDf1SLXyMf5DnkC7Ra9cqug", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201edebd2222f9e616ed293c9c55f35c7cdfd6f0b4e739a588d912dc9761fe4dba02205b7b844d19d075b195a5cd94799d40c3292ca05f0b3bfdb4736739b7869e9d73", - "id": "8f981304ad1db95e27e2fca8bee4e51a421d0c8a406921d2e8aea3318e45fe9f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 944903185065, - "fee": 0, - "recipientId": "AUTtCwMqXuWDskRBVkdvDjCdMPz21Qqn1B", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220455ff666f653f5e990e60a35c440270b625b844ac3154e4d1a959e490fa11a1502203980c454fd84512adb7659279c031f72b23438cb3d5055238b34966837337856", - "id": "1741b8faaca3c787f7abc73fb5a926f1669b4c7ad363b2ff8db9c04f084b3f07", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 954261371043, - "fee": 0, - "recipientId": "ASdMtEgrbnpJzNg8ZbcA9wec7QcvgXBio7", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202a01b72970b65264a10f17e18db74bff086e4908e01ea391baea9f67528abb4a02200a63fe887e4aff1fcbe7235e72d58a6ca6cef97e3f0d7c89f7792afdba1cd41c", - "id": "cef4106da76091c4ea826c2f1cd235a182f20634765bf8f6183a3c767fe36e55", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 956921007076, - "fee": 0, - "recipientId": "AU3A2mNv21o4SkKrPjjumxAMSx9dNSJkbM", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009fb21352f67025aa234098c53b941d99b9afd9acd26e9a66fa08209561bacba0022067f9ec9f3b46d62bb5c30915f80b0da84b9efa71e6b9f357efc7089827803c40", - "id": "6f0e865e6a5d3b5564ebcc4f2e2c0aa6618418fa561284db9780fef7bf553f5b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 964800000000, - "fee": 0, - "recipientId": "AXUg9JM1fE7XbUPCsDeC3UxXCMv4tx8bgv", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206fc92484602f29f7b50a5ffc503c5d1a231dc0e54802d9aeb373ff327602325002204efccab14943708ca58cbf02824c6d1590dc1090bc0470bbfa7760f14fa629d8", - "id": "a059a31470b9a196a3894a864cd22eb0c92e159e776c7406f20cb92db7a54dc0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 967490707524, - "fee": 0, - "recipientId": "ASvJ5NScpJtLy2WQV6AyBtzh5FzKxP5N4H", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100873f68bcdf9688710966b018209079b9a4ebea3c7b49e281b220391537b5b6a7022033d3d9d2463907307693dcfdc80edf4b864cb92cab31bf89ccc3c260ba72b91b", - "id": "c996d17aeb1aebda108edbd8af91fae13aad80b959a5f6cd1b2f8152cf4e829b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 969700000000, - "fee": 0, - "recipientId": "AQinQgE4vAbgTXaUKCj1QabiQbpsAT8fzD", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022041619c75759c53ae76620fabf4c133b4bc2b8cf890f3c574a4ac706436a6e84e02204386f909f08ca380e03cc52330fa553bae5bc73ff9cfc5dbef030be47a4b934a", - "id": "ae59ee72e6a0f70f2a9ec115af54c17664b2515e5af10f3c66ae0ec5687fe005", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 984780889208, - "fee": 0, - "recipientId": "ATunEsg5YC1zoyYSuRap3zJyWmpzQkz9tT", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f8ceeea56b0a689d42103fafd6a58cd236a14183201ede721f666309aa66769302204c345a794bd8c7aa04e3e734bbdac62cb1606cb1375ac66d4a405c9bf515c5fc", - "id": "5711b79607001f91ae6452e19d06b65a23310ffa4ad08acc8a30b24ee67bc3ca", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 993823555585, - "fee": 0, - "recipientId": "AWUKR4R5oo99dRkwRkvTvTJ6ffYBNv7zPF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100eec0a2a2274589c7b1daa03e60382327c2a429daf422815f969a16a2cbe3bcab02201c2cc1ea66118960117cc6c00d5cbabb878e48783e37086bb7fe0e3ad3acb30a", - "id": "a71624227e6daceb049d5ae4cb83a6a6e9a04659f29e53a1fe30f4746f3dc194", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 998232238139, - "fee": 0, - "recipientId": "Ada2uLW2F4xsEVqmYhvk2y1gnjbQbeEGny", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008bcccaa6dc17a49848a90005e91d25cc5a2f3c50af365fb9e84bbd18f8cf19530220582fbdc99507556d418996f4aec523965f88c88efc5248d779526deff09b1460", - "id": "bd15a7f964c5ccfeeff073d396ccb099886e5bd562af5b66717f93dbb20e9bf5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1000000000000, - "fee": 0, - "recipientId": "AV5UwZD1wxVNcDjjXi4K136dBPjT8BUSMp", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022034080e7de43853b75340e97ad6a2823b8d08290c7050fb0b4e7be311d95067c0022035815502cfbadf9851555f9bf30b82544aacfa1c5eccac34377d86520b83d873", - "id": "494e41b53f142601472c50a49c966c2af768701a283d28dac2b09492b902f9f5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1001660153985, - "fee": 0, - "recipientId": "AKmat4frPHmBjrfwuMCk4uBRTPp2zAznfu", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203cd5409d500d5b5fbf817476f4ea709e7e09cdddcb93ebd2a1dd7cec3f46dd0f022051cb3f390cf2d0d9162d5409aded948a48fa466b25186498de1e35a0d360ea24", - "id": "6a948937670927c06e5c1358667bb2f7611dd7387a405bc429b25f43e4e56827", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1004564574086, - "fee": 0, - "recipientId": "AHnrwSwuuXKTxxL41xgqAwAjHBc7nsYCDU", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d73919bc931145f7dc02745832964a7cf2091a3540eacc25569f6fad1c954fd002202ca78633ac126f1a38818b7712572f0b17887e187dddfcfceaa4c92521b4c80e", - "id": "18f0983e59f657e62c56a4b3257d3a14cbbc405b4066f20d687852162c449a43", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1017708933348, - "fee": 0, - "recipientId": "AJnv4qX1hnuecj66hRM9mgLXfYUdEqWr8p", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022012492f457272b7d233a1324f49e7b1e04fcd80d555d91a05b1392b6559b62dcd0220530e2dab22b207a45dfeed314678a22822eb0118715ef7a87fb7bcdaf97cbb93", - "id": "5caa6ca9681dd44dc6bc566caf1289fa243e6448d9d9c5d19200800341f52dea", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1022309295002, - "fee": 0, - "recipientId": "AaZ88ShGS4izM2RAjdRb1v2XBDjeWNRiCS", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022022def44bc8637552e51371d00a3a39b5cd77cbbbf0166ad4f3431933ac94181f022055884070dc5ca41b51cfe9c8f363b03209c76b97c6dbd02476fe94397c1e4bee", - "id": "c09ea42b2ef6849ce9724fcb6e560e83c211df8c31bba0eb5853c1dbdfc2c9ff", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1024417009424, - "fee": 0, - "recipientId": "AXdzLoCsCHkLrhLGW7SbqQF7ycQMdHj5CW", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008b4844b7dd8265790126f46b21ac699ac941d6b9e657ac3efc52ed7c874535e102204c3ac47d179cd180370993b8e7d2e322b584e3848b2d7a0ecb86f8f74169a08f", - "id": "3ad1bc54683cb14a70774636d880c11c2aa3a236ee3af42c524dfec3dbc7bd20", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1026165099001, - "fee": 0, - "recipientId": "Ab5PAShY5CX52trT6YmEB8Xu25t5tM5HjJ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f74a6839a13eb8a3df1b72407585ecddf0893ed4fa3ee32bc30a66f7c49c609a022051dcb39162f3aae1ba1269a4496eb62db24ed3c176e488419bf19250fef3680f", - "id": "fa7af6fc72bd13847f83a7e5b0a9c689315429725a6b6f87101f9f5210a67330", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1027972402698, - "fee": 0, - "recipientId": "AXk5qyz8GftcgVTEzUDhpfWZL52ZJMcN4G", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206a1ff97dae426570695a4585f2e9e5734c742bb8be2981a757890cf99a2d9fe402200f7e67d7241d9fbef45f4c0f51f7c86827649cb337ce8a902f242d6367b48ba7", - "id": "ace052f0c133b5875489f3a7397b48ade06a5ea9465c0860a290b3d34a5f927c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1028958385088, - "fee": 0, - "recipientId": "AK5NYn7AJdvYWy3DyESmvQb2SDf7PxRa5J", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009fb2a2c3952a7df061783e1f6acc7d7ebb1db208f242c9db13e82e0ae515eb8e022078dcba5d7c2487202d4a950b66cb3ba4e4cc8718bac248605405ada6131e617e", - "id": "e1230c88023ca1225588bd7a9dd7ddf53bfc6f93c0610c3dfdccf630f943ac09", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1029105400145, - "fee": 0, - "recipientId": "ARAkS27JzjnmAGTBPMQmKJgSwCgD6SNUDi", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e2fc6163e74ec9cb17eeda9bf171e5b4789200831d5ab73aa00356cb911f884102204a9b214977eedc6106cb9b6c416ee8a14d000e854be082764d65109f88ec2206", - "id": "6c8816c6f0b88e43ad7b4b790bcb539d7be48a61a830b9f4d32ecec9c595552d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1029105400145, - "fee": 0, - "recipientId": "Ac78t9XdQCAVc3aQVwrvB8HTUqSoDqiHAq", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bb701ab9fe8c4f9dc7b1cefa260c8934b2bc4c2aad31fd607ded1c768526aeb902203b1b477789a7e8a69c02fca7bb5a8d348785a975e097b4e6cea3f2515d68ed82", - "id": "5005846db55b1f7f32d13b95ec2ed595dc8927902d1650fe366356fb6cdaa5fa", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1029518225860, - "fee": 0, - "recipientId": "AWPoYy3qsD94b5k2kbNuvUzZVh9SnQjBsa", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200c1463b83dac90ff2ce43bf2743c30616939b4a9a1c3dbee8ce27ab1015ef3bf02203c474948d5240127b76ea1f19ecbfff55033dcb3257faf741c0d73c0c4fbdf83", - "id": "fa84fe86327ff3ff43663f888df8c167f5567aca4f61c02157a7c4403af4f29a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1030548774634, - "fee": 0, - "recipientId": "AdPywJjNoHDnCzYua5irL8ShvUx9KSHnc5", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202199eca47200eb76742b28d1b0767359f588e9c2e6b19405a40ee32816cae9a402204d9b9980c2cf193f6cc38e6ca8796b0e192b055bec5b0c52eeb31b95da466bdd", - "id": "c65d4ec488a9d907cdbadcb865f4732ee6a0ea2edb8baeafa1e7e76c37237c2a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1031249725970, - "fee": 0, - "recipientId": "ANLRw9k91KW3snAC3NCPhhmzgg9cMgdg6j", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022052dd06e5866b27fe253f1b64dae8d3e1924bdd4666062d7e9384bf77cad2b86102201cea424f053cd156ac65ffbeb703d245b14bb2ba0575aa90d9613a7e8fe4a7c5", - "id": "5c7bcfce2963fff9eb502e79dcef563274a4c409a93334c3937c3019cbf55185", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1039396454147, - "fee": 0, - "recipientId": "AaAUZ1yWFqde5BYXbbUEWFB9FGGpQCcScK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008bf00e9e9954748def806ae413bc38629058e7c2daf5f56c34991b81a61fb7e0022015c587a5adb9e7cc11e4da7f2b6e5f1bc1e8f4f36c99a3fc15fef28390dd2c46", - "id": "7dbbdba5101c67507ff51e5375318d0aa8a9e3f25de019dd16af2fb62077d6d7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1040719589660, - "fee": 0, - "recipientId": "ALHQ1oorGCEvSPsFNWdYPwRyk3rVvMj2B6", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204a48b4446d976bce24fe26d4e666947e59567c29d55254c4a4d986ed2e0af1cb02203147efd709c8bb850861b19c71997a5780e606889ccabddb70ea4f5beebcffeb", - "id": "b06a6b4aa2f8d71c39b5ab05f1535e521cea4d1a6cd304d56fcb02518e5d37ee", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1041975741930, - "fee": 0, - "recipientId": "AZVgPvHxJNtfVaG2YkJn7zPdFdUtk3jL53", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206c33c5318130b31313a7431a249900519d511ee8f2a43986ee37ca6d534aeca802204b833d1bd726a50e0a76edd62600031e215239c7e823c556e53aa225cf5d0691", - "id": "f9bf139b97f3dee7e627fab2bd00ad2ee7b2ca61ce259c8a9da4c0e33f8e6680", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1049727197915, - "fee": 0, - "recipientId": "AQrP3AzVXbRbps1UzCfjDFoCCnc1f19a5o", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220333ec0cb3e0cdf420ae2a89997b32607a748333fde1bd62becec1e8231b5a98d02201a424700aba7467ea4141d1d847156b83da94c26acb464cef725f4f8ab36f651", - "id": "69baefe519ef9cf9c530931b69908efef4ba290f89a014f2fe7c93f7d4f4d3da", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1078361899572, - "fee": 0, - "recipientId": "AXhwWcy8HhMYLbiWhUKnZPw28o6HfGJjaN", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008ebdc6ea8c2c50965088daffa9b2ec09f1a4548729284ba4aa2e9adf38cb82510220524ce025795ef5cfb044667870375c205818ba4da875e0c3cc4ad6283673d884", - "id": "fbcced1418f85f3aeb54a66bceb7bfee00708ba6eeaa3d5b7aa85cc841a14536", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1102465913669, - "fee": 0, - "recipientId": "ANMuAyS1SD2CfJvuZSi6RQb261gvFSJt1X", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205e2895cfc0f9708d2af9c6008451ffa63461201ccebd7037c81196198f3c25a00220151c81744a95f78670811a8b66995a0f2229be460d5dfd712a7e744f5d56b706", - "id": "9c19d49856030d283b8772771d575a16c5356413501e2acd042835873fc02cbf", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1102759943784, - "fee": 0, - "recipientId": "AVVhvtdbiqHm6zuFnUYbCQCsgfKs7GxJxS", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a5a5702c73261b34e2ebb67471524f509588fa51756b267c861b3cc8737c9b6e02204322f7c837883abeaa450b02e6c51e81c90c5a0d41196f046dbdbe0827e1de63", - "id": "734294bf0236a11b079ee873079c7c4c9f1b85a53750c0cd97fedcb5cdad0074", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1108427216593, - "fee": 0, - "recipientId": "AYnxyQgq1j5i7h7AEd1UcYVWeZhip2BPsy", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e3c2842f21cc80db48cc3a7fda70e84cf3fed43e68ec7486d2b6316bf94e85f2022040fb1c566bb45142bccea742c68cab0d9093a823c43d623ac6038062393cf171", - "id": "81ba05c7bfc196047ec8955ddb0ee07ab51176ed9884beac7e59d49e117e7bae", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1109838235948, - "fee": 0, - "recipientId": "AJvzuyNaWbYyC8UqDu7hPVBLtB4yEm8ovV", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008d0159ed2472012ee33ddf9230004b427c7f696bf22eccb67f3ef6320e657a33022000b1dab519c3bf58067af22ddb3fbd1df9144324ed404f0a3f67d7fb2f64f7af", - "id": "08c1f4dac6b650a1d2e5ca059fabd37ba23a2a8718ae034fa1759e32f0ecfcdf", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1110036076121, - "fee": 0, - "recipientId": "AGvTatnwQJR22A5dDEWXvWpfLCCFNu8tof", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205979870c79c60800f61d96ff866cc18b494945f2c805678b9d55650548562e6e02205a070cc7341571569be79255018a01cbe86a1330c1e2b02ad4aec2ab498910aa", - "id": "9541a811866acc0b84756a38bbf327a40cc96ae00eef7e20a540be4adce3ca69", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1110492080313, - "fee": 0, - "recipientId": "AGfChYyMy3ezANVDo7JBECGJxEYGeefPwV", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220298aa44e674cfaed265a12495d1ecd96299c34f8ee8beacb597ade8a5b22429c02203b640535bff8f890be8292c5ace7085658b1e71ed361a467945f1d6b97f5a223", - "id": "b1887110f49e04d53f26b0182321f2cc049877bc774784dd69a856ebae16d260", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1113211377847, - "fee": 0, - "recipientId": "AWhG1JweDmRpkT4jbojz4d8QshRZnf1AZr", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022060139759d9b0b550c2b108cf60902acd2da2cfbb0747e2782124c478fab51d1202202d51d8bd860b50a5822448e21f35eb97c39deab8cceabff01e30d7865a486fb3", - "id": "dd436734a516d0647539b60be1932d984dec023de8ec0b3d963bb48e2544da62", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1117612928727, - "fee": 0, - "recipientId": "AaTpLPFKjkyMqe1HAzsQvnvVDAPj7pyv6K", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206a305e6563c1eaca84abbe8141f6d16b053e58796933355afd0c004e4b4615f9022022de5f7a2dcb182a421cacb118d5606b3180e54397e535ead5fafb3808640ec5", - "id": "d946b6d8a8a3a305eaed80a1c5c856dba6b732d0a6f2976d2e1830168d242783", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1127012726593, - "fee": 0, - "recipientId": "AdwPm6G4tnxBBgtuB2UCX6kUt8KYv4vm4L", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201527ac126261312ad02383d5481500ac4bd030a800d2d1e8b0eb2e26d5986df90220666b7179f7e9eb56b0a91b43165247d2743083d37f61c773780314fc18d811fd", - "id": "228725e2039844ee5584f099930f5ab6c1c0185f78e69fd5d418c74abe97e512", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1133071775570, - "fee": 0, - "recipientId": "AQsoWhRqcDBKREvizPBDehMYfUMwpfoyts", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207f4c04ab56643d9175bd4c8179c2c3efc5743bb2b1149c38a182583c72b2ba1a02206fc46634fdd5548d2ae01950b9e9696ce6b880dcfc59abedd4c62c3757d0783b", - "id": "71e16485b6b1168dbedc07642c4dcd4295c7d6073b6b12d8c86afddbc392675a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1135911839174, - "fee": 0, - "recipientId": "AUw9RAfTKkMqFky8G7Q3s97cA1NMwsxsK1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d29979af23b1a9156c2a0223a3e5a76ecd62b666452dbc0931590730b0ef15bc02200eff2f5a2cb906c959ee855bf3261db9240d7ddefb5cc52bc1d70d295856ef99", - "id": "dfd87714aef9d4f11ee8a5383fd9cb8a467e3523a3206eaf99ec66d943bb41d6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1135911839174, - "fee": 0, - "recipientId": "AHkybDKEp9psTVmKPvHzqj47z56BY5UcEc", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205d422f18bd80adc6a12a4c8bf2f933fa5a49328137e696e1738f1e5ad0f743e602202522cbfbe59ca0e6d3080cf64011d7eada1505b5984553bcf353b871c2c6513d", - "id": "7500e3531f8b71b5f4b7aa57e9cfd47de4424a8b7eb5dca89e2f2b9e6f5dc34b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1136025441718, - "fee": 0, - "recipientId": "AKwY5JoLghksohAZG7oEG2JmgZuubyPGVp", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bf6dc08b189c178a9201df3e5c32c3e42b534a09c7f676980d47d6d53335848c0220531edd5c8315583016cefb60ed96217a578c14dfcdc4d6ed703c052cc2c2adf6", - "id": "09bb287a691f3121e58b6f0d862b98e2fc1e6a703594897dada3f2775f8ad9fd", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1136025441718, - "fee": 0, - "recipientId": "AdbutZprjH2HAM17xxtqixENbzXPW9Wys5", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100df1312b79544c50df8309c5c47227587ffd9a46eacee45787cc56e37be4c0c8502206e7679edcba8ed6fe9bb634fdfc96115005803a8c12cc6292445585761069d4c", - "id": "d77a533c624889a76d7135695ab9c70217f78ffea9319af6800249ab846800f1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1136025441718, - "fee": 0, - "recipientId": "AJknkVe3o8zMyMVvhLoeoHsgMowWV2gZj6", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220090cff705808e2057c9375917aa0e29056143e9e30a4a631cf32be7e9d8f7281022001259193a883851281a9978aa198ede0dc910db7215f1da0aa40c6eb749fdbc4", - "id": "f0813945e05653ff3b2f3e0f7be0a392e5183f5805efe5fba11d23386cfa310a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1142768005049, - "fee": 0, - "recipientId": "AKjKD5TuG62qWVFPpeca7JGeg5ZSs87F2t", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008c34f0cc07ecff34844dc8b3c59859eb986e4713d2177923de88beded8a00e4302202dda4d34149b8269f5d62b28e18b3c0eff841acb41c7d19a657f673d8f2ae735", - "id": "f456730f13cabf7d2b355c690cef499f35613b3d9943769770a371d68ceda967", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1143514373646, - "fee": 0, - "recipientId": "AVzbLnDifGM3MgLLXGJokYRRaJtCTSDtJf", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e5ac125df4c653e04fcfa0ac241245873a2ab254eeaecf65b2e55a9974627da5022026cb84adf8e3f7b3b3acfe181df6cc1e4a1d64f06e9aeed4ee3dd2fc809da404", - "id": "7084d48327cb27c550dea563c70084c1b113aac2cfc8eaee8da2d2dfd231e7c4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1166230353463, - "fee": 0, - "recipientId": "AdFkX9y3WiLxaeEsdHgTBr3fTUHSzEynGF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bc010d5be3aa4448bf37dd53b39cde7efd9112922ff340bd4271e6989ef0a6eb02200a155c4869414d9feb7359b3b7abe7a44dfeb329f70dba2f06679ed5f61943bc", - "id": "331e701070f52d72d30610165dfc9aa6c0d7077f7fefbf4daac202ed64563323", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1167544630860, - "fee": 0, - "recipientId": "ASDGBYEPJkRm16xDXW2uBtzegkmtDX7Fs3", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202e7deb619dddea19875e03f43270d03e73f37e48efaff365eda36e2962f676e60220697fe1276d816b0e5b2d6a55f06a0aedc12f76d6f228b07da41213d79d85f2a2", - "id": "4a2f713400914d0d3a66f565c2d1c9cc39a3f19c12543e2ec418110335a0b596", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1172864247614, - "fee": 0, - "recipientId": "AWbSK2Pwu5USnhcQxaraoybLz7XSRRM4JX", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100812581fa24ca31fb48b100b3bd5415e16727dd8be600f0f604c03408010055330220032db4b8659cadee9a1b949add6b4ac8fafaf0fca6774898848f3c417c36a180", - "id": "7dd6f8130f9d3f76512de04ba1c3473ca264e77b0613a2c8a86f3cd791fa659c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1176120457308, - "fee": 0, - "recipientId": "AaRaJMpnL5vKt7ADUsEAbVw3nqnLY6CvMg", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220379c7a758df66f5b8e7879119a902aa415fad87c14d36d7288b8565c90310773022074efd40722d365288b37f2e883b4a9c3aaa8dd211762b496649c63e477bf9e48", - "id": "df44e3d6aea679f21c528cb1e0831f1c2c1a92abf61ba046e05bfe4542b5cad2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1182001059596, - "fee": 0, - "recipientId": "ANZWuhTZmksp11sRpxy1JdT4mymjkdz2ys", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022004f1a606b87c3931b43aa9ad6c39c1b7a4425d7b9463d3fb1165ef64ef3515250220614138bfa85aa722ed98ce55a3a8445c39fdfea6add75b0d780e47f93a45d933", - "id": "fa762596e4127da262c6663483dacf38836d6efa88c818c9bef23d3c8c265cec", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1185739919799, - "fee": 0, - "recipientId": "AV7zqi8PG6BfB3Nud74eC2N3BQA3zi9zCa", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220246839d23affac82057511a02b479902a17d163a2df94b950e8cb7757436cbe402201fd4e87363021f4a3d341f8635d61d38010aba3ea51df5e1a10c9af1613db6c3", - "id": "d3aeecd07fcde727f3e8e8ba0852c7ff5b28adf9da80024cfea7c51f8d2c961d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1187780964457, - "fee": 0, - "recipientId": "ARyuySFRx29xHYMdL7TnKZv3xvBgrWRdep", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201235cf2bb5c1cf1c254fbe24d2337d87d100da46a7f39c211770ebcc937765b802205bbad34509bb0b7f86f8a2807e08be016273cec72bef1ab9bf5457939767832f", - "id": "b7eadc91dfd13958e6a36963095361b289c1c37c68f0aa3fca1ac4f648440ba5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1218554570073, - "fee": 0, - "recipientId": "AGqGgDyDQdj4stacZznPKNDRbdeQ7z4nfz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e43b353214830892288bbb6ac6da9459ccc205692fc81860f5551b4a6fe8f325022040f1567d23489b76539c272293702890658f1acb22ddaa3e43ec2c50ed293d14", - "id": "971db6562dad82beef13c4ec839c0d60866e141b03ec2d4a9d63c67e2fc46992", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1226105576744, - "fee": 0, - "recipientId": "AbumjiTo1eDMprMELVf5RnCwY4jFYxu5js", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205873b0046921b459e29f8227a7d59181a7abee5d75860c5f941c79a9618351dc0220795169efc3aa3651755c0563b623945bb5aa8908196cc2aa0d1fca2e87f4c6fc", - "id": "f22ec22f00452a8c00256018006eb2fd665b84932dff56090d95faffeb895a7f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1241085387065, - "fee": 0, - "recipientId": "AGNRsF3qaggSf4N9i9gpmMuGkHBKnLCYqS", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f6924dec0957658a213b7e04eb24a996018344513ea1a35163b4977bb1db709402205b8d978e83d7c5051c208c33617bf8ab611d63ab376fda7ab8a3ab6917ea0ac2", - "id": "9137dc5ad77a4c7b7946f24e7e150c2622c15b6d1765f3efa552a386dbec4552", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1248321295924, - "fee": 0, - "recipientId": "Ac9WFSBF9MPEjCD1f3G6vWCHtZNT53C538", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c9ff6cd56b1723f0bc7d5e7623bb66ca39e4561b79ed3f6b2e3fc541bd08787e02203b69a4d8b2837be4fc097bdbf174cc41c1b968626d125f497b106c30c6b8f5b7", - "id": "24d7adecccd21860742650572bf854e50a79d8edd7f23af74b7a41311b3d9917", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1251098136462, - "fee": 0, - "recipientId": "AZ8GhCcmNL9HGhLejRShEkbPL4fsDcB6Cw", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210087e8c65ba87f0af0fe5966e9d134ba62000972b7a4c5caba161f634141b2e07002205ec3411d77ffff2f5652d2074f622078dee8b8149b0f14b8dc779164ff03a3df", - "id": "912bb48bb857b0da7007fa875d86644c803c3acc59af672692cb0abe4fd684b3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1254056287561, - "fee": 0, - "recipientId": "ATRX4J1yKRWZwSsNyW2rEHwbgNxaj3SaDG", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009eaceaad92bedbcf6a3137ecef0dd7272072662bf6158c82ee8242dfd6a9265d022032b614fd8ddb333c1b7135197649a09e7cfd3ebee617f4b08753fe5011121330", - "id": "c9eb7cb21ffed293a6dc750757d50b459e5bb98dde6080ba44ea984bb5a3bb81", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1262124265749, - "fee": 0, - "recipientId": "APZXrDufYPZd4M6KK6mU3N9i1o5kypmeFB", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202dd40b2e9929255d731e525e3f2bc1d49dec2151eb3d65a58424d346b6e1ebb7022050c87541ba7b5da3e891df8b9148b9cc7f84d5e3c185172367b76afb8544c34f", - "id": "9c0e58750a4580a5efa8c5dab95ec4948feca82e6750647945584406b2b777c7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1267980213012, - "fee": 0, - "recipientId": "AW5XkGBc44rDNynQgVwVSCSyUHd8dt2ap9", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202fd98a801d3baf19fa8a77e2e0e5b8d684547744e26856719c37f37ed3e5af3202202b3f071b8daf7f5e57ea7ff8c4b54e46dac73204a72f2ceba6e5de7121f0de82", - "id": "b0ac53b52314830e2fecf20cac13d0e3c1c33795c55cc15fe575db1caf5e59a0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1275943681122, - "fee": 0, - "recipientId": "AekYhbhVD1qNL6NmWL1tUZTyopAjEjq1FD", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bc37876c3e04ed282fdab28d5412486eda4952dd02d7cb625cc642409fa0262d0220263503fb14d31513ba463c6fa2bab58838daca4a19dda7604c7708002d576073", - "id": "17bf45d5a938ff3f0e910d5a75c731196d39553d66152b74cd4f8687241e3c49", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1281318617272, - "fee": 0, - "recipientId": "AY6ZLmZrpunUg3XuQZZBSXQCqhUqwdV7Pg", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022033eb26e2f3bf589d76033dbb4ab8a726bf495a26068e54782aa26f86edc83cb70220639b4a7648c43a4aae0e4d3cca922b7274aabdaf8096cc1f99aecbb93379cf70", - "id": "027298b8d2a4425c3b04df6e1200a87e504f79fa7ddf9f8e1453be427d51b5dc", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1283452028431, - "fee": 0, - "recipientId": "AZUXnt5f7UDcqCTn8GLMqrqxFumWzc6xdV", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022061a17e5dd43b0fe5b0bd9f3a10dc22a004fa2ed372e4668ab3fe162444a4a354022025f7353468432e788a5859f589e4cd840f107b908d496bce9e08a0ae8a48f81c", - "id": "fb90e727a1fb40e9f09caabc1bdf855f85ada79315a103e6201f4b05b9bc1554", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1290283529798, - "fee": 0, - "recipientId": "ASxqjAvYhLchFzgx2jwju4GsUCSQEH3AEP", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ee3204265b27ca699266a811730b567a7cc018f07e789156820f4b55bb5dc5a6022037f1b07874363ebac4373e04aa81cdacc8e462d0c8615dec4945254fd78bfdb3", - "id": "ab1293aa990a9bcf79d2c390946ee548564abe1961227cf01b6ea0e4f99caae8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1305366622447, - "fee": 0, - "recipientId": "AKoaU94wDMKcvDdon237iQ4ygpqVEP13Y3", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bf1fcb56c3a81f457103bc035df6f52aa8f37b342db0d24b37d31ab09b345d85022033ea972e903998fd0b87d127f49638d31612c25ddbf176a8f28408da823c5382", - "id": "e33edc4c6ae458da431e7b3fa0c62bb9f04b900c071887835f9e7168cfe57076", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1307153045859, - "fee": 0, - "recipientId": "AYbyHKD2741E6HT7dM2YFnqM9mMPS2yYTd", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100eb0c6b3da5ea62ad2fc5d1d80b88627f9f88de9787c2369ad41d035fd4e984db02207b78c129b934570c69db9b52b5c47e7e922c64d9c0f6d05beb8b93507e232e3b", - "id": "b791858333e83013d9a2bc47ee3f32000e2a4b5425093c89e3615af4033bb90f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1332727985890, - "fee": 0, - "recipientId": "AMwPB8oS9vsN2mfaDVXSBZpGPx4cos5Mjt", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200ed8b29399b29160b2317c2eb075647fdff0f477844348d0c2419f6a99eabc5102205753d822e07f472849206cef2b0bd4e55f7584e793eaf40db6b1a1eb478a65b0", - "id": "393c15a74a9b38eafdb1b8bc58171ba17bfc61674792a0443abd4a4706674b0a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1353732126985, - "fee": 0, - "recipientId": "AeWDk3U68wyXzwYKrG1eFngLb1PmjEJ4NU", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f99a88f1106de48bf260252af1d7c94c0b128bf3a1293b39fa6aa3b39e855671022001c6a107ff261eaf289562ddc495ae5431f2c03eaae98e02d31d186b3891044e", - "id": "677d0d56c62618704636b51d569670848fdb4e838c1d127a643ade064730d7ae", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1363961613487, - "fee": 0, - "recipientId": "AYKGjy1QdNyw1QtuYMKEQiLeeim1PsMGse", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022072093aca8550c3ed5c93ee467e06dda194f02c5434da8b51e8ce1f425f4d6a0e02203b3969a7a176fde24aba664534284790c56aa157e3ab8a6144b2f82e14c328b1", - "id": "7f8fdbf6de9d41fca6389436ffbf660e670356a289f9b823a335e0c6f33a5fb2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1368486431867, - "fee": 0, - "recipientId": "ALqNN4gFAQ4i6keQYWt19VGVdf6srRw3fu", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203fa40db925f8076b84cb54c0442f866c882e25b67274df69d2e668d1713fb702022029b2294f08730eecb92ac2080c7f116ffd3b587ce00ff6907d0ac52b544188c3", - "id": "0ac5811bb68f5a2b9e1f9516414ae78b9b19d68c9d45abd95a83b45ee5b8245e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1373148336615, - "fee": 0, - "recipientId": "AWBBsjmyW9q9Eu6Esu738Bmhm2ZGZiwqmZ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200d6fb95c47336c8107cdda4217db3bbb5c7e6068e82d0e78c158f422d44e473b02202b2c57abf8045984d6a695a1e7396507b47c0e735a38bac8b4731217a618b922", - "id": "0d8c957621281c5dc06c45fd28fd69d0ae5bebd809f991f077d061e692470b25", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1414800000000, - "fee": 0, - "recipientId": "AYsi3kyJfxa5LvhTms3yLbVjLDcwZEuQFU", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201f2578fe3f6799f09c4e21ceb01444643676736794accb55e66065b5e2c7823d022057adec3762d37b685973d42c40d03b2c8ec414d3b4d2bb04499816845a358312", - "id": "0cf6ba6998cc228d806efa6663b4e1024aa417a15e46b5f64225dd86b2e1a2ae", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1445037440539, - "fee": 0, - "recipientId": "APNYtJ4DsTAPr7UzmPoQFsdj4GaBT4cDNw", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b55aacdbdafbf73aaa8ffdd77638cd52c2e572f68c82cce5a21182d8edef856402203605dc48ec3545efcc2ae67e9dfbc440a3e3fb89133dc2eac0969c65e9d7ea89", - "id": "a0f5649db4efa028d6eebdee0636a191c1981a80829cf6c2948c0cd3f5c14ff5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1445600000000, - "fee": 0, - "recipientId": "AZ82o4tRxNgRkjzV5GyBDkuaAgnLnUDMTx", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220713958c68f4b3f560c9592fcbda9dc2a6be93678f7e08ade6e96370c4eaa26d402202c862c934e996d1dd75dd49ed7204128b81b54b78b431076c997cf97e7747cfa", - "id": "2fac0236a0c0a9a6bcaf6401293611d1452f338213641627f8db909d47bfd8e8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1454528664622, - "fee": 0, - "recipientId": "AcCh8h1ZmQN5BeJ74rFJKE6hG9KZqbesYT", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ca4227b7da265016be665600bc3e7bb20f75d7a65d72c13da8c8d9ab4ce383e2022038ff78d1c5e601365c28d9e6d2e4337a7d9d7d0dbde45c4a4023638fa07e87cd", - "id": "8ae411cae067cb9b9f15fac37ff5e9ecf9642a135de60053c3ecddc38a498f77", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1466807483534, - "fee": 0, - "recipientId": "AFqgu6rcFy6QTXGfN1FxC6sUHY2xCW7uc1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b2f227538d4b7706be16ade8e17139e739a09afe03282f64cc5c545dd1e2dafa02205597c6a4d88ced4a5f1a2773b27b256a9138310ec40c62bde619a99f6b4914f6", - "id": "2f4c8dab500b1105cc9545363586a417fd7946615170bfad8928f68b145e067c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1467651315664, - "fee": 0, - "recipientId": "AHHrxPEi2Kj5VP6W5ZKegVh49PCV5n1jYG", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e97ad561c742835e8d25dfe5e8417892e5b73a5fd29200bd36760092bca344c702202441a37b935457517e87484b64edd8b4d8343eee4e6acfb4a231faf29a079c9e", - "id": "806e0255fad866d11ce328cadcbe4002bd5a496bb152984bb09344e890d566e7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1468250973203, - "fee": 0, - "recipientId": "AHBAMChcuEmR4GEqhyx8Kwf2uSadixGJ63", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b2484f242f0e10ff6a3c0a6ce4b32c308153788c8d844ea7b8b6f53eb2bc6013022009b277ce199b03c13cee24d7e1f0d0a519f097e783053ed8fb1e85ff174b5533", - "id": "e024d4271d53d17eee174f952a3577ce724d9f9896036a3c2cd3dfe7680b6853", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1468680421064, - "fee": 0, - "recipientId": "ANAjpvQ3YXDaQBdh3uq9UrCzpgdKbyyPec", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220329b1550fb0f30e6e506aa2a818bc28db4f5f83510cffb3e92c5237ed612e3ba02202b595447789bc432db35f5795a9f37a41dcb7c610dd1148fa46748291a08ef74", - "id": "04ca01893a22c45fc969173c7ae1d0b8f319b6ce02a52cd8439fd40bd7092452", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1468680421064, - "fee": 0, - "recipientId": "AZdWJgmHDvj77EmtJeV1WvpBP2iotiGAm1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022022abe78cf54b66aa2968b866a5f2c89b5ba498a10716b87a4f5b1b22294268b002203cc887e61beae4a9b5d1472a25fc5e9e19fa21195710159c19b433b9004fb33b", - "id": "e1a59241b4186c667e2092a767cad17bcd1e348c0e7507e57783ecaddd8b59a8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1469630945593, - "fee": 0, - "recipientId": "AJduNtdjCT13Xmn1HbRG7DA4H4g4Q6BvaV", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210090c7c64fdfd574b1a2e65c070b231e9c99a4fd26d4ab2046d985076c42fc21c90220481c1d8d1458f1dcf7adf39a52fab40a3f3d25830607ce853e20767623fa691c", - "id": "8bfc8aa67c36db1b6c8db6f65fd89687b83bbf8b45223be0984c084d2d507621", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1470003556578, - "fee": 0, - "recipientId": "Ad4QYPyJaqhXRoMKbezNvCREbK5XPa3zhy", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205188ab1d4c98d2f7c02b3825701e03b310655fbc44db5931a89432b666ffcf3f02200e1eb5e6220abf42c61477be919ab32c21626dc74dcd14a48c5d6078ac155cec", - "id": "4029404e82dedf7fa4e8df1e90238546b5fd8009d9cf6457ad8adc7504a1ac00", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1470150571634, - "fee": 0, - "recipientId": "AYF11aoSpagfi9wzfzY5PWdsYw8erLzi5N", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204973fbd8f15de9230d6a4cb6f9f3bdda3dc7e1b66abb0d5eca95ad2ab43b4d750220132d7876e371bffb26b076bcc1b68ac7b8719917b25d9f09c597ec58e37bfda4", - "id": "3112c2354cb4f918d6bab6f765a16084540663423d129abd46a7f98292f6e781", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1470150571635, - "fee": 0, - "recipientId": "Abw8geGxqiRSNtAZubYj6CM7NPeWK7VU8t", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220532893083e7e86ced90c28507bc86376998b7fe227c26d44835905dc2c4c3fe20220784051c6f98e86370580c94789326635a2737a3f1d9cbe553c9af5636e4c7a15", - "id": "508f69085efd61c4ac263c576c0da0e037ca716874a848a3ef14d7cd8fde3487", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1470150571635, - "fee": 0, - "recipientId": "AHbf1KCCkWZZYww8dBFjAbSK4oi44ATaNj", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207409532c4f13e05ccd6236cc3230bd8c4d8b9fb3335023566af2ec6a22ad545302202dbdccf7e42d4aade9dff05a5a16c5c96e9b8050dd5b2437f3471097f8ad4211", - "id": "dc3acba04a593a641812a6390fa5b0e63837447f08914576425715f8887c02a3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1470150571635, - "fee": 0, - "recipientId": "AP3uaTgqBZiPz6TFYNJ8zNNcZiNBidyLEa", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d44633e422ddb30db8bff2241ffec1290600ac1307393452dc7754e7c0928177022024cf31e3fa2a243218375cc5e66c8f0dfa1df07ec5480196884309b54a4068e8", - "id": "9273a1dda6e37f1e98206d1c5c89f5c36d869a7a4e59d1134875cfb923b9fff1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1470150571635, - "fee": 0, - "recipientId": "AGuHXDf1v7Hwfobh3P337B2579DBPpjSVs", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220278be19de5f1b004e82a31fbb872d3bc14f516c26b56dfd10fc69fa5c724252e02203dddd4e05320b906a2a3945e52b412e12760c0be9e64c9286afa2ed1b907a43c", - "id": "6dfe84e6247aee21e6df75a25030ac199eb74a7079b01df62095fe507c0f3b53", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1470738631864, - "fee": 0, - "recipientId": "AHUF2TeEq2KfWsdKJmKkyb2r7rn1ssxVTD", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c5944532bc50d544fbe393d8067585bac4c00239d96d2cec920b16b5e58be22702207e0594792578262ddd6c2ed4f443078a59b17412b835168837f58ea03268e8f2", - "id": "4e4ec69496a82528dd81743b7121fa8f8337d95a8c960e035ca863c9e9e945ed", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1471620722207, - "fee": 0, - "recipientId": "AdDYWwFY9CHymn4k8dbnShiEJZyF1gCTbe", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202febe57e819afd13b956afbf988130d00fc6894a72128595b9d3c5c10b7171ca0220127aeee9f9e67b4ced063e09300edb638be7001c3d823bf4873f2fc07c1a8052", - "id": "c3efc99b197e710b0db3510f26d78676fccff157f80a0043ff70f862b99bb48d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1476719471690, - "fee": 0, - "recipientId": "ARPtgH415JQHtdLmpkyfXm5QvUB9NWWZRY", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f9f6c9667ac8300ab05a51955046d8653cdbf93d28678be87e4892502aff5bf8022045b45cc9ba1a05612867c00cb08e881ceae4dc43e0369cad40789edc8dd3ea66", - "id": "5accfa3b6f5bde03070a900439418bfcf6e333eeaa78a55dd38dda5281f72359", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1501867950683, - "fee": 0, - "recipientId": "AaZMdjLc3oc2VU79SWzjaM8N6QQxD9JXsp", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a4efa1b07b43cdb06620c1ef7cc124505d440620ac4533bfd20d609d038b0018022050a04b85c435ad086578e698d11bcf35256cde10769c69945e144ea5b369a5b8", - "id": "99e166bd676b85006a59c88b96bafd199145e0464e120a72630a18886fe93347", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1521031173923, - "fee": 0, - "recipientId": "AHGrL6VPzSfw2kGfTZWFLN9hJxh11NLvRH", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022009ce1f620f2978fde9d02d346f69178a5339d0d34d229c9303a6bf3b0d390c7d022052bd984b7abf23e6e1f71f6c532a94eb92fa99a1b1a6683249ca16c93610e06e", - "id": "cd80da60be2804c472b529b4e41b3117d2c54b37047fe32363431735f21f4771", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1538544082336, - "fee": 0, - "recipientId": "Adse4g6EQzevtFtxCaymFKNTXfiqT8q3i3", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201e4d2f7d9ee5804329bcb4a16f1a925878dcc4f35d2462bbbc0e5dd63d6c5d86022039db901cb2fd1af4827f7c902cbf9d3758d56abaa3c2673f9a36eef29237bc4a", - "id": "16d0f95017cd07ec7ee5fb6b9a34cc55b4f3a43b7e551a8a2117fb22117cbe13", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1543246790015, - "fee": 0, - "recipientId": "ALreqLXn9X6tcgYfw7LD1WYYk7Pt77kdUd", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e5d85a41b1562fb3238d3e5fee2f4374b794aec61600ecf1eeaf745ec90b699402202b87a4c0574ee646af802ec7f4ccacfa7d6163c70f169b03384bbacde592b398", - "id": "b0078a99ab5fd8889b072e2022171a7eb345aef853d5d097a78b761989a260c6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1544792613177, - "fee": 0, - "recipientId": "AWuvoaPLVWvzGLAdJhSAY4gdLnTB5uEJdi", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a74e828d11fd810da6ce5be317d67c6795cb1bc02ebef637ff8d0f9de8c00412022073c5c9da3fae7a882d34d29ef370cede0e7b3fdef0ee054e72c659b0dbb25b71", - "id": "eb3495c47848de6b51b7d60f0918727bbd598a58625a3e308353dc27cbe2987b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1544843298795, - "fee": 0, - "recipientId": "AGWDijB8AcjiwxYTB6eybHiV9qeaFXPmsq", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201c541b0daa00bdb881cf99ccff32f8c04575e14f55e0987ddaaa9e83079ca420022042d986fda9b4e961d5487e602f78aa3c982bd4e0f9601ce33ca5b0be2cb51332", - "id": "cb64a6bb232fe4a160ef8a96b6f34bc76e8d1f1355edf969e0e1956886384aae", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1551677103335, - "fee": 0, - "recipientId": "ASqxPCEkpFj8ziJ6j4FY8pC3AmMXXT1KtR", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ecf1d28c1dfbc54be58569ec5f273008330340ab70df7629243b096e32357ff502205f9b0ed9cf1b7d7ee02b6e7a06799da79566c831cbbbb663b56b52b56f2f8632", - "id": "18fc455a58a3d42ae22c76540f497b5c1e74887612b6fb6128f6930d466fb6ca", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1557630114327, - "fee": 0, - "recipientId": "AYi6DEzFoDBwrRjj7sT78hXJGayRm3tZMm", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008ce7aae30ea9cb91237a4a646db6408a52917eef521c4575bb94e8a4a95e25b80220356cea901142679c62987b9d6f36b82029860226a46fc3e056c8120588658144", - "id": "e76471ed188cf048233c7114f4cf26d21b24e77b4e4c4e297cdc3e02645ab56b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1590480695140, - "fee": 0, - "recipientId": "AWevRPHEGJfM4rn2BRV435fKi3AAnZZuxP", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3043021f73eefa8d4dda995031746278feb82015905014306cab9766abb3d428d82fde02205852d35a40e3cf3a0113295ae54c76b4465bfbe46b67eaba24b2cb2590ff4b7a", - "id": "03d82bd53b9b6bb359d6773a6ef3fd340664e1946f8bd02cbda99e53fa35ba69", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1591288549068, - "fee": 0, - "recipientId": "AUkNoFqcTkMpPbvSx6FH5HPssnuWeUN1Wy", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f6ed9dac47067252fcf8bdaee46e90445a60b818d130f96755cfef8aed8c03830220139448b9a28051a55cf48f54f3162f7279e13e6a987a15b020d3d96a7e9cc3c9", - "id": "cba7cd24daeb4cbb94a63f729b6f4628bba3c878b09af82c6d81dcee220e8bd2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1598561495493, - "fee": 0, - "recipientId": "AGwteprikJgukHmkfyLepCefswocmkeJts", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203fd3b520f78acab6d4319657c1995b3abde676c85271af58fd8439fa41ac256502204add7191ae8d36d8028151d67956c891f67bf80b1cc3762fdf0871087a2ce8b1", - "id": "296c9e5c6c5f2fb5d7008c3b6d5191a02aa7410fc8efe451f8ced624d60de865", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1603639652925, - "fee": 0, - "recipientId": "AGQZLKtm74nXFBFvQX954zsXpBCVz6Vs6W", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100eae62745fa946e1d39d8f12c6a0f965c2dd95e709a728bd2b4aa800b8174ca0b0220529f3a59374eaf4208b8f6273d90af22dce3d183624fbe50b0ec49fa3dfc1519", - "id": "fca0223018570612e9d70ae707868624c6c7b9b42209077bfa7eedaecb136b86", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1605551439283, - "fee": 0, - "recipientId": "AH5EBGfkjr5UewU4Pkdk4X32FZUacp1wib", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022020d4c8eae1b41ba868e1796068e109d48cc208a5eeb374318c448b2aa21003d70220323a6f9fab9bc56481e8d6ff37be611370aad7957f8b0506688e20084466460d", - "id": "069a53a5dfb9ee6058e1202769e25903a014b12df9e4a815874bb4675e95ab37", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1608266337418, - "fee": 0, - "recipientId": "AXsBCKFEyrq1TmAyBHBFX6Fs2iLfg7qYsB", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200fa692b85aabb7c9cd1c23288ab8c3c51707598b7e4067ffc018f27a2428e37b02204dd58f775a2ce3340d1ece51a2133b628cf79e26df11dc75669c2f44b2e58df7", - "id": "399cd9f4dcfdd54a47fff7d51568cb6525e54332251a623a94c8497b228a88bd", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1612755177084, - "fee": 0, - "recipientId": "AbjtH1c5sw2cPFu9D7cCDNU5dchyWERKV6", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f78f0db05853c6639d8ab877bae34a93f65ec3001b62a313d0790c1f08429b9702200133bc555e10405721c43638af4b77eaa4c57b73c64f786ee40df2f4827d9f7d", - "id": "fcf8f52e8b0a6ea3d181ca56067bb44d93f744e28aedbbcdb8e06ffeef189db0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1615695478227, - "fee": 0, - "recipientId": "ATQZen8h9DzHAt7HgPAwzcWfAC6CaTH4dp", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203b9df7a00baac5b1c3b61b590455967d9a38701ef39fbc4e3e37d1c74631f0c202205625993ab0006b2b2d65b0b28871765a4345a6482cf2aa42d0d1ebc503f6ac93", - "id": "c13207f4ba76d22fe47a1d538019c73ac6652dd7b6351a4222ddc158950e9958", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1632724842016, - "fee": 0, - "recipientId": "AMbioVwHwrkTYHt4zqt1r577Z4Y5tgWwJH", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210084ed4b704b13b31aba705cdfc9df95fa552f0254e97f89371e9488d672aa337d02206ca6ec6c2cda84cc0534d21a4e74d8782f11aaa5690668ab17768bd97abafa9e", - "id": "18ae7a152adcb4616d7f396119eab66060b48e66553a4562737e95244dc2cf8b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1635164836036, - "fee": 0, - "recipientId": "Adkm397wm32bW55AE1kQTGyk1t6NCz7sfP", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c749b79487813d5201670106a0cf10f990e795c5405801c66e3fda6b7bb8515f022012a06e81726ef353b47297609051d850caba385828b5d9a4e044f1e5edb32471", - "id": "e583c7aa0cf2bb4da4d9886b55b893ce1b1052ba6d79ee510e7f58c273c8b1c3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1658329844804, - "fee": 0, - "recipientId": "AGvf9xsh3xgCMSnoCYLembrzthHqGJdwbM", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200a16ffd1d50f85db3ea81de649a38892df46548d4b69ac6ba8e1cb610bca9be6022005f0b9d13821099d92a8fe5662389f439c232e3da0a024dd43cd5eea7ab4fe21", - "id": "aa4d7b90b4c1fbbd4297a23df89b89a84c9c912f03af1a9bb4dcf77dd52b794b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1672675483771, - "fee": 0, - "recipientId": "AMW2gACZ82N8CNgjFL4q5bzC6dRwpave6f", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200fd4245350f29e79b767acd442c893689dbbe3fe5c6d0b1bb40d3d7d8f4e0dbc02200f0d67c711afabfc3d38324236f051384229ab00f1f9a698627567b8f73460c2", - "id": "a8870e6e74479438af8e464c62dcabafbe785b01824e078e1e121bfff01e880f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1738465434518, - "fee": 0, - "recipientId": "AKFT9sF88rSg7rvy3gvMEJJ8LaYsbEsYH7", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fa6c33a957f7b2b26549f37208c4ab1d968161f531cc2c681c863acaaa90627f02203138faffd5b376854a9e6d7080b2850d571f07b1a2c7c58113dfc105c6318f54", - "id": "b416130c8a04526c058e7dd56d79161c989f0cfcd83aaa798e6235ee6fcc3c57", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1742839839455, - "fee": 0, - "recipientId": "AMXgwGKJbUt9NT5qEMwY2dxUVQquQDBrTv", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220537c4ad8333d6c27ae2d4b5e3822242efa493084a984db369f4538f61fd55cb302205a054469d04d99e8d3e57f47ce237178014ea4c7c33c7e05f5bab627901b1e20", - "id": "d5355bc6a2601705af7f537514f45a838246dd11a95c2b33bd3a3bb2461bc89d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1775549503277, - "fee": 0, - "recipientId": "AWGk5PUpJCLBr9GppUnNY9ryL5RhbstthX", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100cd7bf83928c227b964f34cc5d55483743a1ccdf17f778f14bde1bde676a7051d022034e2bb957bdf212377580f3b6a7ac3024179b1aaa0dfe57e9cf76c32f37ecf6d", - "id": "ee657127722958204f85897e7825ffec69af1504e20ade4076b07aefdeb263cf", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1812187469576, - "fee": 0, - "recipientId": "AReskqQAtGi73GHG6tLyGxpyZKAw6kyhko", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f7d097824b01b8a9d290ac4f7c80cc8bd5f32ef07b22768ab289ae39048b2b2902205bff5fcf23fbcdfe1f60cdd017fe790153ee783facc851af6a65b7e2cde0cbfa", - "id": "3641ee56bb7b499407222f68f9b0432c80b7e6a32a8515f8c5c08ee38bcbdf8c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1847900000000, - "fee": 0, - "recipientId": "ALCuA5ST57RnThByiyCGYti1rFFpy2vaPy", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210088f32afedbe40f6ce661f0e828f6815288b96d78409c0067578bc016149d05c0022018dfcd5230d3fca401a478120a60a153c027212107b380c8f428e3991775d067", - "id": "ad9154e66ae59ac9f80f524568452bca4f00455ecfbfc25ab89652ec84f26c08", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1849124693198, - "fee": 0, - "recipientId": "AbpeUAgMG9Zk1XsLfPcNshwumea7mkekBZ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022012c3db569ba5f74749d21752f16fe382493c639d383aa30d9bc85ffbdb3dd1c002200dcce7a238a59263badcb59d57e531933ca1264161aec6f04e44e5c87a6767c1", - "id": "f5615283d55e13b2b690b281bdcc412f9d87493b212d2b6b76d529e54d0a6b32", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1876941234807, - "fee": 0, - "recipientId": "AdBizdK3H7zfp6cnpAsAi9uR2fvJNQSFd9", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d595be37c3d0bc9d54b9746e84d92ea5d28f1a39df6c0ead2ab145191f95bb4e022020c5ece72829027083379f47cffe67674d4f4ee5e98f9eab80ad16b2fb44a4e4", - "id": "8bb71be1a515fcfdce9a10be706054e87bfd64cabc0e2ad958569ae7a03068a2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1891934510958, - "fee": 0, - "recipientId": "AKxpcA6iVjFtxKzcW8H1EvQY7nz2qrkGFT", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205e4a8eb13b197165a479dd3c0aa6ef0d06a011ab5fc429de4b25021fed9024ab022061c25125f58c392d7d10d9b060edfd3a55df16009b3b50ed6b7825be84e0c375", - "id": "06f4102c8d21139312432f58fc981d0e183a97c6726506f8ea1ea71e5957c4fc", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1895283091114, - "fee": 0, - "recipientId": "AYR4E6VBw7Ut1ygyvwLxbwDYtsLHrq7T7Q", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022012c4b05c360635a515a257733ed66d9f100fd39810beba85b4014c21c068bcd30220090c79f001b853f4e38c3df7f043fc8c7c2c683fd2f2572a01e38d11f4219d88", - "id": "bb54551895feb42ba8c85355084adb36d7dff452ef87dd411558a07356989cae", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1903697975211, - "fee": 0, - "recipientId": "AbT1VtooS3gA8M6KRT4Fy98tFzaTg5N9Gk", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a905b417aa0cd3b7f1b195254e15bbe303e0e59d7e2f7007889e0615bfaf945b02204050e412101f519179944471aaa65e6d32045138a45631fce10b18e31a05748c", - "id": "3f1f30abd5977c35300930eb6a8af9b16f78ee602cbfeabbc3870149d0144c57", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1909607394110, - "fee": 0, - "recipientId": "AZt4XTwHPBLEhr6vkLEYX1b1RKmAN8Yit3", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f5fcda2c42c4756ee68e460366d4ee509299d67559a4ce904dc22adb03d8422c02204be79c434dad458962e524da2e99ab10116c61374ac17bc880dfc5061827bf61", - "id": "8c4192958e2a40b7ac6886d39d6324a17d4c05c9c7e96da6bf6316742f2a7b7f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1911195743126, - "fee": 0, - "recipientId": "ATd8DeNiNgb7R4uiQGyGNLYkq1q7FhikGk", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201e2d7e0d4153794e41d90c9cfdf873867f46da8fb73d4bf5df8250f782d4a4de02204c2c78faf8a805d97119e09e77c132630f7d525bed291fcc40b019e71bc85490", - "id": "cddf63350558f510f3bd2bb86009c71bd39f75d36ea1bb245bf4b3af761f2e1c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1940598754559, - "fee": 0, - "recipientId": "AYeUdkzuGw84sPXgFBdLjfjXtG6SsJ2Rjg", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220621146cc0eb8596f176a16040fa8832112c2fc19b49c5a4a1ba830de141df6b202202f1a8232b2057dd529355fc4aec34ca4825630d203182f02880de2efe342d736", - "id": "00f4cf49905ea8c652b71c522fb8bd19ddecde1d2e8feb1ee2961fcdf7034a04", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1941422708032, - "fee": 0, - "recipientId": "AeVN8SgGb4mUYCjHVCCmWfw8h7WHb8LApw", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203d2d268ff5c91f54eba179cdc53b51726f6eaa3c1dfc3598be7e444c12a5575a02201cd9dd296215e16ebc511146fbda0989c8a41051dcb07d62f9fd4622bfcbf27e", - "id": "fb2a707333b3286f4d44b61db19a2e617bbe3592de77da45695444b62fc0a5fb", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1946861217599, - "fee": 0, - "recipientId": "AGueDNGxUQRDsGUa9aSPw53XKNgNPjUAia", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a8a10c8b659087d985de4bbd6166a9f2c5f8cd46e28be26b2f9babe3a2d063c80220460ce29b3f0327359a4028ad600d25bcb25634f19bd9b3d8e4a3014b2b9636f8", - "id": "e127b243ad79cbf966ab4b1c77b45e6657fbb5f49e4cc3a96406887e2ce4637c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1949845628984, - "fee": 0, - "recipientId": "ARsJyHCQ8YnDZYm8v2sE51e1qtGnK3SdMo", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f82068304b6f191b9452c1160caede73592d36d75a00beb74a9af9f1886715eb02206b38b09daf8e9184cb91130165227fbf05e94b835a7399d4ba07ff8bca6c1179", - "id": "6ed82d8c0071b52aa28e9674e012ba28759081f527a793da1161fc32d058bcc5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1973042309220, - "fee": 0, - "recipientId": "AML7Pg7WEuKaEWAM52rAUfv3RWE8BAbp7i", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204806c034f7027b71d645ab6df4134ce5c2861e479b9ce8a608669221fa1f9991022052ff1460577ba1674b6a15ccc505b53749bd6fdb8f0d1e3dfa596ca91bc956cb", - "id": "5d719b936209fc04fbf9e118b78c688ae6b586d0d9ffac1cf88225f47e2c37e6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1973887957498, - "fee": 0, - "recipientId": "AcSTQCTesrVHnvoAK8r1bwZ9CpDqiPmRK1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201ee8cf1f90aee896e63feb920df725b6897c6d4cf0723198abe4dcb65fd6359702203e5307a6831a54072cf564804482661789e2c78b9c189760ef1b33579de60d56", - "id": "5b82367f3b78997169ed9fcba1e4eefa8054c96b64d2bd30d12f18f2a364444d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1991239524027, - "fee": 0, - "recipientId": "AGhLhXKXAUWDZRS3skaEVMzpcyeqCyE6as", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022019f81fc2e04573a9ec9ffcd4c6504da8d206467a742c8d9cbf96d1980ab77ff2022050b0746effd9c253e61dc8f89b015b4256764292860db88eb9a6832c33812b66", - "id": "b62ecd68c562fa705d41d59b3f871934b7a5594a89354cbce472c9772e100393", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2000000000000, - "fee": 0, - "recipientId": "AVMX3Y79qXSHBk6tUrcLAeKh9C25xtyJVL", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100edcdf6bd19aa85b0c2078804a9a021847592dce74d2dcdddb38a0ece549a080c022057316f1c9f36d89f08ad9020385c16fed5bc088f12e588166fc984e9b0c6fda4", - "id": "4a26a119361e812cb7fa868b10ad2c903dcb681418113abe72c610afd4c1475a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2007149179076, - "fee": 0, - "recipientId": "ARLoK9ABM7bFmaqLMaEBbfTVvsXqyYaeXV", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e924d9016e8ed30d3e06fbea56d70c05b670b6a9d6999a039b93b0d436357b25022015da3def5243cdac0d0baa92e8f695a5018e06da5155d17f7f48dd7814341b3a", - "id": "f40dec591c02dae1e1eb176583476d7a7e291ee5f6be1741b9ba3c0e97013f1a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2020119321574, - "fee": 0, - "recipientId": "AL4youLTt6UXwxXWmqzAxznH2aPEekZCpf", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b4c556e5c648045ac2cb50aea26bfc158506a494c66bfff2d22941ab8a3f6af902206b6560e4d19bbf8b770c2ea0cacca973f94e683b38df2a00d81727d4bc0b41a0", - "id": "394ffc5b4a91e6dd81621246ca0a1070f3b451aaeca3f1ad48c4a4186409079f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2048474511235, - "fee": 0, - "recipientId": "AGsx5dwwQju71REuRJd8iv8AAo6wuo2Hhj", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210083dab3dbab91f91a9f7bbb3fd91b16c5457fe84e918d475cb7e544d09466c8380220504deee2a01460cc9dfe7be2531f92ed2b14a6923bc6bdb02ad1131803fc7b28", - "id": "0c1e0d578f3f6afcdf8d0406ea5478ade1c52d87b31153fcde6c6345cb0db9c2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2058848207492, - "fee": 0, - "recipientId": "ALcc4t52pgbFF1iZ1dntq8oK1TjprrckKT", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008ebec56c81cce72ade2152091e9771c0c9b46c411f17c49272b1647466b4d1bb022022bb7763cfb7af70ab4d97d6301db9a96046225be25b50234a5e1bc868e978e4", - "id": "e3e18ce2031b8052143296bee5317ba5013b6df080a1a222fcd41af5a3263398", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2061097549268, - "fee": 0, - "recipientId": "AFpxF8SMNEMaBjhw5NKdydhfU9zeDByEnK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100df0a94e388245d9cc205bd2739d9e17f02964f957707a88a51aaf03d029fb58f02204fe22271ef442b7364541ce62a5d833bdf061d8c6c3d78809a243e26428b2f05", - "id": "a91fab928d30719452a7890dc5d7b4bf323642923ee6e99c7e12aef8eb5ed821", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2061097549268, - "fee": 0, - "recipientId": "AbfyMpZLZXJukJ1jCLkQaDdxU8hRMCroHM", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203b386391a09c598649acf2575dabb917adcb14572a60f1ea8cbdfeeb4557549902200d3e0c146cd9d726ab91b215b817dbe68adc25dd8f506dec264445062dfff1ea", - "id": "0c1c747f9e680eb8199584b1f2a9b3b855db0f700e61ef3041f4b9388bb0c826", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2066612412189, - "fee": 0, - "recipientId": "AMgiiG8wXhd16NBjazW2je1hK9pfanLyMV", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204c5afff450f8735039b04fb34b6324136842b66fce3e1e87fb5cefbd23d1384c02203364cf3f9b63c10e14909c7ba9aa6d442da124bf257c707277a8167a05bf894e", - "id": "142834112cc331aabf1f30dbcec6c6a538c9b47927eb71ffc30083f8322bb224", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2109666070297, - "fee": 0, - "recipientId": "APJ25Fek6dNkA3jEExkyG67UAzrDRvPxf4", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022047b3d2953eb0b6e1834bb77053b207444fdf49669578ae639e062eaccb721c9b02204d142ead55d393dc0557b1d4b417e71cef41ef65623ffb5bde6e56665da2db91", - "id": "439911c5383eb2e090858e43740e9272b7053d4709c455a5fdf21ed46569a591", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2153677032409, - "fee": 0, - "recipientId": "AXtyzwP8X29Ddr5KUy6GuuxWibW2eNypng", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009ce7072f8d3bf7866b357aa36b475f3eb102e31caeae5d8d3c6a39d8f3c39a4a0220636f0f46dbd6a477194025d39f2db234449195b53798fbd03ce3e2ec3c86edb7", - "id": "dc9c7ec06d60f03495f590e1e46a6283db8cf62dcfd2186fc65249f4ddf51074", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2163834169022, - "fee": 0, - "recipientId": "AaJxRN92BCtogWtzRUrHJC8TWqSswRJGA5", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207120a932e96c536e2c2f051546d173f3c58bfc83946fbddb0f991431da9f685a02202ebb91ef9238d05341afd2012ba2bd456a54d2bfc20d22ce3d10d4a618ebc5a7", - "id": "fb426988eabaa074956707a42bdf4adf3a0c10cd719a6c4cd6fb2828fa9b7c53", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2184588569625, - "fee": 0, - "recipientId": "AXKzjecNQcpJvwpm72ZE2DDkf3c6qV9FQz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204dc5f6f772f7843dca653edee6da99c709d3efcb585092d3b563fbb58ef9ee93022011ad3b1c4226208c88fbe189f9a40fc22964d8336fdc290d3051840a20006499", - "id": "7853c4e80e57ad1152b9b7c8f7e8e1f6d5aa849ae1f0b5a3732121ae11b929c3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2191161363601, - "fee": 0, - "recipientId": "ARV6tjuLPdAAJFhTeho2KbZ3caMXNChvwK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e9fa2dd98b2518aaf44488865877488f3413caa3f38d70d690b8693519f96a7f02202959afa89b20dfc55786c82a636edc53260290744beb427150b506b666414da6", - "id": "0e73c449c0116a23ef5abb5993be907794353557601e0a39e96da882a7c948d8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2204931827339, - "fee": 0, - "recipientId": "AaUvDwmuhLvZRwmW8epsDmRQfLh11ufUzH", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009ebbd2c288878988a5f18a448ec7c33adaeecc203084620f2f98b3c74960ae6402204014eecc985db4e8d94b92e94148453480f526521a604c2222b3c04b90fcea17", - "id": "8e95a6db2d145e700e36e08a413e0d0f2a6f23d0640f468be65b73b56469ffde", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2205078842396, - "fee": 0, - "recipientId": "AJuvjPUQHDYkiV5R9QQrAmKQqMsdCiyW9k", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206083b54d4879edb8e02dcd5828e6117ee97624ac9f1381da575ca3654d7da3ad02201effb3b78038dfbb482cc8d60a8096c77fb351387cbeb5b1fe7423d1aac34c67", - "id": "bf0dad6cac85409e568de7d2ec72bf9a649a5a78e2f2d49c2096800e05dff8bc", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2205225857453, - "fee": 0, - "recipientId": "AdeDz2SgSHtNSS49qk89UQoosmM9nSn8yW", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022060fb58e2b791d584bb622cbfbb9ae89489a68b63cd292445db2bb82c60031025022065952795086ac1cd075d4b2fb58356579b3000948628d8a0cd91170eff57a83a", - "id": "7acf01f865d9e3b0172fd107b066a11d10e561a233af85c325a1891ab695b94a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2241213029168, - "fee": 0, - "recipientId": "AcRFukzbp9dcnpAfhUR7tKr2T9TP9ysr8E", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c9efa61ad9f4ac61a62a753d773e5f5b07c68348d41f651ce11b4381403dbe9202205397961b89303df542243d52407a2f3d1d1314b7f419818fef1b6a5a172a7b9a", - "id": "68995bfad121ad8d25068bd9f6a1c7442730220b39cc002a41650ff3a38221b4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2248243233437, - "fee": 0, - "recipientId": "AeZ1HHc1d6eg8P42i6fVcJVKsXw8Eo32hT", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022047e447051f960a6d1e554a72640858c8557099de95f891b692f44b15ca6b85d102207a4d3e483e26de4de2de0c337ad2bbb581312cfcb77f806d320d88103a1aa227", - "id": "7eb9c09a76d966babff5fef1db9a160af8885f4545fa1da2cba9baf5b64cebe7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2262177091195, - "fee": 0, - "recipientId": "AJzzwPzzyDCiDc23uQ2u7AMywkfzaVCcWt", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206772491d6df99e5475ed8609d3a35a646b77fe85b81aa0cbcd5a79ea725a0f6602205b19e267638b99baa4990e16ccdab0ebfbe2a9130618c83c30ff9f8fd750eb2a", - "id": "fd90da851810e57219244396325da5e2064640daf9ba6114102aabff880414ca", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2266868808469, - "fee": 0, - "recipientId": "ALNvcAUuj1GRHaTvDcEj3J2LJrpbigMfTd", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b0e14408162136a3d9a103e113d860643cb583e260bed70db986b548167bdd1d02201e3cd6711b3a5a47b07cc79a2a0fd24d701003ee54bf4e96c34feaae11586bcd", - "id": "997f9a8497ee44bce5675df192fbef70e582563d5a8d6774a10a38c1ffc870e9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2271096947063, - "fee": 0, - "recipientId": "AKU5SETsMF21C4QNxSAFEuMtucEVZbHrbT", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220405439f374319ae7818197cd605c29f981ec33ae5e5d8ff63daa6c28b79a12fd02204afb1feecba0339d1f6858f00f45053c4fe891a041a810df553c0274b8da8837", - "id": "625cabdf0b3e0ec7461e182b037d5050566ed0cfc96daeee199fec7372b428c9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2272050883436, - "fee": 0, - "recipientId": "Ad3NrB9ak5ASf4k7u8UsMUw62onx7t7c5T", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210093ef0463dc9e57cf30e412631760d1f8c9546fabac8d02a81d4450c1b0225fc7022034d6e9612a01b53cbca18c08c95ded5ecf9e16486772e910e857e32dd7fcc8d6", - "id": "56ec4d726f2f6eb3bdbf96201d7bd4ec0dc76222145e4aab469a5a8031bc0897", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2276774784522, - "fee": 0, - "recipientId": "ASdJV1rBGrv8hr3n6PAzyEstMMaeKRC9r7", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022073089b31090a9893b58f7d356e2ab39709f213bc753efe142dce1028a18fcc85022027516343ec493797a210a0eded5d162f612c19221ee758925bd0bff3b9a38a4d", - "id": "b2f66b94ccca70109adff57f5b0327c01fb8b1cf9266cc5d34e67c1078619b6b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2287751179042, - "fee": 0, - "recipientId": "AQG5AyRdBMK1EiAxjtzumHBKTz5fQziJaQ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203358a1b09882d61785e37ac06243189f37c429a08c245dcb9cd302d67bc23a89022036c0830ea03b7fc64d9d673d83d99d5001a39487d5207983a09a6b2bb45e8b70", - "id": "27f99bcaff31983a34701f2db5614995f09298ec428508c0ef310abca3a17156", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2318734742927, - "fee": 0, - "recipientId": "AHWbM4U2eC5G8GqhEXjKjyjQ14gE2uz8WB", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206f65c7a1ff3c846ba9ecc55cc3485c498ea01d316a456d7044e801dc910edfde022057487ee7c70aa9f4920bd4a32759569e2dda24d493d427c1a1aa2cc92b6f860b", - "id": "726d10604f15f9e5007445f0b8f829d8811952a0b9f81895fa673d45e579a8e4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2328424475356, - "fee": 0, - "recipientId": "Ad9T3DbNPoF7SnCuhBEMQwHuiKjEXmhTFn", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220725c7b21c8cf49ce86093c81ff5079c5f3ad6a7955529a52354e01c8b93db64502204463fa21a31a3cd7fac42e62f0e304ad7861d71c7eed6114e1c64cd37ade9438", - "id": "66ecd1311f19281f119c8bbbd34d6f32daceb58f2c747c32bac3821d1b22bc36", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2338875909420, - "fee": 0, - "recipientId": "AH5nQnHDEueDsMA1oL4AxTYgPkGgzrPPv4", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210093df608b5fc963f1724fbb3f0e3e3c52c1642b17330988bfa45d7e28b143f7af02203b04d3b802038a17bef863ba2e2e17f1a288ae1764ef61e7410643017643f054", - "id": "1446fe4a270106969f516ddb59b0d145c4df5ef0c58aaea92e16d4e73d4edf17", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2356684778844, - "fee": 0, - "recipientId": "AeNyCDJoThuMtgGCqo36Qj1RxiLzp2wKfX", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c7ae57e20b74727add16c03acabc3f2ee6e8e200651ed2dfdb973f3137cbad9d02201c33c46ac75f9d1183ce4a60bfdaf72021415676828992f5b4bb4a15bf8b06a3", - "id": "8e4d777e325bb6e75f240dc86f0def43fd27a1d7db6efeae8af457fb4a0e5a7c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2366781260994, - "fee": 0, - "recipientId": "AGog6GdAzTgGhYXuifWnrKvK8VAPaEhe3U", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210097204cf3140ccc71aa07a31cc969dd5b532d8ffbbe936581b08914982e823033022063df68f995e5b8f76d391cf7faea9b34fdb2b3b26db5ec42dc94055efafb86b2", - "id": "250af0610058d8455a4efe16382bc4b0cb0d3206cbb288333ac3550ceb44cda9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2372499252388, - "fee": 0, - "recipientId": "AQRccct6eua3CnzgReDsYVdZhjJEGa55fM", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022048fcfeda36ed20495c633c7b1e671a306df8229e2a7d4a75547c81b8af509742022066b8f68d2adae68654bb0fd01d56c817b45f1d5fe686c1a180fb53f9109ebefb", - "id": "72c59a55be7083859a7fcbf1e4b9d5e3f39013b292c812fc61a83b07d44aca4a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2381643926049, - "fee": 0, - "recipientId": "AGvdP9o1xDU5HB9ZfFEBKTN9Td97rka9GG", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a95fbdf4d29b7d8d00bf352f5c0faae88fd0c6954119c1e7de8624c539efedd702200aaa95e4924771e70dafc892933be295c372f3d7614058f34f6568955e60e3d7", - "id": "c4be55dbb512485ddcfa1a44a64b77f72f6a623ba28a1ed74f7e164759921b69", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2411046937482, - "fee": 0, - "recipientId": "AS4BtWoaap3AVmgxh8cQj3xkcd7dkWfK1J", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d2efa679287190ad29fd7fbe91978ce07dd5819acfc9f08b12e9aca27c5d9fee02207da29d7168e36a88509b6d73344e461635bbb009e1203bff8c6ea31068f8b2c0", - "id": "dca8904b7776d845555706aa639fbaae9e6ae4d6d912c796a41e793dc2030d6f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2421793988476, - "fee": 0, - "recipientId": "AGErCuSAoSpZNQ7ubeV4HSuHrwoxGQoBbq", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c9947449ab96e6ebafcae96a6c080669ce3045f711e0ea1bce651061cb054d7b02204bd9e3e1fa0bbb0923571ddafa6943c69ca2096fd82dde9a0fea4e3eace6bbdf", - "id": "c36be7fc674eef93fff38d284a209599ffab56eace2e569f9f05db0ebbc98e8c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2424820646199, - "fee": 0, - "recipientId": "ANgyLRoKJC7a6RLUJw4nkkrL1Rni5eYs5i", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022040b3f827c57219cac01e956cb5aec623fcee151b57225ac06edf7fbea1d86521022021808f0d44f5a50c7f35a25e40c0034b7831deb627f8c2922ded0b406a00cc63", - "id": "f3f300b5fb8de1f81c863737617f45c4b67f80fbfb7b72cb4a4e0423718df75e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2440155918800, - "fee": 0, - "recipientId": "Ab9oevZRRtixxzvZjVA5jJrjxi1kwWhUN3", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201a6214822f6166833f7fcb539cbbca80debc0e0d01ca91c0f87a64a5dbe993b002205bfc9366366bca57d54bb5c389edff2e0b90728fdcb58ceca89984ba9aa12e66", - "id": "a6fdc2fec0e799ecc73e8310e8285cee66c1420f3edb9d915cdbe7414c23b824", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2471141445854, - "fee": 0, - "recipientId": "AcQgRCArM8s3iCzXmaNa4LFYcPwPUqR4Xw", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022014be56285973e8fa73c89a4de8c1d5f06c30bbdf6d69c9286e19e18d3c00cdca02203a8777b4f3cb0a28cb42af527b3a13d284c8bb5a8c6a97b1f04c5fa3b7d42dec", - "id": "8dd4e91bcad96169a7d3e4a01fcede8898b36e45797a4aaa6c318f1c0ac02889", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2481166956848, - "fee": 0, - "recipientId": "AbKSfhS4rZQXKJjnF1egbDqBh1fnkoDADx", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bd942f63f791c0cc5f90d50edbbb6837a1445b053c599abd9cb8bf89e933d96e02202e1620e229f7657fd1d87fa97c076b42a5a8a4b5eb6992cd2cf1308a4d2585a2", - "id": "cc51d42bf081b422663bcebf7c9d4676a1ec5fae1e4df76e7ef63bc9184e7c50", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2484636536440, - "fee": 0, - "recipientId": "ANTd31Ts3VHYBkd3BmbAv1FHdrYjVU939o", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202aadc401035be55c7a644fc4204865a0b25c61c8ee7557c35c8d6908ae51748602200eee3eb04aaadda8f210440e269c03868d46fad3f80850c81f7f526471e7090c", - "id": "d5f9faff17330dc56236696dfb08548941a881159948c6aa100136fd416ae834", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2532393949328, - "fee": 0, - "recipientId": "AeZGayWNSzKRkeNxkHJFzHEygeXsh8eytK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022036bd56eef8476da49e4c226f6d5bff50a491a4b0d077f0320df704ec731b3ee9022057b53f23797d0cb167b534f88cb0004961c2ac0ca25adca7a83bb0eebc541cab", - "id": "b6f7fad4820bfd4a97c845918c3f7d39a9cce601db5947b30f0a8cf515346fe3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2576371936586, - "fee": 0, - "recipientId": "AZMvWheiHokcgXW1UBvepmCiSjtLESNoid", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200923d3e46b3595c3f9c728deb6f8f34cb5dc49e32cf5e9668d0d74e31181d5ad02202d6079625ff697cb987da8782d44333768b883c10407af365fb4cc9a5135aba0", - "id": "1768864cee1985d3e4440b710f7cc8dbb7bf095f57f61af22f4fe9c1cef4e1a8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2584344277365, - "fee": 0, - "recipientId": "AUpLiwm2WVWh7Pqeizt33LgjTfLxaeKQZM", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d3cb2ef1d65dfefc87c26a36c625d9c018924faa17876acb422727fafab78c5d022054abdebdb5079c36789fe3f7d97bf736ef5a314555aba0632ec3511f6b72af7c", - "id": "644c4cacbd139ed34ad275cc8e2c5d147a4d3a5e8ceda34f93c7860cbf3285e9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2592139332838, - "fee": 0, - "recipientId": "AWEVGHr3x1e166sLRYWjub9tMuRmrwv5Rz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100dba9d478044f5f59fe1c85f8528f1e0fcbe8ff1fecd0cffc13d09ea320a180480220086a1fa2d5f23c120b0b8e49753d8e4c9a1772f8b2449700e9987f7679acbdbf", - "id": "27a27a46a268bf1af2f7309c1d6d4594513c1e6188d68216eadb573ebaaec3f0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2596699852052, - "fee": 0, - "recipientId": "AVpBNSkQsqp9VtK1hZtf32ZBszABP6mPxz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201d4b144431415a853dcb795f3ead4eb9947a0780af7303c7f8f1bbe95f0bf470022025539723eedc56622cff150b1f6d65e5ff10ee29a39b5f29b77af51d455616a9", - "id": "d6adcf3c3c7307e26318198bb541bfa38f3b12328b4b71a87eb3a6eb760baad0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2599032418076, - "fee": 0, - "recipientId": "AaQDmLECcNXVzALX99oWP8ed8wrz7E6vQV", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022052078242e033e71cc764c5e6cdb1f240fa434c674d8bbcf5253f17b2e4bdd95e02204a1095961b33110272cf1d944ff3cf147cf784f02cd37794205369f75aefeb09", - "id": "06f66303e2b00608900e64f219579cccaf060f93a50eb1ffb83a6af3374128ca", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2619541018549, - "fee": 0, - "recipientId": "AM1WQJyB6ENVcBM3qumifK1iyGgmxAtfky", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ec33ce506f26896c7cb3f5730859b4da1b52fe9ce4fddca9c17976e7522d82cb022040a6c88430b555f187b83324a99cea223f56be138e5de4d860fcf2a3315e91c7", - "id": "105f4cf709516db516b88e985518ccf82eb4939a886c1b8fe356ca85b2444325", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2625323003381, - "fee": 0, - "recipientId": "AbYapv2W4GTvn1XAQopCFPxHTmGuhZ7wrU", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100cbe7e43ef7a56ddda2308f5cb60204611e24dc16d1cd1c681a4190bb109ce8ac02202b41f1a599c6de7bf37d94b8105c618460729edb0235d3070948a002d408a315", - "id": "706cb7d860d44fd5ca836fa5667d81e60dfb86c0e8190850a1ef042da330e2fa", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2646271028944, - "fee": 0, - "recipientId": "ALGc47qvCBw7dL6y5ruLrpWXdghQSMjRX2", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204756f3e295f5dad6ffe44c0bc09e7a8e215acf01bd7fbe85babbea5e1682971802201cd1a6af8e4d6440405631ffaa0b77333ff31a4068675a4ed9e32e345e812c11", - "id": "a7b9cd14e449f0809494ef6a4e94e257206e3b339196dafa6cce9a5911eeccfd", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2735324506659, - "fee": 0, - "recipientId": "AW9ZtarPjeMUH3abFtVvC2ECNTkMS4w9ZY", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e1b443ca71f70cefd6dfd6cbd2120881c7963e60b9601d6793d46587bf64f8e102205519f08774fb4a7ccec42ee49cf10cc1e60c8bdd4b8f9b0ff3d403cf009022c8", - "id": "26d5fd2ff3c8975785fcbbefd6ad746f51727c273f70f5545e95d3fd683593fe", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2745083256244, - "fee": 0, - "recipientId": "AcWyTqpf7XuNnptSCJ4cTyw2hqc6PhDufV", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e1fe873519ee311dfe7114f754260443d272cb2faabdc67d5e13b3e22dce40b902201b2372a23b827ea4c3b588b9e55da4cb09f9f18f201293591bbba3791f1fe46a", - "id": "3ae55c8fc62086c2ab577dad2cc85e9fd025d8e035ccdd040857f25f3215a29f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2764221209306, - "fee": 0, - "recipientId": "AS2NDZSgogBv8K2PdDNYwWcU8RphnXARQo", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200d7315132954c4ddfb3a09c6204a1dc0be0a415d87b5243093e8ca01c02057f70220377bb5e835854b2a1372aac469e103ff59fb023acb7b05abb259083ac0a695c7", - "id": "41220183a52980e1897442d243a14f13cc5f6e3a9356c1fa229b6999899c152b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2806744646340, - "fee": 0, - "recipientId": "ARsQwGzjkRCscuwe8dQ2MHPHSYqmbY4iSU", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207f71fdb335a27b7d37d9e07677d14e546e91f1bc8be8c5519179853be3f6ca9d0220712a67657afe2b13a1ed42f7d29f662c625b55b577a87d032e5a513d3a33e875", - "id": "5a83436df7e6bcea1e1221846e05315cf36f3c5c4b06d785344e8010e48cb64b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2906876974000, - "fee": 0, - "recipientId": "AT51Pc6EAJKjK4Sgr2tf3i2X7Wp6L1Nmef", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210084c2421ebaf0f1cf5c9ebc09454762d5e6b47291c0b88d8c644cef634c92d4a9022000b3c741497725e2659358254baf0cf87ca742dd189ed5b29160e72e9a7e8d94", - "id": "5ef7d8921886188473d15c50c752fbcb5db7d094efbaa8011b23929409a7e642", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2910898131837, - "fee": 0, - "recipientId": "AST9oifUBT4nv5kiNZwFrkTneZEyUshbti", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100be3ed06ba98f260ce44e0049f1ea20d5efa532ec97e9c286a3dc6611f72048bd0220562050948f72a8747b5e9407033c083ecd60aa78621b0f8d7c57283eacd286f7", - "id": "f04b90545d3dfa0567f8b277d172f32c9cbd9de645036edd4d5e0e74c77267a5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2940154128213, - "fee": 0, - "recipientId": "AMZXd6g2nidHGd1pZFUqZVpJnNrpzqtCKF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201396009e8dc5376c341ea496e7542c2b96514819dca5aa23ca8dbf8efc1175ae02205a6083d4d66a15d511c672cf521d57ee61ac55bc876f494ea6450d2b7b5f6640", - "id": "46f24f882e0b0a1df1eeb6e2b17ad3a37ec3b9eb413d8aeb2af5827c1a16e286", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2940154128214, - "fee": 0, - "recipientId": "AHwkTdboEC2Nm6NwXoURbTzoNRPmMVXCqc", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205f1d63f3ad6bb0ee39797711bd1939b72f6081abda7a3249ef9d690c4b2de86102206022e684106c6e47c8441c5cb2ca2e583b3e79fc2f8db0d6885ec2590cf88c2a", - "id": "f868bf1d98ad88b9fa263292382355e6e21513a7614bf35e0a413e94602b4f80", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2940301143271, - "fee": 0, - "recipientId": "ASBzgywUJ9UtciVtbYc512CDrgTDhcMYng", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204939d50212d6492e23e7f3ff583044ae1ad7f01b173966b8fa83491e2c94b7ba022033f1a5538b4790cc5cfdf0d52a093bfc9e579b4da08643c3359d3ef0f39bb34f", - "id": "202834102dabee749fb707c8f620ca53d51c5f92aaa4113dc1b86d7989433ed5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2940301143271, - "fee": 0, - "recipientId": "Ab2DLnDBmKqcwBuzhQUa6wWdCrECZ7MpTo", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100eac45b9e454362b7fa3852c5c8ce5d2ca19db0da27428f75543d6ef075c91b4002207a49026e26676748eb003fa34f5244e0d9ad7cd881e90d9adbd158ab322e82cb", - "id": "f608e70123ec8b29777cf6904ff2e530f8c150d8f59aed648866acd8bd250710", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2940301143271, - "fee": 0, - "recipientId": "Ae7NncFsoDvMMmVqiJPiTmibiB1aW8XYk7", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207767c2f983946bed560b7ebb36101222275a8bd64f0c0b4f8b2fa2727f344b0202204222fa85950d3ba68b37be153d77ee9135a12d1f3a4ca7f8bf201d18502aa12f", - "id": "56e775c567f7f7c2c7e728221167d54bdd021f722acfee6aae640ff888c50801", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2940595173385, - "fee": 0, - "recipientId": "AaYk4acmLnvcKiiuYbxQBB71goY5Tn3Dm3", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a0546e807d1390df8e97606cf30028b6145d287a93dfec9f5c7b9672d172afb102207325221156e2b4e32ad04c80691f073e1dc6402b61fe2aa7296cc7d424d584ea", - "id": "89d8e7581c333ef01ce0ded3dc08da5580c358e02444a6c37bb2637b53e747fe", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2941331203166, - "fee": 0, - "recipientId": "AKJdgCXk6ymHHKaVoNhkgwtsS7zeSHg323", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220172d555b9f5ecc5268d9af740ce9aabd35ba38a1e011b0ab05783148bce1f96102205d4491c92d5bef700518e4ba909025b6e0da94ac550af7f33f14a2a47189b72a", - "id": "09b3d72c0f9d6051e30ec2f6dce7512820a02c0d95edeeaffd79dcf90e461fc6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2943241444414, - "fee": 0, - "recipientId": "AeykjWcAMwZ8jf9UkS7NoAEX7W3kJiBWUZ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008260a61c32c694392729d954b56d02ca72468167ebfe0c2ab4f96999606656d10220247bbc53dfc30f0e6c0267e7cff73f44f3794a77512b483b3a16a78f95cfa609", - "id": "524c4e3f8d8c26f3b78ffeec170648af3410bcd1c4ff3dd91b783cd9a2f229ee", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2975619527639, - "fee": 0, - "recipientId": "AGT3E9aU7PuT3qF5dGVQFwAG8zuXTd7uAp", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220051f525cc7336031431dffad29a63ea2aaba824790c6db30ca40d529d77d1ba402200601f5acc9a39b41fda57e49a3d2e4ed473b323336f82b8c39be54cf72611de9", - "id": "dabb12f952a645ab435977dbe2353cdd2609dc6ed1a672394d547f0880d93cc6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2982935509848, - "fee": 0, - "recipientId": "AKU1FaKbLTBfonNqSAUidf6nWS7fyaAs8d", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a399bcad2f889782a418d8c4f43fd50545a9762a5afcc2f1c0a2584b9cd9cd99022033c4e961ea62a4db1bedf6dc894b402dde87328fc5426bf7080f8181b0220316", - "id": "ce7293fefd0f534c30b3c2845c129fd297d3fd3d4ed7c24d3e0ed490801eb0ea", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2985301143271, - "fee": 0, - "recipientId": "ASgKLrpaJGxArbtTNuKHTQNVWyGitGGpZC", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202de43275dc393daad998ce1dc35038299d94e82655584b05bf1ad3d88ed4ad5902206722a7a5753f18a2f280180f411bbe0be380d0fc48d95007ad5381f367ee3be7", - "id": "9a99bf84368d99e4a83fa780867596c6be71a181ab0e4d80b99e843956435c4d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 2991168353049, - "fee": 0, - "recipientId": "AZePtHJzYCXrdG4tF9SBHY6k6R25R8ZBn5", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f5b8cb767a75c487bb2d1bc078baf5e9bc1ffa024177bec52cd34de5c87306f7022024806722c1dfa0212e49d7cc22c1251ed59d4c0fbd9963bca459aa0c0f64f521", - "id": "a36430d45917be491b50a09e86615f3d1a67af148173e7867509d704df2b1367", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3029944047194, - "fee": 0, - "recipientId": "AM8f4MdYwPFB9XKqvki7wt3MNBXK6d1EtU", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022002187664dbc4b4203779ce0717e3efef35f94aa241477a5e326ececf4897e3d0022029c1e31d1591c2c54f242b43bb27575b94561bbb82fd726f3c54738f96c9025c", - "id": "e0c8f8e46c5377741c86b6854281d0056919cbebffdf4d641785826709879a51", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3030774378583, - "fee": 0, - "recipientId": "AJVm2UANQSZBH6WhTLM8ZBqTNDwmYf48GE", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100988c50ea74333df4771619f0cf7396f7569ed020b6d02ec3bc3ab1b2e93e186502206adc99ee4498664293176a09208ca5631fb03147f6fcc330d9dd28102f088e8c", - "id": "90090e44f39b81ebfd82a3c3c68e11c81c22757c91460e309879913aac6e915c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3043211683285, - "fee": 0, - "recipientId": "AJi3yet6WjHiMNx8j41pbwNyoKfZtFbzwE", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f0d5d871e104cb93b22fe9937872219bb52695f3be7508144d260de8fd33cb43022064656b7b33df040d9a49839e45250c44780cca7ee302112a123694fd5ddf2a81", - "id": "71b958d40ddcd3e70236969225049f99b99e012608253a52102926154e8e6eda", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3055154128214, - "fee": 0, - "recipientId": "Ack5VMNG38f4DCpn53a8rXNoijKCpaEEqq", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200fc998d1c31efc5222de1447a8284cd94cde778da61ef0f13ec333196318fabc022049cb07ea8df855b9712d85f334e2c2fda9ba07a5d7065f978b677bd900e0f899", - "id": "2c42da9e75c92d6f66144898ef06ff9fcdc35853a8036788f3a52fa7cbfcf033", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3059124967808, - "fee": 0, - "recipientId": "AaFaBMRCVAF1ZBfJx9wgpZNxxANpJdJSYs", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205fb4f163f5d2276083b58ce3c7a5739f74e751d86050c6d9bb10b2be84c343870220407388a647c4d12beac50154d72dffa01125a3a2ed5f4c26fe20fd385ad27316", - "id": "a05319b6d84c6991ade6f957ebfcd3193aefe5937122e7afb1bb7768eeeb4057", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3131025807748, - "fee": 0, - "recipientId": "AKuqp4G8AA3XWNe8JwMhTdNFsjhrWhQ7ZF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009c9c994a2e54b52e7dd638337ba112c43bc2150f740f470de9d5b72b852947d10220050af097f8ec27f51f0fbc9d052e5864aee99e7b6082a61e3f1488497febb899", - "id": "e521e3dfa3379d083020dc9c8ab5856bdac23ed0f412e1568d9704ebd73f4114", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3136161306270, - "fee": 0, - "recipientId": "AHWAQFqCmJ5RrKvicKzg4qEvSStWN2f4PF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c2673236c613f050e22ea59478067f803201b9c88b51a4e0215270ebd6ce17f402206a586fbc5b2e0e5b27edf94bca44e9b136fd35a7be96f5d12c2c3030286a8196", - "id": "08a2b9d852bd06e8a2158c95142c88624887ae9ec667eddd62b02036993c3d48", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3141084221939, - "fee": 0, - "recipientId": "Af4K4T6Y13ZyZi6hQxSuFwWHVRZQEhP1D9", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008aa3a5f09a85a7e94a23beef8c95851670ebbaeaa7d251941a92a3f44420951e02204070c008e27d95e14ac9fd50741a889efa464ae4c740cb44791edeeab2815227", - "id": "391d96b95f7d84192a1f319932167a2d2457624222afdb0dba66eb4b3ef9cd69", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3182675621655, - "fee": 0, - "recipientId": "AY36AeEFHXi5uevkukSSY63Qv29xLtBNzj", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c502e26db7ca217fb755fcbe660da9ee6c29047ddbcfa3962e6cee1c15fd213002207ac54ce32e991a0c654e5b3c684d0d2efbee9d2967f4edeb80ad823f4b19f3e6", - "id": "f5c46574a98b4659092e4193d2c05405973cf80be9ef391e824eadf89003f116", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3208001085999, - "fee": 0, - "recipientId": "ARtWsVUYotKEXTXxx1ZxCNvQUrQsnXaGSB", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203d060f9f1a25f865c48d773b58228dd2ccc8061b9903039940c507dcdf895f5c02202bb37da139cd3d4a7cf5f677f1677202e28d0be38468601e829ccce2cb925263", - "id": "8a6dd2571b665a50bdd9aa12e5908b8b7bef290dd7e77365593830c0c3504355", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3234184242541, - "fee": 0, - "recipientId": "Ab3SAMJGK1fTrQAqoio6uxNiQqUPCmrzxj", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210083d0d329a80d6ee339c024b6a8143dc1ccd2a4ef9cc36e2a2290f3edcdb37afe02203a76762e23f40fc99ad1bf9684a19d3e5060ab3de016fe025acdab08546ae83a", - "id": "ab3ddb2107eb42d4b92b1ed42c5c60d6ba432932db57b3f250c5ddef2e5f0b40", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3234331257598, - "fee": 0, - "recipientId": "AYUmyzDXiqHcTnFjtKQC9GHJzFQb76rTjv", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022017b50c51bbc0d381d134b7fa916d7d2e3ff07819c217144c1969a993af614f4402207aa346585c21d6fafd6a31eae748581272ec2ffbcc4cdbf2cd12f258a113340e", - "id": "47d84153cbc76cece88fe01fd661a4fb388a40c05495fd2d4bbe8af7b650e1ee", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3240854690572, - "fee": 0, - "recipientId": "AKdcJzAriRFVftFqfHkrdZ1kYKtSajjVGe", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009956a1473b920fc46270c1b8b7b53ee4be22aad70d53a9e9059305f1a8d3107b022035e82d31b43b221e7a7da617454ad1c27a504ffb107cb4fb9efe88539b886e4e", - "id": "a599896c8dab5a4251366d83c44c108d21f2e82922a01214d24857c5d965c9a4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3264823516954, - "fee": 0, - "recipientId": "AMnGLCrRYXtxmrnzsx7bDzEx3G2W4igcx5", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100cc08ed2979526f2e9e0876e177871deb20ac16bfb094b240475efa11cdf5642b022005f4dddc0a80d1c9df8eaf04f3fa4dff3c5b349e5b4315d6f037f80ebf95aa74", - "id": "0b33f80be4c0145ab42eb74dcd63c93ef5dd8093b8cc2358182e644f12287634", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3307427638563, - "fee": 0, - "recipientId": "AH66WwUgJHCYvEbX8hTFpuKpx4UwzoaQEz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a243814491855ae7abe45fad6ea7c7e939903f198383b224091a25e22fb357f002206e4849d25af6dc5bb6e002f30ab0cf7f8cd939528fa4780256abe3f9f38f4419", - "id": "af232d8be6ed5cceba703d618a040d13db05286235b2610de8c4cda5668ebe8a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3324317837586, - "fee": 0, - "recipientId": "ALTNeLaT2mvuvMohvSjg8FjYhVDNxbeMEu", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009f8ac3837eb8b6363c0ec7788dbaff01a07d9cf68cccad905a67a8016ebd24ca02207acf56e23a7485fffa201ebbe3c515b99bb4121170da3b0884193416881a23fc", - "id": "577010da36ec3f0cca45de28e26aeef46a3bb7fd932b755609aac454e9626fef", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3332461324329, - "fee": 0, - "recipientId": "ARLUcoV2hyhKCzfy25Lk8Y9VWix6deVAYj", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220724da3de0142e6139d36004f68e5ab4eefe84b786c5c80d0374b319c3be8d6f402207276502244f50eae18d9d8cc7ef1636936809fa9f6d72461a5bbe91db9c8c04f", - "id": "65306f6058181b91b964039ad1c18f711fbe7b7d9f318d33a02bc1acdc7618c6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3333461562845, - "fee": 0, - "recipientId": "AHFojRfYcbsAhEqvKY8MXMsAc8MEkxwZJU", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220179bbffea812db28bea6ddc3e3863b6798af30cf9b2789d5f827201fbca974d302200ded60e75ebf3d9eb1f947bf5dfb216e8206a4882eacdeb7269c728fe47c0bfd", - "id": "705f8aacb1c7cca2391a4d432053872f3dc302d9ec36f1f399377e1b10ab1347", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3337241797612, - "fee": 0, - "recipientId": "AHRETqnSboGYrrjQ87Bnu5ovyKZBvfuS35", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009eefca56858fd766265713c444958cccb5d615b012747c011c6f5f355955c60402207cab37ed16c6f328ead3ef60c35257cfeff84c0e36182125822142f2f4a3c471", - "id": "4f16617510a64b071be9c9aeeda35eea4947d52625610d7c8af2d47dc144632e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3341104284118, - "fee": 0, - "recipientId": "AXNH4NZ1wsrkZ1PPFfSQFucDGwt4sfsGor", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e443d4e4a9d3f91f9fddccb71e70555b5376d5928da292c4466f0fb4bfa7f12c02202b083a62b3e4d139c3bbc09c162259ea43c256fb0cd25870b724cddb4d7b074a", - "id": "7664dfadec3af8d62814ad0baf545d16c8aca1be546758a259a05a2115837cb8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3359667184998, - "fee": 0, - "recipientId": "AXDfDrajcMoF4ssBKAj72AefFrS32A6ErV", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100eee29af59dce2e725f1441b665b737c9bc22090bd4b37a68cb79c29e8a041a28022038b10296b17315ce07bfcecd521104a62a7c1598d07ef17fc397aebfebb094ba", - "id": "388dfda809f3802e4f7ca49ae7da2409e195be0b231c037c0c0ee139227b5e74", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3411370798936, - "fee": 0, - "recipientId": "AMg61zEWGdPqgRcebwuY3nABTXcEf5ursg", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ac107612d5b3790a6640e52d8d5cb277f32c7ce79f3d9881641b8e7b613213010220353d4bd4188fabe59e5d8ac78d4135b57faebcd5e8095c1c7ab8d9c1cdbb6b41", - "id": "a16f6f3d94d84c9e83cf9ab117cc17257632d3b7548fe779f21bd02e0cf3db90", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3431641346204, - "fee": 0, - "recipientId": "AV4uwLo1v4g78ZWWEfZWt3zssgjS3HUUK2", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c6ca22e986389d94fa3da2973a1bcea26492e76c8fc8ecc47d2f754ee21e0e65022031cb3ffe69ddd0d63c0c40b089fd445748c9b0527e71d71970f2c1e987670320", - "id": "71ae8506d64400ce1c01558b7dbf6eadb9b915b8576f8024ba54929f56273d8d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3472495650202, - "fee": 0, - "recipientId": "AREUm684gmTkURsYWAb5XFSWjtUpnkHXM1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a6977f3021aaa15636b427ecff64771bfe698cac3e6ab1fcf1113e927656b44d02203bdb670754598eb986329abbf9afa637afced4dd83415f117ffda3a40d7c2da1", - "id": "e03a176681d276f4c1a44b84923d8ae4458333954e8917c67cea9b412d743ceb", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3485679678910, - "fee": 0, - "recipientId": "AeUU182Jnq996yw8TZRaDAPj4khgpxBjKY", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022006a3492b8a039c733c6860409ad2b6639d288f4638863a10955502162aecfedd02205035b3a64ac33b34cc81b07bdec6d74dfd14b28639df434b97cb55c1eb15a9f0", - "id": "aa9f40571467e5776f3ff8378598356aa77cccd4ae6d20b9911472393a3aaa23", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3485679678910, - "fee": 0, - "recipientId": "AesYuSHWM25csWzyJE5aiXD6yL4Vb6G1Vw", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c4de585a2f48a005b07f3cce3a97ca0ad829250496930b3bd2410b0740b8ae2d02205f46e8dcb4cae8879992e1352a3bfa5c947706c36bb8d288516169b536f3299d", - "id": "0116ede2fa14adc70cf6286d703f9dac6d7d29d2121b62a382a197f2e0c9bcc4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3528361371925, - "fee": 0, - "recipientId": "AUxFccM7Y1rX9rgzi7ashGogxk3Am2A5px", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220750199ce472faa98d3eee8da3efcc0e4a381473aa1d48ace4731d7dffa78fa1c02203c91c647378029ce9944d5b647776989c67c7b3e153e7f3af0f33e709c9d0954", - "id": "8911fe01cd1cc981963d244e82226027890ff383183acadde7023091ff3ba196", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3531301673068, - "fee": 0, - "recipientId": "AeeJBq15fTimUFojH6XpR4FUAZmDubP43u", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100933a2422b0d1ce2180e046a6071652758838311737a3424a23ace27474eefba602203e72e72184db462d5af7d862d1d2d20b8eda36d9d5df6cd9cd308b6868a7604e", - "id": "62f6c02daf915e73a4440f65838b9bdc7cd4af1cd3361d7e3ece2755643a753c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3535124064554, - "fee": 0, - "recipientId": "ALfWJY9kKj9UQkPG9WpmaD6ty1xzy19MH5", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022056e64e74a7762a00139bc688a97421ff9ef58d1052019cd594855abb279de55d022075b33e73eed827a243c96a46f97bb3ad1c3998407d84a8d557f61f3e93fb08e1", - "id": "785e5330cca5c0a53d97c3746427ad1dd4e72d75e64567ebad677d00bcb3b8b4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3556280867780, - "fee": 0, - "recipientId": "AccgnHy72MjgSSByZcWQqmeRgMstvJcuYb", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022063057051b56093a07affaed97bdc4c80fcbf7e3e9f1ca81d68347176e39ab60b022054765fcde969a830d58d67dc80f1e2610ced4e6275d3c261b44542add4ff74a2", - "id": "fe4ec15b065062d5662f9d352d1e1885ff2f907f2ba0c6a1528e98bebc2bc32d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3557938075605, - "fee": 0, - "recipientId": "AVEz9XVtNWEBn7DKziwGAYRFUcaXetU2y7", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220603807fb9a4ae361dc3743551f4cec9f78bfc9a5e6bfd00a7576196f8970a1230220586ce37ff801f638feae33e0449cf80c4a389617fbae8712a50e03e45b6e11ce", - "id": "f0fff2d878b1fca856116cebb41cae5a5cc0aff75d6b1c986e302722a81feadd", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3593815899623, - "fee": 0, - "recipientId": "AdWUn8FcTbrtck26543q1oGyaSjZFWu6no", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022045025274c42fbda104625bf2cf9320a01045aee16cacc809074ee6d315cc17f7022036b821e1e26e19cf0a34bb6ba82a3d1e5c164990f1257119677f81e019bb231e", - "id": "e2ecfc0a1f1e5e47ef99d7de7963e51d2af78f5a3671859487a835447cdf9353", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3599965360713, - "fee": 0, - "recipientId": "AGYEkwK8grFmYGrrQkNSsEbQTRxSv1QGBH", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d44a62be1cd41baff04021452b670b11fa4466a50b19624e918c56950c0326d302204734db4e45f8c2fb42f41fc3488cafea5187c051a4b5c21820b864e46336cda5", - "id": "f851283dfdb3e819303fe22d6accb80c935a06112ee8ccd6da917eeda8d9e2d0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3637230969298, - "fee": 0, - "recipientId": "AXtwLLdXbcAhX9j7YadfNoAbEAwmzUDGe3", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202d65e277703ac1505ec5becca2491ae4cc63b47af9e3c9bfd4610601b14e17dd0220659e5031db6fe356fd2297cff629fe3a67f153f05fecc76a37d12ec81c7f77c9", - "id": "737a7570bd435e95df00dd9aea0d8dd9ed6a2d6715919814d240104a31cc29d8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3637230969298, - "fee": 0, - "recipientId": "AFtvTj3CU7Vr2C3bDwNCDXDpbqE5TAR7UD", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008d5661fd7a233b744682e4df941f880b2a499996700246100d94b196db0297e102202c1086cb609d5d24d960307f3adf858268cfa85608ac887b6579f48fdeb2efae", - "id": "8c2a012cf5e9de46fc73d5cf899a51508af66e88951d8d17d9bfeea7b53cbd7e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3675376429087, - "fee": 0, - "recipientId": "AJqdLZvhCLntP4asFsVa7BfxAzvm5vL1TD", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c69c239352a791354a5be8627cfbe1c5d266c11bd6113db1d7a156f33ae5f819022053aaa645c823507cc6bab83b87fda3f3e66bab8e69b4ca2ff29928be6a7b7a16", - "id": "5b19f5b4f515679b9e1dc6a3624a2dd740595f48dd236076fee85848ed65dfa7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3675376429087, - "fee": 0, - "recipientId": "AKrejHN9mQwjGALapuuf38wjs5TTDZGYPD", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022058f35b0134f4ddca74ab3e3d3494946276c15748ac629b4949c1132bbb754dfd02204d6efde7d964d9f74327077b2f693642b5652fa5dab7de8da16aa5a587c04c1c", - "id": "6388b60ea1d6fca35d97d365f06309d93e211362aecf97d0bf4771dbe192006e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3675376429087, - "fee": 0, - "recipientId": "AXH4SRRS9WQqYW6tr32eDScenDgfmZvn6z", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022078cc3fc5584871fc50c9df131c162cae857088ed96cdaad082aeb2bf73a7237a02202175cbca5a39833cedc9c394e0c896b5eb5c4369748e021b588af40539c80e1b", - "id": "e64991d57370d9e29549f930e3c72ba1d2963fea228c69d07da985f20889992d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3675376429089, - "fee": 0, - "recipientId": "ARuZqMpJcTxfyqs9FYwN5o4vHrH9ofLvnm", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207c4cd8d49f9a5babd6d545162f31da577ec194b1783c1cb7a518ea922c589b78022053d81b6610d827dd8e4ee4be48d6ad9f397c28711d8225981027ac283aea127f", - "id": "bbd93094994a8367c48099c29dc4619dcc5da73cf63caa53b1e529395b6b9a6a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3697295037611, - "fee": 0, - "recipientId": "AZE3bGd41tQLJqtP7kmn1SUQLe4ULUpNtK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b7153b2711d4d93933b0d9de3fc66ce2d459464e57fe7c6d502d8a0ca6c8dd3b022027915527227c59c301929b18c2f04b65d58d6dac510d9f9720689fa3bf8bd180", - "id": "c8cf09babe8f3fdfc44ba66a538154418bcb3c3b4c960ddea6d710e893bd4337", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3706851016327, - "fee": 0, - "recipientId": "AXMnw2SyrKVtEpkos4pYLqLyKS6nzBjWL8", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207b5080ed8fa34486c079d94794804e2bb8fafab6c643507a322fb8f1266d4f9702207d80e5d261ec51e6c371b2fa59926163828d91856d8c0b482bd23a03a991e32b", - "id": "8dc1c97b5fd6cc3002868749b97114243ba2a950e9b2beff032840548a875f13", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3709028616718, - "fee": 0, - "recipientId": "AWjDzWr3whEepeRDHMcLSZzfrYykBc2e2V", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e660c72942d1e4722f23166e5a781f5ec8ff9c264e6841695d9799934a9e859b02201a2e7e073c30e1efdf4b1fd17d3ef33914276e7931cde622c42dd3a90dc3f7e7", - "id": "4e83f8b8b7acfea0f43dfba0f8cdf8a8809eec7717d4519f8ba762e7328bd338", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3822391486252, - "fee": 0, - "recipientId": "ASf6GkdyPRoxAQ9e6wNu1eTcGzdCxTwU4F", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008980846f5d043bab9bc69d533094c47209a08b6604052b9097e97d3fb884828002203dd82abb96ec6873149fc3a1f274b04792c30dbeef84d15d274432f91b53ebc1", - "id": "b25ef9750e5e136d27cd99248823856666cb496d15c45af37ef77850791157ae", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3841576429087, - "fee": 0, - "recipientId": "AdJqCvp1xnvHCJKRBYDm6Pps7Yn1Ly4gnJ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ee2c9b676bb97f604b5cddff9c35cd2995b9ed514d71eec031f606fe249cdd5902203cdadf32092513c27107651f170ec3209e52e2fdbf5d19fbc7d6cc222ae367eb", - "id": "4069a73c1653a4dd78863adc651caf01c46b1ce2c603656fabc4bef4e15cc243", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3912040733393, - "fee": 0, - "recipientId": "AR4SrANMGxGvnQNzBtKo4PMiW6gpfnWvrS", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ac2967b732aa360a24d7f5562b138970d165ff8f5bd6c9e8aa8e45e69ed94b0a0220360bce61f2bbedfb4fb7f12ca83b1d9cc803a1e39512b1fc5fe5667f12ac740b", - "id": "2eedf249fe84cfd6b31f3090424050ae839909a12a3c1e39c777bc2e98897b7a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3918939514059, - "fee": 0, - "recipientId": "APwMLxdB8EHM5XXKG3gfT76Z939dLz1JCz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d3efefdd5f23c7d432cfd10d2519ba3dec0cb225c4df2cac9435db8a71646dbb02202e46cc5d7721e3dd8ea325ca859b01899359a3a36657fba4c93f202ca58ffa89", - "id": "d51629cab0261f943ae8bf7c72d8f33220cfdd1cda45a5f4fa5407919e9af420", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3937230969298, - "fee": 0, - "recipientId": "AU412AFph2KBNp1ZUNbMr6UQP9o5NSf55s", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d38cb807e50f55665d84172ae9f51ad7496e6afbf2128f7d79ff6b245b2dd3bd0220315d0693ad470da6c87599ccaa083645aae0bff6f5ecf9f6c540f2b333e0dae3", - "id": "04de02148852008a6b34df8e5c3ec16c21d4d84acc7176ac5a4f828bc14e16c2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3965251953261, - "fee": 0, - "recipientId": "AGQ6aRNRA165aF71WK26Wd7WBn26d928dC", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100996ac1644a8e8fa34351933824de6c784aec3656fecf58b00ecc73058100037202205b82dd95182ecc1b5933392cb43106c56a93faad5da958145e55cb066d055b66", - "id": "00887bc4d7329a1cd596625f9befcc07758c39b425cbc173f70338a62455f8dd", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3970035705567, - "fee": 0, - "recipientId": "AcvUN694h6sjffQYucMLacGpPuq1ZAupLp", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fb1f077325d32c64f6047e8bcc8c4110e079af9fdc76c171d1f8a0abfb32c43a022063f5bd3195cd0985a836b925f56cec34f3331d1fa0a9f0c0243583b7f785ba01", - "id": "db7c86a83a6e9fba3775696e6d06a5051cbdb20b329cd9877358d8e5bf3e1545", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3991462828751, - "fee": 0, - "recipientId": "Aes2M6fkCxfwRyguG6R7K234a8aB3SiD93", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022059bcfb711cd879f837a28c9ab64e342750b5a3bcda836c1aa232ee472004eab50220609448b1560201f56d821c2bfc180e08de802a8349b0f132c111cc5ee7d0e6da", - "id": "c797c9e6646543db243df8ca6f595651745b1262062762393342dced20eaea82", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4000000000000, - "fee": 0, - "recipientId": "AcomwPvjAGZoBiCx3u4e7pnHaXxeLnn4MD", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022024236bc5bde500949d5031c98e8b36f823e00fa915cf01773baad25e20eb77b802201dedd50e48b684a7934b341f4e961bcb703cbdaadf3047bc3a63179992c80367", - "id": "65636f924d10c01ba4289d064623dbc2bbb19b7e6f5da944635edc38442aafd6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4001132109901, - "fee": 0, - "recipientId": "Ac7qjfgoPxS7CvKMsGgPY9qPjy8MRsxtFA", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f4622b4a0e92ef2b2e120d951f547a4bd22afeb383700cf21f66cafda92d752702203d815814d38b4885ecd59e747d6fc0131f3aca76ad6b8f795b8d7d05a13fd8e6", - "id": "4b66a47cd8eccc4247e661526a5010aef5156eeda176cf8e0b1471be657790e7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4015989936987, - "fee": 0, - "recipientId": "AaHvdU5zBUEkqUebphZdJuRQx4K7QxGBc5", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f1843d7b8f388f8fc2130ac2f5b5721b98e6e1905974cee0e5bee71f7bb5359002204ee81e364ba06b6ae5fdb9ff4c3772291dabe596664d64cd8fdc7f9041386b9c", - "id": "cf6bac6a58bd66f9820e8b92a2b7a093d3c874d652f81cf3956c11d785133f82", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4042914071996, - "fee": 0, - "recipientId": "ATBpKuXuUDFEo3uxRDJpZYCYvAuRxedMQP", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022036b9cc8a055c30695316e968334a488c1daaeb796f189549ca5566ca677d4be102205df0433a63bc80d39594ec5aada62412a8776468c5d8996d7c670669f0819070", - "id": "356ddf4e0040d24014d624050eea27429e4ee5002b07977cdf08878e93397531", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4048316026508, - "fee": 0, - "recipientId": "AWd7KCkz7PAP9K8noozmqggx1qdWNHxoa1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022064ae748e8002a9118a3e4bb16d56dc25a8aa37260cfad3b5ff30bb0fda6031b902204fa1d3f9faece8ab66054c47647ad8a863759f0ce3c3c36edcc1e33162e6bf80", - "id": "d37818308a718d3d8ef3c3f4364a10b354b63af68840195c436dbba2fa18e344", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4131665330066, - "fee": 0, - "recipientId": "AMuFbxddTGnj41RK9QRCVymbSbdkxrJBoK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204e2de836477641275c567bd1b574bd277216d355bbaef3574821b70ee55affda02200e01aa3604454ef602992243324c66746e0ad04b91dd65f0275bc349f4329faf", - "id": "d9147524e74c26eaca62684a3a6a1876cfe97e71cbb85b95dc5490dcb1ba094d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4131706645547, - "fee": 0, - "recipientId": "AZ9hZpKCaaJ8c5GyjvNqWVxKoiHj8fUkxj", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220618744db99463616257c71774ccbffbad439cf0cb6283b556a8a7fd98e54da2502206456d52a8c767b60e5de2f3280f852ac049b66dcec4f9e389b8e801ce00aec07", - "id": "265d72b2909c0d5500b9dcd4346f030c32be27ee393d755a4dd017cc66094e4e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4153588271639, - "fee": 0, - "recipientId": "AK9RL2q4zGWGbLtLViUHHdrzGbY19JyVWp", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f6adccb21f415791105482edc5d6a640964148330fa534d8f7e2e80f24b4fcd70220246d7278626b4b645018240af6d11d558a086e9baa7be5f4c02e47cfcd28043d", - "id": "be1d5b2d28236cb2e1551a702bb633e003582007cef416286b54553d1b09f13a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4209041086592, - "fee": 0, - "recipientId": "AQqyqd4wPnAceW8SNg2Jwq7kw7oJdbu8jX", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ad4d36816a2710a030966ae3dfd7964a36e3d6600877bc12a5787877117b160802207c3bcb1abf4acadc60182ba7c5f242cac9276382fdfe31f8cc5c1d34128497e6", - "id": "0657d38310031c46d2207d04760de4dd9768f0f88643c189f234cd0f09fcf33a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4241270774064, - "fee": 0, - "recipientId": "AQ77PDtC953vqib9FNM1QxgHS4c8hTKbY4", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202152e05672aaffa4dd6728794f48c789449778ffee43732fd04a7daf0ea10fa502203d3b1e6d34399f84c96363adbe3e486c4b766e402387b1c88097794f5d681f47", - "id": "faf38dc7c24d054c7b542ca52119cd5325bfa8e48ca24488e259594c72c4b6a9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4335474035753, - "fee": 0, - "recipientId": "ARNrAn3AagtTUXwwPgfNMkG42om4y4tjXp", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022049deea24c707fdc7650d34b049bf69911105a2591b93789cef09ccee23386f1a0220693f7fc8a1bf4ed2c42fd89dff05b49ba16efa2b607dc040c99fee81fd94fa8c", - "id": "4321cc2b30fb9c56a2d3dc0e68cb711fb3706c2bdea33bf06f8d1b532bb15ba7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4362372912987, - "fee": 0, - "recipientId": "ARqijV9qpJv793nYJpSuzhJJtgGt7F5vMh", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220330da9b78b7aa2553cc5b832e4780c160d6c2c9177a2c07240f7ec4aac39f31b02201b6a1e5f6d7003d1d2428eb6d89c1b4dd896a083fc37e136bb7a030322027d2d", - "id": "ef30241456071ab5739cbc01b77c888de52548ce718d290e17ed78dad3ce9de8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4395705996499, - "fee": 0, - "recipientId": "AMZueSSbjv5eBjytCubTZy8gkkFov5GQJb", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220201fa8fdb645e5a6d8867dceaac2c15a03323f7df042a1c32d2cc0500451dc9102202980c35218796eabfe7755d019448caca47b7892d756548585cc2f10803ff878", - "id": "b12f8bef995ddc79c9ab8b62294b59ca171e59af53daf5b45173758fef8314b9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4410451714906, - "fee": 0, - "recipientId": "AeeTLvBUBeVaWNW6WU4dQ5RaM1XkFvAz7Z", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e82506b38a0e8e95f0bba2735b71ea2d00ec3bdb4a1e11519960933acb202f2402207e93347b0fdc6263d6a0725cafb8a15cf6fd82dca3033a5a9520408b37dfd3e0", - "id": "0f41c6cab1711f15519c238ad3913e0f99f45604f01181bf312450fe142b42f3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4410451714909, - "fee": 0, - "recipientId": "AeVsSShGJR7KWQg1DHKEqev1PDSDREiDfy", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a53783236e53c4d54ae16e2172b9152cb44d78194a72a404849ea9d36e783d39022030ea460212b5b2ad93542eb7e27806e6b6215f9d005863297e9a55814071bbcd", - "id": "41666cc004f1e16061027115720b89eb3fded76b0032c3db25e90178fa158ace", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4411611467971, - "fee": 0, - "recipientId": "ASp6DK4Fcrji5LHRFCwx17YPG9wdnXUnq9", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220456e45ce3c5fc8ac7ff423250d7ba6248b29edce819edf5e1ff2fad55702e70102203f1a313fe9dd3705f217ca2a2515cc7e26e316ab7bd4228dce7e600a54f2d98f", - "id": "0515f9a1e521fc8e692c197a0467ab01b300ffc3a4780bbbd5faef682ce9514c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4415891272021, - "fee": 0, - "recipientId": "ANR1PwuJc4o8Wp2qpc6uUk6wWjX5QRhX47", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100cf11b29ca14aca61107d25fac12d9a994c965392dc0ba3e1177c9f91559132ba0220266d7dd8d55b8eabe70e95190c53e9cb606c82b3d4c4f4b578afa933873f6b20", - "id": "ccf658c1b13a22aca678f05fff271fb533459d73fe00e361d1b1b96b64fe2d43", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4507507351910, - "fee": 0, - "recipientId": "AVF5GuU4tJ9qd6YewAzDbN4sZkzFDNFgUi", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ef1c683d99d3c0228c8af10864c6981991f0cd493262303514d907e1a2dbe46802200c087f8aa3e4f2aff028e14664e5760873d753d7f846eb0fe1e9e30fb885eb13", - "id": "450de1b4d45c627fb948d1badd4cc8a70c89f3436fa5e745b795545b1ef06927", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4531651714906, - "fee": 0, - "recipientId": "AcKGYwT1xwcWveaQE3dBqqqqTT3nnfpKbg", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c71f196cf735ce57f667d19e98ca7296c9dddf84bfa634318315edae96a67ba5022062cb8e1fec6e86e0f79e50b9b0c52a943a772fda7f5aba0cac56eca7cbe7066a", - "id": "d679cde12bd6e8ee711e0979e8968a1b97d6ceb9fe19634835355733b0a012ea", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4535436795104, - "fee": 0, - "recipientId": "ANPpeB8JWX4gfskbcev8a6RYXZSSaMs3pv", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b86b575d515ed5ba323eb805882031a307b11d63c65249dbb52d20bac8f41f6702201ecf99a70e608349febe6ff6d33da93336357df5f9255757e9096d1bd1ab0743", - "id": "d496570ca0432b0f7d71fa40c94fbf7f9388cf27d7bfd1c18617e70c96546be5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4641265354653, - "fee": 0, - "recipientId": "AXyPubrCRRig234zKtDMzzSVrPKoTR2vNt", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220312e3a6206e914414d40c7011162de5061089520ed5dc02ac0f88e20ec1a52e2022069011ece4aa149869d180c118870995de0280b5343791f0152705094f088cc4d", - "id": "afc269e369f0a701457aad91613679e5151e0457bdaa3ca386359882cb3ece67", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4680692119983, - "fee": 0, - "recipientId": "ASZgEyR6XZQ9RXFoUtuyXY7gqHbC64vkU2", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203c43b599138e87457b214b1e3d1c1e83bb699d06a01b01e6add07df59b4c5b42022079f8d76bcb6bec406e86a5acb5cf2e926af71349801d6ee20ba89287c432dfc1", - "id": "121fc9d4fc8e2cc2f95508394b6eb20ce3593cf6c297fd4e0cc7d48d852719cf", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4691051871764, - "fee": 0, - "recipientId": "AGa6Frh3DWJMmAsd3Nkn4bGt7n6bFwG8qw", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100808d7e940940c758d49ef8fb12660f15efa0c0cf5ccd391805a490ef877322da022005388d608bf563be295af2d05a993b2b1a87c93ece23ffa0f29c1bb412104b85", - "id": "0ac956b61b69b38a04280abe2c372042dc63a8267b67c4a6f23b7870f88a978e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4697862065215, - "fee": 0, - "recipientId": "ANQkpXR4MwEdPpgbEdi3BaxF3qQMTGo92w", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220522fa6db9ad2b0d1a899e533b97002774bc90d96495b2493c200eb777a52da32022049690e22275a24280cbc7df79b99c92731c8b9b95261a07ab01e02c295f2ebaa", - "id": "fc6e5363cc3ebcb775be0446dbe8add45d7642f58ab984cba460ab5d966116be", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4807392369248, - "fee": 0, - "recipientId": "AXYbSuKBYev8pU4TK5GkcwxWXWsqFL1Vvq", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200ad8d8f2a9ecbd9f954853681c27b5a46ac43b0db909544e6b23763932aad2f4022075e36c1e9e5260f5099f0fef9748071fb3e3202c82b0f685f6e56497c0ed929e", - "id": "a0b9708d51039b38e4285f869cc4ee2e29f620f84837bf3147d09e7dbd91295b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4900011855261, - "fee": 0, - "recipientId": "ALZLdzs2XH7Ma87nS6qnGBvipdXCwXEcom", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022055e239d43d618d29581141d520ea39e940a627d113d417d39b5e8600965ea1fa022013dabd576ca157cf2aacb55588570d6abefa793db90617f9b52b5f8bdc5c0bb9", - "id": "bde6c2c3a29e7d20ef59a478faba012f38555663d3468c12748c1d0d5e70e292", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4972512330701, - "fee": 0, - "recipientId": "ALT8t2gVWLdEgc516c1QTXbajnsDxTvrMS", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c4bed4d0ee6867797c4c8e3c3a15d169602302dff9d0f60a683881ae0a2cad7b022067e97a1f63f524e3037d8f29471b30007579fc372551e0c8e02f2924d8f5cce2", - "id": "c2f507727122bd6a0d0b78f12a8dcf1169d9f16187877e64bc24daad9679e0d9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 4977195555049, - "fee": 0, - "recipientId": "AeNTL1HCB6ZBvKLHVMpHnGqVksTDktSCfx", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a0a35c51f045c7b5020d616cedf80411457f899f160d1edc5a326f827b886e4e022009084b2462322f577fb92d07b220eb0d5f2c17e52838faa7ef0dd9a0913a01a5", - "id": "c0d6bb7397e069bf8fa73f3227860c1dc2f262ebb274d8ac79bc70ee5e6c5129", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5000000000000, - "fee": 0, - "recipientId": "APBJH5q5UPjfekwkaMemUzo32WsAGPLgC7", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220668c6951f67ebbd0aad2929ce9b00210a7d9f669fdcc1de95d713264a0630d420220630ba108e8db883d532c16fa21901ff3fd2a150bb6473d083a859416164fc862", - "id": "595117b36fc9609f6012d0afb10f3c072301bde2b65ee67d4bd5e00b9ea5b456", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5086720977858, - "fee": 0, - "recipientId": "APiXiBgZ5Vk47zL7r6nXbb1feEcJw37a3Z", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220532e792b6107cc509119c9761433c93ec9ffda5cc5f4ebde8b0add5b84d9960b02200ef235d5bfb86c9083ba1917160fe33ec880f547845b80c456f4a2cd2ebdd380", - "id": "2e02e34a648a1136e0237953a75deb9dc336bfa2851b764d3b419dea851b3688", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5145527000724, - "fee": 0, - "recipientId": "AQvzCAZgwfEerwR8Wbi2jtLRZe1xckgwnz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022046c8c7d959a7e4a01250674d44fcfae8d2b1a1d1beb302061f98fb3baecbcca30220795103528d96a6b86575d359c68af22b864bc6e1c412e4501bbf80d55690164e", - "id": "29e47540c5c0663a2a8612e77717256275a82a29084a03ec6b0fdc940ca3b792", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5145527000724, - "fee": 0, - "recipientId": "AUNQekNveCH4o9EGHfuMAc45P46vNAkJWQ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210096a8e326a4c0ae121e4ff4428d3eaf00682d0894367cf354ecd68ac2063fd14b02200f21db687cd6b747dc4da81857fe7decae1dbd7d1e1570c8e609a27b6008d17d", - "id": "fc43db98ac4c5b9f1b3687e91da1f922a1328e4a33b3d5fead709911acf6c810", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5152228598785, - "fee": 0, - "recipientId": "AbFfKLj8cbZCpD1dFcnx9M1quKbGyvY72S", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200108edd763dc24457372d2be73c2e0bf08c551e81646221bd02c298222bd3f6d0220304c7a242cc0b1c97956e7ffde176e16c89bf6bc024ad2f85edf0b531686b235", - "id": "ca1eaa9b36e044f32ce3d08b3d2a25bf6fa71d52efaddff1fa1712eeadf76413", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5165668063555, - "fee": 0, - "recipientId": "ASxsKmBbAojE934PbqhJ1mhXXpkdrEMGi2", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220218d87057621c85e71f76523b123fab334c76307c5ac2030c572ded289d211b702206a80d562a887ad92a6ff0f5ce69202578f382e55a79665526ae3a38af5908f46", - "id": "fabd7438e73fef81feade0e11ed6da77e2eb6e0a4962b9f945cd6288139c37e0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5229284067939, - "fee": 0, - "recipientId": "AV5Ap4mdBEJKpbfUZQYZWxx6Q66yJv25ne", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022039474147574e5fd182277aaab990a51c8254d41ac8d8740b646dc26c2877a2d6022033eaf619263d1b0e415c5badc403f76a4c9851d2de87c35f7ea0c63d35840a77", - "id": "e03816fb6db7a11b0bd2ade86f9a85765e3ab7a94e2906b6c0e30e7d8e0b5fba", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5292542057888, - "fee": 0, - "recipientId": "Abos1R3ZsDqgNa3T62XQ2gsyE9ogYs6fPi", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ea11ad93fe457b16aa68e3b89751a8556aecc737d0a5472695c8731beed5821c02206fce4f51c29956e7c996ce0b9bfb75702c90983b4a8a59a3fa3dc75c06a20ef2", - "id": "05ec9e7cdfa7944660f3468045a013182795f23b066e02d0706be47cc49fa88a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5308261029410, - "fee": 0, - "recipientId": "AGqBNsAT7ndLy25CcGrsaRCrXRzwhR3gLY", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b2253589018b40a1f81e3a216adf75f1b6498cb9761023f6bbc0f120c21ff20f022042596e6765c285aa0905e8d173880704cb843b1ebf97de0e92ac285d972bc2a4", - "id": "c0ac0b134286a830d4f2a84e8e391215336a5f1e46ef05ce9a1b7f47da0729d0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5312153866513, - "fee": 0, - "recipientId": "ANZCH2mAMCUAA9TJByBgt1R9PZp8j2dnsX", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210092551cd132785e29add9309d136a6883834349f2efa084138f259c7b500dc5f102203a39a5ef2dc305e947758bfa72d6d5ebaa3afa3e36cff9b17f3aaaeb0079cb58", - "id": "c646cc3909e6032cd9dbd8df7d5827abfecda82eadc0155d7f2667f33e37207c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5357843265955, - "fee": 0, - "recipientId": "AQJwwgFQ4F8NxNUqn44d8Kx6rB8AmJFiBN", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210090279d7a8f908e661b3651cddb1f1690868b9906c0744724356747ec26e1d4db02200942dbfcdd9e72db4cf2ca3fb4d20b6b94133a781fa221ebe47d964e8c03f977", - "id": "aa45b2bfbff8221f7b5809e7ec6947f39e57754d1866a25d2b855b8b54080ae4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5399929874642, - "fee": 0, - "recipientId": "AeRzn9WbFJkeExpJ1ib9bcqPFGBz6fYha6", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202c2146192848bd5fe02d0968ed6629dc03b5531b945d8e7fd344902c46074a5c0220677a6ade625198cfc73d9e60d9735841111dc5fce252a8ceea6ef5aeea325337", - "id": "d8396e0dc6eff0baaf523bdb9ec0a0f683ea01b2e7b481edab37f679959c72e7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5452743873172, - "fee": 0, - "recipientId": "AK3xPfpuLgDxvcUgvHE8G7nzgJ9TikonCM", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100dddf3ac76223dc2e41a5bf9c19b0e4e448e2f90c1e9970c97c4642c13892f68202203e5e2ad64cc817c835b951212e7a65b09e565f47707f030ae39b770694fd6f6f", - "id": "96fa04274bbbb82e73be6c4baff991523c86cb022cc8f4b0ca267e8f2b5fcd29", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5454583384732, - "fee": 0, - "recipientId": "APrqARoKAGvFvSd2ipxmhWt8LMNoJPxHDX", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205017cfe8817f69a0383d53361483393f92c9275face9004b127e7d3ee4730ecd02201259d5100eb0ed196778b34bdef3e90a59b34dc0d61949d56890c8c1c994e400", - "id": "12514530353f15e198fd07bf42693c050c1bbf92f1094b9060ec2a940f1bae19", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5534027654283, - "fee": 0, - "recipientId": "AURkidZfCN8uin9FX8ByFwCpCpqAs37uyK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220752c29c7102b3d0967a2a369eb8ee0f8fb09bff978cc00502069626526a6b7150220351f29c9591b208740088abc640391b793dc7fdaa56a084b1a12bde9a3ca8ae6", - "id": "d39878fa44b4eacd96c1dde902d6ac74748525845fde03ae3bd74bcf99f0e14c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5596708324890, - "fee": 0, - "recipientId": "AW8dQKTqagJnXcRfDyYXiPBJgtY82sjSoG", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100be71df263fcfb94fea34c0ade0751d3f7b3fd25406d995555f2692179b1100ff022000c3c93736cd0bb38fb59f5a4336dae6486c26f643c8cb8fdbff1b06d8f7bf5e", - "id": "5e016277b51da7b156b41d5790536d3f643297f005202b5e504d4ea588451d31", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5613302182608, - "fee": 0, - "recipientId": "AYoqmnDvAruDtVpKpmJY5XGHVsdtAsfYxu", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220171cc8f28be49906b2b55123b0b5e1dba8a0a5be228aaf517c3b5f03d1202d2d0220559bab26591db1c5348b69ec6b801f607ec8ba8b84030759b6edb6827d3ac0cb", - "id": "9c1b2c56282b48749139ac851a3a991ef9e4ae3926e600cff88a4182cf9b3e19", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5623070875236, - "fee": 0, - "recipientId": "AGtJTLKoLnx2oVgTpfi9briAHR7SrT97PV", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210080d87159f97654d34b9cd2ccb64b5c3b44824405bf464462184bff3cce93d49c02202e72887f5a73adfc0101cd5e4f0bd019ac66e52c029ed1790890389395c7b16e", - "id": "66ee1e6db3ff2989dea803fa06d2cd01981d1833a98b960c79295b63c653d3dc", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5648505606296, - "fee": 0, - "recipientId": "AFsy8pLNJnnq1R36WeJmQNrRygxPZ4TBv7", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210087518cb76baf699d909ef78187be4e5918f47d90bcc88b64612201fef8d672b30220668295a8f17296c8992a8dce97ccafb840b1a939660c7793f76c55ba4d5074f8", - "id": "1eda98af243f8de2c3cfe8cfd2b2de565e5e5b8af91998b716edea0da85607c0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5648906556452, - "fee": 0, - "recipientId": "ASjpXv62BcY7wLgJLkos781E8HhDjfPZ4c", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206f1c839e8b95af5b74909a9febbabc652f35bb9cc6afed4dfb1294fa3a57cabb02200a3e9a2ff90d7d72808c5da906d873984a07576d593343a520279d5d81053904", - "id": "450f9e10debe655d9786b3e3a181b79380207e54a5460e21d27c64cdfe48bd6b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5667502986102, - "fee": 0, - "recipientId": "AMMB8GyfXAnSjbEQap9UV1KxfiQD4SFhnZ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022041f56e22efaa29c67f715270a3ccf5f1fefd2b2c9a3a3473afdaac2731542df802200d42682d5a7f7d578f3d8065395662cca264afcf6c8941d558a45caf3528380d", - "id": "3eb6c19696d81659c7bbd979cd9d926a6473f86263b262aeb0ee10ea6c71a57c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5680013606036, - "fee": 0, - "recipientId": "AVgqPjPJ4RjFpw2bqmMkvHGHhTUnvrT7Sm", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022030881e7cfb479dfdfceccfabb2acc03ffe03ad5e515c79fee770be69e107d75e022071b7098cb80f71d6a26cc8aa3b62df4d1dc56ab67cb86541c3a217ec9737554a", - "id": "9571f17bd17068f34b02419e07be1c2e1a2984ead5800a4b666b38ca8b235816", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5880602286541, - "fee": 0, - "recipientId": "AMjo1KKaWVXTKxgY8f7h95pBGi2wjX9kuW", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022077e2def23f7441b5eebf718fcd350c6a5af8207e01f549932dc78c00eea12f2c02202697c2c7aa53ce55a12e88f4d28a9a92afa6d2e0b03da2c81de9cbeea5945c6b", - "id": "1f79270181b53adc2a6d320fdda137a3a2b0a5d76ebfa29623b7399f1bcd1cfc", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 5893686626629, - "fee": 0, - "recipientId": "AGWQjb8AZSSgin8H2HHHHcMNoA4iS1HweV", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205097acaa5ada5c48df6a182a101fd1ad2005a521ed387bda6a3cf48a93951796022001c1d933f2d00539a616151571e013e10a22c25804ca301ddc65652164a2e58d", - "id": "394aa80cac2db93a5aca32bed46ef28feb1c5e49b0fc26cc220bcdb6a255c58a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 6008981564334, - "fee": 0, - "recipientId": "AKX2t7kQ4asoStjoSHQKhGVBuDLfXVwtHk", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207f53f7fae41d09c34b7371af87d33ccfad7b3fd29df5644dea82ed1e45706d620220027cee4f90fa3a0e6037a4a321251a53940df0b8d44564343068187d19e423cd", - "id": "230f86676f4336a0a640ee78f3534633d1af95728a495c47bc0ab4ef1d826604", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 6048199451708, - "fee": 0, - "recipientId": "AQQCMjGnEgsutWWRfL8BbUrsGe1z5HryH4", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220211e94fec0b2a86f27a27009c1ca016d618d6415f2745e444ffe1d601963f2d1022023829c5346a27ab73f3f70cdde7200df4fdff5be708c6f965874fc200d529a18", - "id": "b26eba2e7164c76a09a874efe75237c6ea80369105872855350b1d3227d0249a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 6062051615496, - "fee": 0, - "recipientId": "AdTSi59wjy3uZTv4tnenKL9fFWajaeYsCU", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d5e6a1d7bdc9206e118eb2552e96893ed4af47543c1ac692798bc2085ae43fef022067cf6d3e916ef610dcdf6953c778f24306a989041bfff1ef3f1f599b90f11570", - "id": "6d99eb7d36ba0a942ae8e0c870b31312e6445392c2a05dbaa8734c56c249f1dd", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 6099257525671, - "fee": 0, - "recipientId": "ARPxAfyT51W26UdTgSbfVRdjUpzeiLg3Th", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3043021f4867133c5f9563bd0e953c12c2c7ed084e469b2933da10ba51eda964f3f74d0220527964fd63a3c23a6e140d7d2c142a0297d02786577d91f3a97c863ff1f43f00", - "id": "652889dd48a67aea94b758b01f454c7126398882189dc752bfb11e0a7d06602d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 6114940797408, - "fee": 0, - "recipientId": "Ad2CVhECLL4ekRXmFVrYjGjALmyFmDBV82", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fa235782a7afa225cc198c424876b1271be94a5cbd18b8b11a2d3a6e5b1ea561022006309b82f6241beea5cbc446d902ae570cb18c6847060e45d6287a2d0524302c", - "id": "2be98fdf7807c6d82dc2d8e7263194164b9f55822de204b1fc9258b735cb9003", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 6177278671897, - "fee": 0, - "recipientId": "AUwfer8xFsv4Yk889R3n4oQF1nxAKts1tL", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b8cbb6b32084b8ef479e3dfa560eb6a99fcd8391c26299ec9d7339e98bbf3e9b02201effcbfd175cf6d97ffc072c22d3966ed4b22ff771dfd78f7ae7b03039e00bdb", - "id": "d6c0d6f8ccb456ff951f74e3033b503433185415be2fef77b700b096a1dfc233", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 6233789537763, - "fee": 0, - "recipientId": "AJ8vfPKvPPLJFdqVSQh7BBcZRF5GSTvBQJ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022073c38a4d26f344b6ead47c4ac5f7e6d96a8946d423da13bb14b24c84c69e68c1022071d29be6bfe25b7b4e33b7b6d6757bb435f9ad9d00b1824b81cd5b16f1e4279a", - "id": "277669ed0959b0a322f658dc6cdc807ca4526ef9b7fb07f7b2c55436cffe85ea", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 6249771580151, - "fee": 0, - "recipientId": "AaXTDvrMHd2nW34ENidytBxjtrALaB3uVF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206c0337c25c9f541d130700ac0475836bff0a69b427ad1a352fa18155212ceebf022022db04421beced42fd6fb6723769f5eb19443f4cee8146a11f2386fa882087b6", - "id": "5de7ff26c18b4b4ad9ccdf0e09a94fad3053d7f61a4d07beaec231083324a602", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 6336348963748, - "fee": 0, - "recipientId": "AGeS6jmsQfmvS95KTbXLCFEKqpwUoEAc5b", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022002d5c9af048e1efd8bc37d2ad0e0aa64781bb3a738bacda828a9d2c3cb7affb302205a3594eaff82c4c46a6386089df34ce45aab71ef0ef1123e623cd64793c5f4d9", - "id": "c13088a3d622fd2925df99c39a4b25eac1632c68b3386f3351d9ede44b4bdf48", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 6347464915470, - "fee": 0, - "recipientId": "ALvAAwn8MHupoymbgiJSCDYiqjQrWTEQ1H", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203d2c5ef313400dde6be26935d2bf2eaa8f7daf90acb6dda71af277fab69b55fd02207280ed0a4f4114db9dbab70b067343a36ca6361e0b00cb9064a39730e9162b76", - "id": "631288a43196ef9f2d5b214a93c261d1453a532d83f3f68a441905899c5e5165", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 6608432032957, - "fee": 0, - "recipientId": "AcbUExwmdGDJgA1GWAjdEvx5VZv5VZT6ck", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e9c9364a47769112d10d48b398c5190e30c124d1c22b6d3bcd751976bd149bf70220297154a4646628b181af186fafc2f2b89c64b2d6b91b472f14353a324663ed63", - "id": "3def7e1baefa19fdfaa298305b4fed13eeda8ee4d8627304754edfa9b656e403", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 6690095694899, - "fee": 0, - "recipientId": "Acj7HFPqsAauRBAmfHqAcPiscUCxdiS7b3", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022034909f75febe76da183cd50b5f7e0745a50b6317d1dd1327eeddab9f42701a9a02202583150e149038dff6694c382b45949ec0d052d1edddfee4cc31da83d7c39311", - "id": "e912a67d2b2f1f5579c60cc12b09898804fd06914fb6b7032add9807c7666bd6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 6816039047765, - "fee": 0, - "recipientId": "AHBdGME8KmcvG63JkaQ1FndwmPWyuBWTgZ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ba60a9d3173dd42720e271a63126cf2b49f83f0846083bc0314d047be29853cc02206d2fe11664db806837fdf975675360c51cf5429a2a32ec88439f4969a40e970c", - "id": "4d1f9302f5b7ceb7682750384d414bf0a5f6b36820de505c8965720f46874df2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 6845037580681, - "fee": 0, - "recipientId": "AXFnbzewz6GyVVXuMhvAm7cyMLZRijpZXZ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210084adf931c399b8330219ecb1e0091fa02e7e400e8f44abffa0011c3d5123a8c702207333080ef7f923e35c53f55fbda2cd3b686fd30606b0d6b92fffafc0990a51e2", - "id": "beaa5d25d2d5156209f7a6bcea44f483080f1a3790f72e2e7405a1b7adaf7d83", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 7198886304127, - "fee": 0, - "recipientId": "AYidBk2SLnvrtgCu9ZYX8thNPweQXKoBgt", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bbc9400997cc61072e38bcf2a3b4a600275332a5667fcf9ba463935309b6a98f02202fa932ee91e6f940b9d943255e1ec649517302c5a93b54fdf49847ee5c0abcd3", - "id": "3e725df96a6f88901607d832bad39c83e18f7b52e689a3590869c85cc4dff5db", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 7274098215498, - "fee": 0, - "recipientId": "AQiRjkc6LSFBc5JgQHfEtRYTBKzuibCm9d", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a0d5eff67117daa369f3b77839d2053ab16c5b659dac2f3457a75877cf346ad402206ef582d8394a23e3f535385d02ec135d09bf6aba3c1d812228d59052f3cac48e", - "id": "f13e17151b9761aa406da0d987c8790eb0556d93eaeaab95d5d2b91458a951ee", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 7350458828062, - "fee": 0, - "recipientId": "ATWCEtjbAVLDMNCyHmdRMeoiqcHPahb3A5", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206fd243d047585330ab1711f38f4a5d8de5d283df73af280628aec73c56bc13f3022035c0af74cb2df381b3e3ea2056d77859d085da0105c2e85edc6d607a6ed6df57", - "id": "9492b0c5b908ff768932a67731efab51d12491d654ddf6144307949596c4f581", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 7350605843105, - "fee": 0, - "recipientId": "AG61Zuk9tUxhu53EfvBBNzh6aLiHshmbgk", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220289eb9006920d7e06fdf963d3a6b1252528cbc897bec005c20773c2a6fcc1d5d022010e275229dd9e83053e9cf8e931e8d6bf28c8b5a7a40275f6da2d08e68f12445", - "id": "a2e69e4e5391966a019444eb1920d8e6c8793cee7c6778848f14eb8e2a711d7b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 7350752858177, - "fee": 0, - "recipientId": "AJ89PsZmopZgpAEJYiFpN4Nsf37AmqGFEJ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b1d30a5b801388ff04e1afbb0a1a08cef63e0ea4f256826b49438559914965e002204c6311d26e092f282985591802be6819a159e368f86f8bc72b39ded94b3bb93c", - "id": "6372eabeb335296df1f7dd2d62aaa635bcfe1259195ae57967a47b1121d16578", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 7350752858177, - "fee": 0, - "recipientId": "AcxafSyp9wY2zZkqDSfX7uM1ZPsVsBw2mT", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100af2d76096e5de9e708de3bd6d68f3144bebc43dfbf09272a9f402870c2581e49022017a11a3726d0930d47a965393455304dee143d0eeb992fba84880474de00a211", - "id": "05418f5b2eaf40ebb39646280a9f1ba7850e96727bfaf4bc4379637ab169e7f6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 7350752858177, - "fee": 0, - "recipientId": "AZXTaxgDD7YqDPJLTkWwKgB6XPZT9L9Ata", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a54c0c0c82ee1943d9fdd66b10a0a6f70f8697bf5c2e5a70b108ce26669a639b0220463b72006b1f45a7403ac406181fbdad7ad4173d4109f20c25c890551851994a", - "id": "d311075ad854a0aabcea25b63cdcdf8f4b81ea41aeecb05e09f64c5e9d9056c3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 7350752858177, - "fee": 0, - "recipientId": "AeX9ZCUY3TgYBmxhT1eA28buQymhzcRkdG", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202bc46f5c4714ddec901a698d0ecb09e4eaedeeb654ca202f55f3af3256774a97022034c060851358a005d60fdc2c88a4706e0cd4252d1ed84f64c42227bea5fa0a5a", - "id": "f5338178edbbfef4715e79399f932057179aa09bca7ec7fbae3c170bffa5bded", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 7350899873234, - "fee": 0, - "recipientId": "ARnH7KwF2dFqZBaVtg89YRNpiFoN9E5s4G", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009a0f64be9d209adf953456872e1bb098c29174f219b4b620287a624663890ed802202894dd9517455c765ef6868b12239246a2b4a862cd9e5c5e53cb7e24b452f5dd", - "id": "833780d0f9ddbdd029d0a4c3fc15248044540cafabd9ea99f39ef268de3e559b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 7350899873234, - "fee": 0, - "recipientId": "AboFjwBTGZYhpHeBSRyX1gRifpmGaba1Ne", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009f02dfcee31f851c53ee9ba7747732b8395dc294e22ce326bba9087ce845939b022067c32661f0ea2f62133dd9367ce19762e8ba976ae5d9fdbdd0b5747880527ef5", - "id": "da390944985838649b7fb34067c7335926531602ab7fd04993f3d3dca8cfa0aa", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 7352075993676, - "fee": 0, - "recipientId": "AHJUzj6eMQ9331gQSJ8VMmxwyTxXgojdUy", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202efe05396510be9de3a85403629b8de370c7e239b9aa7ba8274a3dfe813b7639022019fb6626e456e050efa3b49ff2bbe5d53ff9c7b0f857bb46a60c4574ae9706e3", - "id": "6baf648e78dd4f865452d1b2a10008e9ba53067f5f7b33689715e1012e11b920", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 7467974903126, - "fee": 0, - "recipientId": "ARBHK6526c75WtrSA9twWuFbcvxSQXh1ZB", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100820cd452944f0b765c309ecf5888f897a012290fdfa80ada1d79edc8048b068002200a7b4d0de4d6da32ca4e02527c9630f08e2efcb2b5d9a2ad9fb2ac59f9236058", - "id": "b615051a5fe01170d7972e4cc06eb635e0ac7d8435f7122873308a029d64b029", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 7596490265803, - "fee": 0, - "recipientId": "ATDz1si1NgxYVhwco5FU8rziXikgzMswrL", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203ffe6e56154cd9fec39d3b5ca60ea6b371eb95dac9857a4365b807b329156fdc0220733a0c695b65af96e3f2b8b92661c6a91e1667867580abf25f146ca2362e2545", - "id": "7c77338e20a9478450a35a4ce4a503ae93c1268850c4582deeed42b2ea202600", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 7728600535371, - "fee": 0, - "recipientId": "AUzNXqYNtmcbPUesxouPuLnAqcvxeBv62r", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207dfaa07820dff0518a6781b5637985188caad8c308a6716b0eb5b8e49a399d1a02202a44037fcd6142cd558e01469a283cb7abaa6b63f383eed37425a830a38952b9", - "id": "98b95f131466f769280d7aefbe5cf7fdb7aeefab4dda10d561fd22cefb329e1b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 7863973424054, - "fee": 0, - "recipientId": "AcGpDjv1Ja8gybNf1Kg1y5wfjRvB65nLJ1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022001916ce368bcfc70ab18b04a0972d24a4a798c1bfe1ac82af88af3e57f05109d02207d702327da226d245e3e73db80fda6ab378037e21cd96129b5eda4bdc44f0dd2", - "id": "8da4b1228b425051845def8bf958afddd81be64966832d56ae85f30c09b55298", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 8061487401793, - "fee": 0, - "recipientId": "AYpKBgSEGotwZF3BvgpXWxGKXsawYQEr7U", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b566d3021fa2bd2a9bac9320149fdea4f0aff2e710affebf98a8e4e132270f27022036fcfa529097201dc793d55940b20861d2a359bb14d82e8fd0a1ae17359577ff", - "id": "7f88e5d7f79c63317e0c732c03b451b1ff2714bd5d87900faf43b979e551970b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 8091708746281, - "fee": 0, - "recipientId": "AMgi4FUnRNsseqVpBVQLcHtk2PxkRFkv4M", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202750a5d5fdb30175ba7a0e8a417026664dfde32cff5df156e5d73e25b26b5f0d02202a6ecd37f045c321be0526ce5b854dfebd8a7b29e646fed77e98a126458a62e5", - "id": "834bb22d473469b193464fc80c52058042e14d82d6e3ed85b463cef6da9e68f4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 8150156945826, - "fee": 0, - "recipientId": "ATuYepEksWdtwk3PTdYEdjytkTKv1L3R19", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a2f40bf9457f828ef8c32f1bcbde320417f0f70eae75ee0492509a44986b97be02206a1fa49f2b1079da0af70eab644a2e161c69774bdcaf10d6b924e3b4b0730a9c", - "id": "2e66f27a9f97ace05f8168b01858118e23cdb033ee68c84ab0c5cad7d9154eb1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 8360754246642, - "fee": 0, - "recipientId": "AbA6wrkyQS5p5iDZF25BXcvuUgfZm3Hy7A", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201477fff0e08a35db8bea53057aa0b8cf323ba63d32e3ad28bed7da6d79007ed102200a5bc18c359443f02793e2673e615dca45013fba0dcf11d9719d80478e8d6e93", - "id": "c087ec57f646b73353792010f5dab487f12111ba61950c3cb4dcf26c51daefe8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 8820462384640, - "fee": 0, - "recipientId": "AZf55Hbr3KnRwHbZn6zoEov7TSkJGhDHWU", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b964e775cf55c8c3c174c1a06ab32919bc940dc74121b06f5f985145c06781550220452aed6f7d901c9ea5ccdc9a3c9538fd5f2fffdc8b4abd1778627f54674319d5", - "id": "206c25241d5f0bf068e1d532c0005957992176629f09a897dd39dada84b7d500", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 8820903429812, - "fee": 0, - "recipientId": "AankYCzRAR2mK7z2fRXmXxzUwJMknRPkDq", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022023e35b79c2cd4429d44e9a152371760a704819b464132b8464313c139e8be78b02202b5fcd45b45515a5fb41a997d93f55c089e65090a1ffbf31f07147445181d5a9", - "id": "62b7ad211b62f4c87e4dcd71d72cc6d9aadc8a4077379d96377e3a8fdf468b78", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 8909427869508, - "fee": 0, - "recipientId": "AP47UoDcZ3XMq3P7VBLpJJnHGjGaD7yGyu", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f5619dd275b2e1857001e6a803b2cc5978b562f947e569bb61c66d57a381075702202087f6b32d421f28c766fde6853ce0aeffb672903a9c065f3520d4111653d5f3", - "id": "8a8001b5fd69483fb8abc89b2c13626c30adcdb94c04a25dcdc1d81a5bc3214e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 8947739735763, - "fee": 0, - "recipientId": "AeBVmaLa8zk43EApXSKPcTudHKRqDBYFWd", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100846c22b8c3b999a50686b0df62755762c8f3d6e856d30ef3e467ea55f0755b9402202611440aa0e7b6c220399b990995b991324b3cf2fe8eee8d3724736edc00be2b", - "id": "e434a3e0a1ca8c488797a9a7e10361b4f0889c8528263f2d5da8de0ce2f923b8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 8987732597729, - "fee": 0, - "recipientId": "AYtFa1PZcaXinZSwvsjoym27aAAKwEAnaX", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100871e75cefa2f4aa5aa3e2602c3a918a98589889bf157f72e7f550085423d3300022058f068d7af9ee807c49abae45edda26876e1205504229609397e42bf26d8b4a4", - "id": "89f718ec38b7ac3e6ed5c353e8d6c6b7c77558357fb317e4ad45f00a593b4d2b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 9019606582562, - "fee": 0, - "recipientId": "AKeLaHxdcSBzMyM5bJUvThFcTHsVxmmatz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210092e17b11d4c617baefc3df8fbb432d34ff142e1eba516f363507e6f7d5dd655902207278a3fc300dabd87c6103e680d0b4f9a9a9daa9a7ddedb372d8a5039a5b8118", - "id": "b1a04d1ff1bb94dc1b0b1902a803f95f29b4cb75577899ec9ec8f0b3d042fa17", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 9073460596513, - "fee": 0, - "recipientId": "ATNoXbqJPxJLUxNrPoGDYXo4TL8dsetmgz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008120ddb52945a8a06d38ed29d2444d99ffe8e2ee7a6626ab6444b3484cfd94f102202f304a833c539fe335c91ec20005231362b5872d37f2f716ef8705857dd44bc0", - "id": "aa0f4fabc4952d57a65943c6d9a4c6ffdeb77ddb984aa4c6e73918028e697816", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 9106661500215, - "fee": 0, - "recipientId": "Aa3rBGChkK7iz2NVnkUx7pkjwgtgaZRpVo", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e61fe14e4167951da3bb22c81a12a387602ca09796354cb5d2dfa34b453e31190220493edba4cce40195451c2ef5c260726dd80a81f9e53a2c0bf8714c9b2cd33b14", - "id": "9db14bae790c26d0073c5cb21b54d8a860d79573f6a24004d5938db55917d20b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 9116425808613, - "fee": 0, - "recipientId": "AbSakR7RXYAEWqBh4btWuVXCG29HTdxCgw", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009d069471d4566b87598efb5d7f4550a511b7ce0d303f8eecae2532ec3cf1664b022072ffdbbde754ca2d1b57600ad8f0bf8db9d0c628a4f761479ea547af329091b6", - "id": "a3ade2de7783875db21cdb97071f1cadba19e2a5258fedea37396443f7dc88b3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 9176679868148, - "fee": 0, - "recipientId": "AKWjaZp5KyKzdCnJq9wQiF8umWCJL33E6B", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f6d4983def05d2a75eda8867eb47c6ec6cef8c4d13d43c85302161685ec7a324022055c69ebb708ede45540d87ac55fb4b240f9a1160a5ba8dd1aa7d49a4587e6572", - "id": "4fae51a3f14bd18dd23869bfaa3fae58c31142644e18d4422ff46f922f68ff1e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 9212629406283, - "fee": 0, - "recipientId": "Acj1Rg3SaJrZbiPsTRN7mfzKwHxh9ZLciN", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204dbd9c143b3e6d1335ccee6965087fe0d875d4d8d9c9d3574e018317508e248302207cc154f6ed0baee54eb44551e9a3d67b89ae5c539568c208b6e6243e881909f8", - "id": "5aa7d5de2c38dcc27f69d679349af0ad153ee494ecaee656a757fbf3cc1bbdf0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 9261801586245, - "fee": 0, - "recipientId": "AJN6KnBY8VxHfiE7YDyAwneXCwDVtdh57q", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ce0a2d090fd6bfd170616d273b648bd862a37d30e5dc365af8e70dc702f0763502205b95aeadb2b0e3e02a68638e5e0ddfdf74ada07ee5630e00b22e58392144f3e6", - "id": "006c9df41217c960f9888779a7fe79f5f7e30dfc06d450379c54aafe01820606", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 9431021629737, - "fee": 0, - "recipientId": "AHpsBCfa32ufzETjsSJj3c3hJNGhjAyq6f", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f26cdb6fece9de8ef933318bb3af05a349e888ee8ab47310aa829d96f2b044d602206f97eab55c51cb15bc25e66da6832db6a4b436eb538aa727a5264fff4ccbfe37", - "id": "f543ab6ff8794636fe7aaa81367bbc5b3c47bdb7f9080df77d3fd09067941b56", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 9456800520174, - "fee": 0, - "recipientId": "AXt8HyD1jBZsYE1c6x2HdD8HoVJsjHuqjL", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100dbb11f95e9145884db5207db7a7a45b4eb04bb4f0c833c2949b2019194f7c7b602202d8c32008367ff0d486a7c584eb23a9feaf533188ad2e38d2a7bcdb912561ac1", - "id": "ddd35a61d78d3ef066178e240dee956ba70f73b0ff41aa59fba2543c01ceee60", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 9529256927883, - "fee": 0, - "recipientId": "AXmh1DXiL4g4JjqJp2RgQdHnycZspLeztT", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022039c3d55cd46134f05af871e13604c76aff0e92381980fe2d4e5e61f63efa891a02207b707a5e64a03e5ec1b6cce3372da6da6ca0ebb2e2bab0cda4ee104aac92d0ab", - "id": "ac0915e202a613fe3d133a3b8e0a4b86737e0d06bcca02202d583e5e3ddbbb3f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 9547731294407, - "fee": 0, - "recipientId": "ASjGMDGXLfB9fViy9qPqYGEjgv1jeuwWox", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205e2ad7e24d33353687c030186542c4ecbe2602a35cea56d4e7b241454e785e51022028d425089617605caac7b1827be5cdf18abe4c8235c2a622669c97af32f965ac", - "id": "d31f907d222323d8e5d6034811433ab67dd7dd334e0a5c6bbe24cdd3accdb501", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 9657132907103, - "fee": 0, - "recipientId": "AGFfszsJjBPwUf5ko5gQJaX76xFVoKbh2E", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100cece660ea46f4303e6b76cacb62da21ae4c8a6d11481ffbdf1b071e49d498f4a022034ed3118a40013cfc73123a39ced55c65bdcb01dd8ebccdac9ca689faf8a8514", - "id": "2cf431f04c0124d8149a4493ce3b317257512684868568884658512f5bf02d27", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 9720973358155, - "fee": 0, - "recipientId": "ALxR1nKJXgDfNtEuek1jaiNJG7gNtfZ6qb", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210086691e951494d4808f416e82c1d346653b04f39272cbcafa934b3e643447dbcb0220292d0fd8915227ba07bdc17bb7a7e946f6a50c4c063950fccd47a73433f5b42b", - "id": "f532635980304bfc7afc3ee2873890f529431ecc3217f8bd580f47f1369d0541", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 9861574288956, - "fee": 0, - "recipientId": "AYAYsDGPzVarAcqXrpUE8eYcftN9J2UNtK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d7531a783114009e2775b63dfdf0123ba0bbadd193e284a9076b4e02ad1063dd022041f3c3c3a9dd40d17483c4c01d2615eb89762611a5924718a517aaa0ce09a829", - "id": "cb3f0522a4824486a1277d54d509ea9956aeec80cc2fa77dfdbccdfc7a888a62", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 9967620875688, - "fee": 0, - "recipientId": "AaxB3RG8EhWNB1axLbDrxza3v5E9kphnwn", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c9f22c83fd1327fab869e27902fdce90ed952126e7c43c7e9afae0cd9007591a02202141f6f04a4a4b519fcecbee3afa90cac41a090c31393db048946d780735d33d", - "id": "a47732e4feab4d3817daa01358f6f295d9e4f07ab1e383f08b09ba0cb755af05", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 10271698022691, - "fee": 0, - "recipientId": "AYDYbKKZ22ym3ZpXcyzPWc7bnvZXRZdiem", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205b259b711e8a7cbef4e8b103cdd7af07e6aed38f65d0f91ce15f906dbf6dff7102202a0e181a4c64494bb7a679e0d86b397c644e3139e0ecd8c90e38fb7dbc798811", - "id": "a46b6b26e9c55a9a265845d4109e3e03ea963491b5efa4756e49a6aaea94d91a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 10298404754306, - "fee": 0, - "recipientId": "AcdSagvBLHcUuh1f3GbqhwxDZ9kuNgt9Lw", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201deeb7ffb887271b4ee12f3feb9b5c5ef4ba4c60cb4a1bc6555474cc96e420830220542663416f5fc69b7797d6a4d231041a6d4467d77d1598e19cfc7a64d89632df", - "id": "f9006c06167e9095b7398eeb11f8195d715b04be4689eebe59a5864def4798f0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 10305487746344, - "fee": 0, - "recipientId": "AGVBHkaRr9XWBLZQJDdV1Q9RXcoB67PBeG", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205601083db78190cda32bbc1af11fffcd7f933052794e7d4c95b6292db63c3f2102202bdaf8d58e7cbeab12ce6fa0d34cc70cb02f381273d533db553e15f9b2cd2d08", - "id": "66092335843fe18ab2c4a8d137fe0e341057060964ab4169383b461df9f7f385", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 10318455720429, - "fee": 0, - "recipientId": "AdbuHGsffes9YYyKBp9DzKrcQofg7zuSKQ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200a8c0a7ace259d0393f8c3662af8f8e27a0f261e69fcbe00fe99a095193965ad022034badec2675471d2d846e28aa0f4cc48ec9d1b8f6879f2dfd769300496e0dc78", - "id": "457678f9e0c30fd116dda8f82fa4e479b23a0139d36872bc4b992fcda5fa095e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 10360354001447, - "fee": 0, - "recipientId": "AXkBtARJ9ibkidfLMhzVSRCAuqWBxRaDhj", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204131d85f9493b052192d281fef2a87ecebead69c4e4b72b306c967a2bffd013602201786ecd3284b341843cf434ae92600de4bd044840ea306ef36372da4f8fe7e13", - "id": "eb6590373de4b7155bd45679378a312473be8ceee6c9c4fb86f2ebaea4c1be1d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 10445981844431, - "fee": 0, - "recipientId": "Abu2eNnxLNUKimhqKJg6x2aAcsUJStuCAG", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100eccb27c0cfc2b60f4ebaf647654132c4e7fab649368a77241c55f72dc8ca3025022016463ae4170d699d3cdd336c992057ac3d5c2ace3be998adb189840afd328d20", - "id": "b4b03eee17ce3bfdefba3d60783e5fefbfa99f5e2a3ae36d3a77f42dbba37837", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 10446382135839, - "fee": 0, - "recipientId": "AXBkJGbRzM2rUD6orebcoP3peg2xGbgT7j", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022008bd51d33b28766782018df754e853f8d92672e3ff570d1261fb1cb2f30fc9640220022208c3f0da9cb972e2c1888f1fb720c1e977b7376caff753f23fcad801a5cf", - "id": "818bcbbb73b9470f0e4d0e9606f937ae2281dfec46f55251ac7b77d8ae545bd9", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 10585084115774, - "fee": 0, - "recipientId": "AJXuBCyg1jivEDuJqrJwu9R19C6uNG3rHM", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100887bab7c2e2800b93cc9df58e418ecec30395fb35db6c3598d541295cf4104580220128140bf3e75736d5710c9df703b7f3a9818a6a1caeae847c0ab154645bd68c4", - "id": "95ea9f02ecbd2722503b7ad6754a29b3c743e2e114fdd403fc96b16097a3ff31", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 10792241696323, - "fee": 0, - "recipientId": "ASSd4LMA4BLtskUS3XvPbQYLe84qCJBF5V", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202885acb08d8088274b1a8ab4a28c796f5489b82412344c5ddec9f21389bbb21b0220525fa2842e1e3b91b98a5f8326cdc89d880e543da82be9adff19d923a87b43c6", - "id": "872e270c3eb5dc0d487a040745293ee5373e2622ddc9d34cf3e4ad793f2c6a25", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 11033661805931, - "fee": 0, - "recipientId": "ARo1r6758wTHS1QR8LnhJiyLcudybWPhb7", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bfa945a5eb00deeb1f4774b9a2d46a44832d38877976f385be3fa88628c00ab5022024546c571207800790fab06037d3292a46d31e9c947acf318a27f67db8e42ead", - "id": "1e03b495436ab9cad5aabd942c84cb315f7c7796a597a56a6aaf9680f1124173", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 12262151610133, - "fee": 0, - "recipientId": "AWFxQB7mKARk8W63fNRpp6kZjVsapdFsDa", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a31f640dc8ff053899dbcad3ba8b4d67b1046db0c9cfd2dbe8c15a1b7fb7d920022003b089de0b92bdb55701c76ab22306636acd88e933f75cf6d077bd4cc7844c5b", - "id": "e550f7a823f274dfb83ffdd148c2d18b573abad654583438af7a702ac6de40f1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 12274908823760, - "fee": 0, - "recipientId": "APcaeU4K9uQXSRLRGEgDM1G8gtiBG5tCYZ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210098a25f2a406512474e48e14d7d009cc8e7349e7b4a51f2ebcd63f4996c2507a502203cda6a22c4fec125995d1163ff1ff725ffe060c6b720f91ae76c20a7217b2e6a", - "id": "736b8f9771bbec8794353162441c1dc30720dab02dda5f17721f3577b0f7b454", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 12417330032449, - "fee": 0, - "recipientId": "AHLhrDJwqQmHnZnhEPBJqfEHYHegt9Y5rd", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100edd8d1aa1c15c0249e1cd88ccd81edae73250b10df8c8a32549632404b5f0c560220441b966469b37869d5415cc7d90205be883870c3895db4c8b5ce2b4a7d0dba17", - "id": "9340ea9bdaa5155914bf58d74b856d532f1710efe6c569ecee144d1c6ec51ceb", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 12465317334603, - "fee": 0, - "recipientId": "ASECaxsSkxVDWry9D5fcygAY5ZuutNBvPG", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201ecaf13c7ce1da00ef9f8e28e95be0d15d26951dff1f742399e5298031337c8c02202d756de469c1f1ea127c4d785d723ca5c7f6991bdb8b8e3a9e7be2498f5c9377", - "id": "6902bbe0e9cb1b9cf2faebf4f96d27c85f4b1aacf9ef7b4dd52aa7d882b4470c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 12637331924100, - "fee": 0, - "recipientId": "AJ8YEJ79noVf2n9dm7WktKLy2yJPqmUWiE", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b4236f3e9b43c0c3f503906f146a6c736e8572264c9da1507de8cca20d7df08902203004c4ded33dd32eec7b97384b8edaec3cf17c8b0f813084703c289e17572cfc", - "id": "695597762e9a98153891ef3ce163835b600d7d6c8d622b15b53f70b1da3ec08e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 13156450295530, - "fee": 0, - "recipientId": "AMnJCV7YZg3aQ9XFH2Wm48wX5Q8KP8yf3s", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220798836f686318188a82f5a0b37e419bd52a94582e4bea79a981bfef94485a06002205539539bda455d85f826a824772f5a068359d2c0af5f8fa94876fa7b6a83f14d", - "id": "dfe1528b1eda46e77923bee940a97c678ac3ccf209e146cee8b4d9e590d652f2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 13231355144718, - "fee": 0, - "recipientId": "AYhj1z71Ajs7gQPb7TQoK1Th2HWne8Dicz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204e4e7921958f482891c6edf82e87d577bc1525ec26a1606239dc3bde80c7e20e022036dfd51919362e104d7cf98a1d2fcad1517261761e780569d60503dd87c85b9c", - "id": "d531efdb301103524d38dd7720408e6692c77d4b2581c46b44e98352952e2990", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 13433119945670, - "fee": 0, - "recipientId": "AJuwxFoWbhWGiSghWDuNLh7B2u6orxYKEr", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e2e10a21a73b7fb49a51c9ce6981401bb7041ee97b929781e9a33c55ba85f8fd022045871513d5cd53061f7b21ba1638ea8502f17cec0eceeb1ef66715712d3a2d92", - "id": "c37d982d2277da42cb03590feed99bf612911df0df4d0819c410d906e28237f5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 13711299443014, - "fee": 0, - "recipientId": "AcFJgpMAH1SCrCRcsgWfXgbQvDiBh7suEd", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206fb5cbaef0ff5662a750ac6a89b6090cb5e54934f86e1852f75b65801397fbb4022052e7eab49c5daabe9b69fe1ced745c2cb722c3c221864f9336584082bdc750dd", - "id": "96f9f1ef9345ec0a80a1d49ac3a366ce130174c328432db08758a69f694ef2e0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 14115873577166, - "fee": 0, - "recipientId": "AZLm98LrEyNEmbfe2AHV1q5V3FZza6x6fB", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202d808872dea296887d825d2939947ac1ef11f72f86dd8d0c6725f280d611ac9002205b08d41e9438076a7689cc07f0d74f3af73158568cda9b0469cdeb4d631c1373", - "id": "60df15ce23bbf98fcfef08bd24668c1951af818540910ec3ad9dcbc5d2600995", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 14159732932194, - "fee": 0, - "recipientId": "AWiJP7L3ZvRPzZyc6mmM9qqNcaYbL9ABxx", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204c66842803665c4bcf29c2043e1d9714e3724a1e1dcd935e1ccefe6228704675022010f26ee2519ced03bd61a8095e0830cab83d159c56928d7f11017c67b8c6247c", - "id": "9d5279ed2f9c0e85a47378bdfd168d29b83e2ee7d6ce8813c3a7b4a68fe50d85", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 14302397057832, - "fee": 0, - "recipientId": "AcRE9hpuKFJtyqCSGDS1QcAFTYCU17t1F1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022019ce5205866f88b78846a07a0862122d34113cd250df8abd884c7202603af10c02206d05348f0438e537108ae858798cc390f104c69047eb00aa47455bd89fff0b22", - "id": "5dced5fe4b20327ae767ec3902dfe102e6764a86f20fcb9fc75c6356cbeb7385", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 14627998187772, - "fee": 0, - "recipientId": "AeDZmqf4hg8HYMvpxJujrVWqCyVUyqZ3Zj", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022050e3a4396de2c6bbbc7cd1120e46a01863b9d961bc19ed42f98a18750e61a8bc02205ba8fd0e08d26df41708ae78280ba08fffe1dbdac48092b464da8b77649a716e", - "id": "816320ee8653dbc97e201b24ab45af6d3c4d182ca9d59b96c60f218803bbe76c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 14701505716353, - "fee": 0, - "recipientId": "AdTzqZEgkihPhhqn7isgQNM7UmJCFjSRaJ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205fceba8d1f594a4c189f6939db2567a740598184cbb2787a82560c7d33939191022054ec9894c1f430554cadace94c584a6c6ea5f907f06f5636de53d01c9937667a", - "id": "bbf0def2ca931ae79bb421b00044e5873840c3b761a24cd6db19161a069d8575", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 14702758612028, - "fee": 0, - "recipientId": "AWSCxEf8ZFBNNMturNTbyrwaHqJHTAz5hF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220415ce13dfa1e2a0f62c4a1b452747c0818aeb16cf852e7c649a7e061b522295f0220738dd8e58a0bd7b0cb3035295d38aa9c164cf1a023fb69c2512f964ff840c0b1", - "id": "39aa37fb064becdf8f37a9bb1e55d3a1e8a96beb6be1fc1779967690a74dd7b2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 14702828851868, - "fee": 0, - "recipientId": "ALcRivRn5MpE7gjYPfhvdjkT7EidnM6AsT", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022041e7c81b713fe8ebc58fe4d9db338b89b3663afdd526ecd31dcb0b6e9a8f8b1d02201eafac17f41fdc7dfa29c748261f9a0e79bcfac5e418a1c144b0d820dcf54ab5", - "id": "19270d05eff4baf154541b1e9acea7f70990c9fad504e043c281d384a856314e", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 14708415424040, - "fee": 0, - "recipientId": "AbVMJrPSe2z7UCqxfebGwvVnCPePzYqqPX", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220429ea8d0eb83bc86a8ecbde1777798182f219eae5aa56de3cae7bbdb4d04e054022051e6769e588884e730ca65b1886452de1d2f7050a9bdf758675f78c0606db368", - "id": "a116b90dd6924989225c4ccee132706146ccc6a508c8c0f2055349c71d01a375", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 14818222131272, - "fee": 0, - "recipientId": "AbmqyXrS3NaqZL1JanAV7pi33cxKHewwQP", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b1d6a14c5c53e2c96d5080df55c593d397771c9d4cdd9c87a35dcb886102e7c202205e2ead847be17b2d5c71b93b7b50acffae974232c1164971145e6bdbf912a85e", - "id": "5b864466691fa06bd64c97204b10e4cc4de6acbc4f9f02d9183e96f53ae9e6e6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 14822705716353, - "fee": 0, - "recipientId": "AMwimBgD3PLbthyD2pw6x5kr7XdAG81u4Z", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100cc44646a784672d659731b5c7aa58da1d4f010845bef874c5dcabd149d2c871702204e067aa1fdfcd05315ff030f488bff4160bc6d4dff149e6d0f248cfe0606e5c1", - "id": "ae29982e256bf9213db1e8944b73f2c98942fc31a4b107c2d5201f67595102f4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 14895015891637, - "fee": 0, - "recipientId": "AYJiw1a15awqfRNQWXXuDmxpQ3DNjWitzM", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402205f487f6903a7767049c69f2d80637c45ddf757d5cc2b879df4b12e421223e0c702201ff2ad7ab479099433d5cb74b17dd647a0fb0ea38fe72950601d4510910b730b", - "id": "4b00501c8bfb24939da2e8402e40ef56c58a5c61ab6b4eedfc528b1b28f35157", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15082558701296, - "fee": 0, - "recipientId": "AQbJg9r4c5TxoPPMcywzVfLHavxbWCQs5f", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220502e111dd7864a3bf81e5dfa24abe4f74db23747b2ae7017cdae6cccfb72567e02200737316bb0d540184321f56e7e1099d857c56fb50d9156f2fa4356c56b369d6e", - "id": "430dc9734272db7f88762940c12c32b5063b5ce5042e8846a48937d2636e11ee", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15241050976143, - "fee": 0, - "recipientId": "AdYqsVoQVTUto6ALnocgCvnssTbDWnPAbP", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204012639c893ef39e7d67a0ba9fb133a37120007ae3b9d8ab7544cb2bcdda32640220232cc268d227799a8ea63d58697cfe0a2cc9de3123ff825ee7551a894d0b6d3e", - "id": "bab6b296e910810366f4ccba38ef01545ee34cd4386c0c1f806369c8d71579db", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15458231619516, - "fee": 0, - "recipientId": "AcXhb4DBzzmBG2USvxuJY2uEuFnMr7Qfhm", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a2ce8edebcb297f1948498412a60c8b736320ce1ad347043222bf034b21fa69402205e098d716d247f85e9c454b7b53b43489d55a91be9f19d792ef92b303b48e9e9", - "id": "d4a58d3b71902d221cfe3cf915429551c77a0c4d3c73c92a2473c3c151c1f9af", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15525088530753, - "fee": 0, - "recipientId": "AXDwt4SoPBiSvfjSKgZdV4vMTaNCgaFHMK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100eef9c401c97b593bd75fb52828a9df9bbeb82508006ff0d683309a7c1119e6f10220040995de4c4246197bc51ac7abc5094d9ac8ed8b1a8562ba85634bf430d9d866", - "id": "f3e07c51b39f93978aa8b6e4f133400e33ab808f17b3a47825afa75a58bb76a5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15609782909903, - "fee": 0, - "recipientId": "AaT8V4r48jE2MtWySFmdk2gqz3oz5RkXvs", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b46dbd720df1b60f59d8221ba0f7af053d7447acfe378b0763edc899300e035602206723c53b8ec4b41f1c374efe7620b94e62f0894fc90174404fe2d36c190ec396", - "id": "c37f2b2b221265b74534bb17e8791ae000793bea52102905f35c89a5fea5bbf3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 15840600000000, - "fee": 0, - "recipientId": "APpQDTuoqkWQex8ZjLCHxNf7xpYuEdWpJ6", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b6d5bcdb2466f138713b14a398ad38c470affb52f714693f836574106ea1cc3002200bbe4f8923a286f2e677e660c7316a815b444db2b6f46849d73402466b12cd98", - "id": "4c20e8ee483a76470df958bf62ddd99ee14e9a2536be56599145d28afe1700bf", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 16008869346988, - "fee": 0, - "recipientId": "AeRmfbh2GUoEitySmsQRACFRkPQFh6XxCn", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210082cbed89e51bde2a686b62c96c564914f843ca0771b18ee0f57a1266eb181d2402200d87f5c50a8be268afe0210c67a394dc63cfc5bf026db1b58c8950ea185dfd57", - "id": "9ca43cdb29bb7b775d487bc7392be4f6da70b6065cad1ccf40483b0c53e0b23b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 16171656287989, - "fee": 0, - "recipientId": "Aa3E6iHPVW89sPXamzE5CUfAYhEp9arAw4", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c1f8856ceeae2243ebb9a171b36ab449c637536365e033e244a6ec47688922530220565bd9cea70b34e2f7a7fe5ed5b6b4fe2eec554def7c32b400df035c5dd9997f", - "id": "e370cd7fae15dfee56e44b6b602c0bea7819758a9a61fe0cfb5387cffbf5c747", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 16171656287989, - "fee": 0, - "recipientId": "AYHTbw2P8oKpk9JyPFXKuCw6auvVtr1Upn", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009985662bfaf857f58295def71d71bf62109d4e7454eb087bf0af4509896161e102207bce078f3fba1bddb717c318f6eced379c983b9830d4295ae2f6f90ba2fdfe60", - "id": "7874cc7ba980cda4c93a50b415074b3406415f7a84a10efb43db211eb3bbe794", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 16191556704498, - "fee": 0, - "recipientId": "ASUXHuCsmbvLNURKg8bG33iPKid3dvyX7d", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022061886ff0eba8d281f18efe0bcdd9ee543c91a19fa82e539aab7a1bd83cb5839002204f05a0c30ab8083a8fbd9d1e48f376b52aee5c23470c0f9f270e04e88fbf854b", - "id": "ee246b6d6c7fb8aad48fd8dc16e11b86a796a21d24068a87f9822f6fb6db857c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 16746157299652, - "fee": 0, - "recipientId": "ATybACcBV3KhQEuhhJC2CbEUeAX5V9UpGy", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ae2122c8f375f70a1d174ec4d2d0fcd25936177ecb38ba43095d7166fc19da2802206c22f1e50d9e150d40e1d77bf1e81b153c546c3f55444e9073c440f5f9a08467", - "id": "bdaf7fef1b17036b8c7e15f7bd23c975166b368dfcfa840b5e569e5e95b71d49", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 17763076895273, - "fee": 0, - "recipientId": "AGycSD9trbo9XRQh7cFjk68dniLHM7AfBa", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008ca39400df66d8192aaedf44bc2cd791bed8ac3e0e048f3a94da6d56e201b6a702205fa0caa9f2f27a0d5321e06b0e4a68ca30af1813f7e1069d8e70afa378f794cb", - "id": "d5102ff88df36c6c97dd02fb8ef8e178ea72e32556c91a2e36f41d2363cc4e7a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 17882280422877, - "fee": 0, - "recipientId": "AYzPTsYgUcgZXi1GjA2wHzutz7BMEcbm5z", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100da21d666d853ed342f3cdc1f29eb6e6a557ca202c8876723637b51afd38499200220453d1e8130029e2c9b27bc97babefa23385b46f42b25dadd9467e867d2a6a66f", - "id": "d45a0dad2d8010afcfb30088104c4821dafaffa0c4348db0004e95e4b330fa24", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 17882662903696, - "fee": 0, - "recipientId": "AJAz37b4PJ9J5tnqBoQy2TDoDE5TiN8qnR", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220796c225df7d57a949b38b5dce6ec87cd4e8c1842826369e19ef2fe672c8cf3550220061c7b547e460153c1be12930ca3e9d8fa39edc2cbbdf550d3286ffc1bc2a628", - "id": "ab25cc4069078357692cde0f4cb9d850988fec28be74f7ad3e97c377bacec8ba", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 18446724051788, - "fee": 0, - "recipientId": "AGjo125YZU5Ssop4kuNTUYSeWqxMqvKA6c", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fced646103cd17b38cce5ff86201c1426304b6ca429e5231422d69e1db3ab93d022043255a9791928cd34118c57b07f1f587811179f29f8065ef35efbc8a698bd61d", - "id": "197db69caeb74f3de59dc23a8c5d65878964963e785874dd6b8474890106b791", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 20225218326294, - "fee": 0, - "recipientId": "AWDLp4YdsBUdxtqBjC8J4jNr7dddSCmbDJ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203f5912d44a52659b0a679ab303f5044ff5413876a759cd20974afe45f0041b3c02204e313f1fd22299f8f811fd603f7482ca519a8ca9595694bd28d0416a10952fc1", - "id": "0053820aeed9059247f556586e9e0a4109f4579fefd5245cdaf52f9fd901787a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 20741626925528, - "fee": 0, - "recipientId": "AN9sqKHVipVtacB39BFQkPa6dDhdSbUJ4F", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207f0cc0e15878619c6a0eb4a321492c62cbdb57ea87c5d38bf8c45fe8f188178a02206312324c971349b94bbbfca52ad71928386d6810194c2e1d3d0d7ea9d1f0b216", - "id": "1eff5e79d10063be3e29e764d3a6e4f64cd681a0dabecd2568492e25355bd2d1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 21951221806397, - "fee": 0, - "recipientId": "AS7WWoFGGutbnpBg3GmzqSLapSRxqNqoTZ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c98da3c32cd3fc557d169c23b454a294d7c226f5481593b22e4b4b0168b3110c022063e54d574b1580bbc056eac3598f96b3c6a6bfc4ff348ae3861835e6457eaf2a", - "id": "c14b7e1b79336f7c4b26ccd962d0d594c39b067cfc7475a2a577725937e1017d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 22059609327388, - "fee": 0, - "recipientId": "AHDuDQzUhW9r5AEcxWYLg6rtnAej8LpjVg", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200645d5b06ea85a4712b6dd58dfd40731036928dfd3c72142558b8d45d3889d2102200b2ca62f33a473edf0330d1773c505561e1334452d0bc1aad4eedd4a9771e5d7", - "id": "e5bbad079581ef00c4ade09d30c649cdd6b36b69437c0690561e7b950830a1c8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 22212505397146, - "fee": 0, - "recipientId": "AYo8E9DEoYD94vJuhFSmZ7Z7JdL5vUuFng", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210080fcc91a7c3aea6ab14ddb92132d820f48d44d08f956c6463a40139cc731051f02202f0dbbc598dfa8830d86d23ac58ba13a2e91c283590dcdbda9dcdc7a5ddd9e52", - "id": "f8e39f689d66d7277e0afd9c5e8d8e90582abf5eb2517ada993d9db8b6a4d5a3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 22232699614369, - "fee": 0, - "recipientId": "AL8BTYeajbkWG8r7A2uYpAsKoX3LMk9uga", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206259fb0a490e9fc37569a4f1fbeec01d783a53a18630b70d2ebba2bbe0f6d270022020025b4c0c7215410bb5ab68d5c551a377e7bf7ae7e364454b7baca54f1b045e", - "id": "500ce8f0b6eb93696a6445f97c26e559f93f043f80ec4eba7d8e59ab30cd6220", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 22622858823224, - "fee": 0, - "recipientId": "AWEmVoaBW9Xs9mt14YeXxmqfmSJ7iLszQu", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207d52cc181b70569c0f2058e68603232d5532b5ca9863ad0464ab0af2c29b369802201e7ca2e873ff0f73427d1afb837220a89f17204c04c41c15dc5a32b57e1262c8", - "id": "74f0e117703a37fb072ed990224d3173a02ed8577441faa8704e767f9dbaa517", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 23205200171281, - "fee": 0, - "recipientId": "AdCphTbTtmBouY5wTen7MSzjy9fc84J9M3", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200ac08f44e5c63a577d4dc5536ffb1bfe437813b5e79073d3118f66011c78bb41022034517534731c508f7c6d87d44e5848cd38d8475802a453295ec332ba79fe1366", - "id": "7622484aebbb489351c45157af39ec0e1c2f1c75ba8b6d3cdc5bbeeb87cd32ac", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 23285447659034, - "fee": 0, - "recipientId": "AMnNU9x52nTd9kEu8rDQu4wrDm4DPB3SCi", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202b899c4adaf2d1ba16c09c995dfeb02489ea611d07ed055662388ac291f12d3b02203965814654985d31c688c2678085328126fcc1959913fb6cbc1cf0b671a85e54", - "id": "d2174364d96891d86b3de6457e579b53b5750218ef9bbda87b5cd9fbb0aa24c2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 23749226298416, - "fee": 0, - "recipientId": "AQFdF9mpwxWsjMASsSQFD3kMxghDVfkWh1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204a71c021260c33ee6ae8bb5966e6ab27f09b2c3f11d8123b85c532339b2f6711022042f89f016efa114dee8c5944f160129dc5b1329ecbc61222f3ea5d7a2cd0c547", - "id": "d231ed2b05da5b4771bca09d3262cd4b66d7efd38ee36378b93ee9e66f0bf6aa", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 23788728527651, - "fee": 0, - "recipientId": "AT38VrTjd8gcmW1XWao5hHz7zA5Cxvzber", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402204f4d855fada8a76db9e26e0e79c9e67f51907cd2ea7ada9bb6d640c77d49c15a02206fd26c6dac762a78531b42e74ee643f541fab7b4467caa936a865b63302dab59", - "id": "4cfdd48b353e9f7324dc3991738c9dc8f4c93c51cc18c535390c5328a4ec5ed1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 25133628072635, - "fee": 0, - "recipientId": "ATYKgxB6sMBiW8bkuiq18eZBNM7ceyJ1zK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a7c5a575fc88063777204691c7d8c83bbe794820058c87a47099b4fc523b7ae802201925f71003886a98bd9a14d2e8a4596acf325186449825bfad2d2b0947e75967", - "id": "5d86d1704c731ec1454b149b7105c223ada5a8244045c91a7386444cbcf9fbc2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 25643974662172, - "fee": 0, - "recipientId": "AVQ1UscRdsXQZncshwXSZEEWqvPNMJXtpY", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210092d6502d9d4945e59d22fb7a1141717e41918428362fac7b365c5e6d2ee9bfdc02201c86ca71aac24424cbf2ac44ca3b77088cc7c873f826b8b6542abddb68ba50dd", - "id": "b387e98cd84f82172c378834cc12bf9b21646891deeb37ed9c2647bf400009c8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 25690453309429, - "fee": 0, - "recipientId": "AMpKaxERjupes1GjHCVV4Rctj49BJXQ5Qk", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207bb590646296b2ac4b2ae347eb2da399b9df6ba21273b463e7117dd14ab61e4702204861d99922766f5a05b12730ffd84bb9784a37c8e27b58aed3f0b896230ec082", - "id": "81b65afd6830ef600587c8f22cf425cb485df032f8fa236121d986a0937ba41c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 25763719365860, - "fee": 0, - "recipientId": "ANymwDRzUoszXjbJXQMhPBmLrBv37AoysR", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008815fa72ddf054b47d90c7a4b9f12c783eca5ce10a557caa79109ca2e51176a20220595c9d31fb930159603274e72362a8807999dc80b3bb49ec43958837ac60d270", - "id": "434775c5695597b6554e89281a194cd903f8808b655c65f1b8cbcfae3e39c89d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 25896835969408, - "fee": 0, - "recipientId": "AdLxpRiPQdsdZqxQWEc2kYxD5YFzKsLB5T", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022079e5465fdccb479bd275f6157553ae46ce6b5c51dd51ff0fd4ec517b6deb7c3d02205213870fefea75258a08f564a7920f2740f4026a8655fbc8d1a6efc6dae611b6", - "id": "3c33d4479fbe31777d0263d7a4b6c035be675a2a8b9eb9964353af2c43737a61", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 25963064569714, - "fee": 0, - "recipientId": "AYqoBy73QzTfN9LHCgvT7eH4tfet2RU4Av", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201e891a570ff244ae679fb0284f55f65595d7d86ce5ea42323708bcf5daab0b67022078488e4c73790511ed6c1f9bebe8bcb0dd8914fd9ce070d23ca5553923f9d775", - "id": "5016cce5130bdff278ac554c89615fce32aad318bb8917784b51fe40bbab4e63", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 26922263723607, - "fee": 0, - "recipientId": "APjnWFvHhfjudvjXHQ7t3d3JuW1efhiWkM", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203bdb6af67dbcc1a8a1fc4a71d9e7fd6ca66ac92d0321eafeb3e7e44aae2d25dc022045598a45acc95423dad43741624c047bd2c08aa878cf3175a789f3ef75e6ec26", - "id": "981e2c0669340b781e6989d2e4ed088e71df124c2c3f90eb97f024fdbea70ef1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 27197785575254, - "fee": 0, - "recipientId": "AZrX4pmxJEzr8WkANjFsofL4ww5UeqbVE5", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201b3c396a9b310ecc51420e3ba5d9a711269202abe71353aa9a99c9388b3a282c0220221c52a87c44586507dc1ba810a7aec7eabf07d84ed7185616a3ec891f224fab", - "id": "421acc286ec7cc4bea14a8d3cbe33e77cfdda80ac748e27e4f0dbfc09584bbc0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 27237835405410, - "fee": 0, - "recipientId": "ANgMKTXMWHB3WnU7sBeLqYmUCN8khwc4o4", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fc96ad466ecf9cde5f005a6124f12ea237e66013b782406db1d8974926be0946022063f106ce7ebeba7a8c56393748460eee5ac888385a2467cd495d0ac7e6dae9d7", - "id": "594abec1830a739b1cbf625eef2b6301b5595ca5f229f353545c920c9b74d2e3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 28691934514542, - "fee": 0, - "recipientId": "AM78rDegT1jy4U7CEsWErtPSuJCdF6fJVD", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3043021f33602385c5f0c9d5a374ab0a835c0ebecf104951dd15dc812cc94738ed6ff702202c366784aa6cec2524f816bb41b0eb397cb0702ac7e069a7a8938694093b868b", - "id": "c05ad581c646d9d8975a65e54a7deaf2c255fa6622af349bfa16a9dd753e418c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 29252093379711, - "fee": 0, - "recipientId": "AUL2WbCnaYBkSRfz3Ak11KA3Vn6b6eyM7B", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402203e1c6a962f0d9dac66fe40b694ddf60d1ae12a0e8d7d4d12d71615dc08c885380220597d2eb1eb70e8afac03f9f02a0fed3532d15958d431d41e852adf147015f43b", - "id": "909a1c0f7371c0e7bd5b668e80ee67c980c7c06ac95c9a8561e2d245439eb29f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 29403011432706, - "fee": 0, - "recipientId": "AW3MgXojRTWgrx4PzBmk5pjn5nyhY1aKH5", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402206afb959420376fd8e172ff9535ad4c73423eb6528b90d888ef4698437e5560e002206d00854f5d1964357562ed79f680e7d5cfe887ee413073645842cbb248f5c34e", - "id": "69fd70220c9ad3cef6201a5043bc55f944cabbdf705808c6331a947ffb7933e8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 29495120724982, - "fee": 0, - "recipientId": "ASaaHvXuvS52QJuvp3nkm3EC4pEAze47a3", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c3838c356872fd692642ca595c4c53678396cec839de9841a74a4799cd37c41c0220168441e0f980c92516b41c8ea7e337e384ef95ad130f2a62d297bafc5ae6f710", - "id": "87e6c248637dc0774a78b108505ad19ece71c9248cf5151ef517561135c5467a", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 29617494546387, - "fee": 0, - "recipientId": "Aabe2A3WdrsrhBp8uTjqxUEQVyB7G1tica", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022011583f87dc647bc5887425c585d04c395f17c6bda80dd0fb2b310acde7294e1102205e64a69a81cd848f5743ec144bb932ebf18136f952b240f5f579e9524ea1c61d", - "id": "ca4e72b41eb5b6bf5210d47eedcef40cc9ce15168fbeea443dc8c179ffa6e711", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 30052189818242, - "fee": 0, - "recipientId": "ANaT8P38GwSHV9mcCk7cEZZAieB3Mp8jxE", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100dfe6ce17738cd9e3565cc2ce92f2b1171104573deec8f7c35ebcf39b906afa5b02206310b1b28588bdee1c4c9cccad579606a0150a73fc2500a78b7f126c6a30a736", - "id": "1aee700c3737c07c9a6a8dbb6ad404ab7dc313af7e1e0c63e07cbbc1b3642845", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 31009000000000, - "fee": 0, - "recipientId": "ATgAA1gP4awvYXk4T3FrHRgpNsVtaBU5UY", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f44445e667716c93963815e8872bb798f050c69dd18cf11260cca530866aaf6e0220741085d44bcc255dbfc4a8f34ba652ef20798f50ea350929196dcdcdf79685ca", - "id": "2f5f4923cb78e7d9aaa2b5028c16f3ced1448fdb6749b82711b46d469865bb1d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 31239229496679, - "fee": 0, - "recipientId": "AUcXquR6CPooj4FPVQomhCjfhJik6KTzYp", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e20a35c940073e07cdc048f9cab3ce5417ee3886790f8d8211b72d0d2c68ae97022018201c4a586e505c12d7eda3a6a8976fa4009bc8f10e11debcbd6df8c92bc59a", - "id": "11d6023327daa0bdecfc7f0db4b7f476270cce07d1442af2d81282004841f302", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 31413424826151, - "fee": 0, - "recipientId": "AdYVznebAKBjBGhNuKeKA4zzXLFJBxX6Z4", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100ca7de35722d5a049d61c01a8bcbd7df1a990534e589e5890d07cf7ae6181c0da02206ce232bf4f5d2e144e0aae9001e0f64f38b462720be5417ccd4e3192efcf54dd", - "id": "db369c7c18acfacd9d702e63c7a77e8bcb07d4e104ad8fa39ffa37489e2f1b29", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 31703556507222, - "fee": 0, - "recipientId": "AFumy9qrTPRdfjMq3Q71CyJGADVURsQUyS", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220082801ddb5cee6641ae199fc46cdab4d8bf945a602b60099ab0a6014a07f1b4302205aae82168430ad6632563426a4b6efa855d7ba81773a50894427dc9f54832580", - "id": "1ec3d57963cfe1528f5032fc045c7516f2e811ae5b5362a5e5e98b887abbdd22", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 32988434694317, - "fee": 0, - "recipientId": "AQpbXQB2G9fyoTsc84PZKtWeTANaXg6USH", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220118c9096756390292e7fadd471894e27610985552c8a69abc091abbce4426527022073bfcdb62d459942d9fedfc00dd3b7f473a2b7a8f3b607caddebe79788732adf", - "id": "f5a02075ee137276ace1af818d4e40826cc57ca42a28e2f4966008114f1ee3e2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 33301358574530, - "fee": 0, - "recipientId": "AGv459sY198H3RyLRwSUNUNVY2sdHBakyF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100baa3c39d1f99a14adadaabda9abcc74a8eb0fabf4478ec84e44c4c981194ea9f022026b3d6ed87d29120f5338fa371813700f08d989f8473f4e565e10ae7d7f3a5a0", - "id": "f85ed28225d31a5e12d1390e288d870d3fda8c31004435e7b581fbef143777a6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 34838200000000, - "fee": 0, - "recipientId": "APz3bVED2boFzmM6X8Ri6EptTuwYecymNG", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bc5e1636cc1a3a20189320632b53d9ca4dd4d6ed236cd79bc39897cb32ce1b4e022012c647f2c41f7f7be18989d37b837a1c597b9e8d9db34830e8c96aedf793c36b", - "id": "f762656f41ede6c6b2248a567e9338fa5fbcffd6e3acfcd166b3d7091dabe45b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 34933049632938, - "fee": 0, - "recipientId": "AGnmhUFTHK9waUB2RvPKcGLBfxRow1Hz9e", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201feb35774ceabcbfc577b257045b93395c3eb0e81d91cf4e0799d0c74fa9db4502203fa395d409e2aad11c3cb7a0db6d2904cda3ec8c850b810b737202a9717d90b1", - "id": "9f9b749ef794c617b2136b392f09d358f27c1c7ae33d917812f777405d9ca61b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 36753764290883, - "fee": 0, - "recipientId": "AXicS7Jbvo9wCN2uVa7RfBLrBS4oPHXvmF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a9d7ffc30ff5da829ab6a0705fc673c208fb1d90b4f37865fb859fe32771425a02204dd95d698733a1cd9ea1ae28044186dbca88e643f740593c4528e2a225be7656", - "id": "1f308b5f48e5396ca4b83e14ad69a329a486d695ecbd6819be782db4e79ef161", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 36753764290883, - "fee": 0, - "recipientId": "AMKQ7YtVjAHt8mWniZsvj6rqmA5jzu1rnZ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a75993db6caf823dc6d38968f205c3fec9406d2e215791a62cbe9951bad8677202202d0ccd24e0c9757d557720cb40182f5bbbd4f17b756aba16289de6190a2e7205", - "id": "dff7c0fe0b3548e5b68bc7705dcf7a33660bd77cc5d66bc1a5445f1ee5eee757", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 36827565849579, - "fee": 0, - "recipientId": "AdUHt2sqD22q3Jhby5K4nSVhBjPaBDrcN7", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008dfbca2daed7bead47d885fd3f763a75c4b076bfcac2ad209987a2c0a2ab913c022019fdb82528d253b78768f8b8e508da440fe8f2f7a34ad9aba2c37f91df76bd01", - "id": "22b6643f0273b7fba1e4c4095c245ffd0a59f8c75582bbaba951f0f626fb6b67", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 40000000000000, - "fee": 0, - "recipientId": "ALcLekYJLkWKLwEX4TNsQszu8nfQbWSkDt", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202a594df3905ee3f8c8db32a69857b686badcc6784b698f8ad1ffcbdc5cb5d2d0022072a8e72f42fde11f54bdaccd48de55ed2d3046b1f426ddbb560b97658a0bc83b", - "id": "08a515e48fde2ed1141f3b7c6726284d1ae7b5ed1dbef1742ec43bf52d229eee", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 41574092648018, - "fee": 0, - "recipientId": "AGctmMKFHy54o2pzgeh4ESHfqZ9B7zMnzA", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009ed97e274d06d1458aa4d0860329931ba6e3a2886f45ca9f9cdbe46f089d6a85022068a1f3f8edecbd96d580148602ea10709e92655fde424d68fb8d049d8841c418", - "id": "afc119b8eb143c9fffd3bfe31a39d7e42497770bfbad4ae7190b5b7b2001c9aa", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 42101522494901, - "fee": 0, - "recipientId": "ATAu9Q9UEQQua2UvXfQ6aev6eZAqWmSNFp", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022060955d98b915a99a40fab60a89c9813d050550f17ea432f309c62d1e4da4f80602205685972723f6a5223466af3fd20c15afb95938182e7b2a636a32f6ab88cb4628", - "id": "bbb16d3b87ba67e0ad32da5eb342cc9bf60b51825b11d2d435f94afcc4ec42e3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 43098358629104, - "fee": 0, - "recipientId": "AWbC9Wq2LyJki7J84AkTLJkzguF9mo89BF", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100fa22327b9cd6deb3153c39d8f5d76739754798733aa96fedf2283dfb9b1ba055022014e16da33b966e40e27c883b314341391b7f85ee3c1f272dae914533ba540c3a", - "id": "8a87ccc624c279197648fca4c03622ca87b4dbfac293377c260e536e6ca969f5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 44103929088831, - "fee": 0, - "recipientId": "AUJWBJDUgY52u89unPzk5oV9e9xge7P1Kp", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207069312fd56f16680a836c2a45772777be38c18c6f86287f414e786b8f2095d202203f646bbd58f31c2618c31649f1c48bc1f4ee3ec791a1805d788497179564b72e", - "id": "ba49cc0e31cee0207d2f0211262dfcf9787a314f0ce63d3c292f8f20c596a821", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 44104664164117, - "fee": 0, - "recipientId": "AUTMnQD8ANkE1wtMnH3aoPCrYkVntiaRDc", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402200bdcfb494ba5f808db740b6535ae3adf6719d3442d618e027314833aaa3fd7ef02200cd5039225a23be87af57866b6de9c0e005a643fdbb9d94e2bf0f1bc9eb4227e", - "id": "954643f48700f6776cecc0c8d28edd052b6ada807c4ed80d4020db027f8302a1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 46581295003558, - "fee": 0, - "recipientId": "AM6bDiAfJMXdivdUiGaDy984KA97ZEcKWq", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f8bdac69ac19d78a7db5398b5bc643399689262446788af4805d85766e705480022066cc9169d1a7f9f53351ee695e2016ca5384d8f7069feebd59e8fb4a9788559d", - "id": "545bd5d745ede704e8c2a8d69a2eb6925610f37e7fa79ed9483266d860c7f512", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 47047758593474, - "fee": 0, - "recipientId": "AWvqcwdrJyDEfsbveh5F7Nh6F1KWD3KAHS", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d41cbcf3488a0246488bf2cf73bf1e4e797260d1a5250ed8f04957b31586b0fb022036ded1aad7477562792bb697ed03f86d158ecc8dc325f37ba3efbfc2fa7c1fa2", - "id": "45838af24957952037e376f81200350a6d795b8435613a4845adbec9dece4d66", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 47594373941014, - "fee": 0, - "recipientId": "AJUkMpMFt8Zny1v1rENN9ZPZYitfePBvyz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100c5d4aa1d91f3a0b7468a31097fc9613e9c3830e1b3c6dc3926217442fb605f6f02207efbbe01ac68387e6fe0719ee635b4574d7040595d086d7030f903070f7c3247", - "id": "bb58a2aa2a8048b937156f3618663c1015f1b9ae7527ba8a0bc351c6f0bda349", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 51199117149060, - "fee": 0, - "recipientId": "AZaAZmaQgY3JaUTK72WUJEp9ZEgQgW1r6z", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220060cdc83a40e3ec69b3359c304a0ac46d863617599e2de03fc9a022efdec329f022018cbb65aea9d099cf6cc372a6599525b1aaf8a9cba53edbc95235764d265b3c5", - "id": "97d3f0573caf2552f8f5a696bbdb7f95782b98e5057f26ff7eec8e152513a3d4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 52274142846490, - "fee": 0, - "recipientId": "AMRYJaup1hTjCsV79HCTn4ySvhAzb8xSo7", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022070140fe92e3dcf59f0ae1a87bbb061cc4048a8489d47cc37a3f3ef489548dc2302206163dd3630b960269613c98ca886637f32efc495f6c4e593cdb8c91dbe696c44", - "id": "223bf57b085d5fb463d106ae666648acfa042952e7417e4d22f51a100e3bbdd1", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 56564297142251, - "fee": 0, - "recipientId": "ALy8rPTjEELHQbkkDfsQTWT1M4s1Q4NFDX", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b5f571f366492003d3c17ddbcdafddee27389907a33cf348f7ef0b4d6f4cdffb02207300a2d680dd5ff2fe2fc70d71e76c875ea15572be63fbd583a4de383f99c6da", - "id": "5333df7856532e4d8a13adf69376b48c986a46f773a3e8677b5722baa45bbda3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 56945445022471, - "fee": 0, - "recipientId": "AVRGa6s7qAzChqfXrbJEhExzrTo1uscsf1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d41baf4236b93dac1d8604596cbdabedd8081b8666d239f2a193b69e56acfe490220046d22d267e480523194aeb5890d54b5e161718ad754321aebe61a5a9a6a4947", - "id": "f466357fc462681688413511091b23148fa0271b2045dbd8b28aa591db29f850", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 58952449862348, - "fee": 0, - "recipientId": "AWeSiyLTbvHyfqmUNNcRZABWZkcd36oz7x", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100d8f5f18629c666ef1aad569544d581c9c6fe2beaa751658389ef260559f946ff02202da47ce0d71725b3004c43de69c0fe0a5fc94dd7296d16b5f64c2fb9ce1a515a", - "id": "edacc4e6c26a064c04742503524ac91012a87e77f6bf7186b4f591f3221b7b58", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 60406501374583, - "fee": 0, - "recipientId": "ARviCJv7BdnDHCxPgFa2uqRLXNNtBqc8Jv", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b49e507c020746c9f70dcd26f52d071e05bfe9dbd303d528812a700883f7a0c4022009f6cbfd51b20e464451a6ebb5184c0abcfa758ca304bc88d031c8075c7d6902", - "id": "daa89776995d4fa94c6c64cd06b1c2d1036cfc9082151f9d3a70adf948c93939", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 61218300000000, - "fee": 0, - "recipientId": "ANLeC5wGgtqiDQcX5pmHiYayqMyWy3AYCQ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022049a07caf745eafae488a242e5066c8d2220b99ec3127818ff9ccb2faf682fee6022072aab5d565ac05759ebdf6f896a53c584961a6c0a362a321cd0efb2e78cfa9e8", - "id": "789e6d917e48fa9a4538ce5948a1c4013c189d080b94275eb975299d3d22177c", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 61966258534201, - "fee": 0, - "recipientId": "ASaaFCmPG3yPUwngEFoPA12KnQYqjgVKnv", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220449a94f1a85146353328281b937a9ce91b5644235ece1e2046a28d1199fc7f7d02200dd68146162a869688f44fddddb990bd2edb1c4194aa8c1edccd72cb1dfb7e03", - "id": "e09409c50d3ab8e86cec1216a5a8f971a8830c4bf6f847fa59c7eeff606ee9f5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 63613272588492, - "fee": 0, - "recipientId": "AYpQxRNbaiEL6FT7YUNmjNjRvpUfvRh6xA", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100953475d0a8a1f5b26eeea7d4697faf1eba8f4a4e465b387f6c3550407c2adbef022047a7f4af1bbad90c076617319eed901196e46a692131fb1c8861c3ba2df2a23c", - "id": "311931514d05d2af41ba127a24dbc950f7476f78d9184469947d4b3b59561c4f", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 66966172832127, - "fee": 0, - "recipientId": "ATVwQWf1mpyRa7L4BAkSUxz9osCfBsBopT", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f571ced76143801d0760e47fdb4583bdd45d1dc46d03fe9195be5cbe0a9f44a402200f6a4cd36111e9b5dd06f966e3ebad05294ab03b5be9f6a231665ed4ae53a275", - "id": "28a7145ea1920ab8de9b6037f050627ffb2e37c9f9f1d16be8db7110f401fbd3", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 67622306078799, - "fee": 0, - "recipientId": "AKzAriWhTPPRXueXuSttRiBLkjP7i5wKpQ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100969e9376ec702a41b6da148d02603e8e1b4c1bfbf52e060aff80a89cab4eb3c9022076972f9de9cc4e883c63ab263cc76df952b0bc55dec80b958fa8069b94046f12", - "id": "7a6649786a864355ed73e0f194caab15593a11f93400b13ce6d0b7030057d903", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 80563884771309, - "fee": 0, - "recipientId": "AMFQ8md4BymLJ2ecw436fhyUyjyRM7ygzz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220188dd6b6c7e1c09b420ea73ecb17db20d85848342cf45c65192c77f5390fded502205eb3a4304dd2ee62ec1fa9e848dcf2d695ed92162e13b52271e3430ebde7eeb3", - "id": "fdeba3a56077063853cfb940d036d3ba75e03b6fa68d45de52bf58d9165c4b96", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 81926947255471, - "fee": 0, - "recipientId": "AMMDbSvuBNCi1oAXvqgBguAeTehWRTjBrc", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022043e6c8beefe6b5ab949adabed4830fd87d4639a6afd7fe8e1c3de3087f7f47b302202cce568aacb5bc2a416a20b6f940020b2e9c1f0404264230bf1b9c0086735ea7", - "id": "1630a5b12b69dc1317bce4ba97a2207c6bb4ff8c9e3b5af88d02f4b42829d51d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 97831169789473, - "fee": 0, - "recipientId": "Ab6JpVxrytQhj4QpBJ3LGsLvEhVmgCKk6Q", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202b44c2bf60f8af0927f9a5c62fba3ceabb58e5944dedfbd37cef6368b219225502201c1074fb7303b8a88479009e3a36dda95d6c8d0bc60770bd7f12beafcf680c0d", - "id": "0c8a56b4a0aa03b2f49dce2fd0e7f367096065a51128950188b553b8a750d4c6", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 98443286091931, - "fee": 0, - "recipientId": "AMQEjpMh7o1sJT65Pdm1Wt829evGsTWCJ2", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304502210082a8b45a0e351baaefcdddee3583b1b5ed7af81fdc9e2aa7243696388a5d6b4302205bd1469c96a3494a9b0bc8cc4513cb3ca57b8cf3b3250c028346d42666daa02e", - "id": "564f7c69a9cacf987aaee9ecb5bb726d1ae26a26111f0f8364611984d18b300d", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 100568151043750, - "fee": 0, - "recipientId": "AZdsyiib53nWP1wG5wara6XfvmmqqBWxWR", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100da0594484575e66f1eb34211a7d3249c15d0eb422be3e1b46ee364c4a377befc022041cd9cd4237cc8a1a7e9359adfe182da7edb24a69368ae1deb7e2fad26aa0312", - "id": "16a95bcd5ca6388de776cc01666f616f863f9dced646badfe97b53a266985bbd", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 110555322986976, - "fee": 0, - "recipientId": "APaRmUnSummHSAyHnYqd1BGvPqgYReEuWG", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100874c685c11233280806221994ce6ac3be8ca6f2dd88421991a1c9810614e9bb5022071ededd800067a5a3e6828ab6cec496b153c9331e5f08a417c09e583b05219d7", - "id": "a387aebbc8fefd8f0963f2ffbac837f5bed99137b19b19c356949bdf12c0d0cb", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 113717099151927, - "fee": 0, - "recipientId": "ALC5AHjaUUSaecu9R4wADbKdqzqf9U4e19", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100b5b9bf26d7020ca50abd010624a7443e7e4d0c5d17c50aaff12c2a357380a212022037b7e9e80dd9e5a092d99aaae3fe0722ff6e085b8ed42e4d47030e9497e6093a", - "id": "912d5b497042a6da16f4dc3fb0b7711f19aeadd1ebcb5aea32e2e49d6b2bcde0", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 115371683274712, - "fee": 0, - "recipientId": "AVNXBEcnwKmirrG3kcZSzHa6JvRkiCKqXc", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220401302e2773dfc0ef1a6b4b013d98fcbaca1838e9c688013989ad990a7eb51dc0220268eb491d53660b33ace47049d96e4c97dde689621044002f703425e17a3cc3e", - "id": "1dab66009d3db2e422824d6af3e727ff146c2b3bfb1daeef583742a920d51908", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 120262700000000, - "fee": 0, - "recipientId": "AHHpmXD7F8j1r5bftPMkp35ct9qGYiupwj", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100edfc8dbb54abcc1096194776c222e08fae1e125851b580f510ba5b6c6ea39f38022032b48c581a123b298838dba4a22b71ddfc51c0514f260fb07dcb2374bbfc8e92", - "id": "5ffd7c36deaef4295257fec15f27e451864538aeeae9b21ded6d4e579f19d7c8", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 123626090911753, - "fee": 0, - "recipientId": "AczFP54nMoafRABK4c7idxHpAC4RwmQsDE", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402207a3f7c3ecb0295f08a85e12af23156e0765ad1540047f68cd89be139307b5bb3022072be91d55a080d6e5fae0a327225dac4c63505b292dda6958a1207f9adadc8c4", - "id": "03b08987233a71d17b296c057e03821022e9b504b47471670ddad11080e1f4b2", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 125719320015809, - "fee": 0, - "recipientId": "AUq3S7bXMHmz9zBbz8YLN6LrSrshvxXTU5", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022033d2d418dae0c1c1ef89b1866258410008eb39a409c98f6ec3a73918f449ead102200ca603311c12cb4a5f050e0872e05a0e8f2953827b168b85211b179600154585", - "id": "82b2c63e0139fa52979e008e6a3d3086efc4aaf5e1799c57a29bdaaae9a50189", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 126273901505716, - "fee": 0, - "recipientId": "AdCXBbcpNrnMDHMar7Ebjxu959uwN2jt2y", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022078cb591e525902157b5736e798461266a2c7e32d7aa4d06d07f80d64244e0a2602202981d622da3539fd16efc0aa0ed8503ce2470ce8cc3235cc325904b9246dfb1d", - "id": "367b31d00256d8608bbe3b62f350bbbac10d896a3a3cc084e203493abf6f74b4", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 126299500000000, - "fee": 0, - "recipientId": "AeYkC4MJ24EF7yVkb8NfSryBw9xrTvSJ5o", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100f3fc2ad6305967410752b664c2d8bca041d552c013fa576e10889a69a6c4fce5022067ecc8de665c05a1594067f873e067db5e1884486d4815bd1cb3959c282cf0a0", - "id": "af6c2725034e35ca7dcf0b20a9161af2ece9d9e30d1d8f934661c2e121844832", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 126837200000000, - "fee": 0, - "recipientId": "AXx966jfTHUz4nSjZsFEKkpsgNoByhuMJo", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30440220025e2076ba6db9412d886925b4ccf810ff5aa45711ba9c9f049061f675560937022042ed7606e3f46bc5fb5b2ef2b36e894666c78804a1d95ba7916d6a41f92e36cf", - "id": "e274c6833dc8021404af6782020539329acda8f274ac2be31535eef95cd70668", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 127928194702051, - "fee": 0, - "recipientId": "ARwU8mRM9aM4KjwFLpNH8eHntj157LNAW8", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022050f8011fa9011b5b4501bcc1d4154864b6c5125e9a00f468a194e59edd32831c02200c9f77d38533ceaa8bbda2bbf9ddc73a7d6654d3d3a338a3e04a80f67fe16a59", - "id": "2debd1afcd9cbc2e3c57dc27258cb49d6ef21b10d36648efacc0053acf7cba19", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 128450700000000, - "fee": 0, - "recipientId": "AGto2VECRE2tGFr8X9pfhqHNgF2bdW5mgR", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100aecdaf102f6d5a4b18ddeb17c8d5c00c18cfe0ec6382df8c972937a17e9271e0022074d6bf200487ee5c03800c2cc28e571bea6129c95ebffc2a1a8435fe6bb351d0", - "id": "7477beb697266fa054ab120b1381e293d8533d1a56eec29731525b857d05e8dd", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 136497083929386, - "fee": 0, - "recipientId": "Ac15ysDrQ3fvMYL8S2u52VSwXZPKZQs8MY", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402201e2a494e971932480c8286c2c12748daf90afd3234969cd720ac9346da94fff802203fdeef1aa6daffc70709e5c86bf53fec3169651671060b8e495e1c0c40c725d2", - "id": "811f46c52bc4e94cebb2c8a4053c13c3ac3f7df24134cc027a37f7614e935a7b", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 140099329287361, - "fee": 0, - "recipientId": "AZB965rP7y3PUs8RXqpkRy5Uu9dbQgu8mM", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221008fdc7606464939463c4c48ef06663b63e447db9b560fd323efe695b59832e792022048631bfebc37ff98d5d5089f607869eb05b86119830da14817cd31125a4910e8", - "id": "8bb29e211493fd3f3282498d749b55d5824e9ed9d5813a834258b002204a2cb7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 150034082630911, - "fee": 0, - "recipientId": "AQK3Zu6VW2QMh6a3ckNzNsYduTV1MmDkyJ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "304402202d42c892d4cd76cb582573977a7b2a294a945754dbd3de4411fe3a3ed814f0d402207c49588af322b49914a933cda46e2ade762f5bb9b9383cb690bded2f0a734d90", - "id": "7e1e33f30bf45a2b2b7be555693894171481a7cbdc48397575e6a99d98382138", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 151578069939920, - "fee": 0, - "recipientId": "ATUe1KJzRsHUW6rA8UnNNNo4yVEF6TqyEv", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100e97f8bccc9e3f5e3c3a5b32987c64caf4b60faf4a282784ab3b1ade1579c3b0c02202041fb2375eb061fc3ddb64cf94edc614a690d6fa9e22e9964aab80a66eb5b34", - "id": "be993fc90ee11f5b9bcbeec1a272e94c1894b65804011f76d0d3227c71ba19e5", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 191573850243351, - "fee": 0, - "recipientId": "Abee76TtVambNDBmTTyoCYosWQ13MC9XEn", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bca5294ba5fd28e35afb067b328aa2469656158f4c8580d98bfea76ef925761102202ca7d152501f2999169ee65cac56b25f41d9b140d11708d236f9c4be972b0dcd", - "id": "2bec691439fdb6fd9a324f26d44e2ad64bd9e2ffd566d220e01c4aa12028fa91", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 196019568105246, - "fee": 0, - "recipientId": "AX1ZCuzNnVHfPvKikwsnsNRUxXfKq5g9dv", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3044022011856a7a6cf61478d183e18a4a7404241dc561629d7db7ac70d1ff385e85d724022003404898de3709cac4bd5a6aa718c7ada3c9d08219c60f034a45da8135ef4a51", - "id": "57b80107cf40b39135241aa92e7e254da815763735f0ac5a9919dd8485865d80", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 225493764614631, - "fee": 0, - "recipientId": "AbYwZ1DAUDLgJwusR9bCZoTfuUTwNq1C3c", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100bad48978061d554290cd1afd07851e3101aee65f3b7f9f677e85a28d6590df5f02204d7a721a90dcec6644bff0aecd2f1d5b506c334bad8810b1b2d123b6978d57aa", - "id": "5ffc47d5a0826f031d51f0502e45ddb9d7b8c4f4bf9b2d4bd7a60c4ab027b541", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1034744897737528, - "fee": 0, - "recipientId": "AaFgAghGAmNWndDnaxkbcYFE7fxbxZn6dJ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100a4489ad919a05f73c3be4de717a3b23115812c146caa1f33301485e1d9521dbd0220487441651432d5b34bbe7f383ee3a1bdf84dd22b82982d9566020a6033783685", - "id": "99a3fa3f91f257fd3e3ed284a9bbcc5eb007ca7e004372a5912bc3850d5ec9f7", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 1256766795179826, - "fee": 0, - "recipientId": "AUhVLSiqTnnzqhxp5RKzheuRyvh58TSCUR", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "30450221009e21a29b8428afdd7f140379d839d5ee75191c3e5add0582f58b9a3b2b3d69e30220749a0e7a3a8c63e877f1bdb0ec12987db8b138bb1746efe0b18d9ee817276cfc", - "id": "ac7ce9c3dcbee526a9f15af016fc15b6dabed5f8a61997c4de15fcc987926234", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 0, - "amount": 3141438227793004, - "fee": 0, - "recipientId": "AN7BURQn5oqBRBADeWhmmUMJGQTy5Seey3", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "0235d486fea0193cbe77e955ab175b8f6eb9eaf784de689beffbd649989f5d6be3", - "signature": "3045022100df12738ec2164ce2d2ba980ab8499dae066149a1019f37ae5b17f28e9cc2aafb02202a0db97f9e012f2a8ed819d2cb02e5d9e63fda01754d6f42308c3cab4bf92e1a", - "id": "5222124556517be676fa67fccb07fd581ec2928140a72afdd65259dc3e5db921", - "senderId": "AewxfHQobSc49a4radHp74JZCGP8LRe4xA" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "033c9e5a710bff3131b406a8023a60e6b76a2ccf937cd85b56add7c4a33ae3090f", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_26", - "publicKey": "033c9e5a710bff3131b406a8023a60e6b76a2ccf937cd85b56add7c4a33ae3090f" - } - }, - "signature": "304402205532bc3fe7be805b4c8a0df2995158e634c7146c2467d05d75f754782b87bbbf02207164df69b13c9c3c46bb3c7d027e7ba81eeaf726f25e08d268c1507da4581b73", - "id": "8cc98e02422afa8a246faa66c1672b62996be356735b00614d8739ea3a5f73b6", - "senderId": "AQaKx7UU8857b4tJij1jb7aURUzd3GDyKt" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "031de61bd525abc902396bc66daf513eb20715180beb50be3a1c56a36dc73476a8", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_50", - "publicKey": "031de61bd525abc902396bc66daf513eb20715180beb50be3a1c56a36dc73476a8" - } - }, - "signature": "30440220125d7a0f64c0328590d67dd27eb5d8f209bdcce03d730c7469ab25a8943c8b920220027305ec4ac8d46b0c2e711274159ab2fffec7f5a5994aea775cd48260db93b4", - "id": "bf2641e6ffce5848282027221c7f43eedbd0b72a7ee2a56cee1472c5f69f194c", - "senderId": "AU9BgcsCBDCkzPyY9EZXqiwukYq4Kor4oX" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02614722c83a05de882d887ad51bc5c687e747d6d19b58f5731d38223c58bb6ca0", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_48", - "publicKey": "02614722c83a05de882d887ad51bc5c687e747d6d19b58f5731d38223c58bb6ca0" - } - }, - "signature": "3044022078d5f10573deff4cf16ee4b253c6b985b8a16e5f7f4840493e2834809e26ce6f022020d6502389f7512ea3b8f9fa9ac296c153aabce8f9066cd42d854b94069f6f2d", - "id": "3625b2591f15394ea0dc6b26a113284b1b56de0cf3a21c3a1dd92f373f796982", - "senderId": "AJqbav8MPPAe3zdzo1f471gaciQbx1SRe3" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "029191a8ae0fe0561e3ddce562554e8ba5ba36cf9ecb389290a1a74acdc53ca89e", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_47", - "publicKey": "029191a8ae0fe0561e3ddce562554e8ba5ba36cf9ecb389290a1a74acdc53ca89e" - } - }, - "signature": "3045022100abd4ff196ac1fb56fdcc0f0b71a49ceca5da07b3631570e22626fb43a415a9e2022042d32e0fc05c6e2467b9749ad1f6bf5e1fb9cc8eb5c21a40baa1381cf58fa327", - "id": "05c1fd17a20d62c2740782097261b960f9ad353f68b94d4a13dcab05d08f9dd1", - "senderId": "AXArbuCVSiRuYBkzCAajboxCfNiw8AyQX1" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "0204f8aca74a87f69432298b13555cf50aa0709e1a942aa3efb447620eb78bbcbe", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_46", - "publicKey": "0204f8aca74a87f69432298b13555cf50aa0709e1a942aa3efb447620eb78bbcbe" - } - }, - "signature": "3045022100d15eb20b58fb9a6e7659706d20c3deb26dc176cdaab02fff557dc5a8d65982a3022064686cae9101510295993e1d3663dffc88741bf1e2781021e28a7b2aa71ba80b", - "id": "6debf628e330726bee0f64d9fe7c56fe4024941b4394ad5c7cb08fa9a3abc0f5", - "senderId": "AbrgipEvLp1ZHNJ1GcWWAsCLNgSgDG6Lxh" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02c12428bb56ceb96e9c2a558e045df6b7b2c551fdb6a132ac6c3956d10f479f52", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_45", - "publicKey": "02c12428bb56ceb96e9c2a558e045df6b7b2c551fdb6a132ac6c3956d10f479f52" - } - }, - "signature": "30450221008c43c6f251517d972386adb42d69510ca59d882a89d7fffa55484ad7989a3bbd02200925d009f1183934e4be66ad48ce7149c990d0d87afe629c14551ed34e54ee1d", - "id": "557d5fb9b8b948ba2a6fd3d4dceb476e97e61faefde8eb11f4372028346c5aca", - "senderId": "AbyacFxcWS1JsokdRCx8bFsWP8f48XftmS" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03f11ddeca3845ea59c82120a7eca68558c99e4e7d373142ea0554408c58ca82be", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_44", - "publicKey": "03f11ddeca3845ea59c82120a7eca68558c99e4e7d373142ea0554408c58ca82be" - } - }, - "signature": "3045022100f04fdee2cc09b029d1bedea130174f21e24e673438655e71bd61998732debb040220439b36ecd46cf6ccc9c8a8fc44085733bb82427a760ab87bfee1166c7623813a", - "id": "6d8c04b24c24c9680b4e496b686c2a37cc05fcd9e6f2090cc945712199b2b8ae", - "senderId": "AYLTbdiYWHvPvJo5Lh42MEVS4Fnepg5vgc" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02c134247a5d54cd30a5e3080daa99c74f54e21ad0cffa60e7efdecece862898c9", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_43", - "publicKey": "02c134247a5d54cd30a5e3080daa99c74f54e21ad0cffa60e7efdecece862898c9" - } - }, - "signature": "304402201820565556e3039944c8c7240c567c5d8f96a8a34a05dc1d68577883a8025e94022011f55c1511627de25b4cd7410efea22ef2df50b3c1f4ed55a8ce8d4f3da91862", - "id": "e606ac6acbfd96fc4054131061a69e3d3f58e044636a6646227775c4e646a126", - "senderId": "AVi8PFHr5jFBFGWSissPFDFXUPABuBgxSa" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03afac7e796d781e17600945010be34cb6760fa919be67baec2cfb691cf4ce5f33", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_42", - "publicKey": "03afac7e796d781e17600945010be34cb6760fa919be67baec2cfb691cf4ce5f33" - } - }, - "signature": "3045022100bd7e5550aecbd212d9fc13842c7ef586b146ce6f289199107af979eac6b61fe402204607eb502c9c8f2363e468e22afccd754e042e605199c92a93a0c29a19ec628a", - "id": "2cd3411cf5a52c717afca1f42cf97627fe81c0869f39ab494df0ce6e99f7e485", - "senderId": "APXkAdLbLiLJDC9Ls68Y9a5ws3PcgmUDAo" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "032879c56cfb96539f6a8c03a91b4a987e35973a79e62f9da438831b353066a84b", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_41", - "publicKey": "032879c56cfb96539f6a8c03a91b4a987e35973a79e62f9da438831b353066a84b" - } - }, - "signature": "3044022060dc174d40828324a18074db079ea300d382ed5c2f27a7ff782686e7e78002fb022072e809275cc872b3c4b430e3094268e0260aaf67ced17387ee17a207a9e2032e", - "id": "4b16c34911fac46716cf8ed383401f70657201763b54ecaa5904bc09fddc774d", - "senderId": "Ad8UiXMZPecuNGwCXZTmF8Mysn1TchVVTf" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03bc7be5c8b1221beb63f6ed23d4b5e2748b64983087986c4953579fa7d9022a71", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_40", - "publicKey": "03bc7be5c8b1221beb63f6ed23d4b5e2748b64983087986c4953579fa7d9022a71" - } - }, - "signature": "3045022100c8901dbad46f6fd0ae9db39c7be5b002bc871906a80181ae45de6fa67eb4068a022068af82d46fd008c8efe5c3fe265313e01aca9262b1160a5c095c6b31d97f1579", - "id": "2d808f28b223999c1d652c01b2f22ceeca15d1abf2acb42c04570358ed176913", - "senderId": "ANUfH6C3Jjp46pDUxWgeV6WUbWCagaVxM1" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03c5282b639d0e8f94cfac6c0ed242d1634d8a2c93cbd76c6ed2856a9f19cf6a13", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_39", - "publicKey": "03c5282b639d0e8f94cfac6c0ed242d1634d8a2c93cbd76c6ed2856a9f19cf6a13" - } - }, - "signature": "3044022051a8aa7906358589161956d1c182ef9ee5c77a144c2d9965eeff043ddc3b7a6b0220797a2475b5dcd8f0ef7a24a0fbe3d0185839e33f313b93b566b82be168d4ca33", - "id": "3f21a6f499292b9bf0c2970cac7d78953a977f7004d29446698c7a29da656fda", - "senderId": "Actv8ebcwNsbfx8MM5k2AeJidJuLL7PotT" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "036c474d6b22e6d7c2cf054721ca73d0e2a904135b49bbf9dcc7ed7ce9718984ed", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_38", - "publicKey": "036c474d6b22e6d7c2cf054721ca73d0e2a904135b49bbf9dcc7ed7ce9718984ed" - } - }, - "signature": "3045022100d3ab1ee59bd1a45bf936867661f9fa9b22c161506bcd61ad17ac871ba4a17e130220114d37a98120cd1f2465c9700089101db7748ab0562561d4e01ec98c0e96750f", - "id": "337968b8f3e7ddb26ff3e6740df0b12c7be6bc427d9d570bd77a9312baafc80c", - "senderId": "AR5z28tRUnvaBWC14KSBpvNJDgsVCNtagq" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "028bc0f094738f7699ab4432d13e3c18e09a462dca06960a1fd0eee82482f50be6", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_37", - "publicKey": "028bc0f094738f7699ab4432d13e3c18e09a462dca06960a1fd0eee82482f50be6" - } - }, - "signature": "304402200cd7121995da2f8fb24ef2aab15b8da97b050ff9aa600363305f2c0a0c8ca72602200099ab565497c84319dd91a34001b16a92211248652ee109fd28eb90112bf022", - "id": "061e51e451667ae7ea59e0d1cc66fe47700d6bed1dfa671efc028a2b580b71a3", - "senderId": "AbkDPAUDsfQJgvHn5g8AisDm9XqLAtFFai" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "038467e1dd4d138f007f70cb09afdd8439ea744b282cbba2d76fee8463dfd17e2b", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_36", - "publicKey": "038467e1dd4d138f007f70cb09afdd8439ea744b282cbba2d76fee8463dfd17e2b" - } - }, - "signature": "304402206772c6c4e2f58c1401570fb76ab87bf63865fcf32c5f9a5a3f88f4360f44e346022075710eb73a4303ecf5eb899019ee59c9c5ad206b6da902adf90f3df02f108512", - "id": "ee5347ef87ceaf28aefd8374f686725b8ffbbe55042dba0c59c625e4c822fbf5", - "senderId": "AbAeJ4YJU5rxuNtXpDn3E4W5E8UNHyc26a" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "023f425c88cfa289c28e5fefe120033c9beff72487dbc4722e900d6da59943b8d0", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_35", - "publicKey": "023f425c88cfa289c28e5fefe120033c9beff72487dbc4722e900d6da59943b8d0" - } - }, - "signature": "3045022100c185d867d976dff795ccba4654199f1b075bfd3441e045a2f6ac64c4fd0bb6d702202323d84585da2268276d6cce663e842ea5f8617e5e12cf4a20ed7436a1ff7f04", - "id": "592ee85d2922ef565fb02d649cdb2104caf06fb8747ba2425057f79518132b1c", - "senderId": "AP8agRcU4WmsYhC72pBqyfLwaDU6NYKUL6" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02d2a0fda621ac213ca51dfa8efe1772eab8659d8b8c7023a35dce48f2c88316ed", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_34", - "publicKey": "02d2a0fda621ac213ca51dfa8efe1772eab8659d8b8c7023a35dce48f2c88316ed" - } - }, - "signature": "3045022100da6c5c7945eb7dae41e7ed133a58e06e660fb8d012e9ba93df38b8b86a3aa7ca022007514624a32a5fd6f294bfd8d6fce6e8c75b20b7fcb8d3f8b82725c83fe43db6", - "id": "686baa6cda61403ce346c293a8f27e0dc3899eb4629d4b3bbbb9f02b41ea36bf", - "senderId": "AHmBqWJgxZfaC2azkyASdrbCxF6csE7Qxx" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "026ffd3c35c7ef08e3dcdde741994bfdf9cf6af75cdf9e6f4ef8c251b5eb321fd1", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_33", - "publicKey": "026ffd3c35c7ef08e3dcdde741994bfdf9cf6af75cdf9e6f4ef8c251b5eb321fd1" - } - }, - "signature": "30440220192c5a3433384ea1ec3f81473f18962a179fcdd067359accac33e8ee4e571a780220549fb42c1a5d40daca2fc97aa8e0379e041c28fa524505c0e43760f1022a1369", - "id": "bf1644830edb59dea6ea1f3fa107ce84546d14904715b84bf2d5e9648ad426d1", - "senderId": "AeLV4NUMsPJW1nvHquBWPFVKCWirs1pshf" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "0295f1d78f3adae92f0c2d061367e0d06a3fcc9a838a725ccfe660b9c2024253fb", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_32", - "publicKey": "0295f1d78f3adae92f0c2d061367e0d06a3fcc9a838a725ccfe660b9c2024253fb" - } - }, - "signature": "304402207720c103e2c3000d867ce3073f9c5375d9f3cbc69dc6affd08d72cdf0322dd44022040ef700ba6c235af8b2e0b71b3b8a825a18f276a192ffdabc01118e2786eb567", - "id": "aa03f6eff50359838142b70634991e93d48480369f087d73cecd0b93847233d9", - "senderId": "Ad6y8ae35QWkrtiLBpiXRR5Cj2KK2u3EGX" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02ac0a9348306d3dea50bde6b38791b2716f698044c207bf005788e13f35b46693", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_31", - "publicKey": "02ac0a9348306d3dea50bde6b38791b2716f698044c207bf005788e13f35b46693" - } - }, - "signature": "3044022070bf433e253fa69a33abf85559d804d1d0e055199c89abef5dba34e5cccf1952022023fd278d32b75f0465c777c39b0a616a4dd0a957b08e9723c1702dbf4d6a1ff7", - "id": "c64e9e47d8c7573dd97d2bb634eb7bcb141c156ab701044319e19ac0a7f55f3c", - "senderId": "AGb36aHfdxvqbMqoDCnm6wVkKKtqkE3zAh" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03c075494ad044ab8c0b2dc7ccd19f649db844a4e558e539d3ac2610c4b90a5139", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_1", - "publicKey": "03c075494ad044ab8c0b2dc7ccd19f649db844a4e558e539d3ac2610c4b90a5139" - } - }, - "signature": "30440220470149adad4cf95460a3e02ce0fd79d933a1612ee9d8711d104efb5c708022c5022077eb9aee6dc62bafcb4217484cb2b892d8ddac8ba2a170390438cf47521a8d3a", - "id": "1a7f292a5f3df9f9a3ec97d7abace3d398adb943ff59da7c0b0eed8f19928ec7", - "senderId": "AeLpRK8rFVtBeyBVqBtdQpWDfLzaiNujKr" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02afe652f7d14a877e73f00cca6c836efda1a05e79c6fbd7f92b600a4cd519f4f3", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_30", - "publicKey": "02afe652f7d14a877e73f00cca6c836efda1a05e79c6fbd7f92b600a4cd519f4f3" - } - }, - "signature": "3045022100a7aa2d57b6598379d04e9143542bf9abc4fb4899aff37ea7a7be0dd07c7317cd02201284a2101aa06dd7fa6e34006b666e4916bd76071032002302dde92610e04dc7", - "id": "e8afeca5ff2aa3ccd8c29f7249d93d465049bc31b3cd3fd6cdc6e09ca92ab921", - "senderId": "AW91q3n1QYTn3caBm7KR35zexcJU7PgMtZ" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "024dd37755804e6dda0ddaff6691aa038c57d8db36d59fd1695a2519835a828072", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_51", - "publicKey": "024dd37755804e6dda0ddaff6691aa038c57d8db36d59fd1695a2519835a828072" - } - }, - "signature": "3045022100f5a4c4d258caffc712c9aa515f3e235b352a8d2250695e7570a14337be64cc410220446bbd588e8aab1f69c900e8472c8bbb61cb6b14aecd0e0d693f74714e8511be", - "id": "e77aac7c7d14794e86175a0b0e985a09dcc485c4e1117aba251dc3e45addf4fd", - "senderId": "AW58iWw1ATvzGHu338WqMYvgUhmvMMsRjF" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02763591456b7d3ed76125e4b9d5b34c3926f5aa166200dd270e45ecb6a6b15d9a", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_28", - "publicKey": "02763591456b7d3ed76125e4b9d5b34c3926f5aa166200dd270e45ecb6a6b15d9a" - } - }, - "signature": "3045022100f6ee4060cfdac8c8a9dbc50b49b7cb59d4a587a5d14135fc7345594ac2b7e10102205594aaafae4f78415f01e0228b7233ec36356db4d73da92f6720502874bc1672", - "id": "4164cada2725a8e52f8713f8600b3d945bb3045fc956d79066974e3a012021ec", - "senderId": "AYaYHEKaJvLLWX2NgM8VXk15zSDxV8vCgn" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "025e6a4c19af3228659e5aeb3638e78c50203d76a806d2e1ea938b79247a25a932", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_27", - "publicKey": "025e6a4c19af3228659e5aeb3638e78c50203d76a806d2e1ea938b79247a25a932" - } - }, - "signature": "3044022075186579fba3bb355622ca8e4e0470bc54e82ae15a16b5e1b16784b29087f70602203fabc6d8547b6eb07c59193e0b42f3bd6b5cc234ca5d415c791c9c5f4a4b7f90", - "id": "6aa2b74f026ff9cfb1e5e00a3b4a75f752f912337fdcb61ac9257d58fb406cce", - "senderId": "ARDkQVS4DbJnErrptyWSWdJD8gtaPEyRH8" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03916b256a6ff5c51bc0b7866678cdc40a2d06477fd022b61f79383b6cdf487bcf", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_49", - "publicKey": "03916b256a6ff5c51bc0b7866678cdc40a2d06477fd022b61f79383b6cdf487bcf" - } - }, - "signature": "304402207143545435e31741050f802e434d9559c486dcc19e136e6eb68c7881959f5f970220387f941b78ecbe29b209993bae101828daf5492cd2febf256a87d157b97af8cf", - "id": "d6805135ece218f780e604673b9946b789d4406553a5dcedee1c06575cc7c63b", - "senderId": "AJjkVwkhsvsX6dVKhVxpmhRvz4CyXLEvQ3" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02b38e56ef8fb018fe914c484ea4b0aa18f1a938f46af1c394dfca40adf2771bde", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_25", - "publicKey": "02b38e56ef8fb018fe914c484ea4b0aa18f1a938f46af1c394dfca40adf2771bde" - } - }, - "signature": "304402203222d23708aaba6f4904fa04ff5cd88c9c9aeb91771754763be9206d79f2d85402206ce18e2b3bccb6fbca2e024b1bb38ecd859c27986a0bc123bcc29f99b0dcc6b1", - "id": "86367f56d8b4a1bc34cf1790b3a1f81cf794b9e9683b347b7a5a5110e63b9b34", - "senderId": "AJrXd5u3y6FH5HktH2jgkLQHjgD9ZztMtk" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "034bef7fed4f718a450ec4672533d74ef95039cda85a1a0ff74e6b2dbb9e220d68", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_24", - "publicKey": "034bef7fed4f718a450ec4672533d74ef95039cda85a1a0ff74e6b2dbb9e220d68" - } - }, - "signature": "304402202b33813e4cdc7f97dd41747d53542803cc7c5ae7aae56cfef3e4b9d85e74302102203854d993b16efbf7b21454bcf44873969f35d033ee93866fd2293841f866441a", - "id": "65303140bae99aef3d68b36379acd8b9f4263f1d90887efc1fe7f0735c26b84f", - "senderId": "AZengw5WND4WLC8JKz6xUDFwLz3yCKPpTC" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03d48d4160457b1fe8d76e2da68f33860ad2520836b5ddec8af925757690d93c21", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_23", - "publicKey": "03d48d4160457b1fe8d76e2da68f33860ad2520836b5ddec8af925757690d93c21" - } - }, - "signature": "304402202c43ebd655af5fb0e7336fa41f03977bc5b4c05a5a6f335e544a58acb5f5ff1d022074d415ad20703c67fed90e1d0bc970ebe1d55f31a1a3dede173362c3736ba6e5", - "id": "c529cb54924f221a3aba98b204f82c033b67375a15d52e52671bb526957baa3c", - "senderId": "AaFxHxsjYyYCsZtpQwwYGvYESogJ2SHxe5" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "0384e505b25eaac812f8acbaa1ecd09e2ca4b5d973210189a39dc48d2a8f908780", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_22", - "publicKey": "0384e505b25eaac812f8acbaa1ecd09e2ca4b5d973210189a39dc48d2a8f908780" - } - }, - "signature": "304502210099161fc12c6ea658f382dfcbd495c84961d1f8f029edfb6c2746ddca918418400220556ffdde284f10b5ba62bdf6a67139bc56f7d0e44e87c651cad6e8c2f646aefc", - "id": "bb014f5d3ba5ecdf3dba9949673e1cea6eeb3771f41bd88881aa4f055307e4d4", - "senderId": "AY5GwZtG9sFvDzMKAQfAD1Q8EHDh4bW718" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02c3709dda58fcf7e26f94e865458b08d95d398446f67f465387be56b03ec36f4b", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_21", - "publicKey": "02c3709dda58fcf7e26f94e865458b08d95d398446f67f465387be56b03ec36f4b" - } - }, - "signature": "30440220562a015669686e64c274be9823add0ce9da9bb8ea31e376c194b7e194c7306670220186b3482683572164588f2d89c8483eb6a789efaaf1945ba86b5333f23235254", - "id": "ee1933bf760161dfcce831d57e784e13f1e428bdf6ff59a0f06c778873172032", - "senderId": "AZvjdJSG5V4WnNjPaRbDNfLD72j3rYWqbs" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "021c2280e462313d08fbe92bac59f43203cef4e21611d42747535ec2987a80e30b", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_20", - "publicKey": "021c2280e462313d08fbe92bac59f43203cef4e21611d42747535ec2987a80e30b" - } - }, - "signature": "304402207e28352abc1eab6c5405cf78c2c1fe406912d3b08c6e02bf44877dd94d117eea0220144013da4977b0517f8fd8b7dc6838185e25d0f6f272af17125f50feced46242", - "id": "658f84d6568a7f48295e9f25281c6ca71c404245ece501cbbebb0c60efd08f31", - "senderId": "AKeDRRgG4TdDo4Z2iCzaBZS2UcvjWKMSrC" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "0212fc419fd5f4c88f73b8475ea676f3800f4bc96433a074462d827fc35f4fd883", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_19", - "publicKey": "0212fc419fd5f4c88f73b8475ea676f3800f4bc96433a074462d827fc35f4fd883" - } - }, - "signature": "3044022036cc85f65b9cd55c1a336cc02c06cbecbc90a70314ed2848dbb7bb9cb7f7fad302200b217996c20eca5c5eaafcdc4d2a27a39d8945f55cd39322a0070b180e7a923d", - "id": "b8a1791640f64e4c977bc2c6f8afae412f6d125db9791db33d979fd3f607976d", - "senderId": "ANeLRbMxkSguPMq9CJeMgr8xZxwZ9mbqbx" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02015ebd34b20a1c7bcbcff1d53e7d2bbcb2b4eacee494830409e1f84117b802a1", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_18", - "publicKey": "02015ebd34b20a1c7bcbcff1d53e7d2bbcb2b4eacee494830409e1f84117b802a1" - } - }, - "signature": "3044022023390c4ff70c6f02923b4f55ecd6ef41e93871b4e4eb21be80b5023f72f1095f02205c921d9051395cf7a4d335a00a89a9e0f0d7afaae699b371503d35eda7cd13aa", - "id": "fa9bb4c78bbcbc164ef4a630997199aa01b53bc93d49b9f3237efea13717f04b", - "senderId": "AdCa1oGLMRQqygEBGGq1AEtSV44CJ5Kbdo" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "030cbba2415349f8093539632fe26692411219c11e740e9a3e02b6c64415f056b4", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_17", - "publicKey": "030cbba2415349f8093539632fe26692411219c11e740e9a3e02b6c64415f056b4" - } - }, - "signature": "3045022100c0e0115ba685768aa3aa11b9d0e1149a63824f72a486efe2dfa16df93d8f8123022023b207efa969e876be02f87ec8085da800750c127edeee1fc481fe978029802a", - "id": "4bfcd5ae4f01935aedb2fed2d509698902a054456f7dcf5fce02da02f1c7d752", - "senderId": "AXuVpX3tAewNp5YVWvXWv3etxjrMDgaZdK" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03367a6969c0d62e9b0fb3439ff2574dbacd5d616cc57b08f7c5417d4ac6e94faf", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_16", - "publicKey": "03367a6969c0d62e9b0fb3439ff2574dbacd5d616cc57b08f7c5417d4ac6e94faf" - } - }, - "signature": "3045022100f9a6be773cea615ec0d0cf0e330f3ca1faaa86f7475d620f6ae1299ca58671d5022065ae6e660f1c80e935db4e66387fb44225ee73f671e54003c0780e435029acc3", - "id": "54f2abed7bbdedacff1decc7be229f6385561cc8f800676ea61deae358bb252e", - "senderId": "APQgLh8MaztT1XuWVKb6d4FKM9HMmdmDVB" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "0315ccdbcb0ec7bf484726f95bcb2b331cd976bd7d72f0e3f5c38fe4167d7c69fe", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_15", - "publicKey": "0315ccdbcb0ec7bf484726f95bcb2b331cd976bd7d72f0e3f5c38fe4167d7c69fe" - } - }, - "signature": "304402205a4959cab68877017ad4d4ac61a5b7c9a7cc674ec66b21e7463a8673b8d51ab002200fbdab9ab0e4601e1fccce869be1a1b3348d8127e4febeaab68228953a81cf04", - "id": "7348d455b5dae1aebd48b3668b571f5e44c0e8f612c1377a19de8f3e27c84644", - "senderId": "AGZhXHXFUdvd7W4aWs1fYJe6XFUYeSWrd4" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03477d8e77a43b443d401aeec8b32aec5429f8f453b93d5c4061cd17101e09b077", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_14", - "publicKey": "03477d8e77a43b443d401aeec8b32aec5429f8f453b93d5c4061cd17101e09b077" - } - }, - "signature": "304402201a4433b86c57e2701fdbf7640b13b21634b7cbd3319aa0c914e16b59fae45bfd022044dc7bec58897ddb36fadae3c52662aa228ae88b0ee6c467a52d1e476784a30b", - "id": "6f6fa4b601053f6fcf50e734d6caf590dc4d23980c32d9f1b62e10cda3241018", - "senderId": "AVoTzMjHaaWKDuA26ysXGU681N6Q2PQre6" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "037d5887d9a9bb48e45404fc5c3149bbd53d82bb4572bf468f0da9b4e9f6c73ae7", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_13", - "publicKey": "037d5887d9a9bb48e45404fc5c3149bbd53d82bb4572bf468f0da9b4e9f6c73ae7" - } - }, - "signature": "3045022100c2f43505df18e7682ac36c16f6429764ad86339527e554a78ef44d0941cfc937022007118cfd9b51ea359ce4dcbbb5dcf06147fd219a38bf0eee9a220c507c5944de", - "id": "dca332856f06a5431c648f73f859b8f8286bac60d7f452eac42d7febef0fd7f2", - "senderId": "AGdHR2UZ3MJuaXWejKGoAycztyN8BFQnNG" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "026f1910d432c8ca8f04248e74c4b565a236d9851caeed4422550c3803b313bf39", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_12", - "publicKey": "026f1910d432c8ca8f04248e74c4b565a236d9851caeed4422550c3803b313bf39" - } - }, - "signature": "30450221009a73cbe01aba47c293914b2bd382e5715a611dc14714e225fe9287cf293fc34f02201b05843284b43452c1b779b6121be37b4459e96d6e1328f6ca4c076522f4351f", - "id": "49620d01436262018949d76325f7f7c974fe5419a32626c3ba1e7c3b2f0752e1", - "senderId": "AYTEu82arYgRyvTgi7dbYwjodV7ignYucz" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "024eda8b8e70b93f87c1e18929a2ad789aad3f6b3fe4aa12b4f74513bc45916726", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_11", - "publicKey": "024eda8b8e70b93f87c1e18929a2ad789aad3f6b3fe4aa12b4f74513bc45916726" - } - }, - "signature": "30440220637254d742d5038797dd38323259ba4263c3e98eefae4a8c6717faec0e866a2f022075765f0a4036afbd297bb3bd10c7ee7f62731b45dfe708a04096947af7caf106", - "id": "63acffbbad5440dec99e17dd3eb4d501f44330f0d8c4db4cf161cc0992eae5c1", - "senderId": "AWLoVgSScNNB2kecswuhbY9tcrtv9kf2iC" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02886a2ad45ba50edeffbb7447cc1baf1cdd16e3b91e5ed6ba8b16c687f03e01db", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_10", - "publicKey": "02886a2ad45ba50edeffbb7447cc1baf1cdd16e3b91e5ed6ba8b16c687f03e01db" - } - }, - "signature": "3045022100e78d6752036f323c61e78cefc4355a1dd7372fd782377052b5c80d30f81597c3022004b86acb40c52d91a1eea79d4de929c59a5e24fed6f243157088c1eed03f039e", - "id": "531ecd8620d51f46dc1140c8d8f0ec8344b4ebef61f17e8c304e5b3bd5e02586", - "senderId": "Aa5rKoVusA5xiyh8Git1tJUWZE48ScbCR8" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "037bd8595ad6b5787671c99921208284ac6f791a8ca55c6e49ca94a94731b6cba9", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_9", - "publicKey": "037bd8595ad6b5787671c99921208284ac6f791a8ca55c6e49ca94a94731b6cba9" - } - }, - "signature": "3044022066203d448724540f14a916591051ed79f57e3995af8cd9bab5ef003064c08dd002202a60f251bda6088ce8d43d6289ea2ded6442e95344654f02a17488f26c10a893", - "id": "eb253b77c4560b226d8edbfbb860717289258f4cd1ef15cee6da88f80029a33b", - "senderId": "AbfnTeGFiRM3m8eHcMsrqNvrpUCoCnuSzH" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03e8eeb0e5063caf214986fa2e085dc67897908cc2501ccdeefeef33722afde50a", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_8", - "publicKey": "03e8eeb0e5063caf214986fa2e085dc67897908cc2501ccdeefeef33722afde50a" - } - }, - "signature": "3045022100c6bcd9f75dbc9bafeb2e134028b2c4f8be77214611012f4d9b92bad5610d42c1022039923f93586e1976ae614fc90208352dac120505a08be0a2b9b82840622473a4", - "id": "5378c9044477765f44580a5f42ace617df524457315fe63ef9806e41de5e65fc", - "senderId": "ANstQM4LaxfsuKtFMd8vqdGte3CL9s2vMA" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "0307784cfc3d9002be47ffa32e8d99146869342254247df059452c5f92f7ae521a", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_7", - "publicKey": "0307784cfc3d9002be47ffa32e8d99146869342254247df059452c5f92f7ae521a" - } - }, - "signature": "30450221008ad9a84421282b28c6d4cabc42a7855bb3853a02edb0524fecc21b821216a6d702203b0558b44c659d52fa163f8941a61ae0b8cec81cdd3fdbc8e91577ede6815fcc", - "id": "20d635b856a468eefc0801853afd1d387d25fdbe69c677e56b850fc45a7c3029", - "senderId": "AVFkEkCmEg7cuCXoVrfvtH6mKz6XC9XnvV" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "028dc3fd930165f910571a159f574ab15ac740f48e68429a7fbfb42ba202c64a0d", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_6", - "publicKey": "028dc3fd930165f910571a159f574ab15ac740f48e68429a7fbfb42ba202c64a0d" - } - }, - "signature": "3045022100fdb6d70801c889e1173ae9de78a07e55bed999ef39107f2dac1e65d6bacbe89b022004557733f6fe0d96f67cf420691375a8ab23ab212365a96acb9b36aa54f8f415", - "id": "c7e1b81e3cb7f63eb947647fa572b5309b9f022d893e745baffa282f51e65dba", - "senderId": "AMG1Y3LP4kZJMAtoPhsnVkpsMTJ8nnCDr3" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03de80311e0ca23a780507fae2691b7c995cf36fb2b2d079b7c518e03302d56eff", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_5", - "publicKey": "03de80311e0ca23a780507fae2691b7c995cf36fb2b2d079b7c518e03302d56eff" - } - }, - "signature": "304402200bd5d223d887130a7a8b65f10e8f0a7eacda456257dc5eae4cf0f3f7068ed6e402207d6d5dd9cf0f69f592fa81bb117a83cd0e55a21b16abe048c1d4b603f295fcdc", - "id": "71debbd9fb57e0cdfcf42d9d012768a7d9ad85b4b6f32d520b424ef0e0893bc8", - "senderId": "AFxqVbsVuDfnfyd9ciRAuGq6waR5GqiC5R" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "021780f82dfb6331cbcf35b9fc233cf3494e569a329b2966249c5632b0ecf53cb2", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_4", - "publicKey": "021780f82dfb6331cbcf35b9fc233cf3494e569a329b2966249c5632b0ecf53cb2" - } - }, - "signature": "30440220043a0359c3d68ff69877cbf2f116410bea908036f57c3aa1d51684ef9e4d1fa102202a55ae852b5e547c6733d8ea0fdf137f6525b4effda003e09289936be0f333e4", - "id": "3eee8478d002ba59f2ab94bd539c26f805005b24d8d0cdfda6f79ba576db55ce", - "senderId": "ATjjXXcGPTum6wogPVGb9pmimpSo4EDDEv" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02d113acc492f613cfed6ec60fe31d0d0c1aa9787122070fb8dd76baf27f7a4766", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_3", - "publicKey": "02d113acc492f613cfed6ec60fe31d0d0c1aa9787122070fb8dd76baf27f7a4766" - } - }, - "signature": "304502210097b10f85338d39ca4dfda4baf9b86f97253cb06ff2c80d3d9a3e5023b2294f060220192986b55a91bf3c5d5ad1e8f7aebf69df89f022e1d8f8674ecb675faace1623", - "id": "69483bf341ab8e5ef989340da103a162b25e09ef205b53a7d2f6f8547bc7334c", - "senderId": "ARagsXvdeTHYghaQgJkwbdSkPLZ73qdMkR" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03aa98d2a27ef50e34f6882a089d0915edc0d21c2c7eedc9bf3323f8ca8c260531", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_2", - "publicKey": "03aa98d2a27ef50e34f6882a089d0915edc0d21c2c7eedc9bf3323f8ca8c260531" - } - }, - "signature": "304402207f3b69c5fe22ec832246ff2e0318b361849cb8fb7250d9eee96639e17d112ecd02203a4d4010360992f4162f7ffd5361884d787a07580a579da11fee4a8b8b7e78d0", - "id": "d6424b437baef1b93e967a48b29313347e6a79cd93818528c9a7515ad167b917", - "senderId": "AT9xWcPQ8hGYuXZ8aWE57VJFohyX1TTLkH" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02e83dae2b59ad7923d931f8d0ff96588b6f2b2183288c667bacf2888d5f9d80c9", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_29", - "publicKey": "02e83dae2b59ad7923d931f8d0ff96588b6f2b2183288c667bacf2888d5f9d80c9" - } - }, - "signature": "3045022100e6d497a3905baff592921a269d78882d787a03a4c67e7818a5c71aa57c00186e02205b54519080782485a6ca918a50bdac68c0cc1723259cededb1ae9f011a88b919", - "id": "fd90a8bf3d38e0fc81020705be347205da7006d4db889f8a5c28653069d060b8", - "senderId": "AGNMmJ5upuU38ucaG3TUsG1ESaQDExSMo4" + "version": 0, + "totalAmount": 8318249000000000, + "totalFee": 0, + "reward": 0, + "payloadHash": "14b55c1de06caa015362d59ad97a144bc3c9fc2b50ece84b78d13ceaeaf7d8fb", + "timestamp": 0, + "numberOfTransactions": 52, + "payloadLength": 11392, + "previousBlock": null, + "generatorPublicKey": "02e1222ccbbdf1f71d0a72f20f0c9fd06f21b596bfb7c8ac4eee306012c26d0bba", + "transactions": [ + { + "type": 0, + "amount": 8318249000000000, + "fee": 0, + "recipientId": "PMg2yZDrEPtBEe3R2RwBxaVY2PMTxjapQR", + "timestamp": 0, + "asset": {}, + "senderPublicKey": "03ee148065c447d78923ad66a703947a0173b7439549ac9f58a70a9e703f3afe19", + "signature": "3044022055ea5f0e597fac456f6249553f1ea9ef47f8f25d90e55bf6b1b6543f234dcf5502207da32523e3adc98fdf49fa9c85719b99dedcefc3033dfe92d10e68ce857d3103", + "id": "f1aeaa47928395f7adb543b66be28bc55a331c800530dcf485a55c217f9b5651", + "senderId": "PABtdLWyCU1eywzUgdjYA31uxiRtbfpD2N" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0374b927085c98c3efb37cb2d064af72ff5bf5fe7d3812fe18ad3d8e9674a7c3b8", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_1", + "publicKey": "0374b927085c98c3efb37cb2d064af72ff5bf5fe7d3812fe18ad3d8e9674a7c3b8" } - ], - "height": 1, - "id": "4366553906931540162", - "blockSignature": "3045022100c442ef265f2a7fa102d61e9a180e335fd17e8e3224307dadf8ac856e569c5c5102201a34cb1302cf4e0887b45784bfbdaf5cfbc44f6d6dad638d56bafa82ec96fd45" + }, + "signature": "3045022100998342dff86aaa780dd0c7b61fc75e83f099cea107abd84c4060f22e605c161402206f58ad5297414eca4eb060b27534e7470d6e7e139475b11ba79e9c811e7cff19", + "id": "62baa9fb4402e3a9d320e8b39f92e27b93066051a07a1cec2ff0c252dfa23b07", + "senderId": "PAGhqbWZeSTB4i1rdH4v51zgYdHizFrX9o" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "038bfbf30439e1d938905a03a087e0497696f42b6a8bbaa2b5ee6d8bb09b1bb76f", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_3", + "publicKey": "038bfbf30439e1d938905a03a087e0497696f42b6a8bbaa2b5ee6d8bb09b1bb76f" + } + }, + "signature": "3044022014c4f7fc6bf03ea8c261cc126e6aaf8e75db5030dbdc536de53e692b20cd8c1d0220351d200c7dcc6e4c86bceebcc66f1264e52580c338e37eb1d065752c262aa558", + "id": "e8636777bec316b79668fa7c6185d22e652a0c37acf73e78ba072ed7c87b2471", + "senderId": "PDzWeHhQS1FfqMghJwzz2nJPHZhmeGH1N4" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "024072ea8c2ab0a4885ff120daba091b927481f74f66b2b0dd4e7d13606f98f975", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_4", + "publicKey": "024072ea8c2ab0a4885ff120daba091b927481f74f66b2b0dd4e7d13606f98f975" + } + }, + "signature": "3045022100b98c8db3166608fa6efdb8ced00beff5a010a1b78918f1b80784792ef1343c3302200e9470bd2d6dc0d551caf1dd53acf7ce3414bf559b5898c6f1ba25d11eabc1da", + "id": "1a5faed219dfd9e06e5a949269e62ee5b0f5141bcfc9a05580c36590b0888565", + "senderId": "PKefVy5fGrvoFCUG3AeU1ZUw2jU7bgJ88F" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02f45593b7871c0ee556f44621aaf6195f103198ae39faa968c3e2c13b5f4f69d4", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_5", + "publicKey": "02f45593b7871c0ee556f44621aaf6195f103198ae39faa968c3e2c13b5f4f69d4" + } + }, + "signature": "3045022100c6abc2fad8dc79ff4cf78f22cfa9047040d661f0ac0cc107260180a26229ca8b02203f7c2c805c1de7b4ce4976d414c781df0c2e70874b96d8473baf53ef5c8f9e87", + "id": "d9478ce1d6e735daa696026cfe11accc9e1c7f165c9d3fa6d7df2c592e6d3a52", + "senderId": "PLycu3n9pdTiruErAwQGhuvd81zaBzxApw" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02ef76674522194940c5cb83a8c0ec0d76c1f39c76ac5b4efd91dfe53767b6d099", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_6", + "publicKey": "02ef76674522194940c5cb83a8c0ec0d76c1f39c76ac5b4efd91dfe53767b6d099" + } + }, + "signature": "3045022100f59d8cbe7c7fd23178c3ca8ae355ba7784ee39140632b66287ff484c6f921662022013a76693442fe638b395d5370343892e7362896db0db8a35fc74263cca8f899f", + "id": "40efdbaf16d8946c0323cf2680583fb8daa5438684879187dbf23d2a342f0f6b", + "senderId": "PMbLJ94Mzx9FN1hgo9YiBoNwWvjWMiHpRM" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0232bfbd6de4b3ba17cd41926826bb56027a3f0297d55a7a51e12bb23624f2f3e3", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_7", + "publicKey": "0232bfbd6de4b3ba17cd41926826bb56027a3f0297d55a7a51e12bb23624f2f3e3" + } + }, + "signature": "3045022100e5245a13ab811a9674de60dc187848b5c0ca754d39b76cd09405b0ac2e5c972402206764a1357fe0ec3b89bc11905876257adad7702a18c54ccfecdded87288357ed", + "id": "9fc6cce014247335d167d24757f581bef1faf0654baf8f9f3360d5c7f3b5567c", + "senderId": "PUn2psHfGcGvBPnSKhEXBgYQE8Tpa8cQLY" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03da96dca73b883d8c39034a24b5152606097067f0c6ecc9eeee8816708a3519b3", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_8", + "publicKey": "03da96dca73b883d8c39034a24b5152606097067f0c6ecc9eeee8816708a3519b3" + } + }, + "signature": "3045022100cc64317f7dd3bb9bf69a737295ef509c1f5e998509a11722c4fd9d0d66d7ac1f022003525ccdc909c6af64771de0eb9dcfe1c20a602f349d9ff55df1d084e5b6b4a4", + "id": "c90158d57942757d9ae43cb72acda3181566e7f242c0c84ad1cbf60374767d64", + "senderId": "PPeqAjVitGZp27hQzRuyB4FWpk6QhpE9Zj" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0386c9b401083a81529274fc5649495a115e45034573bf99a5a55d2b3e86a815c3", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_9", + "publicKey": "0386c9b401083a81529274fc5649495a115e45034573bf99a5a55d2b3e86a815c3" + } + }, + "signature": "304502210095709c725fff9f2ad8ad7087f2f5d406d9560ac9553555876a7e4e6d136cf2a902201353115f78060455f11a97e49cc2fb54492422ae451f2a906d77518df13a4ff7", + "id": "597d08a136f323f5c21bed5b6ce8ac547fa98463f07e455212afda02a4eab8a8", + "senderId": "PXmDGjzxZANstdC1DXgimoz9irDYQPnXaW" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02f44208f89238c9cf76f9cff2981d3704e335bdb8cf0c19372b6be59ce68e3d6d", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_10", + "publicKey": "02f44208f89238c9cf76f9cff2981d3704e335bdb8cf0c19372b6be59ce68e3d6d" + } + }, + "signature": "3045022100c590539d3dfa575bfa079837ddbedf48f770fccabcfeae293ff76fc7c54a147302204b40115a018fa916e8daba71cf180c7bb366827a4fc15ca6b6f83ee2ffda840a", + "id": "434dca13dd1d24c214be42a718d38e28e72f5bc95b242bc03b367d438210c5c7", + "senderId": "PVX3YBxnQuN8tLQdnCeqW1qur4f1efSDgr" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03b7ada1f897ff10a2c54e2de623f6e2db286fd0542442216061b98ad2ec2ffd5c", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_11", + "publicKey": "03b7ada1f897ff10a2c54e2de623f6e2db286fd0542442216061b98ad2ec2ffd5c" + } + }, + "signature": "30440220224c66daaa7c24a182778bd28c7e2d6ed098d73a39ef3f9fec6a202915a4d838022037d4ec26866f31bf055863ea469a956f87cb2755538b6183617e267634d88e29", + "id": "c15edff8afe0d095dbe144c9602f7e4cea9e05de526acc1232db8e03e7ddcb0b", + "senderId": "PG19vDthcdRThdRfoohqQxnyCiJEGP9auM" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "034a74f511513fe3d008916a21d1daf35ef5fc0464e02df916612c739ba0ac675e", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_12", + "publicKey": "034a74f511513fe3d008916a21d1daf35ef5fc0464e02df916612c739ba0ac675e" + } + }, + "signature": "304402205030ec5769ba485c11c727f04099792392d4fb098d8bb866bcd0695a7bde96ec022028fe57e7a4fcb3a1cb0999d88927d206926d446848ea3f987687b960829c3183", + "id": "6133f39892de2c7a76bc7489739c0e2c1010b4996f14c1367154ec4937b5c721", + "senderId": "PRb4Mj9n2tANdqXYwZH4QS3zBjQ5F7VmNS" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0217f1634ce090a93f1e0604090398bc1e45b456a344be5257758cb6ffed31e0ff", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_13", + "publicKey": "0217f1634ce090a93f1e0604090398bc1e45b456a344be5257758cb6ffed31e0ff" + } + }, + "signature": "3044022009a1fb88758d38918cf0602030f1a9744eaf1116124221ed400873b2284034540220396b1a86bd312a95af3d8600fe8e0627735df97d91552bf35081f951ee86a7a3", + "id": "6064f883df82b4d5c07e46fe32af2f225bb9c016313e6a946d33b6e0483120a9", + "senderId": "PN2HHUHy6x1mnrTkxScS7sHExkHdHVMdov" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03067a317d8be92c5124b4cd92dbd571567795cfbc0555a796acac8ee4ce3e2324", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_14", + "publicKey": "03067a317d8be92c5124b4cd92dbd571567795cfbc0555a796acac8ee4ce3e2324" + } + }, + "signature": "304402206fe4b115e8c3a585c90754585cd03807a652fe5036028d6761bde248d8769a7c02204d1c85ff7ceea0f138f998e6d6b985c0b8b7fc9dd4635186752b076788eb0aa3", + "id": "1bb6e02eb473c27e692dc96f91a3be73b69e8873359317ebcc200ea4fd5f46f4", + "senderId": "PPAevr9RiaGd5gxTsfx3foRr2fcVLNygiN" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0274d6f8a522a11fea7c822742bdac451479930b9e021bdae662af42c7b72f9d91", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_15", + "publicKey": "0274d6f8a522a11fea7c822742bdac451479930b9e021bdae662af42c7b72f9d91" + } + }, + "signature": "304402204ad435a34a1a2c684efff9641bd9b57050d403606c62fe81a73d22897b714f6302201b627a2d7d5903e798892109b3ba5a13c54267200d765a83a1c1cde9549e03e8", + "id": "e68c21bcc9790819d69e70611ce5b54dda3be968fa33f2fbfc978dde6ad8bcde", + "senderId": "PLRov4cDJusCdDBP21CYj2pwoq1DAnUbYa" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02446f4ce9ed7fe51378d6ed2d309f3668657b0c4c3dbe60d431a0161ec0028914", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_16", + "publicKey": "02446f4ce9ed7fe51378d6ed2d309f3668657b0c4c3dbe60d431a0161ec0028914" + } + }, + "signature": "304402207cc240e6ed22cbe319c5549894d8ce2d39473b5d969ac6e03b2c6d22d3badc1f02203c7bcb4b97853ecc61a478402a33b551b1b61b163610041b122110ef05cdea7b", + "id": "e00e99af3677aa45ea766cf0ea03daf88502a6008279dd1198e18a325321158b", + "senderId": "PEzDqV2tKEj6NcsDzXTucrGjZQPrzMiwUF" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03f4e620420b8deae4f14e24e8f01d7b92cf55066f10e6e5a334c0c17f821e6c3b", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_17", + "publicKey": "03f4e620420b8deae4f14e24e8f01d7b92cf55066f10e6e5a334c0c17f821e6c3b" + } + }, + "signature": "3043022015b37eaca3b33c2e0aff74cd8103f77451a3758f82e2666b8e0fd4a7611018e5021f1f710bccf763835e407227934809859762a0ed509797375372509322bdd901", + "id": "2852ce6064db5b8548de661b462733375eb9f0a9daa699a697aca556960fe556", + "senderId": "PTGKKTbdfM4C2Bwdak9DyQRwenNxuTmAHX" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02da7db0e379b2ba7eb550c221319ad77a9c603afaaad498657d4f924c640539f9", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_18", + "publicKey": "02da7db0e379b2ba7eb550c221319ad77a9c603afaaad498657d4f924c640539f9" + } + }, + "signature": "30440220376652a3bf5c13ce907f98123f7d2aba725ea04eeb9852fafab9faea685a21fd02205094512ce8b58144b1d0139834034e3490f29b0d407363196503964af8efb394", + "id": "b785e8f586f347c376d8d5c07924bb6a561e08c411c914f4a5087e515ce1db0b", + "senderId": "PHRAPJsHEXwSjBeeVcZeRJFZ9eeVxaMnhz" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "033f96f5b615570411abf3ab932cebeab4328aef6436dc85b37894e2c506a6bf4a", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_19", + "publicKey": "033f96f5b615570411abf3ab932cebeab4328aef6436dc85b37894e2c506a6bf4a" + } + }, + "signature": "3045022100e9d3fea5a81a75425c43ada5fb2c9f83a7e77fb3579d9c350d149c384ce9011502202c5fe64d5195e6a788179482217d41ae500952270947657b7ff85ae3cc9fb9c6", + "id": "7085f3afed16a63d84dff798fe84d23e115747de7b3bc39378b500eae1810c5d", + "senderId": "PEK91deBs2FMCzu8u6qkATyz9yy13gQyTM" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "026428275e6f517f9fa51fa082e353f73149d75597b52b28afdf339262b36d8694", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_20", + "publicKey": "026428275e6f517f9fa51fa082e353f73149d75597b52b28afdf339262b36d8694" + } + }, + "signature": "304402205740570548067ca920d28cb16dabdf52db5a59fb51c476ebdb2880b438e412be0220105893f2851b1ad9e20cd028c7a60aa82fce116cb0fa9fdd75e4ae7fbc3901bf", + "id": "f58fd3c7d0d15181997a32e62ad76d981299c4c5aead340a564a0ab3c84bb385", + "senderId": "PLij2hQ6AUvcQuDU6HMzt8B2KnsVuUokd9" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0246c2c251b8f7a971985569186354603c01173a5f552260d7ab11f49d010b028c", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_21", + "publicKey": "0246c2c251b8f7a971985569186354603c01173a5f552260d7ab11f49d010b028c" + } + }, + "signature": "304502210085e04d1a362d0ce8faba093b5aa7cbe4704e3f6684780547d14b3248810f87f002207cbb98fc2fa9dc59fd9a252d14605568e3133c2598e15c429fe7d4d3a428ce7e", + "id": "fdf024677fb43d7267bd7c0a0af7f55255c1a8243c6d9426a46f7469785056f1", + "senderId": "PWQLe7pwMtrncrUnq6kzZpiPj5FXw3W4s8" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "021b3fc50981accfeee903b3d1e621bee84c4f0e7234d10c97f06f391ae6af8d26", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_22", + "publicKey": "021b3fc50981accfeee903b3d1e621bee84c4f0e7234d10c97f06f391ae6af8d26" + } + }, + "signature": "3045022100ab473d6563d8a0981539a2b92279e8707deb96240d054297e129522230243ee5022055851d634f4a22b6301c0543ef6f9c86313ac31722281ceb1764c829cb6ca667", + "id": "96a4298e3077685c7483f41706452e82ae262b8af76b5a87f9cded7e77cde7ce", + "senderId": "PCuAQX2hA4KXhmeKja5JW5RMfUbipC9ntp" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03f6a6107d9b6e8d9e372a235dbe8616e1760413705b9f57f2a0f7ad6d8a417c78", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_23", + "publicKey": "03f6a6107d9b6e8d9e372a235dbe8616e1760413705b9f57f2a0f7ad6d8a417c78" + } + }, + "signature": "304402202fe093a813117602e4193390879ba141eca4f65d246bab7f1100a91f8ad9dcc2022035638b055a387839dd114760078fca80bb6bfa39c0d98aac7049e0e7c1ac6d11", + "id": "b39a19a645c2c8642585baed8ffde232b5b0e3d4c61ae26d1231d574899c8c75", + "senderId": "PR4RyqvhLvU4CpP3iwnWLyEQRHmaWLZmYH" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03d052aa634f0b370b17d6cd4a4b114fe45bc6edb893504716e11f7b1e6ecbed09", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_24", + "publicKey": "03d052aa634f0b370b17d6cd4a4b114fe45bc6edb893504716e11f7b1e6ecbed09" + } + }, + "signature": "3045022100c57f3d1ad8bba73e6f021e00f56a19900d01fb11f1fac384433861e300cf2e55022029e46de780d68a45976b905595a03ff296d5642f51afaff3e84d6976e706e7c0", + "id": "635e5629a6a33a4e7b2a88c8f1e579c8ae045647f149f783e9c9ccb4ecc16725", + "senderId": "PRSGSPCfAZ768XnLDuBv5bhwr335uDNTLJ" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02db8f1045062e9e3fa67c847f1f5856b4eef785018c2c2831fdecaac4d297f6e0", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_25", + "publicKey": "02db8f1045062e9e3fa67c847f1f5856b4eef785018c2c2831fdecaac4d297f6e0" + } + }, + "signature": "3045022100e3edd35144ce84fc862b191065de4e57c976c322f9d370c91bca3767467f149c022061539a32e62e9158d976bb8c4c0bb488e27322a241ecc61a9ee140e990383fb4", + "id": "444a42cede19f6f728d5f2aaf4ed0f2753c8a2d4c7979217b52b951cf817a555", + "senderId": "PAD4bNAXVn7kYBpqmFesFMy9iSsv1q8TNe" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03f496542348e2c71025d9c4caa79cdf041e9aee3b6aa66149319588b973face47", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_26", + "publicKey": "03f496542348e2c71025d9c4caa79cdf041e9aee3b6aa66149319588b973face47" + } + }, + "signature": "3044022031159d711c8a51fef7be8ebd814ea6ab31273675e2b1f3c3926a883e76fa91ec02207a3cd4ce5be4e45888ebd206d09a678b6bffd6047a3f7f130b2ae98ba6a7648d", + "id": "fbcf613b0ca64df24b7d72e744be9efb36e2a0e4eb8be4f5a2f280b95d1d45ec", + "senderId": "PBfAgwGqMa9MTeVpxj1NjfWmkQw7donFFi" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "022d9f3a3ce8efa2aa3b80b7c2509a6b50e2ca26648099e6c28f4b246c5084877e", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_2", + "publicKey": "022d9f3a3ce8efa2aa3b80b7c2509a6b50e2ca26648099e6c28f4b246c5084877e" + } + }, + "signature": "3045022100d680b1678c213cf37d1527fa61a4c6ef61c676bdeb3227bed1930d09165af3f102206814032e1cef8e0a2ce53c6a3695a815760677aa118456e4ecee4d7820fa1704", + "id": "97bccd78b8553be8089bd21d53e7d48c420ee7463485ad664f762b8687082bed", + "senderId": "PCWUKmHv5rQq53aUVbDdkTmLbdJGifURZa" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02ae55b0f7e199ec8fbe2d7d76a185c417ca17358f0c89f39974fc167e081c0474", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_28", + "publicKey": "02ae55b0f7e199ec8fbe2d7d76a185c417ca17358f0c89f39974fc167e081c0474" + } + }, + "signature": "3045022100e7d7bff0a61c90480833efb205bb2aaf8f162e50dc5116d0c61ba07fa2dc40e7022017228b35e6b29ee48ea3b40ada4ec19b42e6cab722e60e549868e02ded277b20", + "id": "2f6966e2b9f4ddd002d1ec516ae6d954fdbe3ac4f17ba722402e1601d4fea845", + "senderId": "PMVYLR1hbkC4CEqDFhcPcPT65Ysb7M31WU" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0219203cf05045e9594914a68823105b90ca9349db0547ead05939579de091ae46", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_29", + "publicKey": "0219203cf05045e9594914a68823105b90ca9349db0547ead05939579de091ae46" + } + }, + "signature": "304402207297c48aae7e6110a1c1cb7736fabccd55ccb85ebf8dc2539bc7d48cf1e68cca022058b18a7c675a4ed3c2c8992a936621401c27aaab010f2e3035d748616f93bc8b", + "id": "c4f3a929477eda2f6577e3bbc709870650ee16bb6631ed9cd1d161be0daf5757", + "senderId": "PRvEZJ4exKLf2K6eFVCFSprrpgtHFLcPfM" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02b761da35ecc4e68d59d7ea17291b47c121165361fa4fdc3fb9a8dc3dfdf40415", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_30", + "publicKey": "02b761da35ecc4e68d59d7ea17291b47c121165361fa4fdc3fb9a8dc3dfdf40415" + } + }, + "signature": "3045022100f86e365bba8639879f28c37de8af9bcf7f449b0389911ba414f04aed0bf917f602204a160f990a98338f36ddfbc5d27c912ce496b5249db2b859b25c2c729848a931", + "id": "fd70e64c1ab11d401d4f3bc836df3e633ce2b62529df5d53ba5b6e36a2c203e0", + "senderId": "PJJVWHEWsu6u48BpRsjHqJDML7m6n2Tkx3" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03f50e7ec2da48986ca854e8ccca05e2216eff54e8baa6ad064c5db8bf2bc4ff3f", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_31", + "publicKey": "03f50e7ec2da48986ca854e8ccca05e2216eff54e8baa6ad064c5db8bf2bc4ff3f" + } + }, + "signature": "304402200bf99816ba0b7efa818415a8a243388ea943ab1603cba4396a788ac07ae6a0fe02205902e79cda8e0603abd0b8ef2cf92ca1026d6eb6ad82c8bb30db55e2fabc6593", + "id": "5cd7f32db4abeda3c9428798d8aaf537853e9b87dc41e1a96673aaf83b00a039", + "senderId": "PRDQGUSu5JGnvVazK8WjSvp8cwE3HSG2By" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03a08a59e6e5ec13d10f701aa3a466cec7d28e537a8f8f546c9a5b41660e621cba", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_32", + "publicKey": "03a08a59e6e5ec13d10f701aa3a466cec7d28e537a8f8f546c9a5b41660e621cba" + } + }, + "signature": "304402206030d3680d23fd7b6266cad6212506365787d96a6da2b1d3abb8de02d376968a02202308bbb120153b994b38d5fb8c2e878aa529a7becf125aed4dad86e8c26e4d32", + "id": "94da2034b98e11e592e7c25b00013fe82c364602fd928fb8579b567230bb9ba2", + "senderId": "PF8zAcgQX6ZGu7V3EvYDANNYrVseSX9NAa" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02b522bcffc13077b5f7c9abf431bd3ee6eb04e1becd52bbce46c7432ae7b91e20", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_33", + "publicKey": "02b522bcffc13077b5f7c9abf431bd3ee6eb04e1becd52bbce46c7432ae7b91e20" + } + }, + "signature": "3045022100ffc35d1e8978e04255211e9c1b035b4ba023fcac07d1020dbbb043ad97110a5e0220747154ea010ee71c8fc05f1271fba6e5177d6bd6a20646b46c88f8af36c02eb5", + "id": "597fa6c78d9eca36bee634fcc792d1bc385daa183bc6489e7ea944ab69432570", + "senderId": "PE2qRq1vXTZZouogZhQS1ymSBDrcBFTK46" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03065d3bea9c1e43e42f59ccf7225d5b898fc9a75a649c1744fa4463c22a0e385e", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_34", + "publicKey": "03065d3bea9c1e43e42f59ccf7225d5b898fc9a75a649c1744fa4463c22a0e385e" + } + }, + "signature": "304402200e5535fab55cb830438b8eaf7087f9aff1573f6b85a6200b634936124747455e02207babb477390a5d22019ea6722433325af6d739ec9a9e1cd789fa12507cba1b0a", + "id": "7173a8c1ae469ade0cae74a2a74b9f6c52db412fb4a30738c80c837a5c2b7865", + "senderId": "PV8ZtTjvaw9HLgZE2Uzu7wucWARszBPUn8" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02bfeb409783fe7144d35caf45c32c9b1bb402f47bcce5b48b69241eb15a921be9", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_35", + "publicKey": "02bfeb409783fe7144d35caf45c32c9b1bb402f47bcce5b48b69241eb15a921be9" + } + }, + "signature": "3044022020e630b1278d632e7dcdf03d06fbc6b6596822d525134849e1b728760cd9b84e02203d1afc27786990cf50ce01804aadd606e22599d496eb640fc76cd27e1694b730", + "id": "fb79ee04250ef43e722bd6174cf36b03f52c29f7b5d22370b9ed9579bead4c79", + "senderId": "PCjmB6KSSFG8etRgFPCB6gwhP5KMAzGrZe" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "039a5a80a201b51e7119bd0811f4f2ac83a4bb256682146d699f948bf2dd894149", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_36", + "publicKey": "039a5a80a201b51e7119bd0811f4f2ac83a4bb256682146d699f948bf2dd894149" + } + }, + "signature": "304402204df43f34428616f4b48f2fea7a2bd9975bd5ca6ab3583be309072fd390b5ff4c02206b2c4b0f90207c51579b72f95610c571485f41d0b7dcf893f2f44c41b8abe47e", + "id": "ee0643306ec067c1d388abc49627d7f9a84207d655f0885e00177b59900d6409", + "senderId": "PKKJUEe3m9nfMTSQr8EUBrr7uP63Ad5L5q" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0229ab4548a74c7fc9f7822cf2a8cf5919afaa4dbff97d8ba73e97ba0ff0a59eb8", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_37", + "publicKey": "0229ab4548a74c7fc9f7822cf2a8cf5919afaa4dbff97d8ba73e97ba0ff0a59eb8" + } + }, + "signature": "3045022100d92742a9e04ddb706a201e660c90d0f373554d52af83cdba2dd80bca7a4806a302200d6138ebfd2c9d8e7508da9fd2e6cd830068054dec1a213ecdcb41dcdd60a863", + "id": "82607b428adcacae8e60a3decd3a9554cf181e8e4bca219279cb9c486fd2bd20", + "senderId": "PQ9gW9QmtSdWw9SbooYA5VycoPGLxsFYgf" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03dbe580d49d7e974d4bb8115849dbc7e0fa2be9ef991fb2f71a338f3c58c7167e", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_38", + "publicKey": "03dbe580d49d7e974d4bb8115849dbc7e0fa2be9ef991fb2f71a338f3c58c7167e" + } + }, + "signature": "3044022042f8938106dac048ef2fc2e793274ad1009728053458bf9631b52ce544ea814e02201e95a18a7007cc04ec9b98e59563dba4bd4d31c80e5fa4125627a342f11676be", + "id": "d8f6890913bd928198c21b9c3aef25dd6d2528bd4b7537019b569b063782751d", + "senderId": "PW97vWQTcgY5KiYePTrpWNj5zg9SQgHWAQ" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03e6221d16e9ed033c18d9faf4c76064b88fd3b22bca99ade55cabfe797b44cab5", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_39", + "publicKey": "03e6221d16e9ed033c18d9faf4c76064b88fd3b22bca99ade55cabfe797b44cab5" + } + }, + "signature": "3045022100a2352368e3931155596aa4d7da0b756f55122972f936d5677b7685f90400d72202200f0df6fad441d9a6e283ea4039e733fe412a1d9dc9d231a6dabf9b4e1b3d350b", + "id": "a7bf0aa5c46d36b4f13b5e45cb56a885bb24f981ca8159600624835ea3eb5f26", + "senderId": "PLeKb1b1A8m4Wmn8ZDZPygC6ejWi4Hw9a9" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "037f1a7ab40de13c97f3f41cdba453642e632d93a5388954b3f7bc1d2f9960abc7", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_40", + "publicKey": "037f1a7ab40de13c97f3f41cdba453642e632d93a5388954b3f7bc1d2f9960abc7" + } + }, + "signature": "3044022077cc3d3d64c26daf5e2bb4723b049f5af11268e496a6b921280683083cb82c580220170a140cfa2044f534aa9ef27cfbaeed912bc41f736168dadf0faf06fbaa7296", + "id": "31912af544c5cf6a7ae6949657f3c0a6bf196fbd512549b7d46317b7b35c252a", + "senderId": "PEW18No97KSeor25Xqw3UwiFowWAKQWfNw" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "033fe3dfc1c3fb0178e4a321cc5a68c28603295822b18088acfd4b75b1ecdbb123", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_41", + "publicKey": "033fe3dfc1c3fb0178e4a321cc5a68c28603295822b18088acfd4b75b1ecdbb123" + } + }, + "signature": "30440220206317d857d41cec4d45ad829b278f2d1d85fc8ac9b4e719fa93a59d75ad7a330220358f446686bedf11527dc3cd6cca0253ff400d08682ce63cb385e0fc516d19d6", + "id": "7a44ee946fb01a90cd4482e1b93bc79e61cd35cf85fb87ea780180dede5afaba", + "senderId": "PGbfyfCtK7wsDQdD6dA6vKqnuFmMGXcGdX" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02190b9c16ca0ff81fb974fb4438d9ab700b24e89d5b5c8b49c8c18dd9c8485eea", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_42", + "publicKey": "02190b9c16ca0ff81fb974fb4438d9ab700b24e89d5b5c8b49c8c18dd9c8485eea" + } + }, + "signature": "3045022100eb51f7f093ed03b63fc0b01f27504c67e79eea9ea3809ddfd02b7b7cde6c109e0220780a112cd2213621deee752ad9e14ca2a7595976f3fd79439cac0acbe71a8afc", + "id": "d31b63850c70133a7a8f192f95a12cba76e42797b5e0be0ff564c8414ab64ae6", + "senderId": "PQn8omxyYxHUrngQuY2UZqNY9NouvL2hyQ" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "033fac8ab12c000a9f83d80a3df863f173e7d092491e4332c20f7abfaa3479185b", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_43", + "publicKey": "033fac8ab12c000a9f83d80a3df863f173e7d092491e4332c20f7abfaa3479185b" + } + }, + "signature": "3045022100dc81de673ae3aa1ffcc148ccaec108ca878d567839f1ae66fda68813ed40460302200dc4d3588119aa9d12a59991243122c573a0bf7551efef7df0774710c6adc2f2", + "id": "de95c9083f035fef71ffd9c4b0e616fb8fdd2a8a7971a24e96ecef582a9978a8", + "senderId": "PNCx2bkj7GRmNMuGiL7bDeft7v7Kx225Q3" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02e32b146acddb181b6e6b58d57dd95fcde585d1a1f7cd8bace6ed12eca956ceff", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_44", + "publicKey": "02e32b146acddb181b6e6b58d57dd95fcde585d1a1f7cd8bace6ed12eca956ceff" + } + }, + "signature": "3045022100e4ac99cb5d1f7046b4078a7f25b91c0be822c6eed8eb4fa5caac5a1d89453a69022070fb8e925ee4263cf274c60d7c150fc4954213307d087b7a177676a11cc54ca7", + "id": "97e4e31a6c2e8924032899f78dd626b2c3a881dc5a73560ceae6fa9a8446aff8", + "senderId": "PS7M4kLBCzHLy1q3MoGa74zyM6Fzwp26Gw" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03f81d36ce2855ff5e7f2729e0a5da489bf83d3fdfe48059b555d0aa9dcf518f61", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_45", + "publicKey": "03f81d36ce2855ff5e7f2729e0a5da489bf83d3fdfe48059b555d0aa9dcf518f61" + } + }, + "signature": "3045022100e0bbe2bf6800513844f917c4c3948fc3f4f715b7f887c6ee4be045d67dff889a022074b79783a7a25aa8f3b712de7e4bacc1e7224e3fffea220fa765d4bb7ec3ae94", + "id": "9a05dc0089439e940a3ff183c5a3ba7ded8806acfa15e2bd642506d1573d0a0e", + "senderId": "P9We6Zdu7RfFwQANo4Th4x996y2E4XwXW1" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03f1a59f9725983927df114fea6fa45a56e894f8b56f643ec1d6b9d015b2256e05", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_46", + "publicKey": "03f1a59f9725983927df114fea6fa45a56e894f8b56f643ec1d6b9d015b2256e05" + } + }, + "signature": "304402201473ea40e9392dcbda970c2ab7ee956891573b1e91a928a8020cab7b2bb2014502201f7cb4db052811dbfaf6489dbe86e0277aedc086dc4131f4acadec9c373b785e", + "id": "599b1ee4c0461e4dd2486902d02819e4a795d5368734c9774eef33fa959b30a6", + "senderId": "PDGgfk6qe6bsnGvVZUcfPR1WTCEonTJNpi" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03d91d04b60b83980de39881c5fe791a7957d63cb512b701394b517e8d089252ae", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_47", + "publicKey": "03d91d04b60b83980de39881c5fe791a7957d63cb512b701394b517e8d089252ae" + } + }, + "signature": "3045022100e650342d3459af5bc73619832cc90166317844805e2befd767cf2cc86be3816402200fe1971e966856ea7f6950946a44ee317c8b0b0053d41fb2c472856b8b6fbb1c", + "id": "7ceb58bd1b0cf53a61d52a84135e9dbf57e8cb87e46575de165dd989dc576f8a", + "senderId": "PRq6bwxq7CWnQchLrpbw4XC1H9DZsMqvp6" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0295926f46f1ada15b5f537f1d817808d7a8cbd65a5a883226d9098d42fac1a7f0", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_48", + "publicKey": "0295926f46f1ada15b5f537f1d817808d7a8cbd65a5a883226d9098d42fac1a7f0" + } + }, + "signature": "30440220067e4870171fe182a981ba908905d6aa6cb4883d33e1c97f2f51d2f480663af602206942b97abc46a59e66839b2529edf8138556923ae5f541be3e5205af7f35e312", + "id": "7b6c1292d0ddcef175d37eef7ec19978690ac263c8ca61851c42378a41203434", + "senderId": "PWVtLnozBAyPkmRwnCdb4rcQBwvFgFCdFy" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03ee14284b186dc35d90137cb559f332437fbb50b604d068781d1e53653cae3619", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_49", + "publicKey": "03ee14284b186dc35d90137cb559f332437fbb50b604d068781d1e53653cae3619" + } + }, + "signature": "30440220663386f652d76ab2f4cd7a8eeecee834329e180aed1d470e2f90be0713536f6a022068e3ba0444bbb711242b51b09be7143e84eda6d5f80a147956073dbe3408f821", + "id": "ed1fe3a6c5c3e84b78759bd687b873bef4c5706d2b156577cdea285a3c3de813", + "senderId": "PWLQm4w2hLgKPRH3AEJcXVihfbh2dB6Tek" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02274fce2efae4acca63f5fc4d3a96b6745b17cf14d023bf3c03601e034d3a2e65", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_50", + "publicKey": "02274fce2efae4acca63f5fc4d3a96b6745b17cf14d023bf3c03601e034d3a2e65" + } + }, + "signature": "3044022050d726288bad6d4f45bd2f6d598301a94b870a857bb449d5e1de007250db242102203cd82e3bc91492a04671278b3d55e6efcadca1b4ca247450daa5e98c768ed0de", + "id": "671ed000a47635f512c201ca16ab710cc8099a3452c75277d016049f1fcf3a8a", + "senderId": "PRhvXJ14NHNLxWe5ty6s1WTYCy14S5W7pf" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "032d47f2ac6d3d1435249202ece702cea686844c413d5b6f1988ca4c4d4cb8490e", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_51", + "publicKey": "032d47f2ac6d3d1435249202ece702cea686844c413d5b6f1988ca4c4d4cb8490e" + } + }, + "signature": "304402201a32c3745dfbc12b659e821fc07f39873646231725b3a7e1779bdde016cc78dc02206d08d211e8e05a60aba347bfe11a8bd7ade2b1204a785b039130f812fb8a857a", + "id": "d14c4674f6429d9d032550c9b00860228d2e2680656d267b1ce7fba20fcfd23e", + "senderId": "PFGGB73Vh9rRgP9cY26JZMEK8ywjjc8ZXS" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "034e1da1e1b48c4932c71854d67502b9694fa65ac8c9d89622dc6dd63ccad763c3", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_27", + "publicKey": "034e1da1e1b48c4932c71854d67502b9694fa65ac8c9d89622dc6dd63ccad763c3" + } + }, + "signature": "3044022027d5cf3286052c2ae322d370464f1e8bfa935cb830635048b2a026eeaec71a670220017793877c695042cb162272cef86361fa15a461d60adb2e60056eebbb61e4a4", + "id": "1b478f83237764748794140135a44fca614380c3bf0492807c1e18527342d985", + "senderId": "PX6m7AizXk8nmCSh7GMPLNQCVkM4tTjeEP" + } + ], + "height": 1, + "id": "11343267785436725796", + "blockSignature": "30440220509577d8e1acb504a76679e35a3f7884fd17b7bbe6bd44470a80034128ab3d2b0220149491aa041f846e6626863a2d324883f338805d023abbbbed50d96f14b8aa74" } diff --git a/packages/crypto/src/networks/mainnet/milestones.json b/packages/crypto/src/networks/mainnet/milestones.json index 6154db5dee..3f878c644e 100644 --- a/packages/crypto/src/networks/mainnet/milestones.json +++ b/packages/crypto/src/networks/mainnet/milestones.json @@ -6,10 +6,10 @@ "blocktime": 8, "block": { "version": 0, - "maxTransactions": 50, - "maxPayload": 2097152 + "maxTransactions": 150, + "maxPayload": 6291456 }, - "epoch": "2017-03-21T13:00:00.000Z", + "epoch": "2018-01-01T00:00:00.000Z", "fees": { "staticFees": { "transfer": 10000000, @@ -22,17 +22,30 @@ "multiPayment": 0, "delegateResignation": 0 } - } + }, + "vendorFieldLength": 64 }, { - "height": 75600, - "reward": 200000000 + "height": 151200, + "reward": 800000000 }, { - "height": 6600000, + "height": 4000000, "block": { - "maxTransactions": 150, - "maxPayload": 6300000 - } + "idFullSha256": true + }, + "vendorFieldLength": 255 + }, + { + "height": 8040600, + "reward": 400000000 + }, + { + "height": 15930000, + "reward": 200000000 + }, + { + "height": 23819400, + "reward": 100000000 } ] diff --git a/packages/crypto/src/networks/mainnet/network.json b/packages/crypto/src/networks/mainnet/network.json index 6eb5620e77..67101c2566 100644 --- a/packages/crypto/src/networks/mainnet/network.json +++ b/packages/crypto/src/networks/mainnet/network.json @@ -1,17 +1,18 @@ { "name": "mainnet", - "messagePrefix": "ARK message:\n", + "messagePrefix": "PRSN message:\n", "bip32": { - "public": 46090600, - "private": 46089520 + "public": 70617039, + "private": 70615956 }, - "pubKeyHash": 23, - "nethash": "6e84d08bd299ed97c212c886c98a57e36545c8f5d645ca7eeae63a8bd62d8988", - "wif": 170, + "pubKeyHash": 55, + "nethash": "14b55c1de06caa015362d59ad97a144bc3c9fc2b50ece84b78d13ceaeaf7d8fb", + "wif": 80, + "slip44": 111, "aip20": 0, "client": { - "token": "ARK", - "symbol": "Ѧ", - "explorer": "https://explorer.ark.io" + "token": "PRSN", + "symbol": "P", + "explorer": "https://explorer.persona.im" } } diff --git a/packages/crypto/src/networks/testnet/exceptions.json b/packages/crypto/src/networks/testnet/exceptions.json index 0967ef424b..3451f6439e 100644 --- a/packages/crypto/src/networks/testnet/exceptions.json +++ b/packages/crypto/src/networks/testnet/exceptions.json @@ -1 +1,6 @@ -{} +{ + "blocks": [], + "transactions": [], + "outlookTable": {}, + "transactionIdFixTable": {} +} diff --git a/packages/crypto/src/networks/testnet/genesisBlock.json b/packages/crypto/src/networks/testnet/genesisBlock.json index a09a1fa3a0..5606d6c482 100644 --- a/packages/crypto/src/networks/testnet/genesisBlock.json +++ b/packages/crypto/src/networks/testnet/genesisBlock.json @@ -1,2210 +1,896 @@ { - "version": 0, - "totalAmount": 12500000000000000, - "totalFee": 0, - "reward": 0, - "payloadHash": "d9acd04bde4234a81addb8482333b4ac906bed7be5a9970ce8ada428bd083192", - "timestamp": 0, - "numberOfTransactions": 153, - "payloadLength": 35960, - "previousBlock": null, - "generatorPublicKey": "03b47f6b6719c76bad46a302d9cff7be9b1c2b2a20602a0d880f139b5b8901f068", - "transactions": [ - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AHXtmB84sTZ9Zd35h9Y1vfFvPE2Xzqj8ri", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "304402205fcb0677e06bde7aac3dc776665615f4b93ef8c3ed0fddecef9900e74fcb00f302206958a0c9868ea1b1f3d151bdfa92da1ce24de0b1fcd91933e64fb7971e92f48d", - "id": "db1aa687737858cc9199bfa336f9b1c035915c30aaee60b1e0f8afadfdb946bd", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AZFEPTWnn2Sn8wDZgCRF8ohwKkrmk2AZi1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3045022100ffff4e9ba62e5e3beb37deee052824da83c4030925bce09f190151652d0669b8022056a432e56a2e1b026d4b54f6c34ce88a0c9cebdccc730659c03449fe878c66f8", - "id": "0762007f825f02979a883396839d6f7425d5ab18f4b8c266bebe60212c793c6d", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AJjv7WztjJNYHrLAeveG5NgHWp6699ZJwD", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3044022001a6326e5d1eb06d0ba1fa39446bd6d56ea45f0c269ebbce5dfc6a649277cfcc02203b252d3a6ef2b22349d9d0a9110ce28a199c39dc8b911edfa82c297a02009d07", - "id": "3c39aca95ad807ce19c0325e3059d7b1cf967751c6929035214a4ef320fb8154", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "ARAibxGqLQJTo1bWMJfu5fCc88rdWWjqgv", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "304502210084d855eddfe616cf1dc238b19226c7959c2fc4027ae2e8aea6fd8e9eb8928e6b0220440f980e40c1c56348782fd69d49a96944df7ee5b68d18028600e0e7501d4000", - "id": "9fdf6ae86f7c005b3b7dc1b9fb6411219407ecaa93adff85fdb61710f5121638", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "ASEJEDLfTxy6upQDWTuYucoVwMUcmhSGhp", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "304402205438b8b9058bbde5d30794e7681e400e52b5fbd22324c5b6b521f97bc8b8aabc022000fe04d7afbd2e668b1d4576988ed596dc92251e33efebc081e2cba14ad5a898", - "id": "1d7c68087c875d7ce555b2c3e71e1d91a1ad62d0c2497efe3cab91415e634041", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "APRiwbs17FdbaF8DYU9js2jChRehQc2e6P", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3045022100b2e634a95b011a68489870f003e4bac4a4f0578bfdc6b9f645c934016c2c0463022022cd4ebf276dd627d98be4b697bae2df10b86d94e984da2eb7e011b08d6dffd2", - "id": "0c993e115ba26981b0be9d22e7c4a13b0f106e0cb472f9d34eabfc8e414dd528", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AdXbS4GKvV6TZVHrNzcYSQKfpenQnFGTxK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3045022100f965e5c280acb22d1cde405223fe9a6fcb765844adbc5321b17a268924e1f597022043d31b1edc5fe0cf60a960d84e3528472cdf34560c9463979043a409f37e7f29", - "id": "c279f2eb1f9e6e7d4b0ba7a98233a0f1a2536231976c99f56f64b248eb06a0c1", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "Ac9dCo9dFgAkkBdEBsoRAN4Mm6xMsgYdZx", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "30440220715463c316a75959dbfb6a59a013fbf914bef1ff739ac8000d49dabbf5118df9022019345ae1c34173dc214bae82f3cfbf438092f0fd2d277acafe3e9deb644b1a3b", - "id": "7e2fc9ecf23e909a3d0fbecd615445a0eed8c2cef18e01b1492d63f616f5d87d", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AReCSCQRssLGF4XyhTjxhQm6mBFAWTaDTz", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3045022100fdd8aff26dceeb5abb6e5e8a8f468c8ac1997a587225298e3d8135d57dadf4dc022072ab80a81b301a162ed5cfa67d213d5a3980185088632f5f592351aff8aa0e9c", - "id": "511c0e1076104743f98932f8e7720bdb3f1539134edadd331914fd9ece1ebede", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AV6GP5qhhsZG6MHb4gShy22doUnVjEKHcN", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "30440220635e04ce278870f17fcd1883aa26c568e63dfbdd302add39aa30fd3637c79c2c02206fdd9e7b1f4d238a97d26ef1758927e2d39f121687490f2bd79831e36afdd43b", - "id": "0768d5016c53d884e3d68a09d1bab0d730b7067c71ef4ca1c4d61b3815f5ff66", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AdWRsk7Lbo97jxGBKzLAFwevVHbqVbW1Cj", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "304402200b1dac57ca6565ac31afb99686f2e0f0e8dc219b9860b295ca5444a1663cecfb02205787393561fe407449af4aaf2f621db9e4d3f11c7438666cd694d495c0a0c41f", - "id": "1aeb50080ea118165e5041f7a897974c2ed1ebde08add85dc78cc7cf73566a91", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AaUgne8txmQB1iBboiFVLVHwLaYChZiFVA", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "304502210098dea25eccf31ce6f874a9528578805aaf07be8b41f1571865793f9e3e6e3c97022033ae9c73dad44c01fe6362665fccf63bb1a0ae8e26f77a1cf60b67dc96b05343", - "id": "254f0f4fa277cc651a746d6ac371eb27afc3ea155ba060552dd26b8e83d17b72", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AG8kwwk4TsYfA2HdwaWBVAJQBj6VhdcpMo", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "304402207f4bf346aac501e766156818089fb16905a9bdca69ff6d5a55ba918a08afc7ab02200ec2c25cc4bb30e2c176d55630d8e2679b899c14ab4ba43c3d62955dd940425b", - "id": "e5ebb02e8e8a6708e22ee5ef99fe1dd8b6eea1095be6b772aa21bf63cf7ade5a", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AFyf2qVpX2JbpKcy29XbusedCpFDeYFX8Q", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3045022100a0bbc15bdad648bb9b439f1d34b12b853442d1cfd4ce7f569905082801fa58e8022036b4e73edf7ab7226f8007233f77b1d497cb6b4736f02721bf1b399312ebe114", - "id": "8a686b21477b64dfd85f08f8598a0f121ca1c7d65ccaca9e42326c75fb5f3abb", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AW8n3yvSAqUJkyfcG5u3bgRxsNKzXYPamN", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "304402205d77dfcde527dcc6669bcb01c27b92c1a6399e35ebac9e69415645f596ab1d2802204179497bfd952f44d5f9e295b2a3219a290a4a82841c084a18553b7712e26415", - "id": "21175347e2acfabc09a7593aae0682e39fe7152199a90561c11125f525211243", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AZuvQC5WuVpPE9jwMCJcA28X5e7Ni32WY2", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3045022100cf77c16df9185727ff717b71a94f8b29ceeae1e5bb3a28da8cef9df5bc63b7c202207bca394ce9ebd344a548e5a5697f672dedbef640dc1f9105f7c063287bcd1840", - "id": "ce1d9b7377551f36568127f5b635b5443f5a58abba6566b50a8d4d7b53c8a874", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AHysG9CfbXvHtxev9eziTK8WUbnFKKLFR8", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3045022100eb8daebb5484f3b0a738c9344fb28298c596f9486963f8fe36e2501ee6876f2a0220559df66986dc9a9a8e76982ef85f907c62745757990c69f0b17b6ae5a7ca4719", - "id": "b56702f5eddad0d8dbbb33b6b1ca3e07e4740def9c5dd2aaed9a70b90a4e31b7", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AMfyf9iRjXiKNcLQVTUE9oCESUPzmQ6iUT", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3045022100d088e9bcd78978f2d67e7c7bccecfb73ddd0d1a2dad5b039390812320355722d02207affe83d815f04f6b11abf98eebe0488bfb87f8cd6513d44b829008ed1c15ceb", - "id": "a73c053c42e83a83498cf58e5b077b31443e265ddf8228081cb17a36bba366ae", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "ANRNMPjQjJGVsVbyeqwShcxKTidYJ2S1Hm", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3045022100db16a8e9682f07efb607bc7c75b654646ff449761ed146ab9358e69d29fadd7f0220436554ad78db0e04ae5b573258e2c8067848e89b55a6e8e1e25011a43882a643", - "id": "2dccb8b44ad2e598673628fd9d74e336b467a0c941d5e257dceb85c8e0a0000c", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AH39inbLsUBhC3k29NcvQP3zKZWnsQksvA", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3045022100b03738eccce8ad0b8ac0a656119c2cdd202089c5650d8e1486bd13eb9c3158980220059079900c7fdc16e799c50dccc074726fbf0068044462faabdf1e73f9f9bc38", - "id": "b2cce30021d139f97925807da796722bf4d5459442523823388c259ca5ad73db", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "ASqzCDRS5cTBwCmC5moQ34W4QZhtrj4pT1", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3045022100becb49fe5edd6806d5ba6eddbbb34ca8eaf3a12dba123d1610b2b120ca8bd017022072972992ee0ca0f319ae754a2a5a10d715a08b23f8239f9d6d59774f790543ea", - "id": "9e4841f43ab355be7a4f93b09f3d82c17065fbe25387dd6c5eb4e2692ea05b0b", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AeooqGMJPE5UWRPkKW6kgLGZu3898vcLPV", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "304402207f1a3fe8c5aa7a77a58ed35c34f128b5df6fba89aa918af35eff432be7d1f8e00220460d4f2a457e1a477974157e33bf2974de6588d56e59729ae980720e9794827a", - "id": "2c7ca823be21724a4876de632dded3b9afca45df357819ed028488128d85d29e", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AWHGbGaz5FgvyChuAfWFmKY2LsbcwqPYL9", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3044022067266dfe9d8f2550b590e1eae2f73d28c6b80fecb24c3eb1b4539bc864b3b4f4022031e5122145c35874c0c48673d088e76fb3e11c308ffe9d5dee6431d3441d627e", - "id": "a91119f04e2201184761f7fdcb26e4aa81c7e1076cb11a58a422d351241d4e4a", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AWtD9W5LCv2TH3VcdzbGQBGaJBwvbzZNDQ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3045022100b970ec89927de0cb7805e614a742d42c2967db5a9c68d0892956dc89d68ca7d1022067fa30265dd2e1a2985980be2bf876748a7a8c7f3cde0382265b601fa658dc17", - "id": "94955e6bac6269fbd19e92d2292ac947225fc6f68c6216001b528596a961040c", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AJPicaX6vmokoK3x8abBMDpi8GMPc7rLiW", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "304402203671b82ddf8a824b8e5aac8bc28be4aef1c00aca1097d14ec1a55003d7a3f28d02203aacb6e7517e916478432b81399828ba7425183ce0fc43feb361bcf345fb0519", - "id": "df563ee9822bd3d7aada600d4800952743ec64fafdc7697428d7a19a60745885", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AbfQq8iRSf9TFQRzQWo33dHYU7HFMS17Zd", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3045022100b77653317c93eb20ee19c71e64a7f9ecb985351bfb1fe351ac65a5738cb37ae202203d540395e1d55f87caaaa867afbfbaf98c553be0b4c7d1748418a76b0c258c89", - "id": "d21b6341e2b4be5ffdc3dd8fbcdf2c576ba02e2ef4ab5eab0e4bfc9da4e9e442", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AQBo4exLwyapRiDoDteh1fF2ctWWdxofSf", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3044022046239e39062a58925099b005888355b8cd6700af66972bf509a10123f9abdec60220202321ea74e56177606fc079d19c29851d832e6d00c93985ffbec3dba6f0d675", - "id": "df6bc7a17ad34f8e9faaa2646e8e5dd8bca35affba352537184f690e200e17b6", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "ARMEiPQE55CfHfR8WmosiFykTAPGYUyYJv", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "304402204eeab87f7ecc2097b85606b986177964f3ae777535f6fc0cf08a55fec587d87602203779d59903b8de63511e4ed0a7967bd85e9cb1fc9d84bbc5091e3caa87d8bd52", - "id": "5f0d5f0dff464d0ad587da5bc93e600a8e2657d359d0a1224bdd4ccc3b6f376a", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "ALHDQyTm7wALtwjmKwEejZjq7f6u6w5xCv", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "304402200a2b9d0f61066fa00a2a2882379aa8ee60e949bdc2a85103bbbb69ce3eafccd9022057364f349faceb3047fa95ada210c64fc4a81978d66925b37d3dbc21ede885af", - "id": "1b39e3702576e6ad7775e34d53e43210549d52a56b3f246031e6ba4121a66bf0", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AcmXmomxpP8NahbbFivq32QmLuKFkTkqRg", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "304502210099e568d3d0c1b48410e0b85c74d04234dacfb2fdf2b1d4b51fca1cfb3445347a02207a2509645aae54560762a37422b66ba4b3ee1c42de35d58c36d2f9d8fdea11b4", - "id": "0f21e53dbb1edb1cfb4c31bb675aa4672b452a03ec363a2b3300a9dda49e3be3", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "Aa3mWTMFTXeTJukUgpeihQLBYDHBzdpWZX", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3044022026cc5f2b588a86241badca73cd9c1686916d516b8c6c397c66a9d5bb6b5d4cd402204ab5a8c8589ee954bda4a116999d2a0e4ab0e3e96f0c7fe131d7c57b9a1ede43", - "id": "410826c255a23a78ac5c3aa10dd48132693bc955845af16c20d9c6f69b05dfe9", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AGqSC7M137ctKtkAjd3J1haCEWNfayXnuJ", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "304402205fedd8d3b5c8d69cdd7db5ca8e9e7c5004f6ba751e45eb1b85b26d9e89800a2402202be56bb2cd824bccf325b6b11432bf6d0ddb5ec97fcc121839ac2ebf884c7173", - "id": "ddb57d8270b2b6c876191c1e1c5974388b9fb3ae0980cb2245d8a7c426237f47", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AcATpmcMU1tmLDX7TzR3wXop4tfLFR21Lf", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3044022053cd42ad147eea33801b2b57388b33f633b4bfe2ad902190e12480522250d07802203066dc0d0c2ffacc4c74cca1e0187fbea1cef7e78a78666d2ec7e4e87ef546eb", - "id": "29e1aedf98935c369946c8dadb2d6784f9ab5ce8d73b9b4de2466c7757e2557b", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AZeX3qaqdU8iCebAKYoLMR2QkiuG5CffgU", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3045022100c10448b87e7176735c8ddfc8fb3c4d5d55c2d71d18b7ce3ab321209ec299fd41022013517a09e4b366ab386698286ec7bb20410bdfb7f6674fab25a739259083b297", - "id": "4cf04852529b5525f22cc540790e36e61ed36045ad1b5b788f61ebe42637391e", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AcFWyJRk5sRKagThYhk5e1jdkx3wzhL5cW", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "304402204cc1588b204ebc0c20f44a31ce53d15ab5e4d1f9c103c02dd4e4eaa1c33630b40220194b6e427b6def0783461cd8d765f97b105d048942be468be2ee9b0a2785d2ac", - "id": "35c6bc3f0799d9c79efc6515f232c58be0d03a3a797d066cba879eef4afaae2c", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AdT9FrWUksf99Lhkr9JGb8f2HLSg14kTqr", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3045022100be44f7ea12e2ee89245fb474643ec6c2c75afa00276826a4ecd6fca4cad5ff30022071a2c083b353a821345e4bbf74d98db0760b8721856572572cc3436ebdb8f08c", - "id": "45f75a349f3b4d73434c0f2ac9c291d5d07278b79e6eaa0d38d6e005f66c4783", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AJQuCRxeJpzkoGSBMXtmuRMYg9mtCb4qHf", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "304402202090f506e8f18fde70b87a3fd6c470a23e9e262f20ec6268dd59b6362e51a29202202b838c598b33c6317c998dc179fad2b660b8a72bfaf8223d7cc82414ab4c6af4", - "id": "a8d9034d1091a4dbe595647ad5f64ca8b243e7842301aee48f7eaf8b8ae98119", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AJRf2oWusRmm8QEiZuwvMg3qLbMxpd7BJ2", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3045022100be59b689a48e198267305f1ae7e116f69f7c360857ea0b1fa81db122278cad69022033436d24ec0103674522f0c559e2357f8696bd498deccad2e0f66b2cf7469538", - "id": "061cb438ba1216cfd5a0f268ce18e6f280557bc944d9aed3655e2bc5f08bdf51", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AWdRZPxQQvG1TP6hdxvQCn2t3skerq9Ky9", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "304402203b5d2aa7c4554d6d2dd6723043350df0199e6e7bbd9f21a1a20dbba8c63918cc022014a78064c5f9c5e2f43d3be36de2b5e2f17e9af557bb6c75e8d82d9f725d0188", - "id": "239f0640ddc3170a737ef349c07cb82b2493d207421b6f71b6b3dab856f16088", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "ALJVm7EYiMtc1JJDG6BupFw3ttRR6Yewej", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3044022005eb29ad4cf79fd4f6898de19459e15cc816acb0975e53530a202e69c29d0d4a0220686cf6e0c14779d6d68dcb9d16358c0e859094d2eec8083598b7bb5869478bf2", - "id": "25d8eef755cfee7cab0d7f9fbbea0fad6d5f906c432d997ae8ef1c49d23735f5", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AJv5ZFmu6fuugsdTZNi6ukPgptbCmdW4AW", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3045022100b93096a287d59545fa3a08593dfc740d9d47f3cfa3c4bd3c8ff8ef53d3a2e957022027eda62e47220774cf799f46916195e5a8b30015c56ceff4f4a1c10a918e3675", - "id": "aac25996e3be809ee88996b6b4063e2097d6306e77a067de8ebc8d7076a28d43", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "ALs933qA9Cm3caRDei4ZXxnzXexpXNem8U", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3044022017282aa4fac7b18e834abc3ca37b2f60cf989c26b12e2f2398a66cb907015a760220428218d39db812a22cc138acc7d5d4d2d5713f0546751c02d2c3fabecca0e724", - "id": "b040f86b75750b49c83ca7eb8f2a458f16b44789796ff306c5f942ca5f19164d", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "Aa4M1zL3a74L51f1AvEsLmBTsKLKrkRScU", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "304402205970d53cb0921a62bbef540dc33189b2313f3574e44f046097067e6991d63b1102200a356c87642cc781df661a1fee21cce354a144463d37053280e000e1b75da7a5", - "id": "25ce96f951d7b7d886ef487331125b3413f655f9c5ee7fb4691a728c3cbce18f", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AMv3iLrvyvpi6d4wEfLqX8kzMxaRvxAcHT", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3045022100aab0201c9d9a9641c11605d32353685cbaa051ecc276da1e6a3b309be9f20cf7022067aecbc7329bdf1770974e317a1243815511efa8c7af7801217a83c96d86eb0e", - "id": "285143b8b19cbde7c680b0f62ef51293e8f315c823ffbd97608c38c02045d831", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AX1M38eZC6TB1mzz33PxZBYBGrmE2zPdFK", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3045022100dc7752f6f8acaa3a1ee2ed1bed306ee04556b3866db92a1e770c4b970c7a932e02202d137b312342f9d0708704833b26b6611d0464c87df97049ad8b616483e9d1f8", - "id": "87b06fccbb63809e976b3405cccec2eeaa3694d5510203f04c0e60bb6c2c0020", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "ALiMCXj25VkGEAbj5PNbNez5NagZZJxLsy", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "304402205ccad5c77ea339f5e3f2b7900b4b1c409d3c8204273e89b6401314fb61f0d224022026a63fef86356de64fe571ff8488a951dcacab56e980fc044ef9f43b9d37439c", - "id": "5597ed52e4123756bea9307c09c916ff9d0f9fbce8d2e9a3a2ff719a87ad0966", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AeenH7EKK4Fo8Ebotorr9NrVfudukkXhof", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "304402207c91153f820f34228bec62772e0d78876bd3277912eacd866fe35b5c86a316c80220104529c6f786cb387ec1e3d5826271c837f0d0a6d0fa5731b9a5c6663cce7108", - "id": "d46fde78608fcc668246cc35336210b3c167ba55c82e91b0fd99df7e36872130", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "ARJWp8VJEieDA8h8YDiHq5LqU9vWcpWGG9", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3045022100acc0cf119c18861d3683bb3b0f6e209f2d62acfdd958f86dfbd35137ada814320220448f6f8adcd46204629b45a4a06f5dc7ccb4dbc2a1d702e107d91053847adf2f", - "id": "aa92faf5d80459b4e058dc8a8212608b589925052e22148384835ab687a4e875", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AU8hpb5QKJXBx6QhAzy3CJJR69pPfdvp5t", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "3044022055b6bbde5fa886db3cf1224a59f1fb43e850e2d9237db593368e1043698fe2c30220067dd20195e794af4152f1ff9e3ae4261698a86c54803ba1890bf176d97844d4", - "id": "432e67db0d5fc8c66376aa96c7324e5a1e6d00a415a9c8898b5e3bf25d8b083d", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245098000000000, - "fee": 0, - "recipientId": "AHXuTrYMxsdSvYJvRoBkM3kH8pS4QSq9i7", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "30450221009d6f38067264df8497d6888e4a8c316ec58ceba8a54c39ccb0ce261d114fbbab02200fae3f2f950f5c5e3387679f8ca341ec70cd90d0e32a30112f03cfb12cd9fc23", - "id": "9321e1b08faa544f592ad8dc7b60ff1cf845efcd28fedf8b445be3bda60434cb", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 0, - "amount": 245100000000000, - "fee": 0, - "recipientId": "ANBkoGqWeTSiaEVgVzSKZd3jS7UWzv9PSo", - "timestamp": 0, - "asset": {}, - "senderPublicKey": "035b63b4668ee261c16ca91443f3371e2fe349e131cb7bf5f8a3e93a3ddfdfc788", - "signature": "304402200aed5a4102bdafda00fda575294f149b393a798c510af8ba877b8c2d7ec8051e022004f7487c4f728c633aee5baa62ab0017f4b91cf2f494eb1c4cc9addc3e9155da", - "id": "0bbc9340798a18a81109bdfdbee9c9003f20a586dd9f80a39507c84588c1b4b1", - "senderId": "APnhwwyTbMiykJwYbGhYjNgtHiVJDSEhSn" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "0377f81a18d25d77b100cb17e829a72259f08334d064f6c887298917a04df8f647", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_9", - "publicKey": "0377f81a18d25d77b100cb17e829a72259f08334d064f6c887298917a04df8f647" - } - }, - "signature": "30440220072124721ba7c997f7c29ad3d4819515fae7a67be2bc395cb73f114eb8d4abe60220523ac295e114de30ce8a4300f4670db91ad2abe1268460e6ad3463fbe9834b84", - "id": "d2e70f9d2de57240571905aa81db0b6883e27a83be2422530722d76b56e63ecd", - "senderId": "AG8kwwk4TsYfA2HdwaWBVAJQBj6VhdcpMo" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02ad799c6bd670746892bd4331e1aebada26a2cc3ccaf0fde1e94942b20066b05a", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_18", - "publicKey": "02ad799c6bd670746892bd4331e1aebada26a2cc3ccaf0fde1e94942b20066b05a" - } - }, - "signature": "304402204b93b06e08e71e3317f9426a1d3d450d6293fdbf5a6b3043fce27b3ce65431e20220683609720ea1d7d921238ca8b5098d3d9c0caab7b1e26efe42a6aebbc095471a", - "id": "8695bcb906f5fd81d858794f7d90447aadaa38418d312e33115a81e856b34d12", - "senderId": "AHXtmB84sTZ9Zd35h9Y1vfFvPE2Xzqj8ri" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "036f612457adc81041662e664ca4ae64f844b412065f2b7d2f9f7d305e59c908cd", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_47", - "publicKey": "036f612457adc81041662e664ca4ae64f844b412065f2b7d2f9f7d305e59c908cd" - } - }, - "signature": "30450221009711559a43005c808113a1e9a01b1665495ff4bf30d635f7d98c752ead4cc3fc02207879e2a939914effe2b5c80cd515c4b3ff77a071b707c85c4444481878803db9", - "id": "55853d2d2a98def00c5ab842866a44d1db91678a07b6dd63d062508db28a00a5", - "senderId": "AW8n3yvSAqUJkyfcG5u3bgRxsNKzXYPamN" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "026c598170201caf0357f202ff14f365a3b09322071e347873869f58d776bfc565", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_5", - "publicKey": "026c598170201caf0357f202ff14f365a3b09322071e347873869f58d776bfc565" - } - }, - "signature": "3044022025ba51a588253524557547ec492d71bd485fe5b291e60eef681c39eaf8ee781702202bf24c3d295c7a2c9aed97a79fb835506797dcfe7e7a2853e2578e7773c7e134", - "id": "553298aadf692c9c5d0334c307dd4ac0e277a49ed165c97ce1362f8ec639ee3f", - "senderId": "AdXbS4GKvV6TZVHrNzcYSQKfpenQnFGTxK" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "0287a12b336fc781f2621aeb703ae47feca4d3ba6f30625f09ba03d225be6ee2bb", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_19", - "publicKey": "0287a12b336fc781f2621aeb703ae47feca4d3ba6f30625f09ba03d225be6ee2bb" - } - }, - "signature": "3044022041291ba10ad30fb9ebcb0e13902e92d85e2c3e98493b6d369d7d1e70e8474e31022009083444460c415eab6b4beed9e0206eb0733bad5d2a476af4db4f5b5e74b835", - "id": "90af927db7b258538c8e21116b5a31418c88ecc163628b2b65fac92a5a949b14", - "senderId": "ARMEiPQE55CfHfR8WmosiFykTAPGYUyYJv" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "0311077c86a98b67850e7ed2c81775d094cf81c6991082ddc33fc7be5347dc765d", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_42", - "publicKey": "0311077c86a98b67850e7ed2c81775d094cf81c6991082ddc33fc7be5347dc765d" - } - }, - "signature": "304402205d4111c87874e696b8f4b8897d0dfe68fabe4ad5c5769026c6ecdd04f09a1e2f02207b9c8a2a16b50164215eb1efea6d5d9f4e693cbb7eec8535e526cf8ba68bb796", - "id": "8a920ebf5255a102d0c9c5fd720e0d36a6a3539991a2267442facf1fea2d0b86", - "senderId": "AcmXmomxpP8NahbbFivq32QmLuKFkTkqRg" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02f7acb179ddfddb2e220aa600921574646ac59fd3f1ae6255ada40b9a7fab75fd", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_10", - "publicKey": "02f7acb179ddfddb2e220aa600921574646ac59fd3f1ae6255ada40b9a7fab75fd" - } - }, - "signature": "3045022100f15ff048872020d9efc561b8c837f542d54d43b9b071f7a6cc09643c6d4180f002207d0e82153a30b66f43fc4cb4b9b3093bb3d5dfd70f96928c8780c838b1448c19", - "id": "30738f376aa40fb3c8d8849a5dc698786aeb1409fa801c18729f8da624631391", - "senderId": "AFyf2qVpX2JbpKcy29XbusedCpFDeYFX8Q" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02c1151ab35e371a333e73f72e9971cfc16782e421186cfff9325d3c3b9cf91751", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_20", - "publicKey": "02c1151ab35e371a333e73f72e9971cfc16782e421186cfff9325d3c3b9cf91751" - } - }, - "signature": "3045022100babb7410d09215def98078bbab6b5e5690c2ebf54960d94527226ed3925877320220342576d1d8fd2d2fe3b6974cab48a2e16b4813f022b341b32f88e13f572bf060", - "id": "ccbe1c27eadc1b3b33f3f87f645be4f756021ee3d4c96f4f094e1f82d5728a3a", - "senderId": "ALHDQyTm7wALtwjmKwEejZjq7f6u6w5xCv" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "032c51f895ccdafae44e68baf283c50605d3f7dbba1c48011c6577383791f4a374", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_49", - "publicKey": "032c51f895ccdafae44e68baf283c50605d3f7dbba1c48011c6577383791f4a374" - } - }, - "signature": "3044022032f2c350cc1319f5838d6880e91b49ae0438fb3a626ed9ab5e27ce8788e3347c02202cca18567c8491e0feea8a5f078e28605029346c509fac0c0a192e934f8c5326", - "id": "f99af0fbb4d65c2c3f2c1c558f0c0c0eac2724942802fcde02fa6da1d3a9000c", - "senderId": "AReCSCQRssLGF4XyhTjxhQm6mBFAWTaDTz" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "038082dad560a22ea003022015e3136b21ef1ffd9f2fd50049026cbe8e2258ca17", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_3", - "publicKey": "038082dad560a22ea003022015e3136b21ef1ffd9f2fd50049026cbe8e2258ca17" - } - }, - "signature": "3045022100f0cb5d885ddf3bd4a58837f9b86486da4171652a5eb39228dfd0ff9d34d9c7c602202dc6e3d268d745a7e8633311a337ec097382342049672880c7c2215cf58e5da2", - "id": "2dca03aed08533585d8bc609da5deb9f17ac9be5a8352769d7ae63d0db16ff59", - "senderId": "ARAibxGqLQJTo1bWMJfu5fCc88rdWWjqgv" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "0395ff46d07f197dd4d4cb5dbb46e164c1e7ca9896c33827f9d6f8003ea167917a", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_21", - "publicKey": "0395ff46d07f197dd4d4cb5dbb46e164c1e7ca9896c33827f9d6f8003ea167917a" - } - }, - "signature": "3045022100999f19fbdc9a12eebbb8c748a4cfc6c91b2233f333a09cddfd49dfeab6aaf38602203d8dc9d1551d400572a88ee812f51f897f8b35508713b789b2c1bf6dd0e88945", - "id": "5d7e51d57b5914ec201ab65a019ecdf651c4f267cbffe403fd2170bb95145f9d", - "senderId": "Aa3mWTMFTXeTJukUgpeihQLBYDHBzdpWZX" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03343930630f8235c2b3ae9ba013dbecd4d8bfc999d34bda33e18c8caf43772f1f", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_41", - "publicKey": "03343930630f8235c2b3ae9ba013dbecd4d8bfc999d34bda33e18c8caf43772f1f" - } - }, - "signature": "3045022100e86e648add940a1e637e32ea9187497c281b843da09597e62d0c927d7f43235102200479f64ae63abb55e338f9ce1073a5c46907f7a2a82ea6f9bd9bc29811683515", - "id": "eaeed4133da26612c53550b6572722d8c3380d0a2344da1bd270eed1ea91fdf3", - "senderId": "AcATpmcMU1tmLDX7TzR3wXop4tfLFR21Lf" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "0345ef2a1e4f64707044ba600efdc72aaad281c5a73195f930527c54d7cc891904", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_11", - "publicKey": "0345ef2a1e4f64707044ba600efdc72aaad281c5a73195f930527c54d7cc891904" - } - }, - "signature": "3045022100bc3b2ebc58a92bf38672206e8311e7ef0e54912abce7338155b11e7d191b0b5d0220765a568c1fa4665c0ace6b4bd3b7ba0f8329e2f25af7a3cc0d78b2ea398084c3", - "id": "bb91e78e43c59a19ac06c015d8a7ef09d7c5b274c9f98505e5a978027354b71c", - "senderId": "AZuvQC5WuVpPE9jwMCJcA28X5e7Ni32WY2" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03a46f2547d20b47003c1c376788db5a54d67264df2ae914f70bf453b6a1fa1b3a", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_22", - "publicKey": "03a46f2547d20b47003c1c376788db5a54d67264df2ae914f70bf453b6a1fa1b3a" - } - }, - "signature": "3045022100aae4868ab75a33e4e77f9bf6c53b920c5e7c523a7cfe271d1afc472655f3d6a60220499f1bcb79bc0fa830dfa939898db5c9fa8571a2788c8de0da7e550bfc818bcc", - "id": "a6e687647dde9c1db68690090afc4fcf11833dd35fff3186b6b709a1e7d24260", - "senderId": "AGqSC7M137ctKtkAjd3J1haCEWNfayXnuJ" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "034776bd6080a504b0f84f8d66b16af292dc253aa5f4be8b807746a82aa383bd3c", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_46", - "publicKey": "034776bd6080a504b0f84f8d66b16af292dc253aa5f4be8b807746a82aa383bd3c" - } - }, - "signature": "3045022100c0cf1fc54705c13f70fde39c55a1703a4c612b8a919379cd5b1ada464c7cc8de022074ee62490a184010ad2418d3177ff2ab03d02d2589000176312b90422b1bd64b", - "id": "70262b0eec3ab5a60a736eb8a628cb600eae7522464a49791c0bf26e82318ec6", - "senderId": "AMfyf9iRjXiKNcLQVTUE9oCESUPzmQ6iUT" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "0363550e2a3fe2153526effd4302045fa2c3027d0d9b30b19821a4870c8cb134bc", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_6", - "publicKey": "0363550e2a3fe2153526effd4302045fa2c3027d0d9b30b19821a4870c8cb134bc" - } - }, - "signature": "3044022045db446b109215c6d3dfb0ee5869154a8a7624376c3760eec4fadc75a29033cf022003e524d64f3ccd0c6de4ca80a7327e2c47ffd16b3ad042bd25a02f5f64500ab7", - "id": "56048c449694964bee3d367609a7bc46c8da20f66878c09c01dcc53c3abd932e", - "senderId": "Ac9dCo9dFgAkkBdEBsoRAN4Mm6xMsgYdZx" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "034ff439276ab784098e66dca4075111008448a3b3519c10701bd2d1600ec1203a", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_23", - "publicKey": "034ff439276ab784098e66dca4075111008448a3b3519c10701bd2d1600ec1203a" - } - }, - "signature": "3045022100f8f69f2957781ed02d64983744c8e51fae613ebe5bbb330d4f509bdcf4fc6b6602205568ad1fd840e01ec26a24ac9a0ff093e978172da55d494138d018a45eb67893", - "id": "e15dfc4e18106480083b3c6211349fd9c803e334e9ba5eb62cca19ae3f57d8e7", - "senderId": "AZeX3qaqdU8iCebAKYoLMR2QkiuG5CffgU" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "022b80e0d314928d93e48d1fe02190378384215237a5d42a86bc91580ba8c88689", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_40", - "publicKey": "022b80e0d314928d93e48d1fe02190378384215237a5d42a86bc91580ba8c88689" - } - }, - "signature": "3044022021eeb9e1db8915a9adb99db72972cd17fc7b5b377fc532ac2c9deffcb2707edf022068b9e08f45bbebad89295f520ad40d7786fe64059d45df95551576e3acb736d1", - "id": "2bd0f888ccdeeca24a0134e3c1bf729582d284f32ee000d97f1417f1349a6594", - "senderId": "AdT9FrWUksf99Lhkr9JGb8f2HLSg14kTqr" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03c74d53dcfef0d79f249a812e95c1a58040b769867df036639f0c107d3b577b12", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_12", - "publicKey": "03c74d53dcfef0d79f249a812e95c1a58040b769867df036639f0c107d3b577b12" - } - }, - "signature": "3044022040a9d0975f747df19792211546410d7c735aff2d26f367d1bf9233ffd1d993d702206890c66d4d0eb5de37df088c082d8fbd8da043817b48a76bd5d70f1e3f6b6529", - "id": "f75ac5ccd243e09fc9da2b3842a0654ca860d2dba5bb73866693a8a918937994", - "senderId": "AHysG9CfbXvHtxev9eziTK8WUbnFKKLFR8" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "039f71d74e13cd8c4b7e134ad46e2c28f1bc8e6eacaa9839b5bf59eef5cea06f95", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_24", - "publicKey": "039f71d74e13cd8c4b7e134ad46e2c28f1bc8e6eacaa9839b5bf59eef5cea06f95" - } - }, - "signature": "30440220550c0ab565ab2de649ca7a2aaf2975453a1e4ab8b0d392d69663c0c9b6b80b7b022039047d4d1bf4e9b167a95adcde0a5a8631aeca060dfd426da28a10d968fb3a64", - "id": "aa2ed932faf4832848356beaf87e5381ee56a1a84fb485ba975acb28f8fcf5df", - "senderId": "AcFWyJRk5sRKagThYhk5e1jdkx3wzhL5cW" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "030a9321ff83e384aef559e6030008c23a137e3b3c5d45028e46cccbaafce772b1", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_50", - "publicKey": "030a9321ff83e384aef559e6030008c23a137e3b3c5d45028e46cccbaafce772b1" - } - }, - "signature": "3044022038df37ef25928d1a04516e982c99f49cbdc193603f814b48ab3802153bdd352002204c918915a3cbfa305c5f898ae4bcdd75394b57460f85c80daa0999751d466c08", - "id": "d30a726e1bb8d199d8f44700bc999c9a0a1a8be86e4be6a15764ecd424f9db1b", - "senderId": "APRiwbs17FdbaF8DYU9js2jChRehQc2e6P" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02def27da9336e7fbf63131b8d7e5c9f45b296235db035f1f4242c507398f0f21d", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_2", - "publicKey": "02def27da9336e7fbf63131b8d7e5c9f45b296235db035f1f4242c507398f0f21d" - } - }, - "signature": "3044022028dd44b9609b0b599c15a257757fd068f9014e33947c77776a6fcbe71879271b02200b46fd8eb0827da6de13f5efd63b17f29e8ba4600e4a690ec31eb08bf2d9af33", - "id": "1410b8b5f15c05528013378251bf5da30e04c8a6b7ac0f729b527664cfbdfbc4", - "senderId": "AbfQq8iRSf9TFQRzQWo33dHYU7HFMS17Zd" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02be75b3862c454887da01b866972b4fb312e0b72fa7d5dda5c0e828c1f4d7f964", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_25", - "publicKey": "02be75b3862c454887da01b866972b4fb312e0b72fa7d5dda5c0e828c1f4d7f964" - } - }, - "signature": "3044022038edfe34f7b89b4e69ea8b94e3335063b60deaee28246932147f53b2525924a402205b89f5e3d956aa49f24f81e2ba3447c19bd5c026568b3bef73a7a7d5160ad661", - "id": "58d14b74b71586e18f0499a50004ec2e0cc2e5b56aa53f4cf57084030ff90fa3", - "senderId": "AJQuCRxeJpzkoGSBMXtmuRMYg9mtCb4qHf" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03827628ae32074e284bcd660aec4f0504ba5d401586cb9566c887dd4da522c1d5", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_39", - "publicKey": "03827628ae32074e284bcd660aec4f0504ba5d401586cb9566c887dd4da522c1d5" - } - }, - "signature": "3045022100bc1e477994bf4cbcdb5cbe2bd92c7d955a03adfe562f8e3bf04d2f62965e9f78022045512772d8453314361161b2bd2a39aa0a7fbb897a5a83f4c7ab54ced615b42c", - "id": "3ee53b3f1455ef0ddb52afe08854c9d87f42c7313babd3e05bb3ca4f94c495ef", - "senderId": "AWdRZPxQQvG1TP6hdxvQCn2t3skerq9Ky9" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "021770413ad01c60b94e1d3ed44c00e0145fe7897e40f5f6265e220f4e65cf427f", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_13", - "publicKey": "021770413ad01c60b94e1d3ed44c00e0145fe7897e40f5f6265e220f4e65cf427f" - } - }, - "signature": "3044022052fe00e8e9f05b1d890f6910beab0627c823eb2d5875b4b9813a33aed11edfb6022034a723b827ce0e73bfdc0f535b244ffc983f8d549ee72b4d432de90d658db72e", - "id": "4a3d204c2916c93360d7bb11390e355bc1a930e3cf503965a45253d65bfe928b", - "senderId": "ANRNMPjQjJGVsVbyeqwShcxKTidYJ2S1Hm" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_1", - "publicKey": "03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37" - } - }, - "signature": "3044022013b2798a4ab4d741850abac10d962360cd4ab6a47dfac7c1c806d6f9c3d810cc02202742414ad8a04ce679b445fcd040fb877bbfed3d2692b873dec8cb46c01c8c4c", - "id": "7d0c5a44a7517f6ad7a1253db45d58e85aa1c735a282a32f45d28efdb7869d7e", - "senderId": "ANBkoGqWeTSiaEVgVzSKZd3jS7UWzv9PSo" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02af50fcb5183b3f2c468fb4e75e573a6bf0a048a6fab095df6d70f9f91fd6651b", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_45", - "publicKey": "02af50fcb5183b3f2c468fb4e75e573a6bf0a048a6fab095df6d70f9f91fd6651b" - } - }, - "signature": "304402202c372b7b9679a8fe66f952a1d47d4327968d6e98770b215ada2fed6a8d87ed5502205a797fb511cfba557255dd37e028fb40981b7b65ad2ce8fe0e559a46eb274bf8", - "id": "70bfe97ae7452dc752ab4de0e2a0e81bd18bef07392c56e7a101257683d4d932", - "senderId": "ASqzCDRS5cTBwCmC5moQ34W4QZhtrj4pT1" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "022f2978d57f95c021b9d4bf082b482738ce392bcf6bc213710e7a21504cfeb5a0", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_7", - "publicKey": "022f2978d57f95c021b9d4bf082b482738ce392bcf6bc213710e7a21504cfeb5a0" - } - }, - "signature": "3044022058851712200f7386d6b3c188444f9c8f05788667649ec17c71b9e514206eb105022061e6a4bc4cd11599792e03298f95509893d56af54d51e9f639981045e754b974", - "id": "f6f90ff09dee5be7d8f3d58d217772df7a95865bf8609d7d5b0b673e9a5bc953", - "senderId": "AV6GP5qhhsZG6MHb4gShy22doUnVjEKHcN" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02589ffa5363e245f8068d823af8b721b6bf9742c17cdd7925bc9a1fefe66a243d", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_27", - "publicKey": "02589ffa5363e245f8068d823af8b721b6bf9742c17cdd7925bc9a1fefe66a243d" - } - }, - "signature": "304402204878d69a166e60e0a779c31fbc48c67b70d2e4aed1d63c60beb9f070963e2894022078c46b6687f23493a4c2ed39709a183a0f7352568cc9cc2c1f0d7bf0d809a4a4", - "id": "f68809e407d20a50029fe460d411c866b79c7e09c076dada768a38d81f184aa3", - "senderId": "ALJVm7EYiMtc1JJDG6BupFw3ttRR6Yewej" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "0232d790f7a6ac16f2581283a47d0dcfbb51ee100f92e46cea46a63a8a043cb294", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_38", - "publicKey": "0232d790f7a6ac16f2581283a47d0dcfbb51ee100f92e46cea46a63a8a043cb294" - } - }, - "signature": "3045022100d5576393a1dea704cf79a5d0bc2757a3a5e66e1055103b52157fca05fc5693ec0220522832ce0e31b779decef83ac8ce764930de927df9ae1d6f6f99a3312d99c90c", - "id": "2ec6c6f33f00431ef063fbb8a79fb90eadb13a79bf46e6e1df36dd9434314df0", - "senderId": "ALs933qA9Cm3caRDei4ZXxnzXexpXNem8U" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03d27324bd4829f57d549bdb273bfd666d88f43d8429ba9a42a4fa1c9bd1032a24", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_14", - "publicKey": "03d27324bd4829f57d549bdb273bfd666d88f43d8429ba9a42a4fa1c9bd1032a24" - } - }, - "signature": "3044022008a7d0bfe9c4c150566ddf701d08e84b4a5f84b07e3b1c91dde1cefa16d2a3c202200b787e898c0b2c68f4343e74f18ae7363f62b5f4ef2962386932aee09a9fa0d4", - "id": "e37b3efbf034bea4c852be7d7013978f8999eacc39549ceea775de197e14e8da", - "senderId": "AH39inbLsUBhC3k29NcvQP3zKZWnsQksvA" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03747096ce60f19e52e99f5d80ae1ddedf6fa88be4ff0669b33f75f3fd991cff28", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_28", - "publicKey": "03747096ce60f19e52e99f5d80ae1ddedf6fa88be4ff0669b33f75f3fd991cff28" - } - }, - "signature": "3044022023b6fbfa5f4482a4dcc34411846696052b1592786ca87243b7d3344fc9fe9954022035402fbca22691de2497552c743f0f68c7591edd1bd7954ab7639548fcd558a3", - "id": "08268f5e6c15cf146523ca928f24aca65b162f363593d927c66144ee5df297cc", - "senderId": "AJv5ZFmu6fuugsdTZNi6ukPgptbCmdW4AW" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03e59140fde881ac437ec3dc3e372bf25f7c19f0b471a5b35cc30f783e8a7b811b", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_48", - "publicKey": "03e59140fde881ac437ec3dc3e372bf25f7c19f0b471a5b35cc30f783e8a7b811b" - } - }, - "signature": "3045022100b3cad169f29a3a95995b87e1b50b35583c1bff91d69cfa236f58ce452491c579022026775f4ef50b50ecf6d78b530b4633711394983456e6a45ec227b652c86e3014", - "id": "ad94ee2ae94813a638b93909930c7cc631c364b6c8528b2dcd6fa8f69260cc2d", - "senderId": "AaUgne8txmQB1iBboiFVLVHwLaYChZiFVA" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03832487ab0aa9450a4c223999bf4311b7b65c50c06baa90d19d4f65c27bfeeb93", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_4", - "publicKey": "03832487ab0aa9450a4c223999bf4311b7b65c50c06baa90d19d4f65c27bfeeb93" - } - }, - "signature": "3044022007ac9ff2f272f3fda4947393b8688586cc8b2958ff5dc7931ac8f82c697bb76802202a66c28852bbff86ef17ac7f51e7eee52e611e825d91a9846f531ab3c3115c81", - "id": "76fb1984da9ef90fd7d588756163c97e00d3e4d6e9dfe78d9e3d3cb6d71ddd38", - "senderId": "ASEJEDLfTxy6upQDWTuYucoVwMUcmhSGhp" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "0308c0d019cd9c0c59618e3b86afc584078b54a85a025c9f30a8bdc82cdc8e1252", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_29", - "publicKey": "0308c0d019cd9c0c59618e3b86afc584078b54a85a025c9f30a8bdc82cdc8e1252" - } - }, - "signature": "304402204416e428688ad29928303fb2b00a26996cf79753fe70fb91c1f4635c644ba859022068ac5eab7d05f87c40ba36bd9dc149607c196778120c061698d7ab64aaade7ac", - "id": "0f442a91857061e87dd193b0b9f17a71719ca7e3da62841a63568713fc12b5e7", - "senderId": "Aa4M1zL3a74L51f1AvEsLmBTsKLKrkRScU" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "030d13c0a7f1433091a5730cfd7956175261bb9442d8c0c0beffeb3b5de32e5aa4", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_37", - "publicKey": "030d13c0a7f1433091a5730cfd7956175261bb9442d8c0c0beffeb3b5de32e5aa4" - } - }, - "signature": "304402206a248caa5949024202f297c38cee18845e344c5f140be74349787097d3b0a33c02207ac84336e02592bb5e00dcd0c490d30eb856b34177ab9ac03410d82a355a7b0d", - "id": "eed30a45c350fdffc5877458f7fe29f28dc4bf81aa1a197d003c9433148b71aa", - "senderId": "AX1M38eZC6TB1mzz33PxZBYBGrmE2zPdFK" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02c39e352d0f3c4ea19842a5bca3114b4247cd56da72157963a5873ecfcd824aca", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_15", - "publicKey": "02c39e352d0f3c4ea19842a5bca3114b4247cd56da72157963a5873ecfcd824aca" - } - }, - "signature": "3045022100c99336ce666cb4a6db3727a61c04c14d8746365f72280d9984441b7d2b568b5402201759e4f417f683743e1d4a14f8a7a215009321cdfa29834b2dbdbe54ee22c1d9", - "id": "ecfba14a58f9d79782c4f905646df28bf566e3e7d1f17b39df6fe6b52c11de59", - "senderId": "AeooqGMJPE5UWRPkKW6kgLGZu3898vcLPV" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02532c68cd0842fb86b2202c1027eafc741bdd581517047d9d19319e6741c54883", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_30", - "publicKey": "02532c68cd0842fb86b2202c1027eafc741bdd581517047d9d19319e6741c54883" - } - }, - "signature": "3044022070de7b4d4ce64bd605c9d008142544c2b113cc84df07ed1982e0adf3cf69f4520220211b01710a6533a270dc2814c7f968adf27eb6dbf437e7a72960b013b9651a0c", - "id": "36ce5323859a92f302f77f27bd08ee3485d720f55842ccba353a47ea96a964c2", - "senderId": "AMv3iLrvyvpi6d4wEfLqX8kzMxaRvxAcHT" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "022ebb110e9630377073d4f0e32897a5928a2c71f2941fb6d4b71251dbd62da98c", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_44", - "publicKey": "022ebb110e9630377073d4f0e32897a5928a2c71f2941fb6d4b71251dbd62da98c" - } - }, - "signature": "3045022100a7c271633ecbf3c6641c7db36913b5fa0ea521f400a4848edf024648f3d7128002206a271f8a88644062b64d856407af9567c0b2937d4a3d89a3b3d07edbd3a0f177", - "id": "e120452e7c56a9327b2be7dfd3dcecae193f2e2e772903008b03cdf00146ebd1", - "senderId": "AWtD9W5LCv2TH3VcdzbGQBGaJBwvbzZNDQ" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03691178f8610d0a295e650201b62345056c788d7f9ac7e8570b69c6c90091b564", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_8", - "publicKey": "03691178f8610d0a295e650201b62345056c788d7f9ac7e8570b69c6c90091b564" - } - }, - "signature": "304402200394b6545015bcf2d0f291de57a4197cb6ef57b2ad5fa37f05e8a220913ba83502204d0d2f2206edba54ada5b8e5afd194ba83dd1bf15f744258409595251dbe3ff0", - "id": "7d15eee8e4e3be3d2c44acd51b87a816bdb593565d4ac358dab24ae9c8a5bae2", - "senderId": "AdWRsk7Lbo97jxGBKzLAFwevVHbqVbW1Cj" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03807f9abe33fb390546bb5dcab075dd1136d0b98c54420c8c463c4ed3545161b2", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_31", - "publicKey": "03807f9abe33fb390546bb5dcab075dd1136d0b98c54420c8c463c4ed3545161b2" - } - }, - "signature": "3045022100989eb331951a13152aa03583efc765499e836c6fbafcafec4302b243ada8de5002203876fc4cf7fdeee4a095667e55a2fef84e5a7053e807b4d8e029883f0d578019", - "id": "baa686d521f95d265e7099cfd9ef14e0a9a92254dd94c16ce50c460bd013c588", - "senderId": "ALiMCXj25VkGEAbj5PNbNez5NagZZJxLsy" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "0282d4297584488b9c843face25f1816f95ccdd6660b1a2788fef259ed26a86e8e", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_36", - "publicKey": "0282d4297584488b9c843face25f1816f95ccdd6660b1a2788fef259ed26a86e8e" - } - }, - "signature": "304402202be177dddfad323302565a866d38a3e7939e0234b16e7dc02075cf258502eba302200928a139ec1a82b4609fcc1bd6d1d027ad050e93fcd2eff94181936d2d43e39c", - "id": "9fcf7ec6fe98ed94710e212226d8b90df7e7467d66dd4c5c9d48474388be3099", - "senderId": "ARJWp8VJEieDA8h8YDiHq5LqU9vWcpWGG9" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02dcb87d64ee2fdc6c2bcecdd841ad8e3b3163599214a818924fd433a8ffe7daf9", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_16", - "publicKey": "02dcb87d64ee2fdc6c2bcecdd841ad8e3b3163599214a818924fd433a8ffe7daf9" - } - }, - "signature": "304402207b4f8c09a728acedf3b6ba0632e12d01670c683215053e49dde8598954d85a9a02202a7d7930baa17c2134b314e47dd6c334c828f78e573a2bf92fcbc1146d630541", - "id": "c35e4b1e7a2435664fc0939251c2052633ebf4b51fb22d15e71bfcab85b26de9", - "senderId": "AWHGbGaz5FgvyChuAfWFmKY2LsbcwqPYL9" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "0305322487b6dfe8abe67f680bed2df70d92379a48840dd636b32a2c142baa1055", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_32", - "publicKey": "0305322487b6dfe8abe67f680bed2df70d92379a48840dd636b32a2c142baa1055" - } - }, - "signature": "30440220127d27312345e015c681adb799c1a87d16fb0caaabd5020b39257d567816b91c022018b2388f6d2d9afb3714d84ed102b3ea61159772786033c855947613c7ce7b5b", - "id": "0d682a3a9c252a674043bee5240e456dae2685d76fbd3bdeda6ff50f0c442fff", - "senderId": "AeenH7EKK4Fo8Ebotorr9NrVfudukkXhof" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02275d8577a0ec2b75fc8683282d53c5db76ebc54514a80c2854e419b793ea259a", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_51", - "publicKey": "02275d8577a0ec2b75fc8683282d53c5db76ebc54514a80c2854e419b793ea259a" - } - }, - "signature": "304402203d0ee691830e4d001553bf4e49b6d9669b3c959376f391410551c8adc679dac902203ba6e275bf6d543efd19d20428649f802d9396bb0967114a1f09c24827be1da7", - "id": "ec2373b0d609ae72fb400ffdfbffc59670ebbf1c15f59c0ac22a4030dae700e3", - "senderId": "AJjv7WztjJNYHrLAeveG5NgHWp6699ZJwD" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "02292be0bf30b21cf57d55b20f9092a70dc1d1b71f51a91d2ccb2d2f8d8abe6983", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_26", - "publicKey": "02292be0bf30b21cf57d55b20f9092a70dc1d1b71f51a91d2ccb2d2f8d8abe6983" - } - }, - "signature": "3045022100f2cf77b0510f589b5aaaf2b0027ffbce6ce8d4873cdc67dc8900865d156de3be02203c22e30945618683182f3d3873e6b3657e0900b062f866bab2705cd593669e79", - "id": "3cb2f0f7d05a515d4c5c873cbe96e33b1dfba1b7718e4548de7f9da54933b652", - "senderId": "AJRf2oWusRmm8QEiZuwvMg3qLbMxpd7BJ2" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03ba0fa7dd4760a15e46bc762ac39fc8cfb7022bdfef31d1fd73428404796c23fe", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_33", - "publicKey": "03ba0fa7dd4760a15e46bc762ac39fc8cfb7022bdfef31d1fd73428404796c23fe" - } - }, - "signature": "304402201e328159172d543d2225c247c6b728800c52eb724f67c0e919f6b7215e6bd7f2022075fc02fe0b14a1499c5602d87ca2c99d6e789beaceed2b9702060dece872d14a", - "id": "2fd77e744399c9632cc8f106c39237f201dafda976f1040235359f99eea3b832", - "senderId": "AU8hpb5QKJXBx6QhAzy3CJJR69pPfdvp5t" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "032b59ba992a9b8987b48606779d92101e4b332f6fcb47a4e61e9b49f2dbb45b2e", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_35", - "publicKey": "032b59ba992a9b8987b48606779d92101e4b332f6fcb47a4e61e9b49f2dbb45b2e" - } - }, - "signature": "3044022063903d82e8bd15a6741a298b9a6007d0dc3626acfe2f072c3b624ccbf91ce3360220486ba4cc5591d8aa31b77dfde025b61691dbaad0feabe13e840d26e40010c5df", - "id": "5baf9e318c9e4cb0513a21eaea27e51c849f95fddc963207fb07aa2fd2b9f9d4", - "senderId": "AZFEPTWnn2Sn8wDZgCRF8ohwKkrmk2AZi1" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "03d7dfe44e771039334f4712fb95ad355254f674c8f5d286503199157b7bf7c357", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_17", - "publicKey": "03d7dfe44e771039334f4712fb95ad355254f674c8f5d286503199157b7bf7c357" - } - }, - "signature": "3045022100efc1bc16e0b646da48f84822543b62ef5253bfa98bed6613f2d6d4634076e61802200ef243f9dbac7633a8819ce45e2a85d0eacfdc9a33a92bd3a03e90cbd312b823", - "id": "b4a959ad75f81b7fdbb957c90a3a63a6c5589e7819e2c455733a3a2b4b034634", - "senderId": "AJPicaX6vmokoK3x8abBMDpi8GMPc7rLiW" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "030fc33db3d3ab20d73bc6d52633a4f3cc26081ce307c89ab9fe493def7dba4b80", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_34", - "publicKey": "030fc33db3d3ab20d73bc6d52633a4f3cc26081ce307c89ab9fe493def7dba4b80" - } - }, - "signature": "3044022012e52a479648990bfc1ed12bf901cad865708ff45962c3724ea67967be4f9d0102201901525ed8dd090af6a2637c123afb304e9fd178794addcb88d916227e66887d", - "id": "6439f2308efe31ac52ad06ef1caa45b9abf6c589118b7997da6a287325ca36e7", - "senderId": "AHXuTrYMxsdSvYJvRoBkM3kH8pS4QSq9i7" - }, - { - "type": 2, - "amount": 0, - "fee": 0, - "recipientId": null, - "senderPublicKey": "034985f6f2167cc8c9df1204aaf6744bc97c0d7f3c07c43ee6c0978bc91b6c680e", - "timestamp": 0, - "asset": { - "delegate": { - "username": "genesis_43", - "publicKey": "034985f6f2167cc8c9df1204aaf6744bc97c0d7f3c07c43ee6c0978bc91b6c680e" - } - }, - "signature": "3045022100a0874d1582ce210081f7ab30e7f951dfb9ce8f512d237f8a8cbd5d85569ef3b902200f0053c05de3d6e5ada4e4cf1403a836779d653573c2f374055645cc954c4c4a", - "id": "b0733072e98d3d6afe977e32f3dd118c15e79212232417743ffb551dc2a2ba55", - "senderId": "AQBo4exLwyapRiDoDteh1fF2ctWWdxofSf" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AJPicaX6vmokoK3x8abBMDpi8GMPc7rLiW", - "senderPublicKey": "03d7dfe44e771039334f4712fb95ad355254f674c8f5d286503199157b7bf7c357", - "timestamp": 0, - "asset": { - "votes": ["+03d7dfe44e771039334f4712fb95ad355254f674c8f5d286503199157b7bf7c357"] - }, - "signature": "30440220158ed59156e0eef2d2b94a296451dffe079be701b3d74f0443ef43bc266b334202205a2c39f57abfcd279d568608b90884b3ebe107316aa7552eca35c743b318a47c", - "id": "ea294b610e51efb3ceb4229f27bf773e87f41d21b6bb1f3bf68629ffd652c2d3", - "senderId": "AJPicaX6vmokoK3x8abBMDpi8GMPc7rLiW" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AHXuTrYMxsdSvYJvRoBkM3kH8pS4QSq9i7", - "senderPublicKey": "030fc33db3d3ab20d73bc6d52633a4f3cc26081ce307c89ab9fe493def7dba4b80", - "timestamp": 0, - "asset": { - "votes": ["+030fc33db3d3ab20d73bc6d52633a4f3cc26081ce307c89ab9fe493def7dba4b80"] - }, - "signature": "3045022100898da9f693a458a6875344c6c4cb73069c4075904c75595ffbc665967d84b07002200f168aaf3ab1b52dfa74599394387dc4cf627a447fbc5a91000e9d251cdb20c0", - "id": "3639b5dc6d19d46d8254d941bf7ace0f3da8a7cf8a56361921b260820c7239cd", - "senderId": "AHXuTrYMxsdSvYJvRoBkM3kH8pS4QSq9i7" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AZFEPTWnn2Sn8wDZgCRF8ohwKkrmk2AZi1", - "senderPublicKey": "032b59ba992a9b8987b48606779d92101e4b332f6fcb47a4e61e9b49f2dbb45b2e", - "timestamp": 0, - "asset": { - "votes": ["+032b59ba992a9b8987b48606779d92101e4b332f6fcb47a4e61e9b49f2dbb45b2e"] - }, - "signature": "3044022055ed9a8b55ccb3bd0945a710269b6f243f1dbfaa28467d3218a17565eb0c962d02207d31561478f16d93a20f5454ad565dea24e8dda4ddc464cb011f4b6b360c4e81", - "id": "fe24509580cde0c2e2f49defedd3a0f7572d2f78f90b51a253b0d8cebd74c20d", - "senderId": "AZFEPTWnn2Sn8wDZgCRF8ohwKkrmk2AZi1" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AeenH7EKK4Fo8Ebotorr9NrVfudukkXhof", - "senderPublicKey": "0305322487b6dfe8abe67f680bed2df70d92379a48840dd636b32a2c142baa1055", - "timestamp": 0, - "asset": { - "votes": ["+0305322487b6dfe8abe67f680bed2df70d92379a48840dd636b32a2c142baa1055"] - }, - "signature": "30440220092f367f833d677e8d0609ad1df65f389c2c35d1501c71c245c2982e6a832268022018e67445f525613d6cb6ac0c9683bd0f55bd40d9c929165649414f083c9041f9", - "id": "6a76553db794ebf4d5f60a7d7d71cfe29f4dbcaad9610106fbc578cdc7167cd4", - "senderId": "AeenH7EKK4Fo8Ebotorr9NrVfudukkXhof" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "ALiMCXj25VkGEAbj5PNbNez5NagZZJxLsy", - "senderPublicKey": "03807f9abe33fb390546bb5dcab075dd1136d0b98c54420c8c463c4ed3545161b2", - "timestamp": 0, - "asset": { - "votes": ["+03807f9abe33fb390546bb5dcab075dd1136d0b98c54420c8c463c4ed3545161b2"] - }, - "signature": "304402203dc028b5013c36b03f97b111a8d7c05d0cd8e505b0b0d18747c0656c9b5cfe8102205e9ce8a78d1183b3e9880c69635d04218d94d17808bcc3f92e7af53195c23daf", - "id": "0f9d7e7708918b77afbdfffb63eef8fe87ba36e0131c88b44c1a7f81750cc025", - "senderId": "ALiMCXj25VkGEAbj5PNbNez5NagZZJxLsy" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "ARJWp8VJEieDA8h8YDiHq5LqU9vWcpWGG9", - "senderPublicKey": "0282d4297584488b9c843face25f1816f95ccdd6660b1a2788fef259ed26a86e8e", - "timestamp": 0, - "asset": { - "votes": ["+0282d4297584488b9c843face25f1816f95ccdd6660b1a2788fef259ed26a86e8e"] - }, - "signature": "3045022100a80ddd7c3adaf0e97ab938773fc78a716f3054d7e03afc1ddfcb5005badbd2810220231c0dabe2262149f994c939f9dc90d46b9bd7ca96b19aad6788cd3571e4f71a", - "id": "0ac77b2637fb25be42b3b60d1651bbbd788aeaba933a08ec4a417c7b4c54e087", - "senderId": "ARJWp8VJEieDA8h8YDiHq5LqU9vWcpWGG9" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AMv3iLrvyvpi6d4wEfLqX8kzMxaRvxAcHT", - "senderPublicKey": "02532c68cd0842fb86b2202c1027eafc741bdd581517047d9d19319e6741c54883", - "timestamp": 0, - "asset": { - "votes": ["+02532c68cd0842fb86b2202c1027eafc741bdd581517047d9d19319e6741c54883"] - }, - "signature": "30440220772c9cd8b96f74fcddc429d57d466eca6fc40fc211845f59eeb78cb027e116c5022004cda291587eb118d622de21333d2a5783969794b5b0101ad8b1044c7d8058af", - "id": "4b0dda465564d53981c0e36d73caec888e3523633eaa80dfb99a9c81b2604c7d", - "senderId": "AMv3iLrvyvpi6d4wEfLqX8kzMxaRvxAcHT" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "Aa4M1zL3a74L51f1AvEsLmBTsKLKrkRScU", - "senderPublicKey": "0308c0d019cd9c0c59618e3b86afc584078b54a85a025c9f30a8bdc82cdc8e1252", - "timestamp": 0, - "asset": { - "votes": ["+0308c0d019cd9c0c59618e3b86afc584078b54a85a025c9f30a8bdc82cdc8e1252"] - }, - "signature": "30440220406d54714b6425ae4553ea8bec75f31fe52e9b1a9b6f6897151253ab7f637d3b022040a2df4b69840f4d9b0b67658c75efdae8d8269780d4cc50d055fa63922dbb9a", - "id": "c7db9d36d97ff0168d0d670ec695e1dc786dfb93f4081586870c8793b50e5f17", - "senderId": "Aa4M1zL3a74L51f1AvEsLmBTsKLKrkRScU" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AX1M38eZC6TB1mzz33PxZBYBGrmE2zPdFK", - "senderPublicKey": "030d13c0a7f1433091a5730cfd7956175261bb9442d8c0c0beffeb3b5de32e5aa4", - "timestamp": 0, - "asset": { - "votes": ["+030d13c0a7f1433091a5730cfd7956175261bb9442d8c0c0beffeb3b5de32e5aa4"] - }, - "signature": "3044022018b7e51118ec83c985fa4eb3d7f0cf0655753bcbde7e82bac521665fb1c0ffaf02204e2ace460b2542db8c77e41d05d5e02fa5514b746a0a1e947256925846ed19f1", - "id": "c41f4cffcdd523f1718154d5bd5f4f0bec0376076b5f8dd340337e9edb4821ae", - "senderId": "AX1M38eZC6TB1mzz33PxZBYBGrmE2zPdFK" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AJv5ZFmu6fuugsdTZNi6ukPgptbCmdW4AW", - "senderPublicKey": "03747096ce60f19e52e99f5d80ae1ddedf6fa88be4ff0669b33f75f3fd991cff28", - "timestamp": 0, - "asset": { - "votes": ["+03747096ce60f19e52e99f5d80ae1ddedf6fa88be4ff0669b33f75f3fd991cff28"] - }, - "signature": "304502210088dbe249503da43c157485bfd4f2c95babfe4d0b8bbefe44afa52529b824a79e022045239b6a374fd9aca52c27171ee66b4863c956ae4085c9760d863b1902596c1a", - "id": "b1736ec6a1ea4c6d4eb278430a8ee214c88daefe296ba98530e692f8b7a7434c", - "senderId": "AJv5ZFmu6fuugsdTZNi6ukPgptbCmdW4AW" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "ALJVm7EYiMtc1JJDG6BupFw3ttRR6Yewej", - "senderPublicKey": "02589ffa5363e245f8068d823af8b721b6bf9742c17cdd7925bc9a1fefe66a243d", - "timestamp": 0, - "asset": { - "votes": ["+02589ffa5363e245f8068d823af8b721b6bf9742c17cdd7925bc9a1fefe66a243d"] - }, - "signature": "3045022100fcdf750a775e728a31691a1b38908a7f990b579da510959cc2c63442f5ffde760220316ebb051d9fecb2486771dd39921fb12675b6d46b2441dd1db3c42fad0a59b0", - "id": "069271456015c2ff842771775993b8afc3404bc070572eeeb0f2fd72d58e18dc", - "senderId": "ALJVm7EYiMtc1JJDG6BupFw3ttRR6Yewej" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "ALs933qA9Cm3caRDei4ZXxnzXexpXNem8U", - "senderPublicKey": "0232d790f7a6ac16f2581283a47d0dcfbb51ee100f92e46cea46a63a8a043cb294", - "timestamp": 0, - "asset": { - "votes": ["+0232d790f7a6ac16f2581283a47d0dcfbb51ee100f92e46cea46a63a8a043cb294"] - }, - "signature": "3044022034ce8f77ea9d0f5cf3a9135d7b72d0ba3b96ac6d7eaa3670e9956aef2c9a83cb0220626d1f269128f673a23f9993ce00ba78a08103e697298be29a4c8ee94f204e3a", - "id": "9a99bba8340e7ad4e05d8424a0977ebbde428d31ee066c9828bd06b42bb42a72", - "senderId": "ALs933qA9Cm3caRDei4ZXxnzXexpXNem8U" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AJRf2oWusRmm8QEiZuwvMg3qLbMxpd7BJ2", - "senderPublicKey": "02292be0bf30b21cf57d55b20f9092a70dc1d1b71f51a91d2ccb2d2f8d8abe6983", - "timestamp": 0, - "asset": { - "votes": ["+02292be0bf30b21cf57d55b20f9092a70dc1d1b71f51a91d2ccb2d2f8d8abe6983"] - }, - "signature": "3044022039ae1155f8b87a61c38b25cbbf30da6ecf6cfcc12b25c2e7fe576373754a41eb0220061a66a893129fbad5d48cdd19cf48b1a0d133dd2f3ecdc60ee7b87277e1f81d", - "id": "6c2c8926420ac269b50fa30127e0e791afb2131aff5821ca7aa80d38a0182048", - "senderId": "AJRf2oWusRmm8QEiZuwvMg3qLbMxpd7BJ2" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AJQuCRxeJpzkoGSBMXtmuRMYg9mtCb4qHf", - "senderPublicKey": "02be75b3862c454887da01b866972b4fb312e0b72fa7d5dda5c0e828c1f4d7f964", - "timestamp": 0, - "asset": { - "votes": ["+02be75b3862c454887da01b866972b4fb312e0b72fa7d5dda5c0e828c1f4d7f964"] - }, - "signature": "3045022100d0dac2b7691aa059b1048d7925a0c5d5099f6e9b0f2e321e6d4f128ab1b3272b02207e8c4f643f8f9d1c3f81f0cce6a698df2da2ab71d5b01042766bbe0f46f4a775", - "id": "9259193c5de72276ed7a99f9d507dd6ea9856411fda521074fb41a556294fdf7", - "senderId": "AJQuCRxeJpzkoGSBMXtmuRMYg9mtCb4qHf" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AWdRZPxQQvG1TP6hdxvQCn2t3skerq9Ky9", - "senderPublicKey": "03827628ae32074e284bcd660aec4f0504ba5d401586cb9566c887dd4da522c1d5", - "timestamp": 0, - "asset": { - "votes": ["+03827628ae32074e284bcd660aec4f0504ba5d401586cb9566c887dd4da522c1d5"] - }, - "signature": "3045022100d5496fec447367ab6b53956a8c40cd8566e050ebb3b92d2c0b2a9d09bef36c7402205e32367605372375801f7b9db39aaafb46ee763b1494f0aca144fb91f3415752", - "id": "2a41e5946ab0773ca2334bba9d3510184bdd258f1c651ff8ec95b7b64a01dc2e", - "senderId": "AWdRZPxQQvG1TP6hdxvQCn2t3skerq9Ky9" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AcFWyJRk5sRKagThYhk5e1jdkx3wzhL5cW", - "senderPublicKey": "039f71d74e13cd8c4b7e134ad46e2c28f1bc8e6eacaa9839b5bf59eef5cea06f95", - "timestamp": 0, - "asset": { - "votes": ["+039f71d74e13cd8c4b7e134ad46e2c28f1bc8e6eacaa9839b5bf59eef5cea06f95"] - }, - "signature": "304502210099249695dc38826e04c8fcffd2570b98c43dec4788cc6a19737ed0872f17ec3302205301f645d803ad5df4ab1a700446e28c7cd76153607f6a2d68ae9168d46f3fe9", - "id": "e5c09b0fb2c24c57a4dcef0078953093800329ab4dc8e16a9d9f68215b5acd3d", - "senderId": "AcFWyJRk5sRKagThYhk5e1jdkx3wzhL5cW" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AZeX3qaqdU8iCebAKYoLMR2QkiuG5CffgU", - "senderPublicKey": "034ff439276ab784098e66dca4075111008448a3b3519c10701bd2d1600ec1203a", - "timestamp": 0, - "asset": { - "votes": ["+034ff439276ab784098e66dca4075111008448a3b3519c10701bd2d1600ec1203a"] - }, - "signature": "3045022100f983b03e319aaa6c6ab6381e3ef8c0c035d6e3cc2139cedf70fd4e385393e38a0220286f73577765eb3e89e362785ad8a6de572bebf41bbc1f515b0ea93e41801eb3", - "id": "00b2c0455ef6f508d65f11bb49e3cfe1e6062d5fd153cafdfdfd2ccbf9c646e5", - "senderId": "AZeX3qaqdU8iCebAKYoLMR2QkiuG5CffgU" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AdT9FrWUksf99Lhkr9JGb8f2HLSg14kTqr", - "senderPublicKey": "022b80e0d314928d93e48d1fe02190378384215237a5d42a86bc91580ba8c88689", - "timestamp": 0, - "asset": { - "votes": ["+022b80e0d314928d93e48d1fe02190378384215237a5d42a86bc91580ba8c88689"] - }, - "signature": "30440220103862ec51621ca27a0ec6b2817848e8824d2d09dbf7e6aac2f45aeea5d2dc9102205e8cce78b5cd7148aa4d406dc7b491dd7758047200e10cfe1e5fde5c56107ac5", - "id": "e25439ad11cb8db3d49ccb3b8b608c1bcb24cb29b2e5ea15101cce3e475224eb", - "senderId": "AdT9FrWUksf99Lhkr9JGb8f2HLSg14kTqr" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AGqSC7M137ctKtkAjd3J1haCEWNfayXnuJ", - "senderPublicKey": "03a46f2547d20b47003c1c376788db5a54d67264df2ae914f70bf453b6a1fa1b3a", - "timestamp": 0, - "asset": { - "votes": ["+03a46f2547d20b47003c1c376788db5a54d67264df2ae914f70bf453b6a1fa1b3a"] - }, - "signature": "304502210099241ced4a0fd1eb02f5cdcc880ae5f48eb3c7e490d4520c20124ecbf403893602204729dc6cacf3e87c97ca57c1be54d1e80791bf31ef022135e68fc06c950f6994", - "id": "1474f50815c6c7df41ab652414806d61abe15bee0d41f32d772f4e2793badce4", - "senderId": "AGqSC7M137ctKtkAjd3J1haCEWNfayXnuJ" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "Aa3mWTMFTXeTJukUgpeihQLBYDHBzdpWZX", - "senderPublicKey": "0395ff46d07f197dd4d4cb5dbb46e164c1e7ca9896c33827f9d6f8003ea167917a", - "timestamp": 0, - "asset": { - "votes": ["+0395ff46d07f197dd4d4cb5dbb46e164c1e7ca9896c33827f9d6f8003ea167917a"] - }, - "signature": "3045022100eccf81d44992c49a5ee37c6fc2ccc4b6bee9aa44888513b3e18e79452ede3156022056b0ddf079d2918d72e8781d3af009c87e6058563591dfd6ee0117b7df5534b2", - "id": "b394e2a8b5c2d20a72ed288408b8f0d48aed922edbee6e16c1c5b0e67517214c", - "senderId": "Aa3mWTMFTXeTJukUgpeihQLBYDHBzdpWZX" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AcATpmcMU1tmLDX7TzR3wXop4tfLFR21Lf", - "senderPublicKey": "03343930630f8235c2b3ae9ba013dbecd4d8bfc999d34bda33e18c8caf43772f1f", - "timestamp": 0, - "asset": { - "votes": ["+03343930630f8235c2b3ae9ba013dbecd4d8bfc999d34bda33e18c8caf43772f1f"] - }, - "signature": "3045022100bdb87894846eccc5a5473edaee1e6dca5f3469963e22f06123b6bde195aede0e02203d0c6833e87c5e60f4597ce624d4c2502a0562b4e54d943f82a4889e3cd69532", - "id": "6a399099bac6c74fa5e956512ef8b3a39f6f946d5d6996f192c2f1dd5ba172dc", - "senderId": "AcATpmcMU1tmLDX7TzR3wXop4tfLFR21Lf" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "ALHDQyTm7wALtwjmKwEejZjq7f6u6w5xCv", - "senderPublicKey": "02c1151ab35e371a333e73f72e9971cfc16782e421186cfff9325d3c3b9cf91751", - "timestamp": 0, - "asset": { - "votes": ["+02c1151ab35e371a333e73f72e9971cfc16782e421186cfff9325d3c3b9cf91751"] - }, - "signature": "304402200785771ccf1a6a40b51183a190d4cb4ce76b9ffd4c2c736d7724e6c667113d020220649ecfe73017d8dda96a7914793470ee7e582693e4866df123b1032194c163b1", - "id": "f20a831a6bae0a85470e308fb66517e70db479657459f6bb39f2cd1783c565e6", - "senderId": "ALHDQyTm7wALtwjmKwEejZjq7f6u6w5xCv" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "ARMEiPQE55CfHfR8WmosiFykTAPGYUyYJv", - "senderPublicKey": "0287a12b336fc781f2621aeb703ae47feca4d3ba6f30625f09ba03d225be6ee2bb", - "timestamp": 0, - "asset": { - "votes": ["+0287a12b336fc781f2621aeb703ae47feca4d3ba6f30625f09ba03d225be6ee2bb"] - }, - "signature": "3044022020b79e1f07bcb17cae9485b9f44e9f583ca235da4ddd363b905fafb884347f71022015a20481b43720ddb3b1e3ca64b1f47e59b5cc2016a62f43327ca14533384dd4", - "id": "7a1285be87dca9718bece5b84266c1bf6801a39cc111d534e660aef9e6d26929", - "senderId": "ARMEiPQE55CfHfR8WmosiFykTAPGYUyYJv" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AcmXmomxpP8NahbbFivq32QmLuKFkTkqRg", - "senderPublicKey": "0311077c86a98b67850e7ed2c81775d094cf81c6991082ddc33fc7be5347dc765d", - "timestamp": 0, - "asset": { - "votes": ["+0311077c86a98b67850e7ed2c81775d094cf81c6991082ddc33fc7be5347dc765d"] - }, - "signature": "3045022100b1615d16763c46d42ca2aae967f04c1c07c119b5af7a378c262ba85515a8d35002202cf7df91676cd137943720e93f06c11907412a6bdc5ef2157cf536a203cf83a3", - "id": "76fb5a1de90f245b1eeb79cb11c7bea7c8b738add0fb8cd95191186a944b0229", - "senderId": "AcmXmomxpP8NahbbFivq32QmLuKFkTkqRg" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AHXtmB84sTZ9Zd35h9Y1vfFvPE2Xzqj8ri", - "senderPublicKey": "02ad799c6bd670746892bd4331e1aebada26a2cc3ccaf0fde1e94942b20066b05a", - "timestamp": 0, - "asset": { - "votes": ["+02ad799c6bd670746892bd4331e1aebada26a2cc3ccaf0fde1e94942b20066b05a"] - }, - "signature": "3045022100e3c7b5d6a72acde4d22e8c1c6cd864c549deba89683f4b84320407d6c380827c02202da57df0ab7cd381b776bdf85802aed371e7cea7269a84f911b1d8e9956badee", - "id": "8da75c8100e6248ab37cc92f72ed9facec3067f4f82f03db8bb8063791463fb3", - "senderId": "AHXtmB84sTZ9Zd35h9Y1vfFvPE2Xzqj8ri" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AU8hpb5QKJXBx6QhAzy3CJJR69pPfdvp5t", - "senderPublicKey": "03ba0fa7dd4760a15e46bc762ac39fc8cfb7022bdfef31d1fd73428404796c23fe", - "timestamp": 0, - "asset": { - "votes": ["+03ba0fa7dd4760a15e46bc762ac39fc8cfb7022bdfef31d1fd73428404796c23fe"] - }, - "signature": "304402205779b5d8acbfedfc105fedb6fcbd4636713ed27605faa9bd988598072640a958022042d8a8b3d7910c7c385f3707a317c5d445d56da250f8d127c71df2d9d4c5d86e", - "id": "fd26e265be88289828d0ce7ffc5faeb9849e1f4cb37a8f1dd5d6fcc436d910b7", - "senderId": "AU8hpb5QKJXBx6QhAzy3CJJR69pPfdvp5t" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AQBo4exLwyapRiDoDteh1fF2ctWWdxofSf", - "senderPublicKey": "034985f6f2167cc8c9df1204aaf6744bc97c0d7f3c07c43ee6c0978bc91b6c680e", - "timestamp": 0, - "asset": { - "votes": ["+034985f6f2167cc8c9df1204aaf6744bc97c0d7f3c07c43ee6c0978bc91b6c680e"] - }, - "signature": "3045022100e18a89fe1fe0a8acaca2b6461314e784ffebbe7374f6aafdb06934e83985ccbf022027314b21a4a25b477bd7cc070b4e00ef8f3d69f3f1af028b96571dc245924c00", - "id": "41d92e128e6b8367cbf8fd111e5263d52e1abad553653f975dd60d7f7c5b637b", - "senderId": "AQBo4exLwyapRiDoDteh1fF2ctWWdxofSf" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AWHGbGaz5FgvyChuAfWFmKY2LsbcwqPYL9", - "senderPublicKey": "02dcb87d64ee2fdc6c2bcecdd841ad8e3b3163599214a818924fd433a8ffe7daf9", - "timestamp": 0, - "asset": { - "votes": ["+02dcb87d64ee2fdc6c2bcecdd841ad8e3b3163599214a818924fd433a8ffe7daf9"] - }, - "signature": "304402201c614c84dbae26f87973c9e2b38df883fe0c8c469080e31fe32a4c4946d50b67022075b8fb498fb1384aa6be785845da02813185ccf095597b5782618033828af4d5", - "id": "1e4a1f8aab6fbf8682c2b35e0d04e9e007ae717ce3f4a82894747e5807e3c759", - "senderId": "AWHGbGaz5FgvyChuAfWFmKY2LsbcwqPYL9" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AeooqGMJPE5UWRPkKW6kgLGZu3898vcLPV", - "senderPublicKey": "02c39e352d0f3c4ea19842a5bca3114b4247cd56da72157963a5873ecfcd824aca", - "timestamp": 0, - "asset": { - "votes": ["+02c39e352d0f3c4ea19842a5bca3114b4247cd56da72157963a5873ecfcd824aca"] - }, - "signature": "3045022100b1ee6becc59d594776a40e5b3caec82390d273b703ecb0d7caece44953141449022016543cc29a28882845118afab6e51296cd216bc662260c28e5efd9597b6025b1", - "id": "2ce068bfccb3f967f4004e9a1e81614a738e55e45c80114c0af30a085f71a2e9", - "senderId": "AeooqGMJPE5UWRPkKW6kgLGZu3898vcLPV" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AWtD9W5LCv2TH3VcdzbGQBGaJBwvbzZNDQ", - "senderPublicKey": "022ebb110e9630377073d4f0e32897a5928a2c71f2941fb6d4b71251dbd62da98c", - "timestamp": 0, - "asset": { - "votes": ["+022ebb110e9630377073d4f0e32897a5928a2c71f2941fb6d4b71251dbd62da98c"] - }, - "signature": "3044022036698a329d7f5f751f91ce02bc188a7527a377d01583b70427cfce64def945ec022079afafea10aa32394a1e42a80577de3869856656221d5f259e05fb44f01668b8", - "id": "3478d1ad3655e10fcc864f191972322c866616866bb1dbf66d7b66b31cd95de6", - "senderId": "AWtD9W5LCv2TH3VcdzbGQBGaJBwvbzZNDQ" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AH39inbLsUBhC3k29NcvQP3zKZWnsQksvA", - "senderPublicKey": "03d27324bd4829f57d549bdb273bfd666d88f43d8429ba9a42a4fa1c9bd1032a24", - "timestamp": 0, - "asset": { - "votes": ["+03d27324bd4829f57d549bdb273bfd666d88f43d8429ba9a42a4fa1c9bd1032a24"] - }, - "signature": "3044022035fa7be80cf881eefefc12b11de04ffb2e2e92815cf05074afef54a3c5b2eccb022041f3347f59db0b3caadefcbfbc5ae275d3fe3e2a52fe1504b23628d4b79a43bf", - "id": "8adfd8e73e96188ed9fdec459d88db1fb041a2b25b3f64830476aec661ae5010", - "senderId": "AH39inbLsUBhC3k29NcvQP3zKZWnsQksvA" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "ANRNMPjQjJGVsVbyeqwShcxKTidYJ2S1Hm", - "senderPublicKey": "021770413ad01c60b94e1d3ed44c00e0145fe7897e40f5f6265e220f4e65cf427f", - "timestamp": 0, - "asset": { - "votes": ["+021770413ad01c60b94e1d3ed44c00e0145fe7897e40f5f6265e220f4e65cf427f"] - }, - "signature": "30440220630da8a73979bd3988b7f84fe9e83a429cf3239f54c140c3dbcc407140513fc002203664ad54ed9f199f2683479b988bd97ad8fffb2c2d5dfdbdb10858aca4abfaca", - "id": "e306328ffefcd9e3809e7390a358199a62cf8ef037d57af1f5c7b54d728d427e", - "senderId": "ANRNMPjQjJGVsVbyeqwShcxKTidYJ2S1Hm" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "ASqzCDRS5cTBwCmC5moQ34W4QZhtrj4pT1", - "senderPublicKey": "02af50fcb5183b3f2c468fb4e75e573a6bf0a048a6fab095df6d70f9f91fd6651b", - "timestamp": 0, - "asset": { - "votes": ["+02af50fcb5183b3f2c468fb4e75e573a6bf0a048a6fab095df6d70f9f91fd6651b"] - }, - "signature": "304402206f1df93f299ffedacc25aa201807df47d32c43369315cf9db280963c357be56302206a66acd553710f49bbb7b803a2bcb71128c8e617ffce66b37b7c968817349247", - "id": "dc69bc8f78502ba34655ed062987788939189709a4112760cd8807245d7461f5", - "senderId": "ASqzCDRS5cTBwCmC5moQ34W4QZhtrj4pT1" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AHysG9CfbXvHtxev9eziTK8WUbnFKKLFR8", - "senderPublicKey": "03c74d53dcfef0d79f249a812e95c1a58040b769867df036639f0c107d3b577b12", - "timestamp": 0, - "asset": { - "votes": ["+03c74d53dcfef0d79f249a812e95c1a58040b769867df036639f0c107d3b577b12"] - }, - "signature": "30440220629e696a10e04d4fbc10a5ac443bf9bd40dd5d89d4b214224abe47d7ab5600340220643f361a24d9916e2c5aaec7bd7d8a6a0d3ffc5fc0b62c3ac4906eb799a862fa", - "id": "c3f49fb80c40f7779b32ba23616f5573a6ba58fc60c4629c2252933038dd89f0", - "senderId": "AHysG9CfbXvHtxev9eziTK8WUbnFKKLFR8" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AZuvQC5WuVpPE9jwMCJcA28X5e7Ni32WY2", - "senderPublicKey": "0345ef2a1e4f64707044ba600efdc72aaad281c5a73195f930527c54d7cc891904", - "timestamp": 0, - "asset": { - "votes": ["+0345ef2a1e4f64707044ba600efdc72aaad281c5a73195f930527c54d7cc891904"] - }, - "signature": "30440220660f9604896dad2a97820b0d7524f0bce5a8b5766f150517d5061fd02bddf768022055e87c25891d4480e66e5d1a71e42cd5a4bef3ab2b2651cd72d44f30a4b32309", - "id": "8e8ac1b1a586e86867abbf25d63387bb6dfb793c691f0b06333c1581a9a568b3", - "senderId": "AZuvQC5WuVpPE9jwMCJcA28X5e7Ni32WY2" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AMfyf9iRjXiKNcLQVTUE9oCESUPzmQ6iUT", - "senderPublicKey": "034776bd6080a504b0f84f8d66b16af292dc253aa5f4be8b807746a82aa383bd3c", - "timestamp": 0, - "asset": { - "votes": ["+034776bd6080a504b0f84f8d66b16af292dc253aa5f4be8b807746a82aa383bd3c"] - }, - "signature": "304402202e2ad64129f61ef1156c4c7e80ab862d4823d62dac502685f53028536ddfb41a02201a3ec777fdfe8fae9f7cd5251fac322c1b6a2a4d41b3ec456daed474986d4872", - "id": "ff73565c373f2cefebf86c72dda3a6a6205750eb03b69178cb83378620715e1d", - "senderId": "AMfyf9iRjXiKNcLQVTUE9oCESUPzmQ6iUT" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AFyf2qVpX2JbpKcy29XbusedCpFDeYFX8Q", - "senderPublicKey": "02f7acb179ddfddb2e220aa600921574646ac59fd3f1ae6255ada40b9a7fab75fd", - "timestamp": 0, - "asset": { - "votes": ["+02f7acb179ddfddb2e220aa600921574646ac59fd3f1ae6255ada40b9a7fab75fd"] - }, - "signature": "304402202e5c78cf21a088db10e1e1f64d98d84c8d3294fde7bc322d4af06bfe99d4c2e302207e7912a16a37b641a9f8c7c722f2b0d699917ca73e4d0f21584b717fb7f02f13", - "id": "3822273b496f2e253081cedf382e4f9937713fabb83449e1f892377cf536e68a", - "senderId": "AFyf2qVpX2JbpKcy29XbusedCpFDeYFX8Q" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AG8kwwk4TsYfA2HdwaWBVAJQBj6VhdcpMo", - "senderPublicKey": "0377f81a18d25d77b100cb17e829a72259f08334d064f6c887298917a04df8f647", - "timestamp": 0, - "asset": { - "votes": ["+0377f81a18d25d77b100cb17e829a72259f08334d064f6c887298917a04df8f647"] - }, - "signature": "3045022100a65ce45164c9bc3e018e26703370c9deb2933ee3b4e814619043cc37c4a39c4802205ae4931ac9e8dffd714c3b601fe248a49c0185c8367887205f497d951c52eb54", - "id": "430d6db0b87c25dce4ce14ac907c13bcc6efa5d95135f05aa4ba7596ea9d400c", - "senderId": "AG8kwwk4TsYfA2HdwaWBVAJQBj6VhdcpMo" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AW8n3yvSAqUJkyfcG5u3bgRxsNKzXYPamN", - "senderPublicKey": "036f612457adc81041662e664ca4ae64f844b412065f2b7d2f9f7d305e59c908cd", - "timestamp": 0, - "asset": { - "votes": ["+036f612457adc81041662e664ca4ae64f844b412065f2b7d2f9f7d305e59c908cd"] - }, - "signature": "3045022100f3cdd7f688ad2d7b6a5b9cc7e793cb8a6e6e07d3327bc67add64691a53fd2911022026ae1adc8f4fcfc01bcca3efc83019026755b443a504265ad1f46f69d1f5951c", - "id": "dda86ecc0332e6c4eed1c0a5af7424374089b85dd274a300fed51b86e2655587", - "senderId": "AW8n3yvSAqUJkyfcG5u3bgRxsNKzXYPamN" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AdWRsk7Lbo97jxGBKzLAFwevVHbqVbW1Cj", - "senderPublicKey": "03691178f8610d0a295e650201b62345056c788d7f9ac7e8570b69c6c90091b564", - "timestamp": 0, - "asset": { - "votes": ["+03691178f8610d0a295e650201b62345056c788d7f9ac7e8570b69c6c90091b564"] - }, - "signature": "3045022100d419072a752acd55792257c96099fb14c56c29112a00535d39bca96fbd7951c902201abdf4db247dc956d79f4543c389823fbd1a9337f95d30df39603a3b52486bfb", - "id": "0998e9a055c53bf6697ee76af94c7a830c1364016d78fce889a21bc38ed70cd5", - "senderId": "AdWRsk7Lbo97jxGBKzLAFwevVHbqVbW1Cj" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AV6GP5qhhsZG6MHb4gShy22doUnVjEKHcN", - "senderPublicKey": "022f2978d57f95c021b9d4bf082b482738ce392bcf6bc213710e7a21504cfeb5a0", - "timestamp": 0, - "asset": { - "votes": ["+022f2978d57f95c021b9d4bf082b482738ce392bcf6bc213710e7a21504cfeb5a0"] - }, - "signature": "3045022100ba1e0ab761326d2a53cbda2a4a5135033c94d8166864d2ad3ceb963b4a0c046402207d755ecf4ada9fa2a598fd75e73a59d30cb83e01f510020b48b6bf162dc60b27", - "id": "be13743deb8486a575d1fb564d2b07d797ac77148d35793c9aca43c0d47aad61", - "senderId": "AV6GP5qhhsZG6MHb4gShy22doUnVjEKHcN" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AaUgne8txmQB1iBboiFVLVHwLaYChZiFVA", - "senderPublicKey": "03e59140fde881ac437ec3dc3e372bf25f7c19f0b471a5b35cc30f783e8a7b811b", - "timestamp": 0, - "asset": { - "votes": ["+03e59140fde881ac437ec3dc3e372bf25f7c19f0b471a5b35cc30f783e8a7b811b"] - }, - "signature": "3044022038a491e2e13ac32025209d00aec1af31b73a8b6ee77ad9b8bb80a34f5df59dfc02200ce82c89fe9f88bd5af236ceeaa80f9954e3fb4af7bc884c447505751d49c134", - "id": "f1d3d44cc289837de9623cba8891a1ed1cde8918473a91e2daead29975afad22", - "senderId": "AaUgne8txmQB1iBboiFVLVHwLaYChZiFVA" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "Ac9dCo9dFgAkkBdEBsoRAN4Mm6xMsgYdZx", - "senderPublicKey": "0363550e2a3fe2153526effd4302045fa2c3027d0d9b30b19821a4870c8cb134bc", - "timestamp": 0, - "asset": { - "votes": ["+0363550e2a3fe2153526effd4302045fa2c3027d0d9b30b19821a4870c8cb134bc"] - }, - "signature": "304402202ae599ce389cd030b8ab48ef53113458b9ba8bf9c9ed09c662eba2849bf540f802202ed63f8af492dd0b67d1b451170a989418a42466a3a7ffe89c4c5a18337e8fb9", - "id": "65ab302a44ea7550891eabc3b4a8d5ecbcb80784c4666195d5d0b7e33394300d", - "senderId": "Ac9dCo9dFgAkkBdEBsoRAN4Mm6xMsgYdZx" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AdXbS4GKvV6TZVHrNzcYSQKfpenQnFGTxK", - "senderPublicKey": "026c598170201caf0357f202ff14f365a3b09322071e347873869f58d776bfc565", - "timestamp": 0, - "asset": { - "votes": ["+026c598170201caf0357f202ff14f365a3b09322071e347873869f58d776bfc565"] - }, - "signature": "304502210088a3a4e82d307c238e01ce154b57631d4429e0b591e828ec36839a783736e842022042c6e1d719781e2edca3dbfe84ad13b9e490821a47ccadfcff379decb9c873c0", - "id": "d26a7ea56f398634a81086bb15c2f0c863c71b8bd728304d324d8245a8fb6c73", - "senderId": "AdXbS4GKvV6TZVHrNzcYSQKfpenQnFGTxK" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AReCSCQRssLGF4XyhTjxhQm6mBFAWTaDTz", - "senderPublicKey": "032c51f895ccdafae44e68baf283c50605d3f7dbba1c48011c6577383791f4a374", - "timestamp": 0, - "asset": { - "votes": ["+032c51f895ccdafae44e68baf283c50605d3f7dbba1c48011c6577383791f4a374"] - }, - "signature": "3045022100ae5805541f085a50076835422b2581d3b7a128a05b4f068ad7e3c14cd02799b802205f4bb40e06f90e02282ae74c0aba97923e601fd78234b9585468c4fb73f47893", - "id": "02504eae7ff4963c081219523bc48d7a07de4c29fdc1622224547f9a7c133abf", - "senderId": "AReCSCQRssLGF4XyhTjxhQm6mBFAWTaDTz" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "ASEJEDLfTxy6upQDWTuYucoVwMUcmhSGhp", - "senderPublicKey": "03832487ab0aa9450a4c223999bf4311b7b65c50c06baa90d19d4f65c27bfeeb93", - "timestamp": 0, - "asset": { - "votes": ["+03832487ab0aa9450a4c223999bf4311b7b65c50c06baa90d19d4f65c27bfeeb93"] - }, - "signature": "3044022078d38cabd8f427ef381d0aa6a0b98c6a590cb18f47acc1d80b429a1c1959b0ab022022a70d4d93d650ca3121dde6065e80cd90d1e2e91cb90f0d0b2eadde609e0d75", - "id": "addb8c1baa833baa52a5b51d8a86f8524bde826b5c9f0a99e57070e6323e1dfc", - "senderId": "ASEJEDLfTxy6upQDWTuYucoVwMUcmhSGhp" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "ARAibxGqLQJTo1bWMJfu5fCc88rdWWjqgv", - "senderPublicKey": "038082dad560a22ea003022015e3136b21ef1ffd9f2fd50049026cbe8e2258ca17", - "timestamp": 0, - "asset": { - "votes": ["+038082dad560a22ea003022015e3136b21ef1ffd9f2fd50049026cbe8e2258ca17"] - }, - "signature": "3044022076dd065e3fba825b77884a179d0231d7fc9e7d3a02e34bc6565fab81a84e559e02200a880c028e690a9d6f2c4c6576b1bf3e913817c834da8ec6afdbadfae78d341d", - "id": "72f31f9a829b93045ef2e860b24c33b9be6a2621c26914acd42121215c1d517e", - "senderId": "ARAibxGqLQJTo1bWMJfu5fCc88rdWWjqgv" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "APRiwbs17FdbaF8DYU9js2jChRehQc2e6P", - "senderPublicKey": "030a9321ff83e384aef559e6030008c23a137e3b3c5d45028e46cccbaafce772b1", - "timestamp": 0, - "asset": { - "votes": ["+030a9321ff83e384aef559e6030008c23a137e3b3c5d45028e46cccbaafce772b1"] - }, - "signature": "304402205261d9d8ded6364fda8b10bd477982be84990cb010f9214d52c492676814e1f40220489f441ffe2478d361a12ab96caa59da495fe62d61d0e2255aa5ec4ed789afb8", - "id": "1f17b4ba072d205761ed3f786491eaf684ed3601b69082e487e568aa74a319e8", - "senderId": "APRiwbs17FdbaF8DYU9js2jChRehQc2e6P" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AbfQq8iRSf9TFQRzQWo33dHYU7HFMS17Zd", - "senderPublicKey": "02def27da9336e7fbf63131b8d7e5c9f45b296235db035f1f4242c507398f0f21d", - "timestamp": 0, - "asset": { - "votes": ["+02def27da9336e7fbf63131b8d7e5c9f45b296235db035f1f4242c507398f0f21d"] - }, - "signature": "3044022040219da41054a3eebd3122df7f09a62a4e8b4fdc287ae77221f2217b42f291ad02202b9a70c54bb546a604eafadcc086ef6b6570f57542374d87de02ad7f61fe51a4", - "id": "5fa837023159d6a3d6cf7c5b2ed6fe05ff7df19300226b2f0be5a48a06993780", - "senderId": "AbfQq8iRSf9TFQRzQWo33dHYU7HFMS17Zd" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "ANBkoGqWeTSiaEVgVzSKZd3jS7UWzv9PSo", - "senderPublicKey": "03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37", - "timestamp": 0, - "asset": { - "votes": ["+03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37"] - }, - "signature": "3045022100ded426768f114f459485ba6ae293c9649b340cf2dcb15e8e887fbb5fed6f7e0b0220752297022de6e93ff64bb9e07b4efef8e946cd2872f84d9e1cb3165ff5c342cb", - "id": "0a16dc31514629a36d7237968ada6a95d6cbec027b7d26e1e0f0d7d4febe9494", - "senderId": "ANBkoGqWeTSiaEVgVzSKZd3jS7UWzv9PSo" - }, - { - "type": 3, - "amount": 0, - "fee": 0, - "recipientId": "AJjv7WztjJNYHrLAeveG5NgHWp6699ZJwD", - "senderPublicKey": "02275d8577a0ec2b75fc8683282d53c5db76ebc54514a80c2854e419b793ea259a", - "timestamp": 0, - "asset": { - "votes": ["+02275d8577a0ec2b75fc8683282d53c5db76ebc54514a80c2854e419b793ea259a"] - }, - "signature": "304402203aa292e7aedcd62bb5a79c2521b666b8e1886b57923d98f51911b0461cfdb5db0220539657d5c1dcb78c2c86376da87cc0db428e03c53da3f4f64ebe7115998f00b6", - "id": "8816f8d8c257ea0c951deba911266394b0f2614df023f8b4ffd9da43d36efd9d", - "senderId": "AJjv7WztjJNYHrLAeveG5NgHWp6699ZJwD" + "version": 0, + "totalAmount": 6677610400000000, + "totalFee": 0, + "reward": 0, + "payloadHash": "527df5a4bf0fbd0dbe4b6c0a255c14a8451f4f3144afdf82bcb65b68ff963114", + "timestamp": 0, + "numberOfTransactions": 52, + "payloadLength": 11393, + "previousBlock": null, + "generatorPublicKey": "025dfd3954bf009a65092cfd3f0ba718d0eb2491dd62c296a1fff6de8ccd4afed6", + "transactions": [ + { + "type": 0, + "amount": 6677610400000000, + "fee": 0, + "recipientId": "LMs6hQAcRYmQk4vGHgE2PndcXWZxc2Du3w", + "timestamp": 0, + "asset": {}, + "senderPublicKey": "029231513615c027c4a91b4dd403a0223ec7a7a34d675c065c1f2fb1a84611b4f1", + "signature": "304402202132ac9e0f1f66c1870781980204d0e64b75d25dd036009f0e430844bcb73a290220746be12a00892433579a10c643d5bd93d5fe1cc3edeb18766e8abf4fa058caa6", + "id": "2f79303a4c7bc8f23c1dcbca7d8f77e9e657b6387eb7c4c691e32ae33843862d", + "senderId": "LiVgpba3pzuyzMd47BYbXiNAoq9aXC4JRv" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03cfc6ab0fba7983067141af79d20af2bc5d0ad5f2b1875b0993166649526f7f3e", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_1", + "publicKey": "03cfc6ab0fba7983067141af79d20af2bc5d0ad5f2b1875b0993166649526f7f3e" } - ], - "height": 1, - "id": "17184958558311101492", - "blockSignature": "304402202fe5de5697fa25d3d3c0cb24617ac02ddfb1c915ee9194a89f8392f948c6076402200d07c5244642fe36afa53fb2d048735f1adfa623e8fa4760487e5f72e17d253b" + }, + "signature": "3044022035f25000d36c84fed5cab963bb27331a12ae7fa64b24a2cb013d69dbea8db47002205a5667ede8456510374b1cf0d5f5e9a49ce6769068b44b9c1afe9834c12a0664", + "id": "1e8fd04a7e3eed63c128b99d68097d284d84ab98a725a61585115bfae4daa5c5", + "senderId": "LTAAFiSnPSsZXnNPGD6dg7cfy2gm4o6Fu7" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02b13bb6485d271cd5d20043b3d19438462c72e993726559f057494ea14f20e279", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_3", + "publicKey": "02b13bb6485d271cd5d20043b3d19438462c72e993726559f057494ea14f20e279" + } + }, + "signature": "30440220155193a009db40c8735ff1efab1b8eab8b4b3c541e94ded64f8e9c1065d7d1c90220529b5f4ac55a4445bae29436a7eff5ac77f198eef042589d8ca18182b61d5918", + "id": "74daca4e3bf70bfc3e00aba6004cd251f9adae767b93e0cf9fe102dfc46fe7a3", + "senderId": "Lguu3RdVHxdBTsT3B5z6hHd9QsHN2UyZMk" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02ed5d74250e0789b6996d06fbc27b0f6c0380e4f33235935efb1f002c280c0a7b", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_4", + "publicKey": "02ed5d74250e0789b6996d06fbc27b0f6c0380e4f33235935efb1f002c280c0a7b" + } + }, + "signature": "3045022100f7b021b651e884fef5312250bd6748c556f29f0fb49a73f92c9f24c7446dc01302205f7e05e86d842eb11d6a5260031fa7a0584ea7700469eee36aa80023dd64d8bf", + "id": "72f772da0fde0198d90e288498743c136c21af906128596f04335c9fff805fc4", + "senderId": "LiQ74VbVC4ALkcN6oTZ2VVva2kK77DeLXw" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "026434affd98ea4f0d9220d30263a46834076058feca62894352e16b4cddce3bae", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_5", + "publicKey": "026434affd98ea4f0d9220d30263a46834076058feca62894352e16b4cddce3bae" + } + }, + "signature": "3044022025cf2c1f7760f40a8d6eb51e9565e16f3db6a9690a52d87e0b64696dee890e5602202da3bc35adfadb0f95b2c05f91e2cf353c85458ed13743202de65ca44167c5d8", + "id": "4809fbd98d87626dd2d1c21571cac34cfda37bc3735b5cd9aebc67a8dcdb88f8", + "senderId": "LXe6ijpkATHu7m2aoNJnvt6kFgQMjEyQLQ" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "022a09511647055f00f46d1546595fa5950349ffd8ac477d5684294ea107f4f84c", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_6", + "publicKey": "022a09511647055f00f46d1546595fa5950349ffd8ac477d5684294ea107f4f84c" + } + }, + "signature": "304402204a85f256429f103324517a9e5ebb29835f82183f8a97c32a4dc40237dc0abecc022004c4819705b57822cdf9d265cad0f1256ed6d5d7957437bdfee2622b2fc562ed", + "id": "a2b4c04c1acb17277a1c694878c83bee3884b76d54289a17be1edfac7c271465", + "senderId": "LNJJKBGmC1GZ89XbQ4nfRRwVCZiNig2H9M" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0338fee3edbaea4eb28dbe78a9340ecf25ad9f363e523dd72e691ab0d6d2bc4f92", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_7", + "publicKey": "0338fee3edbaea4eb28dbe78a9340ecf25ad9f363e523dd72e691ab0d6d2bc4f92" + } + }, + "signature": "304402207e5adc8b159adc1d484a80970b7b0a7e4a3907429b1668ef9a040af0a381e0370220208456bde6eea81af2d5bdbaba82b1a07a08d92ac8a8ae01b74b4c9074515d95", + "id": "b8b8e2479a1170953f212bb3bc2edc2eda5e65774e21e84f9f2d09724cd28e6a", + "senderId": "LX9JSTgnd34zPQaE665qQqXdqiPfJ1gAoJ" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "034e3e86a3bf2af24a660e41db3900dacf61fb2b38bf451b973c43059524a9545a", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_8", + "publicKey": "034e3e86a3bf2af24a660e41db3900dacf61fb2b38bf451b973c43059524a9545a" + } + }, + "signature": "304402207756ce0637a0c0d73ec38f8ed944df5590861be5c9a1c4a48ee2421170ba0c9f02203471f2de3a37fb6bfdee67e39cbfe6d860f5937239533b11d702487480e06ae7", + "id": "2076465c26f5a5c0ab7e37b77ffb050ae0e2f5e94db5a72d18db1077f2e86452", + "senderId": "LSuxyEmzrxEA95aDjDWEjUTEKJwg4eWbvu" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "032699280d1527ed944131ac488fe264a80617b0acc9305fe0d40c61b9a1b924f9", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_9", + "publicKey": "032699280d1527ed944131ac488fe264a80617b0acc9305fe0d40c61b9a1b924f9" + } + }, + "signature": "3044022038992cb6c92afad99178d1274d394933e5fc6c1feaff0da695dfa200ab2992a40220080be455fae8d9e7a7f5a0786a2e3fc06a0e5206bcfc11aad8af5878c0e05054", + "id": "623036939c00cb9e47d827779a711c0e6b71bb4cce05489288d9c779366dd859", + "senderId": "LgMN2A1vB1qSQeacnFZavtakCRtBFydzfe" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03a5ff887dfee40214a767d93cbe4e5eeb545b366bc900f3859c82cd0401b86b72", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_10", + "publicKey": "03a5ff887dfee40214a767d93cbe4e5eeb545b366bc900f3859c82cd0401b86b72" + } + }, + "signature": "3045022100a64d8d58cd0c4e491b1a4bf6c64ceab4ed1cb59c37096d8f26235709d48aad4902204d50a01fb2a84f70f14ee69e8087c88df62b6e1c7b8d21ed188988616131b1d1", + "id": "388e213a34943e1b1cfd8f1a2a9e62ca4e10317524928ca68af5c5e983cfeb36", + "senderId": "Ld9UMYSCaY6r6WFq7xQByULqyyA1S8NvKN" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02e89df52a352dc7d3f4df99ab1deddb9dd0dc882f08cf79936c861276dbaca99a", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_11", + "publicKey": "02e89df52a352dc7d3f4df99ab1deddb9dd0dc882f08cf79936c861276dbaca99a" + } + }, + "signature": "30440220717e1d0d6c11f94c47ef7b3fe729174563af1f9175d3330ef3f9a3b58fc2ac7c02204e040edb7d9e201116e0901fb97514b62c6a1648ff0bab3e4ee795944ed980fe", + "id": "690dd9c86e58248000a2c58ea21ff12e0061fa6758f47f84e8178c72c7a215e7", + "senderId": "LbDsDwL3vt9e35f3djJZHxWHfHGXntq3N5" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0380e1c760787e321f8ae42d13bec4cc57f09d3ab12a642ec24b29e6501f23c115", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_12", + "publicKey": "0380e1c760787e321f8ae42d13bec4cc57f09d3ab12a642ec24b29e6501f23c115" + } + }, + "signature": "304402202a4ec8d65f1d9db3e67fe939d62035fe59e241bd5231117ec9bb2696d635231d02206cbbff26fd2a23bc2b6ef4f27a1366bbf358af84eac1c81c67190c4579cf916c", + "id": "6ac0eb77820de4002f0af2a1c6ee34783d5b916f97eb2068aeabe9ed34a859de", + "senderId": "LWBeHAE1LjaPXLsedn3aqGtNU4zw6CNkR9" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "030cca25405fa440277a2d226656bc281744657f805c1e66480c132d8f89a8692e", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_13", + "publicKey": "030cca25405fa440277a2d226656bc281744657f805c1e66480c132d8f89a8692e" + } + }, + "signature": "3044022042a4e8b8f3c1dee7b909289349dd1195ed32eab85ad7502d75a35a181e249dbc0220176b18aea29df1519d02fc43618cb5cd6bd57156a4c07ce2ba1184fff011dcc2", + "id": "81f2d50c1bcfdbcac4b9d2c1f6a272f7fef3f0f34616032665420dee32367dce", + "senderId": "LSDquEuwwggJJSHjqN46oNj1QWa3xaDqhD" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "035cd0cde241b253dcb38599bc9f670e13b7d07a286e6b91f4bedf3026a2c703ec", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_14", + "publicKey": "035cd0cde241b253dcb38599bc9f670e13b7d07a286e6b91f4bedf3026a2c703ec" + } + }, + "signature": "304502210098383b14ff6011f9d7c85a81ba1976532df89a38a7f0399faedbe9e54a4620c602207b96f7d7342852fd275f08157c76b2ec723aa905105cf613368df9e8339557aa", + "id": "b61cac3451ed77a05a8fb84511e0639c7e5256163da89e159d0b89426b5d294f", + "senderId": "LMsaiMA7vf31LUoXMWcGvsMYQarphwN3Dg" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "036385e52d625357e9a2d1430130d9109ab4ce3ef64e656480e1f41b5f93191e8d", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_15", + "publicKey": "036385e52d625357e9a2d1430130d9109ab4ce3ef64e656480e1f41b5f93191e8d" + } + }, + "signature": "3045022100d271a8e836e67b0ddba315477a4280e96a12be2a73d7cac350fe82a366d24d2a02204ef4f539ad655130dc3ec9131c49696fa97f16f4cfd5fb5a4349152cfa996c15", + "id": "a781f77c56aad0cef3e572604c39afd16907dd35e84093de6174cbd6f70f2a67", + "senderId": "Le4rHsvdhEBYwPnLvkFfJs8p8uHHbZhEDo" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03d48a86be31c12780a3dea34642705ba90d60e3347b93dd3379e8de6d1e76e688", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_16", + "publicKey": "03d48a86be31c12780a3dea34642705ba90d60e3347b93dd3379e8de6d1e76e688" + } + }, + "signature": "304402207b9ff2d99d97e7516bfca53eda2f9407722f7c042e4cd3714bdb5520d25708ff02203587c701473cfac70eeaafa68bdc6d7f8e98d5a9307211235216e8b91dd309ba", + "id": "bce6568c6701bee388cf3b51bcf80cac21f75e50f616a66a278366523f3344d8", + "senderId": "LQpEtWok1ceczUNRYUofJ3ho2AwsC98gPG" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02501441eacbea23240895f833f4876409669c138590a303be563ac7608addf529", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_17", + "publicKey": "02501441eacbea23240895f833f4876409669c138590a303be563ac7608addf529" + } + }, + "signature": "3045022100d30ff8c83128a6449f3cd33426a6718c476c787ed739f7c16dcae66fc2cef36802207bee114c929dbc82df7a0ea30de0b48199b1233d129d485cde4ac7e1bc3ae11c", + "id": "f94774fdde5276fe447e74451a93e52d29065080675407b829fd83bc210310a9", + "senderId": "LfUczrVGW9GKJs1T7CmHDPjGo2NKebrTcA" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0395184dde3fbd18c20f2f878959194ef231dd02f8a07831eb2409c6b8564c1f67", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_18", + "publicKey": "0395184dde3fbd18c20f2f878959194ef231dd02f8a07831eb2409c6b8564c1f67" + } + }, + "signature": "3045022100c401a7cbfd13e533b0aaf9384f285d5c182b42bd73e952f068a04663df912500022059282b93965d801638d768dfccdd4cb744b8d98b8c8cae3386e4859529cf1ab7", + "id": "cc63a6b4279ffb5fc4deef32eb8088d206ea0c4a764ee74d4011e1faaff82d1b", + "senderId": "LRshUEroH2bzKuKuTTNqrQDX8ceVXSQ7bz" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "028affc0e41d164f998bc6c1f88d23069dd0c97a2170082281776c8f06dcac5d4e", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_19", + "publicKey": "028affc0e41d164f998bc6c1f88d23069dd0c97a2170082281776c8f06dcac5d4e" + } + }, + "signature": "304402205bd9dbcd773a4fffb886ac4002ce3ab7a8c52921e38a8312fb6422609860a06602206dcd333dc1ebb458e14710d802503483d871e433dbcf429fc70278138808c01d", + "id": "ac8030dbdfdf2dfc2150bacd7e30f02aa62a8cb769771862f891d341402771db", + "senderId": "LdWqv58YhNYwn429k8mPChwW9z3jKgtowu" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03b343e5d5a9a5d859cd133492d2d4c595a860dbfe7ccb1ce20de46b5f74ad99ef", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_20", + "publicKey": "03b343e5d5a9a5d859cd133492d2d4c595a860dbfe7ccb1ce20de46b5f74ad99ef" + } + }, + "signature": "3045022100c6db61626361a60b9bda23109c546e0549f3960bf01a599347d53d9376c921f0022012f68a0ee11ae9cb4d9fe3c3c8a0ca8fbd7cf14b27d969881f86e5a23392d4a0", + "id": "210731ba4d8f82a984f586b935f7983ccb194afb04f271fda27d775ca106b53f", + "senderId": "LQ8uNeoNXkqgD5NhpZWFe8tDAkf7EWay2k" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0351b5d4c8248f945ab62029ba879e905c60711df80cf517dce21f5fb1b6e02ceb", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_21", + "publicKey": "0351b5d4c8248f945ab62029ba879e905c60711df80cf517dce21f5fb1b6e02ceb" + } + }, + "signature": "3045022100a7eb3592a7557a09301bb6c048178c20e5d6ca39abda6275083ba96a5d115192022052e27350e3f49c78e54a11246d3f7b817d8a850222c02436330757583402b6a9", + "id": "d16210ec3b60c4886c6ef030c4747aa30cfc4a844b2daa47f4aceeeae0aeddbc", + "senderId": "LWGcebTpRFQi4GtcfMTM5YnHgW8d3fVu3P" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "029f5c1d73571903e972e08f2489deb08d1827c1f3a82477436f378fa3bbd09d1e", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_22", + "publicKey": "029f5c1d73571903e972e08f2489deb08d1827c1f3a82477436f378fa3bbd09d1e" + } + }, + "signature": "3045022100811132b2a8ff0e6c1d11578b124be6735baa89c158e8d14e3073fc1e2c2b04210220039998c92b1f73ef139e6893c898afab4e8211c1e8820bcf916aca68b403b262", + "id": "1bac2d5c0e1eb6755c1dc38133559d69c74faec3faec2ce5a08bfbd36f48968e", + "senderId": "LXg5EpSY53chrT5PwkguVgQvcT252mNkog" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0267f0c840ef35c605cb09d7400db6d21a0638d39e7b8c10ca05335537ca557830", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_23", + "publicKey": "0267f0c840ef35c605cb09d7400db6d21a0638d39e7b8c10ca05335537ca557830" + } + }, + "signature": "3045022100b1e777358b24f36be7f8e8a79feb1d68dff582425c618c919f5c8cdaba66ba7f02204c4696f9d2de65aff62b74203f68ecc95848420f1f29b670a05150c605f3af94", + "id": "b554a7bd81f8ab4d49c810962ce6497338f6a87d29ca1b75df9b72950105ed8c", + "senderId": "LSZ1HNo5UPaF5xEPMLwLhJZShxPTKwv4QL" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02bc9d0880d1c1e7bf9c7bf4c1f5ccb341663894ebf379f1da89891de5a01382c1", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_24", + "publicKey": "02bc9d0880d1c1e7bf9c7bf4c1f5ccb341663894ebf379f1da89891de5a01382c1" + } + }, + "signature": "304502210080687fdff5fdc4fee63a7204e8f356dcc04aa829b900fe2894941f1da0659f69022060d497f032e6f67ac53e7e662729e087186aab369b8ab17adf13ac742c9755c7", + "id": "d344843f187b240f0f608899c9c30f3e5639542b1ee0ed41b5e3a1c7bff33759", + "senderId": "LhWCVj9Lk4xFHLEmX8F7WYXKe6VZ1zWsU4" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0399feb84b2f84c436efa7a19928aa2d8b17ee96feeb32c8d72d0d0fcef4e601ee", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_25", + "publicKey": "0399feb84b2f84c436efa7a19928aa2d8b17ee96feeb32c8d72d0d0fcef4e601ee" + } + }, + "signature": "3044022073d693914c0106ca1d176000d40d6fe668390ff5af46cf0e7b57e780ceb340030220664855a8600b1f4fd4eadef8c4c910bb8ac2cda7d6fd9081f16a7f7c53dd2277", + "id": "92ef42ec83cf8137e6b710a3d6b0acef393148def677d12eea9bc424618692fa", + "senderId": "LUqiTMfUuuNXn1G14qQAue8vVGYx4VBTmZ" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03289284e6c6f26f064a023befe8b57334a2da2e576d7f0a5b30a2df7d9bb10ed2", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_26", + "publicKey": "03289284e6c6f26f064a023befe8b57334a2da2e576d7f0a5b30a2df7d9bb10ed2" + } + }, + "signature": "3045022100ef2bed104f53425816d5c051b782b78778d0994d7a1b918eadff3cb8130bed65022075a48e11e61ce38ff5ef2cac42a97de8eaf03deed84f7220a32d86727fe432a4", + "id": "4ba5592e71aafccf163dd57b89c8f1f9fc61e7dc649c5e9c62ebbfbe102d9565", + "senderId": "Lfe1qNRgrNz2tfvEtP2iTLG9j843F1epUJ" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0394b067454e4ff7f55bdf2576be5d9248d302e9fedf17b3cb896bf437fd17f054", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_2", + "publicKey": "0394b067454e4ff7f55bdf2576be5d9248d302e9fedf17b3cb896bf437fd17f054" + } + }, + "signature": "304502210081b9f0e64756db95f42f2e0e52fe9288a0e2a4f850b76816a7a461d7c0b389e50220039dfe4b9aadad9be214005f0093e375b5755d1325769b42182a1d710cf4740e", + "id": "17e954e98cca02121b93e2268b9e12d06f63c79b7586c594fbf02a0ce1133ff3", + "senderId": "LgGouZmkVPU1jyrEZzcNJbDNE18AhyZAMr" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0256e5ced44d98d381f66d553207ae50d1d9d5d6ebbae38ff4c8a8a34459e70356", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_28", + "publicKey": "0256e5ced44d98d381f66d553207ae50d1d9d5d6ebbae38ff4c8a8a34459e70356" + } + }, + "signature": "3045022100de3b70a28a081eda0e1409f88b3e99d71dcc7e24576e95a884209756acd3af980220348d6eeef8576c9ee10ba46980d49245372d7fe42edf58e29f129943afbfce47", + "id": "f03833961afbcd728e829979167763055d561b185625d64c9b5867fbfde1a1f6", + "senderId": "LaPDvvxxZarQx8GGBTdpoeoeAijYCDE8jJ" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02b3d92077b925cca091f0e622efad92cf7b8e1d1820caed382fadfc368a7fcc6b", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_29", + "publicKey": "02b3d92077b925cca091f0e622efad92cf7b8e1d1820caed382fadfc368a7fcc6b" + } + }, + "signature": "304402204d06dd06d49602ef3a48bf3f471758b696e25feeb365d3083990121fcf20cbb902204ecdb2dfe1dcb26827d326897ade93bae54c37691a930421daa5187ca8f42391", + "id": "926e95f768d656a7276c9ede20eb6bc4e041360f068b59e6b4c6f59c39e5842c", + "senderId": "LRVVZ82Z4DZX2n6AGCreoyVPvx7Ng9uDn5" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0212c0d2ace080e31d6ccb8f5b24519c0ff906997af7a6e7e1db469ae5a3785e1c", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_30", + "publicKey": "0212c0d2ace080e31d6ccb8f5b24519c0ff906997af7a6e7e1db469ae5a3785e1c" + } + }, + "signature": "3044022100cf15034f2a667e4bbe416312dea9f9347df0cd6b29e4fe44d7dd60c43437d57b021f72ce0c98a75adce347a497f37465771cc7b1851bdd6ef08beef4d42886c761", + "id": "86d8859712965a289eb023fce7f39a7c547453cc813bdb9fe8b0d583a723828c", + "senderId": "LSiRjpZkrNDEhaE4dE2nxsipN4dcvbyN4f" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03e052a316ee9c3e0984a1005dfe46b65c6a5adf0078101a0ef43b3b6cabcaf586", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_31", + "publicKey": "03e052a316ee9c3e0984a1005dfe46b65c6a5adf0078101a0ef43b3b6cabcaf586" + } + }, + "signature": "304402200ba7a513334a60b1cd2bc508c86640593007fdeba226e6b11ba1c4d261badfa3022017f9d995570dc8aabd0951fe183c90b16a38f9cfae5541bdc67dbeb99a12599a", + "id": "369941bc4aca89d6951902847b9f9b41b9a50beeb5280d523b0343d280914b9f", + "senderId": "LPYVpdy2kXEcCneQBstPhtaWFHrJKuqJom" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03fe76de8126a482889f25d8aa205d918facd9fdb86952d1e1ea310e4071bc2a87", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_32", + "publicKey": "03fe76de8126a482889f25d8aa205d918facd9fdb86952d1e1ea310e4071bc2a87" + } + }, + "signature": "3044022040d27b1dcae6d4f8ff602249344660a38de7c6173a6a3a2eb5a1001b34ffc09802202be170219fb2da1dc5221a462228381bc094d907a85b5e3c1c5df1d99cfdee45", + "id": "d0c291da0bb09b30fb57c66b69906aac021cb7337eba4c386afbfab237ef18e4", + "senderId": "LbaDGseDaS5EEuqv5ZgZAnR59Bjohsd55c" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03548bd1abfd5bafe135841b53bd1f8a5c6c9bd3018e5b03ee793e4ea0ef8a3f24", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_33", + "publicKey": "03548bd1abfd5bafe135841b53bd1f8a5c6c9bd3018e5b03ee793e4ea0ef8a3f24" + } + }, + "signature": "3044022020b6f959227a1195ab801155850cc7f8bf4c22cf364b9750f5e0c2be32fc53d5022068494ad5c0412808c758c29385743cf24e080e55dde12fcef02597bac17b5ed8", + "id": "1d93b57d30da21021c065ffbe5154d111a23e32f3b73ee9fdc804bfd026000bd", + "senderId": "LV4Lc3voaa3NFyN8yLrxeFeyVyJ3iDMEKn" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03d4a902c96fbba4798081438b09fdcd6cf07165c475b8fc2ebff7ddfd6f24787e", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_34", + "publicKey": "03d4a902c96fbba4798081438b09fdcd6cf07165c475b8fc2ebff7ddfd6f24787e" + } + }, + "signature": "3045022100d136c59771414ff5b5427788c3decdc0d2fb2d85ffdf9b215f55a76d67c78ba70220326cdea595405f036d6ebb6d2ccfc4a86dca1035e132304193766ef3ceb60a03", + "id": "79ff5f423c9ed25a611eef15460459a70b63a2fac4ac065546c23e7cee7f915c", + "senderId": "LQPqYxJvXcFE132Eap7TyqekfHnNGsRnmE" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0226423c661236f8e6792f515cbebd2f11492b07d016040242bd8686941b113320", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_35", + "publicKey": "0226423c661236f8e6792f515cbebd2f11492b07d016040242bd8686941b113320" + } + }, + "signature": "3045022100c01b32ed59c12cd1cb54595c3720f69b230c2872ecc0e551b3bf14869d3c7bb102204f42b3b0afbe0ed11c8b6625cfae012d49d6d22c6f65add32add8eb5c449c680", + "id": "aab0f355e46d2a0ab52fa800a1ddce85190b57a15cd300f2bfec45a9a963f64a", + "senderId": "LXh8k7dsxS6QcjvuVvvMSEJbEXhm9jY1RJ" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0259fa9ad9927bee7d5b809c4fbae77d614ee41d3dbb9a4226fe8c4d86277789bb", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_36", + "publicKey": "0259fa9ad9927bee7d5b809c4fbae77d614ee41d3dbb9a4226fe8c4d86277789bb" + } + }, + "signature": "304402206b68ebeb6a4cae819d864f19bce0a5a057cfcdbd3e1bb3025c05658c2bd4f9dd0220044470f705d4769d57d218e06db4edb143c58dcfd6d97e2a620784a0b56e8f2e", + "id": "d26491d7f087dcdd8de5a42184bdda6577c43a2555089aec4a22fe8d92d3af15", + "senderId": "LMof35rFFkusz52X3UpQwChLR3nQCHUppz" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "034efcd078bfb06a45d47088f737c5e3cfabc397f41cf7f548f6cc3186c60b60cb", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_37", + "publicKey": "034efcd078bfb06a45d47088f737c5e3cfabc397f41cf7f548f6cc3186c60b60cb" + } + }, + "signature": "3045022100878655ae53c1c7a6bd0db8555eff4191f77368311e164154f72ea067fe02434f02201ef0c4d5580ea7cc9325ee87c105abdc0544e42f709c9c51ed9aa84a1d39c85d", + "id": "0caddf81d2ec557b0fa65381a97b4641852d5a8e5c1aa5e8281a4130d3367aab", + "senderId": "LgoMweowQ2HvmTSoLvUY5yrxDnCUeM5Rer" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02335449dab0d39bfe0acf724c9ccdc7a49be39f2f3f6aae985a122d1943ae5c29", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_38", + "publicKey": "02335449dab0d39bfe0acf724c9ccdc7a49be39f2f3f6aae985a122d1943ae5c29" + } + }, + "signature": "3045022100eb11d666fb80c7c678e6b4351e67f31942776304b58114ffe1bba93e3e49a392022060c45129640e18439788b49ea580c6803f082ffc8fe1f11929e74b08199de1a9", + "id": "8ad4350f2203b1479abdc0df45cad7961b7bd00f36778343cb8ed7f29e725044", + "senderId": "LUKtdzXMpUzgHmJyaXStZCBqok6QFE9EcC" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "0341da3b2b699f4a57dbf73b345750424e3f71dde5fe1a12075d81ac60efd8afa2", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_39", + "publicKey": "0341da3b2b699f4a57dbf73b345750424e3f71dde5fe1a12075d81ac60efd8afa2" + } + }, + "signature": "304402207733a261839d31934d36deb1e92efe10ca43a9e0dede898a9fe50b9450b11b1502204c87d12beea83a7d0192c4490493eb82a6edffd66b979ac421dc2c477ca43034", + "id": "6edc50c829bc3b634b55299918f2b98db71257db7a41ebae87f5084c8299dbc6", + "senderId": "LZLagvjCWpaj34LDYatuR9a8RkCKWmMRGA" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03e581efec9acf0ff580a822a426b7cf91366ee32542b03dd1ca260b2b7b602528", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_40", + "publicKey": "03e581efec9acf0ff580a822a426b7cf91366ee32542b03dd1ca260b2b7b602528" + } + }, + "signature": "3045022100c3ded6d770dfb91e79763a0ab142f7e02f92aa9a58b0530abee93fa90cad93180220401e13817f6d857ab874619a25f06e94dec4da5f6af45dd23a803e278cec8435", + "id": "9936122cbd36c6150ce5166a9c34d5c96836c46e85446839ad695faca76020bd", + "senderId": "LeJWuMb94RSNG39EqFYecA77nmbxSTvbRk" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02995c08f78ee0c4fca22d0dfde3c0c1ff1274a272cb7ccf9a20ee6f63137c3bf4", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_41", + "publicKey": "02995c08f78ee0c4fca22d0dfde3c0c1ff1274a272cb7ccf9a20ee6f63137c3bf4" + } + }, + "signature": "3045022100aa6dd13c37fc4d7819d5b1b8825da4356285cb42d3bfe29e2b08128f009a8dd1022068ea90796fc1c55fb98629f7af4c59c8eaf696b09acb5188f3819f38a9b7a8c2", + "id": "a0ccf5144b027d37644c4eb53e7a82c608f1b548e7e8ab1d02d884def38e3b97", + "senderId": "LM4v6fDf4ViphhTZFseNCZQTM9QAcWPYTE" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "038909bf13b9e6661985ca8799c6a8172eff25c3bf8ae397787d316119397053bc", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_42", + "publicKey": "038909bf13b9e6661985ca8799c6a8172eff25c3bf8ae397787d316119397053bc" + } + }, + "signature": "304402200ad22674a9c5681e56e648d1c76ad127b1c45411193d567a4376198dc29139ae02204c330b77c7ea3e0d473819476e0a8a59e913ea0fab314922533876a28716a080", + "id": "836bba1e5fc907140bafcbd784219a71060f0477f34883e50f47e4b4abcb1151", + "senderId": "LfhoAAy78k98qbcR4fDCLNRBYNN4fRTX3d" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02c03780a95f9e9fd380c61252d877e392fd0d4b9f1fc2ca15b6d22a2eeb036992", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_43", + "publicKey": "02c03780a95f9e9fd380c61252d877e392fd0d4b9f1fc2ca15b6d22a2eeb036992" + } + }, + "signature": "3045022100960216f7044ca54bdbe6a6992401d7b10e575117a9a96bc9547df0f4ea2db0d702204d80e69155509ccffc778b952e674707ff5feaee99237b2a8e022655a78446f4", + "id": "692ebacfcc7d432d903baf237dcd837504ac4ff12b65bbc7730f9a0fa9db6573", + "senderId": "Le6cgc1DdfvvvipRD2F2bu7AAXRr98iW2C" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02e20bcac96c81ac20d34d2f1df75b009e2b6e959002838ff0bf3eac481b71893e", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_44", + "publicKey": "02e20bcac96c81ac20d34d2f1df75b009e2b6e959002838ff0bf3eac481b71893e" + } + }, + "signature": "304402207407898c00f3d86334b1f402f30e12f8baa3b70793bea2fbe71c8b2360bc9409022045aa2300a2be6db590434103a9d1d4b31a7fec2220c21699116c0f7d09b78c17", + "id": "ff34526724380f90f0e7d55b2c5d7293130fb660d878fd5a987bd1b18b8a9ecc", + "senderId": "LREME43JjN8nxf1ngChReBsqgr384E3Bm1" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "021f487af0822286953386b37c8b1e9342ced4e7f82fcef71bd9cf53ddbc8d424f", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_45", + "publicKey": "021f487af0822286953386b37c8b1e9342ced4e7f82fcef71bd9cf53ddbc8d424f" + } + }, + "signature": "304402201c269f4a4bf3e31a271211ec840adfeddfdc30538ed772cdc707e8babd1bef1202207d624354c3386e5493bd3f108afecac7d83f9cf7df5f312105360088096d27c7", + "id": "7d727d381ddf393e29b6d6c28812794fcbe3a9930e6f8077760490d90e4dcedf", + "senderId": "Li44AVjtH2WpWaLvzmoJ7J93h4drDUY5M2" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "021330ae6f4b99bb1d458e21d2916f67e808c02e7844de9ea7e3de4176e509d09a", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_46", + "publicKey": "021330ae6f4b99bb1d458e21d2916f67e808c02e7844de9ea7e3de4176e509d09a" + } + }, + "signature": "30440220747bd14c1bf57ab74af6eedf343632663999ddf933095850faec75bfa722b0b202206c8df3d4afd4d56eb8cca73ef328f31e8a42af6bf53d39d34763daa0e0b9aa7e", + "id": "1ba97ea42ff58ec2ee42858644135c3ae577867f758c94c40e76c4d95e4b45ef", + "senderId": "LVUvfoQXqgYTi2Qe6jwjCh6n3RapAee1mH" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02e191fe67c88bb60c7301784aadcbd2faf5846650d0ed58da752fba2d2ac17e00", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_47", + "publicKey": "02e191fe67c88bb60c7301784aadcbd2faf5846650d0ed58da752fba2d2ac17e00" + } + }, + "signature": "3045022100aed0d6d60080b872b7e47e0fe5808b2c1baa71c56b29634b9677e69b41f50b3c02200284d891351087415c1f178e3b26d0b6d8ff6c38f127ea33f9e6648fd7e6af4b", + "id": "4dbb67e265c87811734cc2b311171b61f27faad42a5df2663942902693bbf608", + "senderId": "LNDn6T5rJoTvCgJmZnwR8djKTbaDWWjFb3" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03705a8fd2a7e1e332908abe55488d0f0303c131664c182df645059f7df8c376cd", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_48", + "publicKey": "03705a8fd2a7e1e332908abe55488d0f0303c131664c182df645059f7df8c376cd" + } + }, + "signature": "304502210086e84c4fdaeca081f627b519d3d01b83cc985d73417a1ed4b97261392757f9cf0220568510c4d026f4e5435e7742bed108abd7f2e8a3f4be0f372ecdf2af9a3285c7", + "id": "4a2fc5531f23dca4013c2364501c703235eece138a4123e918df78e600296685", + "senderId": "LejXXXhiGFZNpg2FQ9MfZvWP6k8nMQfJFA" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "026267f000cf1052ca24f007ea8ef5ff6c4f8eca653dc4bfc920812cb0d62dc068", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_49", + "publicKey": "026267f000cf1052ca24f007ea8ef5ff6c4f8eca653dc4bfc920812cb0d62dc068" + } + }, + "signature": "304402203926bb3ec0cbee3c82936a5a374016c4c65fd70f530f467bdcc5a3b6e20a139a022003013455d2dda7eac98668314cdac8d864bc5cd540f1e6e1b0a3f5da8f2665f7", + "id": "9c4721829f128fa596b89c78ab0b706d5eb754f1d085baf50a7fa39fcac9bc97", + "senderId": "LRmnGA3xa1FrAFwYkess2BC64Jadme1F1J" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "02d5fa7d12347d81dcb0fcc53973a54c7ed162f09f9250999e3c2e2b2ae8a00fff", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_50", + "publicKey": "02d5fa7d12347d81dcb0fcc53973a54c7ed162f09f9250999e3c2e2b2ae8a00fff" + } + }, + "signature": "304402203016386be3ac0e8a6b03cb5df38cbb9650a976b98e930c80b69a41a0cb0ef522022060ff2f7eab154c8900c5341922f653240b55a4f482148d9c3861efa4ff6f0d8b", + "id": "47bb55775d2f607e4c911a9bab7d479ef6a70180e9815193ed368b2ce80e8276", + "senderId": "LSuejPupA82Dw9G8A5oyK17MNXEac7i233" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "03e89897248f4199b9791abd9c7b589508751ebdb2d2d858bcbcdcab33e4aee3c5", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_51", + "publicKey": "03e89897248f4199b9791abd9c7b589508751ebdb2d2d858bcbcdcab33e4aee3c5" + } + }, + "signature": "3044022006eeef34a07de2afc2ab5f1ab94915ecbdc4aa29033cc05939ecfa6645088be10220592ec5aee21731ca384adee75c4910b3f0c5315e3aa1d0fff004e0fe94605c55", + "id": "946fc96eeda69bb5687fd2471cfa5c35b21bd7f66edf5287eba9fb75f7aa24a2", + "senderId": "LaU6fQNU4mWkKt8Veys8RtbNT3h8yk7b3m" + }, + { + "type": 2, + "amount": 0, + "fee": 0, + "recipientId": null, + "senderPublicKey": "025eb7a2c7c278a246829f2c9c9069133136bef6bda92698207491e075af8bd6b1", + "timestamp": 0, + "asset": { + "delegate": { + "username": "genesis_27", + "publicKey": "025eb7a2c7c278a246829f2c9c9069133136bef6bda92698207491e075af8bd6b1" + } + }, + "signature": "3045022100ea4f1e99e4f65c62cee2ba45b5ae05a5bb8b64804eefbbb1194013a70a9539e4022025a97ffc03cdee827c07bc4cab58f77d3b687b32ecb1ff3312ff5a5c438c2a01", + "id": "3e6a525aad267bf00af9b0710814f6ff3fd29bbf4ed770739714406b7515fb30", + "senderId": "LKGt4Cu6N3qB4VCERKsrNZSvicv8U6DVM1" + } + ], + "height": 1, + "id": "18397232937321428396", + "blockSignature": "3045022100aaf3116e3b203f2c96f48ed9337fb72e3a028539484de1f0b7342216619da72f02207d9f1928a1470f12432e5acd8504a323908197731d1640d3c985693f0ada9c82" } diff --git a/packages/crypto/src/networks/testnet/milestones.json b/packages/crypto/src/networks/testnet/milestones.json index 51c32313cf..3c73e9304a 100644 --- a/packages/crypto/src/networks/testnet/milestones.json +++ b/packages/crypto/src/networks/testnet/milestones.json @@ -7,9 +7,10 @@ "block": { "version": 0, "maxTransactions": 150, - "maxPayload": 2097152 + "maxPayload": 6291456, + "idFullSha256": true }, - "epoch": "2017-03-21T13:00:00.000Z", + "epoch": "2018-01-01T00:00:00.000Z", "fees": { "staticFees": { "transfer": 10000000, @@ -22,10 +23,23 @@ "multiPayment": 0, "delegateResignation": 0 } - } + }, + "vendorFieldLength": 255 + }, + { + "height": 151200, + "reward": 800000000 }, { - "height": 75600, + "height": 8040600, + "reward": 400000000 + }, + { + "height": 15930000, "reward": 200000000 + }, + { + "height": 23819400, + "reward": 100000000 } ] diff --git a/packages/crypto/src/networks/testnet/network.json b/packages/crypto/src/networks/testnet/network.json index 0f373e462e..eeb2db5173 100644 --- a/packages/crypto/src/networks/testnet/network.json +++ b/packages/crypto/src/networks/testnet/network.json @@ -1,17 +1,18 @@ { "name": "testnet", - "messagePrefix": "TEST message:\n", + "messagePrefix": "TPRSN message:\n", "bip32": { "public": 70617039, "private": 70615956 }, - "pubKeyHash": 23, - "nethash": "d9acd04bde4234a81addb8482333b4ac906bed7be5a9970ce8ada428bd083192", - "wif": 186, + "pubKeyHash": 66, + "nethash": "527df5a4bf0fbd0dbe4b6c0a255c14a8451f4f3144afdf82bcb65b68ff963114", + "wif": 187, + "slip44": 111, "aip20": 0, "client": { - "token": "TARK", - "symbol": "TѦ", - "explorer": "http://texplorer.ark.io" + "token": "TPRSN", + "symbol": "TP", + "explorer": "http://texplorer.persona.im" } } diff --git a/packages/crypto/src/networks/unitnet/milestones.json b/packages/crypto/src/networks/unitnet/milestones.json index 51c32313cf..00c4fac52a 100644 --- a/packages/crypto/src/networks/unitnet/milestones.json +++ b/packages/crypto/src/networks/unitnet/milestones.json @@ -22,10 +22,21 @@ "multiPayment": 0, "delegateResignation": 0 } - } + }, + "vendorFieldLength": 64 }, { "height": 75600, "reward": 200000000 + }, + { + "height": 100000, + "vendorFieldLength": 255 + }, + { + "height": 4000000, + "block": { + "idFullSha256": true + } } ] diff --git a/packages/crypto/src/networks/unitnet/network.json b/packages/crypto/src/networks/unitnet/network.json index 68f42e50e4..79638ab80c 100644 --- a/packages/crypto/src/networks/unitnet/network.json +++ b/packages/crypto/src/networks/unitnet/network.json @@ -8,6 +8,7 @@ "pubKeyHash": 23, "nethash": "a63b5a3858afbca23edefac885be74d59f1a26985548a4082f4f479e74fcc348", "wif": 186, + "slip44": 1, "aip20": 0, "client": { "token": "UARK", diff --git a/packages/crypto/src/serializers/index.ts b/packages/crypto/src/serializers/index.ts deleted file mode 100644 index 6e622c69da..0000000000 --- a/packages/crypto/src/serializers/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { transactionSerializer as TransactionSerializer } from "./transaction"; -export { blockSerializer as BlockSerializer } from "./block"; diff --git a/packages/crypto/src/serializers/transaction.ts b/packages/crypto/src/serializers/transaction.ts deleted file mode 100644 index 0e0fd671ae..0000000000 --- a/packages/crypto/src/serializers/transaction.ts +++ /dev/null @@ -1,149 +0,0 @@ -import bs58check from "bs58check"; -import ByteBuffer from "bytebuffer"; -import { TransactionTypes } from "../constants"; -import { TransactionTypeError } from "../errors"; -import { configManager } from "../managers"; -import { Transaction } from "../models"; -import { ITransactionData } from "../models/transaction"; -import { Bignum } from "../utils"; - -// Reference: https://github.com/ArkEcosystem/AIPs/blob/master/AIPS/aip-11.md -class TransactionSerializer { - public serialize(transaction: ITransactionData): Buffer { - const buffer = new ByteBuffer(512, true); - - this.serializeCommon(transaction, buffer); - this.serializeVendorField(transaction, buffer); - this.serializeType(transaction, buffer); - this.serializeSignatures(transaction, buffer); - - return Buffer.from(buffer.flip().toBuffer()); - } - - private serializeCommon(transaction: ITransactionData, buffer: ByteBuffer): void { - buffer.writeByte(0xff); // fill, to disambiguate from v1 - buffer.writeByte(transaction.version || 0x01); // version - buffer.writeByte(transaction.network || configManager.get("pubKeyHash")); // ark = 0x17, devnet = 0x30 - buffer.writeByte(transaction.type); - buffer.writeUint32(transaction.timestamp); - buffer.append(transaction.senderPublicKey, "hex"); - buffer.writeUint64(+new Bignum(transaction.fee).toFixed()); - } - - private serializeVendorField(transaction: ITransactionData, buffer: ByteBuffer): void { - if (Transaction.canHaveVendorField(transaction.type)) { - if (transaction.vendorField) { - const vf = Buffer.from(transaction.vendorField, "utf8"); - buffer.writeByte(vf.length); - buffer.append(vf); - } else if (transaction.vendorFieldHex) { - buffer.writeByte(transaction.vendorFieldHex.length / 2); - buffer.append(transaction.vendorFieldHex, "hex"); - } else { - buffer.writeByte(0x00); - } - } else { - buffer.writeByte(0x00); - } - } - - private serializeType(transaction: ITransactionData, buffer: ByteBuffer): void { - if (transaction.type === TransactionTypes.Transfer) { - this.serializeTransfer(transaction, buffer); - } else if (transaction.type === TransactionTypes.SecondSignature) { - this.serializeSecondSignature(transaction, buffer); - } else if (transaction.type === TransactionTypes.DelegateRegistration) { - this.serializeDelegateRegistration(transaction, buffer); - } else if (transaction.type === TransactionTypes.Vote) { - this.serializeVote(transaction, buffer); - } else if (transaction.type === TransactionTypes.MultiSignature) { - this.serializeMultiSignature(transaction, buffer); - } else if (transaction.type === TransactionTypes.Ipfs) { - this.serializeIpfs(transaction, buffer); - } else if (transaction.type === TransactionTypes.TimelockTransfer) { - this.serializeTimelockTransfer(transaction, buffer); - } else if (transaction.type === TransactionTypes.MultiPayment) { - this.serializeMultiPayment(transaction, buffer); - } else if (transaction.type === TransactionTypes.DelegateResignation) { - this.serializeDelegateResignation(transaction, buffer); - } else { - throw new TransactionTypeError(transaction.type); - } - } - - private serializeTransfer(transaction: ITransactionData, buffer: ByteBuffer): void { - buffer.writeUint64(+new Bignum(transaction.amount).toFixed()); - buffer.writeUint32(transaction.expiration || 0); - buffer.append(bs58check.decode(transaction.recipientId)); - } - - private serializeSecondSignature(transaction: ITransactionData, buffer: ByteBuffer): void { - buffer.append(transaction.asset.signature.publicKey, "hex"); - } - - private serializeDelegateRegistration(transaction: ITransactionData, buffer: ByteBuffer): void { - const delegateBytes = Buffer.from(transaction.asset.delegate.username, "utf8"); - buffer.writeByte(delegateBytes.length); - buffer.append(delegateBytes, "hex"); - } - - private serializeVote(transaction: ITransactionData, buffer: ByteBuffer): void { - const voteBytes = transaction.asset.votes.map(vote => (vote[0] === "+" ? "01" : "00") + vote.slice(1)).join(""); - buffer.writeByte(transaction.asset.votes.length); - buffer.append(voteBytes, "hex"); - } - - private serializeMultiSignature(transaction: ITransactionData, buffer: ByteBuffer): void { - let joined = null; - - if (!transaction.version || transaction.version === 1) { - joined = transaction.asset.multisignature.keysgroup.map(k => (k[0] === "+" ? k.slice(1) : k)).join(""); - } else { - joined = transaction.asset.multisignature.keysgroup.join(""); - } - - const keysgroupBuffer = Buffer.from(joined, "hex"); - buffer.writeByte(transaction.asset.multisignature.min); - buffer.writeByte(transaction.asset.multisignature.keysgroup.length); - buffer.writeByte(transaction.asset.multisignature.lifetime); - buffer.append(keysgroupBuffer, "hex"); - } - - private serializeIpfs(transaction: ITransactionData, buffer: ByteBuffer): void { - buffer.writeByte(transaction.asset.ipfs.dag.length / 2); - buffer.append(transaction.asset.ipfs.dag, "hex"); - } - - private serializeTimelockTransfer(transaction: ITransactionData, buffer: ByteBuffer): void { - buffer.writeUint64(+new Bignum(transaction.amount).toFixed()); - buffer.writeByte(transaction.timelockType); - buffer.writeUint64(transaction.timelock); - buffer.append(bs58check.decode(transaction.recipientId)); - } - - private serializeMultiPayment(transaction: ITransactionData, buffer: ByteBuffer): void { - buffer.writeUint32(transaction.asset.payments.length); - transaction.asset.payments.forEach(p => { - buffer.writeUint64(+new Bignum(p.amount).toFixed()); - buffer.append(bs58check.decode(p.recipientId)); - }); - } - - private serializeDelegateResignation(transaction: ITransactionData, buffer: ByteBuffer): void { - return; - } - - private serializeSignatures(transaction: ITransactionData, buffer: ByteBuffer): void { - if (transaction.signature) { - buffer.append(transaction.signature, "hex"); - } - - if (transaction.secondSignature) { - buffer.append(transaction.secondSignature, "hex"); - } else if (transaction.signSignature) { - buffer.append(transaction.signSignature, "hex"); - } - } -} - -export const transactionSerializer = new TransactionSerializer(); diff --git a/packages/crypto/src/transactions/deserializers/block.ts b/packages/crypto/src/transactions/deserializers/block.ts new file mode 100644 index 0000000000..337c367c99 --- /dev/null +++ b/packages/crypto/src/transactions/deserializers/block.ts @@ -0,0 +1,100 @@ +import ByteBuffer from "bytebuffer"; +import { configManager } from "../../managers"; +import { Block, IBlockData } from "../../models/block"; +import { Bignum } from "../../utils"; +import { Transaction } from "../types"; + +class BlockDeserializer { + public deserialize( + serializedHex: string, + headerOnly: boolean = false, + ): { data: IBlockData; transactions: Transaction[] } { + const block = {} as IBlockData; + let transactions: Transaction[] = []; + + const buf = ByteBuffer.fromHex(serializedHex, true); + + this.deserializeHeader(block, buf); + + headerOnly = headerOnly || buf.remaining() === 0; + if (!headerOnly) { + transactions = this.deserializeTransactions(block, buf); + } + + block.idHex = Block.getIdHex(block); + block.id = Block.getId(block); + + const { outlookTable } = configManager.config.exceptions; + if (outlookTable && outlookTable[block.id]) { + const constants = configManager.getMilestone(block.height); + if (constants.block.idFullSha256) { + block.id = outlookTable[block.id]; + block.idHex = block.id; + } else { + block.id = outlookTable[block.id]; + block.idHex = Block.toBytesHex(block.id); + } + } + + // FIXME: only a workaround + return { data: block, transactions }; + } + + private deserializeHeader(block: IBlockData, buf: ByteBuffer): void { + block.version = buf.readUint32(); + block.timestamp = buf.readUint32(); + block.height = buf.readUint32(); + + const constants = configManager.getMilestone(block.height - 1); + + if (constants.block.idFullSha256) { + block.previousBlockHex = buf.readBytes(32).toString("hex"); + block.previousBlock = block.previousBlockHex; + } else { + block.previousBlockHex = buf.readBytes(8).toString("hex"); + block.previousBlock = new Bignum(block.previousBlockHex, 16).toFixed(); + } + + block.numberOfTransactions = buf.readUint32(); + block.totalAmount = new Bignum(buf.readUint64().toString()); + block.totalFee = new Bignum(buf.readUint64().toString()); + block.reward = new Bignum(buf.readUint64().toString()); + block.payloadLength = buf.readUint32(); + block.payloadHash = buf.readBytes(32).toString("hex"); + block.generatorPublicKey = buf.readBytes(33).toString("hex"); + + const signatureLength = (): number => { + buf.mark(); + const lengthHex = buf + .skip(1) + .readBytes(1) + .toString("hex"); + buf.reset(); + + return parseInt(lengthHex, 16) + 2; + }; + + block.blockSignature = buf.readBytes(signatureLength()).toString("hex"); + } + + private deserializeTransactions(block: IBlockData, buf: ByteBuffer): Transaction[] { + const transactionLengths = []; + + for (let i = 0; i < block.numberOfTransactions; i++) { + transactionLengths.push(buf.readUint32()); + } + + const transactions: Transaction[] = []; + block.transactions = []; + transactionLengths.forEach(length => { + const transactionBytes = buf.readBytes(length).toBuffer(); + const transaction = Transaction.fromBytes(transactionBytes); + transactions.push(transaction); + block.transactions.push(transaction.data); + }); + + return transactions; + } +} + +export const blockDeserializer = new BlockDeserializer(); diff --git a/packages/crypto/src/deserializers/index.ts b/packages/crypto/src/transactions/deserializers/index.ts similarity index 100% rename from packages/crypto/src/deserializers/index.ts rename to packages/crypto/src/transactions/deserializers/index.ts diff --git a/packages/crypto/src/transactions/deserializers/transaction.ts b/packages/crypto/src/transactions/deserializers/transaction.ts new file mode 100644 index 0000000000..bf1dc7eeca --- /dev/null +++ b/packages/crypto/src/transactions/deserializers/transaction.ts @@ -0,0 +1,127 @@ +import ByteBuffer from "bytebuffer"; +import { Transaction, TransactionRegistry } from ".."; +import { TransactionTypes } from "../../constants"; +import { crypto } from "../../crypto"; +import { TransactionVersionError } from "../../errors"; +import { configManager } from "../../managers"; +import { Bignum } from "../../utils"; +import { ITransactionData } from "../interfaces"; + +// Reference: https://github.com/ARKEcosystem/AIPs/blob/master/AIPS/aip-11.md +class TransactionDeserializer { + public deserialize(serialized: string | Buffer): Transaction { + const data = {} as ITransactionData; + + const buffer = this.getByteBuffer(serialized); + this.deserializeCommon(data, buffer); + + const instance = TransactionRegistry.create(data); + this.deserializeVendorField(instance, buffer); + + // Deserialize type specific parts + instance.deserialize(buffer); + + this.deserializeSignatures(data, buffer); + + switch (data.version) { + case 1: + this.applyV1Compatibility(data); + break; + default: + throw new TransactionVersionError(data.version); + } + + instance.serialized = buffer.flip().toBuffer(); + + return instance; + } + + private deserializeCommon(transaction: ITransactionData, buf: ByteBuffer): void { + buf.skip(1); // Skip 0xFF marker + transaction.version = buf.readUint8(); + transaction.network = buf.readUint8(); + transaction.type = buf.readUint8(); + transaction.timestamp = buf.readUint32(); + transaction.senderPublicKey = buf.readBytes(33).toString("hex"); + transaction.fee = new Bignum(buf.readUint64().toString()); + transaction.amount = Bignum.ZERO; + } + + private deserializeVendorField(transaction: Transaction, buf: ByteBuffer): void { + const vendorFieldLength = buf.readUint8(); + if (vendorFieldLength > 0) { + transaction.data.vendorFieldHex = buf.readBytes(vendorFieldLength).toString("hex"); + } + } + + private deserializeSignatures(transaction: ITransactionData, buf: ByteBuffer) { + const currentSignatureLength = (): number => { + buf.mark(); + const lengthHex = buf + .skip(1) + .readBytes(1) + .toString("hex"); + buf.reset(); + + return parseInt(lengthHex, 16) + 2; + }; + + // Signature + if (buf.remaining()) { + const signatureLength = currentSignatureLength(); + transaction.signature = buf.readBytes(signatureLength).toString("hex"); + } + + const beginningMultiSignature = () => { + buf.mark(); + const marker = buf.readUint8(); + buf.reset(); + return marker === 255; + }; + + // Second Signature + if (buf.remaining() && !beginningMultiSignature()) { + const secondSignatureLength = currentSignatureLength(); + transaction.secondSignature = buf.readBytes(secondSignatureLength).toString("hex"); + } + + // Multi Signatures + if (buf.remaining() && beginningMultiSignature()) { + buf.skip(1); + const multiSignature = buf.readBytes(buf.limit - buf.offset).toString("hex"); + transaction.signatures = [multiSignature]; + } + } + + // tslint:disable-next-line:member-ordering + public applyV1Compatibility(transaction: ITransactionData): void { + transaction.secondSignature = transaction.secondSignature || transaction.signSignature; + + if (transaction.type === TransactionTypes.Vote) { + transaction.recipientId = crypto.getAddress(transaction.senderPublicKey, transaction.network); + } else if (transaction.type === TransactionTypes.MultiSignature) { + transaction.asset.multisignature.keysgroup = transaction.asset.multisignature.keysgroup.map(k => + k.startsWith("+") ? k : `+${k}`, + ); + } + + if (transaction.vendorFieldHex) { + transaction.vendorField = Buffer.from(transaction.vendorFieldHex, "hex").toString("utf8"); + } + } + + private getByteBuffer(serialized: Buffer | string): ByteBuffer { + let buffer: ByteBuffer; + if (serialized instanceof Buffer) { + buffer = new ByteBuffer(serialized.length, true); + buffer.append(serialized); + buffer.reset(); + } else { + buffer = ByteBuffer.fromHex(serialized, true); + } + + return buffer; + } +} + +export const transactionDeserializer = new TransactionDeserializer(); diff --git a/packages/crypto/src/transactions/index.ts b/packages/crypto/src/transactions/index.ts new file mode 100644 index 0000000000..d2c3cad173 --- /dev/null +++ b/packages/crypto/src/transactions/index.ts @@ -0,0 +1,5 @@ +export * from "./interfaces"; +export * from "./types"; +export * from "./deserializers"; +export * from "./serializers"; +export { transactionRegistry as TransactionRegistry, TransactionConstructor } from "./registry"; diff --git a/packages/crypto/src/transactions/interfaces.ts b/packages/crypto/src/transactions/interfaces.ts new file mode 100644 index 0000000000..fa1ba09bba --- /dev/null +++ b/packages/crypto/src/transactions/interfaces.ts @@ -0,0 +1,64 @@ +import { TransactionTypes } from "../constants"; +import { Bignum } from "../utils"; + +export interface ITransactionAsset { + signature?: { + publicKey: string; + }; + delegate?: { + username: string; + publicKey?: string; + }; + votes?: string[]; + multisignature?: IMultiSignatureAsset; + ipfs?: { + dag: string; + }; + payments?: any; + [custom: string]: any; +} + +export interface IMultiSignatureAsset { + min: number; + keysgroup: string[]; + lifetime: number; +} + +export interface ITransactionData { + version?: number; + network?: number; + + type: TransactionTypes; + timestamp: number; + senderPublicKey: string; + + fee: Bignum | number | string; + amount: Bignum | number | string; + + expiration?: number; + recipientId?: string; + + asset?: ITransactionAsset; + vendorField?: string; + vendorFieldHex?: string; + + id?: string; + signature?: string; + secondSignature?: string; + signSignature?: string; + signatures?: string[]; + + blockId?: string; + sequence?: number; + + timelock?: any; + timelockType?: number; + + ipfsHash?: string; + payments?: { [key: string]: any }; +} + +export interface ISchemaValidationResult { + value: T; + error: any; +} diff --git a/packages/crypto/src/transactions/registry.ts b/packages/crypto/src/transactions/registry.ts new file mode 100644 index 0000000000..d807dff8d3 --- /dev/null +++ b/packages/crypto/src/transactions/registry.ts @@ -0,0 +1,118 @@ +import camelCase from "lodash.camelcase"; +import { TransactionTypes } from "../constants"; +import { + MissingMilestoneFeeError, + TransactionAlreadyRegisteredError, + TransactionTypeInvalidRangeError, + UnkownTransactionError, +} from "../errors"; +import { configManager } from "../managers"; +import { feeManager } from "../managers/fee"; +import { AjvWrapper } from "../validation"; +import { ITransactionData } from "./interfaces"; +import { + DelegateRegistrationTransaction, + DelegateResignationTransaction, + IpfsTransaction, + MultiPaymentTransaction, + MultiSignatureRegistrationTransaction, + SecondSignatureRegistrationTransaction, + TimelockTransferTransaction, + Transaction, + TransferTransaction, + VoteTransaction, +} from "./types"; + +export type TransactionConstructor = typeof Transaction; + +class TransactionRegistry { + private readonly coreTypes = new Map(); + private readonly customTypes = new Map(); + + constructor() { + this.registerCoreType(TransferTransaction); + this.registerCoreType(SecondSignatureRegistrationTransaction); + this.registerCoreType(DelegateRegistrationTransaction); + this.registerCoreType(VoteTransaction); + this.registerCoreType(MultiSignatureRegistrationTransaction); + this.registerCoreType(IpfsTransaction); + this.registerCoreType(TimelockTransferTransaction); + this.registerCoreType(MultiPaymentTransaction); + this.registerCoreType(DelegateResignationTransaction); + } + + public create(data: ITransactionData): Transaction { + const instance = new (this.get(data.type) as any)() as Transaction; + instance.data = data; + + return instance; + } + + public get(type: TransactionTypes | number): TransactionConstructor { + if (this.coreTypes.has(type)) { + return this.coreTypes.get(type); + } + + if (this.customTypes.has(type)) { + return this.customTypes.get(type); + } + + throw new UnkownTransactionError(type); + } + + public registerCustomType(constructor: TransactionConstructor): void { + const { type } = constructor; + if (this.customTypes.has(type)) { + throw new TransactionAlreadyRegisteredError(constructor.name); + } + + if (type < 100) { + throw new TransactionTypeInvalidRangeError(type); + } + + this.customTypes.set(type, constructor); + this.updateSchemas(constructor); + this.updateStaticFees(); + } + + public deregisterCustomType(type: number): void { + if (this.customTypes.has(type)) { + const schema = this.customTypes.get(type); + this.updateSchemas(schema, true); + this.customTypes.delete(type); + } + } + + public updateStaticFees(height?: number): void { + const customConstructors = Array.from(this.customTypes.values()); + const milestone = configManager.getMilestone(height); + const { staticFees } = milestone.fees; + for (const constructor of customConstructors) { + const { type, name } = constructor; + if (milestone.fees && milestone.fees.staticFees) { + const value = staticFees[camelCase(name.replace("Transaction", ""))]; + if (!value) { + throw new MissingMilestoneFeeError(name); + } + + feeManager.set(type, value); + } + } + } + + private registerCoreType(constructor: TransactionConstructor) { + const { type } = constructor; + if (this.coreTypes.has(type)) { + throw new TransactionAlreadyRegisteredError(constructor.name); + } + + this.coreTypes.set(type, constructor); + this.updateSchemas(constructor); + } + + private updateSchemas(transaction: TransactionConstructor, remove?: boolean) { + AjvWrapper.extendTransaction(transaction.getSchema(), remove); + } +} + +export const transactionRegistry = new TransactionRegistry(); diff --git a/packages/crypto/src/serializers/block.ts b/packages/crypto/src/transactions/serializers/block.ts similarity index 68% rename from packages/crypto/src/serializers/block.ts rename to packages/crypto/src/transactions/serializers/block.ts index 114af78989..8cfd7c0ece 100644 --- a/packages/crypto/src/serializers/block.ts +++ b/packages/crypto/src/transactions/serializers/block.ts @@ -1,7 +1,9 @@ import ByteBuffer from "bytebuffer"; -import { Transaction } from "../models"; -import { Block, IBlockData } from "../models/block"; -import { Bignum } from "../utils"; +import { PreviousBlockIdFormatError } from "../../errors"; +import { configManager } from "../../managers/config"; +import { Block, IBlockData } from "../../models/block"; +import { Bignum } from "../../utils"; +import { Transaction } from "../types"; class BlockSerializer { public serializeFull(block: IBlockData): Buffer { @@ -15,12 +17,12 @@ class BlockSerializer { .skip(transactions.length * 4); for (let i = 0; i < transactions.length; i++) { - const serialized: any = Transaction.serialize(transactions[i]); + const serialized = Transaction.toBytes(transactions[i]); buffer.writeUint32(serialized.length, serializedHeader.length + i * 4); buffer.append(serialized); } - return Buffer.from(buffer.flip().toBuffer()); + return buffer.flip().toBuffer(); } public serialize(block: IBlockData, includeSignature: boolean = true): Buffer { @@ -32,11 +34,20 @@ class BlockSerializer { this.serializeSignature(block, buffer); } - return Buffer.from(buffer.flip().toBuffer()); + return buffer.flip().toBuffer(); } - private serializeHeader(block: IBlockData, buffer: ByteBuffer): any { - block.previousBlockHex = Block.toBytesHex(block.previousBlock); + private serializeHeader(block: IBlockData, buffer: ByteBuffer): void { + const constants = configManager.getMilestone(block.height - 1); + + if (constants.block.idFullSha256) { + if (block.previousBlock.length !== 64) { + throw new PreviousBlockIdFormatError(block.height, block.previousBlock); + } + block.previousBlockHex = block.previousBlock; + } else { + block.previousBlockHex = Block.toBytesHex(block.previousBlock); + } buffer.writeUint32(block.version); buffer.writeUint32(block.timestamp); @@ -51,7 +62,7 @@ class BlockSerializer { buffer.append(block.generatorPublicKey, "hex"); } - private serializeSignature(block: IBlockData, buffer: ByteBuffer): any { + private serializeSignature(block: IBlockData, buffer: ByteBuffer): void { if (block.blockSignature) { buffer.append(block.blockSignature, "hex"); } diff --git a/packages/crypto/src/transactions/serializers/index.ts b/packages/crypto/src/transactions/serializers/index.ts new file mode 100644 index 0000000000..1f33e46a92 --- /dev/null +++ b/packages/crypto/src/transactions/serializers/index.ts @@ -0,0 +1,2 @@ +export * from "./transaction"; +export { blockSerializer as BlockSerializer } from "./block"; diff --git a/packages/crypto/src/transactions/serializers/transaction.ts b/packages/crypto/src/transactions/serializers/transaction.ts new file mode 100644 index 0000000000..0bcfd23260 --- /dev/null +++ b/packages/crypto/src/transactions/serializers/transaction.ts @@ -0,0 +1,238 @@ +/* tslint:disable:no-shadowed-variable */ + +import bs58check from "bs58check"; +import ByteBuffer from "bytebuffer"; +import { TransactionTypes } from "../../constants"; +import { TransactionVersionError } from "../../errors"; +import { Address } from "../../identities"; +import { configManager } from "../../managers"; +import { Bignum } from "../../utils"; +import { ITransactionData } from "../interfaces"; +import { Transaction } from "../types"; + +export interface ISerializeOptions { + excludeSignature?: boolean; + excludeSecondSignature?: boolean; +} + +// Reference: https://github.com/ARKEcosystem/AIPs/blob/master/AIPS/aip-11.md +export class TransactionSerializer { + public static getBytes(transaction: ITransactionData, options?: ISerializeOptions): Buffer { + const version = transaction.version || 1; + + switch (version) { + case 1: + return this.getBytesV1(transaction, options); + default: + throw new TransactionVersionError(version); + } + } + + /** + * Serializes the given transaction according to AIP11. + */ + public static serialize(transaction: Transaction): Buffer { + const buffer = new ByteBuffer(512, true); + const { data } = transaction; + + this.serializeCommon(data, buffer); + this.serializeVendorField(transaction, buffer); + + const typeBuffer = transaction.serialize().flip(); + buffer.append(typeBuffer); + + this.serializeSignatures(data, buffer); + + const flippedBuffer = buffer.flip().toBuffer(); + transaction.serialized = flippedBuffer; + + return flippedBuffer; + } + + /** + * Serializes the given transaction prior to AIP11 (legacy). + */ + private static getBytesV1(transaction: ITransactionData, options: ISerializeOptions = {}): Buffer { + let assetSize = 0; + let assetBytes = null; + + switch (transaction.type) { + case TransactionTypes.SecondSignature: { + const { signature } = transaction.asset; + const bb = new ByteBuffer(33, true); + const publicKeyBuffer = Buffer.from(signature.publicKey, "hex"); + + for (const byte of publicKeyBuffer) { + bb.writeByte(byte); + } + + bb.flip(); + + assetBytes = new Uint8Array(bb.toArrayBuffer()); + assetSize = assetBytes.length; + break; + } + + case TransactionTypes.DelegateRegistration: { + assetBytes = Buffer.from(transaction.asset.delegate.username, "utf8"); + assetSize = assetBytes.length; + break; + } + + case TransactionTypes.Vote: { + if (transaction.asset.votes !== null) { + assetBytes = Buffer.from(transaction.asset.votes.join(""), "utf8"); + assetSize = assetBytes.length; + } + break; + } + + case TransactionTypes.MultiSignature: { + const keysgroupBuffer = Buffer.from(transaction.asset.multisignature.keysgroup.join(""), "utf8"); + const bb = new ByteBuffer(1 + 1 + keysgroupBuffer.length, true); + + bb.writeByte(transaction.asset.multisignature.min); + bb.writeByte(transaction.asset.multisignature.lifetime); + + for (const byte of keysgroupBuffer) { + bb.writeByte(byte); + } + + bb.flip(); + + assetBytes = bb.toBuffer(); + assetSize = assetBytes.length; + break; + } + } + + const bb = new ByteBuffer(1 + 4 + 32 + 8 + 8 + 21 + 64 + 64 + 64 + assetSize, true); + bb.writeByte(transaction.type); + bb.writeInt(transaction.timestamp); + + const senderPublicKeyBuffer = Buffer.from(transaction.senderPublicKey, "hex"); + for (const byte of senderPublicKeyBuffer) { + bb.writeByte(byte); + } + + // Apply fix for broken type 1 and 4 transactions, which were + // erroneously calculated with a recipient id. + const { transactionIdFixTable } = configManager.get("exceptions"); + const isBrokenTransaction = + transactionIdFixTable && Object.values(transactionIdFixTable).includes(transaction.id); + if (isBrokenTransaction || (transaction.recipientId && transaction.type !== 1 && transaction.type !== 4)) { + const recipientId = + transaction.recipientId || Address.fromPublicKey(transaction.senderPublicKey, transaction.network); + const recipient = bs58check.decode(recipientId); + for (const byte of recipient) { + bb.writeByte(byte); + } + } else { + for (let i = 0; i < 21; i++) { + bb.writeByte(0); + } + } + + if (transaction.vendorFieldHex) { + const vf = Buffer.from(transaction.vendorFieldHex, "hex"); + const fillstart = vf.length; + for (let i = 0; i < fillstart; i++) { + bb.writeByte(vf[i]); + } + for (let i = fillstart; i < 64; i++) { + bb.writeByte(0); + } + } else if (transaction.vendorField) { + const vf = Buffer.from(transaction.vendorField); + const fillstart = vf.length; + for (let i = 0; i < fillstart; i++) { + bb.writeByte(vf[i]); + } + for (let i = fillstart; i < 64; i++) { + bb.writeByte(0); + } + } else { + for (let i = 0; i < 64; i++) { + bb.writeByte(0); + } + } + + bb.writeInt64(+new Bignum(transaction.amount).toFixed()); + bb.writeInt64(+new Bignum(transaction.fee).toFixed()); + + if (assetSize > 0) { + for (let i = 0; i < assetSize; i++) { + bb.writeByte(assetBytes[i]); + } + } + + if (!options.excludeSignature && transaction.signature) { + const signatureBuffer = Buffer.from(transaction.signature, "hex"); + for (const byte of signatureBuffer) { + bb.writeByte(byte); + } + } + + if (!options.excludeSecondSignature && transaction.secondSignature) { + const signSignatureBuffer = Buffer.from(transaction.secondSignature, "hex"); + for (const byte of signSignatureBuffer) { + bb.writeByte(byte); + } + } + + bb.flip(); + const arrayBuffer = new Uint8Array(bb.toArrayBuffer()); + const buffer = []; + + for (let i = 0; i < arrayBuffer.length; i++) { + buffer[i] = arrayBuffer[i]; + } + + return Buffer.from(buffer); + } + + private static serializeCommon(transaction: ITransactionData, buffer: ByteBuffer): void { + buffer.writeByte(0xff); // fill, to disambiguate from v1 + buffer.writeByte(transaction.version || 0x01); // version + buffer.writeByte(transaction.network || configManager.get("pubKeyHash")); // ark = 0x17, devnet = 0x30 + buffer.writeByte(transaction.type); + buffer.writeUint32(transaction.timestamp); + buffer.append(transaction.senderPublicKey, "hex"); + buffer.writeUint64(+transaction.fee); + } + + private static serializeVendorField(transaction: Transaction, buffer: ByteBuffer): void { + if (transaction.hasVendorField()) { + const { data } = transaction; + if (data.vendorField) { + const vf = Buffer.from(data.vendorField, "utf8"); + buffer.writeByte(vf.length); + buffer.append(vf); + } else if (data.vendorFieldHex) { + buffer.writeByte(data.vendorFieldHex.length / 2); + buffer.append(data.vendorFieldHex, "hex"); + } else { + buffer.writeByte(0x00); + } + } else { + buffer.writeByte(0x00); + } + } + + private static serializeSignatures(transaction: ITransactionData, buffer: ByteBuffer): void { + if (transaction.signature) { + buffer.append(transaction.signature, "hex"); + } + + if (transaction.secondSignature) { + buffer.append(transaction.secondSignature, "hex"); + } else if (transaction.signSignature) { + buffer.append(transaction.signSignature, "hex"); + } + + if (transaction.signatures) { + buffer.append("ff", "hex"); // 0xff separator to signal start of multi-signature transactions + buffer.append(transaction.signatures.join(""), "hex"); + } + } +} diff --git a/packages/crypto/src/transactions/types/delegate-registration.ts b/packages/crypto/src/transactions/types/delegate-registration.ts new file mode 100644 index 0000000000..9ad84210d5 --- /dev/null +++ b/packages/crypto/src/transactions/types/delegate-registration.ts @@ -0,0 +1,34 @@ +import ByteBuffer from "bytebuffer"; +import { TransactionTypes } from "../../constants"; +import * as schemas from "./schemas"; +import { Transaction } from "./transaction"; + +export class DelegateRegistrationTransaction extends Transaction { + public static type: TransactionTypes = TransactionTypes.DelegateRegistration; + + public static getSchema(): schemas.TransactionSchema { + return schemas.delegateRegistration; + } + + public serialize(): ByteBuffer { + const { data } = this; + const delegateBytes = Buffer.from(data.asset.delegate.username, "utf8"); + const buffer = new ByteBuffer(delegateBytes.length, true); + + buffer.writeByte(delegateBytes.length); + buffer.append(delegateBytes, "hex"); + + return buffer; + } + + public deserialize(buf: ByteBuffer): void { + const { data } = this; + const usernamelength = buf.readUint8(); + + data.asset = { + delegate: { + username: buf.readString(usernamelength), + }, + }; + } +} diff --git a/packages/crypto/src/transactions/types/delegate-resignation.ts b/packages/crypto/src/transactions/types/delegate-resignation.ts new file mode 100644 index 0000000000..276e724fe4 --- /dev/null +++ b/packages/crypto/src/transactions/types/delegate-resignation.ts @@ -0,0 +1,20 @@ +import ByteBuffer from "bytebuffer"; +import { TransactionTypes } from "../../constants"; +import * as schemas from "./schemas"; +import { Transaction } from "./transaction"; + +export class DelegateResignationTransaction extends Transaction { + public static type: TransactionTypes = TransactionTypes.DelegateResignation; + + public static getSchema(): schemas.TransactionSchema { + return schemas.delegateResignation; + } + + public serialize(): ByteBuffer { + return new ByteBuffer(0); + } + + public deserialize(buf: ByteBuffer): void { + return; + } +} diff --git a/packages/crypto/src/transactions/types/index.ts b/packages/crypto/src/transactions/types/index.ts new file mode 100644 index 0000000000..e2dfe4ae6b --- /dev/null +++ b/packages/crypto/src/transactions/types/index.ts @@ -0,0 +1,13 @@ +export * from "./transaction"; +export * from "./transfer"; +export * from "./second-signature"; +export * from "./delegate-registration"; +export * from "./vote"; +export * from "./multi-signature"; +export * from "./ipfs"; +export * from "./timelock-transfer"; +export * from "./multi-payment"; +export * from "./delegate-resignation"; + +import * as schemas from "./schemas"; +export { schemas }; diff --git a/packages/crypto/src/transactions/types/ipfs.ts b/packages/crypto/src/transactions/types/ipfs.ts new file mode 100644 index 0000000000..2764a2e3d7 --- /dev/null +++ b/packages/crypto/src/transactions/types/ipfs.ts @@ -0,0 +1,32 @@ +import ByteBuffer from "bytebuffer"; +import { TransactionTypes } from "../../constants"; +import * as schemas from "./schemas"; +import { Transaction } from "./transaction"; + +export class IpfsTransaction extends Transaction { + public static type: TransactionTypes = TransactionTypes.Ipfs; + + public static getSchema(): schemas.TransactionSchema { + return schemas.ipfs; + } + + public serialize(): ByteBuffer { + const { data } = this; + const buffer = new ByteBuffer(1 + data.asset.ipfs.dag.length / 2, true); + + buffer.writeByte(data.asset.ipfs.dag.length / 2); + buffer.append(data.asset.ipfs.dag, "hex"); + + return buffer; + } + + public deserialize(buf: ByteBuffer): void { + const { data } = this; + const dagLength = buf.readUint8(); + data.asset = { + ipfs: { + dag: buf.readBytes(dagLength).toString("hex"), + }, + }; + } +} diff --git a/packages/crypto/src/transactions/types/multi-payment.ts b/packages/crypto/src/transactions/types/multi-payment.ts new file mode 100644 index 0000000000..d28033269e --- /dev/null +++ b/packages/crypto/src/transactions/types/multi-payment.ts @@ -0,0 +1,44 @@ +import bs58check from "bs58check"; +import ByteBuffer from "bytebuffer"; +import { TransactionTypes } from "../../constants"; +import { MultiPaymentItem } from "../../interfaces"; +import { Bignum } from "../../utils"; +import * as schemas from "./schemas"; +import { Transaction } from "./transaction"; + +export class MultiPaymentTransaction extends Transaction { + public static type: TransactionTypes = TransactionTypes.MultiPayment; + + public static getSchema(): schemas.TransactionSchema { + return schemas.multiPayment; + } + + public serialize(): ByteBuffer { + const { data } = this; + const buffer = new ByteBuffer(64, true); + + buffer.writeUint32(data.asset.payments.length); + data.asset.payments.forEach(p => { + buffer.writeUint64(+new Bignum(p.amount).toFixed()); + buffer.append(bs58check.decode(p.recipientId)); + }); + + return buffer; + } + + public deserialize(buf: ByteBuffer): void { + const { data } = this; + const payments: MultiPaymentItem[] = []; + const total = buf.readUint32(); + + for (let j = 0; j < total; j++) { + payments.push({ + amount: new Bignum(buf.readUint64().toString()), + recipientId: bs58check.encode(buf.readBytes(21).toBuffer()), + }); + } + + data.amount = payments.reduce((a, p) => a.plus(p.amount), Bignum.ZERO); + data.asset = { payments }; + } +} diff --git a/packages/crypto/src/transactions/types/multi-signature.ts b/packages/crypto/src/transactions/types/multi-signature.ts new file mode 100644 index 0000000000..e8ae28b6d1 --- /dev/null +++ b/packages/crypto/src/transactions/types/multi-signature.ts @@ -0,0 +1,41 @@ +import ByteBuffer from "bytebuffer"; +import { IMultiSignatureAsset } from ".."; +import { TransactionTypes } from "../../constants"; +import * as schemas from "./schemas"; +import { Transaction } from "./transaction"; + +export class MultiSignatureRegistrationTransaction extends Transaction { + public static type: TransactionTypes = TransactionTypes.MultiSignature; + + public static getSchema(): schemas.TransactionSchema { + return schemas.multiSignature; + } + + public serialize(): ByteBuffer { + const { data } = this; + + const joined = data.asset.multisignature.keysgroup.map(k => (k[0] === "+" ? k.slice(1) : k)).join(""); + const keysgroupBuffer = Buffer.from(joined, "hex"); + const buffer = new ByteBuffer(keysgroupBuffer.length + 3, true); + + buffer.writeByte(data.asset.multisignature.min); + buffer.writeByte(data.asset.multisignature.keysgroup.length); + buffer.writeByte(data.asset.multisignature.lifetime); + buffer.append(keysgroupBuffer, "hex"); + return buffer; + } + + public deserialize(buf: ByteBuffer): void { + const { data } = this; + data.asset = { multisignature: { keysgroup: [] } as IMultiSignatureAsset }; + data.asset.multisignature.min = buf.readUint8(); + + const num = buf.readUint8(); + data.asset.multisignature.lifetime = buf.readUint8(); + + for (let index = 0; index < num; index++) { + const key = buf.readBytes(33).toString("hex"); + data.asset.multisignature.keysgroup.push(key); + } + } +} diff --git a/packages/crypto/src/transactions/types/schemas.ts b/packages/crypto/src/transactions/types/schemas.ts new file mode 100644 index 0000000000..86f7a818c0 --- /dev/null +++ b/packages/crypto/src/transactions/types/schemas.ts @@ -0,0 +1,169 @@ +import deepmerge = require("deepmerge"); +import { TransactionTypes } from "../../constants"; + +export const extend = (parent, properties): TransactionSchema => { + return deepmerge(parent, properties); +}; + +export type TransactionSchema = typeof transactionBaseSchema; + +export const signedSchema = (schema: TransactionSchema): TransactionSchema => { + const signed = extend(schema, signedTransaction); + signed.$id = `${schema.$id}Signed`; + return signed; +}; + +export const strictSchema = (schema: TransactionSchema): TransactionSchema => { + const signed = signedSchema(schema); + const strict = extend(signed, strictTransaction); + strict.$id = `${schema.$id}Strict`; + return strict; +}; + +export const transactionBaseSchema = { + $id: null, + type: "object", + required: ["type", "senderPublicKey", "fee", "timestamp"], + properties: { + id: { anyOf: [{ $ref: "transactionId" }, { type: "null" }] }, + version: { enum: [1, 2] }, + network: { $ref: "networkByte" }, + expiration: { type: "integer" }, + timestamp: { type: "integer", minimum: 0 }, + amount: { bignumber: { minimum: 1, bypassGenesis: true } }, + fee: { bignumber: { minimum: 1, bypassGenesis: true } }, + senderPublicKey: { $ref: "publicKey" }, + signature: { $ref: "alphanumeric" }, + secondSignature: { $ref: "alphanumeric" }, + signSignature: { $ref: "alphanumeric" }, + }, +}; + +const signedTransaction = { + required: ["id", "signature"], +}; + +const strictTransaction = { + additionalProperties: false, +}; + +export const transfer = extend(transactionBaseSchema, { + $id: "transfer", + required: ["recipientId", "amount"], + properties: { + type: { transactionType: TransactionTypes.Transfer }, + vendorField: { anyOf: [{ type: "null" }, { type: "string", format: "vendorField" }] }, + vendorFieldHex: { anyOf: [{ type: "null" }, { type: "string", format: "vendorFieldHex" }] }, + recipientId: { $ref: "address" }, + }, +}); + +export const secondSignature = extend(transactionBaseSchema, { + $id: "secondSignature", + required: ["asset"], + properties: { + type: { transactionType: TransactionTypes.SecondSignature }, + amount: { bignumber: { minimum: 0, maximum: 0 } }, + secondSignature: { type: "null" }, + asset: { + type: "object", + required: ["signature"], + properties: { + signature: { + type: "object", + required: ["publicKey"], + properties: { + publicKey: { + $ref: "publicKey", + }, + }, + }, + }, + }, + }, +}); + +export const delegateRegistration = extend(transactionBaseSchema, { + $id: "delegateRegistration", + required: ["asset"], + properties: { + type: { transactionType: TransactionTypes.DelegateRegistration }, + amount: { bignumber: { minimum: 0, maximum: 0 } }, + asset: { + type: "object", + required: ["delegate"], + properties: { + delegate: { + type: "object", + required: ["username"], + properties: { + username: { $ref: "delegateUsername" }, + }, + }, + }, + }, + }, +}); + +export const vote = extend(transactionBaseSchema, { + $id: "vote", + required: ["asset"], + properties: { + type: { transactionType: TransactionTypes.Vote }, + amount: { bignumber: { minimum: 0, maximum: 0 } }, + recipientId: { $ref: "address" }, + asset: { + type: "object", + required: ["votes"], + properties: { + votes: { + type: "array", + minItems: 1, + maxItems: 1, + additionalItems: false, + items: { $ref: "walletVote" }, + }, + }, + }, + }, +}); + +export const multiSignature = extend(transactionBaseSchema, { + $id: "multiSignature", + properties: { + type: { transactionType: TransactionTypes.MultiSignature }, + amount: { bignumber: { minimum: 0, maximum: 0 } }, + }, +}); + +export const ipfs = extend(transactionBaseSchema, { + $id: "ipfs", + properties: { + type: { transactionType: TransactionTypes.Ipfs }, + amount: { bignumber: { minimum: 0, maximum: 0 } }, + }, +}); + +export const timelockTransfer = extend(transactionBaseSchema, { + $id: "timelockTransfer", + properties: { + type: { transactionType: TransactionTypes.TimelockTransfer }, + amount: { bignumber: { minimum: 0, maximum: 0 } }, + }, +}); + +export const multiPayment = extend(transactionBaseSchema, { + $id: "multiPayment", + properties: { + type: { transactionType: TransactionTypes.MultiPayment }, + amount: { bignumber: { minimum: 0, maximum: 0 } }, + }, +}); + +export const delegateResignation = extend(transactionBaseSchema, { + $id: "delegateResignation", + properties: { + type: { transactionType: TransactionTypes.DelegateResignation }, + amount: { bignumber: { minimum: 0, maximum: 0 } }, + }, +}); diff --git a/packages/crypto/src/transactions/types/second-signature.ts b/packages/crypto/src/transactions/types/second-signature.ts new file mode 100644 index 0000000000..30ad66254c --- /dev/null +++ b/packages/crypto/src/transactions/types/second-signature.ts @@ -0,0 +1,30 @@ +import ByteBuffer from "bytebuffer"; +import { TransactionTypes } from "../../constants"; +import * as schemas from "./schemas"; +import { Transaction } from "./transaction"; + +export class SecondSignatureRegistrationTransaction extends Transaction { + public static type: TransactionTypes = TransactionTypes.SecondSignature; + + public static getSchema(): schemas.TransactionSchema { + return schemas.secondSignature; + } + + public serialize(): ByteBuffer { + const { data } = this; + const buffer = new ByteBuffer(33, true); + + buffer.append(data.asset.signature.publicKey, "hex"); + + return buffer; + } + + public deserialize(buf: ByteBuffer): void { + const { data } = this; + data.asset = { + signature: { + publicKey: buf.readBytes(33).toString("hex"), + }, + }; + } +} diff --git a/packages/crypto/src/transactions/types/timelock-transfer.ts b/packages/crypto/src/transactions/types/timelock-transfer.ts new file mode 100644 index 0000000000..133cbc9ce2 --- /dev/null +++ b/packages/crypto/src/transactions/types/timelock-transfer.ts @@ -0,0 +1,33 @@ +import bs58check from "bs58check"; +import ByteBuffer from "bytebuffer"; +import { TransactionTypes } from "../../constants"; +import { Bignum } from "../../utils"; +import * as schemas from "./schemas"; +import { Transaction } from "./transaction"; + +export class TimelockTransferTransaction extends Transaction { + public static type: TransactionTypes = TransactionTypes.TimelockTransfer; + + public static getSchema(): schemas.TransactionSchema { + return schemas.timelockTransfer; + } + + public serialize(): ByteBuffer { + const { data } = this; + const buffer = new ByteBuffer(4 + 1 + 4 + 24, true); + + buffer.writeUint64(+new Bignum(data.amount).toFixed()); + buffer.writeByte(data.timelockType); + buffer.writeUint64(data.timelock); + buffer.append(bs58check.decode(data.recipientId)); + return buffer; + } + + public deserialize(buf: ByteBuffer): void { + const { data } = this; + data.amount = new Bignum(buf.readUint64().toString()); + data.timelockType = buf.readUint8(); + data.timelock = buf.readUint64().toNumber(); + data.recipientId = bs58check.encode(buf.readBytes(21).toBuffer()); + } +} diff --git a/packages/crypto/src/transactions/types/transaction.ts b/packages/crypto/src/transactions/types/transaction.ts new file mode 100644 index 0000000000..9d2d354101 --- /dev/null +++ b/packages/crypto/src/transactions/types/transaction.ts @@ -0,0 +1,163 @@ +// tslint:disable:member-ordering +import { TransactionRegistry } from ".."; +import { TransactionTypes } from "../../constants"; +import { crypto } from "../../crypto"; +import { + MalformedTransactionBytesError, + NotImplementedError, + TransactionSchemaError, + TransactionVersionError, +} from "../../errors"; +import { Bignum, isException } from "../../utils"; +import { AjvWrapper } from "../../validation"; +import { TransactionDeserializer } from "../deserializers"; +import { ISchemaValidationResult, ITransactionData } from "../interfaces"; +import { TransactionSerializer } from "../serializers"; +import { TransactionSchema } from "./schemas"; + +export abstract class Transaction { + public static type: TransactionTypes = null; + + public static fromHex(hex: string): Transaction { + return this.fromSerialized(hex); + } + + public static fromBytes(buffer: Buffer): Transaction { + return this.fromSerialized(buffer); + } + + /** + * Deserializes a transaction from `buffer` with the given `id`. It is faster + * than `fromBytes` at the cost of vital safety checks (validation, verification and id calculation). + * + * NOTE: Only use this internally when it is safe to assume the buffer has already been + * verified. + */ + public static fromBytesUnsafe(buffer: Buffer, id?: string): Transaction { + try { + const transaction = TransactionDeserializer.deserialize(buffer); + transaction.data.id = id || crypto.getId(transaction.data); + transaction.isVerified = true; + + return transaction; + } catch (error) { + throw new MalformedTransactionBytesError(); + } + } + + private static fromSerialized(serialized: string | Buffer): Transaction { + try { + const transaction = TransactionDeserializer.deserialize(serialized); + transaction.data.id = crypto.getId(transaction.data); + + const { value, error } = this.validateSchema(transaction.data, true); + if (error !== null && !isException(value)) { + throw new TransactionSchemaError(error); + } + + transaction.isVerified = transaction.verify(); + return transaction; + } catch (error) { + if (error instanceof TransactionVersionError || error instanceof TransactionSchemaError) { + throw error; + } + + throw new MalformedTransactionBytesError(); + } + } + + public static fromData(data: ITransactionData, strict: boolean = true): Transaction { + const { value, error } = this.validateSchema(data, strict); + if (error !== null && !isException(value)) { + throw new TransactionSchemaError(error); + } + + const transaction = TransactionRegistry.create(value); + TransactionDeserializer.applyV1Compatibility(transaction.data); // TODO: generalize this kinda stuff + TransactionSerializer.serialize(transaction); + + data.id = crypto.getId(data); + transaction.isVerified = transaction.verify(); + + return transaction; + } + + public static toBytes(data: ITransactionData): Buffer { + const transaction = TransactionRegistry.create(data); + return TransactionSerializer.serialize(transaction); + } + + public get id(): string { + return this.data.id; + } + + public get type(): TransactionTypes { + return this.data.type; + } + + private isVerified: boolean; + public get verified(): boolean { + return this.isVerified; + } + + public data: ITransactionData; + public serialized: Buffer; + public timestamp: number; + + /** + * Serde + */ + public abstract serialize(): ByteBuffer; + public abstract deserialize(buf: ByteBuffer): void; + + /** + * Misc + */ + protected verify(): boolean { + const { data } = this; + if (isException(data)) { + return true; + } + + if (data.type >= 4 && data.type <= 99) { + return false; + } + + return crypto.verify(data); + } + + public toJson() { + const data = Object.assign({}, this.data); + data.amount = +(data.amount as Bignum).toFixed(); + data.fee = +(data.fee as Bignum).toFixed(); + + if (data.vendorFieldHex === null) { + delete data.vendorFieldHex; + } + + return data; + } + + public hasVendorField(): boolean { + return false; + } + + /** + * Schema + */ + public static getSchema(): TransactionSchema { + throw new NotImplementedError(); + } + + private static validateSchema(data: ITransactionData, strict: boolean): ISchemaValidationResult { + // FIXME: legacy type 4 need special treatment + if (data.type === TransactionTypes.MultiSignature) { + data.amount = new Bignum(data.amount); + data.fee = new Bignum(data.fee); + return { value: data, error: null }; + } + + const { $id } = TransactionRegistry.get(data.type).getSchema(); + return AjvWrapper.validate(strict ? `${$id}Strict` : `${$id}`, data); + } +} diff --git a/packages/crypto/src/transactions/types/transfer.ts b/packages/crypto/src/transactions/types/transfer.ts new file mode 100644 index 0000000000..a3f3dc09b3 --- /dev/null +++ b/packages/crypto/src/transactions/types/transfer.ts @@ -0,0 +1,35 @@ +import bs58check from "bs58check"; +import ByteBuffer from "bytebuffer"; +import { TransactionTypes } from "../../constants"; +import { Bignum } from "../../utils"; +import * as schemas from "./schemas"; +import { Transaction } from "./transaction"; + +export class TransferTransaction extends Transaction { + public static type: TransactionTypes = TransactionTypes.Transfer; + + public static getSchema(): schemas.TransactionSchema { + return schemas.transfer; + } + + public hasVendorField(): boolean { + return true; + } + + public serialize(): ByteBuffer { + const { data } = this; + const buffer = new ByteBuffer(24, true); + buffer.writeUint64(+data.amount); + buffer.writeUint32(data.expiration || 0); + buffer.append(bs58check.decode(data.recipientId)); + + return buffer; + } + + public deserialize(buf: ByteBuffer): void { + const { data } = this; + data.amount = new Bignum(buf.readUint64().toString()); + data.expiration = buf.readUint32(); + data.recipientId = bs58check.encode(buf.readBytes(21).toBuffer()); + } +} diff --git a/packages/crypto/src/transactions/types/vote.ts b/packages/crypto/src/transactions/types/vote.ts new file mode 100644 index 0000000000..1324d4bcb8 --- /dev/null +++ b/packages/crypto/src/transactions/types/vote.ts @@ -0,0 +1,35 @@ +import ByteBuffer from "bytebuffer"; +import { TransactionTypes } from "../../constants"; +import * as schemas from "./schemas"; +import { Transaction } from "./transaction"; + +export class VoteTransaction extends Transaction { + public static type: TransactionTypes = TransactionTypes.Vote; + + public static getSchema(): schemas.TransactionSchema { + return schemas.vote; + } + + public serialize(): ByteBuffer { + const { data } = this; + const buffer = new ByteBuffer(24, true); + + const voteBytes = data.asset.votes.map(vote => (vote[0] === "+" ? "01" : "00") + vote.slice(1)).join(""); + buffer.writeByte(data.asset.votes.length); + buffer.append(voteBytes, "hex"); + + return buffer; + } + + public deserialize(buf: ByteBuffer): void { + const { data } = this; + const votelength = buf.readUint8(); + data.asset = { votes: [] }; + + for (let i = 0; i < votelength; i++) { + let vote = buf.readBytes(34).toString("hex"); + vote = (vote[1] === "1" ? "+" : "-") + vote.slice(2); + data.asset.votes.push(vote); + } + } +} diff --git a/packages/crypto/src/utils.ts b/packages/crypto/src/utils.ts index d21c5a89f8..e6bf6637ed 100644 --- a/packages/crypto/src/utils.ts +++ b/packages/crypto/src/utils.ts @@ -1,7 +1,8 @@ import BigNumber from "bignumber.js"; import { SATOSHI } from "./constants"; import { configManager } from "./managers"; -import { IBlockData, ITransactionData } from "./models"; +import { IBlockData } from "./models"; +import { ITransactionData } from "./transactions/interfaces"; class Bignum extends BigNumber { public static readonly ZERO = new BigNumber(0); @@ -57,4 +58,21 @@ export function sortTransactions(transactions: ITransactionData[]): ITransaction }); } +let genesisTransactions: { [key: string]: boolean }; +let currentNetwork: number; + +export const isGenesisTransaction = (id: string): boolean => { + const network = configManager.get("pubKeyHash"); + if (!genesisTransactions || currentNetwork !== network) { + currentNetwork = network; + genesisTransactions = configManager + .get("genesisBlock.transactions") + .reduce((acc, curr) => Object.assign(acc, { [curr.id]: true }), {}); + } + + return genesisTransactions[id]; +}; + +export const maxVendorFieldLength = (height?: number): number => configManager.getMilestone(height).vendorFieldLength; + export { Bignum }; diff --git a/packages/crypto/src/validation/ajv-wrapper.ts b/packages/crypto/src/validation/ajv-wrapper.ts new file mode 100644 index 0000000000..742b033c70 --- /dev/null +++ b/packages/crypto/src/validation/ajv-wrapper.ts @@ -0,0 +1,72 @@ +import Ajv from "ajv"; +import ajvKeywords from "ajv-keywords"; + +import { ISchemaValidationResult } from "../transactions/interfaces"; +import { signedSchema, strictSchema, TransactionSchema } from "../transactions/types/schemas"; +import { formats } from "./formats"; +import { keywords } from "./keywords"; +import { schemas } from "./schemas"; + +class AjvWrapper { + private ajv: Ajv.Ajv; + private transactionSchemas = new Set(); + + constructor() { + const ajv = new Ajv({ $data: true, schemas, removeAdditional: true, extendRefs: true }); + ajvKeywords(ajv); + + keywords.forEach(addKeyword => { + addKeyword(ajv); + }); + + formats.forEach(addFormat => { + addFormat(ajv); + }); + + this.ajv = ajv; + } + + public instance(): Ajv.Ajv { + return this.ajv; + } + + public validate(schemaName: string, data: T): ISchemaValidationResult { + const valid = this.ajv.validate(schemaName, data); + const error = this.ajv.errors !== null ? this.ajv.errorsText() : null; + return { value: data, error }; + } + + public extendTransaction(schema: TransactionSchema, remove?: boolean) { + if (remove) { + this.transactionSchemas.delete(schema.$id); + this.ajv.removeSchema(schema.$id); + this.ajv.removeSchema(`${schema.$id}Signed`); + this.ajv.removeSchema(`${schema.$id}Strict`); + } else { + this.transactionSchemas.add(schema.$id); + this.ajv.addSchema(schema); + this.ajv.addSchema(signedSchema(schema)); + this.ajv.addSchema(strictSchema(schema)); + } + + this.updateTransactionArray(); + } + + private updateTransactionArray() { + const items = [...this.transactionSchemas].map(schema => ({ $ref: `${schema}Signed` })); + + const transactionsSchema = { + $id: "transactions", + type: "array", + additionalItems: false, + items: { oneOf: items }, + }; + + this.ajv.removeSchema("block"); + this.ajv.removeSchema("transactions"); + this.ajv.addSchema(transactionsSchema); + this.ajv.addSchema(schemas.block); + } +} + +export const ajvWrapper = new AjvWrapper(); diff --git a/packages/crypto/src/validation/extensions/address.ts b/packages/crypto/src/validation/extensions/address.ts deleted file mode 100644 index fb79c564f5..0000000000 --- a/packages/crypto/src/validation/extensions/address.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const address = joi => ({ - name: "address", - base: joi - .string() - .alphanum() - .length(34), -}); diff --git a/packages/crypto/src/validation/extensions/bignumber.ts b/packages/crypto/src/validation/extensions/bignumber.ts deleted file mode 100644 index 05bb51afb3..0000000000 --- a/packages/crypto/src/validation/extensions/bignumber.ts +++ /dev/null @@ -1,76 +0,0 @@ -import BigNumber from "bignumber.js"; - -export const bignumber = joi => ({ - name: "bignumber", - base: joi.object().type(BigNumber), - language: { - min: "is less than minimum", - max: "is greater than maximum", - only: "is different from allowed value", - integer: "is not an integer", - positive: "is not positive", - }, - rules: [ - { - name: "min", - params: { - q: joi.number().required(), - }, - validate(params, value, state, options) { - if (value.isLessThan(params.q)) { - return this.createError("bignumber.min", { v: value }, state, options); - } - - return value; - }, - }, - { - name: "max", - params: { - q: joi.number().required(), - }, - validate(params, value, state, options) { - if (value.isGreaterThan(params.q)) { - return this.createError("bignumber.max", { v: value }, state, options); - } - - return value; - }, - }, - { - name: "only", - params: { - q: joi.number().required(), - }, - validate(params, value, state, options) { - if (!value.isEqualTo(params.q)) { - return this.createError("bignumber.only", { v: value }, state, options); - } - - return value; - }, - }, - { - name: "integer", - params: {}, - validate(_, value, state, options) { - if (!value.isInteger()) { - return this.createError("bignumber.integer", { v: value }, state, options); - } - - return value; - }, - }, - { - name: "positive", - params: {}, - validate(_, value, state, options) { - if (!value.isPositive() || value.isZero()) { - return this.createError("bignumber.positive", { v: value }, state, options); - } - - return value; - }, - }, - ], -}); diff --git a/packages/crypto/src/validation/extensions/block-id.ts b/packages/crypto/src/validation/extensions/block-id.ts deleted file mode 100644 index 43bfb2caa8..0000000000 --- a/packages/crypto/src/validation/extensions/block-id.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const blockId = joi => ({ - name: "blockId", - base: joi.string().regex(/^[0-9]+$/, "numbers"), -}); diff --git a/packages/crypto/src/validation/extensions/block.ts b/packages/crypto/src/validation/extensions/block.ts deleted file mode 100644 index d58c2d7b40..0000000000 --- a/packages/crypto/src/validation/extensions/block.ts +++ /dev/null @@ -1,63 +0,0 @@ -export const block = joi => ({ - name: "block", - base: joi.object().keys({ - id: joi.blockId().required(), - idHex: joi.string().hex(), - version: joi - .number() - .integer() - .min(0), - timestamp: joi - .number() - .integer() - .min(0) - .required(), - previousBlock: joi.blockId().required(), - previousBlockHex: joi.string().hex(), - height: joi - .number() - .integer() - .positive() - .required(), - numberOfTransactions: joi - .number() - .integer() - .only(joi.ref("transactions.length")), - totalAmount: joi.alternatives().try( - joi - .number() - .integer() - .min(0) - .required(), - joi - .string() - .regex(/[0-9]+/) - .required(), - ), - totalFee: joi - .number() - .integer() - .min(0) - .required(), - reward: joi - .number() - .integer() - .min(0) - .required(), - payloadLength: joi - .number() - .integer() - .min(0), - payloadHash: joi.string().hex(), - generatorPublicKey: joi - .string() - .hex() - .length(66) - .required(), - blockSignature: joi - .string() - .hex() - .required(), - transactions: joi.transactionArray(), - }), -}); diff --git a/packages/crypto/src/validation/extensions/index.ts b/packages/crypto/src/validation/extensions/index.ts deleted file mode 100644 index 946adf7911..0000000000 --- a/packages/crypto/src/validation/extensions/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { address } from "./address"; -import { bignumber } from "./bignumber"; -import { block } from "./block"; -import { blockId } from "./block-id"; -import { publicKey } from "./public-key"; -import { transactionArray } from "./transaction-array"; -import { transactions } from "./transactions"; -import { username } from "./username"; - -export const extensions = [address, bignumber, publicKey, username, blockId, ...transactions, transactionArray, block]; diff --git a/packages/crypto/src/validation/extensions/public-key.ts b/packages/crypto/src/validation/extensions/public-key.ts deleted file mode 100644 index 7686920ba6..0000000000 --- a/packages/crypto/src/validation/extensions/public-key.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const publicKey = joi => ({ - name: "publicKey", - base: joi - .string() - .hex() - .length(66), -}); diff --git a/packages/crypto/src/validation/extensions/transaction-array.ts b/packages/crypto/src/validation/extensions/transaction-array.ts deleted file mode 100644 index 9e4917c8b0..0000000000 --- a/packages/crypto/src/validation/extensions/transaction-array.ts +++ /dev/null @@ -1,16 +0,0 @@ -export const transactionArray = joi => ({ - name: "transactionArray", - base: joi - .array() - .items( - joi - .alternatives() - .try( - joi.transfer(), - joi.secondSignature(), - joi.delegateRegistration(), - joi.vote(), - joi.multiSignature(), - ), - ), -}); diff --git a/packages/crypto/src/validation/extensions/transactions/base.ts b/packages/crypto/src/validation/extensions/transactions/base.ts deleted file mode 100644 index 9e13c7d65d..0000000000 --- a/packages/crypto/src/validation/extensions/transactions/base.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { configManager } from "../../../managers"; - -export const base = joi => - joi.object().keys({ - id: joi - .string() - .alphanum() - .required(), - blockid: joi.alternatives().try( - // TODO: remove in 2.1 - joi.blockId(), - // @ts-ignore - joi.number().unsafe(), - ), - network: joi.lazy( - () => - joi - .number() - .only(configManager.get("pubKeyHash")) - .optional(), - { once: false }, - ), - version: joi - .number() - .integer() - .min(1) - .max(2) - .optional(), - timestamp: joi - .number() - .integer() - .min(0) - .required(), - amount: joi - .alternatives() - .try( - joi - .bignumber() - .integer() - .positive(), - joi - .number() - .integer() - .positive(), - ) - .required(), - fee: joi - .alternatives() - .try( - joi - .bignumber() - .integer() - .positive(), - joi - .number() - .integer() - .positive(), - ) - .required(), - senderId: joi.address(), // TODO: remove in 2.1 - recipientId: joi.address().required(), - senderPublicKey: joi.publicKey().required(), - signature: joi - .string() - .alphanum() - .required(), - secondSignature: joi.string().alphanum(), - signSignature: joi.string().alphanum(), // TODO: remove in 2.1 - confirmations: joi // TODO: remove in 2.1 - .number() - .integer() - .min(0), - }); diff --git a/packages/crypto/src/validation/extensions/transactions/delegate-registration.ts b/packages/crypto/src/validation/extensions/transactions/delegate-registration.ts deleted file mode 100644 index 0a4a9891e6..0000000000 --- a/packages/crypto/src/validation/extensions/transactions/delegate-registration.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { TransactionTypes } from "../../../constants"; -import { base as transaction } from "./base"; - -export const delegateRegistration = joi => ({ - name: "delegateRegistration", - base: transaction(joi).append({ - type: joi - .number() - .only(TransactionTypes.DelegateRegistration) - .required(), - amount: joi - .alternatives() - .try(joi.bignumber().only(0), joi.number().only(0)) - .optional(), - asset: joi - .object({ - delegate: joi - .object({ - username: joi.delegateUsername().required(), - publicKey: joi.publicKey(), - }) - .required(), - }) - .required(), - recipientId: joi.empty(), - }), -}); diff --git a/packages/crypto/src/validation/extensions/transactions/delegate-resignation.ts b/packages/crypto/src/validation/extensions/transactions/delegate-resignation.ts deleted file mode 100644 index 1751bac049..0000000000 --- a/packages/crypto/src/validation/extensions/transactions/delegate-resignation.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { TransactionTypes } from "../../../constants"; -import { base as transaction } from "./base"; - -export const delegateResignation = joi => ({ - name: "delegateResignation", - base: transaction(joi).append({ - type: joi - .number() - .only(TransactionTypes.DelegateResignation) - .required(), - amount: joi - .alternatives() - .try(joi.bignumber().only(0), joi.number().valid(0)) - .optional(), - asset: joi.object().required(), - recipientId: joi.empty(), - }), -}); diff --git a/packages/crypto/src/validation/extensions/transactions/index.ts b/packages/crypto/src/validation/extensions/transactions/index.ts deleted file mode 100644 index ed5f497b89..0000000000 --- a/packages/crypto/src/validation/extensions/transactions/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { delegateRegistration } from "./delegate-registration"; -import { delegateResignation } from "./delegate-resignation"; -import { ipfs } from "./ipfs"; -import { multiPayment } from "./multi-payment"; -import { multiSignature } from "./multi-signature"; -import { secondSignature } from "./second-signature"; -import { timelockTransfer } from "./timelock-transfer"; -import { transfer } from "./transfer"; -import { vote } from "./vote"; - -export const transactions = [ - transfer, - secondSignature, - delegateRegistration, - vote, - multiSignature, - ipfs, - timelockTransfer, - multiPayment, - delegateResignation, -]; diff --git a/packages/crypto/src/validation/extensions/transactions/ipfs.ts b/packages/crypto/src/validation/extensions/transactions/ipfs.ts deleted file mode 100644 index 6e5bba5c10..0000000000 --- a/packages/crypto/src/validation/extensions/transactions/ipfs.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { TransactionTypes } from "../../../constants"; -import { base as transaction } from "./base"; - -export const ipfs = joi => ({ - name: "ipfs", - base: transaction(joi).append({ - type: joi - .number() - .only(TransactionTypes.Ipfs) - .required(), - amount: joi - .alternatives() - .try(joi.bignumber().only(0), joi.number().valid(0)) - .optional(), - asset: joi.object().required(), - recipientId: joi.empty(), - }), -}); diff --git a/packages/crypto/src/validation/extensions/transactions/multi-payment.ts b/packages/crypto/src/validation/extensions/transactions/multi-payment.ts deleted file mode 100644 index aeae21929b..0000000000 --- a/packages/crypto/src/validation/extensions/transactions/multi-payment.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { TransactionTypes } from "../../../constants"; -import { base as transaction } from "./base"; - -export const multiPayment = joi => ({ - name: "multiPayment", - base: transaction(joi).append({ - type: joi - .number() - .only(TransactionTypes.MultiPayment) - .required(), - asset: joi.object().required(), - recipientId: joi.empty(), - }), -}); diff --git a/packages/crypto/src/validation/extensions/transactions/multi-signature.ts b/packages/crypto/src/validation/extensions/transactions/multi-signature.ts deleted file mode 100644 index 6d2c96ff38..0000000000 --- a/packages/crypto/src/validation/extensions/transactions/multi-signature.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { TransactionTypes } from "../../../constants"; -import { base as transaction } from "./base"; - -export const multiSignature = joi => ({ - name: "multiSignature", - base: transaction(joi).append({ - type: joi - .number() - .only(TransactionTypes.MultiSignature) - .required(), - amount: joi - .alternatives() - .try(joi.bignumber().only(0), joi.number().only(0)) - .optional(), - recipientId: joi.empty(), - signatures: joi - .array() - .length(joi.ref("asset.multisignature.keysgroup.length")) - .required(), - asset: joi - .object({ - multisignature: joi - .object({ - min: joi - .when(joi.ref("keysgroup.length"), { - is: joi.number().greater(16), - then: joi - .number() - .positive() - .max(16), - otherwise: joi - .number() - .positive() - .max(joi.ref("keysgroup.length")), - }) - .required(), - keysgroup: joi - .array() - .unique() - .min(2) - .items( - joi - .string() - .not(`+${(transaction as any).senderPublicKey}`) - .length(67) - .regex(/^\+/) - .required(), - ) - .required(), - lifetime: joi - .number() - .integer() - .min(1) - .max(72) - .required(), - }) - .required(), - }) - .required(), - }), -}); diff --git a/packages/crypto/src/validation/extensions/transactions/second-signature.ts b/packages/crypto/src/validation/extensions/transactions/second-signature.ts deleted file mode 100644 index 7d52691f49..0000000000 --- a/packages/crypto/src/validation/extensions/transactions/second-signature.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { TransactionTypes } from "../../../constants"; -import { base as transaction } from "./base"; - -export const secondSignature = joi => ({ - name: "secondSignature", - base: transaction(joi).append({ - type: joi - .number() - .only(TransactionTypes.SecondSignature) - .required(), - amount: joi - .alternatives() - .try(joi.bignumber().only(0), joi.number().only(0)) - .optional(), - secondSignature: joi.string().only(""), - asset: joi - .object({ - signature: joi - .object({ - publicKey: joi.publicKey().required(), - }) - .required(), - }) - .required(), - recipientId: joi.empty(), - }), -}); diff --git a/packages/crypto/src/validation/extensions/transactions/timelock-transfer.ts b/packages/crypto/src/validation/extensions/transactions/timelock-transfer.ts deleted file mode 100644 index 049203acf7..0000000000 --- a/packages/crypto/src/validation/extensions/transactions/timelock-transfer.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { TransactionTypes } from "../../../constants"; -import { base as transaction } from "./base"; - -export const timelockTransfer = joi => ({ - name: "timelockTransfer", - base: transaction(joi).append({ - type: joi - .number() - .only(TransactionTypes.MultiPayment) - .required(), - amount: joi - .alternatives() - .try(joi.bignumber().only(0), joi.number().only(0)) - .optional(), - asset: joi.object().required(), - vendorFieldHex: joi - .string() - .max(64, "hex") - .optional(), - vendorField: joi - .string() - .max(64, "utf8") - .allow("", null) - .optional(), // TODO: remove in 2.1 - recipientId: joi.empty(), - }), -}); diff --git a/packages/crypto/src/validation/extensions/transactions/transfer.ts b/packages/crypto/src/validation/extensions/transactions/transfer.ts deleted file mode 100644 index 1db77c6bfa..0000000000 --- a/packages/crypto/src/validation/extensions/transactions/transfer.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { TransactionTypes } from "../../../constants"; -import { base as transaction } from "./base"; - -export const transfer = joi => ({ - name: "transfer", - base: transaction(joi).append({ - type: joi - .number() - .only(TransactionTypes.Transfer) - .required(), - expiration: joi - .number() - .integer() - .min(0), - vendorField: joi - .string() - .max(64, "utf8") - .allow("", null) - .optional(), // TODO: remove in 2.1 - vendorFieldHex: joi - .string() - .max(64, "hex") - .optional(), - asset: joi.object().empty(), - }), -}); diff --git a/packages/crypto/src/validation/extensions/transactions/vote.ts b/packages/crypto/src/validation/extensions/transactions/vote.ts deleted file mode 100644 index b8b54268bc..0000000000 --- a/packages/crypto/src/validation/extensions/transactions/vote.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { TransactionTypes } from "../../../constants"; -import { base as transaction } from "./base"; - -export const vote = joi => ({ - name: "vote", - base: transaction(joi).append({ - type: joi - .number() - .only(TransactionTypes.Vote) - .required(), - amount: joi - .alternatives() - .try(joi.bignumber().only(0), joi.number().only(0)) - .optional(), - asset: joi - .object({ - votes: joi - .array() - .items( - joi - .string() - .length(67) - .regex(/^(\+|-)[a-zA-Z0-9]+$/), - ) - .length(1) - .required(), - }) - .required(), - recipientId: joi - .address() - .allow(null) - .optional(), - }), -}); diff --git a/packages/crypto/src/validation/extensions/username.ts b/packages/crypto/src/validation/extensions/username.ts deleted file mode 100644 index 0bff850c80..0000000000 --- a/packages/crypto/src/validation/extensions/username.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const username = joi => ({ - name: "delegateUsername", - base: joi - .string() - .regex(/^[a-z0-9!@$&_.]+$/) - .min(1) - .max(20), -}); diff --git a/packages/crypto/src/validation/formats.ts b/packages/crypto/src/validation/formats.ts new file mode 100644 index 0000000000..b33acc56d4 --- /dev/null +++ b/packages/crypto/src/validation/formats.ts @@ -0,0 +1,28 @@ +import { Ajv } from "ajv"; +import { maxVendorFieldLength } from "../utils"; + +const vendorField = (ajv: Ajv) => { + ajv.addFormat("vendorField", data => { + try { + return Buffer.from(data, "utf8").length <= maxVendorFieldLength(); + } catch { + return false; + } + }); +}; + +const vendorFieldHex = (ajv: Ajv) => { + ajv.addFormat("vendorFieldHex", data => { + try { + if (/^[0123456789A-Fa-f]+$/.test(data)) { + return Buffer.from(data, "hex").length <= maxVendorFieldLength(); + } + } catch { + return false; + } + + return false; + }); +}; + +export const formats = [vendorField, vendorFieldHex]; diff --git a/packages/crypto/src/validation/index.ts b/packages/crypto/src/validation/index.ts index b316d7add4..ef42ac9646 100644 --- a/packages/crypto/src/validation/index.ts +++ b/packages/crypto/src/validation/index.ts @@ -1,5 +1 @@ -export { transactionValidator } from "./validators/transaction"; - -import { Validator } from "./validator"; - -export const Joi = Validator.joi; +export { ajvWrapper as AjvWrapper } from "./ajv-wrapper"; diff --git a/packages/crypto/src/validation/keywords.ts b/packages/crypto/src/validation/keywords.ts new file mode 100644 index 0000000000..49a336a0fa --- /dev/null +++ b/packages/crypto/src/validation/keywords.ts @@ -0,0 +1,165 @@ +import { Ajv } from "ajv"; +import ajvKeywords from "ajv-keywords"; +import { Address } from "../identities/address"; +import { configManager } from "../managers"; +import { Bignum, isGenesisTransaction } from "../utils"; + +const maxBytes = (ajv: Ajv) => { + ajv.addKeyword("maxBytes", { + type: "string", + compile(schema, parentSchema) { + return data => { + if ((parentSchema as any).type !== "string") { + return false; + } + + return Buffer.from(data, "utf8").byteLength <= schema; + }; + }, + errors: false, + metaSchema: { + type: "integer", + minimum: 0, + }, + }); +}; + +const transactionType = (ajv: Ajv) => { + ajv.addKeyword("transactionType", { + compile(schema) { + return data => { + return data === schema; + }; + }, + errors: false, + metaSchema: { + type: "integer", + minimum: 0, + }, + }); +}; + +const network = (ajv: Ajv) => { + ajv.addKeyword("network", { + compile(schema) { + return data => { + return schema && data === configManager.get("pubKeyHash"); + }; + }, + errors: false, + metaSchema: { + type: "boolean", + }, + }); +}; + +const bignumber = (ajv: Ajv) => { + const instanceOf = ajvKeywords.get("instanceof").definition; + instanceOf.CONSTRUCTORS.Bignum = Bignum; + + ajv.addKeyword("bignumber", { + compile(schema) { + return (data, dataPath, parentObject: any, property) => { + const minimum = typeof schema.minimum !== "undefined" ? schema.minimum : 0; + const maximum = typeof schema.maximum !== "undefined" ? schema.maximum : Number.MAX_SAFE_INTEGER; + + const bignum = new Bignum(data); + + if (!bignum.isInteger()) { + return false; + } + + let bypassGenesis = false; + if (schema.bypassGenesis) { + if (parentObject.id) { + if (schema.block) { + bypassGenesis = parentObject.height === 1; + } else { + bypassGenesis = isGenesisTransaction(parentObject.id); + } + } + } + + if (bignum.isLessThan(minimum) && !(bignum.isZero() && bypassGenesis)) { + return false; + } + + if (bignum.isGreaterThan(maximum) && !bypassGenesis) { + return false; + } + + if (parentObject && property) { + parentObject[property] = bignum; + } + + return true; + }; + }, + errors: false, + modifying: true, + metaSchema: { + type: "object", + properties: { + minimum: { type: "integer" }, + maximum: { type: "integer" }, + bypassGenesis: { type: "boolean" }, + block: { type: "boolean" }, + }, + additionalItems: false, + }, + }); +}; + +const blockId = (ajv: Ajv) => { + ajv.addKeyword("blockId", { + compile(schema) { + return (data, dataPath, parentObject: any) => { + if (parentObject && parentObject.height === 1 && schema.allowNullWhenGenesis) { + return !data || Number(data) === 0; + } + + if (typeof data !== "string") { + return false; + } + + // Partial SHA256 block id (old/legacy), before the switch to full SHA256. + // 8 byte integer either decimal without leading zeros or hex with leading zeros. + const isPartial = /^[0-9]{1,20}$/.test(data) || /^[0-9a-f]{16}$/i.test(data); + const isFullSha256 = /^[0-9a-f]{64}$/i.test(data); + + if (parentObject && parentObject.height) { + const height = schema.isPreviousBlock ? parentObject.height - 1 : parentObject.height; + const constants = configManager.getMilestone(height); + return constants.block.idFullSha256 ? isFullSha256 : isPartial; + } + + return isPartial || isFullSha256; + }; + }, + errors: false, + metaSchema: { + type: "object", + properties: { + allowNullWhenGenesis: { type: "boolean" }, + isPreviousBlock: { type: "boolean" }, + }, + additionalItems: false, + }, + }); +}; + +const addressOnNetwork = (ajv: Ajv) => { + ajv.addKeyword("addressOnNetwork", { + compile(schema) { + return data => { + return schema && Address.validate(data); + }; + }, + errors: false, + metaSchema: { + type: "boolean", + }, + }); +}; + +export const keywords = [bignumber, blockId, maxBytes, network, transactionType, addressOnNetwork]; diff --git a/packages/crypto/src/validation/schemas.ts b/packages/crypto/src/validation/schemas.ts new file mode 100644 index 0000000000..89d12242f2 --- /dev/null +++ b/packages/crypto/src/validation/schemas.ts @@ -0,0 +1,92 @@ +export const schemas = { + hex: { + $id: "hex", + type: "string", + pattern: "^[0123456789A-Fa-f]+$", + }, + + base58: { + $id: "base58", + type: "string", + pattern: "^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$", + }, + + alphanumeric: { + $id: "alphanumeric", + type: "string", + pattern: "^[a-zA-Z0-9]+$", + }, + + transactionId: { + $id: "transactionId", + allOf: [{ minLength: 64, maxLength: 64 }, { $ref: "alphanumeric" }], + }, + + networkByte: { + $id: "networkByte", + network: true, + }, + + address: { + $id: "address", + allOf: [{ minLength: 34, maxLength: 34 }, { $ref: "base58" }, { addressOnNetwork: true }], + }, + + publicKey: { + $id: "publicKey", + allOf: [{ minLength: 66, maxLength: 66 }, { $ref: "hex" }, { transform: ["toLowerCase"] }], + }, + + walletVote: { + $id: "walletVote", + allOf: [{ type: "string", pattern: "^[+|-][a-zA-Z0-9]{66}$" }, { transform: ["toLowerCase"] }], + }, + + username: { + $id: "delegateUsername", + allOf: [ + { type: "string", pattern: "^[a-z0-9!@$&_.]+$" }, + { minLength: 1, maxLength: 20 }, + { transform: ["toLowerCase"] }, + ], + }, + + block: { + $id: "block", + type: "object", + required: [ + "id", + "timestamp", + "previousBlock", + "height", + "totalAmount", + "totalFee", + "reward", + "generatorPublicKey", + "blockSignature", + ], + additionalProperties: false, + properties: { + id: { blockId: {} }, + idHex: { blockId: {} }, + version: { type: "integer", minimum: 0 }, + timestamp: { type: "integer", minimum: 0 }, + previousBlock: { blockId: { allowNullWhenGenesis: true, isPreviousBlock: true } }, + previousBlockHex: { blockId: { allowNullWhenGenesis: true, isPreviousBlock: true } }, + height: { type: "integer", minimum: 1 }, + numberOfTransactions: { type: "integer" }, + totalAmount: { bignumber: { minimum: 0, bypassGenesis: true, block: true } }, + totalFee: { bignumber: { minimum: 0, bypassGenesis: true, block: true } }, + reward: { bignumber: { minimum: 0 } }, + payloadLength: { type: "integer", minimum: 0 }, + payloadHash: { $ref: "hex" }, + generatorPublicKey: { $ref: "publicKey" }, + blockSignature: { $ref: "hex" }, + transactions: { + $ref: "transactions", + minItems: { $data: "1/numberOfTransactions" }, + maxItems: { $data: "1/numberOfTransactions" }, + }, + }, + }, +}; diff --git a/packages/crypto/src/validation/validator.ts b/packages/crypto/src/validation/validator.ts index 336c00edef..841965fc82 100644 --- a/packages/crypto/src/validation/validator.ts +++ b/packages/crypto/src/validation/validator.ts @@ -1,29 +1,11 @@ -import Joi from "joi"; -import { extensions } from "./extensions"; +import { AjvWrapper } from "."; export class Validator { - public static joi: any; - - public static init(): void { - this.joi = Joi.extend(extensions); - } - - public static validate(attributes, rules, options?) { + public static validate(data, schema, options?) { try { - return this.joi.validate( - attributes, - rules, - Object.assign( - { - convert: true, - }, - options, - ), - ); + return AjvWrapper.instance().validate(data, schema); } catch (error) { return { value: null, error: error.stack }; } } } - -Validator.init(); diff --git a/packages/crypto/src/validation/validators/transaction.ts b/packages/crypto/src/validation/validators/transaction.ts deleted file mode 100644 index 340b7b3384..0000000000 --- a/packages/crypto/src/validation/validators/transaction.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { transactions } from "../extensions/transactions"; -import { Validator } from "../validator"; - -export class TransactionValidator { - public rules: any; - - constructor() { - this.rules = Object.keys(transactions).reduce((rules, type) => { - rules[type] = transactions[type](Validator.joi).base; - return rules; - }, {}); - } - - public validate(transaction) { - const { value, error } = Validator.validate(transaction, this.rules[transaction.type], { allowUnknown: true }); - return { - data: value, - errors: error ? error.details : null, - passes: !error, - fails: error, - }; - } -} - -export const transactionValidator = new TransactionValidator(); diff --git a/scripts/deps.sh b/scripts/deps.sh new file mode 100755 index 0000000000..9976d6ab1b --- /dev/null +++ b/scripts/deps.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +for dir in `find packages -mindepth 1 -maxdepth 1 -type d | sort -nr`; do + cd $dir + echo $PWD + cd ../.. + ./node_modules/npm-check-updates/bin/npm-check-updates -u +done diff --git a/scripts/publish/alpha.sh b/scripts/publish/alpha.sh new file mode 100755 index 0000000000..087c575ff9 --- /dev/null +++ b/scripts/publish/alpha.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +for dir in `find packages -mindepth 1 -maxdepth 1 -type d | sort -nr`; do + cd $dir + echo $PWD + npm publish --tag alpha + cd ../.. +done diff --git a/scripts/publish/beta.sh b/scripts/publish/beta.sh new file mode 100755 index 0000000000..0dc6e0e8a8 --- /dev/null +++ b/scripts/publish/beta.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +for dir in `find packages -mindepth 1 -maxdepth 1 -type d | sort -nr`; do + cd $dir + echo $PWD + npm publish --tag beta + cd ../.. +done diff --git a/scripts/publish/latest.sh b/scripts/publish/latest.sh new file mode 100755 index 0000000000..3d88726726 --- /dev/null +++ b/scripts/publish/latest.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +for dir in `find packages -mindepth 1 -maxdepth 1 -type d | sort -nr`; do + cd $dir + echo $PWD + npm publish --tag latest + cd ../.. +done diff --git a/scripts/publish/next.sh b/scripts/publish/next.sh new file mode 100755 index 0000000000..795f0c1ae7 --- /dev/null +++ b/scripts/publish/next.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +for dir in `find packages -mindepth 1 -maxdepth 1 -type d | sort -nr`; do + cd $dir + echo $PWD + npm publish --tag next + cd ../.. +done diff --git a/scripts/publish/rc.sh b/scripts/publish/rc.sh new file mode 100755 index 0000000000..2383de4200 --- /dev/null +++ b/scripts/publish/rc.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +for dir in `find packages -mindepth 1 -maxdepth 1 -type d | sort -nr`; do + cd $dir + echo $PWD + npm publish --tag rc + cd ../.. +done diff --git a/scripts/upgrade.sh b/scripts/upgrade.sh deleted file mode 100755 index f9ebbd0d69..0000000000 --- a/scripts/upgrade.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash - -pm2 delete ark-core > /dev/null 2>&1 -pm2 delete ark-core-relay > /dev/null 2>&1 -pm2 delete ark-core-forger > /dev/null 2>&1 - -pm2 delete core > /dev/null 2>&1 -pm2 delete core-relay > /dev/null 2>&1 -pm2 delete core-forger > /dev/null 2>&1 - -node ./scripts/upgrade/upgrade.js - -# Sometimes the upgrade script doesn't properly replace ARK_ with CORE_ -# https://github.com/ArkEcosystem/core/blob/develop/scripts/upgrade/upgrade.js#L206 -cd ~ - -if [ -f .config/ark-core/devnet/.env ]; then - sed -i 's/ARK_/CORE_/g' .config/ark-core/devnet/.env -fi - -if [ -f .config/ark-core/devnet/plugins.js ]; then - sed -i 's/ARK_/CORE_/g' .config/ark-core/devnet/plugins.js -fi - -if [ -f .config/ark-core/mainnet/.env ]; then - sed -i 's/ARK_/CORE_/g' .config/ark-core/mainnet/.env -fi - -if [ -f .config/ark-core/mainnet/plugins.js ]; then - sed -i 's/ARK_/CORE_/g' .config/ark-core/mainnet/plugins.js -fi - -cd ~/ark-core -yarn setup diff --git a/scripts/upgrade/test.sh b/scripts/upgrade/test.sh deleted file mode 100644 index c4063a6f4b..0000000000 --- a/scripts/upgrade/test.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash - -rm -rf /home/ark/ark-core -git clone https://github.com/ArkEcosystem/core -b upgrade /home/ark/ark-core - -mkdir /home/ark/.ark -touch /home/ark/.ark/.env - -mkdir /home/ark/.ark/config - -mkdir /home/ark/.ark/database -touch /home/ark/.ark/database/json-rpc.sqlite -touch /home/ark/.ark/database/transaction-pool.sqlite -touch /home/ark/.ark/database/webhooks.sqlite - -mkdir /home/ark/.ark/logs -mkdir /home/ark/.ark/logs/mainnet -touch /home/ark/.ark/logs/mainnet/test.log diff --git a/scripts/upgrade/upgrade.js b/scripts/upgrade/upgrade.js deleted file mode 100644 index 2a170926b7..0000000000 --- a/scripts/upgrade/upgrade.js +++ /dev/null @@ -1,227 +0,0 @@ -const envPaths = require('env-paths'); -const expandHomeDir = require('expand-home-dir'); -const fs = require('fs-extra'); -const Joi = require('joi'); -const prompts = require('prompts'); -const { EOL } = require('os'); - -const main = async () => { - let { - corePath, - coreData, - coreNetwork - } = await prompts([{ - type: 'text', - name: 'corePath', - initial: expandHomeDir('~/ark-core'), - message: 'Where is the installation located at? [press ENTER to use default]', - validate: value => fs.existsSync(value) ? true : `${value} does not exist.` - }, { - type: 'text', - name: 'coreData', - initial: expandHomeDir('~/.ark'), - message: 'Where is the configuration located at? [press ENTER to use default]', - validate: value => fs.existsSync(value) ? true : `${value} does not exist.` - }, { - type: 'select', - name: 'coreNetwork', - message: 'What network are you on?', - validate: value => ['mainnet', 'devnet', 'testnet'].includes(value) ? true : `${value} is not a valid network.`, - choices: [ - { title: 'mainnet', value: 'mainnet' }, - { title: 'devnet', value: 'devnet' }, - { title: 'testnet', value: 'testnet' } - ], - }]); - - // Paths - const corePaths = envPaths('ark', { - suffix: 'core' - }); - - corePath = expandHomeDir(corePath); - - const paths = { - cache: { - old: expandHomeDir(`${coreData}/database`), - new: `${corePaths.cache}/${coreNetwork}`, - }, - config: { - old: expandHomeDir(`${coreData}/config`), - new: `${corePaths.config}/${coreNetwork}`, - }, - log: { - old: expandHomeDir(`${coreData}/logs`), - new: `${corePaths.log}/${coreNetwork}`, - }, - temp: { - old: expandHomeDir(`${coreData}/temp`), - new: `${corePaths.temp}/${coreNetwork}`, - }, - data: { - old: expandHomeDir(coreData), - new: `${corePaths.data}/${coreNetwork}`, - }, - }; - - // update commander file if present - const commanderEnv = expandHomeDir('~/.commander') - - if (fs.existsSync(commanderEnv)) { - const commanderContents = fs.readFileSync(commanderEnv).toString(); - - if (!commanderContents.includes('CORE_PATH_DATA')) { - fs.appendFileSync(commanderEnv, `CORE_PATH_DATA=${paths.data.new}${EOL}`); - } - - if (!commanderContents.includes('CORE_PATH_CONFIG')) { - fs.appendFileSync(commanderEnv, `CORE_PATH_CONFIG=${paths.config.new}${EOL}`); - } - - if (!commanderContents.includes('CORE_PATH_CACHE')) { - fs.appendFileSync(commanderEnv, `CORE_PATH_CACHE=${paths.cache.new}${EOL}`); - } - - if (!commanderContents.includes('CORE_PATH_LOG')) { - fs.appendFileSync(commanderEnv, `CORE_PATH_LOG=${paths.log.new}${EOL}`); - } - - if (!commanderContents.includes('CORE_PATH_TEMP')) { - fs.appendFileSync(commanderEnv, `CORE_PATH_TEMP=${paths.temp.new}${EOL}`); - } - - fs.writeFileSync(commanderEnv, commanderContents); - } - - // Create directories - for (const value of Object.values(paths)) { - fs.ensureDirSync(value.new); - } - - // Ensure we copy the .env file - if (!fs.existsSync(`${paths.data.old}/.env`)) { - console.log(`The ${paths.data.old}/.env file does not exist.`) - process.exit(1); - } - - const envCurrent = fs.readFileSync(`${paths.data.old}/.env`).toString(); - - // Move files & directories - for (const value of Object.values(paths)) { - if (fs.existsSync(value.old)) { - console.error(`Moving ${value.old} to ${value.new}.`); - - fs.moveSync(value.old, value.new, { - overwrite: true - }); - } else { - console.error(`Folder ${value.old} does not exist.`); - } - } - - // Move files - if (fs.existsSync(`${paths.cache.new}/json-rpc.sqlite`)) { - fs.moveSync(`${paths.cache.new}/json-rpc.sqlite`, `${paths.data.new}/json-rpc.sqlite`); - } - - if (fs.existsSync(`${paths.cache.new}/transaction-pool-${coreNetwork}.sqlite`)) { - fs.moveSync(`${paths.cache.new}/transaction-pool-${coreNetwork}.sqlite`, `${paths.data.new}/transaction-pool.sqlite`); - } - - if (fs.existsSync(`${paths.cache.new}/webhooks.sqlite`)) { - fs.moveSync(`${paths.cache.new}/webhooks.sqlite`, `${paths.data.new}/webhooks.sqlite`); - } - - if (fs.existsSync(`${corePaths.log}/core/${coreNetwork}`)) { - fs.moveSync(`${corePaths.log}/core/${coreNetwork}`, `${paths.log.new}/${coreNetwork}`); - } - - if (fs.existsSync(`${paths.data.new}/snapshots/${coreNetwork}`)) { - fs.moveSync(`${paths.data.new}/snapshots/${coreNetwork}`, `${paths.data.new}/snapshots.tmp`) - fs.rmdirSync(`${paths.data.new}/snapshots`) - fs.renameSync(`${paths.data.new}/snapshots.tmp`, `${paths.data.new}/snapshots`) - } - - // Remove old or temp files - fs.removeSync(`${paths.config.old}/genesisBlock.json`); - fs.removeSync(`${paths.config.old}/peers_backup.json`); - fs.removeSync(`${paths.config.old}/network.json`); - fs.removeSync(`${paths.config.new}/genesisBlock.json`); - fs.removeSync(`${paths.config.new}/peers_backup.json`); - fs.removeSync(`${paths.config.new}/network.json`); - - // Ensure that all files core needs exist - const requiredFiles = [ - { - copy: `${paths.config.new}/delegates.json`, - original: `${corePath}/packages/core/src/config/${coreNetwork}/delegates.json`, - }, { - copy: `${paths.config.new}/peers.json`, - original: `${corePath}/packages/core/src/config/${coreNetwork}/peers.json`, - }, { - copy: `${paths.config.new}/plugins.js`, - original: `${corePath}/packages/core/src/config/${coreNetwork}/plugins.js`, - }, - ]; - - for (const file of requiredFiles) { - if (!fs.existsSync(file.copy)) { - if (fs.existsSync(file.original)) { - console.error(`Copying ${file.original} to ${file.copy} because it is missing.`); - - fs.copySync(file.original, file.copy); - } else { - console.error(`Original ${file.original} does not exist.`); - } - } - } - - // Update delegate configuration - console.log('Update delegate configuration'); - let configDelegates = require(`${paths.config.new}/delegates.json`) - delete configDelegates.dynamicFee - delete configDelegates.dynamicFees - fs.writeFileSync(`${paths.config.new}/delegates.json`, JSON.stringify(configDelegates, null, 4)); - - // Update environment file - console.log('Update environment configuration'); - fs.writeFileSync(`${paths.config.new}/.env`, envCurrent.replace(new RegExp('ARK_', 'g'), 'CORE_')); - - // Update plugins file - console.log('Update plugins configuration'); - let pluginContents = fs.readFileSync(`${paths.config.new}/plugins.js`).toString(); - pluginContents = pluginContents.replace('@arkecosystem/core-transaction-pool-mem', '@arkecosystem/core-transaction-pool'); - pluginContents = pluginContents.replace('"@arkecosystem/core-config": {},', ''); - pluginContents = pluginContents.replace("'@arkecosystem/core-config': {},", ''); - pluginContents = pluginContents.replace(new RegExp('ARK_', 'g'), 'CORE_'); - fs.writeFileSync(`${paths.config.new}/plugins.js`, pluginContents); - - // Validate configuration files - console.log('Validating configuration'); - const { error } = Joi.validate({ - delegates: require(`${paths.config.new}/delegates.json`), - peers: require(`${paths.config.new}/peers.json`), - plugins: require(`${paths.config.new}/plugins.js`), - }, Joi.object({ - delegates: Joi.object({ - secrets: Joi.array().items(Joi.string()), - bip38: Joi.string(), - }), - peers: Joi.object().required(), - plugins: Joi.object().required(), - }).unknown()); - - if (error) { - console.log(error); - } - - // Clean up - console.log('Performing clean up'); - for (const value of Object.values(paths)) { - if (fs.existsSync(value.old)) { - fs.removeSync(value.old); - } - } -} - -main() diff --git a/upgrade/2.1.0/exchange.sh b/upgrade/2.1.0/exchange.sh deleted file mode 100644 index ef5b0c5c49..0000000000 --- a/upgrade/2.1.0/exchange.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -cd ~/ark-core -pm2 delete ark-core -pm2 delete ark-core-relay -git reset --hard -git pull -git checkout master -yarn run bootstrap -yarn run upgrade - -pm2 --name 'ark-core-relay' start ~/ark-core/packages/core/dist/index.js -- relay --network mainnet diff --git a/upgrade/2.1.0/normal.sh b/upgrade/2.1.0/normal.sh deleted file mode 100644 index be61882355..0000000000 --- a/upgrade/2.1.0/normal.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash - -cd ~/ark-core -pm2 delete all -git reset --hard -git pull -git checkout master -yarn run bootstrap -yarn run upgrade - -# If you do not use Core Commander you can skip this step. -cd ~/core-commander -git reset --hard -git pull -git checkout master -bash commander.sh diff --git a/vagrant/bootstrap.sh b/vagrant/bootstrap.sh index cf6fc6b88c..c7c3b77cff 100644 --- a/vagrant/bootstrap.sh +++ b/vagrant/bootstrap.sh @@ -199,11 +199,11 @@ fi success "Installed system updates!" -heading "Installing Ark Core..." +heading "Installing Persona Core..." cd /home/vagrant -git clone https://github.com/ArkEcosystem/core.git ark-core -b develop +git clone https://github.com/ARKEcosystem/core.git ark-core -b develop cd ark-core yarn setup -success "Installed Ark Core!" +success "Installed Persona Core!" diff --git a/yarn.lock b/yarn.lock index 88a4402b7a..392b6a9e8f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,22 +2,10 @@ # yarn lockfile v1 -"@apollographql/apollo-tools@^0.3.0": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@apollographql/apollo-tools/-/apollo-tools-0.3.2.tgz#415c984955c4ae249550def698cbffab8d27bda4" - integrity sha512-IiuO1XaxxvbZa19gGHBOEgQKmMHB+ghXaNjSbY5dWqCEQgBDgJBA/2a1Oq9tMPhEPAmEY2FOuhaWRxxKxmVdlQ== - dependencies: - apollo-env "0.3.2" - -"@apollographql/graphql-playground-html@^1.6.6": - version "1.6.6" - resolved "https://registry.yarnpkg.com/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.6.tgz#022209e28a2b547dcde15b219f0c50f47aa5beb3" - integrity sha512-lqK94b+caNtmKFs5oUVXlSpN3sm5IXZ+KfhMxOtr0LR2SqErzkoJilitjDvJ1WbjHlxLI7WtCjRmOLdOGJqtMQ== - -"@arkecosystem/utils@^0.2.4": - version "0.2.4" - resolved "https://registry.yarnpkg.com/@arkecosystem/utils/-/utils-0.2.4.tgz#1458a1046141adb55e9b8af0a355b00920d35332" - integrity sha512-24+DQLJ42KOJywFiq6oWd7oUJwbUZMRrVUkec8HprwXXza5MjmcIYf62cHqSfikTCxWJdXXKujUaKNd9ZJpPoA== +"@arkecosystem/utils@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@arkecosystem/utils/-/utils-0.3.0.tgz#f0d6040ab0134ee7db046ab8246cb17b2c5b9867" + integrity sha512-FNHihowzUItP8qQC8m8I2/XFogffjynbhIlQaAAvAQHep3QDfPL4Rr92iGdCDzpPpP166GlorkHD7344PfgxzA== dependencies: "@flatten/array" "^1.1.7" dottie "^2.0.1" @@ -33,34 +21,34 @@ dependencies: "@babel/highlight" "^7.0.0" -"@babel/core@^7.2.2": - version "7.2.2" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.2.2.tgz#07adba6dde27bb5ad8d8672f15fde3e08184a687" - integrity sha512-59vB0RWt09cAct5EIe58+NzGP4TFSD3Bz//2/ELy3ZeTeKF6VTD1AXlH8BGGbCX0PuobZBsIzO7IAI9PH67eKw== +"@babel/core@^7.1.0", "@babel/core@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.3.4.tgz#921a5a13746c21e32445bf0798680e9d11a6530b" + integrity sha512-jRsuseXBo9pN197KnDwhhaaBzyZr2oIcLHHTt2oDdQrej5Qp57dCCJafWx5ivU8/alEYDpssYqv1MUqcxwQlrA== dependencies: "@babel/code-frame" "^7.0.0" - "@babel/generator" "^7.2.2" + "@babel/generator" "^7.3.4" "@babel/helpers" "^7.2.0" - "@babel/parser" "^7.2.2" + "@babel/parser" "^7.3.4" "@babel/template" "^7.2.2" - "@babel/traverse" "^7.2.2" - "@babel/types" "^7.2.2" + "@babel/traverse" "^7.3.4" + "@babel/types" "^7.3.4" convert-source-map "^1.1.0" debug "^4.1.0" json5 "^2.1.0" - lodash "^4.17.10" + lodash "^4.17.11" resolve "^1.3.2" semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.2.2": - version "7.3.2" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.3.2.tgz#fff31a7b2f2f3dad23ef8e01be45b0d5c2fc0132" - integrity sha512-f3QCuPppXxtZOEm5GWPra/uYUjmNQlu9pbAD8D/9jze4pTY83rTtB1igTBSwvkeNlC5gR24zFFkz+2WHLFQhqQ== +"@babel/generator@^7.0.0", "@babel/generator@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.3.4.tgz#9aa48c1989257877a9d971296e5b73bfe72e446e" + integrity sha512-8EXhHRFqlVVWXPezBW5keTiQi/rJMQTg/Y9uVCEZ0CAF3PKtCCaVRnp64Ii1ujhkoDhhF1fVsImoN4yJ2uz4Wg== dependencies: - "@babel/types" "^7.3.2" + "@babel/types" "^7.3.4" jsesc "^2.5.1" - lodash "^4.17.10" + lodash "^4.17.11" source-map "^0.5.0" trim-right "^1.0.1" @@ -184,15 +172,15 @@ "@babel/traverse" "^7.1.0" "@babel/types" "^7.0.0" -"@babel/helper-replace-supers@^7.1.0": - version "7.2.3" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.2.3.tgz#19970020cf22677d62b3a689561dbd9644d8c5e5" - integrity sha512-GyieIznGUfPXPWu0yLS6U55Mz67AZD9cUk0BfirOWlPrXlBcan9Gz+vHGz+cPfuoweZSnPzPIm67VtQM0OWZbA== +"@babel/helper-replace-supers@^7.1.0", "@babel/helper-replace-supers@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.3.4.tgz#a795208e9b911a6eeb08e5891faacf06e7013e13" + integrity sha512-pvObL9WVf2ADs+ePg0jrqlhHoxRXlOa+SHRHzAXIz2xkYuOHfGl+fKxPMaS4Fq+uje8JQPobnertBBvyrWnQ1A== dependencies: "@babel/helper-member-expression-to-functions" "^7.0.0" "@babel/helper-optimise-call-expression" "^7.0.0" - "@babel/traverse" "^7.2.3" - "@babel/types" "^7.0.0" + "@babel/traverse" "^7.3.4" + "@babel/types" "^7.3.4" "@babel/helper-simple-access@^7.1.0": version "7.1.0" @@ -237,10 +225,10 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.2.2", "@babel/parser@^7.2.3": - version "7.3.2" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.3.2.tgz#95cdeddfc3992a6ca2a1315191c1679ca32c55cd" - integrity sha512-QzNUC2RO1gadg+fs21fi0Uu0OuGNzRKEmgCxoLNzbCdoprLwjfmZwzUrpUNfJPaVRwBpDY47A17yYEGWyRelnQ== +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.2.2", "@babel/parser@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.3.4.tgz#a43357e4bbf4b92a437fb9e465c192848287f27c" + integrity sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ== "@babel/plugin-proposal-async-generator-functions@^7.2.0": version "7.2.0" @@ -259,10 +247,10 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-json-strings" "^7.2.0" -"@babel/plugin-proposal-object-rest-spread@^7.3.1": - version "7.3.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.3.2.tgz#6d1859882d4d778578e41f82cc5d7bf3d5daf6c1" - integrity sha512-DjeMS+J2+lpANkYLLO+m6GjoTMygYglKmRe6cDTbFv3L9i6mmiE8fe6B8MtCSLZpVXscD5kn7s6SgtHrDoBWoA== +"@babel/plugin-proposal-object-rest-spread@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.3.4.tgz#47f73cf7f2a721aad5c0261205405c642e424654" + integrity sha512-j7VQmbbkA+qrzNqbKHrBsW3ddFnOeva6wzSe/zB7T+xaxGc+RCpwo44wCmRixAIGRoIpmVgvzFzNJqQcO3/9RA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-object-rest-spread" "^7.2.0" @@ -298,7 +286,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-syntax-object-rest-spread@^7.2.0": +"@babel/plugin-syntax-object-rest-spread@^7.0.0", "@babel/plugin-syntax-object-rest-spread@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz#3b7a3e733510c57e820b9142a6579ac8b0dfad2e" integrity sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA== @@ -319,10 +307,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-async-to-generator@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.2.0.tgz#68b8a438663e88519e65b776f8938f3445b1a2ff" - integrity sha512-CEHzg4g5UraReozI9D4fblBYABs7IM6UerAVG7EJVrTLC5keh00aEuLUT+O40+mJCEzaXkYfTCUKIyeDfMOFFQ== +"@babel/plugin-transform-async-to-generator@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.3.4.tgz#4e45408d3c3da231c0e7b823f407a53a7eb3048c" + integrity sha512-Y7nCzv2fw/jEZ9f678MuKdMo99MFDJMT/PvD9LisrR5JDFcJH6vYeH6RnjVt3p5tceyGRvTtEN0VOlU+rgHZjA== dependencies: "@babel/helper-module-imports" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" @@ -335,25 +323,25 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-block-scoping@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.2.0.tgz#f17c49d91eedbcdf5dd50597d16f5f2f770132d4" - integrity sha512-vDTgf19ZEV6mx35yiPJe4fS02mPQUUcBNwWQSZFXSzTSbsJFQvHt7DqyS3LK8oOWALFOsJ+8bbqBgkirZteD5Q== +"@babel/plugin-transform-block-scoping@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.3.4.tgz#5c22c339de234076eee96c8783b2fed61202c5c4" + integrity sha512-blRr2O8IOZLAOJklXLV4WhcEzpYafYQKSGT3+R26lWG41u/FODJuBggehtOwilVAcFu393v3OFj+HmaE6tVjhA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" - lodash "^4.17.10" + lodash "^4.17.11" -"@babel/plugin-transform-classes@^7.2.0": - version "7.2.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.2.2.tgz#6c90542f210ee975aa2aa8c8b5af7fa73a126953" - integrity sha512-gEZvgTy1VtcDOaQty1l10T3jQmJKlNVxLDCs+3rCVPr6nMkODLELxViq5X9l+rfxbie3XrfrMCYYY6eX3aOcOQ== +"@babel/plugin-transform-classes@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.3.4.tgz#dc173cb999c6c5297e0b5f2277fdaaec3739d0cc" + integrity sha512-J9fAvCFBkXEvBimgYxCjvaVDzL6thk0j0dBvCeZmIUDBwyt+nv6HfbImsSrWsYXfDNDivyANgJlFXDUWRTZBuA== dependencies: "@babel/helper-annotate-as-pure" "^7.0.0" "@babel/helper-define-map" "^7.1.0" "@babel/helper-function-name" "^7.1.0" "@babel/helper-optimise-call-expression" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-replace-supers" "^7.1.0" + "@babel/helper-replace-supers" "^7.3.4" "@babel/helper-split-export-declaration" "^7.0.0" globals "^11.1.0" @@ -434,10 +422,10 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-simple-access" "^7.1.0" -"@babel/plugin-transform-modules-systemjs@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.2.0.tgz#912bfe9e5ff982924c81d0937c92d24994bb9068" - integrity sha512-aYJwpAhoK9a+1+O625WIjvMY11wkB/ok0WClVwmeo3mCjcNRjt+/8gHWrB5i+00mUju0gWsBkQnPpdvQ7PImmQ== +"@babel/plugin-transform-modules-systemjs@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.3.4.tgz#813b34cd9acb6ba70a84939f3680be0eb2e58861" + integrity sha512-VZ4+jlGOF36S7TjKs8g4ojp4MEI+ebCQZdswWb/T9I4X84j8OtFAyjXjt/M16iIm5RIZn0UMQgg/VgIwo/87vw== dependencies: "@babel/helper-hoist-variables" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" @@ -473,20 +461,20 @@ "@babel/helper-replace-supers" "^7.1.0" "@babel/plugin-transform-parameters@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.2.0.tgz#0d5ad15dc805e2ea866df4dd6682bfe76d1408c2" - integrity sha512-kB9+hhUidIgUoBQ0MsxMewhzr8i60nMa2KgeJKQWYrqQpqcBYtnpR+JgkadZVZoaEZ/eKu9mclFaVwhRpLNSzA== + version "7.3.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.3.3.tgz#3a873e07114e1a5bee17d04815662c8317f10e30" + integrity sha512-IrIP25VvXWu/VlBWTpsjGptpomtIkYrN/3aDp4UKm7xK6UxZY88kcJ1UwETbzHAlwN21MnNfwlar0u8y3KpiXw== dependencies: "@babel/helper-call-delegate" "^7.1.0" "@babel/helper-get-function-arity" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-regenerator@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.0.0.tgz#5b41686b4ed40bef874d7ed6a84bdd849c13e0c1" - integrity sha512-sj2qzsEx8KDVv1QuJc/dEfilkg3RRPvPYx/VnKLtItVQRWt1Wqf5eVCOLZm29CiGFfYYsA3VPjfizTCV0S0Dlw== +"@babel/plugin-transform-regenerator@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.3.4.tgz#1601655c362f5b38eead6a52631f5106b29fa46a" + integrity sha512-hvJg8EReQvXT6G9H2MvNPXkv9zK36Vxa1+csAVTpE1J3j0zlHplw76uudEbJxgvqZzAq9Yh45FLD4pk5mKRFQA== dependencies: - regenerator-transform "^0.13.3" + regenerator-transform "^0.13.4" "@babel/plugin-transform-shorthand-properties@^7.2.0": version "7.2.0" @@ -534,16 +522,16 @@ "@babel/helper-regex" "^7.0.0" regexpu-core "^4.1.3" -"@babel/preset-env@^7.2.0": - version "7.3.1" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.3.1.tgz#389e8ca6b17ae67aaf9a2111665030be923515db" - integrity sha512-FHKrD6Dxf30e8xgHQO0zJZpUPfVZg+Xwgz5/RdSWCbza9QLNk4Qbp40ctRoqDxml3O8RMzB1DU55SXeDG6PqHQ== +"@babel/preset-env@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.3.4.tgz#887cf38b6d23c82f19b5135298bdb160062e33e1" + integrity sha512-2mwqfYMK8weA0g0uBKOt4FE3iEodiHy9/CW0b+nWXcbL+pGzLx8ESYc+j9IIxr6LTDHWKgPm71i9smo02bw+gA== dependencies: "@babel/helper-module-imports" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-proposal-async-generator-functions" "^7.2.0" "@babel/plugin-proposal-json-strings" "^7.2.0" - "@babel/plugin-proposal-object-rest-spread" "^7.3.1" + "@babel/plugin-proposal-object-rest-spread" "^7.3.4" "@babel/plugin-proposal-optional-catch-binding" "^7.2.0" "@babel/plugin-proposal-unicode-property-regex" "^7.2.0" "@babel/plugin-syntax-async-generators" "^7.2.0" @@ -551,10 +539,10 @@ "@babel/plugin-syntax-object-rest-spread" "^7.2.0" "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" "@babel/plugin-transform-arrow-functions" "^7.2.0" - "@babel/plugin-transform-async-to-generator" "^7.2.0" + "@babel/plugin-transform-async-to-generator" "^7.3.4" "@babel/plugin-transform-block-scoped-functions" "^7.2.0" - "@babel/plugin-transform-block-scoping" "^7.2.0" - "@babel/plugin-transform-classes" "^7.2.0" + "@babel/plugin-transform-block-scoping" "^7.3.4" + "@babel/plugin-transform-classes" "^7.3.4" "@babel/plugin-transform-computed-properties" "^7.2.0" "@babel/plugin-transform-destructuring" "^7.2.0" "@babel/plugin-transform-dotall-regex" "^7.2.0" @@ -565,13 +553,13 @@ "@babel/plugin-transform-literals" "^7.2.0" "@babel/plugin-transform-modules-amd" "^7.2.0" "@babel/plugin-transform-modules-commonjs" "^7.2.0" - "@babel/plugin-transform-modules-systemjs" "^7.2.0" + "@babel/plugin-transform-modules-systemjs" "^7.3.4" "@babel/plugin-transform-modules-umd" "^7.2.0" "@babel/plugin-transform-named-capturing-groups-regex" "^7.3.0" "@babel/plugin-transform-new-target" "^7.0.0" "@babel/plugin-transform-object-super" "^7.2.0" "@babel/plugin-transform-parameters" "^7.2.0" - "@babel/plugin-transform-regenerator" "^7.0.0" + "@babel/plugin-transform-regenerator" "^7.3.4" "@babel/plugin-transform-shorthand-properties" "^7.2.0" "@babel/plugin-transform-spread" "^7.2.0" "@babel/plugin-transform-sticky-regex" "^7.2.0" @@ -583,14 +571,6 @@ js-levenshtein "^1.1.3" semver "^5.3.0" -"@babel/runtime-corejs2@7.3.1": - version "7.3.1" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.3.1.tgz#0c113242e2328f9674d42703a89bee6ebebe9a82" - integrity sha512-YpO13776h3e6Wy8dl2J8T9Qwlvopr+b4trCEhHE+yek6yIqV8sx6g3KozdHMbXeBpjosbPi+Ii5Z7X9oXFHUKA== - dependencies: - core-js "^2.5.7" - regenerator-runtime "^0.12.0" - "@babel/runtime@7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.0.0.tgz#adeb78fedfc855aa05bc041640f3f6f98e85424c" @@ -598,7 +578,7 @@ dependencies: regenerator-runtime "^0.12.0" -"@babel/template@^7.1.0", "@babel/template@^7.1.2", "@babel/template@^7.2.2": +"@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.1.2", "@babel/template@^7.2.2": version "7.2.2" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.2.2.tgz#005b3fdf0ed96e88041330379e0da9a708eb2907" integrity sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g== @@ -607,55 +587,62 @@ "@babel/parser" "^7.2.2" "@babel/types" "^7.2.2" -"@babel/traverse@^7.1.0", "@babel/traverse@^7.1.5", "@babel/traverse@^7.2.2", "@babel/traverse@^7.2.3": - version "7.2.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.2.3.tgz#7ff50cefa9c7c0bd2d81231fdac122f3957748d8" - integrity sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw== +"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.1.5", "@babel/traverse@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.3.4.tgz#1330aab72234f8dea091b08c4f8b9d05c7119e06" + integrity sha512-TvTHKp6471OYEcE/91uWmhR6PrrYywQntCHSaZ8CM8Vmp+pjAusal4nGB2WCCQd0rvI7nOMKn9GnbcvTUz3/ZQ== dependencies: "@babel/code-frame" "^7.0.0" - "@babel/generator" "^7.2.2" + "@babel/generator" "^7.3.4" "@babel/helper-function-name" "^7.1.0" "@babel/helper-split-export-declaration" "^7.0.0" - "@babel/parser" "^7.2.3" - "@babel/types" "^7.2.2" + "@babel/parser" "^7.3.4" + "@babel/types" "^7.3.4" debug "^4.1.0" globals "^11.1.0" - lodash "^4.17.10" + lodash "^4.17.11" -"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.3.0", "@babel/types@^7.3.2": - version "7.3.2" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.3.2.tgz#424f5be4be633fff33fb83ab8d67e4a8290f5a2f" - integrity sha512-3Y6H8xlUlpbGR+XvawiH0UXehqydTmNmEpozWcXymqwcrwYAl5KMvKtQ+TF6f6E08V6Jur7v/ykdDSF+WDEIXQ== +"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.3.0", "@babel/types@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.3.4.tgz#bf482eaeaffb367a28abbf9357a94963235d90ed" + integrity sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ== dependencies: esutils "^2.0.2" - lodash "^4.17.10" + lodash "^4.17.11" to-fast-properties "^2.0.0" -"@bugsnag/browser@^5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@bugsnag/browser/-/browser-5.2.0.tgz#69ff77c8d310eccbdecd545744a22ea6030703f1" - integrity sha512-QImSbwVjcEbaUjCxNRVOELUahdHmRfkZ9o0UJpMDsfYNqpMpYzI7DdyH0BUnXv/yI4ymGpAQbFSEqVMTqSa9WA== +"@bugsnag/browser@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@bugsnag/browser/-/browser-6.0.0.tgz#d6e55e05d2f7cfa1f729ac5143b60e4a715122bf" + integrity sha512-EZ+I9CscLLkCsPdY8+X47yAjb7n/qWdLx299GNdW53/D4pO2Yo2sKCE53flyUx5BtR9yfE1Yu1vCojq89uKq7w== -"@bugsnag/js@^5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@bugsnag/js/-/js-5.2.0.tgz#3ef8c63cfac7fc8764459461ad33a31fe44cad36" - integrity sha512-tL+Jr4DnQbCH+nLR6Kb9m5QbstIbmFbL9yKId2W3z0MvPxTg9vhRSlu35+99+04CRHrQu0VdY2n9RzDbD5kpgg== +"@bugsnag/js@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@bugsnag/js/-/js-6.0.0.tgz#78c245b9e97bfe445559abeee3004068afa81883" + integrity sha512-tF1D7hjhG8aXPk5rx23rDo0j7ZrIjuRSTcgkvD0mJX6cg3f1+qqXL2GlUMQ1siSx6hvNONTr3Q7SnlhEEQ1G6A== dependencies: - "@bugsnag/browser" "^5.2.0" - "@bugsnag/node" "^5.2.0" + "@bugsnag/browser" "^6.0.0" + "@bugsnag/node" "^6.0.0" -"@bugsnag/node@^5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@bugsnag/node/-/node-5.2.0.tgz#b1c193161d2a006fabf8ce2c22484c297d57f4ad" - integrity sha512-2KqUcZ/uTkNP9G4aGW+sKhdce3Z68pg9I+LnjUDvpMEQ82O/zxy+NLkGTSz9hveNBdznJ1YoqjfoBdRnHkyRSw== +"@bugsnag/node@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@bugsnag/node/-/node-6.0.0.tgz#bae6e6b4d7c172755e66253ecdd66a06505e98e5" + integrity sha512-TUsX8N9Dzpxisfml8n6LmMyo2YdjItJ5A3RoIVokZ8qBHbFXp3lQmtvp5nTVQc/buL42hFW2+ZPWxcNRQUDcEA== dependencies: byline "^5.0.0" error-stack-parser "^2.0.2" iserror "^0.0.2" pump "^3.0.0" - request "^2.87.0" stack-generator "^2.0.3" +"@cnakazawa/watch@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef" + integrity sha512-r5160ogAvGyHsal38Kux7YYtodEKOj89RGb28ht1jh3SJb08VwRwAKKJL0bGb04Zd/3r9FL3BFIc3bBidYffCA== + dependencies: + exec-sh "^0.3.2" + minimist "^1.2.0" + "@faustbrian/benchmarker@^0.1.2": version "0.1.2" resolved "https://registry.yarnpkg.com/@faustbrian/benchmarker/-/benchmarker-0.1.2.tgz#80fe90f51473eb711fb59b706d52acf4253fbad2" @@ -663,6 +650,11 @@ dependencies: benchmark "^2.1.4" +"@faustbrian/dato@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@faustbrian/dato/-/dato-0.2.0.tgz#80149f37504d658fada9c270187f10cc7578a12f" + integrity sha512-4f5w3bJoitqPfvsllSJXCo1jKSwRDaZbeNtp8XOKr0kXA124YkUY4ziET41p6/WhPIlx26yP0CADfOrnDInI1Q== + "@faustbrian/hapi-version@^0.2.11": version "0.2.11" resolved "https://registry.yarnpkg.com/@faustbrian/hapi-version/-/hapi-version-0.2.11.tgz#9e897d48ce9c4382e752635c34232ba769854d39" @@ -685,19 +677,142 @@ resolved "https://registry.yarnpkg.com/@flatten/array/-/array-1.1.7.tgz#f94659bbf32d558f692a6234304a247312a6fa38" integrity sha512-qoKzWImYDQ3MR+bhycIZbRYmzeFabPXZKojrZJCO0QyFWZisob+1+OelW2XE3aRtWgOymdm08k8FlXN9jCZgkQ== -"@iamstarkov/listr-update-renderer@0.4.1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/@iamstarkov/listr-update-renderer/-/listr-update-renderer-0.4.1.tgz#d7c48092a2dcf90fd672b6c8b458649cb350c77e" - integrity sha512-IJyxQWsYDEkf8C8QthBn5N8tIUR9V9je6j3sMIpAkonaadjbvxmRC6RAhpa3RKxndhNnU2M6iNbtJwd7usQYIA== +"@jest/console@^24.3.0": + version "24.3.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-24.3.0.tgz#7bd920d250988ba0bf1352c4493a48e1cb97671e" + integrity sha512-NaCty/OOei6rSDcbPdMiCbYCI0KGFGPgGO6B09lwWt5QTxnkuhKYET9El5u5z1GAcSxkQmSMtM63e24YabCWqA== dependencies: - chalk "^1.1.3" - cli-truncate "^0.2.1" - elegant-spinner "^1.0.1" - figures "^1.7.0" - indent-string "^3.0.0" - log-symbols "^1.0.2" - log-update "^2.3.0" - strip-ansi "^3.0.1" + "@jest/source-map" "^24.3.0" + "@types/node" "*" + chalk "^2.0.1" + slash "^2.0.0" + +"@jest/core@^24.3.1": + version "24.3.1" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-24.3.1.tgz#9811596d9fcc6dbb3d4062c67e4c4867bc061585" + integrity sha512-orucOIBKfXgm1IJirtPT0ToprqDVGYKUNJKNc9a6v1Lww6qLPq+xj5OfxyhpJb2rWOgzEkATW1bfZzg3oqV70w== + dependencies: + "@jest/console" "^24.3.0" + "@jest/reporters" "^24.3.1" + "@jest/test-result" "^24.3.0" + "@jest/transform" "^24.3.1" + "@jest/types" "^24.3.0" + ansi-escapes "^3.0.0" + chalk "^2.0.1" + exit "^0.1.2" + graceful-fs "^4.1.15" + jest-changed-files "^24.3.0" + jest-config "^24.3.1" + jest-haste-map "^24.3.1" + jest-message-util "^24.3.0" + jest-regex-util "^24.3.0" + jest-resolve-dependencies "^24.3.1" + jest-runner "^24.3.1" + jest-runtime "^24.3.1" + jest-snapshot "^24.3.1" + jest-util "^24.3.0" + jest-validate "^24.3.1" + jest-watcher "^24.3.0" + micromatch "^3.1.10" + p-each-series "^1.0.0" + pirates "^4.0.1" + realpath-native "^1.1.0" + rimraf "^2.5.4" + strip-ansi "^5.0.0" + +"@jest/environment@^24.3.1": + version "24.3.1" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-24.3.1.tgz#1fbda3ec8fb8ffbaee665d314da91d662227e11e" + integrity sha512-M8bqEkQqPwZVhMMFMqqCnzqIZtuM5vDMfFQ9ZvnEfRT+2T1zTA4UAOH/V4HagEi6S3BCd/mdxFdYmPgXf7GKCA== + dependencies: + "@jest/fake-timers" "^24.3.0" + "@jest/transform" "^24.3.1" + "@jest/types" "^24.3.0" + "@types/node" "*" + jest-mock "^24.3.0" + +"@jest/fake-timers@^24.3.0": + version "24.3.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-24.3.0.tgz#0a7f8b877b78780c3fa5c3f8683cc0aaf9488331" + integrity sha512-rHwVI17dGMHxHzfAhnZ04+wFznjFfZ246QugeBnbiYr7/bDosPD2P1qeNjWnJUUcfl0HpS6kkr+OB/mqSJxQFg== + dependencies: + "@jest/types" "^24.3.0" + "@types/node" "*" + jest-message-util "^24.3.0" + jest-mock "^24.3.0" + +"@jest/reporters@^24.3.1": + version "24.3.1" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.3.1.tgz#68e4abc8d4233acd0dd87287f3bd270d81066248" + integrity sha512-jEIDJcvk20ReUW1Iqb+prlAcFV+kfFhQ/01poCq8X9As7/l/2y1GqVwJ3+6SaPTZuCXh0d0LVDy86zDAa8zlVA== + dependencies: + "@jest/environment" "^24.3.1" + "@jest/test-result" "^24.3.0" + "@jest/transform" "^24.3.1" + "@jest/types" "^24.3.0" + chalk "^2.0.1" + exit "^0.1.2" + glob "^7.1.2" + istanbul-api "^2.1.1" + istanbul-lib-coverage "^2.0.2" + istanbul-lib-instrument "^3.0.1" + istanbul-lib-source-maps "^3.0.1" + jest-haste-map "^24.3.1" + jest-resolve "^24.3.1" + jest-runtime "^24.3.1" + jest-util "^24.3.0" + jest-worker "^24.3.1" + node-notifier "^5.2.1" + slash "^2.0.0" + source-map "^0.6.0" + string-length "^2.0.0" + +"@jest/source-map@^24.3.0": + version "24.3.0" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-24.3.0.tgz#563be3aa4d224caf65ff77edc95cd1ca4da67f28" + integrity sha512-zALZt1t2ou8le/crCeeiRYzvdnTzaIlpOWaet45lNSqNJUnXbppUUFR4ZUAlzgDmKee4Q5P/tKXypI1RiHwgag== + dependencies: + callsites "^3.0.0" + graceful-fs "^4.1.15" + source-map "^0.6.0" + +"@jest/test-result@^24.3.0": + version "24.3.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-24.3.0.tgz#4c0b1c9716212111920f7cf8c4329c69bc81924a" + integrity sha512-j7UZ49T8C4CVipEY99nLttnczVTtLyVzFfN20OiBVn7awOs0U3endXSTq7ouPrLR5y4YjI5GDcbcvDUjgeamzg== + dependencies: + "@jest/console" "^24.3.0" + "@jest/types" "^24.3.0" + "@types/istanbul-lib-coverage" "^1.1.0" + +"@jest/transform@^24.3.1": + version "24.3.1" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-24.3.1.tgz#ce9e1329eb5e640f493bcd5c8eb9970770959bfc" + integrity sha512-PpjylI5goT4Si69+qUjEeHuKjex0LjjrqJzrMYzlOZn/+SCumGKuGC0UQFeEPThyGsFvWH1Q4gj0R66eOHnIpw== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^24.3.0" + babel-plugin-istanbul "^5.1.0" + chalk "^2.0.1" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.1.15" + jest-haste-map "^24.3.1" + jest-regex-util "^24.3.0" + jest-util "^24.3.0" + micromatch "^3.1.10" + realpath-native "^1.1.0" + slash "^2.0.0" + source-map "^0.6.1" + write-file-atomic "2.4.1" + +"@jest/types@^24.3.0": + version "24.3.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.3.0.tgz#3f6e117e47248a9a6b5f1357ec645bd364f7ad23" + integrity sha512-VoO1F5tU2n/93QN/zaZ7Q8SeV/Rj+9JJOgbvKbBwy4lenvmdj1iDaQEPXGTKrO6OSvDeb2drTFipZJYxgo6kIQ== + dependencies: + "@types/istanbul-lib-coverage" "^1.1.0" + "@types/yargs" "^12.0.9" "@keyv/sql@1.1.2": version "1.1.2" @@ -715,49 +830,49 @@ pify "3.0.0" sqlite3 "4.0.2" -"@lerna/add@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.11.0.tgz#eb924d05457b5c46ce4836cf3a0a05055ae788aa" - integrity sha512-A2u889e+GeZzL28jCpcN53iHq2cPWVnuy5tv5nvG/MIg0PxoAQOUvphexKsIbqzVd9Damdmv5W0u9kS8y8TTow== +"@lerna/add@3.13.1": + version "3.13.1" + resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.13.1.tgz#2cd7838857edb3b43ed73e3c21f69a20beb9b702" + integrity sha512-cXk42YbuhzEnADCK8Qte5laC9Qo03eJLVnr0qKY85jQUM/T4URe3IIUemqpg0CpVATrB+Vz+iNdeqw9ng1iALw== dependencies: - "@lerna/bootstrap" "3.11.0" - "@lerna/command" "3.11.0" - "@lerna/filter-options" "3.11.0" - "@lerna/npm-conf" "3.7.0" - "@lerna/validation-error" "3.11.0" + "@lerna/bootstrap" "3.13.1" + "@lerna/command" "3.13.1" + "@lerna/filter-options" "3.13.0" + "@lerna/npm-conf" "3.13.0" + "@lerna/validation-error" "3.13.0" dedent "^0.7.0" npm-package-arg "^6.1.0" p-map "^1.2.0" - pacote "^9.4.1" + pacote "^9.5.0" semver "^5.5.0" -"@lerna/batch-packages@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/batch-packages/-/batch-packages-3.11.0.tgz#cb009b6680b6e5fb586e9578072f4b595288eaf8" - integrity sha512-ETO3prVqDZs/cpZo00ij61JEZ8/ADJx1OG/d/KtTdHlyRfQsb09Xzf0w+boimqa8fIqhpM3o5FV9GKd6GQ3iFQ== +"@lerna/batch-packages@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/batch-packages/-/batch-packages-3.13.0.tgz#697fde5be28822af9d9dca2f750250b90a89a000" + integrity sha512-TgLBTZ7ZlqilGnzJ3xh1KdAHcySfHytgNRTdG9YomfriTU6kVfp1HrXxKJYVGs7ClPUNt2CTFEOkw0tMBronjw== dependencies: - "@lerna/package-graph" "3.11.0" - "@lerna/validation-error" "3.11.0" + "@lerna/package-graph" "3.13.0" + "@lerna/validation-error" "3.13.0" npmlog "^4.1.2" -"@lerna/bootstrap@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/bootstrap/-/bootstrap-3.11.0.tgz#01bfda72894b5ebf3b550b9849ee4b44c03e50be" - integrity sha512-MqwviGJTy86joqSX2A3fmu2wXLBXc23tHJp5Xu4bVhynPegDnRrA3d9UI80UM3JcuYIQsxT4t2q2LNsZ4VdZKQ== - dependencies: - "@lerna/batch-packages" "3.11.0" - "@lerna/command" "3.11.0" - "@lerna/filter-options" "3.11.0" - "@lerna/has-npm-version" "3.10.0" - "@lerna/npm-install" "3.11.0" - "@lerna/package-graph" "3.11.0" - "@lerna/pulse-till-done" "3.11.0" - "@lerna/rimraf-dir" "3.11.0" - "@lerna/run-lifecycle" "3.11.0" - "@lerna/run-parallel-batches" "3.0.0" - "@lerna/symlink-binary" "3.11.0" - "@lerna/symlink-dependencies" "3.11.0" - "@lerna/validation-error" "3.11.0" +"@lerna/bootstrap@3.13.1": + version "3.13.1" + resolved "https://registry.yarnpkg.com/@lerna/bootstrap/-/bootstrap-3.13.1.tgz#f2edd7c8093c8b139e78b0ca5f845f23efd01f08" + integrity sha512-mKdi5Ds5f82PZwEFyB9/W60I3iELobi1i87sTeVrbJh/um7GvqpSPy7kG/JPxyOdMpB2njX6LiJgw+7b6BEPWw== + dependencies: + "@lerna/batch-packages" "3.13.0" + "@lerna/command" "3.13.1" + "@lerna/filter-options" "3.13.0" + "@lerna/has-npm-version" "3.13.0" + "@lerna/npm-install" "3.13.0" + "@lerna/package-graph" "3.13.0" + "@lerna/pulse-till-done" "3.13.0" + "@lerna/rimraf-dir" "3.13.0" + "@lerna/run-lifecycle" "3.13.0" + "@lerna/run-parallel-batches" "3.13.0" + "@lerna/symlink-binary" "3.13.0" + "@lerna/symlink-dependencies" "3.13.0" + "@lerna/validation-error" "3.13.0" dedent "^0.7.0" get-port "^3.2.0" multimatch "^2.1.0" @@ -770,93 +885,93 @@ read-package-tree "^5.1.6" semver "^5.5.0" -"@lerna/changed@3.11.1": - version "3.11.1" - resolved "https://registry.yarnpkg.com/@lerna/changed/-/changed-3.11.1.tgz#d8a856f8237e37e7686d17a1e13bf4d082a3e48b" - integrity sha512-A21h3DvMjDwhksmCmTQ1+3KPHg7gHVHFs3zC5lR9W+whYlm0JI2Yp70vYnqMv2hPAcJx+2tlCrqJkzCFkNQdqg== +"@lerna/changed@3.13.1": + version "3.13.1" + resolved "https://registry.yarnpkg.com/@lerna/changed/-/changed-3.13.1.tgz#dc92476aad43c932fe741969bbd0bcf6146a4c52" + integrity sha512-BRXitEJGOkoudbxEewW7WhjkLxFD+tTk4PrYpHLyCBk63pNTWtQLRE6dc1hqwh4emwyGncoyW6RgXfLgMZgryw== dependencies: - "@lerna/collect-updates" "3.11.0" - "@lerna/command" "3.11.0" - "@lerna/listable" "3.11.0" - "@lerna/output" "3.11.0" - "@lerna/version" "3.11.1" + "@lerna/collect-updates" "3.13.0" + "@lerna/command" "3.13.1" + "@lerna/listable" "3.13.0" + "@lerna/output" "3.13.0" + "@lerna/version" "3.13.1" -"@lerna/check-working-tree@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/check-working-tree/-/check-working-tree-3.11.0.tgz#a513d3e28168826fa4916ef2d0ff656daa6e6de0" - integrity sha512-uWKKmX4BKdK57MyX3rGNHNz4JmFP3tHnaIDDVeuSlgK5KwncPFyRXi3E9H0eiq6DUvDDLtztNOfWeGP2IY656Q== +"@lerna/check-working-tree@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/check-working-tree/-/check-working-tree-3.13.0.tgz#1ddcd4d9b1aceb65efaaa4cd1333a66706d67c9c" + integrity sha512-dsdO15NXX5To+Q53SYeCrBEpiqv4m5VkaPZxbGQZNwoRen1MloXuqxSymJANQn+ZLEqarv5V56gydebeROPH5A== dependencies: - "@lerna/describe-ref" "3.11.0" - "@lerna/validation-error" "3.11.0" + "@lerna/describe-ref" "3.13.0" + "@lerna/validation-error" "3.13.0" -"@lerna/child-process@3.3.0": - version "3.3.0" - resolved "https://registry.yarnpkg.com/@lerna/child-process/-/child-process-3.3.0.tgz#71184a763105b6c8ece27f43f166498d90fe680f" - integrity sha512-q2d/OPlNX/cBXB6Iz1932RFzOmOHq6ZzPjqebkINNaTojHWuuRpvJJY4Uz3NGpJ3kEtPDvBemkZqUBTSO5wb1g== +"@lerna/child-process@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/child-process/-/child-process-3.13.0.tgz#84e35adf3217a6983edd28080657b9596a052674" + integrity sha512-0iDS8y2jiEucD4fJHEzKoc8aQJgm7s+hG+0RmDNtfT0MM3n17pZnf5JOMtS1FJp+SEXOjMKQndyyaDIPFsnp6A== dependencies: chalk "^2.3.1" execa "^1.0.0" strong-log-transformer "^2.0.0" -"@lerna/clean@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/clean/-/clean-3.11.0.tgz#21dc85d8280cd6956d3cb8998f3f5667382a8b8f" - integrity sha512-sHyMYv56MIVMH79+5vcxHVdgmd8BcsihI+RL2byW+PeoNlyDeGMjTRmnzLmbSD7dkinHGoa5cghlXy9GGIqpRw== +"@lerna/clean@3.13.1": + version "3.13.1" + resolved "https://registry.yarnpkg.com/@lerna/clean/-/clean-3.13.1.tgz#9a7432efceccd720a51da5c76f849fc59c5a14ce" + integrity sha512-myGIaXv7RUO2qCFZXvx8SJeI+eN6y9SUD5zZ4/LvNogbOiEIlujC5lUAqK65rAHayQ9ltSa/yK6Xv510xhZXZQ== dependencies: - "@lerna/command" "3.11.0" - "@lerna/filter-options" "3.11.0" - "@lerna/prompt" "3.11.0" - "@lerna/pulse-till-done" "3.11.0" - "@lerna/rimraf-dir" "3.11.0" + "@lerna/command" "3.13.1" + "@lerna/filter-options" "3.13.0" + "@lerna/prompt" "3.13.0" + "@lerna/pulse-till-done" "3.13.0" + "@lerna/rimraf-dir" "3.13.0" p-map "^1.2.0" p-map-series "^1.0.0" p-waterfall "^1.0.0" -"@lerna/cli@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/cli/-/cli-3.11.0.tgz#502f0409a794934b8dafb7be71dc3e91ca862907" - integrity sha512-dn2m2PgUxcb2NyTvwfYOFZf8yN5CMf1uKxht3ajQYdDjRgFi5pUQt/DmdguOZ3CMJkENa0i3yPOmrxGPXLD2aw== +"@lerna/cli@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/cli/-/cli-3.13.0.tgz#3d7b357fdd7818423e9681a7b7f2abd106c8a266" + integrity sha512-HgFGlyCZbYaYrjOr3w/EsY18PdvtsTmDfpUQe8HwDjXlPeCCUgliZjXLOVBxSjiOvPeOSwvopwIHKWQmYbwywg== dependencies: - "@lerna/global-options" "3.10.6" + "@lerna/global-options" "3.13.0" dedent "^0.7.0" npmlog "^4.1.2" yargs "^12.0.1" -"@lerna/collect-updates@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/collect-updates/-/collect-updates-3.11.0.tgz#2332cd8c2c2e091801c8e78fea3aea0e766f971e" - integrity sha512-O0Y18OC2P6j9/RFq+u5Kdq7YxsDd+up3ZRoW6+i0XHWktqxXA9P4JBQppkpYtJVK2yH8QyOzuVLQgtL0xtHdYA== +"@lerna/collect-updates@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/collect-updates/-/collect-updates-3.13.0.tgz#f0828d84ff959ff153d006765659ffc4d68cdefc" + integrity sha512-uR3u6uTzrS1p46tHQ/mlHog/nRJGBqskTHYYJbgirujxm6FqNh7Do+I1Q/7zSee407G4lzsNxZdm8IL927HemQ== dependencies: - "@lerna/child-process" "3.3.0" - "@lerna/describe-ref" "3.11.0" + "@lerna/child-process" "3.13.0" + "@lerna/describe-ref" "3.13.0" minimatch "^3.0.4" npmlog "^4.1.2" slash "^1.0.0" -"@lerna/command@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/command/-/command-3.11.0.tgz#a25199de8dfaf120ffa1492d5cb9185b17c45dea" - integrity sha512-N+Z5kauVHSb2VhSIfQexG2VlCAAQ9xYKwVTxYh0JFOFUnZ/QPcoqx4VjynDXASFXXDgcXs4FLaGsJxq83Mf5Zg== +"@lerna/command@3.13.1": + version "3.13.1" + resolved "https://registry.yarnpkg.com/@lerna/command/-/command-3.13.1.tgz#b60dda2c0d9ffbb6030d61ddf7cceedc1e8f7e6e" + integrity sha512-SYWezxX+iheWvzRoHCrbs8v5zHPaxAx3kWvZhqi70vuGsdOVAWmaG4IvHLn11ztS+Vpd5PM+ztBWSbnykpLFKQ== dependencies: - "@lerna/child-process" "3.3.0" - "@lerna/package-graph" "3.11.0" - "@lerna/project" "3.11.0" - "@lerna/validation-error" "3.11.0" - "@lerna/write-log-file" "3.11.0" + "@lerna/child-process" "3.13.0" + "@lerna/package-graph" "3.13.0" + "@lerna/project" "3.13.1" + "@lerna/validation-error" "3.13.0" + "@lerna/write-log-file" "3.13.0" dedent "^0.7.0" execa "^1.0.0" is-ci "^1.0.10" lodash "^4.17.5" npmlog "^4.1.2" -"@lerna/conventional-commits@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/conventional-commits/-/conventional-commits-3.11.0.tgz#6a56925a8ef3c0f66174bc74226bbdf1646800cf" - integrity sha512-ix1Ki5NiZdk2eMlCWNgLchWPKQTgkJdLeNjneep6OCF3ydSINizReGbFvCftRivun641cOHWswgWMsIxbqhMQw== +"@lerna/conventional-commits@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/conventional-commits/-/conventional-commits-3.13.0.tgz#877aa225ca34cca61c31ea02a5a6296af74e1144" + integrity sha512-BeAgcNXuocmLhPxnmKU2Vy8YkPd/Uo+vu2i/p3JGsUldzrPC8iF3IDxH7fuXpEFN2Nfogu7KHachd4tchtOppA== dependencies: - "@lerna/validation-error" "3.11.0" - conventional-changelog-angular "^5.0.2" - conventional-changelog-core "^3.1.5" + "@lerna/validation-error" "3.13.0" + conventional-changelog-angular "^5.0.3" + conventional-changelog-core "^3.1.6" conventional-recommended-bump "^4.0.4" fs-extra "^7.0.0" get-stream "^4.0.0" @@ -865,24 +980,24 @@ pify "^3.0.0" semver "^5.5.0" -"@lerna/create-symlink@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/create-symlink/-/create-symlink-3.11.0.tgz#2698b1f41aa81db820c20937701d7ceeb92cd421" - integrity sha512-UDR32uos8FIEc1keMKxXj5goZAHpCbpUd4u/btHXymUL9WqIym3cgz2iMr3ZNdZtjdMyUoHup5Dp0zjSgKCaEA== +"@lerna/create-symlink@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/create-symlink/-/create-symlink-3.13.0.tgz#e01133082fe040779712c960683cb3a272b67809" + integrity sha512-PTvg3jAAJSAtLFoZDsuTMv1wTOC3XYIdtg54k7uxIHsP8Ztpt+vlilY/Cni0THAqEMHvfiToel76Xdta4TU21Q== dependencies: cmd-shim "^2.0.2" fs-extra "^7.0.0" npmlog "^4.1.2" -"@lerna/create@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/create/-/create-3.11.0.tgz#06121b6370f650fc51e04afc2631c56de5a950e4" - integrity sha512-1izS82QML+H/itwEu1GPrcoXyugFaP9z9r6KuIQRQq8RtmNCGEmK85aiOw6mukyRcRziq2akALgFDyrundznPQ== +"@lerna/create@3.13.1": + version "3.13.1" + resolved "https://registry.yarnpkg.com/@lerna/create/-/create-3.13.1.tgz#2c1284cfdc59f0d2b88286d78bc797f4ab330f79" + integrity sha512-pLENMXgTkQuvKxAopjKeoLOv9fVUCnpTUD7aLrY5d95/1xqSZlnsOcQfUYcpMf3GpOvHc8ILmI5OXkPqjAf54g== dependencies: - "@lerna/child-process" "3.3.0" - "@lerna/command" "3.11.0" - "@lerna/npm-conf" "3.7.0" - "@lerna/validation-error" "3.11.0" + "@lerna/child-process" "3.13.0" + "@lerna/command" "3.13.1" + "@lerna/npm-conf" "3.13.0" + "@lerna/validation-error" "3.13.0" camelcase "^5.0.0" dedent "^0.7.0" fs-extra "^7.0.0" @@ -890,7 +1005,7 @@ init-package-json "^1.10.3" npm-package-arg "^6.1.0" p-reduce "^1.0.0" - pacote "^9.4.1" + pacote "^9.5.0" pify "^3.0.0" semver "^5.5.0" slash "^1.0.0" @@ -898,196 +1013,196 @@ validate-npm-package-name "^3.0.0" whatwg-url "^7.0.0" -"@lerna/describe-ref@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/describe-ref/-/describe-ref-3.11.0.tgz#935049a658f3f6e30b3da9132bdf121bc890addf" - integrity sha512-lX/NVMqeODg4q/igN06L/KjtVUpW1oawh6IgOINy2oqm4RUR+1yDpsdVu3JyZZ4nHB572mJfbW56dl8qoxEVvQ== +"@lerna/describe-ref@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/describe-ref/-/describe-ref-3.13.0.tgz#fb4c3863fd6bcccad67ce7b183887a5fc1942bb6" + integrity sha512-UJefF5mLxLae9I2Sbz5RLYGbqbikRuMqdgTam0MS5OhXnyuuKYBUpwBshCURNb1dPBXTQhSwc7+oUhORx8ojCg== dependencies: - "@lerna/child-process" "3.3.0" + "@lerna/child-process" "3.13.0" npmlog "^4.1.2" -"@lerna/diff@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/diff/-/diff-3.11.0.tgz#9c3417c1f1daabd55770c7a2631a1cc2125f1a4e" - integrity sha512-r3WASQix31ApA0tlkZejXhS8Z3SEg6Jw9YnKDt9V6wLjEUXGLauUDMrgx1YWu3cs9KB8/hqheRyRI7XAXGJS1w== +"@lerna/diff@3.13.1": + version "3.13.1" + resolved "https://registry.yarnpkg.com/@lerna/diff/-/diff-3.13.1.tgz#5c734321b0f6c46a3c87f55c99afef3b01d46520" + integrity sha512-cKqmpONO57mdvxtp8e+l5+tjtmF04+7E+O0QEcLcNUAjC6UR2OSM77nwRCXDukou/1h72JtWs0jjcdYLwAmApg== dependencies: - "@lerna/child-process" "3.3.0" - "@lerna/command" "3.11.0" - "@lerna/validation-error" "3.11.0" + "@lerna/child-process" "3.13.0" + "@lerna/command" "3.13.1" + "@lerna/validation-error" "3.13.0" npmlog "^4.1.2" -"@lerna/exec@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/exec/-/exec-3.11.0.tgz#391351b024ec243050f54ca92cef5d298dc821d4" - integrity sha512-oIkI+Hj74kpsnHhw0qJj12H4XMPSlDbBsshLWY+f3BiwKhn6wkXoQZ1FC8/OVNHM67GtSRv4bkcOaM4ucHm9Hw== - dependencies: - "@lerna/batch-packages" "3.11.0" - "@lerna/child-process" "3.3.0" - "@lerna/command" "3.11.0" - "@lerna/filter-options" "3.11.0" - "@lerna/run-parallel-batches" "3.0.0" - "@lerna/validation-error" "3.11.0" - -"@lerna/filter-options@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/filter-options/-/filter-options-3.11.0.tgz#2c9b47abd5bb860652b7f40bc466539f56e6014b" - integrity sha512-z0krgC/YBqz7i6MGHBsPLvsQ++XEpPdGnIkSpcN0Cjp5J67K9vb5gJ2hWp1c1bitNh3xiwZ69voGqN+DYk1mUg== - dependencies: - "@lerna/collect-updates" "3.11.0" - "@lerna/filter-packages" "3.11.0" +"@lerna/exec@3.13.1": + version "3.13.1" + resolved "https://registry.yarnpkg.com/@lerna/exec/-/exec-3.13.1.tgz#4439e90fb0877ec38a6ef933c86580d43eeaf81b" + integrity sha512-I34wEP9lrAqqM7tTXLDxv/6454WFzrnXDWpNDbiKQiZs6SIrOOjmm6I4FiQsx+rU3o9d+HkC6tcUJRN5mlJUgA== + dependencies: + "@lerna/batch-packages" "3.13.0" + "@lerna/child-process" "3.13.0" + "@lerna/command" "3.13.1" + "@lerna/filter-options" "3.13.0" + "@lerna/run-parallel-batches" "3.13.0" + "@lerna/validation-error" "3.13.0" + +"@lerna/filter-options@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/filter-options/-/filter-options-3.13.0.tgz#976e3d8b9fcd47001ab981d276565c1e9f767868" + integrity sha512-SRp7DCo9zrf+7NkQxZMkeyO1GRN6GICoB9UcBAbXhLbWisT37Cx5/6+jh49gYB63d/0/WYHSEPMlheUrpv1Srw== + dependencies: + "@lerna/collect-updates" "3.13.0" + "@lerna/filter-packages" "3.13.0" dedent "^0.7.0" -"@lerna/filter-packages@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/filter-packages/-/filter-packages-3.11.0.tgz#b9087495df4fd035f47d193e3538a56e79be3702" - integrity sha512-bnukkW1M0uMKWqM/m/IHou2PKRyk4fDAksAj3diHc1UVQkH2j8hXOfLl9+CgHA/cnTrf6/LARg8hKujqduqHyA== +"@lerna/filter-packages@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/filter-packages/-/filter-packages-3.13.0.tgz#f5371249e7e1a15928e5e88c544a242e0162c21c" + integrity sha512-RWiZWyGy3Mp7GRVBn//CacSnE3Kw82PxE4+H6bQ3pDUw/9atXn7NRX+gkBVQIYeKamh7HyumJtyOKq3Pp9BADQ== dependencies: - "@lerna/validation-error" "3.11.0" + "@lerna/validation-error" "3.13.0" multimatch "^2.1.0" npmlog "^4.1.2" -"@lerna/get-npm-exec-opts@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/get-npm-exec-opts/-/get-npm-exec-opts-3.11.0.tgz#6e151d52265921205ea3e49b08bd7ee99051741a" - integrity sha512-EDxsbuq2AbB3LWwH/4SOcn4gWOnoIYrSHfITWo7xz/SbEKeHtiva99l424ZRWUJqLPGIpQiMTlmOET2ZEI8WZg== +"@lerna/get-npm-exec-opts@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/get-npm-exec-opts/-/get-npm-exec-opts-3.13.0.tgz#d1b552cb0088199fc3e7e126f914e39a08df9ea5" + integrity sha512-Y0xWL0rg3boVyJk6An/vurKzubyJKtrxYv2sj4bB8Mc5zZ3tqtv0ccbOkmkXKqbzvNNF7VeUt1OJ3DRgtC/QZw== dependencies: npmlog "^4.1.2" -"@lerna/get-packed@3.7.0": - version "3.7.0" - resolved "https://registry.yarnpkg.com/@lerna/get-packed/-/get-packed-3.7.0.tgz#549c7738f7be5e3b1433e82ed9cda9123bcd1ed5" - integrity sha512-yuFtjsUZIHjeIvIYQ/QuytC+FQcHwo3peB+yGBST2uWCLUCR5rx6knoQcPzbxdFDCuUb5IFccFGd3B1fHFg3RQ== +"@lerna/get-packed@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/get-packed/-/get-packed-3.13.0.tgz#335e40d77f3c1855aa248587d3e0b2d8f4b06e16" + integrity sha512-EgSim24sjIjqQDC57bgXD9l22/HCS93uQBbGpkzEOzxAVzEgpZVm7Fm1t8BVlRcT2P2zwGnRadIvxTbpQuDPTg== dependencies: fs-extra "^7.0.0" ssri "^6.0.1" tar "^4.4.8" -"@lerna/github-client@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/github-client/-/github-client-3.11.0.tgz#54e87160a56567f4cd1d48f20d1c6b9d88fe032b" - integrity sha512-yPMBhzShuth3uJo0kKu84RvgjSZgOYNT8fKfhZmzTeVGuPbYBKlK+UQ6jjpb6E9WW2BVdiUCrFhqIsbK5Lqe7A== +"@lerna/github-client@3.13.1": + version "3.13.1" + resolved "https://registry.yarnpkg.com/@lerna/github-client/-/github-client-3.13.1.tgz#cb9bf9f01685a0cee0fac63f287f6c3673e45aa3" + integrity sha512-iPLUp8FFoAKGURksYEYZzfuo9TRA+NepVlseRXFaWlmy36dCQN20AciINpoXiXGoHcEUHXUKHQvY3ARFdMlf3w== dependencies: - "@lerna/child-process" "3.3.0" - "@octokit/plugin-enterprise-rest" "^2.1.0" - "@octokit/rest" "^16.15.0" + "@lerna/child-process" "3.13.0" + "@octokit/plugin-enterprise-rest" "^2.1.1" + "@octokit/rest" "^16.16.0" git-url-parse "^11.1.2" npmlog "^4.1.2" -"@lerna/global-options@3.10.6": - version "3.10.6" - resolved "https://registry.yarnpkg.com/@lerna/global-options/-/global-options-3.10.6.tgz#c491a64b0be47eca4ffc875011958a5ee70a9a3e" - integrity sha512-k5Xkq1M/uREFC2R9uwN5gcvIgjj4iOXo0YyeEXCMWBiW3j2GL9xN4d1MmAIcrYlAzVYh6kLlWaFWl/rNIneHIw== +"@lerna/global-options@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/global-options/-/global-options-3.13.0.tgz#217662290db06ad9cf2c49d8e3100ee28eaebae1" + integrity sha512-SlZvh1gVRRzYLVluz9fryY1nJpZ0FHDGB66U9tFfvnnxmueckRQxLopn3tXj3NU1kc3QANT2I5BsQkOqZ4TEFQ== -"@lerna/has-npm-version@3.10.0": - version "3.10.0" - resolved "https://registry.yarnpkg.com/@lerna/has-npm-version/-/has-npm-version-3.10.0.tgz#d3a73c0fedd2f2e9c6fbe166c41809131dc939d2" - integrity sha512-N4RRYxGeivuaKgPDzrhkQOQs1Sg4tOnxnEe3akfqu1wDA4Ng5V6Y2uW3DbkAjFL3aNJhWF5Vbf7sBsGtfgDQ8w== +"@lerna/has-npm-version@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/has-npm-version/-/has-npm-version-3.13.0.tgz#6e1f7e9336cce3e029066f0175f06dd9d51ad09f" + integrity sha512-Oqu7DGLnrMENPm+bPFGOHnqxK8lCnuYr6bk3g/CoNn8/U0qgFvHcq6Iv8/Z04TsvleX+3/RgauSD2kMfRmbypg== dependencies: - "@lerna/child-process" "3.3.0" + "@lerna/child-process" "3.13.0" semver "^5.5.0" -"@lerna/import@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/import/-/import-3.11.0.tgz#e417231754bd660763d3b483901ff786d949a48e" - integrity sha512-WgF0We+4k/MrC1vetT8pt3/SSJPMvXhyPYmL2W9rcvch3zV0IgLyso4tEs8gNbwZorDVEG1KcM+x8TG4v1nV5Q== +"@lerna/import@3.13.1": + version "3.13.1" + resolved "https://registry.yarnpkg.com/@lerna/import/-/import-3.13.1.tgz#69d641341a38b79bd379129da1c717d51dd728c7" + integrity sha512-A1Vk1siYx1XkRl6w+zkaA0iptV5TIynVlHPR9S7NY0XAfhykjztYVvwtxarlh6+VcNrO9We6if0+FXCrfDEoIg== dependencies: - "@lerna/child-process" "3.3.0" - "@lerna/command" "3.11.0" - "@lerna/prompt" "3.11.0" - "@lerna/pulse-till-done" "3.11.0" - "@lerna/validation-error" "3.11.0" + "@lerna/child-process" "3.13.0" + "@lerna/command" "3.13.1" + "@lerna/prompt" "3.13.0" + "@lerna/pulse-till-done" "3.13.0" + "@lerna/validation-error" "3.13.0" dedent "^0.7.0" fs-extra "^7.0.0" p-map-series "^1.0.0" -"@lerna/init@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/init/-/init-3.11.0.tgz#c56d9324984d926e98723c78c64453f46426f608" - integrity sha512-JZC5jpCVJgK34grye52kGWjrYCyh4LB8c0WBLaS8MOUt6rxTtPqubwvCDKPOF2H0Se6awsgEfX4wWNuqiQVpRQ== +"@lerna/init@3.13.1": + version "3.13.1" + resolved "https://registry.yarnpkg.com/@lerna/init/-/init-3.13.1.tgz#0392c822abb3d63a75be4916c5e761cfa7b34dda" + integrity sha512-M59WACqim8WkH5FQEGOCEZ89NDxCKBfFTx4ZD5ig3LkGyJ8RdcJq5KEfpW/aESuRE9JrZLzVr0IjKbZSxzwEMA== dependencies: - "@lerna/child-process" "3.3.0" - "@lerna/command" "3.11.0" + "@lerna/child-process" "3.13.0" + "@lerna/command" "3.13.1" fs-extra "^7.0.0" p-map "^1.2.0" write-json-file "^2.3.0" -"@lerna/link@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/link/-/link-3.11.0.tgz#97253ffeb8a8956c3589ff4c1acf6fda322d76a2" - integrity sha512-QN+kxRWb6P9jrKpE2t6K9sGnFpqy1KOEjf68NpGhmp+J9Yt6Kvz9kG43CWoqg4Zyqqgqgn3NVV2Z7zSDNhdH0g== +"@lerna/link@3.13.1": + version "3.13.1" + resolved "https://registry.yarnpkg.com/@lerna/link/-/link-3.13.1.tgz#7d8ed4774bfa198d1780f790a14abb8722a3aad1" + integrity sha512-N3h3Fj1dcea+1RaAoAdy4g2m3fvU7m89HoUn5X/Zcw5n2kPoK8kTO+NfhNAatfRV8VtMXst8vbNrWQQtfm0FFw== dependencies: - "@lerna/command" "3.11.0" - "@lerna/package-graph" "3.11.0" - "@lerna/symlink-dependencies" "3.11.0" + "@lerna/command" "3.13.1" + "@lerna/package-graph" "3.13.0" + "@lerna/symlink-dependencies" "3.13.0" p-map "^1.2.0" slash "^1.0.0" -"@lerna/list@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/list/-/list-3.11.0.tgz#0796d6076aa242d930ca5e470c49fc91066a1063" - integrity sha512-hBAwZzEzF1LQOOB2/5vQkal/nSriuJbLY39BitIGkUxifsmu7JK0k3LYrwe1sxXv5SMf2HDaTLr+Z23mUslhaQ== +"@lerna/list@3.13.1": + version "3.13.1" + resolved "https://registry.yarnpkg.com/@lerna/list/-/list-3.13.1.tgz#f9513ed143e52156c10ada4070f903c5847dcd10" + integrity sha512-635iRbdgd9gNvYLLIbYdQCQLr+HioM5FGJLFS0g3DPGygr6iDR8KS47hzCRGH91LU9NcM1mD1RoT/AChF+QbiA== dependencies: - "@lerna/command" "3.11.0" - "@lerna/filter-options" "3.11.0" - "@lerna/listable" "3.11.0" - "@lerna/output" "3.11.0" + "@lerna/command" "3.13.1" + "@lerna/filter-options" "3.13.0" + "@lerna/listable" "3.13.0" + "@lerna/output" "3.13.0" -"@lerna/listable@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/listable/-/listable-3.11.0.tgz#5a687c4547f0fb2211c9ab59629f689e170335f3" - integrity sha512-nCrtGSS3YiAlh5dU5mmTAU9aLRlmIUn2FnahqsksN2uQ5O4o+614tneDuO298/eWLZo00eGw69EFngaQEl8quw== +"@lerna/listable@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/listable/-/listable-3.13.0.tgz#babc18442c590b549cf0966d20d75fea066598d4" + integrity sha512-liYJ/WBUYP4N4MnSVZuLUgfa/jy3BZ02/1Om7xUY09xGVSuNVNEeB8uZUMSC+nHqFHIsMPZ8QK9HnmZb1E/eTA== dependencies: - "@lerna/batch-packages" "3.11.0" + "@lerna/batch-packages" "3.13.0" chalk "^2.3.1" columnify "^1.5.4" -"@lerna/log-packed@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/log-packed/-/log-packed-3.11.0.tgz#4b348d8b3b4faa00ae5a03a7cec389dce91f8393" - integrity sha512-TH//81TzSTMuNzJIQE7zqu+ymI5rH25jdEdmbYEWmaJ+T42GMQXKxP8cj2m+fWRaDML8ta0uzBOm5PKHdgoFYQ== +"@lerna/log-packed@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/log-packed/-/log-packed-3.13.0.tgz#497b5f692a8d0e3f669125da97b0dadfd9e480f3" + integrity sha512-Rmjrcz+6aM6AEcEVWmurbo8+AnHOvYtDpoeMMJh9IZ9SmZr2ClXzmD7wSvjTQc8BwOaiWjjC/ukcT0UYA2m7wg== dependencies: byte-size "^4.0.3" columnify "^1.5.4" has-unicode "^2.0.1" npmlog "^4.1.2" -"@lerna/npm-conf@3.7.0": - version "3.7.0" - resolved "https://registry.yarnpkg.com/@lerna/npm-conf/-/npm-conf-3.7.0.tgz#f101d4fdf07cefcf1161bcfaf3c0f105b420a450" - integrity sha512-+WSMDfPKcKzMfqq283ydz9RRpOU6p9wfx0wy4hVSUY/6YUpsyuk8SShjcRtY8zTM5AOrxvFBuuV90H4YpZ5+Ng== +"@lerna/npm-conf@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/npm-conf/-/npm-conf-3.13.0.tgz#6b434ed75ff757e8c14381b9bbfe5d5ddec134a7" + integrity sha512-Jg2kANsGnhg+fbPEzE0X9nX5oviEAvWj0nYyOkcE+cgWuT7W0zpnPXC4hA4C5IPQGhwhhh0IxhWNNHtjTuw53g== dependencies: config-chain "^1.1.11" pify "^3.0.0" -"@lerna/npm-dist-tag@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/npm-dist-tag/-/npm-dist-tag-3.11.0.tgz#679fea8b6534d6a877d7efa658ba9eea5b3936ed" - integrity sha512-WqZcyDb+wiqAKRFcYEK6R8AQfspyro85zGGHyjYw6ZPNgJX3qhwtQ+MidDmOesi2p5/0GfeVSWega+W7fPzVpg== +"@lerna/npm-dist-tag@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/npm-dist-tag/-/npm-dist-tag-3.13.0.tgz#49ecbe0e82cbe4ad4a8ea6de112982bf6c4e6cd4" + integrity sha512-mcuhw34JhSRFrbPn0vedbvgBTvveG52bR2lVE3M3tfE8gmR/cKS/EJFO4AUhfRKGCTFn9rjaSEzlFGYV87pemQ== dependencies: figgy-pudding "^3.5.1" npm-package-arg "^6.1.0" npm-registry-fetch "^3.9.0" npmlog "^4.1.2" -"@lerna/npm-install@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/npm-install/-/npm-install-3.11.0.tgz#40533527186d774ac27906d94a8073373d4641e4" - integrity sha512-iNKEgFvFHMmBqn9AnFye2rv7CdUBlYciwWSTNtpfVqtOnoL/lg+4A774oL4PDoxTCGmougztyxMkqLVSBYXTpw== +"@lerna/npm-install@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/npm-install/-/npm-install-3.13.0.tgz#88f4cc39f4f737c8a8721256b915ea1bcc6a7227" + integrity sha512-qNyfts//isYQxore6fsPorNYJmPVKZ6tOThSH97tP0aV91zGMtrYRqlAoUnDwDdAjHPYEM16hNujg2wRmsqqIw== dependencies: - "@lerna/child-process" "3.3.0" - "@lerna/get-npm-exec-opts" "3.11.0" + "@lerna/child-process" "3.13.0" + "@lerna/get-npm-exec-opts" "3.13.0" fs-extra "^7.0.0" npm-package-arg "^6.1.0" npmlog "^4.1.2" signal-exit "^3.0.2" write-pkg "^3.1.0" -"@lerna/npm-publish@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/npm-publish/-/npm-publish-3.11.0.tgz#886a408c86c30c4f18df20f338d576a53902b6ba" - integrity sha512-wgbb55gUXRlP8uTe60oW6c06ZhquaJu9xbi2vWNpb5Fmjh/KbZ2iNm9Kj2ciZlvb8D+k4Oc3qV7slBGxyMm8wg== +"@lerna/npm-publish@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/npm-publish/-/npm-publish-3.13.0.tgz#5c74808376e778865ffdc5885fe83935e15e60c3" + integrity sha512-y4WO0XTaf9gNRkI7as6P2ItVDOxmYHwYto357fjybcnfXgMqEA94c3GJ++jU41j0A9vnmYC6/XxpTd9sVmH9tA== dependencies: - "@lerna/run-lifecycle" "3.11.0" + "@lerna/run-lifecycle" "3.13.0" figgy-pudding "^3.5.1" fs-extra "^7.0.0" libnpmpublish "^1.1.1" @@ -1095,62 +1210,62 @@ pify "^3.0.0" read-package-json "^2.0.13" -"@lerna/npm-run-script@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/npm-run-script/-/npm-run-script-3.11.0.tgz#ef5880735aa471d9ce1109e9213a45cbdbe8146b" - integrity sha512-cLnTMrRQlK/N5bCr6joOFMBfRyW2EbMdk3imtjHk0LwZxsvQx3naAPUB/2RgNfC8fGf/yHF/0bmBrpb5sa2IlA== +"@lerna/npm-run-script@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/npm-run-script/-/npm-run-script-3.13.0.tgz#e5997f045402b9948bdc066033ebb36bf94fc9e4" + integrity sha512-hiL3/VeVp+NFatBjkGN8mUdX24EfZx9rQlSie0CMgtjc7iZrtd0jCguLomSCRHYjJuvqgbp+LLYo7nHVykfkaQ== dependencies: - "@lerna/child-process" "3.3.0" - "@lerna/get-npm-exec-opts" "3.11.0" + "@lerna/child-process" "3.13.0" + "@lerna/get-npm-exec-opts" "3.13.0" npmlog "^4.1.2" -"@lerna/output@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/output/-/output-3.11.0.tgz#cc2c1e8573d9523f3159524e44a7cf788db6102e" - integrity sha512-xHYGcEaZZ4cR0Jw368QgUgFvV27a6ZO5360BMNGNsjCjuY0aOPQC5+lBhgfydJtJteKjDna853PSjBK3uMhEjw== +"@lerna/output@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/output/-/output-3.13.0.tgz#3ded7cc908b27a9872228a630d950aedae7a4989" + integrity sha512-7ZnQ9nvUDu/WD+bNsypmPG5MwZBwu86iRoiW6C1WBuXXDxM5cnIAC1m2WxHeFnjyMrYlRXM9PzOQ9VDD+C15Rg== dependencies: npmlog "^4.1.2" -"@lerna/pack-directory@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/pack-directory/-/pack-directory-3.11.0.tgz#5fa5d818eba97ad2c4d1f688e0754f3a4c34cc81" - integrity sha512-bgA3TxZx5AyZeqUadSPspktdecW7nIpg/ODq0o0gKFr7j+DC9Fqu8vQa2xmFSKsXDtOYkCV0jox6Ox9XSFSM3A== +"@lerna/pack-directory@3.13.1": + version "3.13.1" + resolved "https://registry.yarnpkg.com/@lerna/pack-directory/-/pack-directory-3.13.1.tgz#5ad4d0945f86a648f565e24d53c1e01bb3a912d1" + integrity sha512-kXnyqrkQbCIZOf1054N88+8h0ItC7tUN5v9ca/aWpx298gsURpxUx/1TIKqijL5TOnHMyIkj0YJmnH/PyBVLKA== dependencies: - "@lerna/get-packed" "3.7.0" - "@lerna/package" "3.11.0" - "@lerna/run-lifecycle" "3.11.0" + "@lerna/get-packed" "3.13.0" + "@lerna/package" "3.13.0" + "@lerna/run-lifecycle" "3.13.0" figgy-pudding "^3.5.1" - npm-packlist "^1.1.12" + npm-packlist "^1.4.1" npmlog "^4.1.2" tar "^4.4.8" temp-write "^3.4.0" -"@lerna/package-graph@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/package-graph/-/package-graph-3.11.0.tgz#d43472eb9aa2e6ca2c18984b9f86bb5924790d7a" - integrity sha512-ICYiOZvCfcmeH1qfzOkFYh0t0QA56OddQfI3ydxCiWi5G+UupJXnCIWSTh3edTAtw/kyxhCOWny/PJsG4CQfjA== +"@lerna/package-graph@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/package-graph/-/package-graph-3.13.0.tgz#607062f8d2ce22b15f8d4a0623f384736e67f760" + integrity sha512-3mRF1zuqFE1HEFmMMAIggXy+f+9cvHhW/jzaPEVyrPNLKsyfJQtpTNzeI04nfRvbAh+Gd2aNksvaW/w3xGJnnw== dependencies: - "@lerna/validation-error" "3.11.0" + "@lerna/validation-error" "3.13.0" npm-package-arg "^6.1.0" semver "^5.5.0" -"@lerna/package@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/package/-/package-3.11.0.tgz#b783f8c93f398e4c41cfd3fc8f2bb38ad1e07b76" - integrity sha512-hMzBhFEubhg+Tis5C8skwIfgOk+GTl0qudvzfPU9gQqLV8u4/Hs6mka6N0rKgbUb4VFVc5MJVe1eZ6Rv+kJAWw== +"@lerna/package@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/package/-/package-3.13.0.tgz#4baeebc49a57fc9b31062cc59f5ee38384429fc8" + integrity sha512-kSKO0RJQy093BufCQnkhf1jB4kZnBvL7kK5Ewolhk5gwejN+Jofjd8DGRVUDUJfQ0CkW1o6GbUeZvs8w8VIZDg== dependencies: load-json-file "^4.0.0" npm-package-arg "^6.1.0" write-pkg "^3.1.0" -"@lerna/project@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/project/-/project-3.11.0.tgz#3f403e277b724a39e5fd9124b6978c426815c588" - integrity sha512-j3DGds+q/q2YNpoBImaEsMpkWgu5gP0IGKz1o1Ju39NZKrTPza+ARIzEByL4Jqu87tcoOj7RbZzhhrBP8JBbTg== +"@lerna/project@3.13.1": + version "3.13.1" + resolved "https://registry.yarnpkg.com/@lerna/project/-/project-3.13.1.tgz#bce890f60187bd950bcf36c04b5260642e295e79" + integrity sha512-/GoCrpsCCTyb9sizk1+pMBrIYchtb+F1uCOn3cjn9yenyG/MfYEnlfrbV5k/UDud0Ei75YBLbmwCbigHkAKazQ== dependencies: - "@lerna/package" "3.11.0" - "@lerna/validation-error" "3.11.0" - cosmiconfig "^5.0.2" + "@lerna/package" "3.13.0" + "@lerna/validation-error" "3.13.0" + cosmiconfig "^5.1.0" dedent "^0.7.0" dot-prop "^4.2.0" glob-parent "^3.1.0" @@ -1161,37 +1276,37 @@ resolve-from "^4.0.0" write-json-file "^2.3.0" -"@lerna/prompt@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/prompt/-/prompt-3.11.0.tgz#35c6bf18e5218ccf4bf2cde678667fd967ea1564" - integrity sha512-SB/wvyDPQASze9txd+8/t24p6GiJuhhL30zxuRwvVwER5lIJR7kaXy1KhQ7kUAKPlNTVfCBm3GXReIMl4jhGhw== +"@lerna/prompt@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/prompt/-/prompt-3.13.0.tgz#53571462bb3f5399cc1ca6d335a411fe093426a5" + integrity sha512-P+lWSFokdyvYpkwC3it9cE0IF2U5yy2mOUbGvvE4iDb9K7TyXGE+7lwtx2thtPvBAfIb7O13POMkv7df03HJeA== dependencies: inquirer "^6.2.0" npmlog "^4.1.2" -"@lerna/publish@3.11.1": - version "3.11.1" - resolved "https://registry.yarnpkg.com/@lerna/publish/-/publish-3.11.1.tgz#06b0f646afea7f29cd820a63086692a4ac4d080e" - integrity sha512-UOvmSivuqzWoiTqoYWk+liPDZvC6O7NrT8DwoG2peRvjIPs5RKYMubwXPOrBBVVE+yX/vR6V1Y3o6vf3av52dg== - dependencies: - "@lerna/batch-packages" "3.11.0" - "@lerna/check-working-tree" "3.11.0" - "@lerna/child-process" "3.3.0" - "@lerna/collect-updates" "3.11.0" - "@lerna/command" "3.11.0" - "@lerna/describe-ref" "3.11.0" - "@lerna/log-packed" "3.11.0" - "@lerna/npm-conf" "3.7.0" - "@lerna/npm-dist-tag" "3.11.0" - "@lerna/npm-publish" "3.11.0" - "@lerna/output" "3.11.0" - "@lerna/pack-directory" "3.11.0" - "@lerna/prompt" "3.11.0" - "@lerna/pulse-till-done" "3.11.0" - "@lerna/run-lifecycle" "3.11.0" - "@lerna/run-parallel-batches" "3.0.0" - "@lerna/validation-error" "3.11.0" - "@lerna/version" "3.11.1" +"@lerna/publish@3.13.1": + version "3.13.1" + resolved "https://registry.yarnpkg.com/@lerna/publish/-/publish-3.13.1.tgz#217e401dcb5824cdd6d36555a36303fb7520c514" + integrity sha512-KhCJ9UDx76HWCF03i5TD7z5lX+2yklHh5SyO8eDaLptgdLDQ0Z78lfGj3JhewHU2l46FztmqxL/ss0IkWHDL+g== + dependencies: + "@lerna/batch-packages" "3.13.0" + "@lerna/check-working-tree" "3.13.0" + "@lerna/child-process" "3.13.0" + "@lerna/collect-updates" "3.13.0" + "@lerna/command" "3.13.1" + "@lerna/describe-ref" "3.13.0" + "@lerna/log-packed" "3.13.0" + "@lerna/npm-conf" "3.13.0" + "@lerna/npm-dist-tag" "3.13.0" + "@lerna/npm-publish" "3.13.0" + "@lerna/output" "3.13.0" + "@lerna/pack-directory" "3.13.1" + "@lerna/prompt" "3.13.0" + "@lerna/pulse-till-done" "3.13.0" + "@lerna/run-lifecycle" "3.13.0" + "@lerna/run-parallel-batches" "3.13.0" + "@lerna/validation-error" "3.13.0" + "@lerna/version" "3.13.1" figgy-pudding "^3.5.1" fs-extra "^7.0.0" libnpmaccess "^3.0.1" @@ -1202,119 +1317,119 @@ p-map "^1.2.0" p-pipe "^1.2.0" p-reduce "^1.0.0" - pacote "^9.4.1" + pacote "^9.5.0" semver "^5.5.0" -"@lerna/pulse-till-done@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/pulse-till-done/-/pulse-till-done-3.11.0.tgz#44221de131606104b705dc861440887d543d28ed" - integrity sha512-nMwBa6S4+VI/ketN92oj1xr8y74Fz4ul2R5jdbrRqLLEU/IMBWIqn6NRM2P+OQBoLpPZ2MdWENLJVFNN8X1Q+A== +"@lerna/pulse-till-done@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/pulse-till-done/-/pulse-till-done-3.13.0.tgz#c8e9ce5bafaf10d930a67d7ed0ccb5d958fe0110" + integrity sha512-1SOHpy7ZNTPulzIbargrgaJX387csN7cF1cLOGZiJQA6VqnS5eWs2CIrG8i8wmaUavj2QlQ5oEbRMVVXSsGrzA== dependencies: npmlog "^4.1.2" -"@lerna/resolve-symlink@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/resolve-symlink/-/resolve-symlink-3.11.0.tgz#0df9834cbacc5a39774899a83b119a7187dfb277" - integrity sha512-lDer8zPXS36iL4vJdZwOk6AnuUjDXswoTWdYkl+HdAKXp7cBlS+VeGmcFIJS4R3mSSZE20h1oEDuH8h8GGORIQ== +"@lerna/resolve-symlink@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/resolve-symlink/-/resolve-symlink-3.13.0.tgz#3e6809ef53b63fe914814bfa071cd68012e22fbb" + integrity sha512-Lc0USSFxwDxUs5JvIisS8JegjA6SHSAWJCMvi2osZx6wVRkEDlWG2B1JAfXUzCMNfHoZX0/XX9iYZ+4JIpjAtg== dependencies: fs-extra "^7.0.0" npmlog "^4.1.2" read-cmd-shim "^1.0.1" -"@lerna/rimraf-dir@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/rimraf-dir/-/rimraf-dir-3.11.0.tgz#98e6a41b2a7bfe83693d9594347cb3dbed2aebdc" - integrity sha512-roy4lKel7BMNLfFvyzK0HI251mgI9EwbpOccR2Waz0V22d0gaqLKzfVrzovat9dVHXrKNxAhJ5iKkKeT93IunQ== +"@lerna/rimraf-dir@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/rimraf-dir/-/rimraf-dir-3.13.0.tgz#bb1006104b4aabcb6985624273254648f872b278" + integrity sha512-kte+pMemulre8cmPqljxIYjCmdLByz8DgHBHXB49kz2EiPf8JJ+hJFt0PzEubEyJZ2YE2EVAx5Tv5+NfGNUQyQ== dependencies: - "@lerna/child-process" "3.3.0" + "@lerna/child-process" "3.13.0" npmlog "^4.1.2" path-exists "^3.0.0" rimraf "^2.6.2" -"@lerna/run-lifecycle@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/run-lifecycle/-/run-lifecycle-3.11.0.tgz#2839d18d7603318dbdd545cbfa1321bc41cbc474" - integrity sha512-3xeeVz9s3Dh2ljKqJI/Fl+gkZD9Y8JblAN62f4WNM76d/zFlgpCXDs62OpxNjEuXujA7YFix0sJ+oPKMm8mDrw== +"@lerna/run-lifecycle@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/run-lifecycle/-/run-lifecycle-3.13.0.tgz#d8835ee83425edee40f687a55f81b502354d3261" + integrity sha512-oyiaL1biZdjpmjh6X/5C4w07wNFyiwXSSHH5GQB4Ay4BPwgq9oNhCcxRoi0UVZlZ1YwzSW8sTwLgj8emkIo3Yg== dependencies: - "@lerna/npm-conf" "3.7.0" + "@lerna/npm-conf" "3.13.0" figgy-pudding "^3.5.1" npm-lifecycle "^2.1.0" npmlog "^4.1.2" -"@lerna/run-parallel-batches@3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@lerna/run-parallel-batches/-/run-parallel-batches-3.0.0.tgz#468704934084c74991d3124d80607857d4dfa840" - integrity sha512-Mj1ravlXF7AkkewKd9YFq9BtVrsStNrvVLedD/b2wIVbNqcxp8lS68vehXVOzoL/VWNEDotvqCQtyDBilCodGw== +"@lerna/run-parallel-batches@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/run-parallel-batches/-/run-parallel-batches-3.13.0.tgz#0276bb4e7cd0995297db82d134ca2bd08d63e311" + integrity sha512-bICFBR+cYVF1FFW+Tlm0EhWDioTUTM6dOiVziDEGE1UZha1dFkMYqzqdSf4bQzfLS31UW/KBd/2z8jy2OIjEjg== dependencies: p-map "^1.2.0" p-map-series "^1.0.0" -"@lerna/run@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/run/-/run-3.11.0.tgz#2a07995ccd570230d01ee8fe2e8c6b742ed58c37" - integrity sha512-8c2yzbKJFzgO6VTOftWmB0fOLTL7G1GFAG5UTVDSk95Z2Gnjof3I/Xkvtbzq8L+DIOLpr+Tpj3fRBjZd8rONlA== - dependencies: - "@lerna/batch-packages" "3.11.0" - "@lerna/command" "3.11.0" - "@lerna/filter-options" "3.11.0" - "@lerna/npm-run-script" "3.11.0" - "@lerna/output" "3.11.0" - "@lerna/run-parallel-batches" "3.0.0" - "@lerna/timer" "3.5.0" - "@lerna/validation-error" "3.11.0" +"@lerna/run@3.13.1": + version "3.13.1" + resolved "https://registry.yarnpkg.com/@lerna/run/-/run-3.13.1.tgz#87e174c1d271894ddd29adc315c068fb7b1b0117" + integrity sha512-nv1oj7bsqppWm1M4ifN+/IIbVu9F4RixrbQD2okqDGYne4RQPAXyb5cEZuAzY/wyGTWWiVaZ1zpj5ogPWvH0bw== + dependencies: + "@lerna/batch-packages" "3.13.0" + "@lerna/command" "3.13.1" + "@lerna/filter-options" "3.13.0" + "@lerna/npm-run-script" "3.13.0" + "@lerna/output" "3.13.0" + "@lerna/run-parallel-batches" "3.13.0" + "@lerna/timer" "3.13.0" + "@lerna/validation-error" "3.13.0" p-map "^1.2.0" -"@lerna/symlink-binary@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/symlink-binary/-/symlink-binary-3.11.0.tgz#927e1e0d561e52949feb7e3b2a83b26a00cbde49" - integrity sha512-5sOED+1O8jI+ckDS6DRUKtAtbKo7lbxFIJs6sWWEu5qKzM5e21O6E2wTWimJkad8nJ1SJAuyc8DC8M8ki4kT4w== +"@lerna/symlink-binary@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/symlink-binary/-/symlink-binary-3.13.0.tgz#36a9415d468afcb8105750296902f6f000a9680d" + integrity sha512-obc4Y6jxywkdaCe+DB0uTxYqP0IQ8mFWvN+k/YMbwH4G2h7M7lCBWgPy8e7xw/50+1II9tT2sxgx+jMus1sTJg== dependencies: - "@lerna/create-symlink" "3.11.0" - "@lerna/package" "3.11.0" + "@lerna/create-symlink" "3.13.0" + "@lerna/package" "3.13.0" fs-extra "^7.0.0" p-map "^1.2.0" -"@lerna/symlink-dependencies@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/symlink-dependencies/-/symlink-dependencies-3.11.0.tgz#f1e9488c5d7e87aa945b34f4f4ce53e655178698" - integrity sha512-XKNX8oOgcOmiKHUn7qT5GvvmKP3w5otZPOjRixUDUILWTc3P8nO5I1VNILNF6IE5ajNw6yiXOWikSxc6KuFqBQ== +"@lerna/symlink-dependencies@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/symlink-dependencies/-/symlink-dependencies-3.13.0.tgz#76c23ecabda7824db98a0561364f122b457509cf" + integrity sha512-7CyN5WYEPkbPLbqHBIQg/YiimBzb5cIGQB0E9IkLs3+racq2vmUNQZn38LOaazQacAA83seB+zWSxlI6H+eXSg== dependencies: - "@lerna/create-symlink" "3.11.0" - "@lerna/resolve-symlink" "3.11.0" - "@lerna/symlink-binary" "3.11.0" + "@lerna/create-symlink" "3.13.0" + "@lerna/resolve-symlink" "3.13.0" + "@lerna/symlink-binary" "3.13.0" fs-extra "^7.0.0" p-finally "^1.0.0" p-map "^1.2.0" p-map-series "^1.0.0" -"@lerna/timer@3.5.0": - version "3.5.0" - resolved "https://registry.yarnpkg.com/@lerna/timer/-/timer-3.5.0.tgz#8dee6acf002c55de64678c66ef37ca52143f1b9b" - integrity sha512-TAb99hqQN6E3JBGtG9iyZNPq1/DbmqgBOeNrKtdJsGvIeX/NGLgUDWMrj2h04V4O+jpBFmSf6HIld6triKmxCA== +"@lerna/timer@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/timer/-/timer-3.13.0.tgz#bcd0904551db16e08364d6c18e5e2160fc870781" + integrity sha512-RHWrDl8U4XNPqY5MQHkToWS9jHPnkLZEt5VD+uunCKTfzlxGnRCr3/zVr8VGy/uENMYpVP3wJa4RKGY6M0vkRw== -"@lerna/validation-error@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/validation-error/-/validation-error-3.11.0.tgz#3b2e97a7f5158bb1fc6c0eb3789061b99f01d7fb" - integrity sha512-/mS4o6QYm4OXUqfPJnW1mKudGhvhLe9uiQ9eK2cgSxkCAVq9G2Sl/KVohpnqAgeRI3nXordGxHS745CdAhg7pA== +"@lerna/validation-error@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/validation-error/-/validation-error-3.13.0.tgz#c86b8f07c5ab9539f775bd8a54976e926f3759c3" + integrity sha512-SiJP75nwB8GhgwLKQfdkSnDufAaCbkZWJqEDlKOUPUvVOplRGnfL+BPQZH5nvq2BYSRXsksXWZ4UHVnQZI/HYA== dependencies: npmlog "^4.1.2" -"@lerna/version@3.11.1": - version "3.11.1" - resolved "https://registry.yarnpkg.com/@lerna/version/-/version-3.11.1.tgz#c4031670838ccd5e285ec481c36e8703f4d835b2" - integrity sha512-+lFq4D8BpchIslIz6jyUY6TZO1kuAgQ+G1LjaYwUBiP2SzXVWgPoPoq/9dnaSq38Hhhvlf7FF6i15d+q8gk1xQ== - dependencies: - "@lerna/batch-packages" "3.11.0" - "@lerna/check-working-tree" "3.11.0" - "@lerna/child-process" "3.3.0" - "@lerna/collect-updates" "3.11.0" - "@lerna/command" "3.11.0" - "@lerna/conventional-commits" "3.11.0" - "@lerna/github-client" "3.11.0" - "@lerna/output" "3.11.0" - "@lerna/prompt" "3.11.0" - "@lerna/run-lifecycle" "3.11.0" - "@lerna/validation-error" "3.11.0" +"@lerna/version@3.13.1": + version "3.13.1" + resolved "https://registry.yarnpkg.com/@lerna/version/-/version-3.13.1.tgz#5e919d13abb13a663dcc7922bb40931f12fb137b" + integrity sha512-WpfKc5jZBBOJ6bFS4atPJEbHSiywQ/Gcd+vrwaEGyQHWHQZnPTvhqLuq3q9fIb9sbuhH5pSY6eehhuBrKqTnjg== + dependencies: + "@lerna/batch-packages" "3.13.0" + "@lerna/check-working-tree" "3.13.0" + "@lerna/child-process" "3.13.0" + "@lerna/collect-updates" "3.13.0" + "@lerna/command" "3.13.1" + "@lerna/conventional-commits" "3.13.0" + "@lerna/github-client" "3.13.1" + "@lerna/output" "3.13.0" + "@lerna/prompt" "3.13.0" + "@lerna/run-lifecycle" "3.13.0" + "@lerna/validation-error" "3.13.0" chalk "^2.3.1" dedent "^0.7.0" minimatch "^3.0.4" @@ -1327,10 +1442,10 @@ slash "^1.0.0" temp-write "^3.4.0" -"@lerna/write-log-file@3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@lerna/write-log-file/-/write-log-file-3.11.0.tgz#20550b5e6e6e4c20b11d80dc042aacb2a250502a" - integrity sha512-skpTDMDOkQAN4lCeAoI6/rPhbNE431eD0i6Ts3kExUOrYTr0m5CIwVtMZ31Flpky0Jfh4ET6rOl5SDNMLbf4VA== +"@lerna/write-log-file@3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@lerna/write-log-file/-/write-log-file-3.13.0.tgz#b78d9e4cfc1349a8be64d91324c4c8199e822a26" + integrity sha512-RibeMnDPvlL8bFYW5C8cs4mbI3AHfQef73tnJCQ/SgrXZHehmHnsyWUiE7qDQCAo+B1RfTapvSyFF69iPj326A== dependencies: npmlog "^4.1.2" write-file-atomic "^2.3.0" @@ -1343,6 +1458,28 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" +"@newrelic/koa@^1.0.8": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@newrelic/koa/-/koa-1.0.8.tgz#59bf54e86cef700427e32dbdc7c931a213146ac1" + integrity sha512-kY//FlLQkGdUIKEeGJlyY3dJRU63EG77YIa48ACMGZxQbWRd3WZMikyft33f8XScTq6WpCDo9xa0viNo8zeYkg== + dependencies: + methods "^1.1.2" + +"@newrelic/native-metrics@^4.0.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@newrelic/native-metrics/-/native-metrics-4.1.0.tgz#cb1641e55179cd6ec058441ae0b0eedfa36ee0eb" + integrity sha512-7CZlKMLuaYQW7mV9qVyo9b9HVe2xBnyn+kkETRJoZGs5P7gdfv9AAE3RPhtOBUopTfbmc8ju7njYadjui9J1XA== + dependencies: + nan "^2.12.1" + semver "^5.5.1" + +"@newrelic/superagent@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@newrelic/superagent/-/superagent-1.0.3.tgz#8c6ea84f8b463275c58e740204bf2939c2e2a649" + integrity sha512-lJbsqKa79qPLbHZsbiRaXl1jfzaXAN7zqqnLRqBY+zI/O5zcfyNngTmdi+9y+qIUq7xHYNaLsAxCXerrsoINKg== + dependencies: + methods "^1.1.2" + "@nodelib/fs.stat@^1.1.2": version "1.1.3" resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" @@ -1357,24 +1494,60 @@ supports-color "^5.4.0" tslib "^1" -"@oclif/command@^1.4.31", "@oclif/command@^1.5.1", "@oclif/command@^1.5.10", "@oclif/command@^1.5.3", "@oclif/command@^1.5.4", "@oclif/command@^1.5.8": - version "1.5.10" - resolved "https://registry.yarnpkg.com/@oclif/command/-/command-1.5.10.tgz#74696d9d76e1b83c13ec21df98e90ecc99f8482a" - integrity sha512-zZdPHKj7u7d50XIcWer0x/YriL8c3NHPZCNhqA/RP62Bg9BgmvLAdn1dJ8YuxxNrhFCv+DmTyukyCYGLle+eNQ== +"@oclif/command@^1.4.31", "@oclif/command@^1.5.1", "@oclif/command@^1.5.11", "@oclif/command@^1.5.3", "@oclif/command@^1.5.4", "@oclif/command@^1.5.8": + version "1.5.11" + resolved "https://registry.yarnpkg.com/@oclif/command/-/command-1.5.11.tgz#5f540b2eba234cfda9bd8102076ba99b9e821e85" + integrity sha512-jcdCmaShB7k7Lfr+rGvBeTzcCbprTF690n0dv/cB4PmXESfOx/5P4/scDR75mToht+VxOADI0aXMGQJz3T4uMg== dependencies: "@oclif/errors" "^1.2.2" "@oclif/parser" "^3.7.2" debug "^4.1.1" semver "^5.6.0" -"@oclif/config@^1.10.3", "@oclif/config@^1.12.6", "@oclif/config@^1.6.22", "@oclif/config@^1.8.7": - version "1.12.6" - resolved "https://registry.yarnpkg.com/@oclif/config/-/config-1.12.6.tgz#dacdb8fdf4eeb3d7fde22bc5d45b5b7b8ba3fc35" - integrity sha512-1xrLZkx6v/g+9FCYiyqfqikn4MtlQMH5+REZ2BOfwmcj43TBStHXylp9JtMWDvoYZpJLK/wSbBSzAxMum8LJRQ== +"@oclif/command@^1.5.10": + version "1.5.12" + resolved "https://registry.yarnpkg.com/@oclif/command/-/command-1.5.12.tgz#d4b5033c4e9d9f37e06857cf84cf0c8927e368b5" + integrity sha512-D5/Kph9smL92X1z9WPmxFd9zDruFsCk4/LbfCaBmiO2Vyyt7Y9O6kI1YLsC3B0KC9wymSCTH14IK96rf9AFHfQ== + dependencies: + "@oclif/errors" "^1.2.2" + "@oclif/parser" "^3.7.3" + debug "^4.1.1" + semver "^5.6.0" + +"@oclif/config@^1.12.8": + version "1.12.10" + resolved "https://registry.yarnpkg.com/@oclif/config/-/config-1.12.10.tgz#08ef278cfad981d618468c969c1a1c64f3a38c98" + integrity sha512-AQYFA72ktXgmrY4hjRtBQRfKLDKXPG4WGSLUgWn0KencNuqnmbiKS6zfKXf1umfZ26zoDOEfCdqZjm3aNZnIaw== dependencies: debug "^4.1.1" tslib "^1.9.3" +"@oclif/config@^1.12.9", "@oclif/config@^1.6.22", "@oclif/config@^1.8.7": + version "1.12.9" + resolved "https://registry.yarnpkg.com/@oclif/config/-/config-1.12.9.tgz#60e666135938e5c9159fbd60675606546d2d7c30" + integrity sha512-kirFdwOPHmcKPJWcTvM8Xhz30i/usC0rITfENshIvFZySWigpWaj3nLWMbu482EOcxzUqjaz55kSHv6jDFjc5w== + dependencies: + debug "^4.1.1" + tslib "^1.9.3" + +"@oclif/dev-cli@^1.21.3": + version "1.21.3" + resolved "https://registry.yarnpkg.com/@oclif/dev-cli/-/dev-cli-1.21.3.tgz#229b69d90ef6b9b121e5b2c3a2751ad90c1a417d" + integrity sha512-TYhK6kz1skw28VZq6R5anSpwidZl+48jnDdKuwc3bbyaqyCqXQAnDAxliW562iDt3GkdOxpdDMza7O4qtyDSYQ== + dependencies: + "@oclif/command" "^1.5.10" + "@oclif/config" "^1.12.8" + "@oclif/errors" "^1.2.2" + "@oclif/plugin-help" "^2.1.6" + cli-ux "^5.2.0" + debug "^4.1.1" + fs-extra "^7.0.1" + github-slugger "^1.2.1" + lodash "^4.17.11" + normalize-package-data "^2.5.0" + qqjs "^0.3.10" + tslib "^1.9.3" + "@oclif/errors@^1.2.1", "@oclif/errors@^1.2.2": version "1.2.2" resolved "https://registry.yarnpkg.com/@oclif/errors/-/errors-1.2.2.tgz#9d8f269b15f13d70aa93316fed7bebc24688edc2" @@ -1400,6 +1573,15 @@ chalk "^2.4.1" tslib "^1.9.3" +"@oclif/parser@^3.7.3": + version "3.7.3" + resolved "https://registry.yarnpkg.com/@oclif/parser/-/parser-3.7.3.tgz#cb2240f457396e7b1af2f13caa0dca85b92a5a37" + integrity sha512-yfYpDzVn9ipo4HZtYLfMtd3j3ArpTQlRbQfy9pNnHFd4VedE2PNYQTRWYYMuu1FxEOoknlMZbzsewVvl41TvKg== + dependencies: + "@oclif/linewrap" "^1.0.0" + chalk "^2.4.1" + tslib "^1.9.3" + "@oclif/plugin-autocomplete@^0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@oclif/plugin-autocomplete/-/plugin-autocomplete-0.1.0.tgz#3e813277deaa5f9fc6a67878a20bd337ee3776ec" @@ -1425,7 +1607,7 @@ lodash "^4.17.11" tslib "^1.9.3" -"@oclif/plugin-help@^2.1.4", "@oclif/plugin-help@^2.1.6": +"@oclif/plugin-help@^2.1.6": version "2.1.6" resolved "https://registry.yarnpkg.com/@oclif/plugin-help/-/plugin-help-2.1.6.tgz#ae14cbe2c5cd3eaf5f796d205ffbe66d3e369173" integrity sha512-M4kTERpPWNSM1Mga7K/zo9DWHLCVf2FRaIeXPoytmTPd+0kSvG3TR0Vc1bwx9/cxXoYyYGgEejwNlrfayr8FZw== @@ -1450,7 +1632,7 @@ fast-levenshtein "^2.0.6" lodash "^4.17.11" -"@oclif/plugin-plugins@^1.7.3": +"@oclif/plugin-plugins@^1.7.7": version "1.7.7" resolved "https://registry.yarnpkg.com/@oclif/plugin-plugins/-/plugin-plugins-1.7.7.tgz#950c91dd04480c9b57510415fc54bf7a42bb59b8" integrity sha512-bKHruaqt3MbQefRJUgsaA1B78J69+26kUA28IhB4GlWO7RpNWEBPIAMeBk/fRB4Tfw2ZuLC7T/zwOFzvY5V1Tw== @@ -1474,37 +1656,39 @@ integrity sha512-60CHpq+eqnTxLZQ4PGHYNwUX572hgpMHGPtTWMjdTMsAvlm69lZV/4ly6O3sAYkomo4NggGcomrDpBe34rxUqw== "@octokit/endpoint@^3.1.1": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-3.1.2.tgz#22b5aa8596482fbefc3f1ce22c24ad217aed60fa" - integrity sha512-iRx4kDYybAv9tOrHDBE6HwlgiFi8qmbZl8SHliZWtxbUFuXLZXh2yv8DxGIK9wzD9J0wLDMZneO8vNYJNUSJ9Q== + version "3.1.3" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-3.1.3.tgz#f6e9c2521b83b74367600e474b24efec2b0471c4" + integrity sha512-vAWzeoj9Lzpl3V3YkWKhGzmDUoMfKpyxJhpq74/ohMvmLXDoEuAGnApy/7TRi3OmnjyX2Lr+e9UGGAD0919ohA== dependencies: - deepmerge "3.1.0" + deepmerge "3.2.0" is-plain-object "^2.0.4" universal-user-agent "^2.0.1" url-template "^2.0.8" -"@octokit/plugin-enterprise-rest@^2.1.0": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-2.1.1.tgz#ee7b245aada06d3ffdd409205ad1b891107fee0b" - integrity sha512-DJNXHH0LptKCLpJ8y3vCA/O+s+3/sDU4JNN2V0M04tsMN0hVGLPzoGgejPJgaxGP8Il5aw+jA5Nl5mTfdt9NrQ== +"@octokit/plugin-enterprise-rest@^2.1.1": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-2.2.0.tgz#7ee72a187e8a034d6fc21b8174bef40e34c22f02" + integrity sha512-/uXIvjK5bxmMKI1MDZXxVSiheiyvqv7GCWjoN1s43jF3MMrfqnErOwbZkreeL0CgO1R2lNW6dESDV5NbRiWEQA== -"@octokit/request@2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@octokit/request/-/request-2.3.0.tgz#da2672308bcf0b9376ef66f51bddbe5eb87cc00a" - integrity sha512-5YRqYNZOAaL7+nt7w3Scp6Sz4P2g7wKFP9npx1xdExMomk8/M/ICXVLYVam2wzxeY0cIc6wcKpjC5KI4jiNbGw== +"@octokit/request@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-2.4.1.tgz#98c4d6870e4abe3ccdd2b9799034b4ae3f441c30" + integrity sha512-nN8W24ZXEpJQJoVgMsGZeK9FOzxkc39Xn9ykseUpPpPMNEDFSvqfkCeqqKrjUiXRm72ubGLWG1SOz0aJPcgGww== dependencies: "@octokit/endpoint" "^3.1.1" + deprecation "^1.0.1" is-plain-object "^2.0.4" node-fetch "^2.3.0" + once "^1.4.0" universal-user-agent "^2.0.1" -"@octokit/rest@^16.15.0": - version "16.15.0" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-16.15.0.tgz#648a88d5de055bcf38976709c5b2bdf1227b926f" - integrity sha512-Un+e7rgh38RtPOTe453pT/KPM/p2KZICimBmuZCd2wEo8PacDa4h6RqTPZs+f2DPazTTqdM7QU4LKlUjgiBwWw== +"@octokit/rest@^16.16.0": + version "16.17.0" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-16.17.0.tgz#3a8c0ff5290e25a48b11f6957aa90791c672c91e" + integrity sha512-1RB7e4ptR/M+1Ik3Qn84pbppbSadBaCtpgFqgqsXn6s4ZVE6hqW9SOm6UW5yd3KT7ObVfdYUkhMlgR937oKyDw== dependencies: - "@octokit/request" "2.3.0" - before-after-hook "^1.2.0" + "@octokit/request" "2.4.1" + before-after-hook "^1.4.0" btoa-lite "^1.0.0" lodash.get "^4.4.2" lodash.set "^4.3.2" @@ -1513,59 +1697,6 @@ universal-user-agent "^2.0.0" url-template "^2.0.8" -"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" - integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78= - -"@protobufjs/base64@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" - integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== - -"@protobufjs/codegen@^2.0.4": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" - integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== - -"@protobufjs/eventemitter@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" - integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A= - -"@protobufjs/fetch@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" - integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU= - dependencies: - "@protobufjs/aspromise" "^1.1.1" - "@protobufjs/inquire" "^1.1.0" - -"@protobufjs/float@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" - integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E= - -"@protobufjs/inquire@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" - integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik= - -"@protobufjs/path@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" - integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0= - -"@protobufjs/pool@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" - integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q= - -"@protobufjs/utf8@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" - integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= - "@samverschueren/stream-to-observable@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f" @@ -1573,44 +1704,44 @@ dependencies: any-observable "^0.3.0" -"@sentry/core@4.5.4": - version "4.5.4" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-4.5.4.tgz#31c9a0b66d33cbd5b4d7de2b69d6c7951c7c06a3" - integrity sha512-Nj5VZjAVntroHn9tuqez8KnRgv91KWQfESzN9/DA/k9orZ8vI4+8bm78Zpq6IteumaxLxGdXw8H1s8xVGlU3/Q== +"@sentry/core@4.6.4": + version "4.6.4" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-4.6.4.tgz#7236e08115423b81b96a13c2c37f29bcc1477745" + integrity sha512-NGl2nkAaQ8dGqJAMS1Hb+7RyVjW4tmCbK6d7H/zKnOpBuU+qSW4XCm2NoGLLa8qb4SZUPIBRv6U0ByvEQlGtqw== dependencies: - "@sentry/hub" "4.5.4" - "@sentry/minimal" "4.5.4" + "@sentry/hub" "4.6.4" + "@sentry/minimal" "4.6.4" "@sentry/types" "4.5.3" - "@sentry/utils" "4.5.4" + "@sentry/utils" "4.6.4" tslib "^1.9.3" -"@sentry/hub@4.5.4": - version "4.5.4" - resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-4.5.4.tgz#7e0980dd2f3ead60ce1393e4e108fd4fe65e40b7" - integrity sha512-ghvfydSTDHh6pVuLpcubI6ksELplStORhsklOSw+fq7HtDPWNyr9Cv2USgd341whnURXvuT+TQuPnZnOMuBqSg== +"@sentry/hub@4.6.4": + version "4.6.4" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-4.6.4.tgz#2bd5d67ccd43d4f5afc45005a330a11b14d46cea" + integrity sha512-R3ACxUZbrAMP6vyIvt1k4bE3OIyg1CzbEhzknKljPrk1abVmJVP7W/X1vBysdRtI3m/9RjOSO7Lxx3XXqoHoQg== dependencies: "@sentry/types" "4.5.3" - "@sentry/utils" "4.5.4" + "@sentry/utils" "4.6.4" tslib "^1.9.3" -"@sentry/minimal@4.5.4": - version "4.5.4" - resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-4.5.4.tgz#9c3203feae39e4172fdf4c7d34bc650383555a66" - integrity sha512-KuR7PmpGQcOLkMV7IMXo9R4EjkPoQYsLRf5WkswacKnaFNLwrfxU0/0Mv+eyY/JqRN8+vewPQ1hKkAWsbzBtqg== +"@sentry/minimal@4.6.4": + version "4.6.4" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-4.6.4.tgz#dc4bb47df90dad6025d832852ac11fe29ed50147" + integrity sha512-jZa9mfzDzJI98tg6uxFG3gdVLyz0nOHpLP9H8Kn/BelZ7WEG/ogB8PDi1hI9JvCTXAr8kV81mEecldADa9L9Yg== dependencies: - "@sentry/hub" "4.5.4" + "@sentry/hub" "4.6.4" "@sentry/types" "4.5.3" tslib "^1.9.3" -"@sentry/node@^4.5.4": - version "4.5.4" - resolved "https://registry.yarnpkg.com/@sentry/node/-/node-4.5.4.tgz#c6963b519fe3e21b8c3aa6b94526ebe60fec8dbd" - integrity sha512-JXMbbB+gNzomyJIoq4nVHRP3URVDb0eta5DTsIxM6ZVf7fOgHt+yrOQ7FAOz3qzT68xxY9wlGRxAnPEsiqPIXA== +"@sentry/node@^4.6.4": + version "4.6.4" + resolved "https://registry.yarnpkg.com/@sentry/node/-/node-4.6.4.tgz#933c2e3ce93bc7861de6d4310ed1fe66f85da301" + integrity sha512-nfaLB+cE0dddjWD0yI0nB/UqXkPw/6FKDRpB1NZ61amAM4QRXa4hRTdHvqjUovzV/5/pVMQYsOyCk0pNWMtMUQ== dependencies: - "@sentry/core" "4.5.4" - "@sentry/hub" "4.5.4" + "@sentry/core" "4.6.4" + "@sentry/hub" "4.6.4" "@sentry/types" "4.5.3" - "@sentry/utils" "4.5.4" + "@sentry/utils" "4.6.4" "@types/stack-trace" "0.0.29" cookie "0.3.1" https-proxy-agent "2.2.1" @@ -1624,10 +1755,10 @@ resolved "https://registry.yarnpkg.com/@sentry/types/-/types-4.5.3.tgz#3350dce2b7f9b936a8c327891c12e3aef7bd8852" integrity sha512-7ll1PAFNjrBNX9rzy3P2qAQrpQwHaDO3uKj735qsnGw34OtAS8Xr8WYrjI14f9fMPa/XIeWvMPb4GMic28V/ag== -"@sentry/utils@4.5.4": - version "4.5.4" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-4.5.4.tgz#fc074b0c16850f39685a6c17f96407f56ed36b03" - integrity sha512-LhWnCuTEzmbQKyc0qyahs5PYPpj9qkY3BpMOyEIoYz79RU5lMD1iPR62wG8cqO09JmrCHuqfWeQIQIwUFQTaBQ== +"@sentry/utils@4.6.4": + version "4.6.4" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-4.6.4.tgz#ca254c142b519b4f20d63c2f9edf1a89966be36f" + integrity sha512-Tc5R46z7ve9Z+uU34ceDoEUR7skfQgXVIZqjbrTQphgm6EcMSNdRfkK3SJYZL5MNKiKhb7Tt/O3aPBy5bTZy6w== dependencies: "@sentry/types" "4.5.3" tslib "^1.9.3" @@ -1642,25 +1773,25 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow== -"@sindresorhus/tsconfig@^0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@sindresorhus/tsconfig/-/tsconfig-0.1.1.tgz#c13d7d79ab5a7b02c2374487d0822695a1baa496" - integrity sha512-n2KZlZ5tD2oc0CUNYtKSRSJr2PC6pqSG9n1JWvY12rU6UjkQ563czDUxCSEGyOEhhogvu8Ad5SonjH7DoBNa7w== +"@sindresorhus/tsconfig@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@sindresorhus/tsconfig/-/tsconfig-0.2.1.tgz#b209d3f2be2c39b4cff39ec5bded7b5154427989" + integrity sha512-z/jGJwReg/eHlBUuQkgrQtBo9d+HB0rxu9hYYsnV89dVItMNeohAsHUnfI5y4HHMNyOkFJ+g0a/6lLoPKZehpw== -"@snyk/dep-graph@1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@snyk/dep-graph/-/dep-graph-1.1.2.tgz#a0377fbb29dd42bc12d1c2493b51a7b7fe0d334a" - integrity sha512-mCoAFKtmezBL61JOzLMzqqd/sXXxp0iektEwf4zw+sM3zuG4Tnmhf8OqNO6Wscn84bMIfLlI/nvECdxvSS7MTw== +"@snyk/dep-graph@1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@snyk/dep-graph/-/dep-graph-1.4.0.tgz#0d0971c3ef3238a74112ed5d81c782a6a292575a" + integrity sha512-dz4Fo4L9sN0Rt8hMe+zMYZ4mAiljtyIeWvujLyCxhIT5iHSlceUYlba5TDsOFuKyuZKkuhVjORWY1oFEuRzCcA== dependencies: graphlib "^2.1.5" lodash "^4" source-map-support "^0.5.9" tslib "^1.9.3" -"@snyk/gemfile@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@snyk/gemfile/-/gemfile-1.1.0.tgz#8c254dfc7739b2e8513c44c976fc41872d5f6af0" - integrity sha512-mLwF+ccuvRZMS0SxUAxA3dAp8mB3m2FxIsBIUWFTYvzxl+E4XTZb8uFrUqXHbcxhZH1Z8taHohNTbzXZn3M8ag== +"@snyk/gemfile@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@snyk/gemfile/-/gemfile-1.2.0.tgz#919857944973cce74c650e5428aaf11bcd5c0457" + integrity sha512-nI7ELxukf7pT4/VraL4iabtNNMz8mUo7EXlqCFld8O5z6mIMLX9llps24iPpaIZOwArkY3FWA+4t+ixyvtTSIA== "@szmarczak/http-timer@^1.1.2": version "1.1.2" @@ -1670,23 +1801,48 @@ defer-to-connect "^1.0.1" "@types/anymatch@*": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.0.tgz#d1d55958d1fccc5527d4aba29fc9c4b942f563ff" - integrity sha512-7WcbyctkE8GTzogDb0ulRAEw7v8oIS54ft9mQTU7PfM0hp5e+8kpa+HeQ7IQrFbKtJXBKcZ4bh+Em9dTw5L6AQ== + version "1.3.1" + resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" + integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA== -"@types/async@^2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@types/async/-/async-2.4.0.tgz#2d707a0127a4fe45877c3152d813e4b23c6ce160" - integrity sha512-1/6EglKPJZjkj2KWSFrvbOeqDZTb+KEiU293MUwc/z3/g4a+jOPADHfcOzt/EbY0uEEr+dkUWTYQfVDzzOrMaw== +"@types/async@^2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@types/async/-/async-2.4.1.tgz#43c3b2c60eab41c25ca0009c07ca7d619d943119" + integrity sha512-C09BK/wXzbW+/JK9zckhe+FeSbg7NmvVjUWwApnw7ksRpUq3ecGLiq2Aw1LlY4Z/VmtdhSaIs7jO5/MWRYMcOA== + +"@types/babel__core@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.0.tgz#710f2487dda4dcfd010ca6abb2b4dc7394365c51" + integrity sha512-wJTeJRt7BToFx3USrCDs2BhEi4ijBInTQjOIukj6a/5tEkwpFMVZ+1ppgmE+Q/FQyc5P/VWUbx7I9NELrKruHA== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" -"@types/babel__core@^7.0.4": - version "7.0.4" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.0.4.tgz#14b30c11113bad353cabfaea73e327b48edb0f0e" - integrity sha512-2Y2RK1BN5BRFfhneGfQA8mmFmTANbzGgS5uQPluoRqGNWb6uAcefqxzNbqgxPpmPkLqKapQfmYcyyl5iAQV+fA== +"@types/babel__generator@*": + version "7.0.2" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.0.2.tgz#d2112a6b21fad600d7674274293c85dce0cb47fc" + integrity sha512-NHcOfab3Zw4q5sEE2COkpfXjoE7o+PmqD9DQW4koUT3roNxwziUdXGnRndMat/LJNUtePwn1TlP4do3uoe3KZQ== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.0.2" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.0.2.tgz#4ff63d6b52eddac1de7b975a5223ed32ecea9307" + integrity sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg== dependencies: "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.0.6" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.6.tgz#328dd1a8fc4cfe3c8458be9477b219ea158fd7b2" + integrity sha512-XYVgHF2sQ0YblLRMLNPB3CkFMewzFmlDsH/TneZFHUXDlABQgh88uOxuez7ZcXxayLFrqLwtDH1t+FmlFwNZxw== + dependencies: + "@babel/types" "^7.3.0" + "@types/better-sqlite3@^5.2.2": version "5.2.2" resolved "https://registry.yarnpkg.com/@types/better-sqlite3/-/better-sqlite3-5.2.2.tgz#72e9e8adb47f4087bbe31c2d13581c1bf3012a6b" @@ -1701,32 +1857,24 @@ dependencies: "@types/node" "*" -"@types/bip38@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@types/bip38/-/bip38-2.0.0.tgz#2c8bed75ac77aefead77580147fe6cac443c133b" - integrity sha512-R8/rsp9fbqrxTg6FelmEuiNR39/VIrmNtbyG487Q9ObhPnxf+HZ/Sc0FvqcqLq+Mbcj5bjXSDu+wB3dNeZS1zQ== +"@types/bip38@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/bip38/-/bip38-2.0.1.tgz#6fd5181b2a137302d8a59099d19e6feddbbdcb6c" + integrity sha512-mZTvxt9Oue3dDV8/sgJWNCsdmrkmhnGVRja3ZFKeMHheKbyS6FgcBep0WlpTP5pK8Z3Y3YILBP4nHnAV7knq0g== dependencies: "@types/node" "*" -"@types/bip39@^2.4.1": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@types/bip39/-/bip39-2.4.1.tgz#1a47b453b59a50d7b5856819b834c74798915eb3" - integrity sha512-QHx0qI6JaTIW/S3zxE/bXrwOWu6Boos+LZ4438xmFAHY5k+qHkExMdAnb/DENEt2RBnOdZ6c5J+SHrnLEhUohQ== +"@types/bip39@^2.4.2": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@types/bip39/-/bip39-2.4.2.tgz#f5d6617212be496bb998d3969f657f77a10c5287" + integrity sha512-Vo9lqOIRq8uoIzEVrV87ZvcIM0PN9t0K3oYZ/CS61fIYKCBdOIM7mlWzXuRvSXrDtVa1uUO2w1cdfufxTC0bzg== dependencies: "@types/node" "*" -"@types/bluebird@*", "@types/bluebird@^3.5.25": - version "3.5.25" - resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.25.tgz#59188b871208092e37767e4b3d80c3b3eaae43bd" - integrity sha512-yfhIBix+AIFTmYGtkC0Bi+XGjSkOINykqKvO/Wqdz/DuXlAKK7HmhLAXdPIGsV4xzKcL3ev/zYc4yLNo+OvGaw== - -"@types/body-parser@*", "@types/body-parser@^1.17.0": - version "1.17.0" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.17.0.tgz#9f5c9d9bd04bb54be32d5eb9fc0d8c974e6cf58c" - integrity sha512-a2+YeUjPkztKJu5aIF2yArYFQQp8d51wZ7DavSHjFuY1mqVgidGyzEQ41JIVNy82fXj8yPgy2vJmfIywgESW6w== - dependencies: - "@types/connect" "*" - "@types/node" "*" +"@types/bluebird@*", "@types/bluebird@^3.5.26": + version "3.5.26" + resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.26.tgz#a38c438ae84fa02431d6892edf86e46edcbca291" + integrity sha512-aj2mrBLn5ky0GmAg6IPXrQjnN0iB/ulozuJ+oZdrHRAzRbXjGmu4UXsNCjFvPbSaaPZmniocdOzsM392qLOlmQ== "@types/boom@*", "@types/boom@^7.2.1": version "7.2.1" @@ -1739,9 +1887,9 @@ integrity sha512-A3MV5EsLHgShHoJ/XES/fQAnwNISKLrFuH9eNBZY5OkTQB7JPIwbRoExvRpDsNABvkMojnKqKWS8x0m2rLYi+A== "@types/bson@*": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@types/bson/-/bson-1.0.11.tgz#c95ad69bb0b3f5c33b4bb6cc86d86cafb273335c" - integrity sha512-j+UcCWI+FsbI5/FQP/Kj2CXyplWAz39ktHFkXk84h7dNblKRSoNJs95PZFRd96NQGqsPEPgeclqnznWZr14ZDA== + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/bson/-/bson-4.0.0.tgz#9073772679d749116eb1dfca56f8eaac6d59cc7a" + integrity sha512-pq/rqJwJWkbS10crsG5bgnrisL8pML79KlMKQMoQwLUjlPAkrUHMvHJ3oGwE7WHR61Lv/nadMwXVAD2b+fpD8Q== dependencies: "@types/node" "*" @@ -1766,9 +1914,9 @@ integrity sha512-FhlMa34NHp9K5MY1Uz8yb+ZvuX0pnvn3jScRSNAb75KHGB8d3rEU6hqMs3Z2vjuytcMfRg6c5CHMc3wtYyD2/A== "@types/catbox@*": - version "10.0.4" - resolved "https://registry.yarnpkg.com/@types/catbox/-/catbox-10.0.4.tgz#26a88c82856e18f4ea86b50e3a92b2f6f534f70d" - integrity sha512-N+a5rLOsZyLYoqtCHYfw5sIcTJgm0SofLM8GSNNOwvZdFbuefMyHScJrN4xHtmcXwkY+aGXN7M2bQWtz63gJhA== + version "10.0.6" + resolved "https://registry.yarnpkg.com/@types/catbox/-/catbox-10.0.6.tgz#8a4c91261cf0afca03351bb82a95b2d6cf43a5d0" + integrity sha512-qS0VHlL6eBUUoUeBnI/ASCffoniS62zdV6IUtLSIjGKmRhZNawotaOMsTYivZOTZVktfe9koAJkD9XFac7tEEg== "@types/cli-progress@^1.8.0": version "1.8.0" @@ -1782,13 +1930,6 @@ resolved "https://registry.yarnpkg.com/@types/clipboardy/-/clipboardy-1.1.0.tgz#316fe1a3ed71b1a51becb925e7e0497986c6a85c" integrity sha512-KOxf4ah9diZWmREM5jCupx2+pZaBPwKk5d5jeNK2+TY6IgEO35uhG55NnDT4cdXeRX8irDSHQPtdRrr0JOTQIw== -"@types/connect@*": - version "3.4.32" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.32.tgz#aa0e9616b9435ccad02bc52b5b454ffc2c70ba28" - integrity sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg== - dependencies: - "@types/node" "*" - "@types/continuation-local-storage@*": version "3.2.1" resolved "https://registry.yarnpkg.com/@types/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz#a33e0df9dce9b424d1c98fc4fdebd8578dceec7e" @@ -1803,15 +1944,10 @@ dependencies: "@types/node" "*" -"@types/elasticsearch@^5.0.30": - version "5.0.30" - resolved "https://registry.yarnpkg.com/@types/elasticsearch/-/elasticsearch-5.0.30.tgz#3c52f7119e3a20a47e2feb8e2b4cc54030a54e23" - integrity sha512-swxiNcLOtnHhJhAE5HcUL3WsKLHr8rEQ+fwpaJ0x4dfEE3oK2kGUoyz4wCcQfvulcMm2lShyxZ+2E4BQJzsAlg== - -"@types/env-paths@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@types/env-paths/-/env-paths-1.0.2.tgz#8b2b2b89d959591de2bf74ea448a7db2289299aa" - integrity sha512-EVOU+P4CSugzatd2GD8tVEYYR/OMcwzkneaJLx0R091gTqW2wZO1vpiWoM+BKZAEYdzif0SfwRuBvegIZBcZnw== +"@types/elasticsearch@^5.0.31": + version "5.0.31" + resolved "https://registry.yarnpkg.com/@types/elasticsearch/-/elasticsearch-5.0.31.tgz#82c02c743a5eaca6dbb4b04a0689e6c53fd4f296" + integrity sha512-XiYeE0vQMJG6rSrDw2WgNIK1RelWB1FmplDJGlmlLG5/Cb75Q0+EHS0X38hzptI+jjvyvPorE6pvJkgPoM6hhg== "@types/events@*": version "3.0.0" @@ -1825,23 +1961,6 @@ dependencies: "@types/node" "*" -"@types/express-serve-static-core@*": - version "4.16.1" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.16.1.tgz#35df7b302299a4ab138a643617bd44078e74d44e" - integrity sha512-QgbIMRU1EVRry5cIu1ORCQP4flSYqLM1lS5LYyGWfKnFT3E58f0gKto7BR13clBFVrVZ0G0rbLZ1hUpSkgQQOA== - dependencies: - "@types/node" "*" - "@types/range-parser" "*" - -"@types/express@^4.16.0": - version "4.16.1" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.16.1.tgz#d756bd1a85c34d87eaf44c888bad27ba8a4b7cf0" - integrity sha512-V0clmJow23WeyblmACoxbHBu2JKlE5TiIme6Lem14FnPW9gsttyHtk6wq7njcdIWH1njAaFgR8gW09lgY98gQg== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "*" - "@types/serve-static" "*" - "@types/form-data@*": version "2.2.1" resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-2.2.1.tgz#ee2b3b8eaa11c0938289953606b745b738c54b1e" @@ -1849,26 +1968,14 @@ dependencies: "@types/node" "*" -"@types/fs-extra@^5.0.2", "@types/fs-extra@^5.0.4", "@types/fs-extra@^5.0.5": +"@types/fs-extra@^5.0.2", "@types/fs-extra@^5.0.5": version "5.0.5" resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.0.5.tgz#080d90a792f3fa2c5559eb44bd8ef840aae9104b" integrity sha512-w7iqhDH9mN8eLClQOYTkhdYUOSpp25eXxfc6VbFOGtzxW34JcvctH2bKjj4jD4++z4R5iO5D+pg48W2e03I65A== dependencies: "@types/node" "*" -"@types/fs-extra@^5.0.3": - version "5.0.4" - resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.0.4.tgz#b971134d162cc0497d221adde3dbb67502225599" - integrity sha512-DsknoBvD8s+RFfSGjmERJ7ZOP1HI0UZRA3FSI+Zakhrc/Gy26YQsLI+m5V5DHxroHRJqCDLKJp7Hixn8zyaF7g== - dependencies: - "@types/node" "*" - -"@types/geojson@^1.0.0": - version "1.0.6" - resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-1.0.6.tgz#3e02972728c69248c2af08d60a48cbb8680fffdf" - integrity sha512-Xqg/lIZMrUd0VRmSRbCAewtwGZiAk3mEUDvV4op1tGl+LvyPcb/MIOSxTl9z+9+J+R4/vpjiCAT4xeKzH9ji1w== - -"@types/glob@*": +"@types/glob@*", "@types/glob@^7.1.1": version "7.1.1" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w== @@ -1877,23 +1984,25 @@ "@types/minimatch" "*" "@types/node" "*" -"@types/got@^9.4.0": - version "9.4.0" - resolved "https://registry.yarnpkg.com/@types/got/-/got-9.4.0.tgz#5c8b0f5a0dc1b67e676430561a0b85a0fde1d12d" - integrity sha512-18c/Bu9tpv6owaz17urNFfkzfxdmr3PoREqFpAkUasBbV9gc7OqChRj/e2f98abiJo7j++PhArfm66EFqAY4qA== +"@types/got@^9.4.1": + version "9.4.1" + resolved "https://registry.yarnpkg.com/@types/got/-/got-9.4.1.tgz#7479a3a321599b5e17647f3bd9d402b8c55bfee1" + integrity sha512-m7Uc07bG/bZ+Dis7yI3mGssYDcAdUvP4irF3ZmBzf0ig7zEd1FyADfnELVGcf+p1Ol/iPCXbZYwcSNOJA2a+Qg== dependencies: "@types/node" "*" "@types/tough-cookie" "*" -"@types/handlebars@^4.0.38", "@types/handlebars@^4.0.40": - version "4.0.40" - resolved "https://registry.yarnpkg.com/@types/handlebars/-/handlebars-4.0.40.tgz#b714e13d296a75bff3f199316d1311933ca79ffd" - integrity sha512-sGWNtsjNrLOdKha2RV1UeF8+UbQnPSG7qbe5wwbni0mw4h2gHXyPFUMOC+xwGirIiiydM/HSqjDO4rk6NFB18w== +"@types/handlebars@^4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@types/handlebars/-/handlebars-4.1.0.tgz#3fcce9bf88f85fe73dc932240ab3fb682c624850" + integrity sha512-gq9YweFKNNB1uFK71eRqsd4niVkXrxHugqWFQkeLRJvGjnxsLr16bYtcsG4tOFwmYi0Bax+wCkbf1reUfdl4kA== + dependencies: + handlebars "*" -"@types/hapi@*", "@types/hapi@^18.0.0": - version "18.0.0" - resolved "https://registry.yarnpkg.com/@types/hapi/-/hapi-18.0.0.tgz#48b5a894a59fe95895d80068cca3683638714bf5" - integrity sha512-6OSXjpS1AmN90BrJ90ruXR3H+utR3ByZcRdQyJqqAGA+saD2s/E3Dz54V09xI3phDTu2F0VSUxpRdFJprTuw+w== +"@types/hapi@*", "@types/hapi@^18.0.0", "@types/hapi@^18.0.1": + version "18.0.1" + resolved "https://registry.yarnpkg.com/@types/hapi/-/hapi-18.0.1.tgz#4e1f76b89b6d939aaab2ecbce7576146f5ea2739" + integrity sha512-2YznFunreTu7j+qWp8HU/0qLQ/RVJveWvqRQeT8N5/xOKMe6lIIS6gikNpuCZP5Ugh7WM+kpv9Nh/enVP+4E6w== dependencies: "@types/boom" "*" "@types/catbox" "*" @@ -1904,11 +2013,6 @@ "@types/podium" "*" "@types/shot" "*" -"@types/highlight.js@^9.12.3": - version "9.12.3" - resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-9.12.3.tgz#b672cfaac25cbbc634a0fd92c515f66faa18dbca" - integrity sha512-pGF/zvYOACZ/gLGWdQH8zSwteQS1epp68yRcVLJMgUck/MjEn/FBYmPub9pXT8C1e4a8YZfHo1CKyV8q1vKUnQ== - "@types/hoek@^4.1.3": version "4.1.3" resolved "https://registry.yarnpkg.com/@types/hoek/-/hoek-4.1.3.tgz#d1982d48fb0d2a0e5d7e9d91838264d8e428d337" @@ -1945,17 +2049,34 @@ resolved "https://registry.yarnpkg.com/@types/is-reachable/-/is-reachable-3.0.0.tgz#3203aa585cd5d3a4c1339bc857f4de06c4a18aff" integrity sha512-rCl0PxFRPqwnKnWOO+6YdfnIIEsqahrEQjBfKR31C6E5oC4MY2U9W6VvXl090ZBYoCEY2wtqrBQaZn0UBGY85g== -"@types/jest@^23.3.10": - version "23.3.14" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.14.tgz#37daaf78069e7948520474c87b80092ea912520a" - integrity sha512-Q5hTcfdudEL2yOmluA1zaSyPbzWPmJ3XfSWeP3RyoYvS9hnje1ZyagrZOuQ6+1nQC1Gw+7gap3pLNL3xL6UBug== +"@types/istanbul-lib-coverage@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.0.tgz#2cc2ca41051498382b43157c8227fea60363f94a" + integrity sha512-ohkhb9LehJy+PA40rDtGAji61NCgdtKLAlFoYp4cnuuQEswwdK3vz9SOIkkyc3wrk8dzjphQApNs56yyXLStaQ== + +"@types/jest-diff@*": + version "20.0.1" + resolved "https://registry.yarnpkg.com/@types/jest-diff/-/jest-diff-20.0.1.tgz#35cc15b9c4f30a18ef21852e255fdb02f6d59b89" + integrity sha512-yALhelO3i0hqZwhjtcr6dYyaLoCHbAMshwtj6cGxTvHZAKXHsYGdff6E8EPw3xLKY0ELUTQ69Q1rQiJENnccMA== + +"@types/jest@^24.0.9": + version "24.0.11" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-24.0.11.tgz#1f099bea332c228ea6505a88159bfa86a5858340" + integrity sha512-2kLuPC5FDnWIDvaJBzsGTBQaBbnDweznicvK7UGYzlIJP4RJR2a4A/ByLUXEyEgag6jz8eHdlWExGDtH3EYUXQ== + dependencies: + "@types/jest-diff" "*" "@types/joi@*", "@types/joi@^14.3.1": version "14.3.1" resolved "https://registry.yarnpkg.com/@types/joi/-/joi-14.3.1.tgz#1bc31b60a928da30cc206b1b4f88e13a48bffc79" integrity sha512-1kAPyOBvPPQoWOV4b1qT2SRJrQbYY9kz6h5DV8fNgSW0+/Ma+aDLQDACISC1M982uWwjAgsMEVXdalA/+IhvaA== -"@types/js-yaml@^3.11.4": +"@types/joi@^14.3.2": + version "14.3.2" + resolved "https://registry.yarnpkg.com/@types/joi/-/joi-14.3.2.tgz#eff771f0b19698b8e6e7c6fe6b625e07e2618fe9" + integrity sha512-riJTpNwDOoRgRrrOjfcjwMBYJLVg8rzwMEAAcZ2JBvdmYWCLIsbzTFXHV517terbYzGmdzkwFYeXUem7eXVNoQ== + +"@types/js-yaml@^3.12.0": version "3.12.0" resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.0.tgz#3494ce97358e2675e24e97a747ec23478eeaf8b6" integrity sha512-UGEe/6RsNAxgWdknhzFZbCxuYc5I7b/YEKlfKbo+76SM8CJzGs7XKCj7zyugXViRbKYpXhSXhCYVQZL5tmDbpQ== @@ -1987,187 +2108,182 @@ dependencies: "@types/node" "*" -"@types/lodash.camelcase@^4.3.4": - version "4.3.4" - resolved "https://registry.yarnpkg.com/@types/lodash.camelcase/-/lodash.camelcase-4.3.4.tgz#bdc60ff98f7727787d9ea593e398a3e9bf9f6180" - integrity sha512-yx+TmSP3QnhUGdcxkvwV3O++eI6kXKf5E89yJutHR8ebdr5f7KF5XmTBIWrbXFBLo2JIcaBz2axdpe7/WiOT2g== +"@types/lodash.camelcase@^4.3.6": + version "4.3.6" + resolved "https://registry.yarnpkg.com/@types/lodash.camelcase/-/lodash.camelcase-4.3.6.tgz#393c748b70cd926fc629e6502a9d0929f217d5fd" + integrity sha512-hd/TEuPd76Jtf1xEq85CHbCqR+iqvs5IOKyrYbiaOg69BRQgPN9XkvLj8Jl8rBp/dfJ2wQ1AVcP8mZmybq7kIg== dependencies: "@types/lodash" "*" -"@types/lodash.chunk@^4.2.4": - version "4.2.4" - resolved "https://registry.yarnpkg.com/@types/lodash.chunk/-/lodash.chunk-4.2.4.tgz#d0301e7ed435f7eb60c570e715fb05047256d536" - integrity sha512-8/M4C4g2xIKCZb6B66J3pQrcdGgAEb9O8gZrYULJ7dI3BDOFLm5bzrg+K4u5MogGqx3K19rJoy1BnJ0KNQvMBA== +"@types/lodash.chunk@^4.2.6": + version "4.2.6" + resolved "https://registry.yarnpkg.com/@types/lodash.chunk/-/lodash.chunk-4.2.6.tgz#9d35f05360b0298715d7f3d9efb34dd4f77e5d2a" + integrity sha512-SPlusB7jxXyGcTXYcUdWr7WmhArO/rmTq54VN88iKMxGUhyg79I4Q8n4riGn3kjaTjOJrVlHhxgX/d7woak5BQ== dependencies: "@types/lodash" "*" -"@types/lodash.clonedeep@^4.5.4": - version "4.5.4" - resolved "https://registry.yarnpkg.com/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.4.tgz#2515c5f08bc95afebfb597711871b0497f5d7da7" - integrity sha512-+rCVPIZOJaub++wU/lmyp/SxiKlqXQaXI5LryzjuHBKFj51ApVt38Xxk9psLWNGMuR/obEQNTH0l/yDfG4ANNQ== +"@types/lodash.clonedeep@^4.5.6": + version "4.5.6" + resolved "https://registry.yarnpkg.com/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.6.tgz#3b6c40a0affe0799a2ce823b440a6cf33571d32b" + integrity sha512-cE1jYr2dEg1wBImvXlNtp0xDoS79rfEdGozQVgliDZj1uERH4k+rmEMTudP9b4VQ8O6nRb5gPqft0QzEQGMQgA== dependencies: "@types/lodash" "*" -"@types/lodash.compact@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/lodash.compact/-/lodash.compact-3.0.4.tgz#f5eb5b34fa19521029febf27b8eca0a6689c2384" - integrity sha512-oHZH8dWjSJHIstoGL7ZKtZ87UP7Diz9SXKAFLh19JZaEkdxhld89vg+XykjMzdR0mfcZWF5mWOrco+SKG+uXtg== +"@types/lodash.compact@^3.0.6": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/lodash.compact/-/lodash.compact-3.0.6.tgz#a4c356372f51fb39ff48eb4f014a19f39f674ab5" + integrity sha512-0pDKTX4alTyxH85Y5Al4YzS8oriqBQykADW6zLAHkZwNBMPXFIhdE2ctg0Z2GVcZsABxo5CI/J3vmHrFkdQBfA== dependencies: "@types/lodash" "*" -"@types/lodash.fill@^3.4.4": - version "3.4.4" - resolved "https://registry.yarnpkg.com/@types/lodash.fill/-/lodash.fill-3.4.4.tgz#c54608d7da691142bf281134149b6ecf0d1f701b" - integrity sha512-D2c164uS5YG3OYmalDFW3yXlhq3DmFE8y1EdQ9MqQ9VPFBD9+73GMzZxRAdG4/G8O3ZNeERkRGXMJCgsWi7c6A== +"@types/lodash.fill@^3.4.6": + version "3.4.6" + resolved "https://registry.yarnpkg.com/@types/lodash.fill/-/lodash.fill-3.4.6.tgz#f9d23b632af90e123bbd8e8b26585095b5983534" + integrity sha512-1QFEsIejhgbsoxFj+jRwGB5kYpTCziFXFV4Ga5z97kbh24O8Pof3Rrh5RDB0LDeOJ/USCt0aeZrWt8LPDZwVVQ== dependencies: "@types/lodash" "*" -"@types/lodash.flatten@^4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@types/lodash.flatten/-/lodash.flatten-4.4.4.tgz#7f28009ef57c8d2b1d8463c3e53fdccf780120a5" - integrity sha512-F106FV2hmztEtMHozFMfS41u+58vjMEv2SJljMlXmPCn13yWS+/B1r0KjQuaZpsPE857req0BunDwzgpqQ2Ydg== +"@types/lodash.flatten@^4.4.6": + version "4.4.6" + resolved "https://registry.yarnpkg.com/@types/lodash.flatten/-/lodash.flatten-4.4.6.tgz#b74c3267c87e44e603137d4621e8a9396b6551f5" + integrity sha512-omCBl4M8EJSmf2DZqh4/zwjgXQpzC7YO/PXTcG8rI9r7xun8CohrHeNx8HZRkqWc61uJfIaZop9MwJEXPVssHw== dependencies: "@types/lodash" "*" -"@types/lodash.get@^4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@types/lodash.get/-/lodash.get-4.4.4.tgz#34b67841594e4ddc8853341d65e971a38cb4e2f0" - integrity sha512-6igkhtKoWAEvTWCgd5uubiuxXLUY/kljQOQZV1G5Y7SrivpmCU+NWG5tGLgRBkccobrDljbJYzBM2vgCG4Oc8Q== +"@types/lodash.get@^4.4.6": + version "4.4.6" + resolved "https://registry.yarnpkg.com/@types/lodash.get/-/lodash.get-4.4.6.tgz#0c7ac56243dae0f9f09ab6f75b29471e2e777240" + integrity sha512-E6zzjR3GtNig8UJG/yodBeJeIOtgPkMgsLjDU3CbgCAPC++vJ0eCMnJhVpRZb/ENqEFlov1+3K9TKtY4UdWKtQ== dependencies: "@types/lodash" "*" -"@types/lodash.groupby@^4.6.4": - version "4.6.4" - resolved "https://registry.yarnpkg.com/@types/lodash.groupby/-/lodash.groupby-4.6.4.tgz#4dbb730b0a8ad46915b7406d1e247cfd7a79b288" - integrity sha512-WvjZ0XtKdNUNGwY0udB3DiP3PC4mW6L3ESkJudA+LsgB2+LCUXUu3Pa+idzenLqBbMOcbBeAWPmEWSvyH0VWjw== +"@types/lodash.groupby@^4.6.6": + version "4.6.6" + resolved "https://registry.yarnpkg.com/@types/lodash.groupby/-/lodash.groupby-4.6.6.tgz#4d9b61a4d8b0d83d384975cabfed4c1769d6792e" + integrity sha512-kwg3T7Ia63KtDNoQQR8hKrLHCAgrH4I44l5uEMuA6JCbj7DiSccaV4tNV1vbjtAOpX990SolVthJCmBVtRVRgw== dependencies: "@types/lodash" "*" -"@types/lodash.head@^4.0.4": - version "4.0.4" - resolved "https://registry.yarnpkg.com/@types/lodash.head/-/lodash.head-4.0.4.tgz#1e788bad0435d31c06cbefe8781dc368be756d49" - integrity sha512-Tngbn0PblpxwEljuFWJ+CK1hp4GMDjtoJtQjpz0JLJ/7u4eNA3Wemu0e+mNPPqtlrFMoY8eFKEPQ5OX6/K5q6g== +"@types/lodash.head@^4.0.6": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@types/lodash.head/-/lodash.head-4.0.6.tgz#5c793cfc420799174541412f24c0b0dc69652de5" + integrity sha512-WrbllVqxpgpEnLPNooU9q3h9vFVYUzS4sMDp0UpEnguIHFxdAA161UMnaJ92ccGSc87e901EHFwXbuNysdSaQg== dependencies: "@types/lodash" "*" -"@types/lodash.isempty@^4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@types/lodash.isempty/-/lodash.isempty-4.4.4.tgz#0748feefeb1d639017bb76f155d7e402ca3e3cdf" - integrity sha512-d0pn1toi559K94bpy1/Huv82xZWhEHpump1nrhToZ+CDZizEZ9hBx8J+pT4aUbnBtyeGdEMyWqknQOIIie83NA== +"@types/lodash.isempty@^4.4.6": + version "4.4.6" + resolved "https://registry.yarnpkg.com/@types/lodash.isempty/-/lodash.isempty-4.4.6.tgz#48a5576985727d9b85d59a60199d6b11ac756a3e" + integrity sha512-AauKrFlA4z3Usog5HLGDupKzkCP7h5KXGlfAcRGUfvTmL7guVuEzDSNI6lYJ7syO7J2RE2F47179pSLr26UHIw== dependencies: "@types/lodash" "*" -"@types/lodash.isequal@^4.5.3": - version "4.5.3" - resolved "https://registry.yarnpkg.com/@types/lodash.isequal/-/lodash.isequal-4.5.3.tgz#b0d2747d3e76cc94da47ebd932c69a98c0ba61b4" - integrity sha512-tpTUmHksO2H9RF98Y2w7v06ZeEKAxHPo2ysL0bV5qv5UBweiZl33NFu5QYmYOSxSnHMqBt/vsVsBVeQAcJiokg== +"@types/lodash.isequal@^4.5.5": + version "4.5.5" + resolved "https://registry.yarnpkg.com/@types/lodash.isequal/-/lodash.isequal-4.5.5.tgz#4fed1b1b00bef79e305de0352d797e9bb816c8ff" + integrity sha512-4IKbinG7MGP131wRfceK6W4E/Qt3qssEFLF30LnJbjYiSfHGGRU/Io8YxXrZX109ir+iDETC8hw8QsDijukUVg== dependencies: "@types/lodash" "*" -"@types/lodash.isstring@^4.0.4": - version "4.0.4" - resolved "https://registry.yarnpkg.com/@types/lodash.isstring/-/lodash.isstring-4.0.4.tgz#d049735cd098c39227a9974068b61c874ac107bc" - integrity sha512-kdDz6h18L4H8Stbrm0S3uKADdtMJsq+7+AmqsMtZ5h0fi+6gSpphe8qQHJBeeGtz8EFzz+8pyIBLAxuvmCInww== +"@types/lodash.isstring@^4.0.6": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@types/lodash.isstring/-/lodash.isstring-4.0.6.tgz#1534d0c19a2ad79caa17558a298e366893ffd08c" + integrity sha512-uUGvF9G1G7jQ5H42Y38GA9rZmUoY8wI/OMSwnW0BZA+Ra0uxzpuQf4CixXl3yG3TvF6LjuduMyt1WvKl+je8QA== dependencies: "@types/lodash" "*" -"@types/lodash.orderby@^4.6.4": - version "4.6.4" - resolved "https://registry.yarnpkg.com/@types/lodash.orderby/-/lodash.orderby-4.6.4.tgz#adea1ffe3d5c33ae13b137ca1685f267a6b50f14" - integrity sha512-zOdkK4xTzEfwXH2ffwIMAJbZ2CeCKs6egg+xr/TWJttHAEccvEH/qX/mbRnTHWTqBZsXPHUpOHfXo2l2lMDKUQ== +"@types/lodash.orderby@^4.6.6": + version "4.6.6" + resolved "https://registry.yarnpkg.com/@types/lodash.orderby/-/lodash.orderby-4.6.6.tgz#126543bb597477dc9b27d748b5822244f577915c" + integrity sha512-wQzu6xK+bSwhu45OeMI7fjywiIZiiaBzJB8W3fwnF1SJXHoOXRLutrSnVmq4yHPOM036qsy8lx9wHQcAbXNjJw== dependencies: "@types/lodash" "*" -"@types/lodash.partition@^4.6.4": - version "4.6.4" - resolved "https://registry.yarnpkg.com/@types/lodash.partition/-/lodash.partition-4.6.4.tgz#55858a53778724cfc8a0feff05fa0b28e0757cd3" - integrity sha512-znM//f4B0PcPk13GZJB66kzZnw5MewcX3DIM4e1cQ+TmJbFUV/GUbDD/e8m08nbdUDiZlCOLm/1oOVe6s2SthQ== +"@types/lodash.partition@^4.6.6": + version "4.6.6" + resolved "https://registry.yarnpkg.com/@types/lodash.partition/-/lodash.partition-4.6.6.tgz#fdc23c9809b64b1d2e2f07faef045c035f0cd2c7" + integrity sha512-s8ZNNFWhBgTKI4uNxVrTs3Aa7UQoi7Fesw55bfpBBMCLda+uSuwDyuax8ka9aBy8Ccsjp2SiS034DkSZa+CzVA== dependencies: "@types/lodash" "*" -"@types/lodash.pick@^4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@types/lodash.pick/-/lodash.pick-4.4.4.tgz#381ac6c0a92f50405e2a6f9caeff07b0e40a9f75" - integrity sha512-54nf0ndJlN3ULz9fLceKhYJZfwf50jAqqPJyI3/UbU/qxyuMSgNC3niWhFqmVAnGPoxMwAwNQCYTXG0Gv2lfPg== +"@types/lodash.pick@^4.4.6": + version "4.4.6" + resolved "https://registry.yarnpkg.com/@types/lodash.pick/-/lodash.pick-4.4.6.tgz#ae4e8f109e982786313bb6aac4b1a73aefa6e9be" + integrity sha512-u8bzA16qQ+8dY280z3aK7PoWb3fzX5ATJ0rJB6F+uqchOX2VYF02Aqa+8aYiHiHgPzQiITqCgeimlyKFy4OA6g== dependencies: "@types/lodash" "*" -"@types/lodash.sample@^4.2.4": - version "4.2.4" - resolved "https://registry.yarnpkg.com/@types/lodash.sample/-/lodash.sample-4.2.4.tgz#d979476e1b5b05781adad5185027cd286bf4e963" - integrity sha512-6TFpHgqLDu8Dmpn6xfzzjKC/25leqA1sV3CJt7LbXrLCkbnaSGaax3WKacvllaS3FOdBocVLgXU/nVqdKERl9A== +"@types/lodash.sample@^4.2.6": + version "4.2.6" + resolved "https://registry.yarnpkg.com/@types/lodash.sample/-/lodash.sample-4.2.6.tgz#ec7f6a6dbd0401c4a1e5f5b3c85fa5a85a42a84a" + integrity sha512-hxBvsUjPcW1O8mC9TiBE4m8TwvLuUU+zW8J6GI1M6WmPg8J87mXGt7zavpJ/9Znb+0rVsSB3VNAjCFaJ9YUJKg== dependencies: "@types/lodash" "*" -"@types/lodash.set@^4.3.4": - version "4.3.4" - resolved "https://registry.yarnpkg.com/@types/lodash.set/-/lodash.set-4.3.4.tgz#ef11a971c7d3858e74fa6f745b4b69b2256f6c07" - integrity sha512-oY+y8V6Bg69q4U4eDhR7K177gE76I2Zb40OMHb+epTwo6RMGXeJpY7sKN7xrzvr1aXxPsfS50pvKVlcRq34JPQ== +"@types/lodash.set@^4.3.6": + version "4.3.6" + resolved "https://registry.yarnpkg.com/@types/lodash.set/-/lodash.set-4.3.6.tgz#33e635c2323f855359225df6a5c8c6f1f1908264" + integrity sha512-ZeGDDlnRYTvS31Laij0RsSaguIUSBTYIlJFKL3vm3T2OAZAQj2YpSvVWJc0WiG4jqg9fGX6PAPGvDqBcHfSgFg== dependencies: "@types/lodash" "*" -"@types/lodash.shuffle@^4.2.4": - version "4.2.4" - resolved "https://registry.yarnpkg.com/@types/lodash.shuffle/-/lodash.shuffle-4.2.4.tgz#3931aeafe65770c132e3a4061c833eaf5936c2b2" - integrity sha512-GnqZmVNNRDbDTzaFOf5TaumjlN+Nq83+kTSnU1EsTo6NtKlifBKU0oNM2wsL3BAxMWk4E6WVtzoBu+2Vg7RIjw== +"@types/lodash.shuffle@^4.2.6": + version "4.2.6" + resolved "https://registry.yarnpkg.com/@types/lodash.shuffle/-/lodash.shuffle-4.2.6.tgz#191b0fc66699214558352123811d1657d9ed8930" + integrity sha512-ucI9VswlV9jOZiIh43Nd0tJ4Z8pfXy3PbQ9cB6Re1gPds8gLbOdmB0l3UkVI2crZjnQB95bhyNZVEDH8DgglYA== dependencies: "@types/lodash" "*" -"@types/lodash.snakecase@^4.1.4": - version "4.1.4" - resolved "https://registry.yarnpkg.com/@types/lodash.snakecase/-/lodash.snakecase-4.1.4.tgz#58729021e111db6a4ff814d3ff16ac13ef7d0132" - integrity sha512-cC7ebPwSRw3hvBBfB6AV2Aja/XsIxL1HkwKjgDoQPZWjQlNtkkpFCGF7wxGaHMYsEaoUrnX1RE0FZW5Zzacr9Q== +"@types/lodash.snakecase@^4.1.6": + version "4.1.6" + resolved "https://registry.yarnpkg.com/@types/lodash.snakecase/-/lodash.snakecase-4.1.6.tgz#85f2b68f67b36c39875f26484b3a78b158ebf060" + integrity sha512-qGTf27ncTRUhSwvxD0hzYFmelmTrzEBGvBigrLyx6PRN1rKuy0ZEK+A3X3QnW7k+CwEjIJeAM6XBN4Ay6F03IQ== dependencies: "@types/lodash" "*" -"@types/lodash.sortby@^4.7.4": - version "4.7.4" - resolved "https://registry.yarnpkg.com/@types/lodash.sortby/-/lodash.sortby-4.7.4.tgz#14f9d45b6214b32cbe2f6332990b386d4b2dd09a" - integrity sha512-Byy/JXUl7VCKOjqk2XyOEa4kRp2UBuPPkdQpIwSi+54t3KDa1vkIRU+qFEoWZMLcMUbBq8+Iy8Ybri8AqFYLTA== +"@types/lodash.sortby@^4.7.6": + version "4.7.6" + resolved "https://registry.yarnpkg.com/@types/lodash.sortby/-/lodash.sortby-4.7.6.tgz#eed689835f274b553db4ae16a4a23f58b79618a1" + integrity sha512-EnvAOmKvEg7gdYpYrS6+fVFPw5dL9rBnJi3vcKI7wqWQcLJVF/KRXK9dH29HjGNVvFUj0s9prRP3J8jEGnGKDw== dependencies: "@types/lodash" "*" -"@types/lodash.sumby@^4.6.4": - version "4.6.4" - resolved "https://registry.yarnpkg.com/@types/lodash.sumby/-/lodash.sumby-4.6.4.tgz#169974c2e54a24ce3f27ee785f1969abe6d8e385" - integrity sha512-CY7N49UIPO7CdArz5Kj3IyQKpZbXcnP4tVqQgL6+qDsd9jmcukqEmyD4weyxBUxXH3EvEmIYoBQjA8loAi266Q== +"@types/lodash.sumby@^4.6.6": + version "4.6.6" + resolved "https://registry.yarnpkg.com/@types/lodash.sumby/-/lodash.sumby-4.6.6.tgz#25f9aba30bc9fd8203e43ebb3a3b6460f5f9b4b4" + integrity sha512-C7P2OuGcbCHxQ7dfEHNWfdQUhziwosJ8X6TzDZqIUVWamqFxUZX32JhoWZ/L5Z/lZX3HLTlQs1E5MtG0l9C9cA== dependencies: "@types/lodash" "*" -"@types/lodash.take@^4.1.4": - version "4.1.4" - resolved "https://registry.yarnpkg.com/@types/lodash.take/-/lodash.take-4.1.4.tgz#07e5670ac15501fcfccb4d0a4dd81ef87b239bb3" - integrity sha512-kGCIqpisGQs8x0dB5Usd6+7lL1pGgUThD9HlpJXS+xdTAE075HpqHBdl9YAezBKEWyx7F9qFR076eclld9QRhQ== +"@types/lodash.take@^4.1.6": + version "4.1.6" + resolved "https://registry.yarnpkg.com/@types/lodash.take/-/lodash.take-4.1.6.tgz#15f4911344d23647d660eceeee8e2c2679291bf5" + integrity sha512-KFENlBE7Dh81ndKQJf7jzwfRN5cDZIyxjn1MKubmu/MkcZtJaL8zh7szLFU6AuhxZL5PPS/wPeSfCwV+1G8pIA== dependencies: "@types/lodash" "*" -"@types/lodash.uniq@^4.5.4": - version "4.5.4" - resolved "https://registry.yarnpkg.com/@types/lodash.uniq/-/lodash.uniq-4.5.4.tgz#8dd571e4a68adddcd1bac810538e68f440e87403" - integrity sha512-q0FI7RCY99bUPBR7sJyfefWDa/KSD21pMWM1hi+2O+rJTzY2N4eRs+A6BwLotPNy/JOySfcZJYamZ8Owcs3SkQ== +"@types/lodash.uniq@^4.5.6": + version "4.5.6" + resolved "https://registry.yarnpkg.com/@types/lodash.uniq/-/lodash.uniq-4.5.6.tgz#adb052f6c7eeb38b920c13166e7a972dd960b4c5" + integrity sha512-XHNMXBtiwsWZstZMyxOYjr0e8YYWv0RgPlzIHblTuwBBiWo2MzWVaTBihtBpslb5BglgAWIeBv69qt1+RTRW1A== dependencies: "@types/lodash" "*" -"@types/lodash@*", "@types/lodash@^4.14.110", "@types/lodash@^4.14.120": - version "4.14.120" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.120.tgz#cf265d06f6c7a710db087ed07523ab8c1a24047b" - integrity sha512-jQ21kQ120mo+IrDs1nFNVm/AsdFxIx2+vZ347DbogHJPd/JzKNMOqU6HCYin1W6v8l5R9XSO2/e9cxmn7HAnVw== +"@types/lodash@*", "@types/lodash@^4.14.120": + version "4.14.122" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.122.tgz#3e31394c38cf1e5949fb54c1192cbc406f152c6c" + integrity sha512-9IdED8wU93ty8gP06ninox+42SBSJHp2IAamsSYMUY76mshRTeUsid/gtbl8ovnOwy8im41ib4cxTiIYMXGKew== "@types/log-symbols@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/log-symbols/-/log-symbols-2.0.0.tgz#7919e2ec3c8d13879bfdcab310dd7a3f7fc9466d" integrity sha512-YJhbp0sz3egFFKl3BcCNPQKzuGFOP4PACcsifhK6ROGnJUW9ViYLuLybQ9GQZm7Zejy3tkGuiXYMq3GiyGkU4g== -"@types/long@*", "@types/long@^4.0.0": +"@types/long@*": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.0.tgz#719551d2352d301ac8b81db732acb6bdc28dbdef" integrity sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q== -"@types/marked@^0.4.0": - version "0.4.2" - resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.4.2.tgz#64a89e53ea37f61cc0f3ee1732c555c2dbf6452f" - integrity sha512-cDB930/7MbzaGF6U3IwSQp6XBru8xWajF5PV2YZZeV8DyiliTuld11afVztGI9+yJZ29il5E+NpGA6ooV/Cjkg== - "@types/micromatch@^3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@types/micromatch/-/micromatch-3.1.0.tgz#514c8a3d24b2680a9b838eeb80e6d7d724545433" @@ -2180,11 +2296,6 @@ resolved "https://registry.yarnpkg.com/@types/mime-db/-/mime-db-1.27.0.tgz#9bc014a1fd1fdf47649c1a54c6dd7966b8284792" integrity sha1-m8AUof0f30dknBpUxt15ZrgoR5I= -"@types/mime@*": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d" - integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw== - "@types/mimos@*": version "3.0.1" resolved "https://registry.yarnpkg.com/@types/mimos/-/mimos-3.0.1.tgz#59d96abe1c9e487e7463fe41e8d86d76b57a441a" @@ -2192,15 +2303,15 @@ dependencies: "@types/mime-db" "*" -"@types/minimatch@*", "@types/minimatch@3.0.3": +"@types/minimatch@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== "@types/mongodb@*": - version "3.1.19" - resolved "https://registry.yarnpkg.com/@types/mongodb/-/mongodb-3.1.19.tgz#40c4310986888975771857810aa47fd3ea8e1acf" - integrity sha512-H54hQEovAhyLrIZOhPNfGyCCDoTqKsjb8GQBy8nptJqfxrYCp5WVcPJf9v0kfTPR72xOhaz9+WcYxOXWwEg1Vg== + version "3.1.21" + resolved "https://registry.yarnpkg.com/@types/mongodb/-/mongodb-3.1.21.tgz#8e40f5ec8de4950f03f14b5e20bb3f174b663cec" + integrity sha512-V1W/OFjUdz8xeVetg37eF1tpC+4y60YAn5pgB1pGW58oxNwNw/gU/k7EaUFXTVUTwKS6Ot6Ui6xEm60ZD4zCAg== dependencies: "@types/bson" "*" "@types/node" "*" @@ -2212,11 +2323,6 @@ dependencies: "@types/node" "*" -"@types/node-emoji@^1.8.1": - version "1.8.1" - resolved "https://registry.yarnpkg.com/@types/node-emoji/-/node-emoji-1.8.1.tgz#689cb74fdf6e84309bcafce93a135dfecd01de3f" - integrity sha512-0fRfA90FWm6KJfw6P9QGyo0HDTCmthZ7cWaBQndITlaWLTZ6njRyKwrwpzpg+n6kBXBIGKeUHEQuBx7bphGJkA== - "@types/node-forge@^0.7.11": version "0.7.11" resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-0.7.11.tgz#6b25860d54a0e2e37a53b97e2554f99fbcc7e8bb" @@ -2224,27 +2330,22 @@ dependencies: "@types/node" "*" -"@types/node@*": - version "11.9.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-11.9.0.tgz#35fea17653490dab82e1d5e69731abfdbf13160d" - integrity sha512-ry4DOrC+xenhQbzk1iIPzCZGhhPGEFv7ia7Iu6XXSLVluiJIe9FfG7Iu3mObH9mpxEXCWLCMU4JWbCCR9Oy1Zg== - -"@types/node@^10.1.0", "@types/node@^10.12.17": - version "10.12.25" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.25.tgz#0d01a7dd6127de60d861ece4a650963042abb538" - integrity sha512-IcvnGLGSQFDvC07Bz2I8SX+QKErDZbUdiQq7S2u3XyzTyJfUmT0sWJMbeQkMzpTAkO7/N7sZpW/arUM2jfKsbQ== +"@types/node@*", "@types/node@^11.10.5": + version "11.10.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-11.10.5.tgz#fbaca34086bdc118011e1f05c47688d432f2d571" + integrity sha512-DuIRlQbX4K+d5I+GMnv+UfnGh+ist0RdlvOp+JZ7ePJ6KQONCFQv/gKYSU1ZzbVdFSUCKZOltjmpFAGGv5MdYA== "@types/node@^8.0.7": - version "8.10.40" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.40.tgz#4314888d5cd537945d73e9ce165c04cc550144a4" - integrity sha512-RRSjdwz63kS4u7edIwJUn8NqKLLQ6LyqF/X4+4jp38MBT3Vwetewi2N4dgJEshLbDwNgOJXNYoOwzVZUSSLhkQ== + version "8.10.43" + resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.43.tgz#8d3281a33c92a56038b05d9460a65bc1dcd5735b" + integrity sha512-5m5W13HR2k3cu88mpzlnPBBv5+GyMHtj4F0P83RG4mqoC0AYVYHVMHfF3SgwKNtqEZiZQASMxU92QsLEekKcnw== -"@types/ora@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/ora/-/ora-3.0.0.tgz#fd30d5414e00fc4608cc7e18b565dceb57f4f66c" - integrity sha512-AOY050qkT67vf17bQFyHSjF615vs1yAeiHrpwh55BQCnpHi70LGJaLdrrl8YlvtAzDdXTWyYLs4kb6uQXiuHIg== +"@types/ora@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@types/ora/-/ora-3.2.0.tgz#b2f65d1283a8f36d8b0f9ee767e0732a2f429362" + integrity sha512-jll99xUKpiFbIFZSQcxm4numfsLaOWBzWNaRk3PvTSE7BPqTzzOCFmS0mQ7m8qkTfmYhuYbehTGsxkvRLPC++w== dependencies: - "@types/node" "*" + ora "*" "@types/otplib@^7.0.0": version "7.0.0" @@ -2267,17 +2368,17 @@ moment ">=2.14.0" "@types/pg@*": - version "7.4.11" - resolved "https://registry.yarnpkg.com/@types/pg/-/pg-7.4.11.tgz#dcc560e89c17d859c83c91ad3faf60bfeacaad6c" - integrity sha512-Eksj2yOBNHnNqLuU1AqwF1qXgdaYDcWVH1ZQlxS7k1i34+JZd/ZNd15ugIpTVQxmBBMqjliJstmrnusjXy08Tg== + version "7.4.13" + resolved "https://registry.yarnpkg.com/@types/pg/-/pg-7.4.13.tgz#4630494096bceee5ee562339502e3448c09703a8" + integrity sha512-jk/hFADnywyjnWXZ6IeNsAk2lZtKDInZWGs1ASxmLb3QBpwLDGcSvH5s6IIRlPqsXjwzu8btnNbQ5Cx+AX/CjA== dependencies: "@types/node" "*" "@types/pg-types" "*" -"@types/pino@^5.8.4": - version "5.8.4" - resolved "https://registry.yarnpkg.com/@types/pino/-/pino-5.8.4.tgz#8f7d4a7ce79f32533758847a1e636a2f3b35a677" - integrity sha512-HTW0JVyX6l9wZv4jhgQU2DGpGAFzyDJgVEOTZCyCkPEmO6lAKfMSqxQOuii3HRAADazpD7d6Oj93Lhonk0c0Mw== +"@types/pino@^5.8.6": + version "5.8.6" + resolved "https://registry.yarnpkg.com/@types/pino/-/pino-5.8.6.tgz#b356ac7bb607d9c489821752b47475bbcfd698b9" + integrity sha512-3hKjgaAXi8FALboVw1LC+3wiQN+bLZ/byzVgRI65XZxdVLSgjIl3kMVh2etKEdQ96qUgMd6bhNtzrUwh1e1x9g== dependencies: "@types/node" "*" "@types/sonic-boom" "*" @@ -2292,10 +2393,10 @@ resolved "https://registry.yarnpkg.com/@types/podium/-/podium-1.0.0.tgz#bfaa2151be2b1d6109cc69f7faa9dac2cba3bb20" integrity sha1-v6ohUb4rHWEJzGn3+qnawsujuyA= -"@types/prettier@^1.15.2": - version "1.16.0" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.16.0.tgz#ee6190019ed78c41a22c3d8a2e7ebc4015e2e022" - integrity sha512-ExfCqCyU+SPZIDB1I+CTolTv0kNAATsxLIRdl2z3YAdAjstf6f+0aJQBatCNn8LRdREpAX28GAjw4FlU89Ct1Q== +"@types/prettier@^1.16.1": + version "1.16.1" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.16.1.tgz#328d1c9b54402e44119398bcb6a31b7bbd606d59" + integrity sha512-db6pZL5QY3JrlCHBhYQzYDci0xnoDuxfseUuguLRr3JNk+bnCfpkK6p8quiUDyO8A0vbpBKkk59Fw125etrNeA== "@types/pretty-bytes@^5.1.0": version "5.1.0" @@ -2312,15 +2413,25 @@ resolved "https://registry.yarnpkg.com/@types/prompts/-/prompts-1.2.0.tgz#891e73f735ad5e82e8adae3a99424128e105fb62" integrity sha512-7JXpT2rSd4hqd2oBWU1wfEW6x6gX+qPH+gLzGEx+My3wcb67K9Rc02xNQRVn67phusmXm5Yqn4oTP2OW1G5zdQ== +"@types/pump@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/pump/-/pump-1.0.1.tgz#ae8157cefef04d1a4d24c1cc91d403c2f5da5cd0" + integrity sha1-roFXzv7wTRpNJMHMkdQDwvXaXNA= + dependencies: + "@types/node" "*" + "@types/random-seed@^0.3.3": version "0.3.3" resolved "https://registry.yarnpkg.com/@types/random-seed/-/random-seed-0.3.3.tgz#7741f7b0a4513198a9396ce4ad25832f799a6727" integrity sha512-kHsCbIRHNXJo6EN5W8EA5b4i1hdT6jaZke5crBPLUcLqaLdZ0QBq8QVMbafHzhjFF83Cl9qlee2dChD18d/kPg== -"@types/range-parser@*": - version "1.2.3" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" - integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== +"@types/readable-stream@^2.3.1": + version "2.3.1" + resolved "https://registry.yarnpkg.com/@types/readable-stream/-/readable-stream-2.3.1.tgz#59d458b51c84c585caea06e296e2225057c9ea8e" + integrity sha512-Dp6t95yGEOm2y669mQrSl0kUg+oL+bJEiCWMyDv0Yq+FcVvjzNRLTAqJks2LDBYYrazZXNI7lZXq3lp7MOvt4A== + dependencies: + "@types/node" "*" + safe-buffer "*" "@types/request-promise@^4.1.42": version "4.1.42" @@ -2360,42 +2471,16 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45" integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ== -"@types/sequelize@*": - version "4.27.35" - resolved "https://registry.yarnpkg.com/@types/sequelize/-/sequelize-4.27.35.tgz#a1888651e4297415237256a77dbd914220df00de" - integrity sha512-Ng6n+TLIIQOHb86WZAcL0YzwIBffuEdILjxZ/k749un2elW8j8EMvSo3G4BMdcwFXtgu5AsE2sBzey5yFSU9pg== +"@types/sequelize@*", "@types/sequelize@^4.27.39": + version "4.27.39" + resolved "https://registry.yarnpkg.com/@types/sequelize/-/sequelize-4.27.39.tgz#af5ac8f50a872ddfb52c625b3cd1c8fdcaf222a7" + integrity sha512-FABh5gLbXgh6/0pmJQ7VzjN5KAxd/IbX3G5Z6fSBZ6DSqaYl7DBP22nGtLh2e6dPmC0rUxfJclNhzQcC3XgvVw== dependencies: "@types/bluebird" "*" "@types/continuation-local-storage" "*" "@types/lodash" "*" "@types/validator" "*" -"@types/sequelize@^4.27.36": - version "4.27.36" - resolved "https://registry.yarnpkg.com/@types/sequelize/-/sequelize-4.27.36.tgz#ebeeb9e8a92b9e01ba5dbea87100e906aaaaf362" - integrity sha512-iXGpgtdIz3y7jQkj43TG826mpRrLUoeOuF+ykHOUzjI+lXY8DHaN578rzw4NeOqy4fU32y2AokAC0fWQXenStQ== - dependencies: - "@types/bluebird" "*" - "@types/continuation-local-storage" "*" - "@types/lodash" "*" - "@types/validator" "*" - -"@types/serve-static@*": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.2.tgz#f5ac4d7a6420a99a6a45af4719f4dcd8cd907a48" - integrity sha512-/BZ4QRLpH/bNYgZgwhKEh+5AsboDBcUdlBYgzoLX0fpj3Y2gp6EApyOlM3bK53wQS/OE1SrdSYBAbux2D1528Q== - dependencies: - "@types/express-serve-static-core" "*" - "@types/mime" "*" - -"@types/shelljs@^0.8.0": - version "0.8.2" - resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.8.2.tgz#6fa9314497ec7b61c28a21485bfa915e64894751" - integrity sha512-vVp7BCQn0yUQgpiohrdxAhHdm/bTlXshB4HG3LEBq1PgvjKiyeYHohIPIv0QBt/jipb140iMS5Xy1iR6qKovKw== - dependencies: - "@types/glob" "*" - "@types/node" "*" - "@types/shot@*": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/shot/-/shot-4.0.0.tgz#7545500c489b65c69b5bc5446ba4fef3bd26af92" @@ -2403,19 +2488,25 @@ dependencies: "@types/node" "*" -"@types/sonic-boom@*": - version "0.6.1" - resolved "https://registry.yarnpkg.com/@types/sonic-boom/-/sonic-boom-0.6.1.tgz#530d17e0b971c8f41cdfd78206171155aee58795" - integrity sha512-I0LVjE/VPehYvvMgmLZ8kSutqCaGzwDbyf74C5zoNwsb64KCppCJ7GkrLC4Sic3SzfEsGhcAVFpxR7UEpDi+bg== +"@types/sonic-boom@*": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@types/sonic-boom/-/sonic-boom-0.6.2.tgz#5f6c7bf6b4a0994f9339d778da6a7adcc3d37080" + integrity sha512-vP9Sn1tuz/BTh8L1o776Cbzr+WH4dZGmRXOjQ5L+IVQx40hUmvOS2wfIkqUsID1vL62tThWdlXWIqijwewu3mw== + dependencies: + "@types/node" "*" + +"@types/split2@^2.1.6": + version "2.1.6" + resolved "https://registry.yarnpkg.com/@types/split2/-/split2-2.1.6.tgz#b095c9e064853824b22c67993d99b066777402b1" + integrity sha512-ddaFSOMuy2Rp97l6q/LEteQygvTQJuEZ+SRhxFKR0uXGsdbFDqX/QF2xoGcOqLQ8XV91v01SnAv2vpgihNgW/Q== dependencies: "@types/node" "*" -"@types/sqlite3@^3.1.3": - version "3.1.3" - resolved "https://registry.yarnpkg.com/@types/sqlite3/-/sqlite3-3.1.3.tgz#580d547203b8ad6e11aa6a6769c8ca5d7e197d13" - integrity sha512-BgGToABnI/8/HnZtZz2Qac6DieU2Dm/j3rtbMmUlDVo4T/uLu8cuVfU/n2UkHowiiwXb6/7h/CmSqBIVKgcTMA== +"@types/sqlite3@^3.1.5": + version "3.1.5" + resolved "https://registry.yarnpkg.com/@types/sqlite3/-/sqlite3-3.1.5.tgz#c9bbc94b8e8ed0654e1f8bc98716aba6030e9533" + integrity sha512-upsrd1zEYMa4Y+prurQ+vpo5SN63BUF6tOjeTv3ziF+9W9PHVh4/S5cy0qAqkHvmOEm/AZhEKd7V/0bR2udmFw== dependencies: - "@types/events" "*" "@types/node" "*" "@types/stack-trace@0.0.29": @@ -2423,10 +2514,10 @@ resolved "https://registry.yarnpkg.com/@types/stack-trace/-/stack-trace-0.0.29.tgz#eb7a7c60098edb35630ed900742a5ecb20cfcb4d" integrity sha512-TgfOX+mGY/NyNxJLIbDWrO9DjGoVSW9+aB8H2yy1fy32jsvxijhmyJI9fDFgvz3YP4lvJaq9DzdR/M1bOgVc9g== -"@types/tail@^1.2.0": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@types/tail/-/tail-1.2.1.tgz#d42d582558f9e138c23d8194216ae3a7f64b34bf" - integrity sha512-v7z6nE6QS634ugD9QZofcPXADgbpB/Wd+hIVt14UrN267/UzimHhnQ8tfitxdosOYycTzgFXP2s+XdZTo2d7/w== +"@types/stack-utils@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" + integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== "@types/tapable@*": version "1.0.4" @@ -2466,12 +2557,13 @@ resolved "https://registry.yarnpkg.com/@types/validator/-/validator-10.9.0.tgz#747f36c7ad281da769458ab4c3b8837aee1578b6" integrity sha512-mf0VpXk+NoTmkUmuJCsdwBYxjYZW41amCSzd4t/fABMKl+qGMViwFP0pR7ukFdZRXWI1LIkca3VIbXVBmWZ4kQ== -"@types/vision@^5.3.5": - version "5.3.5" - resolved "https://registry.yarnpkg.com/@types/vision/-/vision-5.3.5.tgz#10cd8a155f58d6ceb65c8b889b5aca8ca80ea57b" - integrity sha512-CbGBNPBUSjyLW1bsC42mgvDffGtkHXzKIB69hGgCGwkoE90YS5DH1N8E4v+LXf/0LDepj/oc0IT3IDdnTQOOGQ== +"@types/vision@^5.3.6": + version "5.3.6" + resolved "https://registry.yarnpkg.com/@types/vision/-/vision-5.3.6.tgz#d696d007ca82254b955fd485bfae0f738d8ae617" + integrity sha512-Wo+9LNI9wGcHRShuZW+lbwwj+g6+dyTxtHtNpzfYl4iehYzvFVzGyNo1MgW4qTHQCFEYOirLHBlrutkIRE2TMw== dependencies: "@types/hapi" "*" + handlebars "^4.1.0" "@types/webpack-merge@^4.1.3": version "4.1.3" @@ -2487,10 +2579,10 @@ dependencies: "@types/webpack" "*" -"@types/webpack@*", "@types/webpack@^4.4.23": - version "4.4.24" - resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.4.24.tgz#75bc301176066f566ec54151b6101c2b45abb8b2" - integrity sha512-yg99CjvB7xZ/iuHrsZ7dkGKoq/FRDzqLzAxKh2EmTem6FWjzrty4FqCqBYuX5z+MFwSaaQGDAX4Q9HQkLjGLnQ== +"@types/webpack@*", "@types/webpack@^4.4.25": + version "4.4.25" + resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.4.25.tgz#c8a1eb968a33a3e6da641f529c5add0d44d34809" + integrity sha512-YaYVbSK1bC3xiAWFLSgDQyVHdCTNq5cLlcx633basmrwSoUxJiv4SZ0SoT1uoF15zWx98afOcCbqA1YHeCdRYA== dependencies: "@types/anymatch" "*" "@types/node" "*" @@ -2505,166 +2597,171 @@ dependencies: "@types/node" "*" -"@types/ws@^6.0.0": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-6.0.1.tgz#ca7a3f3756aa12f62a0a62145ed14c6db25d5a28" - integrity sha512-EzH8k1gyZ4xih/MaZTXwT2xOkPiIMSrhQ9b8wrlX88L0T02eYsddatQlwVFlEPyEqV0ChpdpNnE51QPH6NVT4Q== +"@types/yargs@^12.0.2", "@types/yargs@^12.0.9": + version "12.0.9" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.9.tgz#693e76a52f61a2f1e7fb48c0eef167b95ea4ffd0" + integrity sha512-sCZy4SxP9rN2w30Hlmg5dtdRwgYQfYRiLo9usw8X9cxlf+H4FqM1xX7+sNH7NNKVdbXMJWqva7iyy+fxh/V7fA== + +"@tyriar/fibonacci-heap@^2.0.7": + version "2.0.9" + resolved "https://registry.yarnpkg.com/@tyriar/fibonacci-heap/-/fibonacci-heap-2.0.9.tgz#df3dcbdb1b9182168601f6318366157ee16666e9" + integrity sha512-bYuSNomfn4hu2tPiDN+JZtnzCpSpbJ/PNeulmocDy3xN2X5OkJL65zo6rPZp65cPPhLF9vfT/dgE+RtFRCSxOA== + +"@webassemblyjs/ast@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359" + integrity sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ== dependencies: - "@types/events" "*" - "@types/node" "*" + "@webassemblyjs/helper-module-context" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/wast-parser" "1.8.5" + +"@webassemblyjs/floating-point-hex-parser@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz#1ba926a2923613edce496fd5b02e8ce8a5f49721" + integrity sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ== + +"@webassemblyjs/helper-api-error@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz#c49dad22f645227c5edb610bdb9697f1aab721f7" + integrity sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA== + +"@webassemblyjs/helper-buffer@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz#fea93e429863dd5e4338555f42292385a653f204" + integrity sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q== + +"@webassemblyjs/helper-code-frame@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz#9a740ff48e3faa3022b1dff54423df9aa293c25e" + integrity sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ== + dependencies: + "@webassemblyjs/wast-printer" "1.8.5" + +"@webassemblyjs/helper-fsm@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz#ba0b7d3b3f7e4733da6059c9332275d860702452" + integrity sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow== + +"@webassemblyjs/helper-module-context@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz#def4b9927b0101dc8cbbd8d1edb5b7b9c82eb245" + integrity sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g== + dependencies: + "@webassemblyjs/ast" "1.8.5" + mamacro "^0.0.3" + +"@webassemblyjs/helper-wasm-bytecode@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz#537a750eddf5c1e932f3744206551c91c1b93e61" + integrity sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ== -"@webassemblyjs/ast@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.7.11.tgz#b988582cafbb2b095e8b556526f30c90d057cace" - integrity sha512-ZEzy4vjvTzScC+SH8RBssQUawpaInUdMTYwYYLh54/s8TuT0gBLuyUnppKsVyZEi876VmmStKsUs28UxPgdvrA== - dependencies: - "@webassemblyjs/helper-module-context" "1.7.11" - "@webassemblyjs/helper-wasm-bytecode" "1.7.11" - "@webassemblyjs/wast-parser" "1.7.11" - -"@webassemblyjs/floating-point-hex-parser@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.7.11.tgz#a69f0af6502eb9a3c045555b1a6129d3d3f2e313" - integrity sha512-zY8dSNyYcgzNRNT666/zOoAyImshm3ycKdoLsyDw/Bwo6+/uktb7p4xyApuef1dwEBo/U/SYQzbGBvV+nru2Xg== - -"@webassemblyjs/helper-api-error@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.7.11.tgz#c7b6bb8105f84039511a2b39ce494f193818a32a" - integrity sha512-7r1qXLmiglC+wPNkGuXCvkmalyEstKVwcueZRP2GNC2PAvxbLYwLLPr14rcdJaE4UtHxQKfFkuDFuv91ipqvXg== - -"@webassemblyjs/helper-buffer@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.7.11.tgz#3122d48dcc6c9456ed982debe16c8f37101df39b" - integrity sha512-MynuervdylPPh3ix+mKZloTcL06P8tenNH3sx6s0qE8SLR6DdwnfgA7Hc9NSYeob2jrW5Vql6GVlsQzKQCa13w== - -"@webassemblyjs/helper-code-frame@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.7.11.tgz#cf8f106e746662a0da29bdef635fcd3d1248364b" - integrity sha512-T8ESC9KMXFTXA5urJcyor5cn6qWeZ4/zLPyWeEXZ03hj/x9weSokGNkVCdnhSabKGYWxElSdgJ+sFa9G/RdHNw== - dependencies: - "@webassemblyjs/wast-printer" "1.7.11" - -"@webassemblyjs/helper-fsm@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.7.11.tgz#df38882a624080d03f7503f93e3f17ac5ac01181" - integrity sha512-nsAQWNP1+8Z6tkzdYlXT0kxfa2Z1tRTARd8wYnc/e3Zv3VydVVnaeePgqUzFrpkGUyhUUxOl5ML7f1NuT+gC0A== - -"@webassemblyjs/helper-module-context@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.7.11.tgz#d874d722e51e62ac202476935d649c802fa0e209" - integrity sha512-JxfD5DX8Ygq4PvXDucq0M+sbUFA7BJAv/GGl9ITovqE+idGX+J3QSzJYz+LwQmL7fC3Rs+utvWoJxDb6pmC0qg== - -"@webassemblyjs/helper-wasm-bytecode@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.7.11.tgz#dd9a1e817f1c2eb105b4cf1013093cb9f3c9cb06" - integrity sha512-cMXeVS9rhoXsI9LLL4tJxBgVD/KMOKXuFqYb5oCJ/opScWpkCMEz9EJtkonaNcnLv2R3K5jIeS4TRj/drde1JQ== - -"@webassemblyjs/helper-wasm-section@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.7.11.tgz#9c9ac41ecf9fbcfffc96f6d2675e2de33811e68a" - integrity sha512-8ZRY5iZbZdtNFE5UFunB8mmBEAbSI3guwbrsCl4fWdfRiAcvqQpeqd5KHhSWLL5wuxo53zcaGZDBU64qgn4I4Q== - dependencies: - "@webassemblyjs/ast" "1.7.11" - "@webassemblyjs/helper-buffer" "1.7.11" - "@webassemblyjs/helper-wasm-bytecode" "1.7.11" - "@webassemblyjs/wasm-gen" "1.7.11" - -"@webassemblyjs/ieee754@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.7.11.tgz#c95839eb63757a31880aaec7b6512d4191ac640b" - integrity sha512-Mmqx/cS68K1tSrvRLtaV/Lp3NZWzXtOHUW2IvDvl2sihAwJh4ACE0eL6A8FvMyDG9abes3saB6dMimLOs+HMoQ== +"@webassemblyjs/helper-wasm-section@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz#74ca6a6bcbe19e50a3b6b462847e69503e6bfcbf" + integrity sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-buffer" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/wasm-gen" "1.8.5" + +"@webassemblyjs/ieee754@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz#712329dbef240f36bf57bd2f7b8fb9bf4154421e" + integrity sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g== dependencies: "@xtuc/ieee754" "^1.2.0" -"@webassemblyjs/leb128@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.7.11.tgz#d7267a1ee9c4594fd3f7e37298818ec65687db63" - integrity sha512-vuGmgZjjp3zjcerQg+JA+tGOncOnJLWVkt8Aze5eWQLwTQGNgVLcyOTqgSCxWTR4J42ijHbBxnuRaL1Rv7XMdw== - dependencies: - "@xtuc/long" "4.2.1" - -"@webassemblyjs/utf8@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.7.11.tgz#06d7218ea9fdc94a6793aa92208160db3d26ee82" - integrity sha512-C6GFkc7aErQIAH+BMrIdVSmW+6HSe20wg57HEC1uqJP8E/xpMjXqQUxkQw07MhNDSDcGpxI9G5JSNOQCqJk4sA== - -"@webassemblyjs/wasm-edit@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.7.11.tgz#8c74ca474d4f951d01dbae9bd70814ee22a82005" - integrity sha512-FUd97guNGsCZQgeTPKdgxJhBXkUbMTY6hFPf2Y4OedXd48H97J+sOY2Ltaq6WGVpIH8o/TGOVNiVz/SbpEMJGg== - dependencies: - "@webassemblyjs/ast" "1.7.11" - "@webassemblyjs/helper-buffer" "1.7.11" - "@webassemblyjs/helper-wasm-bytecode" "1.7.11" - "@webassemblyjs/helper-wasm-section" "1.7.11" - "@webassemblyjs/wasm-gen" "1.7.11" - "@webassemblyjs/wasm-opt" "1.7.11" - "@webassemblyjs/wasm-parser" "1.7.11" - "@webassemblyjs/wast-printer" "1.7.11" - -"@webassemblyjs/wasm-gen@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.7.11.tgz#9bbba942f22375686a6fb759afcd7ac9c45da1a8" - integrity sha512-U/KDYp7fgAZX5KPfq4NOupK/BmhDc5Kjy2GIqstMhvvdJRcER/kUsMThpWeRP8BMn4LXaKhSTggIJPOeYHwISA== - dependencies: - "@webassemblyjs/ast" "1.7.11" - "@webassemblyjs/helper-wasm-bytecode" "1.7.11" - "@webassemblyjs/ieee754" "1.7.11" - "@webassemblyjs/leb128" "1.7.11" - "@webassemblyjs/utf8" "1.7.11" - -"@webassemblyjs/wasm-opt@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.7.11.tgz#b331e8e7cef8f8e2f007d42c3a36a0580a7d6ca7" - integrity sha512-XynkOwQyiRidh0GLua7SkeHvAPXQV/RxsUeERILmAInZegApOUAIJfRuPYe2F7RcjOC9tW3Cb9juPvAC/sCqvg== - dependencies: - "@webassemblyjs/ast" "1.7.11" - "@webassemblyjs/helper-buffer" "1.7.11" - "@webassemblyjs/wasm-gen" "1.7.11" - "@webassemblyjs/wasm-parser" "1.7.11" - -"@webassemblyjs/wasm-parser@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.7.11.tgz#6e3d20fa6a3519f6b084ef9391ad58211efb0a1a" - integrity sha512-6lmXRTrrZjYD8Ng8xRyvyXQJYUQKYSXhJqXOBLw24rdiXsHAOlvw5PhesjdcaMadU/pyPQOJ5dHreMjBxwnQKg== - dependencies: - "@webassemblyjs/ast" "1.7.11" - "@webassemblyjs/helper-api-error" "1.7.11" - "@webassemblyjs/helper-wasm-bytecode" "1.7.11" - "@webassemblyjs/ieee754" "1.7.11" - "@webassemblyjs/leb128" "1.7.11" - "@webassemblyjs/utf8" "1.7.11" - -"@webassemblyjs/wast-parser@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.7.11.tgz#25bd117562ca8c002720ff8116ef9072d9ca869c" - integrity sha512-lEyVCg2np15tS+dm7+JJTNhNWq9yTZvi3qEhAIIOaofcYlUp0UR5/tVqOwa/gXYr3gjwSZqw+/lS9dscyLelbQ== - dependencies: - "@webassemblyjs/ast" "1.7.11" - "@webassemblyjs/floating-point-hex-parser" "1.7.11" - "@webassemblyjs/helper-api-error" "1.7.11" - "@webassemblyjs/helper-code-frame" "1.7.11" - "@webassemblyjs/helper-fsm" "1.7.11" - "@xtuc/long" "4.2.1" - -"@webassemblyjs/wast-printer@1.7.11": - version "1.7.11" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.7.11.tgz#c4245b6de242cb50a2cc950174fdbf65c78d7813" - integrity sha512-m5vkAsuJ32QpkdkDOUPGSltrg8Cuk3KBx4YrmAGQwCZPRdUHXxG4phIOuuycLemHFr74sWL9Wthqss4fzdzSwg== - dependencies: - "@webassemblyjs/ast" "1.7.11" - "@webassemblyjs/wast-parser" "1.7.11" - "@xtuc/long" "4.2.1" +"@webassemblyjs/leb128@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.8.5.tgz#044edeb34ea679f3e04cd4fd9824d5e35767ae10" + integrity sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.8.5.tgz#a8bf3b5d8ffe986c7c1e373ccbdc2a0915f0cedc" + integrity sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw== + +"@webassemblyjs/wasm-edit@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz#962da12aa5acc1c131c81c4232991c82ce56e01a" + integrity sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-buffer" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/helper-wasm-section" "1.8.5" + "@webassemblyjs/wasm-gen" "1.8.5" + "@webassemblyjs/wasm-opt" "1.8.5" + "@webassemblyjs/wasm-parser" "1.8.5" + "@webassemblyjs/wast-printer" "1.8.5" + +"@webassemblyjs/wasm-gen@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz#54840766c2c1002eb64ed1abe720aded714f98bc" + integrity sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/ieee754" "1.8.5" + "@webassemblyjs/leb128" "1.8.5" + "@webassemblyjs/utf8" "1.8.5" + +"@webassemblyjs/wasm-opt@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz#b24d9f6ba50394af1349f510afa8ffcb8a63d264" + integrity sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-buffer" "1.8.5" + "@webassemblyjs/wasm-gen" "1.8.5" + "@webassemblyjs/wasm-parser" "1.8.5" + +"@webassemblyjs/wasm-parser@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz#21576f0ec88b91427357b8536383668ef7c66b8d" + integrity sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-api-error" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/ieee754" "1.8.5" + "@webassemblyjs/leb128" "1.8.5" + "@webassemblyjs/utf8" "1.8.5" + +"@webassemblyjs/wast-parser@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz#e10eecd542d0e7bd394f6827c49f3df6d4eefb8c" + integrity sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/floating-point-hex-parser" "1.8.5" + "@webassemblyjs/helper-api-error" "1.8.5" + "@webassemblyjs/helper-code-frame" "1.8.5" + "@webassemblyjs/helper-fsm" "1.8.5" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/wast-printer@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz#114bbc481fd10ca0e23b3560fa812748b0bae5bc" + integrity sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/wast-parser" "1.8.5" + "@xtuc/long" "4.2.2" "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== -"@xtuc/long@4.2.1": - version "4.2.1" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.1.tgz#5c85d662f76fa1d34575766c5dcd6615abcd30d8" - integrity sha512-FZdkNBDqBRHKQ2MEbSC17xnPFOhZxeJ2YGSfr2BKf3sujG49Qe3bB+rGCwQfIaA7WHnGeGkSijX4FuBCdrzW/g== +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== "@yarnpkg/lockfile@^1.0.2": version "1.1.0" @@ -2689,12 +2786,7 @@ abbrev@1, abbrev@^1.1.1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -abbrev@~1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" - integrity sha1-kbR5JYinc4wl813W9jdSovh3YTU= - -accept@3.x.x, accept@^3.0.2: +accept@3.x.x: version "3.1.3" resolved "https://registry.yarnpkg.com/accept/-/accept-3.1.3.tgz#29c3e2b3a8f4eedbc2b690e472b9ebbdc7385e87" integrity sha512-OgOEAidVEOKPup+Gv2+2wdH2AgVKI9LxsJ4hicdJ6cY0faUuZdZoi56kkXWlHp9qicN1nWQLmW5ZRGk+SBS5xg== @@ -2702,14 +2794,6 @@ accept@3.x.x, accept@^3.0.2: boom "7.x.x" hoek "6.x.x" -accepts@~1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" - integrity sha1-63d99gEXI6OxTopywIBcjoZ0a9I= - dependencies: - mime-types "~2.1.18" - negotiator "0.6.1" - acorn-dynamic-import@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz#482210140582a36b83c3e342e1cfebcaa9240948" @@ -2734,9 +2818,9 @@ acorn@^5.5.3: integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== acorn@^6.0.1, acorn@^6.0.5: - version "6.1.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.0.tgz#b0a3be31752c97a0f7013c5f4903b71a05db6818" - integrity sha512-MW/FjM+IvU9CgBzjO3UIPCE2pyEwUsoFl+VGdczOPEdxfGFjuKny/gN54mOuX7Qxmb9Rg9MCn2oKiSUeW+pjrw== + version "6.1.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f" + integrity sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA== agent-base@4, agent-base@^4.1.0, agent-base@^4.2.0, agent-base@~4.2.0: version "4.2.1" @@ -2760,20 +2844,25 @@ aggregate-error@^1.0.0: clean-stack "^1.0.0" indent-string "^3.0.0" +airbrake-js@^1.6.6: + version "1.6.6" + resolved "https://registry.yarnpkg.com/airbrake-js/-/airbrake-js-1.6.6.tgz#9693c17a45883f4b440c89df0cc7a3a22ef1c223" + integrity sha512-2oIdVjNarNJ2/hT3f5gv2ZDSOGKm9WKl1fQRfQfhBEy9fWDGu/jrtQxaSO2EvjBdw4CvpfnK8YzRgsRwqDaXNg== + ajv-errors@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== -ajv-keywords@^3.1.0: +ajv-keywords@^3.1.0, ajv-keywords@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.0.tgz#4b831e7b531415a7cc518cd404e73f6193c6349d" integrity sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw== -ajv@^6.1.0, ajv@^6.5.5, ajv@^6.9.1: - version "6.9.1" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.9.1.tgz#a4d3683d74abc5670e75f0b16520f70a20ea8dc1" - integrity sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA== +ajv@^6.1.0, ajv@^6.10.0, ajv@^6.5.5: + version "6.10.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1" + integrity sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg== dependencies: fast-deep-equal "^2.0.1" fast-json-stable-stringify "^2.0.0" @@ -2818,9 +2907,9 @@ ansi-regex@^3.0.0: integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= ansi-regex@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.0.0.tgz#70de791edf021404c3fd615aa89118ae0432e5a9" - integrity sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w== + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== ansi-styles@^2.2.1: version "2.2.1" @@ -2839,16 +2928,16 @@ ansicolors@^0.3.2, ansicolors@~0.3.2: resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" integrity sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk= -ansistyles@~0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/ansistyles/-/ansistyles-0.1.3.tgz#5de60415bda071bb37127854c864f41b23254539" - integrity sha1-XeYEFb2gcbs3EnhUyGT0GyMlRTk= - any-observable@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.3.0.tgz#af933475e5806a67d0d7df090dd5e8bef65d119b" integrity sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog== +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= + anymatch@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" @@ -2857,142 +2946,6 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" -apollo-cache-control@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/apollo-cache-control/-/apollo-cache-control-0.5.0.tgz#78cfefec57c5c0a9648137e3250b91172998061e" - integrity sha512-zu26CFj7CboxLB6cckZQEiSUGXIr8MViEGIC5Vesz2yd37sjtevMfRwQhxFuK0HinR0T/WC3dz2k5cj+33vQQQ== - dependencies: - apollo-server-env "2.2.0" - graphql-extensions "0.5.0" - -apollo-datasource@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/apollo-datasource/-/apollo-datasource-0.3.0.tgz#88c680edd63a2f7ea05fc25da290a430632d39f4" - integrity sha512-+jWs3ezhx4lcAAPIHtlj0Zoiv2tvwfzn7feHuhxub3xFwkJm39T8hPjb3aMQCsuS7TukBD+F5ndgVob5hL/5Nw== - dependencies: - apollo-server-caching "0.3.0" - apollo-server-env "2.2.0" - -apollo-engine-reporting-protobuf@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.2.0.tgz#2aaf4d2eddefe7924d469cf1135267bc0deadf73" - integrity sha512-qI+GJKN78UMJ9Aq/ORdiM2qymZ5yswem+/VDdVFocq+/e1QqxjnpKjQWISkswci5+WtpJl9SpHBNxG98uHDKkA== - dependencies: - protobufjs "^6.8.6" - -apollo-engine-reporting@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.0.0.tgz#af93b755482ad5a6c52f9f728f3e24e801aa2385" - integrity sha512-9gZSME9ggZwL1nBBvfgSehwc+PtcvZC1/3NYrBOFgidJbrEFita2w5A0jM8Brjo+N2FMKNYWGj8WQ1ywO21JPg== - dependencies: - apollo-engine-reporting-protobuf "0.2.0" - apollo-graphql "0.1.0" - apollo-server-core "2.4.0" - apollo-server-env "2.2.0" - async-retry "^1.2.1" - graphql-extensions "0.5.0" - -apollo-env@0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/apollo-env/-/apollo-env-0.3.2.tgz#7cd9abd7a65cb0e00c4cfc057a999864f6157b55" - integrity sha512-r6nrOw5Pyk6YLNKEtvBiTguJK+oPI1sthKogd7tp6jfkJ+q8SR/9sOoTxyV3vsmR/mMENuBHF89BGLLo9rxDiA== - dependencies: - core-js "3.0.0-beta.3" - node-fetch "^2.2.0" - -apollo-graphql@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/apollo-graphql/-/apollo-graphql-0.1.0.tgz#e453b133daa8b686644da05ec8e556cf31c57037" - integrity sha512-Mi5GqZJz1A/0i8SEm9EVHWe/LkGbYzV5wzobUY+1Q0SI1NdFtRgqHZUdHU0hz1jDnL+dpRqK1huVmtOO/DGa/A== - dependencies: - lodash.sortby "^4.7.0" - -apollo-link@^1.2.3: - version "1.2.8" - resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.8.tgz#0f252adefd5047ac1a9f35ba9439d216587dcd84" - integrity sha512-lfzGRxhK9RmiH3HPFi7TIEBhhDY9M5a2ZDnllcfy5QDk7cCQHQ1WQArcw1FK0g1B+mV4Kl72DSrlvZHZJEolrA== - dependencies: - zen-observable-ts "^0.8.15" - -apollo-server-caching@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/apollo-server-caching/-/apollo-server-caching-0.3.0.tgz#96669b5e3bf693e6a54683edfab4b9495deb17d8" - integrity sha512-dHwWUsRZu7I1yUfzTwPJgOigMsftgp8w3X96Zdch1ICWN7cM6aNxks9tTnLd+liUSEzdYLlTmEy5VUturF2IAw== - dependencies: - lru-cache "^5.0.0" - -apollo-server-core@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.4.0.tgz#928c563f68d9f69a4288e7a4bf14ca79f8599f2b" - integrity sha512-rTFJa12NzTWC9IJrXDr8AZMs1Slbes9YxbyaI+cMC5fs8O9wkCkb34C/1Tp7xKX0fgauXrKZpXv7yPTSm+4YFg== - dependencies: - "@apollographql/apollo-tools" "^0.3.0" - "@apollographql/graphql-playground-html" "^1.6.6" - "@types/ws" "^6.0.0" - apollo-cache-control "0.5.0" - apollo-datasource "0.3.0" - apollo-engine-reporting "1.0.0" - apollo-server-caching "0.3.0" - apollo-server-env "2.2.0" - apollo-server-errors "2.2.0" - apollo-server-plugin-base "0.3.0" - apollo-tracing "0.5.0" - fast-json-stable-stringify "^2.0.0" - graphql-extensions "0.5.0" - graphql-subscriptions "^1.0.0" - graphql-tag "^2.9.2" - graphql-tools "^4.0.0" - graphql-upload "^8.0.2" - lodash "^4.17.10" - subscriptions-transport-ws "^0.9.11" - ws "^6.0.0" - -apollo-server-env@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/apollo-server-env/-/apollo-server-env-2.2.0.tgz#5eec5dbf46581f663fd6692b2e05c7e8ae6d6034" - integrity sha512-wjJiI5nQWPBpNmpiLP389Ezpstp71szS6DHAeTgYLb/ulCw3CTuuA+0/E1bsThVWiQaDeHZE0sE3yI8q2zrYiA== - dependencies: - node-fetch "^2.1.2" - util.promisify "^1.0.0" - -apollo-server-errors@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-2.2.0.tgz#5b452a1d6ff76440eb0f127511dc58031a8f3cb5" - integrity sha512-gV9EZG2tovFtT1cLuCTavnJu2DaKxnXPRNGSTo+SDI6IAk6cdzyW0Gje5N2+3LybI0Wq5KAbW6VLei31S4MWmg== - -apollo-server-hapi@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/apollo-server-hapi/-/apollo-server-hapi-2.4.0.tgz#bc7b96e51c0f12801468874e1c4ec9d71211e54c" - integrity sha512-f/TMnqf/cXRDuWuWtlku2ykfn/flV6iNVQAg2npLKoy7OIpMYb0NSzd3R/00rTcGtX1hYugDU3tCb35Jp6AGOQ== - dependencies: - "@apollographql/graphql-playground-html" "^1.6.6" - accept "^3.0.2" - apollo-server-core "2.4.0" - boom "^7.1.0" - graphql-subscriptions "^1.0.0" - graphql-tools "^4.0.0" - -apollo-server-plugin-base@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-0.3.0.tgz#26c7f9c020b2d444de5bebd2e3c406bebd9c302e" - integrity sha512-SOwp4cpZwyklvP1MkMcY6+12c1hrb5gwC4vK4a23kL5rr9FC0sENcXo3uVVM4XlDGOXIkY+sCM8ngKFuef2flw== - -apollo-tracing@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/apollo-tracing/-/apollo-tracing-0.5.0.tgz#6eebaf1e840ece99299103def689d39fe49780a3" - integrity sha512-j0icEhLYf0xS6Q/iCXA2j9KfpYw0a/XvLSUio7fm5yUwtXP2Pp11x5BtK1dI8sLMiaOqUrREz2XjV4PKLzQPuA== - dependencies: - apollo-server-env "2.2.0" - graphql-extensions "0.5.0" - -apollo-utilities@^1.0.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.1.2.tgz#aa5eca9d1f1eb721c381a22e0dde03559d856db3" - integrity sha512-EjDx8vToK+zkWIxc76ZQY/irRX52puNg04xf/w8R0kVTDAgHuVfnFVC01O5vE25kFnIaa5em0pFI0p9b6YMkhQ== - dependencies: - fast-json-stable-stringify "^2.0.0" - tslib "^1.9.3" - append-transform@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-0.4.0.tgz#d76ebf8ca94d276e247a36bad44a4b74ab611991" @@ -3000,6 +2953,13 @@ append-transform@^0.4.0: dependencies: default-require-extensions "^1.0.0" +append-transform@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-1.0.0.tgz#046a52ae582a228bd72f58acfbe2967c678759ab" + integrity sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw== + dependencies: + default-require-extensions "^2.0.0" + aproba@^1.0.3, aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -3010,17 +2970,12 @@ aproba@^2.0.0: resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== -aproba@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.0.4.tgz#2713680775e7614c8ba186c065d4e2e52d1072c0" - integrity sha1-JxNoB3XnYUyLoYbAZdTi5S0QcsA= - arch@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/arch/-/arch-2.1.1.tgz#8f5c2731aa35a30929221bb0640eed65175ec84e" integrity sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg== -archy@^1.0.0, archy@~1.0.0: +archy@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA= @@ -3049,14 +3004,14 @@ argparse@^1.0.7: sprintf-js "~1.0.2" args@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/args/-/args-5.0.0.tgz#8a3e376f28550f9fbdfefcb097179f2f75848efe" - integrity sha512-eCZo33yLdQ3DiG/Ko5n11uPonyYofYd9F2cqWID8TKGZwK/Z2ZcUj/oZ1HNMeNL2lgraPnv3JBZumfbUMqmZtg== + version "5.0.1" + resolved "https://registry.yarnpkg.com/args/-/args-5.0.1.tgz#4bf298df90a4799a09521362c579278cc2fdd761" + integrity sha512-1kqmFCFsPffavQFGt8OxJdIcETti99kySRUPMpOhaGjL6mRJn8HFU1OxKY5bMqfZKUwTQc1mZkAjmGYaVOHFtQ== dependencies: camelcase "5.0.0" - chalk "2.4.1" + chalk "2.4.2" leven "2.1.0" - mri "1.1.1" + mri "1.1.4" argv@^0.0.2: version "0.0.2" @@ -3100,25 +3055,12 @@ array-find-index@^1.0.1: resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= - array-ify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4= -array-index@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-index/-/array-index-1.0.0.tgz#ec56a749ee103e4e08c790b9c353df16055b97f9" - integrity sha1-7FanSe4QPk4Ix5C5w1PfFgVbl/k= - dependencies: - debug "^2.2.0" - es6-symbol "^3.0.2" - -array-union@^1.0.1: +array-union@^1.0.1, array-union@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= @@ -3150,7 +3092,7 @@ arrify@^1.0.0, arrify@^1.0.1: resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= -asap@^2.0.0, asap@~2.0.3, asap@~2.0.5: +asap@^2.0.0, asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= @@ -3171,16 +3113,16 @@ asn1@~0.2.3: dependencies: safer-buffer "~2.1.0" +assert-options@0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/assert-options/-/assert-options-0.1.3.tgz#ea56c6a67a558eefb9db52c394f2fa92b94bfcde" + integrity sha512-DXrZ5WkCv/igD+H8OmeUTl9k0pBhYSTdyA7DRZoSJERCzQ8Z2v85yDjkhYVnHUOeCXGfCNKaogRbLWQsIQbtpg== + assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= -assert-plus@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" - integrity sha1-104bh+ev/A24qttwIfP+SBAasjQ= - assert@^1.1.1: version "1.4.1" resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" @@ -3188,6 +3130,11 @@ assert@^1.1.1: dependencies: util "0.10.3" +assertion-error@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" + integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== + assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" @@ -3213,32 +3160,23 @@ async-limiter@~1.0.0: resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg== -async-retry@^1.2.1: - version "1.2.3" - resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.2.3.tgz#a6521f338358d322b1a0012b79030c6f411d1ce0" - integrity sha512-tfDb02Th6CE6pJUF2gjW5ZVjsgwlucVXOEQMvEX9JgSJMs9gAX+Nz3xRuJBKuUYjTSYORqvDBORdAQ3LU59g7Q== - dependencies: - retry "0.12.0" - -async@^1.4.0, async@~1.5: +async@^1.4.0: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= -async@^2.1.4, async@^2.5.0, async@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" - integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ== - dependencies: - lodash "^4.17.10" - -async@^2.6.2: +async@^2.1.4, async@^2.5.0, async@^2.6.1, async@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/async/-/async-2.6.2.tgz#18330ea7e6e313887f5d2f2a904bac6fe4dd5381" integrity sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg== dependencies: lodash "^4.17.11" +async@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/async/-/async-1.2.1.tgz#a4816a17cd5ff516dfa2c7698a453369b9790de0" + integrity sha1-pIFqF81f9RbfosdpikUzabl5DeA= + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -3249,44 +3187,24 @@ atob@^2.1.1: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== -awilix@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/awilix/-/awilix-4.2.0.tgz#425e688a861f049db7cf44208859638af76a42b9" - integrity sha512-q46nv90ZTDKPqBE8RiXN0WMSP5y+F0WSvsd2NCKQBE2sr+rv5eHM+0gFkHUtGwoaI169LwoaglxXK5/ZumShSQ== +awilix@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/awilix/-/awilix-4.2.1.tgz#9584e7721718b9644ddd2b67d362111459edd9a8" + integrity sha512-zwWF8mD8bNF+ijQr1xgldbMudRZOOTpwziXJT50f/5G+S1zWy6gIGd95u26Eyo0eqww1G6AxHTljDfJlUn4ZfA== dependencies: camel-case "^3.0.0" glob "^7.1.3" -aws-sign2@~0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" - integrity sha1-FDQt0428yU0OW4fXY81jYSwOeU8= - aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= -aws4@^1.2.1, aws4@^1.8.0: +aws4@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== -axios-mock-adapter@^1.16.0: - version "1.16.0" - resolved "https://registry.yarnpkg.com/axios-mock-adapter/-/axios-mock-adapter-1.16.0.tgz#cdd55bb60d8cb3fcd77fdb9cbb269e47b8b95180" - integrity sha512-m2D8ngMTQ5p4zZNBsPKoENgwz5rDfd0pZmXI/spdE2eeeKIcR3jquk+NRiBVFtb9UJlciBYplNzSUmgQ6X385Q== - dependencies: - deep-equal "^1.0.1" - -axios@^0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.0.tgz#32d53e4851efdc0a11993b6cd000789d70c05102" - integrity sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI= - dependencies: - follow-redirects "^1.3.0" - is-buffer "^1.1.5" - b64@4.x.x: version "4.1.2" resolved "https://registry.yarnpkg.com/b64/-/b64-4.1.2.tgz#7015372ba8101f7fb18da070717a93c28c8580d8" @@ -3358,7 +3276,20 @@ babel-jest@^23.6.0: babel-plugin-istanbul "^4.1.6" babel-preset-jest "^23.2.0" -babel-loader@^8.0.4: +babel-jest@^24.3.1: + version "24.3.1" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.3.1.tgz#168468a37e90426520c5293da4f55e1a512063b0" + integrity sha512-6KaXyUevY0KAxD5Ba+EBhyfwvc+R2f7JV7BpBZ5T8yJGgj0M1hYDfRhDq35oD5MzprMf/ggT81nEuLtMyxfDIg== + dependencies: + "@jest/transform" "^24.3.1" + "@jest/types" "^24.3.0" + "@types/babel__core" "^7.1.0" + babel-plugin-istanbul "^5.1.0" + babel-preset-jest "^24.3.0" + chalk "^2.4.2" + slash "^2.0.0" + +babel-loader@^8.0.5: version "8.0.5" resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.0.5.tgz#225322d7509c2157655840bba52e46b6c2f2fe33" integrity sha512-NTnHnVRd2JnRqPC0vW+iOQWU5pchDbYXsG2E6DMXEpMfUcQKclF9gmf3G3ZMhzG7IG9ji4coL0cm+FxeWxDpnw== @@ -3385,11 +3316,27 @@ babel-plugin-istanbul@^4.1.6: istanbul-lib-instrument "^1.10.1" test-exclude "^4.2.1" +babel-plugin-istanbul@^5.1.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.1.tgz#7981590f1956d75d67630ba46f0c22493588c893" + integrity sha512-RNNVv2lsHAXJQsEJ5jonQwrJVWK8AcZpG1oxhnjCUaAjL7xahYLANhPUZbzEQHjKy1NMYUwn+0NPKQc8iSY4xQ== + dependencies: + find-up "^3.0.0" + istanbul-lib-instrument "^3.0.0" + test-exclude "^5.0.0" + babel-plugin-jest-hoist@^23.2.0: version "23.2.0" resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz#e61fae05a1ca8801aadee57a6d66b8cefaf44167" integrity sha1-5h+uBaHKiAGq3uV6bWa4zvr0QWc= +babel-plugin-jest-hoist@^24.3.0: + version "24.3.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.3.0.tgz#f2e82952946f6e40bb0a75d266a3790d854c8b5b" + integrity sha512-nWh4N1mVH55Tzhx2isvUN5ebM5CDUvIpXPZYMRazQughie/EqGnbR+czzoQlhUmJG9pPJmYDRhvocotb2THl1w== + dependencies: + "@types/babel__traverse" "^7.0.6" + babel-plugin-syntax-object-rest-spread@^6.13.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" @@ -3403,6 +3350,14 @@ babel-preset-jest@^23.2.0: babel-plugin-jest-hoist "^23.2.0" babel-plugin-syntax-object-rest-spread "^6.13.0" +babel-preset-jest@^24.3.0: + version "24.3.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-24.3.0.tgz#db88497e18869f15b24d9c0e547d8e0ab950796d" + integrity sha512-VGTV2QYBa/Kn3WCOKdfS31j9qomaXSgJqi65B6o05/1GsJyj9LVhSljM9ro4S+IBGj/ENhNBuH9bpqzztKAQSw== + dependencies: + "@babel/plugin-syntax-object-rest-spread" "^7.0.0" + babel-plugin-jest-hoist "^24.3.0" + babel-register@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" @@ -3416,7 +3371,7 @@ babel-register@^6.26.0: mkdirp "^0.5.1" source-map-support "^0.4.15" -babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0: +babel-runtime@^6.22.0, babel-runtime@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= @@ -3465,11 +3420,6 @@ babylon@^6.18.0: resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== -backo2@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" - integrity sha1-MasayLEpNjRj41s+u2n038+6eUc= - balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" @@ -3507,10 +3457,20 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -before-after-hook@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-1.3.2.tgz#7bfbf844ad670aa7a96b5a4e4e15bd74b08ed66b" - integrity sha512-zyPgY5dgbf99c0uGUjhY4w+mxqEGxPKg9RQDl34VvrVh2bM31lFN+mwR1ZHepq/KA3VCPk1gwJZL6IIJqjLy2w== +bcrypto@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/bcrypto/-/bcrypto-3.0.2.tgz#3fd96da06447724cebdfeed14821ade86babec53" + integrity sha512-JGPVqeO+uvlo+5QTEO78jL4/5UVvBcqc8UPd7ctLMQTb2FR85aocdDDKk0vtyLpXY2/orRrD51h3/Q0GwuyZAg== + dependencies: + bindings "~1.3.1" + bsert "~0.0.8" + bufio "~1.0.4" + nan "~2.12.1" + +before-after-hook@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-1.4.0.tgz#2b6bf23dca4f32e628fd2747c10a37c74a4b484d" + integrity sha512-l5r9ir56nda3qu14nAXIlyq1MmUSs0meCIaFAh8HwkFwP1F8eToOuS3ah2VAHHcY04jaYD7FpJC5JTXHYRbkzg== benchmark@^2.1.4: version "2.1.4" @@ -3543,10 +3503,10 @@ bigi@^1.1.0, bigi@^1.2.0: resolved "https://registry.yarnpkg.com/bigi/-/bigi-1.4.2.tgz#9c665a95f88b8b08fc05cfd731f561859d725825" integrity sha1-nGZalfiLiwj8Bc/XMfVhhZ1yWCU= -bignumber.js@^8.0.2: - version "8.0.2" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-8.0.2.tgz#d8c4e1874359573b1ef03011a2d861214aeef137" - integrity sha512-EiuvFrnbv0jFixEQ9f58jo7X0qI2lNGIr/MxntmVzQc5JUweDSh8y8hbTCAomFtqwUPIOWcLXP0VEOSZTG7FFw== +bignumber.js@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-8.1.1.tgz#4b072ae5aea9c20f6730e4e5d529df1271c4d885" + integrity sha512-QD46ppGintwPGuL1KqmwhR0O+N2cZUg8JG/VzwI2e28sM9TqHjQB10lI4QAaMHVbLzwVLLAwEglpKPViWX+5NQ== binary-extensions@^1.0.0: version "1.13.0" @@ -3554,12 +3514,17 @@ binary-extensions@^1.0.0: integrity sha512-EgmjVLMn22z7eGGv3kcnHwSnJXmFHjISTY9E/S5lIcTD3Oxw05QTcBLNkJFzcb3cNueUdF/IN4U+d78V0zO8Hw== bindings@^1.2.1, bindings@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.4.0.tgz#909efa49f2ebe07ecd3cb136778f665052040127" - integrity sha512-7znEVX22Djn+nYjxCWKDne0RRloa9XfYa84yk3s+HkE3LpDYZmhArYr9O9huBoHY3/oXispx5LorIX7Sl2CgSQ== + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== dependencies: file-uri-to-path "1.0.0" +bindings@~1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.1.tgz#21fc7c6d67c18516ec5aaa2815b145ff77b26ea5" + integrity sha512-i47mqjF9UbjxJhxGf+pZ6kSxrnI3wBLlnGI2ArWJ4r0VrvDS7ZYXkprq/pLaBWYq4GM0r4zdHY+NNRqEMU7uew== + bip32@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/bip32/-/bip32-1.0.2.tgz#982e2ad2cae6fc6a2f53dda3e6c3be9364674b28" @@ -3603,12 +3568,13 @@ bip66@^1.1.3: dependencies: safe-buffer "^5.0.1" -bl@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/bl/-/bl-1.1.2.tgz#fdca871a99713aa00d19e3bbba41c44787a65398" - integrity sha1-/cqHGplxOqANGeO7ukHER4emU5g= +bl@^1.0.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c" + integrity sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA== dependencies: - readable-stream "~2.0.5" + readable-stream "^2.3.5" + safe-buffer "^5.1.1" block-stream@*: version "0.0.9" @@ -3617,7 +3583,7 @@ block-stream@*: dependencies: inherits "~2.0.0" -bluebird@^3.4.3, bluebird@^3.4.6, bluebird@^3.5.0, bluebird@^3.5.1, bluebird@^3.5.3: +bluebird@^3.5.0, bluebird@^3.5.1, bluebird@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw== @@ -3627,30 +3593,7 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.11.3, bn.js@^4.11.8, bn.js@^4 resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== -body-parser@1.18.3, body-parser@^1.18.3: - version "1.18.3" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4" - integrity sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ= - dependencies: - bytes "3.0.0" - content-type "~1.0.4" - debug "2.6.9" - depd "~1.1.2" - http-errors "~1.6.3" - iconv-lite "0.4.23" - on-finished "~2.3.0" - qs "6.5.2" - raw-body "2.3.3" - type-is "~1.6.16" - -boom@2.x.x: - version "2.10.1" - resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" - integrity sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8= - dependencies: - hoek "2.x.x" - -boom@7.x.x, boom@^7.1.0, boom@^7.1.1, boom@^7.2.0, boom@^7.3.0: +boom@7.x.x, boom@^7.1.1, boom@^7.2.0, boom@^7.3.0: version "7.3.0" resolved "https://registry.yarnpkg.com/boom/-/boom-7.3.0.tgz#733a6d956d33b0b1999da3fe6c12996950d017b9" integrity sha512-Swpoyi2t5+GhOEGw8rEsKvTxFLIDiiKoUc2gsoV6Lyr43LHBIzch3k2MvYUs8RTROrIkVJ3Al0TkaOGjnb+B6A== @@ -3733,7 +3676,7 @@ browser-resolve@^1.11.3: dependencies: resolve "1.1.7" -browserify-aes@^1.0.0, browserify-aes@^1.0.1, browserify-aes@^1.0.4, browserify-aes@^1.0.6: +browserify-aes@^1.0.0, browserify-aes@^1.0.1, browserify-aes@^1.0.4, browserify-aes@^1.0.6, browserify-aes@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== @@ -3793,13 +3736,13 @@ browserify-zlib@^0.2.0: pako "~1.0.5" browserslist@^4.3.4: - version "4.4.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.4.1.tgz#42e828954b6b29a7a53e352277be429478a69062" - integrity sha512-pEBxEXg7JwaakBXjATYw/D1YZh4QUSCX/Mnd/wnqSRPPSi1U39iDhDoKGoBUcraKdxDlrYqJxSI5nNvD+dWP2A== + version "4.4.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.4.2.tgz#6ea8a74d6464bb0bd549105f659b41197d8f0ba2" + integrity sha512-ISS/AIAiHERJ3d45Fz0AVYKkgcy+F/eJHzKEvv1j0wwKGKD9T3BrwKr/5g45L+Y4XIK5PlTqefHciRFcfE1Jxg== dependencies: - caniuse-lite "^1.0.30000929" - electron-to-chromium "^1.3.103" - node-releases "^1.1.3" + caniuse-lite "^1.0.30000939" + electron-to-chromium "^1.3.113" + node-releases "^1.1.8" bs-logger@0.x: version "0.2.6" @@ -3831,21 +3774,39 @@ bser@^2.0.0: dependencies: node-int64 "^0.4.0" +bsert@~0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/bsert/-/bsert-0.0.8.tgz#1614b6d0faf6b5940d8ba2ab9520180e2b9a695d" + integrity sha512-MzSxGNGymvJ2wAJ0lrC2cT+Irq2q+EMz5ijt8qXIxt02VoO+ZFWPNrmzNHGq6F8RJnIzzRmKz8mv1/j2xU1TQg== + btoa-lite@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337" integrity sha1-M3dm2hWAEhD92VbCLpxokaudAzc= +buffer-alloc-unsafe@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" + integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg== + +buffer-alloc@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" + integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow== + dependencies: + buffer-alloc-unsafe "^1.1.0" + buffer-fill "^1.0.0" + +buffer-fill@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" + integrity sha1-+PeLdniYiO858gXNY39o5wISKyw= + buffer-from@1.x, buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== -buffer-shims@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" - integrity sha1-mXjOMXOIxkmth5MCjDR37wRKi1E= - buffer-writer@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" @@ -3856,6 +3817,13 @@ buffer-xor@^1.0.2, buffer-xor@^1.0.3: resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= +buffer-xor@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-2.0.2.tgz#34f7c64f04c777a1f8aac5e661273bb9dd320289" + integrity sha512-eHslX0bin3GB+Lx2p7lEYRShRewuNZL3fUl4qlVJGGiwoPGftmt8JQgk2Y9Ji5/01TnVDo33E5b5O3vUB1HdqQ== + dependencies: + safe-buffer "^5.1.1" + buffer@^4.3.0: version "4.9.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" @@ -3865,7 +3833,12 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" -builtin-modules@^1.0.0, builtin-modules@^1.1.1: +bufio@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/bufio/-/bufio-1.0.4.tgz#f6e197928cf4d1196c19b9dba6c3e89bb879aca7" + integrity sha512-z5HOP06DHRlwB6C4Ol6RNA2HWam8Pluo5pasPniQVyC4dzwutzYHHHDjDSOgSyGeThE7kmgT7ynV1A+EuD8s3w== + +builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= @@ -3875,23 +3848,11 @@ builtin-status-codes@^3.0.0: resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= -builtins@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/builtins/-/builtins-0.0.7.tgz#355219cd6cf18dbe7c01cc7fd2dce765cfdc549a" - integrity sha1-NVIZzWzxjb58Acx/0tznZc/cVJo= - builtins@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" integrity sha1-y5T662HIaWRR2zZTThQi+U8K7og= -busboy@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.3.0.tgz#6ee3cb1c844fc1f691d8f9d824f70128b3b5e485" - integrity sha512-e+kzZRAbbvJPLjQz2z+zAyr78BSi9IFeBTyLwF76g78Q2zRt/RZ1NtS3MS17v2yLqYfLz69zHdC+1L4ja8PwqQ== - dependencies: - dicer "0.3.0" - byline@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" @@ -4007,6 +3968,11 @@ callsites@^2.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= +callsites@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.0.0.tgz#fb7eb569b72ad7a45812f93fd9430a3e410b3dd3" + integrity sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw== + camel-case@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73" @@ -4032,7 +3998,7 @@ camelcase-keys@^4.0.0: map-obj "^2.0.0" quick-lru "^1.0.0" -camelcase@5.0.0, camelcase@^5.0.0: +camelcase@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42" integrity sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA== @@ -4047,10 +4013,15 @@ camelcase@^4.0.0, camelcase@^4.1.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= -caniuse-lite@^1.0.30000929: - version "1.0.30000936" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000936.tgz#5d33b118763988bf721b9b8ad436d0400e4a116b" - integrity sha512-orX4IdpbFhdNO7bTBhSbahp1EBpqzBc+qrvTRVUFfZgA4zta7TdM6PN5ZxkEUgDnz36m+PfWGcdX7AVfFWItJw== +camelcase@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.2.0.tgz#e7522abda5ed94cc0489e1b8466610e88404cf45" + integrity sha512-IXFsBS2pC+X0j0N/GE7Dm7j3bsEBp+oTpb7F50dwEVX7rf3IgwO9XatnegTsDtniKCUtEJH4fSU6Asw7uoVLfQ== + +caniuse-lite@^1.0.30000939: + version "1.0.30000942" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000942.tgz#454139b28274bce70bfe1d50c30970df7430c6e4" + integrity sha512-wLf+IhZUy2rfz48tc40OH7jHjXjnvDFEYqBHluINs/6MgzoNLPf25zhE4NOVzqxLKndf+hau81sAW0RcGHIaBQ== capture-console@^1.0.1: version "1.0.1" @@ -4081,11 +4052,6 @@ cardinal@^2.1.1: ansicolors "~0.3.2" redeyed "~2.1.0" -caseless@~0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" - integrity sha1-cVuW6phBWTzDMGeSP17GDr2k99c= - caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -4117,10 +4083,22 @@ catbox@10.x.x: hoek "6.x.x" joi "14.x.x" -chalk@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" - integrity sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ== +chai@^4.1.2: + version "4.2.0" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5" + integrity sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw== + dependencies: + assertion-error "^1.1.0" + check-error "^1.0.2" + deep-eql "^3.0.1" + get-func-name "^2.0.0" + pathval "^1.1.0" + type-detect "^4.0.5" + +chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== dependencies: ansi-styles "^3.2.1" escape-string-regexp "^1.0.5" @@ -4137,15 +4115,6 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - chardet@^0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" @@ -4156,10 +4125,15 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== -chokidar@^2.0.2: - version "2.1.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.1.tgz#adc39ad55a2adf26548bd2afa048f611091f9184" - integrity sha512-gfw3p2oQV2wEt+8VuMlNsPjCxDxvvgnm/kz+uATu805mWVF8IJN7uz9DN7iBz+RMJISmiVbCOBFs9qBGMjtPfQ== +check-error@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" + integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= + +chokidar@2.1.2, chokidar@^2.0.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.2.tgz#9c23ea40b01638439e0513864d362aeacc5ad058" + integrity sha512-IwXUx0FXc5ibYmPC2XeEj5mpXoV66sR+t3jqu2NS2GYwCktt3KF1/Qqjws/NkegajBA4RbZ5+DDwlOiJsxDHEg== dependencies: anymatch "^2.0.0" async-each "^1.0.1" @@ -4175,16 +4149,11 @@ chokidar@^2.0.2: optionalDependencies: fsevents "^1.2.7" -chownr@^1.1.1: +chownr@^1.0.1, chownr@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g== -chownr@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181" - integrity sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE= - chrome-trace-event@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.0.tgz#45a91bd2c20c9411f0963b5aaeb9a1b95e09cc48" @@ -4235,6 +4204,11 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.0.0.tgz#301bfa9e8dd2d3d984c0e542f7aa67b996f63e0a" integrity sha512-VEoL9Qh7I8s8iHnV53DaeWSt8NJ0g3khMfK6NiCPB7H657juhro+cSw2O88uo3bo0c0X5usamtXk0/Of0wXa5A== +clear@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/clear/-/clear-0.1.0.tgz#b81b1e03437a716984fd7ac97c87d73bdfe7048a" + integrity sha512-qMjRnoL+JDPJHeLePZJuao6+8orzHMGP04A8CdwCNsKhRbOnKRjefxONR7bwILT3MHecxKBjHkKL/tkZ8r4Uzw== + cli-boxes@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" @@ -4255,12 +4229,12 @@ cli-progress@^2.1.1: colors "^1.1.2" string-width "^2.1.1" -cli-spinners@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-1.3.1.tgz#002c1990912d0d59580c93bd36c056de99e4259a" - integrity sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg== +cli-spinners@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.0.0.tgz#4b078756fc17a8f72043fdc9f1f14bf4fa87e2df" + integrity sha512-yiEBmhaKPPeBj7wWm4GEdtPZK940p9pl3EANIrnJ3JnvWyrPjcFcsEq6qRUuQ7fzB0+Y82ld3p6B34xo95foWw== -cli-table3@^0.5.0, cli-table3@^0.5.1: +cli-table3@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.5.1.tgz#0252372d94dfc40dbd8df06005f48f31f656f202" integrity sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw== @@ -4312,10 +4286,10 @@ cli-ux@^4.4.0, cli-ux@^4.9.0: treeify "^1.1.0" tslib "^1.9.3" -cli-ux@^5.0.0, cli-ux@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/cli-ux/-/cli-ux-5.1.0.tgz#945c672bb835f68f590924cbf83b975743299c64" - integrity sha512-cSBrgGkS1yDC7aHVZU9+TzLdqyvHumyyKXs3Ts4q3YKzOPFy2d9EaNkBgeV5JVR4gdCwNJuQJhYTXguewE70lA== +cli-ux@^5.0.0, cli-ux@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/cli-ux/-/cli-ux-5.2.0.tgz#a6c170e306beece72d916cbac49e175b134b1ea2" + integrity sha512-Z1fkInh/k+VBpBi4UkeUGr7Ees6HX4P5+LMdgNlqkq06e7XQDmZkz65smc/PMiF5WfecD2MW58MaUbvJmS2Xww== dependencies: "@oclif/command" "^1.5.1" "@oclif/errors" "^1.2.1" @@ -4333,7 +4307,7 @@ cli-ux@^5.0.0, cli-ux@^5.1.0: is-wsl "^1.1.0" lodash "^4.17.11" natural-orderby "^1.0.2" - password-prompt "^1.0.7" + password-prompt "^1.1.0" semver "^5.6.0" string-width "^2.1.1" strip-ansi "^5.0.0" @@ -4392,18 +4366,10 @@ clone-response@1.0.2, clone-response@^1.0.2: clone@^1.0.2: version "1.0.4" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" - integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= - -cls-bluebird@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cls-bluebird/-/cls-bluebird-2.1.0.tgz#37ef1e080a8ffb55c2f4164f536f1919e7968aee" - integrity sha1-N+8eCAqP+1XC9BZPU28ZGeeWiu4= - dependencies: - is-bluebird "^1.0.2" - shimmer "^1.1.0" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= -cmd-shim@^2.0.2, cmd-shim@~2.0.2: +cmd-shim@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-2.0.2.tgz#6fcbda99483a8fd15d7d30a196ca69d688a2efdb" integrity sha1-b8vamUg6j9FdfTChlspp1oii79s= @@ -4421,15 +4387,15 @@ code-point-at@^1.0.0: resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= -codecov@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/codecov/-/codecov-3.1.0.tgz#340bd968d361f256976b5af782dd8ba9f82bc849" - integrity sha512-aWQc/rtHbcWEQLka6WmBAOpV58J2TwyXqlpAQGhQaSiEUoigTTUk6lLd2vB3kXkhnDyzyH74RXfmV4dq2txmdA== +codecov@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/codecov/-/codecov-3.2.0.tgz#4465ee19884528092d8c313e1f9e4bdc7d3065cd" + integrity sha512-3NJvNARXxilqnqVfgzDHyVrF4oeVgaYW1c1O6Oi5mn93exE7HTSSFNiYdwojWW6IwrCZABJ8crpNbKoo9aUHQw== dependencies: argv "^0.0.2" ignore-walk "^3.0.1" js-yaml "^3.12.0" - request "^2.87.0" + teeny-request "^3.7.0" urlgrey "^0.4.4" collection-visit@^1.0.0: @@ -4496,7 +4462,7 @@ colorspace@1.1.x: color "3.0.x" text-hex "1.0.x" -columnify@^1.5.4, columnify@~1.5.4: +columnify@^1.5.4: version "1.5.4" resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.5.4.tgz#4737ddf1c7b69a8a7c340570782e947eec8e78bb" integrity sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs= @@ -4504,14 +4470,14 @@ columnify@^1.5.4, columnify@~1.5.4: strip-ansi "^3.0.0" wcwidth "^1.0.0" -combined-stream@^1.0.5, combined-stream@^1.0.6, combined-stream@~1.0.5, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828" integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w== dependencies: delayed-stream "~1.0.0" -commander@2.19.0, commander@^2.12.1, commander@^2.14.1, commander@^2.9.0: +commander@2.19.0, commander@^2.12.1, commander@^2.14.1, commander@^2.19.0, commander@^2.9.0: version "2.19.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== @@ -4534,6 +4500,11 @@ compare-func@^1.3.1: array-ify "^1.0.0" dot-prop "^3.0.0" +compare-versions@^3.2.1: + version "3.4.0" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.4.0.tgz#e0747df5c9cb7f054d6d3dc3e1dbc444f9e92b26" + integrity sha512-tK69D7oNXXqUW3ZNo/z7NXTEz22TCF0pTE+YF9cxvaAM9XnkLo1fV621xCLrRR6aevJlKxExkss0vWqUCUpqdg== + component-emitter@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" @@ -4544,7 +4515,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.5.0, concat-stream@^1.5.2, concat-stream@^1.6.0: +concat-stream@^1.5.0, concat-stream@^1.6.0: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -4554,7 +4525,17 @@ concat-stream@^1.5.0, concat-stream@^1.5.2, concat-stream@^1.6.0: readable-stream "^2.2.2" typedarray "^0.0.6" -config-chain@^1.1.11, config-chain@~1.1.11: +concat-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1" + integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.0.2" + typedarray "^0.0.6" + +config-chain@^1.1.11: version "1.1.12" resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA== @@ -4586,17 +4567,17 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= +console-polyfill@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/console-polyfill/-/console-polyfill-0.3.0.tgz#84900902a18c47a5eba932be75fa44d23e8af861" + integrity sha512-w+JSDZS7XML43Xnwo2x5O5vxB0ID7T5BdqDtyqT6uiCAX2kZAgcWxNaGqT97tZfSHzfOcvrfsDAodKcJ3UvnXQ== + constants-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= -content-disposition@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" - integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ= - -content-type@^1.0.4, content-type@~1.0.4: +content-type@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== @@ -4608,20 +4589,20 @@ content@4.x.x: dependencies: boom "7.x.x" -conventional-changelog-angular@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.2.tgz#39d945635e03b6d0c9d4078b1df74e06163dc66a" - integrity sha512-yx7m7lVrXmt4nKWQgWZqxSALEiAKZhOAcbxdUaU9575mB0CzXVbgrgpfSnSP7OqWDUTYGD0YVJ0MSRdyOPgAwA== +conventional-changelog-angular@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.3.tgz#299fdd43df5a1f095283ac16aeedfb0a682ecab0" + integrity sha512-YD1xzH7r9yXQte/HF9JBuEDfvjxxwDGGwZU1+ndanbY0oFgA+Po1T9JDSpPLdP0pZT6MhCAsdvFKC4TJ4MTJTA== dependencies: compare-func "^1.3.1" q "^1.5.1" -conventional-changelog-core@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/conventional-changelog-core/-/conventional-changelog-core-3.1.5.tgz#c2edf928539308b54fe1b90a2fc731abc021852c" - integrity sha512-iwqAotS4zk0wA4S84YY1JCUG7X3LxaRjJxuUo6GI4dZuIy243j5nOg/Ora35ExT4DOiw5dQbMMQvw2SUjh6moQ== +conventional-changelog-core@^3.1.6: + version "3.1.6" + resolved "https://registry.yarnpkg.com/conventional-changelog-core/-/conventional-changelog-core-3.1.6.tgz#ac1731a461c50d150d29c1ad4f33143293bcd32f" + integrity sha512-5teTAZOtJ4HLR6384h50nPAaKdDr+IaU0rnD2Gg2C3MS7hKsEPH8pZxrDNqam9eOSPQg9tET6uZY79zzgSz+ig== dependencies: - conventional-changelog-writer "^4.0.2" + conventional-changelog-writer "^4.0.3" conventional-commits-parser "^3.0.1" dateformat "^3.0.0" get-pkg-repo "^1.0.0" @@ -4640,15 +4621,15 @@ conventional-changelog-preset-loader@^2.0.2: resolved "https://registry.yarnpkg.com/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.0.2.tgz#81d1a07523913f3d17da3a49f0091f967ad345b0" integrity sha512-pBY+qnUoJPXAXXqVGwQaVmcye05xi6z231QM98wHWamGAmu/ghkBprQAwmF5bdmyobdVxiLhPY3PrCfSeUNzRQ== -conventional-changelog-writer@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-4.0.2.tgz#eb493ed84269e7a663da36e49af51c54639c9a67" - integrity sha512-d8/FQY/fix2xXEBUhOo8u3DCbyEw3UOQgYHxLsPDw+wHUDma/GQGAGsGtoH876WyNs32fViHmTOUrgRKVLvBug== +conventional-changelog-writer@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-4.0.3.tgz#916a2b302d0bb5ef18efd236a034c13fb273cde1" + integrity sha512-bIlpSiQtQZ1+nDVHEEh798Erj2jhN/wEjyw9sfxY9es6h7pREE5BNJjfv0hXGH/FTrAsEpHUq4xzK99eePpwuA== dependencies: compare-func "^1.3.1" conventional-commits-filter "^2.0.1" dateformat "^3.0.0" - handlebars "^4.0.2" + handlebars "^4.1.0" json-stringify-safe "^5.0.1" lodash "^4.2.1" meow "^4.0.0" @@ -4698,11 +4679,6 @@ convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.5.1: dependencies: safe-buffer "~5.1.1" -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= - cookie@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" @@ -4725,45 +4701,36 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= -core-js@3.0.0-beta.3: - version "3.0.0-beta.3" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.0.0-beta.3.tgz#b0f22009972b8c6c04550ebf38513ca4b3cc9559" - integrity sha512-kM/OfrnMThP5PwGAj5HhQLdjUqzjrllqN2EVnk/X9qrLsfYjR2hzZ+E/8CzH0xuosexZtqMTLQrk//BULrBj9w== - -core-js@^2.4.0, core-js@^2.5.0, core-js@^2.5.7: - version "2.6.4" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.4.tgz#b8897c062c4d769dd30a0ac5c73976c47f92ea0d" - integrity sha512-05qQ5hXShcqGkPZpXEFLIpxayZscVD2kuMBZewxiIPPEagukO4mqgPA9CWhUvFBJfy3ODdK2p9xyHh7FTU9/7A== - -core-js@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.3.0.tgz#fab83fbb0b2d8dc85fa636c4b9d34c75420c6d65" - integrity sha1-+rg/uwstjchfpjbEudNMdUIMbWU= +core-js@^2.4.0, core-js@^2.5.0: + version "2.6.5" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.5.tgz#44bc8d249e7fb2ff5d00e0341a7ffb94fbf67895" + integrity sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= -cosmiconfig@^5.0.2, cosmiconfig@^5.0.7: - version "5.0.7" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.0.7.tgz#39826b292ee0d78eda137dfa3173bd1c21a43b04" - integrity sha512-PcLqxTKiDmNT6pSpy4N6KtuPwb53W+2tzNvwOZw0WH9N6O0vLIBq0x8aj8Oj75ere4YcGi48bDFCL+3fRJdlNA== +cosmiconfig@^5.0.2, cosmiconfig@^5.0.7, cosmiconfig@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.1.0.tgz#6c5c35e97f37f985061cdf653f114784231185cf" + integrity sha512-kCNPvthka8gvLtzAxQXvWo4FxqRB+ftRZyPZNuab5ngvM9Y7yw7hbEysglptLgpkGX9nAOKTBVkHUAe8xtYR6Q== dependencies: import-fresh "^2.0.0" is-directory "^0.3.1" js-yaml "^3.9.0" + lodash.get "^4.4.2" parse-json "^4.0.0" -cp-file@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/cp-file/-/cp-file-6.0.0.tgz#f38477ece100b403fcf780fd34d030486beb693e" - integrity sha512-OtHMgPugkgwHlbph25wlMKd358lZNhX1Y2viUpPoFmlBPlEiPIRhztYWha11grbGPnlM+urp5saVmwsChCIOEg== +cp-file@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/cp-file/-/cp-file-6.1.0.tgz#b48d2d80577d4c5025c68eb597a38093c1dc9ccf" + integrity sha512-an34I0lJwKncRKjxe3uGWUuiIIVYsHHjBGKld3OQB56hfoPCYom31VysvfuysKqHLbz6drnqP5YrCfLw17I2kw== dependencies: graceful-fs "^4.1.2" - make-dir "^1.0.0" + make-dir "^2.0.0" nested-error-stacks "^2.0.0" - pify "^3.0.0" + pify "^4.0.1" safe-buffer "^5.0.1" cpy-cli@^2.0.0: @@ -4775,13 +4742,13 @@ cpy-cli@^2.0.0: meow "^5.0.0" cpy@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/cpy/-/cpy-7.0.1.tgz#d817e4d81bd7f0f25ff812796c5f1392dc0fb485" - integrity sha512-Zo52tXKLJcgy/baacn6KaNoRAakkl2wb+R4u6qJ4wlD0uchncwRQcIk66PlGlkzuToCJO6A6PWX27Tdwc8LU2g== + version "7.1.0" + resolved "https://registry.yarnpkg.com/cpy/-/cpy-7.1.0.tgz#085aa6077b28d211d585521ad7d8f3d05234a31d" + integrity sha512-HT6xnKeHwACUObD3LEFAsjeQ9IUVhC1Pn6Qbk0q6CEWy0WG061khT3ZxQU6IuMXPEEyb+vvluyUOyTdl+9EPWQ== dependencies: arrify "^1.0.1" - cp-file "^6.0.0" - globby "^8.0.1" + cp-file "^6.1.0" + globby "^9.1.0" nested-error-stacks "^2.0.0" create-ecdh@^4.0.0: @@ -4850,13 +4817,6 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cryptiles@2.x.x: - version "2.0.5" - resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" - integrity sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g= - dependencies: - boom "2.x.x" - cryptiles@4.x.x: version "4.1.3" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-4.1.3.tgz#2461d3390ea0b82c643a6ba79f0ed491b0934c25" @@ -4892,9 +4852,9 @@ cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": integrity sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A== cssstyle@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.1.1.tgz#18b038a9c44d65f7a8e428a653b9f6fe42faf5fb" - integrity sha512-364AI1l/M5TYcFH83JnOH/pSqgaNnKmYgKrm0didZMGKWjQB60dymwWy1rKUgL3J1ffdq9xVi2yGLHdSjjSNog== + version "1.2.1" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.2.1.tgz#3aceb2759eaf514ac1a21628d723d6043a819495" + integrity sha512-7DYm8qe+gPx/h77QlCyFmX80+fGaE/6A/Ekl0zaszYOubvySO2saYFdQ78P29D0UsULxFKCetDGNaNRUdSF+2A== dependencies: cssom "0.3.x" @@ -4915,13 +4875,6 @@ cyclist@~0.2.2: resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA= -d@1: - version "1.0.0" - resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" - integrity sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8= - dependencies: - es5-ext "^0.10.9" - dargs@^4.0.1: version "4.1.0" resolved "https://registry.yarnpkg.com/dargs/-/dargs-4.1.0.tgz#03a9dbb4b5c2f139bf14ae53f0b8a2a6a86f4e17" @@ -4987,7 +4940,7 @@ debug@2, debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, de dependencies: ms "2.0.0" -debug@3.1.0, debug@=3.1.0: +debug@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== @@ -5013,6 +4966,13 @@ debuglog@^1.0.1: resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= +decache@^3.0.5: + version "3.1.0" + resolved "https://registry.yarnpkg.com/decache/-/decache-3.1.0.tgz#4f5036fbd6581fcc97237ac3954a244b9536c2da" + integrity sha1-T1A2+9ZYH8yXI3rDlUokS5U2wto= + dependencies: + find "^0.2.4" + decamelize-keys@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" @@ -5043,7 +5003,14 @@ dedent@^0.7.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= -deep-equal@^1.0.1: +deep-eql@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" + integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== + dependencies: + type-detect "^4.0.0" + +deep-equal@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" integrity sha1-9dJgKStmDghO/0zbyfCK0yR0SLU= @@ -5058,10 +5025,10 @@ deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= -deepmerge@3.1.0, deepmerge@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-3.1.0.tgz#a612626ce4803da410d77554bfd80361599c034d" - integrity sha512-/TnecbwXEdycfbsM2++O3eGiatEFHjjNciHEwJclM+T5Kd94qD1AP+2elP/Mq0L5b9VZJao5znR01Mz6eX8Seg== +deepmerge@3.2.0, deepmerge@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-3.2.0.tgz#58ef463a57c08d376547f8869fdc5bcee957f44e" + integrity sha512-6+LuZGU7QCNUnAJyX8cIrlzoEgggTM6B7mm+znKOX4t5ltluT9KLjN6g61ECMS0LTsLW7yDpNoxhix5FZcrIow== default-require-extensions@^1.0.0: version "1.0.0" @@ -5070,6 +5037,13 @@ default-require-extensions@^1.0.0: dependencies: strip-bom "^2.0.0" +default-require-extensions@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-2.0.0.tgz#f5f8fbb18a7d6d50b21f641f649ebb522cfe24f7" + integrity sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc= + dependencies: + strip-bom "^3.0.0" + defaults@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" @@ -5156,15 +5130,15 @@ delegates@^1.0.0: resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= -depd@^1.1.0, depd@~1.1.2: +depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= -deprecated-decorator@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/deprecated-decorator/-/deprecated-decorator-0.1.6.tgz#00966317b7a12fe92f3cc831f7583af329b86c37" - integrity sha1-AJZjF7ehL+kvPMgx91g68ym4bDc= +deprecation@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-1.0.1.tgz#2df79b79005752180816b7b6e079cbd80490d711" + integrity sha512-ccVHpE72+tcIKaGMql33x5MAjKQIZrk+3x2GbJ7TeraUCZWHoT+KSZpoC+JQFsUBlSTXUrBaGiF0j6zVTepPLg== des.js@^1.0.0: version "1.0.0" @@ -5174,11 +5148,6 @@ des.js@^1.0.0: inherits "^2.0.1" minimalistic-assert "^1.0.0" -destroy@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= - detect-file@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" @@ -5206,7 +5175,7 @@ detect-newline@^2.1.0: resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I= -dezalgo@^1.0.0, dezalgo@^1.0.1, dezalgo@~1.0.3: +dezalgo@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456" integrity sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY= @@ -5223,18 +5192,21 @@ diagnostics@^1.1.1: enabled "1.0.x" kuler "1.0.x" -dicer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.0.tgz#eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872" - integrity sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA== - dependencies: - streamsearch "0.1.2" +diff-sequences@^24.3.0: + version "24.3.0" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.3.0.tgz#0f20e8a1df1abddaf4d9c226680952e64118b975" + integrity sha512-xLqpez+Zj9GKSnPWS0WZw1igGocZ+uua8+y+5dDNTT934N3QuY1sp2LkHzwiaYQGz60hMq0pjAshdeXm5VUOEw== diff@^3.2.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== +diff@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff" + integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -5252,10 +5224,17 @@ dir-glob@2.0.0: arrify "^1.0.1" path-type "^3.0.0" -docdash@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/docdash/-/docdash-1.0.2.tgz#0449a8f6bb247f563020b78a5485dea95ae2e094" - integrity sha512-IEM57bWPLtVXpUeCKbiGvHsHtW9O9ZiiBPfeQDAZ7JdQiAF3aNWQoJ3e/+uJ63lHO009yn0tbJjtKpXJ2AURCQ== +dir-glob@^2.2.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4" + integrity sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw== + dependencies: + path-type "^3.0.0" + +docdash@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/docdash/-/docdash-1.0.3.tgz#1ca4d37940275f05f926c1326cde36d88b9f3fbc" + integrity sha512-gPepWBPJSWK70PRqOtxLvZ2FLJlw49DZASs73yqmM7uM45WEygQJmHPBrrQ9eLeEkgPKUzDv+sr2SHgQS1OQ0g== dockerfile-ast@0.0.12: version "0.0.12" @@ -5290,7 +5269,7 @@ dot-prop@^4.1.0, dot-prop@^4.2.0: dependencies: is-obj "^1.0.0" -dottie@^2.0.0, dottie@^2.0.1: +dottie@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/dottie/-/dottie-2.0.1.tgz#697ad9d72004db7574d21f892466a3c285893659" integrity sha512-ch5OQgvGDK2u8pSZeSYAQaV/lczImd7pMJ7BcEPXmnFVjy4yJIzP6CsODJUTH8mg1tyH1Z2abOiuJO3DjZ/GBw== @@ -5304,11 +5283,6 @@ drbg.js@^1.0.1: create-hash "^1.1.2" create-hmac "^1.1.4" -ducky@2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/ducky/-/ducky-2.7.0.tgz#a8c263cbbc839d41190442c68aa462d4fff3cf47" - integrity sha512-zeeSq/GXN6xRmUBxssViJRsvEsTEKUyoKbEabmFO46d8J0DG07JGD82F3F8qYSfLHSD2fWEvwSIwQWhp+LxXQw== - duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" @@ -5366,16 +5340,6 @@ editions@^2.1.0, editions@^2.1.2, editions@^2.1.3: errlop "^1.1.1" semver "^5.6.0" -editor@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/editor/-/editor-1.0.0.tgz#60c7f87bd62bcc6a894fa8ccd6afb7823a24f742" - integrity sha1-YMf4e9YrzGqJT6jM1q+3gjok90I= - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= - elasticsearch@^15.4.1: version "15.4.1" resolved "https://registry.yarnpkg.com/elasticsearch/-/elasticsearch-15.4.1.tgz#28b54de22fea74003e57395e035926fe7b63b8dd" @@ -5385,7 +5349,7 @@ elasticsearch@^15.4.1: chalk "^1.0.0" lodash "^4.17.10" -electron-to-chromium@^1.3.103: +electron-to-chromium@^1.3.113: version "1.3.113" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.113.tgz#b1ccf619df7295aea17bc6951dc689632629e4a9" integrity sha512-De+lPAxEcpxvqPTyZAXELNpRZXABRxf+uL/rSykstQhzj/B0l1150G/ExIIxKc16lI89Hgz81J0BHAcbTqK49g== @@ -5413,6 +5377,11 @@ email-validator@^2.0.4: resolved "https://registry.yarnpkg.com/email-validator/-/email-validator-2.0.4.tgz#b8dfaa5d0dae28f1b03c95881d904d4e40bfe7ed" integrity sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ== +"emoji-regex@>=6.0.0 <=6.1.1": + version "6.1.1" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.1.1.tgz#c6cd0ec1b0642e2a3c67a1137efc5e796da4f88e" + integrity sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4= + emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -5430,11 +5399,6 @@ enabled@1.0.x: dependencies: env-variable "0.0.x" -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= - encoding@^0.1.11: version "0.1.12" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" @@ -5458,10 +5422,10 @@ enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0: memory-fs "^0.4.0" tapable "^1.0.0" -env-paths@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.0.0.tgz#5a71723f3df7ca98113541f6fa972184f2c9611d" - integrity sha512-13VpSqOO91W3MskXxWJ8x+Y33RKaPT53/HviPp8QcMmEbAJaPFEm8BmMpxCHroJ5rGADqr34Zl6zosBt3F+xAA== +env-paths@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.1.0.tgz#aa0554965e8d109dd6759b90c1bd3d1cdd76d57b" + integrity sha512-UP0khs8YtfSoTOMiDsZMuH2nFXAF3dk2JKfkWZd5j3cRDwsUDPIaV7brRsQmclCTi/h7po/zVs+w10FgJTf9ag== env-variable@0.0.x: version "0.0.5" @@ -5504,6 +5468,13 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" +error-stack-parser@1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-1.3.3.tgz#fada6e3a9cd2b0e080e6d6fc751418649734f35c" + integrity sha1-+tpuOpzSsOCA5tb8dRQYZJc081w= + dependencies: + stackframe "^0.3.1" + error-stack-parser@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.2.tgz#4ae8dbaa2bf90a8b450707b9149dcabca135520d" @@ -5532,33 +5503,10 @@ es-to-primitive@^1.2.0: is-date-object "^1.0.1" is-symbol "^1.0.2" -es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14: - version "0.10.47" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.47.tgz#d24232e1380daad5449a817be19bde9729024a11" - integrity sha512-/1TItLfj+TTfWoeRcDn/0FbGV6SNo4R+On2GGVucPU/j3BWnXE2Co8h8CTo4Tu34gFJtnmwS9xiScKs4EjZhdw== - dependencies: - es6-iterator "~2.0.3" - es6-symbol "~3.1.1" - next-tick "1" - -es6-iterator@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" - integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= - dependencies: - d "1" - es5-ext "^0.10.35" - es6-symbol "^3.1.1" - es6-promise@^4.0.3: - version "4.2.5" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.5.tgz#da6d0d5692efb461e082c14817fe2427d8f5d054" - integrity sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg== - -es6-promise@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.0.2.tgz#010d5858423a5f118979665f46486a95c6ee2bb6" - integrity sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y= + version "4.2.6" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.6.tgz#b685edd8258886365ea62b57d30de28fadcd974f" + integrity sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q== es6-promisify@^5.0.0: version "5.0.0" @@ -5567,28 +5515,15 @@ es6-promisify@^5.0.0: dependencies: es6-promise "^4.0.3" -es6-symbol@^3.0.2, es6-symbol@^3.1.1, es6-symbol@~3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" - integrity sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc= - dependencies: - d "1" - es5-ext "~0.10.14" - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= - escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.4, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= escodegen@1.x.x, escodegen@^1.9.1: - version "1.11.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.0.tgz#b27a9389481d5bfd5bec76f7bb1eb3f8f4556589" - integrity sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw== + version "1.11.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.1.tgz#c485ff8d6b4cdb89e27f4a856e91f118401ca510" + integrity sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw== dependencies: esprima "^3.1.3" estraverse "^4.2.0" @@ -5598,9 +5533,9 @@ escodegen@1.x.x, escodegen@^1.9.1: source-map "~0.6.1" eslint-scope@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.0.tgz#50bf3071e9338bcdc43331794a0cb533f0136172" - integrity sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA== + version "4.0.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.2.tgz#5f10cd6cabb1965bf479fa65745673439e21cb0e" + integrity sha512-5q1+B/ogmHl8+paxtOKx38Z8LtWkVGuNt3+GQNErqwLl6ViNp/gdJGMCjZNxZ8j/VYjDNZ2Fo+eQc1TAVPIzbg== dependencies: esrecurse "^4.1.0" estraverse "^4.1.1" @@ -5632,21 +5567,11 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= - event-lite@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/event-lite/-/event-lite-0.1.2.tgz#838a3e0fdddef8cc90f128006c8e55a4e4e4c11b" integrity sha512-HnSYx1BsJ87/p6swwzv+2v6B4X+uxUteoDfRxsAb1S1BePzQqOLevVmkdA15GHJVd9A9Ok6wygUR18Hu0YeV9g== -eventemitter3@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163" - integrity sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA== - events@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" @@ -5667,6 +5592,11 @@ exec-sh@^0.2.0: dependencies: merge "^1.2.0" +exec-sh@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.2.tgz#6738de2eb7c8e671d0366aea0b0db8c6f7d7391b" + integrity sha512-9sLAvzhI5nc8TpuQUh4ahMdCrWT00wPWz7j47/emR5+2qEfoZP5zzUXvx+vdx+H6ohhnsYC31iX04QLYJK8zTg== + execa@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/execa/-/execa-0.10.0.tgz#ff456a8f53f90f8eccc71a96d11bdfc7f082cb50" @@ -5775,41 +5705,17 @@ expect@^23.6.0: jest-message-util "^23.4.0" jest-regex-util "^23.3.0" -express@^4.16.4: - version "4.16.4" - resolved "https://registry.yarnpkg.com/express/-/express-4.16.4.tgz#fddef61926109e24c515ea97fd2f1bdbf62df12e" - integrity sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg== +expect@^24.3.1: + version "24.3.1" + resolved "https://registry.yarnpkg.com/expect/-/expect-24.3.1.tgz#7c42507da231a91a8099d065bc8dc9322dc85fc0" + integrity sha512-xnmobSlaqhg4FKqjb5REk4AobQzFMJoctDdREKfSGqrtzRfCWYbfqt3WmikAvQz/J8mCNQhORgYdEjPMJbMQPQ== dependencies: - accepts "~1.3.5" - array-flatten "1.1.1" - body-parser "1.18.3" - content-disposition "0.5.2" - content-type "~1.0.4" - cookie "0.3.1" - cookie-signature "1.0.6" - debug "2.6.9" - depd "~1.1.2" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.1.1" - fresh "0.5.2" - 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 "~2.0.4" - qs "6.5.2" - range-parser "~1.2.0" - safe-buffer "5.1.2" - send "0.16.2" - serve-static "1.13.2" - setprototypeof "1.1.0" - statuses "~1.4.0" - type-is "~1.6.16" - utils-merge "1.0.1" - vary "~1.1.2" + "@jest/types" "^24.3.0" + ansi-styles "^3.2.0" + jest-get-type "^24.3.0" + jest-matcher-utils "^24.3.1" + jest-message-util "^24.3.0" + jest-regex-util "^24.3.0" extend-shallow@^2.0.1: version "2.0.1" @@ -5826,7 +5732,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@3, extend@~3.0.0, extend@~3.0.2: +extend@3, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -5890,12 +5796,12 @@ fast-deep-equal@^2.0.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= -fast-diff@^1.0.1: +fast-diff@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== -fast-glob@^2.0.2: +fast-glob@^2.0.2, fast-glob@^2.2.6: version "2.2.6" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.6.tgz#a5d5b697ec8deda468d85a74035290a025a95295" integrity sha512-0BvMaZc1k9F+MeWWMe8pL6YltFzZYcJsYU7D4JyDA6PAczaXvxqQQ/z+mDF7/4Mw01DeUc+i3CTKajnkANkV4w== @@ -5927,10 +5833,10 @@ fast-plural-rules@^0.0.1: resolved "https://registry.yarnpkg.com/fast-plural-rules/-/fast-plural-rules-0.0.1.tgz#7032c8afa979e6dc65a452f0ef5b4ef31eca763b" integrity sha512-0Cxx7LaH7+dNJEBozlisCxqaN5g68VTFT9PyLeFGBHmkPnQ3e46zss+r8pRC94KpzPlitL6m33GVdbMIDiUgqg== -fast-redact@^1.4.2: - version "1.4.3" - resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-1.4.3.tgz#a11db5191e659354f8a431cca512d48d5f7951c7" - integrity sha512-x4qQsA2zOcVuUBHv80EURely8MiAOTR3Z6T1Od82LzFbthhq7DXVUdxwfxtvP9hNCvd+rdcY9qMipK0YDTwWCw== +fast-redact@^1.4.2, fast-redact@^1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-1.4.4.tgz#d29bd1d0cc3ab808a9d4f9870f6e27e85c750db4" + integrity sha512-QOQZ8sDDQPZMJ6x6zlm6hLZ2cjPDqfN3R/AYnAbM+yy8VNPvOnVXdUF/E/xbMv7g44c1krhWuzgjH2u0V5Vhsg== fast-safe-stringify@2.0.x, fast-safe-stringify@^2.0.4, fast-safe-stringify@^2.0.6: version "2.0.6" @@ -5938,9 +5844,9 @@ fast-safe-stringify@2.0.x, fast-safe-stringify@^2.0.4, fast-safe-stringify@^2.0. integrity sha512-q8BZ89jjc+mz08rSxROs8VsrBBcn1SIw1kq9NjolL509tkABRk9io01RAjSaEv1Xb2uFLt8VtRiZbGp5H8iDtg== fast-sort@^1.5.4: - version "1.5.5" - resolved "https://registry.yarnpkg.com/fast-sort/-/fast-sort-1.5.5.tgz#83e962f53bcb716440a3231fd6d0b811b345c2a0" - integrity sha512-3yDkva6FYchpKvwC7HglDSod+w9a7LslOzhRonL5w9IXVA3zRcgujPpGWrnWG2hm4pD00VMZ8jUIAaWz3AqGDw== + version "1.5.6" + resolved "https://registry.yarnpkg.com/fast-sort/-/fast-sort-1.5.6.tgz#fe5dbe919f2f2333d1823808d6d1e7c3b8c562cc" + integrity sha512-/UihlqTK3c6uie43pn5+tciRncoUBgMFXcxrqqmeZrBTmTQM+TeZ4YW2sZlTyzMICg4skHBTbMlK3OvEIy11ew== fast.js@^0.1.1: version "0.1.1" @@ -5996,7 +5902,7 @@ filename-regex@^2.0.0: resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" integrity sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY= -fileset@^2.0.2: +fileset@^2.0.2, fileset@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/fileset/-/fileset-2.0.3.tgz#8e7548a96d3cc2327ee5e674168723a333bba2a0" integrity sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA= @@ -6025,19 +5931,6 @@ fill-range@^4.0.0: repeat-string "^1.6.1" to-regex-range "^2.1.0" -finalhandler@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" - integrity sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.2" - statuses "~1.4.0" - unpipe "~1.0.0" - find-cache-dir@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.0.0.tgz#4c1faed59f45184530fb9d7fa123a4d04a98472d" @@ -6052,7 +5945,14 @@ find-parent-dir@^0.3.0: resolved "https://registry.yarnpkg.com/find-parent-dir/-/find-parent-dir-0.3.0.tgz#33c44b429ab2b2f0646299c5f9f718f376ff8d54" integrity sha1-M8RLQpqysvBkYpnF+fcY83b/jVQ= -find-up@1.1.2, find-up@^1.0.0: +find-up@3.0.0, find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +find-up@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8= @@ -6067,12 +5967,12 @@ find-up@^2.0.0, find-up@^2.1.0: dependencies: locate-path "^2.0.0" -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== +find@^0.2.4: + version "0.2.9" + resolved "https://registry.yarnpkg.com/find/-/find-0.2.9.tgz#4b73f1ff9e56ad91b76e716407fe5ffe6554bb8c" + integrity sha1-S3Px/55WrZG3bnFkB/5f/mVUu4w= dependencies: - locate-path "^3.0.0" + traverse-chain "~0.1.0" findup-sync@^2.0.0: version "2.0.0" @@ -6102,13 +6002,6 @@ fn-name@~2.0.1: resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-2.0.1.tgz#5214d7537a4d06a4a301c0cc262feb84188002e7" integrity sha1-UhTXU3pNBqSjAcDMJi/rhBiAAuc= -follow-redirects@^1.3.0: - version "1.6.1" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.6.1.tgz#514973c44b5757368bad8bddfe52f81f015c94cb" - integrity sha512-t2JCjbzxQpWvbhts3l6SH1DKzSrx8a+SsaVf4h6bG4kOXUuPYS/kg2Lr4gQSb7eemaHqJkOThF1BGyjlUkO1GQ== - dependencies: - debug "=3.1.0" - for-in@^0.1.3: version "0.1.8" resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1" @@ -6138,15 +6031,6 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= -form-data@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.0.0.tgz#6f0aebadcc5da16c13e1ecc11137d85f9b883b25" - integrity sha1-bwrrrcxdoWwT4ezBETfYX5uIOyU= - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.5" - mime-types "^2.1.11" - form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -6156,11 +6040,6 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" -forwarded@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" - integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= - fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -6168,11 +6047,6 @@ fragment-cache@^0.2.1: dependencies: map-cache "^0.2.2" -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= - from2@^2.1.0, from2@^2.1.1: version "2.3.0" resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" @@ -6181,10 +6055,10 @@ from2@^2.1.0, from2@^2.1.1: inherits "^2.0.1" readable-stream "^2.0.0" -fs-capacitor@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fs-capacitor/-/fs-capacitor-2.0.0.tgz#6cbafaa39313eebf9c49ecff8795aadc08337fc5" - integrity sha512-CIJZpxbVWhO+qyODeCR55Q+6vj0p2oL8DAWd/DZi3Ev+25PimUoScw07K0fPgluaH3lFoqNvwW13BDYfHWFQJw== +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== fs-extra@^6.0.1: version "6.0.1" @@ -6211,16 +6085,7 @@ fs-minipass@^1.2.5: dependencies: minipass "^2.2.1" -fs-vacuum@~1.2.9: - version "1.2.10" - resolved "https://registry.yarnpkg.com/fs-vacuum/-/fs-vacuum-1.2.10.tgz#b7629bec07a4031a2548fdf99f5ecf1cc8b31e36" - integrity sha1-t2Kb7AekAxolSP35n17PHMizHjY= - dependencies: - graceful-fs "^4.1.2" - path-is-inside "^1.0.1" - rimraf "^2.5.2" - -fs-write-stream-atomic@^1.0.8, fs-write-stream-atomic@~1.0.8: +fs-write-stream-atomic@^1.0.8: version "1.0.10" resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" integrity sha1-tH31NJPvkR33VzHnCp3tAYnbQMk= @@ -6243,24 +6108,7 @@ fsevents@^1.2.3, fsevents@^1.2.7: nan "^2.9.2" node-pre-gyp "^0.10.0" -fstream-ignore@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" - integrity sha1-nDHa40dnAY/h0kmyTa2mfQktoQU= - dependencies: - fstream "^1.0.0" - inherits "2" - minimatch "^3.0.0" - -fstream-npm@~1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/fstream-npm/-/fstream-npm-1.2.1.tgz#08c4a452f789dcbac4c89a4563c902b2c862fd5b" - integrity sha512-iBHpm/LmD1qw0TlHMAqVd9rwdU6M+EHRUnPkXpRi5G/Hf0FIFH+oZFryodAU2MFNfGRh/CzhUFlMKV3pdeOTDw== - dependencies: - fstream-ignore "^1.0.0" - inherits "2" - -fstream@^1.0.0, fstream@^1.0.2, fstream@~1.0.10: +fstream@^1.0.0, fstream@^1.0.2: version "1.0.11" resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" integrity sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE= @@ -6292,22 +6140,7 @@ g-status@^2.0.2: matcher "^1.0.0" simple-git "^1.85.0" -gauge@~2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.6.0.tgz#d35301ad18e96902b4751dcbbe40f4218b942a46" - integrity sha1-01MBrRjpaQK0dR3LvkD0IYuUKkY= - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-color "^0.1.7" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - -gauge@~2.7.1, gauge@~2.7.3: +gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= @@ -6321,25 +6154,6 @@ gauge@~2.7.1, gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" -generate-function@^2.0.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f" - integrity sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ== - dependencies: - is-property "^1.0.2" - -generate-object-property@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" - integrity sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA= - dependencies: - is-property "^1.0.0" - -generic-pool@^3.4.0: - version "3.6.1" - resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-3.6.1.tgz#a51a8439ee86f0bbcf100fc1db3f45c86289deb4" - integrity sha512-iMmD/pY4q0+V+f8o4twE9JPeqfNuX+gJAaIPB3B0W1lFkBOtTxBo6B0HxHPgGhzQA8jego7EWopcYq/UDJO2KA== - genfun@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/genfun/-/genfun-5.0.0.tgz#9dd9710a06900a5c4a5bf57aca5da4e52fe76537" @@ -6350,6 +6164,11 @@ get-caller-file@^1.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== +get-func-name@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" + integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= + get-own-enumerable-property-symbols@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.0.tgz#b877b49a5c16aefac3655f2ed2ea5b684df8d203" @@ -6376,11 +6195,6 @@ get-stdin@^4.0.1: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= -get-stdin@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398" - integrity sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g= - get-stdin@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" @@ -6483,6 +6297,13 @@ gitconfiglocal@^1.0.0: dependencies: ini "^1.3.2" +github-slugger@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.2.1.tgz#47e904e70bf2dccd0014748142d31126cfd49508" + integrity sha512-SsZUjg/P03KPzQBt7OxJPasGw6NRO5uOgiZ5RGXVud5iSIZ0eNZeNp5rTwCxtavrRUa/A77j8mePVc5lEvk0KQ== + dependencies: + emoji-regex ">=6.0.0 <=6.1.1" + glob-base@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" @@ -6511,7 +6332,7 @@ glob-to-regexp@^0.3.0: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= -glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@~7.1.0: +glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3: version "7.1.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== @@ -6589,6 +6410,20 @@ globby@^8.0.1: pify "^3.0.0" slash "^1.0.0" +globby@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-9.1.0.tgz#e90f4d5134109e6d855abdd31bdb1b085428592e" + integrity sha512-VtYjhHr7ncls724Of5W6Kaahz0ag7dB4G62/2HsN+xEKG6SrPzM1AJMerGxQTwJGnN9reeyxdvXbuZYpfssCvg== + dependencies: + "@types/glob" "^7.1.1" + array-union "^1.0.2" + dir-glob "^2.2.1" + fast-glob "^2.2.6" + glob "^7.1.3" + ignore "^4.0.3" + pify "^4.0.1" + slash "^2.0.0" + globrex@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" @@ -6662,7 +6497,7 @@ got@^8.3.1: url-parse-lax "^3.0.0" url-to-options "^1.0.1" -got@^9.3.2: +got@^9.3.2, got@^9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== @@ -6679,7 +6514,7 @@ got@^9.3.2: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@~4.1.9: +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6: version "4.1.15" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== @@ -6691,69 +6526,12 @@ graphlib@^2.1.1, graphlib@^2.1.5: dependencies: lodash "^4.17.5" -graphql-extensions@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.5.0.tgz#117b9872e5cafbebef5254ad795b41fa97037c14" - integrity sha512-2i0rpe4/D8jZj6XmxXGLFDAsGLhkFrSdpS5WfvTAzoXOc52hUAxNdsbgRQGeKMFhmanqA6FDXxO/s+BtPHChVA== - dependencies: - "@apollographql/apollo-tools" "^0.3.0" - -graphql-subscriptions@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/graphql-subscriptions/-/graphql-subscriptions-1.0.0.tgz#475267694b3bd465af6477dbab4263a3f62702b8" - integrity sha512-+ytmryoHF1LVf58NKEaNPRUzYyXplm120ntxfPcgOBC7TnK7Tv/4VRHeh4FAR9iL+O1bqhZs4nkibxQ+OA5cDQ== - dependencies: - iterall "^1.2.1" - -graphql-tag@^2.9.2: - version "2.10.1" - resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.1.tgz#10aa41f1cd8fae5373eaf11f1f67260a3cad5e02" - integrity sha512-jApXqWBzNXQ8jYa/HLkZJaVw9jgwNqZkywa2zfFn16Iv1Zb7ELNHkJaXHR7Quvd5SIGsy6Ny7SUKATgnu05uEg== - -graphql-tools-types@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/graphql-tools-types/-/graphql-tools-types-1.2.1.tgz#b8c277545d8623ae4e8944e7a869010ef879ffe1" - integrity sha512-wQj+SrM3T/s5s+mOpSJjxCNaJCdGCZwi1XvDizUHdUfgVBL4TP26FioRjtjmdi/psdWDktIEEqObiChQ1lYyxw== - dependencies: - "@babel/runtime-corejs2" "7.3.1" - ducky "2.7.0" - graphql "14.1.1" - pure-uuid "1.5.6" - -graphql-tools@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-4.0.4.tgz#ca08a63454221fdde825fe45fbd315eb2a6d566b" - integrity sha512-chF12etTIGVVGy3fCTJ1ivJX2KB7OSG4c6UOJQuqOHCmBQwTyNgCDuejZKvpYxNZiEx7bwIjrodDgDe9RIkjlw== - dependencies: - apollo-link "^1.2.3" - apollo-utilities "^1.0.1" - deprecated-decorator "^0.1.6" - iterall "^1.1.3" - uuid "^3.1.0" - -graphql-upload@^8.0.2: - version "8.0.4" - resolved "https://registry.yarnpkg.com/graphql-upload/-/graphql-upload-8.0.4.tgz#ed7cbde883b5cca493de77e39f95cddf40dfd514" - integrity sha512-jsTfVYXJ5mU6BXiiJ20CUCAcf41ICCQJ2ltwQFUuaFKiY4JhlG99uZZp5S3hbpQ/oA1kS7hz4pRtsnxPCa7Yfg== - dependencies: - busboy "^0.3.0" - fs-capacitor "^2.0.0" - http-errors "^1.7.1" - object-path "^0.11.4" - -graphql@14.1.1: - version "14.1.1" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.1.1.tgz#d5d77df4b19ef41538d7215d1e7a28834619fac0" - integrity sha512-C5zDzLqvfPAgTtP8AUPIt9keDabrdRAqSWjj2OPRKrKxI9Fb65I36s1uCs1UUBFnSWTdO7hyHi7z1ZbwKMKF6Q== - dependencies: - iterall "^1.2.2" - growly@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= -handlebars@4.x.x, handlebars@^4.0.2, handlebars@^4.0.3, handlebars@^4.0.6, handlebars@^4.1.0: +handlebars@*, handlebars@^4.0.3, handlebars@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.0.tgz#0d6a6f34ff1f63cecec8423aa4169827bf787c3a" integrity sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w== @@ -6764,10 +6542,10 @@ handlebars@4.x.x, handlebars@^4.0.2, handlebars@^4.0.3, handlebars@^4.0.6, handl optionalDependencies: uglify-js "^3.1.4" -hapi-pagination@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/hapi-pagination/-/hapi-pagination-2.0.1.tgz#aef33ec4510257612a0b9be3855f77aa6a9635a3" - integrity sha512-7X+d7DcQ/Lef8MZ5HxlMk7kRk0+6ne4aR5lDP6d9obFPZB4K/pfGqJWvhgQeVCkHwKYe0ts6StdCRPLo8EQ2Pw== +hapi-pagination@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/hapi-pagination/-/hapi-pagination-2.1.0.tgz#c8036b779ef12a9172101fd999c5ef619ef3b5a6" + integrity sha512-kESI1xiLKfhxh8gS88fXO6juqbPIwllnjXyQyDWMMRlvHMv03XDZQwj6nMdwru7epWy77/dVljQ8/CNY/YfmHQ== dependencies: boom "^7.1.1" hapi "^17.1.1" @@ -6852,16 +6630,6 @@ har-schema@^2.0.0: resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= -har-validator@~2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d" - integrity sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0= - dependencies: - chalk "^1.1.1" - commander "^2.9.0" - is-my-json-valid "^2.12.4" - pinkie-promise "^2.0.0" - har-validator@~5.1.0: version "5.1.3" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" @@ -6877,11 +6645,6 @@ has-ansi@^2.0.0: dependencies: ansi-regex "^2.0.0" -has-color@^0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/has-color/-/has-color-0.1.7.tgz#67144a5260c34fc3cca677d041daf52fe7b78b2f" - integrity sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8= - has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" @@ -6914,7 +6677,7 @@ has-to-string-tag-x@^1.2.0: dependencies: has-symbol-support-x "^1.4.1" -has-unicode@^2.0.0, has-unicode@^2.0.1, has-unicode@~2.0.1: +has-unicode@^2.0.0, has-unicode@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= @@ -6957,13 +6720,6 @@ has@^1.0.1, has@^1.0.3: dependencies: function-bind "^1.1.1" -hasbin@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/hasbin/-/hasbin-1.2.3.tgz#78c5926893c80215c2b568ae1fd3fcab7a2696b0" - integrity sha1-eMWSaJPIAhXCtWiuH9P8q3omlrA= - dependencies: - async "~1.5" - hash-base@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" @@ -6980,16 +6736,6 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" -hawk@~3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" - integrity sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ= - dependencies: - boom "2.x.x" - cryptiles "2.x.x" - hoek "2.x.x" - sntp "1.x.x" - heavy@6.x.x: version "6.1.2" resolved "https://registry.yarnpkg.com/heavy/-/heavy-6.1.2.tgz#e5d56f18170a37b01d4381bc07fece5edc68520b" @@ -6999,11 +6745,6 @@ heavy@6.x.x: hoek "6.x.x" joi "14.x.x" -highlight.js@^9.0.0: - version "9.14.2" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.14.2.tgz#efbfb22dc701406e4da406056ef8c2b70ebe5b26" - integrity sha512-Nc6YNECYpxyJABGYJAyw7dBAYbXEuIzwzkqoJnwbc1nIpCiN+3ioYf0XrBnLiyyG0JLuJhpPtt2iTSbXiKLoyA== - hmac-drbg@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -7013,11 +6754,6 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoek@2.x.x: - version "2.16.3" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" - integrity sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0= - hoek@4.2.x: version "4.2.1" resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb" @@ -7042,22 +6778,17 @@ home-or-tmp@^2.0.0: os-tmpdir "^1.0.1" homedir-polyfill@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc" - integrity sha1-TCu8inWJmP7r9e1oWA921GdotLw= + version "1.0.3" + resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" + integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== dependencies: parse-passwd "^1.0.0" -hosted-git-info@^2.1.4, hosted-git-info@^2.1.5, hosted-git-info@^2.4.2, hosted-git-info@^2.6.0, hosted-git-info@^2.7.1: +hosted-git-info@^2.1.4, hosted-git-info@^2.6.0, hosted-git-info@^2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" integrity sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w== -hosted-git-info@~2.1.5: - version "2.1.5" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.1.5.tgz#0ba81d90da2e25ab34a332e6ec77936e1598118b" - integrity sha1-C6gdkNouJas0ozLm7HeTbhWYEYs= - html-encoding-sniffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" @@ -7075,7 +6806,7 @@ http-cache-semantics@^4.0.0: resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz#495704773277eeef6e43f9ab2c2c7d259dda25c5" integrity sha512-TcIMG3qeVLgDr1TEd2XvHaTnMPwYQUQMIBLy+5pLSDKYFc7UIqj39w8EGzZkaxoLv/l2K8HaI0t5AVA+YYgUew== -http-call@^5.2.2: +http-call@^5.1.2, http-call@^5.2.2: version "5.2.3" resolved "https://registry.yarnpkg.com/http-call/-/http-call-5.2.3.tgz#4b59df8c313c92056e2004e666330b46f7d0532b" integrity sha512-IkwGruHVHATmnonLKMGX5tkpM0KSn/C240o8/OfBsESRaJacykSia+akhD0d3fljQ5rQPXtBvSrVShAsj+EOUQ== @@ -7086,7 +6817,7 @@ http-call@^5.2.2: is-stream "^1.1.0" tunnel-agent "^0.6.0" -http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3: +http-errors@1.6.3: version "1.6.3" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= @@ -7096,17 +6827,6 @@ http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3: setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" -http-errors@^1.7.1: - version "1.7.1" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.1.tgz#6a4ffe5d35188e1c39f872534690585852e1f027" - integrity sha512-jWEUgtZWGSMba9I1N3gc1HmvpBUaNC9vDdA46yScAdp+C5rdEuKWUBLWTQpW9FwSWSbYYs++b6SDCxf9UEJzfw== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - http-proxy-agent@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" @@ -7115,15 +6835,6 @@ http-proxy-agent@^2.1.0: agent-base "4" debug "3.1.0" -http-signature@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" - integrity sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8= - dependencies: - assert-plus "^0.2.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -7153,7 +6864,7 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" -husky@^1.3.0: +husky@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/husky/-/husky-1.3.1.tgz#26823e399300388ca2afff11cfa8a86b0033fae0" integrity sha512-86U6sVVVf4b5NYSZ0yvv88dRgBSSXXmHaiq5pP4KDj5JVzdwKgBjEtUPOm8hcoytezFwbU+7gotXNhpHdystlg== @@ -7201,7 +6912,7 @@ ieee754@^1.1.4, ieee754@^1.1.8: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b" integrity sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA== -iferr@^0.1.5, iferr@~0.1.5: +iferr@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= @@ -7218,6 +6929,11 @@ ignore@^3.3.5: resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== +ignore@^4.0.3: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" @@ -7279,24 +6995,7 @@ indexof@0.0.1: resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10= -inert@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/inert/-/inert-5.1.2.tgz#0c26f15bc22aae7af9c1f1a164bf867c58c5f4a6" - integrity sha512-5jSCKrQ7ENfdECnzLatCejXSkJwVzKFXZW30fI6TNHFbDuigT8IilRfydI2H5j9ZTnH7vXKO4WUg2qph9bItow== - dependencies: - ammo "3.x.x" - boom "7.x.x" - bounce "1.x.x" - hoek "6.x.x" - joi "14.x.x" - lru-cache "4.1.x" - -inflection@1.12.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.12.0.tgz#a200935656d6f5f6bc4dc7502e1aecb703228416" - integrity sha1-ogCTVlbW9fa8TcdQLhrstwMihBY= - -inflight@^1.0.4, inflight@~1.0.5: +inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= @@ -7314,7 +7013,7 @@ inherits@2.0.1: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= -ini@^1.3.0, ini@^1.3.2, ini@^1.3.4, ini@~1.3.0, ini@~1.3.4: +ini@^1.3.0, ini@^1.3.2, ini@^1.3.4, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== @@ -7333,20 +7032,6 @@ init-package-json@^1.10.3: validate-npm-package-license "^3.0.1" validate-npm-package-name "^3.0.0" -init-package-json@~1.9.4: - version "1.9.6" - resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-1.9.6.tgz#789fc2b74466a4952b9ea77c0575bc78ebd60a61" - integrity sha1-eJ/Ct0RmpJUrnqd8BXW8eOvWCmE= - dependencies: - glob "^7.1.1" - npm-package-arg "^4.0.0 || ^5.0.0" - promzard "^0.3.0" - read "~1.0.1" - read-package-json "1 || 2" - semver "2.x || 3.x || 4 || 5" - validate-npm-package-license "^3.0.1" - validate-npm-package-name "^3.0.0" - inquirer@^3.0.0: version "3.3.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" @@ -7396,7 +7081,7 @@ integer@^2.1.0: resolved "https://registry.yarnpkg.com/integer/-/integer-2.1.0.tgz#29134ea2f7ba3362ed4dbe6bcca992b1f18ff276" integrity sha512-vBtiSgrEiNocWvvZX1RVfeOKa2mCHLZQ2p9nkQkQZ/BvEiY+6CcUz0eyjvIiewjJoeNidzg2I+tpPJvpyspL1w== -interpret@^1.0.0, interpret@^1.1.0: +interpret@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== @@ -7426,21 +7111,11 @@ invert-kv@^2.0.0: resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== -ip-regex@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" - integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= - ip@^1.1.4, ip@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= -ipaddr.js@1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.8.0.tgz#eaa33d6ddd7ace8f7f6fe0c9ca0440e706738b1e" - integrity sha1-6qM9bd16zo9/b+DJygRA5wZzix4= - ipaddr.js@^1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65" @@ -7487,23 +7162,11 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" -is-bluebird@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-bluebird/-/is-bluebird-1.0.2.tgz#096439060f4aa411abee19143a84d6a55346d6e2" - integrity sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI= - is-buffer@^1.0.2, is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-builtin-module@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" - integrity sha1-VAVy0096wxGfj3bDDLwbHgN6/74= - dependencies: - builtin-modules "^1.0.0" - is-callable@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" @@ -7623,6 +7286,11 @@ is-generator-fn@^1.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-1.0.0.tgz#969d49e1bb3329f6bb7f09089be26578b2ddd46a" integrity sha1-lp1J4bszKfa7fwkIm+JleLLd1Go= +is-generator-fn@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.0.0.tgz#038c31b774709641bda678b1f06a4e3227c10b3e" + integrity sha512-elzyIdM7iKoFHzcrndIqjYomImhxrFRnGP3galODoII4TB9gI7mZ+FnlLQmmjf27SxHS2gKEeyhX5/+YRS6H9g== + is-glob@^2.0.0, is-glob@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" @@ -7652,22 +7320,6 @@ is-installed-globally@^0.1.0: global-dirs "^0.1.0" is-path-inside "^1.0.0" -is-my-ip-valid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824" - integrity sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ== - -is-my-json-valid@^2.12.4: - version "2.19.0" - resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.19.0.tgz#8fd6e40363cd06b963fa877d444bfb5eddc62175" - integrity sha512-mG0f/unGX1HZ5ep4uhRaPOS8EkAY8/j6mDRMJrutq4CqhoJWYp7qAlonIPy3TV7p3ju4TK9fo/PbnoksWmsp5Q== - dependencies: - generate-function "^2.0.0" - generate-object-property "^1.1.0" - is-my-ip-valid "^1.0.0" - jsonpointer "^4.0.0" - xtend "^4.0.0" - is-npm@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" @@ -7760,11 +7412,6 @@ is-promise@^2.1.0: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= -is-property@^1.0.0, is-property@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" - integrity sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ= - is-reachable@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-reachable/-/is-reachable-3.0.0.tgz#73ac3e3ff1d77af49b1dcd8d02a4bcf2721a4cec" @@ -7853,6 +7500,11 @@ is-wsl@^1.1.0: resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= +is_js@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/is_js/-/is_js-0.9.0.tgz#0ab94540502ba7afa24c856aa985561669e9c52d" + integrity sha1-CrlFQFArp6+iTIVqqYVWFmnpxS0= + isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" @@ -7914,11 +7566,35 @@ istanbul-api@^1.3.1: mkdirp "^0.5.1" once "^1.4.0" +istanbul-api@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-2.1.1.tgz#194b773f6d9cbc99a9258446848b0f988951c4d0" + integrity sha512-kVmYrehiwyeBAk/wE71tW6emzLiHGjYIiDrc8sfyty4F8M02/lrgXSm+R1kXysmF20zArvmZXjlE/mg24TVPJw== + dependencies: + async "^2.6.1" + compare-versions "^3.2.1" + fileset "^2.0.3" + istanbul-lib-coverage "^2.0.3" + istanbul-lib-hook "^2.0.3" + istanbul-lib-instrument "^3.1.0" + istanbul-lib-report "^2.0.4" + istanbul-lib-source-maps "^3.0.2" + istanbul-reports "^2.1.1" + js-yaml "^3.12.0" + make-dir "^1.3.0" + minimatch "^3.0.4" + once "^1.4.0" + istanbul-lib-coverage@^1.2.0, istanbul-lib-coverage@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz#ccf7edcd0a0bb9b8f729feeb0930470f9af664f0" integrity sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ== +istanbul-lib-coverage@^2.0.2, istanbul-lib-coverage@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#0b891e5ad42312c2b9488554f603795f9a2211ba" + integrity sha512-dKWuzRGCs4G+67VfW9pBFFz2Jpi4vSp/k7zBcJ888ofV5Mi1g5CUML5GvMvV6u9Cjybftu+E8Cgp+k0dI1E5lw== + istanbul-lib-hook@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz#bc6bf07f12a641fbf1c85391d0daa8f0aea6bf86" @@ -7926,6 +7602,13 @@ istanbul-lib-hook@^1.2.2: dependencies: append-transform "^0.4.0" +istanbul-lib-hook@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-2.0.3.tgz#e0e581e461c611be5d0e5ef31c5f0109759916fb" + integrity sha512-CLmEqwEhuCYtGcpNVJjLV1DQyVnIqavMLFHV/DP+np/g3qvdxu3gsPqYoJMXm15sN84xOlckFB3VNvRbf5yEgA== + dependencies: + append-transform "^1.0.0" + istanbul-lib-instrument@^1.10.1, istanbul-lib-instrument@^1.10.2: version "1.10.2" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz#1f55ed10ac3c47f2bdddd5307935126754d0a9ca" @@ -7939,6 +7622,19 @@ istanbul-lib-instrument@^1.10.1, istanbul-lib-instrument@^1.10.2: istanbul-lib-coverage "^1.2.1" semver "^5.3.0" +istanbul-lib-instrument@^3.0.0, istanbul-lib-instrument@^3.0.1, istanbul-lib-instrument@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.1.0.tgz#a2b5484a7d445f1f311e93190813fa56dfb62971" + integrity sha512-ooVllVGT38HIk8MxDj/OIHXSYvH+1tq/Vb38s8ixt9GoJadXska4WkGY+0wkmtYCZNYtaARniH/DixUGGLZ0uA== + dependencies: + "@babel/generator" "^7.0.0" + "@babel/parser" "^7.0.0" + "@babel/template" "^7.0.0" + "@babel/traverse" "^7.0.0" + "@babel/types" "^7.0.0" + istanbul-lib-coverage "^2.0.3" + semver "^5.5.0" + istanbul-lib-report@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz#f2a657fc6282f96170aaf281eb30a458f7f4170c" @@ -7949,6 +7645,15 @@ istanbul-lib-report@^1.1.5: path-parse "^1.0.5" supports-color "^3.1.2" +istanbul-lib-report@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.4.tgz#bfd324ee0c04f59119cb4f07dab157d09f24d7e4" + integrity sha512-sOiLZLAWpA0+3b5w5/dq0cjm2rrNdAfHWaGhmn7XEFW6X++IV9Ohn+pnELAl9K3rfpaeBfbmH9JU5sejacdLeA== + dependencies: + istanbul-lib-coverage "^2.0.3" + make-dir "^1.3.0" + supports-color "^6.0.0" + istanbul-lib-source-maps@^1.2.4, istanbul-lib-source-maps@^1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz#37b9ff661580f8fca11232752ee42e08c6675d8f" @@ -7960,6 +7665,17 @@ istanbul-lib-source-maps@^1.2.4, istanbul-lib-source-maps@^1.2.6: rimraf "^2.6.1" source-map "^0.5.3" +istanbul-lib-source-maps@^3.0.1, istanbul-lib-source-maps@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.2.tgz#f1e817229a9146e8424a28e5d69ba220fda34156" + integrity sha512-JX4v0CiKTGp9fZPmoxpu9YEkPbEqCqBbO3403VabKjH+NRXo72HafD5UgnjTEqHL2SAjaZK1XDuDOkn6I5QVfQ== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^2.0.3" + make-dir "^1.3.0" + rimraf "^2.6.2" + source-map "^0.6.1" + istanbul-reports@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.5.1.tgz#97e4dbf3b515e8c484caea15d6524eebd3ff4e1a" @@ -7967,6 +7683,13 @@ istanbul-reports@^1.5.1: dependencies: handlebars "^4.0.3" +istanbul-reports@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.1.1.tgz#72ef16b4ecb9a4a7bd0e2001e00f95d1eec8afa9" + integrity sha512-FzNahnidyEPBCI0HcufJoSEoKykesRlFcSzQqjH9x0+LC8tnnE/p/90PBLu8iZTxr8yYZNyTtiAujUqyN+CIxw== + dependencies: + handlebars "^4.1.0" + isurl@^1.0.0-alpha5: version "1.0.0" resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67" @@ -7980,11 +7703,6 @@ items@2.x.x: resolved "https://registry.yarnpkg.com/items/-/items-2.1.2.tgz#0849354595805d586dac98e7e6e85556ea838558" integrity sha512-kezcEqgB97BGeZZYtX/MA8AG410ptURstvnz5RAgyFZ8wQFPMxHY8GpTq+/ZHKT3frSlIthUq7EvLt9xn3TvXg== -iterall@^1.1.3, iterall@^1.2.1, iterall@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.2.2.tgz#92d70deb8028e0c39ff3164fdbf4d8b088130cd7" - integrity sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA== - jest-changed-files@^23.4.2: version "23.4.2" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-23.4.2.tgz#1eed688370cd5eebafe4ae93d34bb3b64968fe83" @@ -7992,6 +7710,15 @@ jest-changed-files@^23.4.2: dependencies: throat "^4.0.0" +jest-changed-files@^24.3.0: + version "24.3.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.3.0.tgz#7050ae29aaf1d59437c80f21d5b3cd354e88a499" + integrity sha512-fTq0YAUR6644fgsqLC7Zi2gXA/bAplMRvfXQdutmkwgrCKK6upkj+sgXqsUfUZRm15CVr3YSojr/GRNn71IMvg== + dependencies: + "@jest/types" "^24.3.0" + execa "^1.0.0" + throat "^4.0.0" + jest-cli@^23.6.0: version "23.6.0" resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-23.6.0.tgz#61ab917744338f443ef2baa282ddffdd658a5da4" @@ -8034,6 +7761,25 @@ jest-cli@^23.6.0: which "^1.2.12" yargs "^11.0.0" +jest-cli@^24.3.1: + version "24.3.1" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-24.3.1.tgz#52e4ae5f11044b41e06ca39fc7a7302fbbcb1661" + integrity sha512-HdwMgigvDQdlWX7gwM2QMkJJRqSk7tTYKq7kVplblK28RarqquJMWV/lOCN8CukuG9u3DZTeXpCDXR7kpGfB3w== + dependencies: + "@jest/core" "^24.3.1" + "@jest/test-result" "^24.3.0" + "@jest/types" "^24.3.0" + chalk "^2.0.1" + exit "^0.1.2" + import-local "^2.0.0" + is-ci "^2.0.0" + jest-config "^24.3.1" + jest-util "^24.3.0" + jest-validate "^24.3.1" + prompts "^2.0.1" + realpath-native "^1.1.0" + yargs "^12.0.2" + jest-config@^23.6.0: version "23.6.0" resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-23.6.0.tgz#f82546a90ade2d8c7026fbf6ac5207fc22f8eb1d" @@ -8054,6 +7800,28 @@ jest-config@^23.6.0: micromatch "^2.3.11" pretty-format "^23.6.0" +jest-config@^24.3.1: + version "24.3.1" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-24.3.1.tgz#271aff2d3aeabf1ff92512024eeca3323cd31a07" + integrity sha512-ujHQywsM//vKFvJwEC02KNZgKAGOzGz1bFPezmTQtuj8XdfsAVq8p6N/dw4yodXV11gSf6TJ075i4ehM+mKatA== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^24.3.0" + babel-jest "^24.3.1" + chalk "^2.0.1" + glob "^7.1.1" + jest-environment-jsdom "^24.3.1" + jest-environment-node "^24.3.1" + jest-get-type "^24.3.0" + jest-jasmine2 "^24.3.1" + jest-regex-util "^24.3.0" + jest-resolve "^24.3.1" + jest-util "^24.3.0" + jest-validate "^24.3.1" + micromatch "^3.1.10" + pretty-format "^24.3.1" + realpath-native "^1.1.0" + jest-diff@^23.6.0: version "23.6.0" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-23.6.0.tgz#1500f3f16e850bb3d71233408089be099f610c7d" @@ -8064,6 +7832,16 @@ jest-diff@^23.6.0: jest-get-type "^22.1.0" pretty-format "^23.6.0" +jest-diff@^24.3.1: + version "24.3.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.3.1.tgz#87952e5ea1548567da91df398fa7bf7977d3f96a" + integrity sha512-YRVzDguyzShP3Pb9wP/ykBkV7Z+O4wltrMZ2P4LBtNxrHNpxwI2DECrpD9XevxWubRy5jcE8sSkxyX3bS7W+rA== + dependencies: + chalk "^2.0.1" + diff-sequences "^24.3.0" + jest-get-type "^24.3.0" + pretty-format "^24.3.1" + jest-docblock@^23.2.0: version "23.2.0" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-23.2.0.tgz#f085e1f18548d99fdd69b20207e6fd55d91383a7" @@ -8071,6 +7849,13 @@ jest-docblock@^23.2.0: dependencies: detect-newline "^2.1.0" +jest-docblock@^24.3.0: + version "24.3.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.3.0.tgz#b9c32dac70f72e4464520d2ba4aec02ab14db5dd" + integrity sha512-nlANmF9Yq1dufhFlKG9rasfQlrY7wINJbo3q01tu56Jv5eBU5jirylhF2O5ZBnLxzOVBGRDz/9NAwNyBtG4Nyg== + dependencies: + detect-newline "^2.1.0" + jest-each@^23.6.0: version "23.6.0" resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-23.6.0.tgz#ba0c3a82a8054387016139c733a05242d3d71575" @@ -8079,6 +7864,17 @@ jest-each@^23.6.0: chalk "^2.0.1" pretty-format "^23.6.0" +jest-each@^24.3.1: + version "24.3.1" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.3.1.tgz#ed8fe8b9f92a835a6625ca8c7ee06bc904440316" + integrity sha512-GTi+nxDaWwSgOPLiiqb/p4LURy0mv3usoqsA2eoTYSmRsLgjgZ6VUyRpUBH5JY9EMBx33suNFXk0iyUm29WRpw== + dependencies: + "@jest/types" "^24.3.0" + chalk "^2.0.1" + jest-get-type "^24.3.0" + jest-util "^24.3.0" + pretty-format "^24.3.1" + jest-environment-jsdom@^23.4.0: version "23.4.0" resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-23.4.0.tgz#056a7952b3fea513ac62a140a2c368c79d9e6023" @@ -8088,6 +7884,18 @@ jest-environment-jsdom@^23.4.0: jest-util "^23.4.0" jsdom "^11.5.1" +jest-environment-jsdom@^24.3.1: + version "24.3.1" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.3.1.tgz#49826bcf12fb3e38895f1e2aaeb52bde603cc2e4" + integrity sha512-rz2OSYJiQerDqWDwjisqRwhVNpwkqFXdtyMzEuJ47Ip9NRpRQ+qy7/+zFujPUy/Z+zjWRO5seHLB/dOD4VpEVg== + dependencies: + "@jest/environment" "^24.3.1" + "@jest/fake-timers" "^24.3.0" + "@jest/types" "^24.3.0" + jest-mock "^24.3.0" + jest-util "^24.3.0" + jsdom "^11.5.1" + jest-environment-node@^23.4.0: version "23.4.0" resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-23.4.0.tgz#57e80ed0841dea303167cce8cd79521debafde10" @@ -8096,7 +7904,18 @@ jest-environment-node@^23.4.0: jest-mock "^23.2.0" jest-util "^23.4.0" -jest-extended@^0.11.0: +jest-environment-node@^24.3.1: + version "24.3.1" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.3.1.tgz#333d864c569b27658a96bb3b10e02e7172125415" + integrity sha512-Xy+/yFem/yUs9OkzbcawQT237vwDjBhAVLjac1KYAMYVjGb0Vb/Ovw4g61PunVdrEIpfcXNtRUltM4+9c7lARQ== + dependencies: + "@jest/environment" "^24.3.1" + "@jest/fake-timers" "^24.3.0" + "@jest/types" "^24.3.0" + jest-mock "^24.3.0" + jest-util "^24.3.0" + +jest-extended@^0.11.1: version "0.11.1" resolved "https://registry.yarnpkg.com/jest-extended/-/jest-extended-0.11.1.tgz#aad7cf5b3035ee0d058fefcef44c516bbfad66d6" integrity sha512-4klauyMgaoqMG27yu2HMGoQLVJ5ntJuJRgUKA/HS0oiGNBuSOkXNB7dxDtL83qYaBDMLVaOjy23QPLXFASUbVg== @@ -8110,6 +7929,11 @@ jest-get-type@^22.1.0, jest-get-type@^22.4.3: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-22.4.3.tgz#e3a8504d8479342dd4420236b322869f18900ce4" integrity sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w== +jest-get-type@^24.3.0: + version "24.3.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.3.0.tgz#582cfd1a4f91b5cdad1d43d2932f816d543c65da" + integrity sha512-HYF6pry72YUlVcvUx3sEpMRwXEWGEPlJ0bSPVnB3b3n++j4phUEoSPcS6GC0pPJ9rpyPSe4cb5muFo6D39cXow== + jest-haste-map@^23.6.0: version "23.6.0" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-23.6.0.tgz#2e3eb997814ca696d62afdb3f2529f5bbc935e16" @@ -8124,6 +7948,21 @@ jest-haste-map@^23.6.0: micromatch "^2.3.11" sane "^2.0.0" +jest-haste-map@^24.3.1: + version "24.3.1" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.3.1.tgz#b4a66dbe1e6bc45afb9cd19c083bff81cdd535a1" + integrity sha512-OTMQle+astr1lWKi62Ccmk2YWn6OtUoU/8JpJdg8zdsnpFIry/k0S4sQ4nWocdM07PFdvqcthWc78CkCE6sXvA== + dependencies: + "@jest/types" "^24.3.0" + fb-watchman "^2.0.0" + graceful-fs "^4.1.15" + invariant "^2.2.4" + jest-serializer "^24.3.0" + jest-util "^24.3.0" + jest-worker "^24.3.1" + micromatch "^3.1.10" + sane "^4.0.3" + jest-jasmine2@^23.6.0: version "23.6.0" resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-23.6.0.tgz#840e937f848a6c8638df24360ab869cc718592e0" @@ -8142,6 +7981,28 @@ jest-jasmine2@^23.6.0: jest-util "^23.4.0" pretty-format "^23.6.0" +jest-jasmine2@^24.3.1: + version "24.3.1" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.3.1.tgz#127d628d3ac0829bd3c0fccacb87193e543b420b" + integrity sha512-STo6ar1IyPlIPq9jPxDQhM7lC0dAX7KKN0LmCLMlgJeXwX+1XiVdtZDv1a4zyg6qhNdpo1arOBGY0BcovUK7ug== + dependencies: + "@babel/traverse" "^7.1.0" + "@jest/environment" "^24.3.1" + "@jest/test-result" "^24.3.0" + "@jest/types" "^24.3.0" + chalk "^2.0.1" + co "^4.6.0" + expect "^24.3.1" + is-generator-fn "^2.0.0" + jest-each "^24.3.1" + jest-matcher-utils "^24.3.1" + jest-message-util "^24.3.0" + jest-runtime "^24.3.1" + jest-snapshot "^24.3.1" + jest-util "^24.3.0" + pretty-format "^24.3.1" + throat "^4.0.0" + jest-leak-detector@^23.6.0: version "23.6.0" resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-23.6.0.tgz#e4230fd42cf381a1a1971237ad56897de7e171de" @@ -8149,6 +8010,13 @@ jest-leak-detector@^23.6.0: dependencies: pretty-format "^23.6.0" +jest-leak-detector@^24.3.1: + version "24.3.1" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-24.3.1.tgz#ed89d05ca07e91b2b51dac1f676ab354663aa8da" + integrity sha512-GncRwEtAw/SohdSyY4bk2RE06Ac1dZrtQGZQ2j35hSuN4gAAAKSYMszJS2WDixsAEaFN+GHBHG+d8pjVGklKyw== + dependencies: + pretty-format "^24.3.1" + jest-matcher-utils@^22.0.0: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-22.4.3.tgz#4632fe428ebc73ebc194d3c7b65d37b161f710ff" @@ -8167,6 +8035,16 @@ jest-matcher-utils@^23.6.0: jest-get-type "^22.1.0" pretty-format "^23.6.0" +jest-matcher-utils@^24.3.1: + version "24.3.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.3.1.tgz#025e1cd9c54a5fde68e74b12428775d06d123aa8" + integrity sha512-P5VIsUTJeI0FYvWVMwEHjxK1L83vEkDiKMV0XFPIrT2jzWaWPB2+dPCHkP2ID9z4eUKElaHqynZnJiOdNVHfXQ== + dependencies: + chalk "^2.0.1" + jest-diff "^24.3.1" + jest-get-type "^24.3.0" + pretty-format "^24.3.1" + jest-message-util@^23.4.0: version "23.4.0" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-23.4.0.tgz#17610c50942349508d01a3d1e0bda2c079086a9f" @@ -8178,6 +8056,20 @@ jest-message-util@^23.4.0: slash "^1.0.0" stack-utils "^1.0.1" +jest-message-util@^24.3.0: + version "24.3.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.3.0.tgz#e8f64b63ebc75b1a9c67ee35553752596e70d4a9" + integrity sha512-lXM0YgKYGqN5/eH1NGw4Ix+Pk2I9Y77beyRas7xM24n+XTTK3TbT0VkT3L/qiyS7WkW0YwyxoXnnAaGw4hsEDA== + dependencies: + "@babel/code-frame" "^7.0.0" + "@jest/test-result" "^24.3.0" + "@jest/types" "^24.3.0" + "@types/stack-utils" "^1.0.1" + chalk "^2.0.1" + micromatch "^3.1.10" + slash "^2.0.0" + stack-utils "^1.0.1" + jest-mock-process@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/jest-mock-process/-/jest-mock-process-1.1.0.tgz#0a6f2751bbbd1acc4aa5a3d2fdf707b5d68931a2" @@ -8190,11 +8082,23 @@ jest-mock@^23.2.0: resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-23.2.0.tgz#ad1c60f29e8719d47c26e1138098b6d18b261134" integrity sha1-rRxg8p6HGdR8JuETgJi20YsmETQ= +jest-mock@^24.3.0: + version "24.3.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.3.0.tgz#95a86b6ad474e3e33227e6dd7c4ff6b07e18d3cb" + integrity sha512-AhAo0qjbVWWGvcbW5nChFjR0ObQImvGtU6DodprNziDOt+pP0CBdht/sYcNIOXeim8083QUi9bC8QdKB8PTK4Q== + dependencies: + "@jest/types" "^24.3.0" + jest-regex-util@^23.3.0: version "23.3.0" resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-23.3.0.tgz#5f86729547c2785c4002ceaa8f849fe8ca471bc5" integrity sha1-X4ZylUfCeFxAAs6qj4Sf6MpHG8U= +jest-regex-util@^24.3.0: + version "24.3.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-24.3.0.tgz#d5a65f60be1ae3e310d5214a0307581995227b36" + integrity sha512-tXQR1NEOyGlfylyEjg1ImtScwMq8Oh3iJbGTjN7p0J23EuVX1MA8rwU69K4sLbCmwzgCUbVkm0FkSF9TdzOhtg== + jest-resolve-dependencies@^23.6.0: version "23.6.0" resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-23.6.0.tgz#b4526af24c8540d9a3fab102c15081cf509b723d" @@ -8203,6 +8107,15 @@ jest-resolve-dependencies@^23.6.0: jest-regex-util "^23.3.0" jest-snapshot "^23.6.0" +jest-resolve-dependencies@^24.3.1: + version "24.3.1" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-24.3.1.tgz#a22839d611ba529a74594ee274ce2b77d046bea9" + integrity sha512-9JUejNImGnJjbNR/ttnod+zQIWANpsrYMPt18s2tYGK6rP191qFsyEQ2BhAQMdYDRkTmi8At+Co9tL+jTPqdpw== + dependencies: + "@jest/types" "^24.3.0" + jest-regex-util "^24.3.0" + jest-snapshot "^24.3.1" + jest-resolve@^23.6.0: version "23.6.0" resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-23.6.0.tgz#cf1d1a24ce7ee7b23d661c33ba2150f3aebfa0ae" @@ -8212,6 +8125,16 @@ jest-resolve@^23.6.0: chalk "^2.0.1" realpath-native "^1.0.0" +jest-resolve@^24.3.1: + version "24.3.1" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-24.3.1.tgz#103dbd438b59618ea428ec4acbd65c56495ba397" + integrity sha512-N+Q3AcVuKxpn/kjQMxUVLwBk32ZE1diP4MPcHyjVwcKpCUuKrktfRR3Mqe/T2HoD25wyccstaqcPUKIudl41bg== + dependencies: + "@jest/types" "^24.3.0" + browser-resolve "^1.11.3" + chalk "^2.0.1" + realpath-native "^1.1.0" + jest-runner@^23.6.0: version "23.6.0" resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-23.6.0.tgz#3894bd219ffc3f3cb94dc48a4170a2e6f23a5a38" @@ -8231,6 +8154,31 @@ jest-runner@^23.6.0: source-map-support "^0.5.6" throat "^4.0.0" +jest-runner@^24.3.1: + version "24.3.1" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-24.3.1.tgz#5488566fa60cdb4b00a89c734ad6b54b9561415d" + integrity sha512-Etc9hQ5ruwg+q7DChm+E8qzHHdNTLeUdlo+whPQRSpNSgl0AEgc2r2mT4lxODREqmnHg9A8JHA44pIG4GE0Gzg== + dependencies: + "@jest/console" "^24.3.0" + "@jest/environment" "^24.3.1" + "@jest/test-result" "^24.3.0" + "@jest/types" "^24.3.0" + chalk "^2.4.2" + exit "^0.1.2" + graceful-fs "^4.1.15" + jest-config "^24.3.1" + jest-docblock "^24.3.0" + jest-haste-map "^24.3.1" + jest-jasmine2 "^24.3.1" + jest-leak-detector "^24.3.1" + jest-message-util "^24.3.0" + jest-resolve "^24.3.1" + jest-runtime "^24.3.1" + jest-util "^24.3.0" + jest-worker "^24.3.1" + source-map-support "^0.5.6" + throat "^4.0.0" + jest-runtime@^23.6.0: version "23.6.0" resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-23.6.0.tgz#059e58c8ab445917cd0e0d84ac2ba68de8f23082" @@ -8258,11 +8206,45 @@ jest-runtime@^23.6.0: write-file-atomic "^2.1.0" yargs "^11.0.0" +jest-runtime@^24.3.1: + version "24.3.1" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-24.3.1.tgz#2798230b4fbed594b375a13e395278694d4751e2" + integrity sha512-Qz/tJWbZ2naFJ2Kvy1p+RhhRgsPYh4e6wddVRy6aHBr32FTt3Ja33bfV7pkMFWXFbVuAsJMJVdengbvdhWzq4A== + dependencies: + "@jest/console" "^24.3.0" + "@jest/environment" "^24.3.1" + "@jest/source-map" "^24.3.0" + "@jest/transform" "^24.3.1" + "@jest/types" "^24.3.0" + "@types/yargs" "^12.0.2" + chalk "^2.0.1" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.1.15" + jest-config "^24.3.1" + jest-haste-map "^24.3.1" + jest-message-util "^24.3.0" + jest-mock "^24.3.0" + jest-regex-util "^24.3.0" + jest-resolve "^24.3.1" + jest-snapshot "^24.3.1" + jest-util "^24.3.0" + jest-validate "^24.3.1" + realpath-native "^1.1.0" + slash "^2.0.0" + strip-bom "^3.0.0" + yargs "^12.0.2" + jest-serializer@^23.0.1: version "23.0.1" resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-23.0.1.tgz#a3776aeb311e90fe83fab9e533e85102bd164165" integrity sha1-o3dq6zEekP6D+rnlM+hRAr0WQWU= +jest-serializer@^24.3.0: + version "24.3.0" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-24.3.0.tgz#074e307300d1451617cf2630d11543ee4f74a1c8" + integrity sha512-RiSpqo2OFbVLJN/PgAOwQIUeHDfss6NBUDTLhjiJM8Bb5rMrwRqHfkaqahIsOf9cXXB5UjcqDCzbQ7AIoMqWkg== + jest-snapshot@^23.6.0: version "23.6.0" resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-23.6.0.tgz#f9c2625d1b18acda01ec2d2b826c0ce58a5aa17a" @@ -8279,6 +8261,24 @@ jest-snapshot@^23.6.0: pretty-format "^23.6.0" semver "^5.5.0" +jest-snapshot@^24.3.1: + version "24.3.1" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-24.3.1.tgz#0f22a86c1b8c87e823f5ad095e82c19d9ed93d72" + integrity sha512-7wbNJWh0sBjmoaexTOWqS7nleTQME7o2W9XKU6CHCxG49Thjct4aVPC/QPNF5NHnvf4M/VDmudIDbwz6noJTRA== + dependencies: + "@babel/types" "^7.0.0" + "@jest/types" "^24.3.0" + chalk "^2.0.1" + expect "^24.3.1" + jest-diff "^24.3.1" + jest-matcher-utils "^24.3.1" + jest-message-util "^24.3.0" + jest-resolve "^24.3.1" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + pretty-format "^24.3.1" + semver "^5.5.0" + jest-util@^23.4.0: version "23.4.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-23.4.0.tgz#4d063cb927baf0a23831ff61bec2cbbf49793561" @@ -8293,6 +8293,25 @@ jest-util@^23.4.0: slash "^1.0.0" source-map "^0.6.0" +jest-util@^24.3.0: + version "24.3.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.3.0.tgz#a549ae9910fedbd4c5912b204bb1bcc122ea0057" + integrity sha512-eKIAC+MTKWZthUUVOwZ3Tc5a0cKMnxalQHr6qZ4kPzKn6k09sKvsmjCygqZ1SxVVfUKoa8Sfn6XDv9uTJ1iXTg== + dependencies: + "@jest/console" "^24.3.0" + "@jest/fake-timers" "^24.3.0" + "@jest/source-map" "^24.3.0" + "@jest/test-result" "^24.3.0" + "@jest/types" "^24.3.0" + "@types/node" "*" + callsites "^3.0.0" + chalk "^2.0.1" + graceful-fs "^4.1.15" + is-ci "^2.0.0" + mkdirp "^0.5.1" + slash "^2.0.0" + source-map "^0.6.0" + jest-validate@^23.6.0: version "23.6.0" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-23.6.0.tgz#36761f99d1ed33fcd425b4e4c5595d62b6597474" @@ -8303,6 +8322,18 @@ jest-validate@^23.6.0: leven "^2.1.0" pretty-format "^23.6.0" +jest-validate@^24.3.1: + version "24.3.1" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.3.1.tgz#9359eea5a767a3d20b4fa7a5764fd78330ba8312" + integrity sha512-ww3+IPNCOEMi1oKlrHdSnBXetXtdrrdSh0bqLNTVkWglduhORf94RJWd1ko9oEPU2TcEQS5QIPacYziQIUzc4A== + dependencies: + "@jest/types" "^24.3.0" + camelcase "^5.0.0" + chalk "^2.0.1" + jest-get-type "^24.3.0" + leven "^2.1.0" + pretty-format "^24.3.1" + jest-watcher@^23.4.0: version "23.4.0" resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-23.4.0.tgz#d2e28ce74f8dad6c6afc922b92cabef6ed05c91c" @@ -8312,6 +8343,20 @@ jest-watcher@^23.4.0: chalk "^2.0.1" string-length "^2.0.0" +jest-watcher@^24.3.0: + version "24.3.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-24.3.0.tgz#ee51c6afbe4b35a12fcf1107556db6756d7b9290" + integrity sha512-EpJS/aUG8D3DMuy9XNA4fnkKWy3DQdoWhY92ZUdlETIeEn1xya4Np/96MBSh4II5YvxwKe6JKwbu3Bnzfwa7vA== + dependencies: + "@jest/test-result" "^24.3.0" + "@jest/types" "^24.3.0" + "@types/node" "*" + "@types/yargs" "^12.0.9" + ansi-escapes "^3.0.0" + chalk "^2.0.1" + jest-util "^24.3.0" + string-length "^2.0.0" + jest-worker@^23.2.0: version "23.2.0" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-23.2.0.tgz#faf706a8da36fae60eb26957257fa7b5d8ea02b9" @@ -8319,7 +8364,16 @@ jest-worker@^23.2.0: dependencies: merge-stream "^1.0.1" -jest@^23.4.2, jest@^23.6.0: +jest-worker@^24.3.1: + version "24.3.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.3.1.tgz#c1759dd2b1d5541b09a2e5e1bc3288de6c9d8632" + integrity sha512-ZCoAe/iGLzTJvWHrO8fyx3bmEQhpL16SILJmWHKe8joHhyF3z00psF1sCRT54DoHw5GJG0ZpUtGy+ylvwA4haA== + dependencies: + "@types/node" "*" + merge-stream "^1.0.1" + supports-color "^6.1.0" + +jest@^23.4.2: version "23.6.0" resolved "https://registry.yarnpkg.com/jest/-/jest-23.6.0.tgz#ad5835e923ebf6e19e7a1d7529a432edfee7813d" integrity sha512-lWzcd+HSiqeuxyhG+EnZds6iO3Y3ZEnMrfZq/OTGvF/C+Z4fPMCdhWTGSAiO2Oym9rbEXfwddHhh6jqrTF3+Lw== @@ -8327,6 +8381,14 @@ jest@^23.4.2, jest@^23.6.0: import-local "^1.0.0" jest-cli "^23.6.0" +jest@^24.3.1: + version "24.3.1" + resolved "https://registry.yarnpkg.com/jest/-/jest-24.3.1.tgz#81959de0d57b2df923510f4fafe266712d37dcca" + integrity sha512-SqZguEbYNcZ3r0KUUBN+IkKfyPS1VBbIUiK4Wrc0AiGUR52gJa0fmlWSOCL3x25908QrfoQwkVDu5jCsfXb2ig== + dependencies: + import-local "^2.0.0" + jest-cli "^24.3.1" + jju@^1.1.0: version "1.4.0" resolved "https://registry.yarnpkg.com/jju/-/jju-1.4.0.tgz#a3abe2718af241a2b2904f84a625970f389ae32a" @@ -8370,10 +8432,10 @@ js-tokens@^3.0.2: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= -js-yaml@^3.12.0, js-yaml@^3.7.0, js-yaml@^3.9.0: - version "3.12.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.1.tgz#295c8632a18a23e054cf5c9d3cecafe678167600" - integrity sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA== +js-yaml@^3.12.0, js-yaml@^3.12.2, js-yaml@^3.7.0, js-yaml@^3.9.0: + version "3.12.2" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.2.tgz#ef1d067c5a9d9cb65bd72f285b5d8105c77f14fc" + integrity sha512-QHn/Lh/7HhZ/Twc7vJYQTkjuCa0kaCcDcjK5Zlk2rvnUpy7DxMJ23+Jc2dcyvltwQVg1nygAVlB2oRDFHoRS5Q== dependencies: argparse "^1.0.7" esprima "^4.0.0" @@ -8457,7 +8519,7 @@ json-schema@0.2.3: resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= -json-stringify-safe@5.x.x, json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: +json-stringify-safe@5.x.x, json-stringify-safe@^5.0.0, json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.0, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= @@ -8493,11 +8555,6 @@ jsonparse@^1.2.0: resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= -jsonpointer@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" - integrity sha1-T9kss04OnbPInIYi7PUfm5eMbLk= - jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -8509,15 +8566,14 @@ jsprim@^1.2.2: verror "1.10.0" jszip@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.1.5.tgz#e3c2a6c6d706ac6e603314036d43cd40beefdf37" - integrity sha512-5W8NUaFRFRqTOL7ZDDrx5qWHJyBXy6velVudIzQUSoqAAYqzSh2Z7/m0Rf1QbmQJccegD0r+YZxBjzqoBiEeJQ== + version "3.2.0" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.2.0.tgz#1c179e8692777490ca4e9b8f3ced08f9b820da2c" + integrity sha512-4WjbsaEtBK/DHeDZOPiPw5nzSGLDEDDreFRDEgnoMwmknPjTqa+23XuYFk6NiGbeiAeZCctiQ/X/z0lQBmDVOQ== dependencies: - core-js "~2.3.0" - es6-promise "~3.0.2" - lie "~3.1.0" + lie "~3.3.0" pako "~1.0.2" - readable-stream "~2.0.6" + readable-stream "~2.3.6" + set-immediate-shim "~1.0.1" keyv@3.0.0: version "3.0.0" @@ -8619,26 +8675,26 @@ left-pad@^1.3.0: resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e" integrity sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA== -lerna@^3.6.0: - version "3.11.1" - resolved "https://registry.yarnpkg.com/lerna/-/lerna-3.11.1.tgz#c3f86aaf6add615ffc172554657a139bc2360b9a" - integrity sha512-7an/cia9u6qVTts5PQ/adFq8QSgE7gzG1pUHhH+XKVU1seDKQ99JLu61n3/euv2qeQF+ww4WLKnFHIPa5+LJSQ== - dependencies: - "@lerna/add" "3.11.0" - "@lerna/bootstrap" "3.11.0" - "@lerna/changed" "3.11.1" - "@lerna/clean" "3.11.0" - "@lerna/cli" "3.11.0" - "@lerna/create" "3.11.0" - "@lerna/diff" "3.11.0" - "@lerna/exec" "3.11.0" - "@lerna/import" "3.11.0" - "@lerna/init" "3.11.0" - "@lerna/link" "3.11.0" - "@lerna/list" "3.11.0" - "@lerna/publish" "3.11.1" - "@lerna/run" "3.11.0" - "@lerna/version" "3.11.1" +lerna@^3.13.1: + version "3.13.1" + resolved "https://registry.yarnpkg.com/lerna/-/lerna-3.13.1.tgz#feaff562176f304bd82329ca29ce46ab6c033463" + integrity sha512-7kSz8LLozVsoUNTJzJzy+b8TnV9YdviR2Ee2PwGZSlVw3T1Rn7kOAPZjEi+3IWnOPC96zMPHVmjCmzQ4uubalw== + dependencies: + "@lerna/add" "3.13.1" + "@lerna/bootstrap" "3.13.1" + "@lerna/changed" "3.13.1" + "@lerna/clean" "3.13.1" + "@lerna/cli" "3.13.0" + "@lerna/create" "3.13.1" + "@lerna/diff" "3.13.1" + "@lerna/exec" "3.13.1" + "@lerna/import" "3.13.1" + "@lerna/init" "3.13.1" + "@lerna/link" "3.13.1" + "@lerna/list" "3.13.1" + "@lerna/publish" "3.13.1" + "@lerna/run" "3.13.1" + "@lerna/version" "3.13.1" import-local "^1.0.0" npmlog "^4.1.2" @@ -8680,19 +8736,18 @@ libnpmpublish@^1.1.1: semver "^5.5.1" ssri "^6.0.1" -lie@~3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e" - integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4= +lie@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" + integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== dependencies: immediate "~3.0.5" -lint-staged@^8.1.0: - version "8.1.3" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-8.1.3.tgz#bb069db5466c0fe16710216e633a84f2b362fa60" - integrity sha512-6TGkikL1B+6mIOuSNq2TV6oP21IhPMnV8q0cf9oYZ296ArTVNcbFh1l1pfVOHHbBIYLlziWNsQ2q45/ffmJ4AA== +lint-staged@^8.1.5: + version "8.1.5" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-8.1.5.tgz#372476fe1a58b8834eb562ed4c99126bd60bdd79" + integrity sha512-e5ZavfnSLcBJE1BTzRTqw6ly8OkqVyO3GL2M6teSmTBYQ/2BuueD5GIt2RPsP31u/vjKdexUyDCxSyK75q4BDA== dependencies: - "@iamstarkov/listr-update-renderer" "0.4.1" chalk "^2.3.1" commander "^2.14.1" cosmiconfig "^5.0.2" @@ -8705,7 +8760,8 @@ lint-staged@^8.1.0: is-glob "^4.0.0" is-windows "^1.0.2" listr "^0.14.2" - lodash "^4.17.5" + listr-update-renderer "^0.5.0" + lodash "^4.17.11" log-symbols "^2.2.0" micromatch "^3.1.8" npm-which "^3.0.1" @@ -8783,7 +8839,7 @@ load-json-file@^4.0.0: pify "^3.0.0" strip-bom "^3.0.0" -load-json-file@^5.1.0: +load-json-file@^5.0.0, load-json-file@^5.1.0: version "5.2.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-5.2.0.tgz#1553627a18bf7b08cd7ec232d63981239085a578" integrity sha512-HvjIlM2Y/RDHk1X6i4sGgaMTrAsnNrgQCJtuf5PEhbOV6MCJuMVZLMdlJRE0JGLMkF7b6O5zs9LcDxKIUt9CbQ== @@ -8823,36 +8879,11 @@ locate-path@^3.0.0: p-locate "^3.0.0" path-exists "^3.0.0" -lockfile@~1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/lockfile/-/lockfile-1.0.4.tgz#07f819d25ae48f87e538e6578b6964a4981a5609" - integrity sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA== - dependencies: - signal-exit "^3.0.2" - -lodash._baseuniq@~4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8" - integrity sha1-DrtE5FaBSveQXGIS+iybLVG4Qeg= - dependencies: - lodash._createset "~4.0.0" - lodash._root "~3.0.0" - -lodash._createset@~4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26" - integrity sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY= - lodash._reinterpolate@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= -lodash._root@~3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692" - integrity sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI= - lodash.assign@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" @@ -8878,7 +8909,7 @@ lodash.clone@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y= -lodash.clonedeep@^4.3.0, lodash.clonedeep@^4.5.0, lodash.clonedeep@~4.5.0: +lodash.clonedeep@^4.3.0, lodash.clonedeep@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= @@ -8888,11 +8919,6 @@ lodash.compact@^3.0.1: resolved "https://registry.yarnpkg.com/lodash.compact/-/lodash.compact-3.0.1.tgz#540ce3837745975807471e16b4a2ba21e7256ca5" integrity sha1-VAzjg3dFl1gHRx4WtKK6IeclbKU= -lodash.fill@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/lodash.fill/-/lodash.fill-3.4.0.tgz#a3c74ae640d053adf0dc2079f8720788e8bfef85" - integrity sha1-o8dK5kDQU63w3CB5+HIHiOi/74U= - lodash.flatten@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" @@ -9008,36 +9034,21 @@ lodash.templatesettings@^4.0.0: dependencies: lodash._reinterpolate "~3.0.0" -lodash.toarray@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" - integrity sha1-JMS/zWsvuji/0FlNsRedjptlZWE= - -lodash.union@~4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" - integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= - -lodash.uniq@^4.5.0, lodash.uniq@~4.5.0: +lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash.without@~4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac" - integrity sha1-PNRXSgC2e643OpS3SHcmQFB7eqw= +lodash@4, lodash@^4, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== lodash@4.1.x: version "4.1.0" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.1.0.tgz#299894283de01a9eefbedff4c4b9b00a6a2e6e96" integrity sha1-KZiUKD3gGp7vvt/0xLmwCmoubpY= -lodash@^4, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0: - version "4.17.11" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" - integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== - log-symbols@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18" @@ -9083,11 +9094,6 @@ logform@^2.1.1: ms "^2.1.1" triple-beam "^1.3.0" -long@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" - integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== - long@~3: version "3.2.0" resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b" @@ -9108,14 +9114,16 @@ loud-rejection@^1.0.0: currently-unhandled "^0.4.1" signal-exit "^3.0.0" -lout@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/lout/-/lout-11.1.0.tgz#4c48b6b068b42197acb6420d79b856095d174589" - integrity sha512-HAowXHOraHWEPrYPuNc7mR8v1Sn1Gnmy8uvpXYDidVHsTMlrkxqUZ1liBXGOf4eN22kVB+eG29NWymPhZG/wKg== +lowdb@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lowdb/-/lowdb-1.0.0.tgz#5243be6b22786ccce30e50c9a33eac36b20c8064" + integrity sha512-2+x8esE/Wb9SQ1F9IHaYWfsC9FIecLOPrK4g17FGEayjUWH172H6nwicRovGvSE2CPZouc2MCIqCI7h9d+GftQ== dependencies: - boom "7.x.x" - handlebars "4.x.x" - hoek "5.x.x" + graceful-fs "^4.1.3" + is-promise "^2.1.0" + lodash "4" + pify "^3.0.0" + steno "^0.4.1" lower-case@^1.1.1: version "1.1.4" @@ -9140,13 +9148,18 @@ lru-cache@4.1.x, lru-cache@^4.0.0, lru-cache@^4.0.1, lru-cache@^4.1.2, lru-cache pseudomap "^1.0.2" yallist "^2.1.2" -lru-cache@^5.0.0, lru-cache@^5.1.1: +lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== dependencies: yallist "^3.0.2" +lru-cache@~2.2.1: + version "2.2.4" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.2.4.tgz#6c658619becf14031d0d0b594b16042ce4dc063d" + integrity sha1-bGWGGb7PFAMdDQtZSxYELOTcBj0= + lru_map@0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd" @@ -9157,23 +9170,26 @@ lsmod@1.0.0: resolved "https://registry.yarnpkg.com/lsmod/-/lsmod-1.0.0.tgz#9a00f76dca36eb23fa05350afe1b585d4299e64b" integrity sha1-mgD3bco26yP6BTUK/htYXUKZ5ks= -macos-release@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-1.1.0.tgz#831945e29365b470aa8724b0ab36c8f8959d10fb" - integrity sha512-mmLbumEYMi5nXReB9js3WGsB8UE6cDBWyIO62Z4DNx6GbRhDxHNjA1MlzSpJ2S2KM1wyiPRA0d19uHWYYvMHjA== - macos-release@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.0.0.tgz#7dddf4caf79001a851eb4fba7fb6034f251276ab" integrity sha512-iCM3ZGeqIzlrH7KxYK+fphlJpCCczyHXc+HhRVbEu9uNTCrzYJjvvtefzeKTCVHd5AP/aD/fzC80JZ4ZP+dQ/A== -make-dir@^1.0.0: +make-dir@^1.0.0, make-dir@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== dependencies: pify "^3.0.0" +make-dir@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + make-error@1.x: version "1.3.5" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8" @@ -9203,6 +9219,11 @@ makeerror@1.0.x: dependencies: tmpl "1.0.x" +mamacro@^0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/mamacro/-/mamacro-0.0.3.tgz#ad2c9576197c9f1abf308d0787865bd975a3f3e4" + integrity sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA== + manakin@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/manakin/-/manakin-0.5.2.tgz#abe3df430ca6085f6983f6e4cf5af0298f4d30cc" @@ -9237,11 +9258,6 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -marked@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/marked/-/marked-0.4.0.tgz#9ad2c2a7a1791f10a852e0112f77b571dce10c66" - integrity sha512-tMsdNBgOsrUophCAFQl0XPe6Zqk/uy9gnue+jIIKhykO51hxyu6uNx7zBPy0+y/WKYVZZMspV9YeXLNdKk+iYw== - matcher@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/matcher/-/matcher-1.1.1.tgz#51d8301e138f840982b338b116bb0c09af62c1c2" @@ -9268,11 +9284,6 @@ media-type@^0.3.1: resolved "https://registry.yarnpkg.com/media-type/-/media-type-0.3.1.tgz#5d569cdd0c52d9c41c7c6451973edd267fb21bcb" integrity sha1-XVac3QxS2cQcfGRRlz7dJn+yG8s= -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= - mem@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" @@ -9343,11 +9354,6 @@ meow@^5.0.0: trim-newlines "^2.0.0" yargs-parser "^10.0.0" -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= - merge-stream@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-1.0.1.tgz#4041202d508a342ba00174008df0c251b8c135e1" @@ -9365,7 +9371,7 @@ merge@^1.2.0: resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145" integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ== -methods@~1.1.2: +methods@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= @@ -9416,27 +9422,17 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@1.x.x: +mime-db@1.x.x, mime-db@~1.38.0: version "1.38.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.38.0.tgz#1a2aab16da9eb167b49c6e4df2d9c68d63d8e2ad" integrity sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg== -mime-db@~1.37.0: - version "1.37.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8" - integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg== - -mime-types@^2.1.11, mime-types@^2.1.12, mime-types@~2.1.18, mime-types@~2.1.19, mime-types@~2.1.7: - version "2.1.21" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96" - integrity sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg== +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.22" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.22.tgz#fe6b355a190926ab7698c9a0556a11199b2199bd" + integrity sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog== dependencies: - mime-db "~1.37.0" - -mime@1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" - integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ== + mime-db "~1.38.0" mimic-fn@^1.0.0: version "1.2.0" @@ -9466,7 +9462,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: +minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.3, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -9543,7 +9539,7 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" -mkdirp@0.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: +mkdirp@0.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= @@ -9555,14 +9551,7 @@ modify-values@^1.0.0: resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== -moment-timezone@^0.5.14: - version "0.5.23" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.23.tgz#7cbb00db2c14c71b19303cb47b0fb0a6d8651463" - integrity sha512-WHFH85DkCfiNMDX5D3X7hpNH3/PUhjTGcD0U1SgfBGZxJ3qUmJh5FdvaFjcClxOvB3rzdfj4oRffbI38jEnC1w== - dependencies: - moment ">= 2.9.0" - -moment@2.x.x, "moment@>= 2.9.0", moment@>=2.14.0, moment@^2.11.2, moment@^2.20.0, moment@^2.22.1: +moment@2.x.x, moment@>=2.14.0, moment@^2.11.2, moment@^2.22.1: version "2.24.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== @@ -9579,10 +9568,10 @@ move-concurrently@^1.0.1: rimraf "^2.5.4" run-queue "^1.0.3" -mri@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.1.tgz#85aa26d3daeeeedf80dc5984af95cc5ca5cad9f1" - integrity sha1-haom09ru7t+A3FmEr5XMXKXK2fE= +mri@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.4.tgz#7cb1dd1b9b40905f1fac053abe25b6720f44744a" + integrity sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w== ms@2.0.0: version "2.0.0" @@ -9624,17 +9613,31 @@ mute-stream@~0.0.4: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== -nan@^2.10.0, nan@^2.2.1, nan@^2.9.2: +mz@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + +nan@^2.10.0, nan@^2.2.1, nan@^2.9.2, nan@~2.12.1: version "2.12.1" resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552" integrity sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw== +nan@^2.12.1: + version "2.13.2" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7" + integrity sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw== + nan@~2.10.0: version "2.10.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" integrity sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA== -nanomatch@^1.2.9: +nanomatch@^1.2.13, nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== @@ -9680,11 +9683,6 @@ needle@^2.2.1, needle@^2.2.4: iconv-lite "^0.4.4" sax "^1.2.4" -negotiator@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" - integrity sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk= - neo-async@^2.5.0: version "2.6.0" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.0.tgz#b9d15e4d71c6762908654b5183ed38b753340835" @@ -9695,15 +9693,32 @@ nested-error-stacks@^2.0.0: resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz#0fbdcf3e13fe4994781280524f8b96b0cdff9c61" integrity sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug== +nested-error-stacks@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.0.1.tgz#d2cc9fc5235ddb371fc44d506234339c8e4b0a4b" + integrity sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A== + netmask@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35" integrity sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU= -next-tick@1: - version "1.0.0" - resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" - integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= +newrelic@^5.6.1: + version "5.6.2" + resolved "https://registry.yarnpkg.com/newrelic/-/newrelic-5.6.2.tgz#3577fe80588cc7a997079d4011824a9f676e8fa5" + integrity sha512-KEauza5GOYVUIIhFxJPU6s6LDI9v1dfcXSBQ0CrtGmQ67z8v4bcF0mfdw6zlPVXdBTORm9dPBnHc6Jrk3VKHiA== + dependencies: + "@newrelic/koa" "^1.0.8" + "@newrelic/superagent" "^1.0.2" + "@tyriar/fibonacci-heap" "^2.0.7" + async "^2.1.4" + concat-stream "^2.0.0" + https-proxy-agent "^2.2.1" + json-stringify-safe "^5.0.0" + readable-stream "^3.1.1" + semver "^5.3.0" + optionalDependencies: + "@newrelic/native-metrics" "^4.0.0" nice-try@^1.0.4: version "1.0.5" @@ -9725,6 +9740,36 @@ no-case@^2.2.0: dependencies: lower-case "^1.1.1" +nock@^10.0.6: + version "10.0.6" + resolved "https://registry.yarnpkg.com/nock/-/nock-10.0.6.tgz#e6d90ee7a68b8cfc2ab7f6127e7d99aa7d13d111" + integrity sha512-b47OWj1qf/LqSQYnmokNWM8D88KvUl2y7jT0567NB3ZBAZFz2bWp2PC81Xn7u8F2/vJxzkzNZybnemeFa7AZ2w== + dependencies: + chai "^4.1.2" + debug "^4.1.0" + deep-equal "^1.0.0" + json-stringify-safe "^5.0.1" + lodash "^4.17.5" + mkdirp "^0.5.0" + propagate "^1.0.0" + qs "^6.5.1" + semver "^5.5.0" + +nock@~9: + version "9.6.1" + resolved "https://registry.yarnpkg.com/nock/-/nock-9.6.1.tgz#d96e099be9bc1d0189a77f4490bbbb265c381b49" + integrity sha512-EDgl/WgNQ0C1BZZlASOQkQdE6tAWXJi8QQlugqzN64JJkvZ7ILijZuG24r4vCC7yOfnm6HKpne5AGExLGCeBWg== + dependencies: + chai "^4.1.2" + debug "^3.1.0" + deep-equal "^1.0.0" + json-stringify-safe "^5.0.1" + lodash "^4.17.5" + mkdirp "^0.5.0" + propagate "^1.0.0" + qs "^6.5.1" + semver "^5.5.0" + node-alias@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/node-alias/-/node-alias-1.0.4.tgz#1f1b916b56b9ea241c0135f97ced6940f556f292" @@ -9733,13 +9778,6 @@ node-alias@^1.0.4: chalk "^1.1.1" lodash "^4.2.0" -node-emoji@^1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.8.1.tgz#6eec6bfb07421e2148c75c6bba72421f8530a826" - integrity sha512-+ktMAh1Jwas+TnGodfCfjUbJKoANqPaJFN0z0iqh41eqD8dvguNzcitVSBSVK1pidz0AqGbLKcoVuVLRVZ/aVg== - dependencies: - lodash.toarray "^4.4.0" - node-fetch-npm@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz#7258c9046182dca345b4208eda918daf33697ff7" @@ -9749,15 +9787,15 @@ node-fetch-npm@^2.0.2: json-parse-better-errors "^1.0.0" safe-buffer "^5.1.1" -node-fetch@^2.1.2, node-fetch@^2.2.0, node-fetch@^2.3.0: +node-fetch@^2.2.0, node-fetch@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.3.0.tgz#1a1d940bbfb916a1d3e0219f037e89e71f8c5fa5" integrity sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA== -node-forge@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.8.0.tgz#b897a89f25fe85ed7d0e3bd0c744c62931254dca" - integrity sha512-DVrvVeXwnSSX0Bgi9jy8p4IXQKLhGRQltG+UTR3Oci3Wb/zIROMoxw9im/K5s6KnNMheSWgG8K4qz8Njccdj3g== +node-forge@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.8.1.tgz#67aeb9df7bb78d15444ec04a0162d2c565559c37" + integrity sha512-C/42HVb5eRtnDgRKOFx4bJH6LwbGQwbEHOC/trQwQSR6xWAUR/jlWoUJnxOmFTvdmDIZtjl2VH4dl3VpQuIz5g== node-gyp@^3.8.0: version "3.8.0" @@ -9777,26 +9815,6 @@ node-gyp@^3.8.0: tar "^2.0.0" which "1" -node-gyp@~3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.4.0.tgz#dda558393b3ecbbe24c9e6b8703c71194c63fa36" - integrity sha1-3aVYOTs+y74kyea4cDxxGUxj+jY= - dependencies: - fstream "^1.0.0" - glob "^7.0.3" - graceful-fs "^4.1.2" - minimatch "^3.0.2" - mkdirp "^0.5.0" - nopt "2 || 3" - npmlog "0 || 1 || 2 || 3" - osenv "0" - path-array "^1.0.0" - request "2" - rimraf "2" - semver "2.x || 3.x || 4 || 5" - tar "^2.0.0" - which "1" - node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -9831,6 +9849,11 @@ node-libs-browser@^2.0.0: util "^0.11.0" vm-browserify "0.0.4" +node-modules-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" + integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= + node-notifier@^5.2.1: version "5.4.0" resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.4.0.tgz#7b455fdce9f7de0c63538297354f3db468426e6a" @@ -9858,35 +9881,21 @@ node-pre-gyp@^0.10.0, node-pre-gyp@^0.10.3: semver "^5.3.0" tar "^4" -node-pre-gyp@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz#db1f33215272f692cd38f03238e3e9b47c5dd054" - integrity sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q== +node-releases@^1.1.8: + version "1.1.9" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.9.tgz#70d0985ec4bf7de9f08fc481f5dae111889ca482" + integrity sha512-oic3GT4OtbWWKfRolz5Syw0Xus0KRFxeorLNj0s93ofX6PWyuzKjsiGxsCtWktBwwmTF6DdRRf2KreGqeOk5KA== dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.1" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" semver "^5.3.0" - tar "^4" -node-releases@^1.1.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.7.tgz#b09a10394d0ed8f7778f72bb861dde68b146303b" - integrity sha512-bKdrwaqJUPHqlCzDD7so/R+Nk0jGv9a11ZhLrD9f6i947qGLrGAhU3OxRENa19QQmwzGy/g6zCDEuLGDO8HPvA== +nodejs-tail@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/nodejs-tail/-/nodejs-tail-1.1.0.tgz#5e4316b079c4ec922e95ad8ac1f0a980ae7e0264" + integrity sha512-Gt2fi69P9tgdj41Xl9vFoLVcmnXW9uKQbWJsfgParmQZAPFnoBPHiLNoLY1ytF720UGozVD0tg6cAESP9hUrgA== dependencies: - semver "^5.3.0" - -node-uuid@~1.4.7: - version "1.4.8" - resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.8.tgz#b040eb0923968afabf8d32fb1f17f1167fdab907" - integrity sha1-sEDrCSOWivq/jTL7HxfxFn/auQc= + chokidar "2.1.2" -"nopt@2 || 3", nopt@~3.0.6: +"nopt@2 || 3": version "3.0.6" resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k= @@ -9901,12 +9910,7 @@ nopt@^4.0.1: abbrev "1" osenv "^0.1.4" -normalize-git-url@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/normalize-git-url/-/normalize-git-url-3.0.2.tgz#8e5f14be0bdaedb73e07200310aa416c27350fc4" - integrity sha1-jl8Uvgva7bc+ByADEKpBbCc1D8Q= - -normalize-package-data@^2.0.0, normalize-package-data@^2.3.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.3.5, normalize-package-data@^2.4.0, "normalize-package-data@~1.0.1 || ^2.0.0": +normalize-package-data@^2.0.0, normalize-package-data@^2.3.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.3.5, normalize-package-data@^2.4.0, normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== @@ -9916,16 +9920,6 @@ normalize-package-data@^2.0.0, normalize-package-data@^2.3.0, normalize-package- semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" -normalize-package-data@~2.3.5: - version "2.3.8" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.3.8.tgz#d819eda2a9dedbd1ffa563ea4071d936782295bb" - integrity sha1-2Bntoqne29H/pWPqQHHZNngilbs= - dependencies: - hosted-git-info "^2.1.4" - is-builtin-module "^1.0.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - normalize-path@^2.0.1, normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" @@ -9957,41 +9951,28 @@ npm-bundled@^1.0.1: resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g== -npm-cache-filename@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/npm-cache-filename/-/npm-cache-filename-1.0.2.tgz#ded306c5b0bfc870a9e9faf823bc5f283e05ae11" - integrity sha1-3tMGxbC/yHCp6fr4I7xfKD4FrhE= - -npm-check-updates@^2.15.0: - version "2.15.0" - resolved "https://registry.yarnpkg.com/npm-check-updates/-/npm-check-updates-2.15.0.tgz#7c0f540c048fc801bfd11add8708479a6b82c32e" - integrity sha512-0ZUokfgS+4uPVSVWP3CMie7kQwngiQag2e70CdcnUfBM7/tOUmxb0DDMfxqshw4MvgqlPsxSYprzKaBGlvXqnA== +npm-check-updates@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/npm-check-updates/-/npm-check-updates-3.0.2.tgz#2bef3d981d0a2c6e2bcf2d950979f4afbfabf585" + integrity sha512-1FSBF3s9fAz+Dg1jW668oV04apVvrIIk0gfKvEt+3zYKCa7113BhiJsba5/pL+x1+F1zUU7F60Iq4QDnU5v0jw== dependencies: - bluebird "^3.4.3" - chalk "^1.1.3" + bluebird "^3.5.3" + chalk "^2.4.2" cint "^8.2.1" cli-table "^0.3.1" - commander "^2.9.0" - fast-diff "^1.0.1" - find-up "1.1.2" - get-stdin "^5.0.1" + commander "^2.19.0" + fast-diff "^1.2.0" + find-up "3.0.0" + get-stdin "^6.0.0" json-parse-helpfulerror "^1.0.3" - lodash "^4.15.0" + lodash "^4.17.11" node-alias "^1.0.4" - npm "^3.10.6" - npmi "^2.0.1" - rc-config-loader "^2.0.1" - semver "^5.3.0" - semver-utils "^1.1.1" + rc-config-loader "^2.0.2" + requireg "^0.2.1" + semver "^5.6.0" + semver-utils "^1.1.4" spawn-please "^0.3.0" - update-notifier "^2.2.0" - -npm-install-checks@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/npm-install-checks/-/npm-install-checks-3.0.0.tgz#d4aecdfd51a53e3723b7b2f93b2ee28e307bc0d7" - integrity sha1-1K7N/VGlPjcjt7L5Oy7ijjB7wNc= - dependencies: - semver "^2.3.0 || 3.x || 4 || 5" + update-notifier "^2.5.0" npm-lifecycle@^2.1.0: version "2.1.0" @@ -10007,24 +9988,6 @@ npm-lifecycle@^2.1.0: umask "^1.1.0" which "^1.3.1" -"npm-package-arg@^3.0.0 || ^4.0.0", npm-package-arg@^4.1.1, npm-package-arg@~4.2.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-4.2.1.tgz#593303fdea85f7c422775f17f9eb7670f680e3ec" - integrity sha1-WTMD/eqF98Qid18X+et2cPaA4+w= - dependencies: - hosted-git-info "^2.1.5" - semver "^5.1.0" - -"npm-package-arg@^4.0.0 || ^5.0.0": - version "5.1.2" - resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-5.1.2.tgz#fb18d17bb61e60900d6312619919bd753755ab37" - integrity sha512-wJBsrf0qpypPT7A0LART18hCdyhpCMxeTtcb0X4IZO2jsP6Om7EHN1d9KSKiqD+KVH030RVNpWS9thk+pb7wzA== - dependencies: - hosted-git-info "^2.4.2" - osenv "^0.1.4" - semver "^5.1.0" - validate-npm-package-name "^3.0.0" - "npm-package-arg@^4.0.0 || ^5.0.0 || ^6.0.0", npm-package-arg@^6.0.0, npm-package-arg@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-6.1.0.tgz#15ae1e2758a5027efb4c250554b85a737db7fcc1" @@ -10035,10 +9998,10 @@ npm-lifecycle@^2.1.0: semver "^5.5.0" validate-npm-package-name "^3.0.0" -npm-packlist@^1.1.12, npm-packlist@^1.1.6: - version "1.3.0" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.3.0.tgz#7f01e8e44408341379ca98cfd756e7b29bd2626c" - integrity sha512-qPBc6CnxEzpOcc4bjoIBJbYdy0D/LFFPUdxvfwor4/w3vxeE0h6TiOVurCEPpQ6trjN77u/ShyfeJGsbAfB3dA== +npm-packlist@^1.1.12, npm-packlist@^1.1.6, npm-packlist@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.1.tgz#19064cdf988da80ea3cee45533879d90192bbfbc" + integrity sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw== dependencies: ignore-walk "^3.0.1" npm-bundled "^1.0.1" @@ -10059,23 +10022,6 @@ npm-pick-manifest@^2.2.3: npm-package-arg "^6.0.0" semver "^5.4.1" -npm-registry-client@~7.2.1: - version "7.2.1" - resolved "https://registry.yarnpkg.com/npm-registry-client/-/npm-registry-client-7.2.1.tgz#c792266b088cc313f8525e7e35248626c723db75" - integrity sha1-x5ImawiMwxP4Ul5+NSSGJscj23U= - dependencies: - concat-stream "^1.5.2" - graceful-fs "^4.1.6" - normalize-package-data "~1.0.1 || ^2.0.0" - npm-package-arg "^3.0.0 || ^4.0.0" - once "^1.3.3" - request "^2.74.0" - retry "^0.10.0" - semver "2 >=2.2.1 || 3.x || 4 || 5" - slide "^1.1.3" - optionalDependencies: - npmlog "~2.0.0 || ~3.1.0" - npm-registry-fetch@^3.8.0, npm-registry-fetch@^3.9.0: version "3.9.0" resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-3.9.0.tgz#44d841780e2833f06accb34488f8c7450d1a6856" @@ -10095,11 +10041,6 @@ npm-run-path@^2.0.0, npm-run-path@^2.0.2: dependencies: path-key "^2.0.0" -npm-user-validate@~0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/npm-user-validate/-/npm-user-validate-0.1.5.tgz#52465d50c2d20294a57125b996baedbf56c5004b" - integrity sha1-UkZdUMLSApSlcSW5lrrtv1bFAEs= - npm-which@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/npm-which/-/npm-which-3.0.1.tgz#9225f26ec3a285c209cae67c3b11a6b4ab7140aa" @@ -10109,101 +10050,6 @@ npm-which@^3.0.1: npm-path "^2.0.2" which "^1.2.10" -npm@^3, npm@^3.10.6: - version "3.10.10" - resolved "https://registry.yarnpkg.com/npm/-/npm-3.10.10.tgz#5b1d577e4c8869d6c8603bc89e9cd1637303e46e" - integrity sha1-Wx1XfkyIadbIYDvInpzRY3MD5G4= - dependencies: - abbrev "~1.0.9" - ansicolors "~0.3.2" - ansistyles "~0.1.3" - aproba "~1.0.4" - archy "~1.0.0" - asap "~2.0.5" - chownr "~1.0.1" - cmd-shim "~2.0.2" - columnify "~1.5.4" - config-chain "~1.1.11" - dezalgo "~1.0.3" - editor "~1.0.0" - fs-vacuum "~1.2.9" - fs-write-stream-atomic "~1.0.8" - fstream "~1.0.10" - fstream-npm "~1.2.0" - glob "~7.1.0" - graceful-fs "~4.1.9" - has-unicode "~2.0.1" - hosted-git-info "~2.1.5" - iferr "~0.1.5" - inflight "~1.0.5" - inherits "~2.0.3" - ini "~1.3.4" - init-package-json "~1.9.4" - lockfile "~1.0.2" - lodash._baseuniq "~4.6.0" - lodash.clonedeep "~4.5.0" - lodash.union "~4.6.0" - lodash.uniq "~4.5.0" - lodash.without "~4.4.0" - mkdirp "~0.5.1" - node-gyp "~3.4.0" - nopt "~3.0.6" - normalize-git-url "~3.0.2" - normalize-package-data "~2.3.5" - npm-cache-filename "~1.0.2" - npm-install-checks "~3.0.0" - npm-package-arg "~4.2.0" - npm-registry-client "~7.2.1" - npm-user-validate "~0.1.5" - npmlog "~4.0.0" - once "~1.4.0" - opener "~1.4.2" - osenv "~0.1.3" - path-is-inside "~1.0.2" - read "~1.0.7" - read-cmd-shim "~1.0.1" - read-installed "~4.0.3" - read-package-json "~2.0.4" - read-package-tree "~5.1.5" - readable-stream "~2.1.5" - realize-package-specifier "~3.0.3" - request "~2.75.0" - retry "~0.10.0" - rimraf "~2.5.4" - semver "~5.3.0" - sha "~2.0.1" - slide "~1.1.6" - sorted-object "~2.0.1" - strip-ansi "~3.0.1" - tar "~2.2.1" - text-table "~0.2.0" - uid-number "0.0.6" - umask "~1.1.0" - unique-filename "~1.1.0" - unpipe "~1.0.0" - validate-npm-package-name "~2.2.2" - which "~1.2.11" - wrappy "~1.0.2" - write-file-atomic "~1.2.0" - -npmi@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/npmi/-/npmi-2.0.1.tgz#32607657e1bd47ca857ab4e9d98f0a0cff96bcea" - integrity sha1-MmB2V+G9R8qFerTp2Y8KDP+WvOo= - dependencies: - npm "^3" - semver "^4.1.0" - -"npmlog@0 || 1 || 2 || 3", "npmlog@~2.0.0 || ~3.1.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-3.1.2.tgz#2d46fa874337af9498a2f12bb43d8d0be4a36873" - integrity sha1-LUb6h0M3r5SYovErtD2NC+SjaHM= - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.6.0" - set-blocking "~2.0.0" - "npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.2, npmlog@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" @@ -10214,30 +10060,15 @@ npmi@^2.0.1: gauge "~2.7.3" set-blocking "~2.0.0" -npmlog@~4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.0.2.tgz#d03950e0e78ce1527ba26d2a7592e9348ac3e75f" - integrity sha1-0DlQ4OeM4VJ7om0qdZLpNIrD518= - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.1" - set-blocking "~2.0.0" - number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= nwsapi@^2.0.7: - version "2.1.0" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.1.0.tgz#781065940aed90d9bb01ca5d0ce0fcf81c32712f" - integrity sha512-ZG3bLAvdHmhIjaQ/Db1qvBxsGvFMLIRpQszyqbg31VJ53UP++uZX1/gf3Ut96pdwN9AuDwlMqIYLm0UPCdUeHg== - -oauth-sign@~0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" - integrity sha1-Rqarfwrq2N6unsBWV4C31O/rnUM= + version "2.1.1" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.1.1.tgz#08d6d75e69fd791bdea31507ffafe8c843b67e9c" + integrity sha512-T5GaA1J/d34AC8mkrFD2O0DR17kwJ702ZOtJOsS8RpbsQZVOC2/xYFb1i/cw+xdM54JIlMuojjDOYct8GIWtwg== oauth-sign@~0.9.0: version "0.9.0" @@ -10268,10 +10099,10 @@ object-keys@^1.0.12: resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.0.tgz#11bd22348dd2e096a045ab06f6c85bcc340fa032" integrity sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg== -object-path@^0.11.4: - version "0.11.4" - resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.4.tgz#370ae752fbf37de3ea70a861c23bba8915691949" - integrity sha1-NwrnUvvzfePqcKhhwju6iRVpGUk= +object-to-human-string@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/object-to-human-string/-/object-to-human-string-0.0.3.tgz#7feb121a79496248ef8daa160da23862c09e89f2" + integrity sha1-f+sSGnlJYkjvjaoWDaI4YsCeifI= object-visit@^1.0.0: version "1.0.1" @@ -10308,14 +10139,7 @@ octokit-pagination-methods@^1.1.0: resolved "https://registry.yarnpkg.com/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz#cf472edc9d551055f9ef73f6e42b4dbb4c80bea4" integrity sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ== -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= - dependencies: - ee-first "1.1.1" - -once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0, once@~1.4.0: +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= @@ -10334,12 +10158,7 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" -opener@~1.4.2: - version "1.4.3" - resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8" - integrity sha1-XG2ixdflgx6P+jlklQ+NZnSskLg= - -opn@^5.2.0: +opn@^5.4.0: version "5.4.0" resolved "https://registry.yarnpkg.com/opn/-/opn-5.4.0.tgz#cb545e7aab78562beb11aa3bfabc7042e1761035" integrity sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw== @@ -10373,14 +10192,14 @@ optionator@^0.8.1: type-check "~0.3.2" wordwrap "~1.0.0" -ora@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/ora/-/ora-3.1.0.tgz#dbedd8c03b5d017fb67083e87ee52f5ec89823ed" - integrity sha512-vRBPaNCclUi8pUxRF/G8+5qEQkc6EgzKK1G2ZNJUIGu088Un5qIxFXeDgymvPRM9nmrcUOGzQgS1Vmtz+NtlMw== +ora@*, ora@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ora/-/ora-3.2.0.tgz#67e98a7e11f7f0ac95deaaaf11bb04de3d09e481" + integrity sha512-XHMZA5WieCbtg+tu0uPF8CjvwQdNzKCX6BVh3N6GFsEXH40mTk5dsw/ya1lBTUGJslcEFJFQ8cBhOgkkZXQtMA== dependencies: chalk "^2.4.2" cli-cursor "^2.1.0" - cli-spinners "^1.3.1" + cli-spinners "^2.0.0" log-symbols "^2.2.0" strip-ansi "^5.0.0" wcwidth "^1.0.1" @@ -10420,14 +10239,6 @@ os-locale@^3.0.0: lcid "^2.0.0" mem "^4.0.0" -os-name@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/os-name/-/os-name-2.0.1.tgz#b9a386361c17ae3a21736ef0599405c9a8c5dc5e" - integrity sha1-uaOGNhwXrjohc27wWZQFyajF3F4= - dependencies: - macos-release "^1.0.0" - win-release "^1.0.0" - os-name@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/os-name/-/os-name-3.0.0.tgz#e1434dbfddb8e74b44c98b56797d951b7648a5d9" @@ -10441,7 +10252,7 @@ os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.2: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= -osenv@0, osenv@^0.1.4, osenv@^0.1.5, osenv@~0.1.3: +osenv@0, osenv@^0.1.4, osenv@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== @@ -10478,6 +10289,13 @@ p-defer@^1.0.0: resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= +p-each-series@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-1.0.0.tgz#930f3d12dd1f50e7434457a22cd6f04ac6ad7f71" + integrity sha1-kw89Et0fUOdDRFeiLNbwSsatf3E= + dependencies: + p-reduce "^1.0.0" + p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" @@ -10501,9 +10319,9 @@ p-limit@^1.1.0: p-try "^1.0.0" p-limit@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.1.0.tgz#1d5a0d20fb12707c758a655f6bbc4386b5930d68" - integrity sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g== + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.0.tgz#417c9941e6027a9abcba5092dd2904e255b5fbc2" + integrity sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ== dependencies: p-try "^2.0.0" @@ -10624,15 +10442,15 @@ package-json@^5.0.0: registry-url "^3.1.0" semver "^5.5.0" -packet-reader@0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-0.3.1.tgz#cd62e60af8d7fea8a705ec4ff990871c46871f27" - integrity sha1-zWLmCvjX/qinBexP+ZCHHEaHHyc= +packet-reader@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74" + integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ== -pacote@^9.4.1: - version "9.4.1" - resolved "https://registry.yarnpkg.com/pacote/-/pacote-9.4.1.tgz#f0af2a52d241bce523d39280ac810c671db62279" - integrity sha512-YKSRsQqmeHxgra0KCdWA2FtVxDPUlBiCdmew+mSe44pzlx5t1ViRMWiQg18T+DREA+vSqYfKzynaToFR4hcKHw== +pacote@^9.5.0: + version "9.5.0" + resolved "https://registry.yarnpkg.com/pacote/-/pacote-9.5.0.tgz#85f3013a3f6dd51c108b0ccabd3de8102ddfaeda" + integrity sha512-aUplXozRbzhaJO48FaaeClmN+2Mwt741MC6M3bevIGZwdCaP7frXzbUOfOWa91FPHoLITzG0hYaKY363lxO3bg== dependencies: bluebird "^3.5.3" cacache "^11.3.2" @@ -10663,9 +10481,9 @@ pacote@^9.4.1: which "^1.3.1" pako@~1.0.2, pako@~1.0.5: - version "1.0.8" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.8.tgz#6844890aab9c635af868ad5fecc62e8acbba3ea4" - integrity sha512-6i0HVbUfcKaTv+EG8ZTr75az7GFXcLYk9UyLEg7Notv/Ma+z/UG3TCoz6GiNeOrn1E/e63I0X/Hpw18jHOTUnA== + version "1.0.10" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732" + integrity sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw== parallel-transform@^1.1.0: version "1.1.0" @@ -10677,9 +10495,9 @@ parallel-transform@^1.1.0: readable-stream "^2.1.5" parse-asn1@^5.0.0: - version "5.1.3" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.3.tgz#1600c6cc0727365d68b97f3aa78939e735a75204" - integrity sha512-VrPoetlz7B/FqjBLD2f5wBVZvsZVLnRUrxVLfRYhGXCODa/NWE4p3Wp+6+aV3ZPL3KM7/OZmxDIwwijD7yuucg== + version "5.1.4" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.4.tgz#37f6628f823fbdeb2273b4d540434a22f3ef1fcc" + integrity sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw== dependencies: asn1.js "^4.0.0" browserify-aes "^1.0.0" @@ -10751,31 +10569,19 @@ parse5@4.0.0: resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" integrity sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA== -parseurl@~1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" - integrity sha1-/CidTtiZMRlGDBViUyYs3I3mW/M= - pascalcase@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= -password-prompt@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/password-prompt/-/password-prompt-1.0.7.tgz#8e27748d3400bc9c9140d5ade705dfb7aeb7d91a" - integrity sha1-jid0jTQAvJyRQNWt5wXft6632Ro= +password-prompt@^1.0.7, password-prompt@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/password-prompt/-/password-prompt-1.1.2.tgz#85b2f93896c5bd9e9f2d6ff0627fa5af3dc00923" + integrity sha512-bpuBhROdrhuN3E7G/koAju0WjVw9/uQOG5Co5mokNj0MiOSBVZS1JTwM4zl55hu0WFmIEFvO9cU9sJQiBIYeIA== dependencies: ansi-escapes "^3.1.0" cross-spawn "^6.0.5" -path-array@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-array/-/path-array-1.0.1.tgz#7e2f0f35f07a2015122b868b7eac0eb2c4fec271" - integrity sha1-fi8PNfB6IBUSK4aLfqwOssT+wnE= - dependencies: - array-index "^1.0.0" - path-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a" @@ -10803,7 +10609,7 @@ path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-is-inside@^1.0.1, path-is-inside@^1.0.2, path-is-inside@~1.0.2: +path-is-inside@^1.0.1, path-is-inside@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= @@ -10818,11 +10624,6 @@ path-parse@^1.0.5, path-parse@^1.0.6: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= - path-type@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" @@ -10839,6 +10640,11 @@ path-type@^3.0.0: dependencies: pify "^3.0.0" +pathval@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" + integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA= + pbkdf2@^3.0.3, pbkdf2@^3.0.9: version "3.0.17" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" @@ -10891,13 +10697,14 @@ pg-pool@^2.0.4: resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-2.0.6.tgz#7b561a482feb0a0e599b58b5137fd2db3ad8111c" integrity sha512-hod2zYQxM8Gt482q+qONGTYcg/qVcV32VHVPtktbBJs0us3Dj7xibISw0BAAXVMCzt8A/jhfJvpZaxUlqtqs0g== -pg-promise@^8.5.5: - version "8.5.5" - resolved "https://registry.yarnpkg.com/pg-promise/-/pg-promise-8.5.5.tgz#29b288b012ca9a9391c3db3749b65ea4ee71f947" - integrity sha512-TaIXqoIPEUJNxm3cYWUr2fm50qfOY3ahFAuUoS3GJiozeWmhNYj9vsfBAsiCzaCV6IE9EdDK6255yuZkAxLJ2g== +pg-promise@^8.6.3: + version "8.6.3" + resolved "https://registry.yarnpkg.com/pg-promise/-/pg-promise-8.6.3.tgz#21107c31170c30188f50df52cb07aec99a812b02" + integrity sha512-lhJCGSpQcqT75tc380kzBYlBs+buInLFV91+mB5EyEPjNPtKqVERMCpW9k1mTwER8CxXONXJsS69im9h8WhOyw== dependencies: + assert-options "0.1.3" manakin "0.5.2" - pg "7.8.0" + pg "7.8.2" pg-minify "0.5.5" spex "2.1.0" @@ -10919,13 +10726,13 @@ pg-types@~2.0.0: postgres-date "~1.0.0" postgres-interval "^1.1.0" -pg@7.8.0: - version "7.8.0" - resolved "https://registry.yarnpkg.com/pg/-/pg-7.8.0.tgz#541c25b3323d85f67ce7d4501a77470976868ce9" - integrity sha512-yS3C9YD+ft0H7G47uU0eKajgTieggCXdA+Fxhm5G+wionY6kPBa8BEVDwPLMxQvkRkv3/LXiFEqjZm9gfxdW+g== +pg@7.8.2: + version "7.8.2" + resolved "https://registry.yarnpkg.com/pg/-/pg-7.8.2.tgz#d53ffcbbaa789e15e80ffec570603294d90116e8" + integrity sha512-5U4fjV43DnQxelkhyPdU3YfUbYVa21bNmreXRCM/gFFw09YxWaitWWITm/u0twUNF5EYOSDhkgyEAocgtpP9JQ== dependencies: buffer-writer "2.0.0" - packet-reader "0.3.1" + packet-reader "1.0.0" pg-connection-string "0.1.3" pg-pool "^2.0.4" pg-types "~2.0.0" @@ -10949,6 +10756,11 @@ pify@^2.0.0, pify@^2.3.0: resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + pinkie-promise@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" @@ -10993,6 +10805,33 @@ pino@^5.11.1: quick-format-unescaped "^3.0.0" sonic-boom "^0.7.1" +pino@^5.11.2: + version "5.11.2" + resolved "https://registry.yarnpkg.com/pino/-/pino-5.11.2.tgz#52b9c42a48f50f33f063ccdf52eabc2c4129e6eb" + integrity sha512-r5gxI//cBF+cA0K/ZYTF1/mMDNybB2XqCKY2nI6txoXumN4sq3u+dIEsm3uBkr5OdYlhEzP861xDp7Af3anseA== + dependencies: + fast-redact "^1.4.4" + fast-safe-stringify "^2.0.6" + flatstr "^1.0.9" + pino-std-serializers "^2.3.0" + quick-format-unescaped "^3.0.2" + sonic-boom "^0.7.3" + +pirates@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" + integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA== + dependencies: + node-modules-regexp "^1.0.0" + +pkg-conf@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pkg-conf/-/pkg-conf-2.1.0.tgz#2126514ca6f2abfebd168596df18ba57867f0058" + integrity sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg= + dependencies: + find-up "^2.0.0" + load-json-file "^4.0.0" + pkg-dir@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" @@ -11037,10 +10876,17 @@ podium@3.x.x: hoek "6.x.x" joi "14.x.x" +pokemon@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/pokemon/-/pokemon-1.2.3.tgz#2b494263606408df10f6c499c299e9c2446ac05c" + integrity sha512-3wwtG0QKvkje3umDTnaNbPNLmL3XoxdjQwRMz3WEcA/txEagzmWHB1H1TwPNCgZvjIsTrkX7j+DK5Y9+Eu9Xyg== + dependencies: + unique-random-array "^1.0.0" + port-numbers@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/port-numbers/-/port-numbers-4.0.4.tgz#fe1c1fa7cd551f4ceb835b3bbf88c07baa0783d7" - integrity sha512-iicgo0Gltog+OinSw/ESmKOLi4Kfus/OQ6KfN4iOObYVh/Ap13PlFIBulPooBMKnEVm2SUO3Fs2Lk6T9culk1Q== + version "4.0.5" + resolved "https://registry.yarnpkg.com/port-numbers/-/port-numbers-4.0.5.tgz#882401ea99d7b6e09186173bcd374ed31dde682e" + integrity sha512-UwbiF2luL0YCb4wKrPcNvSEewxFbT8ybSsbMGCnACOxN408wcq2k4YW34O6pXLaZqIihgPAye6K9Es0prDWy2A== posix-character-classes@^0.1.0: version "0.1.1" @@ -11063,9 +10909,9 @@ postgres-date@~1.0.0: integrity sha1-4tiXAu/bJY/52c7g/pG9BpdSV6g= postgres-interval@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.1.2.tgz#bf71ff902635f21cb241a013fc421d81d1db15a9" - integrity sha512-fC3xNHeTskCxL1dC8KOtxXt7YeFmlbTYtn7ul8MkVERuTmf7pI4DrkAxcw3kh1fQ9uz4wQmd03a1mRiXUZChfQ== + version "1.2.0" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" + integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== dependencies: xtend "^4.0.0" @@ -11089,7 +10935,7 @@ preserve@^0.2.0: resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= -prettier@^1.15.3: +prettier@^1.16.4: version "1.16.4" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.4.tgz#73e37e73e018ad2db9c76742e2647e21790c9717" integrity sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g== @@ -11115,6 +10961,16 @@ pretty-format@^23.6.0: ansi-regex "^3.0.0" ansi-styles "^3.2.0" +pretty-format@^24.3.1: + version "24.3.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.3.1.tgz#ae4a98e93d73d86913a8a7dd1a7c3c900f8fda59" + integrity sha512-NZGH1NWS6o4i9pvRWLsxIK00JB9pqOUzVrO7yWT6vjI2thdxwvxefBJO6O5T24UAhI8P5dMceZ7x5wphgVI7Mg== + dependencies: + "@jest/types" "^24.3.0" + ansi-regex "^4.0.0" + ansi-styles "^3.2.0" + react-is "^16.8.4" + pretty-ms@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-4.0.0.tgz#31baf41b94fd02227098aaa03bd62608eb0d6e92" @@ -11127,11 +10983,6 @@ private@^0.1.6, private@^0.1.8: resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== -process-nextick-args@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" - integrity sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M= - process-nextick-args@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" @@ -11142,11 +10993,6 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= -progress@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" @@ -11175,10 +11021,10 @@ prompts@^0.1.9: kleur "^2.0.1" sisteransi "^0.1.1" -prompts@^2.0.0, prompts@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.0.2.tgz#094119b0b0a553ec652908b583205b9867630154" - integrity sha512-Pc/c53d2WZHJWZr78/BhZ5eHsdQtltbyBjHoA4T0cs/4yKJqCcoOHrq2SNKwtspVE0C+ebqAR5u0/mXwrHaADQ== +prompts@^2.0.1, prompts@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.0.3.tgz#c5ccb324010b2e8f74752aadceeb57134c1d2522" + integrity sha512-H8oWEoRZpybm6NV4to9/1limhttEo13xK62pNvn2JzY0MA03p7s0OjtmhXyon3uJmxiJJVSuUwEJFFssI3eBiQ== dependencies: kleur "^3.0.2" sisteransi "^1.0.0" @@ -11190,6 +11036,11 @@ promzard@^0.3.0: dependencies: read "1" +propagate@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/propagate/-/propagate-1.0.0.tgz#00c2daeedda20e87e3782b344adba1cddd6ad709" + integrity sha1-AMLa7t2iDofjeCs0Stuhzd1q1wk= + property-expr@^1.5.0: version "1.5.1" resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-1.5.1.tgz#22e8706894a0c8e28d58735804f6ba3a3673314f" @@ -11200,25 +11051,6 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= -protobufjs@^6.8.6: - version "6.8.8" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.8.8.tgz#c8b4f1282fd7a90e6f5b109ed11c84af82908e7c" - integrity sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw== - dependencies: - "@protobufjs/aspromise" "^1.1.2" - "@protobufjs/base64" "^1.1.2" - "@protobufjs/codegen" "^2.0.4" - "@protobufjs/eventemitter" "^1.1.0" - "@protobufjs/fetch" "^1.1.0" - "@protobufjs/float" "^1.0.2" - "@protobufjs/inquire" "^1.1.0" - "@protobufjs/path" "^1.1.2" - "@protobufjs/pool" "^1.1.0" - "@protobufjs/utf8" "^1.1.0" - "@types/long" "^4.0.0" - "@types/node" "^10.1.0" - long "^4.0.0" - protocols@^1.1.0, protocols@^1.4.0: version "1.4.7" resolved "https://registry.yarnpkg.com/protocols/-/protocols-1.4.7.tgz#95f788a4f0e979b291ffefcf5636ad113d037d32" @@ -11231,14 +11063,6 @@ protoduck@^5.0.1: dependencies: genfun "^5.0.0" -proxy-addr@~2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.4.tgz#ecfc733bf22ff8c6f407fa275327b9ab67e48b93" - integrity sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA== - dependencies: - forwarded "~0.1.2" - ipaddr.js "1.8.0" - proxy-agent@2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-2.3.1.tgz#3d49d863d46cf5f37ca8394848346ea02373eac6" @@ -11285,6 +11109,14 @@ public-encrypt@^4.0.0: randombytes "^2.0.1" safe-buffer "^5.1.2" +pump@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.3.tgz#5dfe8311c33bbf6fc18261f9f34702c47c08a954" + integrity sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + pump@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" @@ -11334,26 +11166,40 @@ punycode@^1.2.4, punycode@^1.4.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= -pure-uuid@1.5.6: - version "1.5.6" - resolved "https://registry.yarnpkg.com/pure-uuid/-/pure-uuid-1.5.6.tgz#313dc69835b42c0e3468199538edcbb730ddfa09" - integrity sha512-yUXFK0Xt+twSu6efNkBWPEBryDQ/3uaeA5nMKaGP4RM6WJyap657lU9yS4NSPB7X485sAkmP8SNv0vCxkXhrNA== - q@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= -qs@6.5.2, qs@~6.5.2: +qqjs@^0.3.10: + version "0.3.10" + resolved "https://registry.yarnpkg.com/qqjs/-/qqjs-0.3.10.tgz#ae3af7cb4c424242db4aa9b92c42d29fa9101562" + integrity sha1-rjr3y0xCQkLbSqm5LELSn6kQFWI= + dependencies: + chalk "^2.4.1" + debug "^3.1.0" + execa "^0.10.0" + fs-extra "^6.0.1" + get-stream "^3.0.0" + glob "^7.1.2" + globby "^8.0.1" + http-call "^5.1.2" + load-json-file "^5.0.0" + pkg-dir "^2.0.0" + tar-fs "^1.16.2" + tmp "^0.0.33" + write-json-file "^2.3.0" + +qs@^6.5.1: + version "6.6.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.6.0.tgz#a99c0f69a8d26bf7ef012f871cdabb0aee4424c2" + integrity sha512-KIJqT9jQJDQx5h5uAVPimw6yVg2SekOKu959OCtktD3FjzbpvaPr8i4zzg07DOMz+igA4W/aNM7OV8H37pFYfA== + +qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -qs@~6.2.0: - version "6.2.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.3.tgz#1cfcb25c10a9b2b483053ff39f5dfc9233908cfe" - integrity sha1-HPyyXBCpsrSDBT/zn138kjOQjP4= - query-string@^5.0.1: version "5.1.1" resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" @@ -11378,7 +11224,7 @@ querystringify@^2.0.0: resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.0.tgz#7ded8dfbf7879dcc60d0a644ac6754b283ad17ef" integrity sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg== -quick-format-unescaped@^3.0.0: +quick-format-unescaped@^3.0.0, quick-format-unescaped@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-3.0.2.tgz#0137e94d8fb37ffeb70040535111c378e75396fb" integrity sha512-FXTaCkwvpIlkdKeGDNgcq07SXWS383noQUuZjvdE1QcTt+eLuqof6/BDiEPqB59FWLie/l91+HtlJSw7iCViSA== @@ -11405,9 +11251,9 @@ randomatic@^3.0.0: math-random "^1.0.1" randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: - version "2.0.6" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80" - integrity sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A== + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== dependencies: safe-buffer "^5.1.0" @@ -11426,12 +11272,7 @@ randomstring@~1.1.5: dependencies: array-uniq "1.0.2" -range-parser@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" - integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4= - -raw-body@2.3.3, raw-body@^2.2.0: +raw-body@^2.2.0: version "2.3.3" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3" integrity sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw== @@ -11441,7 +11282,16 @@ raw-body@2.3.3, raw-body@^2.2.0: iconv-lite "0.4.23" unpipe "1.0.0" -rc-config-loader@^2.0.1: +raygun@^0.10.1: + version "0.10.1" + resolved "https://registry.yarnpkg.com/raygun/-/raygun-0.10.1.tgz#093b26765f5d42114673c082e6216c7e472418fe" + integrity sha512-wXZJ/898b8CCGTsAhIiZ/I4fnTna5W1l3aXv+H675G9jV7PueZ98X214OZ0Ou2kLcaGpsjPnOpypfJU9YvmEDg== + dependencies: + nock "~9" + object-to-human-string "0.0.3" + stack-trace "0.0.6" + +rc-config-loader@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/rc-config-loader/-/rc-config-loader-2.0.2.tgz#46eb2f98fb5b2aa7b1119d66c0554de5133f1bc1" integrity sha512-Nx9SNM47eNRqe0TdntOY600qWb8NDh+xU9sv5WnTscEtzfTB0ukihlqwuCLPteyJksvZ0sEVPoySNE01TKrmTQ== @@ -11454,7 +11304,7 @@ rc-config-loader@^2.0.1: path-exists "^3.0.0" require-from-string "^2.0.2" -rc@^1.0.1, rc@^1.1.6, rc@^1.2.7: +rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@~1.2.7: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -11464,28 +11314,26 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -read-cmd-shim@^1.0.1, read-cmd-shim@~1.0.1: +react-is@^16.8.4: + version "16.8.4" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.4.tgz#90f336a68c3a29a096a3d648ab80e87ec61482a2" + integrity sha512-PVadd+WaUDOAciICm/J1waJaSvgq+4rHE/K70j0PFqKhkTBsPv/82UGQJNXAngz1fOQLLxI6z1sEDmJDQhCTAA== + +read-cmd-shim@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-1.0.1.tgz#2d5d157786a37c055d22077c32c53f8329e91c7b" integrity sha1-LV0Vd4ajfAVdIgd8MsU/gynpHHs= dependencies: graceful-fs "^4.1.2" -read-installed@~4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/read-installed/-/read-installed-4.0.3.tgz#ff9b8b67f187d1e4c29b9feb31f6b223acd19067" - integrity sha1-/5uLZ/GH0eTCm5/rMfayI6zRkGc= +read-last-lines@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/read-last-lines/-/read-last-lines-1.7.0.tgz#20ef2f97637b96596d54cc2a97380e11567e158e" + integrity sha512-hm2Wd9F9SrfgWrjb4qlfdsg9R4inL+hqH4xC/4BnBBUc9cC3n9HZhWg6RBBirzCkNQt/VtHDbRQbs/lkN1KWiQ== dependencies: - debuglog "^1.0.1" - read-package-json "^2.0.0" - readdir-scoped-modules "^1.0.0" - semver "2 || 3 || 4 || 5" - slide "~1.1.3" - util-extend "^1.0.1" - optionalDependencies: - graceful-fs "^4.1.2" + mz "^2.7.0" -"read-package-json@1 || 2", read-package-json@^2.0.0, read-package-json@^2.0.13, read-package-json@~2.0.4: +"read-package-json@1 || 2", read-package-json@^2.0.0, read-package-json@^2.0.13: version "2.0.13" resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-2.0.13.tgz#2e82ebd9f613baa6d2ebe3aa72cefe3f68e41f4a" integrity sha512-/1dZ7TRZvGrYqE0UAfN6qQb5GYBsNcqS1C0tNK601CFOJmtHI7NIGXwetEPU/OtoFHZL3hDxm4rolFFVE9Bnmg== @@ -11498,20 +11346,9 @@ read-installed@~4.0.3: graceful-fs "^4.1.2" read-package-tree@^5.1.6: - version "5.2.1" - resolved "https://registry.yarnpkg.com/read-package-tree/-/read-package-tree-5.2.1.tgz#6218b187d6fac82289ce4387bbbaf8eef536ad63" - integrity sha512-2CNoRoh95LxY47LvqrehIAfUVda2JbuFE/HaGYs42bNrGG+ojbw1h3zOcPcQ+1GQ3+rkzNndZn85u1XyZ3UsIA== - dependencies: - debuglog "^1.0.1" - dezalgo "^1.0.0" - once "^1.3.0" - read-package-json "^2.0.0" - readdir-scoped-modules "^1.0.0" - -read-package-tree@~5.1.5: - version "5.1.6" - resolved "https://registry.yarnpkg.com/read-package-tree/-/read-package-tree-5.1.6.tgz#4f03e83d0486856fb60d97c94882841c2a7b1b7a" - integrity sha512-FCX1aT3GWyY658wzDICef4p+n0dB+ENRct8E/Qyvppj6xVpOYerBHfUu7OP5Rt1/393Tdglguf5ju5DEX4wZNg== + version "5.2.2" + resolved "https://registry.yarnpkg.com/read-package-tree/-/read-package-tree-5.2.2.tgz#4b6a0ef2d943c1ea36a578214c9a7f6b7424f7a8" + integrity sha512-rW3XWUUkhdKmN2JKB4FL563YAgtINifso5KShykufR03nJ5loGFlkUMe1g/yxmqX073SoYYTsgXu7XdDinKZuA== dependencies: debuglog "^1.0.1" dezalgo "^1.0.0" @@ -11535,6 +11372,14 @@ read-pkg-up@^3.0.0: find-up "^2.0.0" read-pkg "^3.0.0" +read-pkg-up@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-4.0.0.tgz#1b221c6088ba7799601c808f91161c66e58f8978" + integrity sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA== + dependencies: + find-up "^3.0.0" + read-pkg "^3.0.0" + read-pkg@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" @@ -11562,14 +11407,14 @@ read-pkg@^4.0.1: parse-json "^4.0.0" pify "^3.0.0" -read@1, read@~1.0.1, read@~1.0.7: +read@1, read@~1.0.1: version "1.0.7" resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" integrity sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ= dependencies: mute-stream "~0.0.4" -"readable-stream@1 || 2", readable-stream@2, readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: +"readable-stream@1 || 2", readable-stream@2, readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== @@ -11592,40 +11437,15 @@ readable-stream@1.1.x: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.6, readable-stream@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.1.1.tgz#ed6bbc6c5ba58b090039ff18ce670515795aeb06" - integrity sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA== +readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.2.0.tgz#de17f229864c120a9f56945756e4f32c4045245d" + integrity sha512-RV20kLjdmpZuTF1INEb9IA3L68Nmi+Ri7ppZqo78wj//Pn62fCoJyV9zalccNzDD/OuJpMG4f+pfMl8+L6QdGw== dependencies: inherits "^2.0.3" string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@~2.0.5, readable-stream@~2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" - integrity sha1-j5A0HmilPMySh4jaz80Rs265t44= - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - string_decoder "~0.10.x" - util-deprecate "~1.0.1" - -readable-stream@~2.1.5: - version "2.1.5" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.1.5.tgz#66fa8b720e1438b364681f2ad1a63c618448c9d0" - integrity sha1-ZvqLcg4UOLNkaB8q0aY8YYRIydA= - dependencies: - buffer-shims "^1.0.0" - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - string_decoder "~0.10.x" - util-deprecate "~1.0.1" - readdir-scoped-modules@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.0.2.tgz#9fafa37d286be5d92cbaebdee030dc9b5f406747" @@ -11645,28 +11465,13 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" -realize-package-specifier@~3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/realize-package-specifier/-/realize-package-specifier-3.0.3.tgz#d0def882952b8de3f67eba5e91199661271f41f4" - integrity sha1-0N74gpUrjeP2frpekRmWYScfQfQ= - dependencies: - dezalgo "^1.0.1" - npm-package-arg "^4.1.1" - -realpath-native@^1.0.0: +realpath-native@^1.0.0, realpath-native@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c" integrity sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA== dependencies: util.promisify "^1.0.0" -rechoir@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" - integrity sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q= - dependencies: - resolve "^1.1.6" - recursive-readdir@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.2.tgz#9946fb3274e1628de6e36b2f6714953b4845094f" @@ -11697,10 +11502,10 @@ redeyed@~2.1.0: dependencies: esprima "~4.0.0" -regenerate-unicode-properties@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz#107405afcc4a190ec5ed450ecaa00ed0cafa7a4c" - integrity sha512-s5NGghCE4itSlUS+0WUj88G6cfMVMmH8boTPNvABf8od+2dhT9WDlWu8n01raQAJZMOK8Ch6jSexaRO7swd6aw== +regenerate-unicode-properties@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.0.1.tgz#58a4a74e736380a7ab3c5f7e03f303a941b31289" + integrity sha512-HTjMafphaH5d5QDHuwW8Me6Hbc/GhXg8luNqTkPVwZ/oCZhnoifjWhGYsu2BzepMELTlbnoVcXvV0f+2uDDvoQ== dependencies: regenerate "^1.4.0" @@ -11724,10 +11529,10 @@ regenerator-runtime@^0.13.1: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.1.tgz#522ea2aafd9200a00eee143dc14219a35a0f3991" integrity sha512-5KzMIyPLvfdPmvsdlYsHqITrDfK9k7bmvf97HvHSN4810i254ponbxCQ1NukpRWlu6en2MBWzAlhDExEKISwAA== -regenerator-transform@^0.13.3: - version "0.13.3" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.3.tgz#264bd9ff38a8ce24b06e0636496b2c856b57bcbb" - integrity sha512-5ipTrZFSq5vU2YoGoww4uaRVAK4wyYC4TSICibbfEPOruUu8FFP7ErV0BjmbIOEpn3O/k9na9UEdYR/3m7N6uA== +regenerator-transform@^0.13.4: + version "0.13.4" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.4.tgz#18f6763cf1382c69c36df76c6ce122cc694284fb" + integrity sha512-T0QMBjK3J0MtxjPmdIMXm72Wvj2Abb0Bd4HADdfijwMdoIsyQZ6fWC7kDFhk2YinBBEMZDL7Y7wh0J1sGx3S4A== dependencies: private "^0.1.6" @@ -11747,25 +11552,21 @@ regex-not@^1.0.0, regex-not@^1.0.2: safe-regex "^1.1.0" regexp-tree@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.1.tgz#27b455f9b138ca2e84c090e9aff1ffe2a04d97fa" - integrity sha512-HwRjOquc9QOwKTgbxvZTcddS5mlNlwePMQ3NFL8broajMLD5CXDAqas8Y5yxJH5QtZp5iRor3YCILd5pz71Cgw== - dependencies: - cli-table3 "^0.5.0" - colors "^1.1.2" - yargs "^12.0.5" + version "0.1.5" + resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.5.tgz#7cd71fca17198d04b4176efd79713f2998009397" + integrity sha512-nUmxvfJyAODw+0B13hj8CFVAxhe7fDEAgJgaotBu3nnR+IgGgZq59YedJP5VYTlkEfqjuK6TuRpnymKdatLZfQ== regexpu-core@^4.1.3, regexpu-core@^4.2.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.4.0.tgz#8d43e0d1266883969720345e70c275ee0aec0d32" - integrity sha512-eDDWElbwwI3K0Lo6CqbQbA6FwgtCz4kYTarrri1okfkRLZAqstU+B3voZBCjg8Fl6iq0gXrJG6MvRgLthfvgOA== + version "4.5.3" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.5.3.tgz#72f572e03bb8b9f4f4d895a0ccc57e707f4af2e4" + integrity sha512-LON8666bTAlViVEPXMv65ZqiaR3rMNLz36PIaQ7D+er5snu93k0peR7FSvO0QteYbZ3GOkvfHKbGr/B1xDu9FA== dependencies: regenerate "^1.4.0" - regenerate-unicode-properties "^7.0.0" + regenerate-unicode-properties "^8.0.1" regjsgen "^0.5.0" regjsparser "^0.6.0" unicode-match-property-ecmascript "^1.0.4" - unicode-match-property-value-ecmascript "^1.0.2" + unicode-match-property-value-ecmascript "^1.1.0" registry-auth-token@^3.0.1, registry-auth-token@^3.3.2: version "3.3.2" @@ -11816,33 +11617,40 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" -request-promise-core@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.1.tgz#3eee00b2c5aa83239cfb04c5700da36f81cd08b6" - integrity sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY= +request-ip@~2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/request-ip/-/request-ip-2.0.2.tgz#deeae6d4af21768497db8cd05fa37143f8f1257e" + integrity sha1-3urm1K8hdoSX24zQX6NxQ/jxJX4= + dependencies: + is_js "^0.9.0" + +request-promise-core@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.2.tgz#339f6aababcafdb31c799ff158700336301d3346" + integrity sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag== dependencies: - lodash "^4.13.1" + lodash "^4.17.11" request-promise-native@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.5.tgz#5281770f68e0c9719e5163fd3fab482215f4fda5" - integrity sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU= + version "1.0.7" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.7.tgz#a49868a624bdea5069f1251d0a836e0d89aa2c59" + integrity sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w== dependencies: - request-promise-core "1.1.1" - stealthy-require "^1.1.0" - tough-cookie ">=2.3.3" + request-promise-core "1.1.2" + stealthy-require "^1.1.1" + tough-cookie "^2.3.3" -request-promise@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/request-promise/-/request-promise-4.2.2.tgz#d1ea46d654a6ee4f8ee6a4fea1018c22911904b4" - integrity sha1-0epG1lSm7k+O5qT+oQGMIpEZBLQ= +request-promise@^4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/request-promise/-/request-promise-4.2.4.tgz#1c5ed0d71441e38ad58c7ce4ea4ea5b06d54b310" + integrity sha512-8wgMrvE546PzbR5WbYxUQogUnUDfM0S7QIFZMID+J73vdFARkFy+HElj4T+MWYhpXwlLp0EQ8Zoj8xUA0he4Vg== dependencies: bluebird "^3.5.0" - request-promise-core "1.1.1" - stealthy-require "^1.1.0" - tough-cookie ">=2.3.3" + request-promise-core "1.1.2" + stealthy-require "^1.1.1" + tough-cookie "^2.3.3" -request@2, request@^2.74.0, request@^2.87.0: +request@^2.87.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== @@ -11868,33 +11676,6 @@ request@2, request@^2.74.0, request@^2.87.0: tunnel-agent "^0.6.0" uuid "^3.3.2" -request@~2.75.0: - version "2.75.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.75.0.tgz#d2b8268a286da13eaa5d01adf5d18cc90f657d93" - integrity sha1-0rgmiihtoT6qXQGt9dGMyQ9lfZM= - dependencies: - aws-sign2 "~0.6.0" - aws4 "^1.2.1" - bl "~1.1.2" - caseless "~0.11.0" - combined-stream "~1.0.5" - extend "~3.0.0" - forever-agent "~0.6.1" - form-data "~2.0.0" - har-validator "~2.0.6" - hawk "~3.1.3" - http-signature "~1.1.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.7" - node-uuid "~1.4.7" - oauth-sign "~0.8.1" - qs "~6.2.0" - stringstream "~0.0.4" - tough-cookie "~2.3.0" - tunnel-agent "~0.4.1" - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -11910,6 +11691,15 @@ require-main-filename@^1.0.1: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= +requireg@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/requireg/-/requireg-0.2.1.tgz#f8822f38181b73211d631b5842507dbde4baecdf" + integrity sha512-bbNOK9MAyAGe4Q9/0skx6bzkFVRvVuHerXZthc3jmf2/cO7gthKM7GpnhW522Vx3/uCLA9RXXpQaqdXHRrZVxQ== + dependencies: + nested-error-stacks "~2.0.1" + rc "~1.2.7" + resolve "~1.7.1" + requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" @@ -11950,13 +11740,20 @@ resolve@1.1.7: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@1.x, resolve@^1.1.6, resolve@^1.10.0, resolve@^1.3.2: +resolve@1.x, resolve@^1.10.0, resolve@^1.3.2: version "1.10.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba" integrity sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg== dependencies: path-parse "^1.0.6" +resolve@~1.7.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" + integrity sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw== + dependencies: + path-parse "^1.0.5" + responselike@1.0.2, responselike@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" @@ -11977,38 +11774,18 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== -retry-as-promised@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/retry-as-promised/-/retry-as-promised-2.3.2.tgz#cd974ee4fd9b5fe03cbf31871ee48221c07737b7" - integrity sha1-zZdO5P2bX+A8vzGHHuSCIcB3N7c= - dependencies: - bluebird "^3.4.6" - debug "^2.6.9" - -retry@0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= - -retry@^0.10.0, retry@~0.10.0: +retry@^0.10.0: version "0.10.1" resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q= -rimraf@2, rimraf@^2.2.8, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2: +rimraf@2, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== dependencies: glob "^7.1.3" -rimraf@~2.5.4: - version "2.5.4" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.5.4.tgz#96800093cbf1a0c86bd95b4625467535c29dfa04" - integrity sha1-loAAk8vxoMhr2VtGJUZ1NcKd+gQ= - dependencies: - glob "^7.0.5" - ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" @@ -12017,6 +11794,27 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" +rollbar@^2.5.4: + version "2.5.4" + resolved "https://registry.yarnpkg.com/rollbar/-/rollbar-2.5.4.tgz#8b2ec6de999aaeee04c784110ecb40268a38c71d" + integrity sha512-JIcDNTqnHstU/vXBRafrh0pKB9Wc1FXP/wI33xhs84MJyCGw00vQ2nVDjH8Zq6M/EH0Fmezrr8vA4AikTvsISA== + dependencies: + async "~1.2.1" + console-polyfill "0.3.0" + debug "2.6.9" + error-stack-parser "1.3.3" + json-stringify-safe "~5.0.0" + lru-cache "~2.2.1" + request-ip "~2.0.1" + uuid "3.0.x" + optionalDependencies: + decache "^3.0.5" + +rotating-file-stream@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/rotating-file-stream/-/rotating-file-stream-1.4.0.tgz#7df5dcfe1c8ac32296cee4ab79430d189608c780" + integrity sha512-y1hYZ+JcgPSCchbfeZYuFed0GyWpdcs7TR3eagZmvFTv4nWZq86gxyMmtuV4z8/D8ozcfJ2uJw4QCzQdn5X+WQ== + router-ips@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/router-ips/-/router-ips-1.0.0.tgz#44e00858ebebc0133d58e40b2cd8a1fbb04203f5" @@ -12065,7 +11863,7 @@ rxjs@^6.3.3, rxjs@^6.4.0: dependencies: tslib "^1.9.0" -safe-buffer@5.1.2, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@*, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== @@ -12098,6 +11896,21 @@ sane@^2.0.0: optionalDependencies: fsevents "^1.2.3" +sane@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/sane/-/sane-4.0.3.tgz#e878c3f19e25cc57fbb734602f48f8a97818b181" + integrity sha512-hSLkC+cPHiBQs7LSyXkotC3UUtyn8C4FMn50TNaacRyvBlI+3ebcxMpqckmTdtXVtel87YS7GXN3UIOj7NiGVQ== + dependencies: + "@cnakazawa/watch" "^1.0.3" + anymatch "^2.0.0" + capture-exit "^1.2.0" + exec-sh "^0.3.2" + execa "^1.0.0" + fb-watchman "^2.0.0" + micromatch "^3.1.4" + minimist "^1.1.1" + walker "~1.0.5" + sax@>=0.6.0, sax@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" @@ -12148,12 +11961,12 @@ semver-diff@^2.0.0: dependencies: semver "^5.0.3" -semver-utils@^1.1.1: +semver-utils@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/semver-utils/-/semver-utils-1.1.4.tgz#cf0405e669a57488913909fc1c3f29bf2a4871e2" integrity sha512-EjnoLE5OGmDAVV/8YDoN5KiajNadjzIp9BAHOhYeQHt7j0UWxjmgsx4YD48wp4Ue1Qogq38F1GNUJNqF1kKKxA== -"semver@2 >=2.2.1 || 3.x || 4 || 5", "semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", "semver@^2.3.0 || 3.x || 4 || 5", semver@^5.0.1, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: +"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.0.1, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== @@ -12163,78 +11976,26 @@ semver@4.3.2: resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7" integrity sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c= -semver@^4.1.0: - version "4.3.6" - resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" - integrity sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto= - semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" integrity sha1-myzl094C0XxgEq0yaqa00M9U+U8= -send@0.16.2: - version "0.16.2" - resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" - integrity sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw== - dependencies: - debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "~1.6.2" - mime "1.4.1" - ms "2.0.0" - on-finished "~2.3.0" - range-parser "~1.2.0" - statuses "~1.4.0" - -sequelize@^4.42.0: - version "4.42.0" - resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-4.42.0.tgz#439467ba7bfe7d5afcc56d62b3e091860fbf18f3" - integrity sha512-qxAYnX4rcv7PbNtEidb56REpxNJCdbN0qyk1jb3+e6sE7OrmS0nYMU+MFVbNTJtZfSpOEEL1TX0TkMw+wzZBxg== - dependencies: - bluebird "^3.5.0" - cls-bluebird "^2.1.0" - debug "^3.1.0" - depd "^1.1.0" - dottie "^2.0.0" - generic-pool "^3.4.0" - inflection "1.12.0" - lodash "^4.17.1" - moment "^2.20.0" - moment-timezone "^0.5.14" - retry-as-promised "^2.3.2" - semver "^5.5.0" - terraformer-wkt-parser "^1.1.2" - toposort-class "^1.0.1" - uuid "^3.2.1" - validator "^10.4.0" - wkx "^0.4.1" - serialize-javascript@^1.4.0: version "1.6.1" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.6.1.tgz#4d1f697ec49429a847ca6f442a2a755126c4d879" integrity sha512-A5MOagrPFga4YaKQSWHryl7AXvbQkEqpw4NNYMTNYUNV51bA8ABHgYFpqKx+YFFrw59xMV1qGH1R4AgoNIVgCw== -serve-static@1.13.2: - version "1.13.2" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1" - integrity sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.2" - send "0.16.2" - set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= +set-immediate-shim@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" + integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= + set-value@^0.4.3: version "0.4.3" resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1" @@ -12273,14 +12034,6 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" -sha@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/sha/-/sha-2.0.1.tgz#6030822fbd2c9823949f8f72ed6411ee5cf25aae" - integrity sha1-YDCCL70smCOUn49y7WQR7lzyWq4= - dependencies: - graceful-fs "^4.1.2" - readable-stream "^2.0.2" - shallow-clone@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-0.1.2.tgz#5909e874ba77106d73ac414cfec1ffca87d97060" @@ -12303,25 +12056,11 @@ shebang-regex@^1.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= -shelljs@^0.8.2: - version "0.8.3" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.3.tgz#a7f3319520ebf09ee81275b2368adb286659b097" - integrity sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A== - dependencies: - glob "^7.0.0" - interpret "^1.0.0" - rechoir "^0.6.2" - shellwords@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== -shimmer@^1.1.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" - integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== - shot@4.x.x: version "4.0.7" resolved "https://registry.yarnpkg.com/shot/-/shot-4.0.7.tgz#b05d2858634fedc18ece99e8f638fab7c9f9d4c4" @@ -12335,6 +12074,15 @@ signal-exit@^3.0.0, signal-exit@^3.0.2: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= +signale@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/signale/-/signale-1.4.0.tgz#c4be58302fb0262ac00fc3d886a7c113759042f1" + integrity sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w== + dependencies: + chalk "^2.3.2" + figures "^2.0.0" + pkg-conf "^2.1.0" + simple-git@^1.85.0: version "1.107.0" resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-1.107.0.tgz#12cffaf261c14d6f450f7fdb86c21ccee968b383" @@ -12379,7 +12127,7 @@ sliced@0.0.x: resolved "https://registry.yarnpkg.com/sliced/-/sliced-0.0.5.tgz#5edc044ca4eb6f7816d50ba2fc63e25d8fe4707f" integrity sha1-XtwETKTrb3gW1Qui/GPiXY/kcH8= -slide@^1.1.3, slide@^1.1.5, slide@^1.1.6, slide@~1.1.3, slide@~1.1.6: +slide@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" integrity sha1-VusCfWW00tzmyy4tMsTUr8nh1wc= @@ -12424,13 +12172,6 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" -sntp@1.x.x: - version "1.0.9" - resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" - integrity sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg= - dependencies: - hoek "2.x.x" - sntp@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/sntp/-/sntp-3.0.2.tgz#3f0b5de6115681dce82a9478691f0e5c552de5a3" @@ -12441,19 +12182,19 @@ sntp@^3.0.2: hoek "6.x.x" teamwork "3.x.x" -snyk-config@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/snyk-config/-/snyk-config-2.2.0.tgz#d400ce50e293ce5c3ade4cf46a53bea8205771e6" - integrity sha512-mq0wbP/AgjcmRq5i5jg2akVVV3iSYUPTowZwKn7DChRLDL8ySOzWAwan+ImXiyNbrWo87FNI/15O6MpOnTxOIg== +snyk-config@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/snyk-config/-/snyk-config-2.2.1.tgz#bdacf79193158ec659bdcc4194140fd8d3772f9d" + integrity sha512-eCsFKHHE4J2DpD/1NzAtCmkmVDK310OXRtmoW0RlLnld1ESprJ5A/QRJ5Zxx1JbA8gjuwERY5vfUFA8lEJeopA== dependencies: debug "^3.1.0" - lodash "^4.17.5" + lodash "^4.17.11" nconf "^0.10.0" -snyk-docker-plugin@1.19.0: - version "1.19.0" - resolved "https://registry.yarnpkg.com/snyk-docker-plugin/-/snyk-docker-plugin-1.19.0.tgz#28a1169a40f752a12c5a56e03d6f74710e5def1b" - integrity sha512-VNMbpt4ENj+UlDBPDejhy79upPiOVL5XN+QQNH0k5k7dlU00aFc2NbIpJlAhHENd1CWxZrm9UC8ruS3tSmh0rg== +snyk-docker-plugin@1.22.0: + version "1.22.0" + resolved "https://registry.yarnpkg.com/snyk-docker-plugin/-/snyk-docker-plugin-1.22.0.tgz#4eb5887934860a60e150d9ebb417ebb071f5cddb" + integrity sha512-bykxNtfeWQNFjF6gv8u8w+TOa4fdr+teLm+DkvYlWkdlvaw5m4yywRI5USve4X6S9p4G+Fw4/wfjXx7LgCcxrQ== dependencies: debug "^3" dockerfile-ast "0.0.12" @@ -12469,10 +12210,10 @@ snyk-go-plugin@1.6.1: tmp "0.0.33" toml "^2.3.2" -snyk-gradle-plugin@2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/snyk-gradle-plugin/-/snyk-gradle-plugin-2.1.3.tgz#cd8dbcbad4b9a1f6cf8dc2f29245505312ae647e" - integrity sha512-xti5Uox0NLPO89O/MQd9qgnlynNtO2eXSukzyjONeGgueyNv6I7FQnUvHtVj6IUCBPlMP8c5D7bQmlFfemz8ZA== +snyk-gradle-plugin@2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/snyk-gradle-plugin/-/snyk-gradle-plugin-2.1.4.tgz#4ca478b87f591bd1c37b5d127c9eff4c042f429c" + integrity sha512-EOG4OEt9KAkMgaiG3IWi1ENkfIehMhQTd/KU+uoNbEJJ9KYk3POcB+7CsRq6kJa2Th/9aMGp+ojm6HOrOkJM4w== dependencies: clone-deep "^0.3.0" @@ -12501,16 +12242,24 @@ snyk-nodejs-lockfile-parser@1.11.0: tslib "^1.9.3" uuid "^3.3.2" -snyk-nuget-plugin@1.6.5: - version "1.6.5" - resolved "https://registry.yarnpkg.com/snyk-nuget-plugin/-/snyk-nuget-plugin-1.6.5.tgz#0a5d53ba47a8bbdc82e245171446ec0485cc591b" - integrity sha512-3qIndzkxCxiaGvAwMkqChbChGdwhNePPyfi0WjhC/nJGwecqU3Fb/NeTW7lgyT+xoq/dFnzW0DgBJ4+AyNA2gA== +snyk-nuget-plugin@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/snyk-nuget-plugin/-/snyk-nuget-plugin-1.7.2.tgz#35ae0a0c68bfc68c6c52710bebd037ff765d1f9a" + integrity sha512-zmYD9veH7OeIqGnZHiGv8c8mKtmYrxo2o7P4lNUkpHdCMMsar7moRJxGgO9WlcIrwAGjIhMdP9fUvJ+jVDEteQ== dependencies: debug "^3.1.0" jszip "^3.1.5" lodash "^4.17.10" + snyk-paket-parser "1.4.3" xml2js "^0.4.17" +snyk-paket-parser@1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/snyk-paket-parser/-/snyk-paket-parser-1.4.3.tgz#380ae8c5fb598f81c110f6b645c728c9cc50b7a5" + integrity sha512-6m736zGVoeT/zS9KEtlmqTSPEPjAfLe8iYoQ3AwbyxDhzuLY49lTaV67MyZtGwjhi1x4KBe+XOgeWwyf6Avf/A== + dependencies: + tslib "^1.9.3" + snyk-php-plugin@1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/snyk-php-plugin/-/snyk-php-plugin-1.5.2.tgz#277512f6f2bbb87ae0c6f4859cef44591f732b30" @@ -12592,37 +12341,37 @@ snyk-try-require@1.3.1, snyk-try-require@^1.1.1, snyk-try-require@^1.3.1: lru-cache "^4.0.0" then-fs "^2.0.0" -snyk@^1.118.0: - version "1.126.0" - resolved "https://registry.yarnpkg.com/snyk/-/snyk-1.126.0.tgz#14224b054a08ca0963587f95ba44558ce75a89d0" - integrity sha512-n6N0Qt8QbsxzPhMFkAdCoV7GyamjRfYmRGWyELXhqETcjbhnVTGmL2hlhdG8/TEajkVUXdb1DKzc0tBtPBKBjQ== +snyk@^1.136.1: + version "1.136.1" + resolved "https://registry.yarnpkg.com/snyk/-/snyk-1.136.1.tgz#bc5c14f00f9e16fa71bab8502eea426a6fede51d" + integrity sha512-BhUH2xUvhFR0rzHX2Zf6Eh3JiUgSt8YmLSTUSHKLkUDqyKHyXvig4cItgj/VN0v0K5XLNzzOn2+RjI/zCiAaIQ== dependencies: - "@snyk/dep-graph" "1.1.2" - "@snyk/gemfile" "1.1.0" + "@snyk/dep-graph" "1.4.0" + "@snyk/gemfile" "1.2.0" abbrev "^1.1.1" ansi-escapes "^3.1.0" chalk "^2.4.1" configstore "^3.1.2" debug "^3.1.0" + diff "^4.0.1" get-uri "2.0.2" - hasbin "^1.2.3" inquirer "^3.0.0" - lodash "^4.17.5" + lodash "^4.17.11" needle "^2.2.4" - opn "^5.2.0" - os-name "^2.0.1" + opn "^5.4.0" + os-name "^3.0.0" proxy-agent "2.3.1" proxy-from-env "^1.0.0" recursive-readdir "^2.2.2" - semver "^5.5.0" - snyk-config "2.2.0" - snyk-docker-plugin "1.19.0" + semver "^5.6.0" + snyk-config "2.2.1" + snyk-docker-plugin "1.22.0" snyk-go-plugin "1.6.1" - snyk-gradle-plugin "2.1.3" + snyk-gradle-plugin "2.1.4" snyk-module "1.9.1" snyk-mvn-plugin "2.0.1" snyk-nodejs-lockfile-parser "1.11.0" - snyk-nuget-plugin "1.6.5" + snyk-nuget-plugin "1.7.2" snyk-php-plugin "1.5.2" snyk-policy "1.13.3" snyk-python-plugin "1.9.1" @@ -12631,12 +12380,11 @@ snyk@^1.118.0: snyk-sbt-plugin "2.0.1" snyk-tree "^1.0.0" snyk-try-require "1.3.1" - source-map-support "^0.5.9" + source-map-support "^0.5.10" tempfile "^2.0.0" then-fs "^2.0.0" - undefsafe "^2.0.0" update-notifier "^2.5.0" - uuid "^3.2.1" + uuid "^3.3.2" socks-proxy-agent@^3.0.0: version "3.0.1" @@ -12678,7 +12426,7 @@ somever@2.x.x: bounce "1.x.x" hoek "6.x.x" -sonic-boom@^0.7.1: +sonic-boom@^0.7.1, sonic-boom@^0.7.3: version "0.7.3" resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-0.7.3.tgz#cbfc18e87c2b8078b00e38ad9475c05fce5ea696" integrity sha512-A9EyoIeLD+g9vMLYQKjNCatJtAKdBQMW03+L8ZWWX/A6hq+srRCwdqHrBD1R8oSMLXov3oHN13dljtZf12q2Ow== @@ -12692,11 +12440,6 @@ sort-keys@^2.0.0: dependencies: is-plain-obj "^1.0.0" -sorted-object@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/sorted-object/-/sorted-object-2.0.1.tgz#7d631f4bd3a798a24af1dffcfbfe83337a5df5fc" - integrity sha1-fWMfS9OnmKJK8d/8+/6DM3pd9fw= - source-list-map@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" @@ -12720,7 +12463,7 @@ source-map-support@^0.4.15: dependencies: source-map "^0.5.6" -source-map-support@^0.5.6, source-map-support@^0.5.7, source-map-support@^0.5.9, source-map-support@~0.5.9: +source-map-support@^0.5.10, source-map-support@^0.5.6, source-map-support@^0.5.7, source-map-support@^0.5.9, source-map-support@~0.5.9: version "0.5.10" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.10.tgz#2214080bc9d51832511ee2bab96e3c2f9353120c" integrity sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ== @@ -12800,6 +12543,13 @@ split2@^3.0.0: dependencies: readable-stream "^3.0.0" +split2@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/split2/-/split2-3.1.1.tgz#c51f18f3e06a8c4469aaab487687d8d956160bb6" + integrity sha512-emNzr1s7ruq4N+1993yht631/JH+jaj0NYBosuKmLcq+JkGQ9MmTw1RB1fGaTCzUuseRIClrlSLHRNYGwWQ58Q== + dependencies: + readable-stream "^3.0.0" + split@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" @@ -12829,15 +12579,6 @@ sqlite3@4.0.2: node-pre-gyp "^0.10.3" request "^2.87.0" -sqlite3@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-4.0.6.tgz#e587b583b5acc6cb38d4437dedb2572359c080ad" - integrity sha512-EqBXxHdKiwvNMRCgml86VTL5TK1i0IKiumnfxykX0gh6H6jaKijAXvE9O1N7+omfNSawR2fOmIyJZcfe8HYWpw== - dependencies: - nan "~2.10.0" - node-pre-gyp "^0.11.0" - request "^2.87.0" - sshpk@^1.7.0: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" @@ -12872,11 +12613,21 @@ stack-trace@0.0.10, stack-trace@0.0.x: resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= +stack-trace@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.6.tgz#1e719bd6a2629ff09c189e17a9ef902a94fc5db0" + integrity sha1-HnGb1qJin/CcGJ4Xqe+QKpT8XbA= + stack-utils@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8" integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA== +stackframe@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-0.3.1.tgz#33aa84f1177a5548c8935533cbfeb3420975f5a4" + integrity sha1-M6qE8Rd6VUjIk1Uzy/6zQgl19aQ= + stackframe@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.0.4.tgz#357b24a992f9427cba6b545d96a14ed2cbca187b" @@ -12908,21 +12659,23 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2": +"statuses@>= 1.4.0 < 2": version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= -statuses@~1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" - integrity sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew== - -stealthy-require@^1.1.0: +stealthy-require@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= +steno@^0.4.1: + version "0.4.4" + resolved "https://registry.yarnpkg.com/steno/-/steno-0.4.4.tgz#071105bdfc286e6615c0403c27e9d7b5dcb855cb" + integrity sha1-BxEFvfwobmYVwEA8J+nXtdy4Vcs= + dependencies: + graceful-fs "^4.1.3" + stream-browserify@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" @@ -12955,11 +12708,6 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI= -streamsearch@0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" - integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= - strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" @@ -13032,12 +12780,7 @@ stringify-object@^3.2.2: is-obj "^1.0.1" is-regexp "^1.0.0" -stringstream@~0.0.4: - version "0.0.6" - resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72" - integrity sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA== - -strip-ansi@^3.0.0, strip-ansi@^3.0.1, strip-ansi@~3.0.1: +strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= @@ -13101,17 +12844,6 @@ strong-log-transformer@^2.0.0: minimist "^1.2.0" through "^2.3.4" -subscriptions-transport-ws@^0.9.11: - version "0.9.15" - resolved "https://registry.yarnpkg.com/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.15.tgz#68a8b7ba0037d8c489fb2f5a102d1494db297d0d" - integrity sha512-f9eBfWdHsePQV67QIX+VRhf++dn1adyC/PZHP6XI5AfKnZ4n0FW+v5omxwdHVpd4xq2ZijaHEcmlQrhBY79ZWQ== - dependencies: - backo2 "^1.0.2" - eventemitter3 "^3.1.0" - iterall "^1.2.1" - symbol-observable "^1.0.4" - ws "^5.2.0" - subtext@6.x.x: version "6.0.12" resolved "https://registry.yarnpkg.com/subtext/-/subtext-6.0.12.tgz#ac09be3eac1eca3396933adeadd65fc781f64fc1" @@ -13150,6 +12882,13 @@ supports-color@^5.0.0, supports-color@^5.3.0, supports-color@^5.4.0, supports-co dependencies: has-flag "^3.0.0" +supports-color@^6.0.0, supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + supports-hyperlinks@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-1.0.1.tgz#71daedf36cc1060ac5100c351bb3da48c29c0ef7" @@ -13158,7 +12897,7 @@ supports-hyperlinks@^1.0.1: has-flag "^2.0.0" supports-color "^5.0.0" -symbol-observable@^1.0.4, symbol-observable@^1.1.0: +symbol-observable@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== @@ -13173,17 +12912,35 @@ synchronous-promise@^2.0.5: resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.6.tgz#de76e0ea2b3558c1e673942e47e714a930fa64aa" integrity sha512-TyOuWLwkmtPL49LHCX1caIwHjRzcVd62+GF6h8W/jHOeZUFHpnd2XJDVuUlaTaLPH1nuu2M69mfHr5XbQJnf/g== -tail@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/tail/-/tail-2.0.2.tgz#86073f3a9a568807b7fd886897a7350314275b5f" - integrity sha512-raFipiKWdGKEzxbvZwnhUGqjvsv0gpa/1A479rL//NOxnNwYZDN4MPk6xJJdUFs8P2Xrff3nbH5fcyYRLU4UHQ== - tapable@^1.0.0, tapable@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.1.tgz#4d297923c5a72a42360de2ab52dadfaaec00018e" integrity sha512-9I2ydhj8Z9veORCw5PRm4u9uebCn0mcCa6scWoNcbZ6dAtoo2618u9UUzxgmsCOreJpqDDuv61LvwofW7hLcBA== -tar@^2.0.0, tar@~2.2.1: +tar-fs@^1.16.2: + version "1.16.3" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-1.16.3.tgz#966a628841da2c4010406a82167cbd5e0c72d509" + integrity sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw== + dependencies: + chownr "^1.0.1" + mkdirp "^0.5.1" + pump "^1.0.0" + tar-stream "^1.1.2" + +tar-stream@^1.1.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555" + integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A== + dependencies: + bl "^1.0.0" + buffer-alloc "^1.2.0" + end-of-stream "^1.0.0" + fs-constants "^1.0.0" + readable-stream "^2.3.0" + to-buffer "^1.1.1" + xtend "^4.0.0" + +tar@^2.0.0: version "2.2.1" resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" integrity sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE= @@ -13210,6 +12967,15 @@ teamwork@3.x.x: resolved "https://registry.yarnpkg.com/teamwork/-/teamwork-3.0.3.tgz#0c08748efe00c32c1eaf1128ef7f07ba0c7cc4ea" integrity sha512-OCB56z+G70iA1A1OFoT+51TPzfcgN0ks75uN3yhxA+EU66WTz2BevNDK4YzMqfaL5tuAvxy4iFUn35/u8pxMaQ== +teeny-request@^3.7.0: + version "3.11.3" + resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-3.11.3.tgz#335c629f7645e5d6599362df2f3230c4cbc23a55" + integrity sha512-CKncqSF7sH6p4rzCgkb/z/Pcos5efl0DmolzvlqRQUNcpRIruOhY9+T1FsIlyEbfWd7MsFpodROOwHYh2BaXzw== + dependencies: + https-proxy-agent "^2.2.1" + node-fetch "^2.2.0" + uuid "^3.3.2" + temp-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" @@ -13242,25 +13008,10 @@ term-size@^1.2.0: dependencies: execa "^0.7.0" -terraformer-wkt-parser@^1.1.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/terraformer-wkt-parser/-/terraformer-wkt-parser-1.2.0.tgz#c9d6ac3dff25f4c0bd344e961f42694961834c34" - integrity sha512-QU3iA54St5lF8Za1jg1oj4NYc8sn5tCZ08aNSWDeGzrsaV48eZk1iAVWasxhNspYBoCqdHuoot1pUTUrE1AJ4w== - dependencies: - "@types/geojson" "^1.0.0" - terraformer "~1.0.5" - -terraformer@~1.0.5: - version "1.0.9" - resolved "https://registry.yarnpkg.com/terraformer/-/terraformer-1.0.9.tgz#77851fef4a49c90b345dc53cf26809fdf29dcda6" - integrity sha512-YlmQ1fsMWTkKGDGibCRWgmLzrpDRUr63Q025LJ/taYQ6j1Yb8q9McKF7NBi6ACAyUXO6F/bl9w6v4MY307y5Ag== - optionalDependencies: - "@types/geojson" "^1.0.0" - terser-webpack-plugin@^1.1.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.2.2.tgz#9bff3a891ad614855a7dde0d707f7db5a927e3d9" - integrity sha512-1DMkTk286BzmfylAvLXwpJrI7dWa5BnFmscV/2dCr8+c56egFcbaeFAl7+sujAjdmpLam21XRdhA4oifLyiWWg== + version "1.2.3" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.2.3.tgz#3f98bc902fac3e5d0de730869f50668561262ec8" + integrity sha512-GOK7q85oAb/5kE12fMuLdn2btOS9OBZn4VsecpHDywoUC/jLhSAKOiYo0ezx7ss2EXPMzyEWFoE0s1WLE+4+oA== dependencies: cacache "^11.0.2" find-cache-dir "^2.0.0" @@ -13291,6 +13042,16 @@ test-exclude@^4.2.1: read-pkg-up "^1.0.1" require-main-filename "^1.0.1" +test-exclude@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.1.0.tgz#6ba6b25179d2d38724824661323b73e03c0c1de1" + integrity sha512-gwf0S2fFsANC55fSeSqpb8BYk6w3FDvwZxfNjeF6FRgvFa43r+7wRiA/Q0IxoRU37wB/LE8IQ4221BsNucTaCA== + dependencies: + arrify "^1.0.1" + minimatch "^3.0.4" + read-pkg-up "^4.0.0" + require-main-filename "^1.0.1" + text-extensions@^1.0.0: version "1.9.0" resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26" @@ -13301,11 +13062,6 @@ text-hex@1.0.x: resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== -text-table@~0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= - then-fs@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/then-fs/-/then-fs-2.0.0.tgz#72f792dd9d31705a91ae19ebfcf8b3f968c81da2" @@ -13313,6 +13069,20 @@ then-fs@^2.0.0: dependencies: promise ">=3.2 <8" +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY= + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.0" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.0.tgz#e69e38a1babe969b0108207978b9f62b88604839" + integrity sha1-5p44obq+lpsBCCB5eLn2K4hgSDk= + dependencies: + any-promise "^1.0.0" + thirty-two@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/thirty-two/-/thirty-two-1.0.2.tgz#4ca2fffc02a51290d2744b9e3f557693ca6b627a" @@ -13396,6 +13166,11 @@ to-arraybuffer@^1.0.0: resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= +to-buffer@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" + integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg== + to-fast-properties@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" @@ -13436,11 +13211,6 @@ to-regex@^3.0.1, to-regex@^3.0.2: regex-not "^1.0.2" safe-regex "^1.1.0" -toidentifier@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" - integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== - toml@^2.3.2: version "2.3.6" resolved "https://registry.yarnpkg.com/toml/-/toml-2.3.6.tgz#25b0866483a9722474895559088b436fd11f861b" @@ -13453,26 +13223,12 @@ topo@3.x.x: dependencies: hoek "6.x.x" -toposort-class@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toposort-class/-/toposort-class-1.0.1.tgz#7ffd1f78c8be28c3ba45cd4e1a3f5ee193bd9988" - integrity sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg= - toposort@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA= -tough-cookie@>=2.3.3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" - integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg== - dependencies: - ip-regex "^2.1.0" - psl "^1.1.28" - punycode "^2.1.1" - -tough-cookie@^2.3.4: +tough-cookie@^2.3.3, tough-cookie@^2.3.4: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== @@ -13480,13 +13236,6 @@ tough-cookie@^2.3.4: psl "^1.1.28" punycode "^2.1.1" -tough-cookie@~2.3.0: - version "2.3.4" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" - integrity sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA== - dependencies: - punycode "^1.4.1" - tough-cookie@~2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" @@ -13502,6 +13251,11 @@ tr46@^1.0.1: dependencies: punycode "^2.1.0" +traverse-chain@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/traverse-chain/-/traverse-chain-0.1.0.tgz#61dbc2d53b69ff6091a12a168fd7d433107e40f1" + integrity sha1-YdvC1Ttp/2CRoSoWj9fUMxB+QPE= + treeify@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/treeify/-/treeify-1.1.0.tgz#4e31c6a463accd0943879f30667c4fdaff411bb8" @@ -13532,10 +13286,10 @@ triple-beam@^1.2.0, triple-beam@^1.3.0: resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== -ts-jest@^23.10.5: - version "23.10.5" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-23.10.5.tgz#cdb550df4466a30489bf70ba867615799f388dd5" - integrity sha512-MRCs9qnGoyKgFc8adDEntAOP64fWK1vZKnOYU1o2HxaqjdJvGqmkLCPCnVq1/If4zkUmEjKPnCiUisTrlX2p2A== +ts-jest@^24.0.0: + version "24.0.0" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-24.0.0.tgz#3f26bf2ec1fa584863a5a9c29bd8717d549efbf6" + integrity sha512-o8BO3TkMREpAATaFTrXkovMsCpBl2z4NDBoLJuWZcJJj1ijI49UnvDMfVpj+iogn/Jl8Pbhuei5nc/Ti+frEHw== dependencies: bs-logger "0.x" buffer-from "1.x" @@ -13563,15 +13317,15 @@ tslib@^1, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== -tslint-config-prettier@^1.17.0: +tslint-config-prettier@^1.18.0: version "1.18.0" resolved "https://registry.yarnpkg.com/tslint-config-prettier/-/tslint-config-prettier-1.18.0.tgz#75f140bde947d35d8f0d238e0ebf809d64592c37" integrity sha512-xPw9PgNPLG3iKRxmK7DWr+Ea/SzrvfHtjFt5LBl61gk2UBG/DB9kCXRjv+xyIU1rUtnayLeMUVJBcMX8Z17nDg== -tslint@^5.12.0: - version "5.12.1" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.12.1.tgz#8cec9d454cf8a1de9b0a26d7bdbad6de362e52c1" - integrity sha512-sfodBHOucFg6egff8d1BvuofoOQ/nOeYNfbp7LDlKBcLNrL3lmS5zoiDGyOMdT7YsEXAwWpTdAHwOGOc8eRZAw== +tslint@^5.13.1: + version "5.13.1" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.13.1.tgz#fbc0541c425647a33cd9108ce4fd4cd18d7904ed" + integrity sha512-fplQqb2miLbcPhyHoMV4FU9PtNRbgmm/zI5d3SZwwmJQM6V0eodju+hplpyfhLWpmwrDNfNYU57uYRb8s0zZoQ== dependencies: babel-code-frame "^6.22.0" builtin-modules "^1.1.1" @@ -13581,6 +13335,7 @@ tslint@^5.12.0: glob "^7.1.1" js-yaml "^3.7.0" minimatch "^3.0.4" + mkdirp "^0.5.1" resolve "^1.3.2" semver "^5.3.0" tslib "^1.8.0" @@ -13605,11 +13360,6 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" -tunnel-agent@~0.4.1: - version "0.4.3" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" - integrity sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us= - tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" @@ -13622,13 +13372,10 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-is@~1.6.16: - version "1.6.16" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" - integrity sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.18" +type-detect@^4.0.0, type-detect@^4.0.5: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== typechecker@^4.0.1, typechecker@^4.3.0: version "4.7.0" @@ -13642,48 +13389,15 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -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" - integrity sha1-bcJDPnjti+qOiHo6zeLzF4W9Yic= - -typedoc@^0.13.0: - version "0.13.0" - resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.13.0.tgz#9efdf352bd54873955cd161bd4b75f24a8c59dde" - integrity sha512-jQWtvPcV+0fiLZAXFEe70v5gqjDO6pJYJz4mlTtmGJeW2KRoIU/BEfktma6Uj8Xii7UakuZjbxFewl3UYOkU/w== - dependencies: - "@types/fs-extra" "^5.0.3" - "@types/handlebars" "^4.0.38" - "@types/highlight.js" "^9.12.3" - "@types/lodash" "^4.14.110" - "@types/marked" "^0.4.0" - "@types/minimatch" "3.0.3" - "@types/shelljs" "^0.8.0" - fs-extra "^7.0.0" - handlebars "^4.0.6" - highlight.js "^9.0.0" - lodash "^4.17.10" - marked "^0.4.0" - minimatch "^3.0.0" - progress "^2.0.0" - shelljs "^0.8.2" - typedoc-default-themes "^0.5.0" - typescript "3.1.x" - typeforce@^1.11.5: version "1.18.0" resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.18.0.tgz#d7416a2c5845e085034d70fcc5b6cc4a90edbfdc" integrity sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g== -typescript@3.1.x: - version "3.1.6" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.6.tgz#b6543a83cfc8c2befb3f4c8fba6896f5b0c9be68" - integrity sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA== - -typescript@^3.2.2: - version "3.3.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.3.tgz#f1657fc7daa27e1a8930758ace9ae8da31403221" - integrity sha512-Y21Xqe54TBVp+VDSNbuDYdGw0BpoR/Q6wo/+35M8PAU0vipahnyduJWirxxdxjsAkS7hue53x2zp8gz7F05u0A== +typescript@^3.3.3333: + version "3.3.3333" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.3333.tgz#171b2c5af66c59e9431199117a3bcadc66fdcfd6" + integrity sha512-JjSKsAfuHBE/fB2oZ8NxtRTk5iGcg6hkYXMnZ3Wc+b2RSqejEqTaem11mHASMnFilHrax3sLK0GDzcJrekZYLw== uglify-js@^3.1.4: version "3.4.9" @@ -13698,26 +13412,11 @@ uid-number@0.0.6: resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" integrity sha1-DqEOgDXo61uOREnwbaHHMGY7qoE= -umask@^1.1.0, umask@~1.1.0: +umask@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d" integrity sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0= -umzug@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/umzug/-/umzug-2.2.0.tgz#6160bdc1817e4a63a625946775063c638623e62e" - integrity sha512-xZLW76ax70pND9bx3wqwb8zqkFGzZIK8dIHD9WdNy/CrNfjWcwQgQkGCuUqcuwEBvUm+g07z+qWvY+pxDmMEEw== - dependencies: - babel-runtime "^6.23.0" - bluebird "^3.5.3" - -undefsafe@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.2.tgz#225f6b9e0337663e0d8e7cfd686fc2836ccace76" - integrity sha1-Il9rngM3Zj4Njnz9aG/Cg2zKznY= - dependencies: - debug "^2.2.0" - unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" @@ -13731,15 +13430,15 @@ unicode-match-property-ecmascript@^1.0.4: unicode-canonical-property-names-ecmascript "^1.0.4" unicode-property-aliases-ecmascript "^1.0.4" -unicode-match-property-value-ecmascript@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.0.2.tgz#9f1dc76926d6ccf452310564fd834ace059663d4" - integrity sha512-Rx7yODZC1L/T8XKo/2kNzVAQaRE88AaMvI1EF/Xnj3GW2wzN6fop9DDWuFAKUVFH7vozkz26DzP0qyWLKLIVPQ== +unicode-match-property-value-ecmascript@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz#5b4b426e08d13a80365e0d657ac7a6c1ec46a277" + integrity sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g== unicode-property-aliases-ecmascript@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz#5a533f31b4317ea76f17d807fa0d116546111dd0" - integrity sha512-2WSLa6OdYd2ng8oqiGIWnJqyFArvhn+5vgx5GTxMbUYjCYKUcuKS62YLFF0R/BDGlB1yzXjQOLtPAfHsgirEpg== + version "1.0.5" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz#a9cc6cc7ce63a0a3023fc99e341b94431d405a57" + integrity sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw== union-value@^1.0.0: version "1.0.0" @@ -13751,7 +13450,7 @@ union-value@^1.0.0: is-extendable "^0.1.1" set-value "^0.4.3" -unique-filename@^1.1.1, unique-filename@~1.1.0: +unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== @@ -13797,11 +13496,11 @@ universalify@^0.1.0: integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== unorm@^1.3.3: - version "1.4.1" - resolved "https://registry.yarnpkg.com/unorm/-/unorm-1.4.1.tgz#364200d5f13646ca8bcd44490271335614792300" - integrity sha1-NkIA1fE2RsqLzURJAnEzVhR5IwA= + version "1.5.0" + resolved "https://registry.yarnpkg.com/unorm/-/unorm-1.5.0.tgz#01fa9b76f1c60f7916834605c032aa8962c3f00a" + integrity sha512-sMfSWoiRaXXeDZSXC+YRZ23H4xchQpwxjpw1tmfR+kgbBCaOgln4NI0LXejJIhnBuKINrB3WRn+ZI8IWssirVw== -unpipe@1.0.0, unpipe@~1.0.0: +unpipe@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= @@ -13820,11 +13519,11 @@ unzip-response@^2.0.1: integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c= upath@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd" - integrity sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw== + version "1.1.1" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.1.tgz#497f7c1090b0818f310bbfb06783586a68d28014" + integrity sha512-D0yetkpIOKiZQquxjM2Syvy48Y1DbZ0SWxgsZiwd9GCWRpc75vN8ytzem14WDSg+oiX6+Qt31FpiS/ExODCrLg== -update-notifier@^2.1.0, update-notifier@^2.2.0, update-notifier@^2.5.0: +update-notifier@^2.1.0, update-notifier@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6" integrity sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw== @@ -13920,11 +13619,6 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -util-extend@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/util-extend/-/util-extend-1.0.3.tgz#a7c216d267545169637b3b6edc6ca9119e2ff93f" - integrity sha1-p8IW0mdUUWljeztu3GypEZ4v+T8= - util.promisify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" @@ -13947,17 +13641,17 @@ util@^0.11.0: dependencies: inherits "2.0.3" -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= - uuid-parse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/uuid-parse/-/uuid-parse-1.0.0.tgz#f4657717624b0e4b88af36f98d89589a5bbee569" - integrity sha1-9GV3F2JLDkuIrzb5jYlYmlu+5Wk= + version "1.1.0" + resolved "https://registry.yarnpkg.com/uuid-parse/-/uuid-parse-1.1.0.tgz#7061c5a1384ae0e1f943c538094597e1b5f3a65b" + integrity sha512-OdmXxA8rDsQ7YpNVbKSJkNzTw2I+S5WsbMDnCtIWSQaosNAcWtFuI/YK1TjzUI6nbkgiqEyh8gWngfcv8Asd9A== -uuid@^3.0.1, uuid@^3.1.0, uuid@^3.2.1, uuid@^3.3.2: +uuid@3.0.x: + version "3.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" + integrity sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE= + +uuid@^3.0.1, uuid@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== @@ -13982,23 +13676,6 @@ validate-npm-package-name@^3.0.0: dependencies: builtins "^1.0.3" -validate-npm-package-name@~2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-2.2.2.tgz#f65695b22f7324442019a3c7fa39a6e7fd299085" - integrity sha1-9laVsi9zJEQgGaPH+jmm5/0pkIU= - dependencies: - builtins "0.0.7" - -validator@^10.4.0: - version "10.11.0" - resolved "https://registry.yarnpkg.com/validator/-/validator-10.11.0.tgz#003108ea6e9a9874d31ccc9e5006856ccd76b228" - integrity sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw== - -vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= - verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" @@ -14080,7 +13757,7 @@ webidl-conversions@^4.0.2: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== -webpack-cli@^3.1.2: +webpack-cli@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.2.3.tgz#13653549adfd8ccd920ad7be1ef868bacc22e346" integrity sha512-Ik3SjV6uJtWIAN5jp5ZuBMWEAaP5E4V78XJ2nI+paFPh8v4HPSwo/myN0r29Xc/6ZKnd2IdrAlpSgNOu2CDQ6Q== @@ -14097,7 +13774,7 @@ webpack-cli@^3.1.2: v8-compile-cache "^2.0.2" yargs "^12.0.4" -webpack-merge@^4.1.5, webpack-merge@^4.2.1: +webpack-merge@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.1.tgz#5e923cf802ea2ace4fd5af1d3247368a633489b4" integrity sha512-4p8WQyS98bUJcCvFMbdGZyZmsKuWjWVnVHnAS3FFg0HDaRVrPbkivx2RYCre8UiemD67RsiFFLfn4JhLAin8Vw== @@ -14117,15 +13794,15 @@ webpack-sources@^1.1.0, webpack-sources@^1.3.0: source-list-map "^2.0.0" source-map "~0.6.1" -webpack@^4.27.1: - version "4.29.3" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.29.3.tgz#e0b406a7b4201ed5e4fb4f84fd7359f9a7db4647" - integrity sha512-xPJvFeB+8tUflXFq+OgdpiSnsCD5EANyv56co5q8q8+YtEasn5Sj3kzY44mta+csCIEB0vneSxnuaHkOL2h94A== +webpack@^4.29.6: + version "4.29.6" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.29.6.tgz#66bf0ec8beee4d469f8b598d3988ff9d8d90e955" + integrity sha512-MwBwpiE1BQpMDkbnUUaW6K8RFZjljJHArC6tWQJoFm0oQtfoSebtg4Y7/QHnJ/SddtjYLHaKGX64CFjG5rehJw== dependencies: - "@webassemblyjs/ast" "1.7.11" - "@webassemblyjs/helper-module-context" "1.7.11" - "@webassemblyjs/wasm-edit" "1.7.11" - "@webassemblyjs/wasm-parser" "1.7.11" + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-module-context" "1.8.5" + "@webassemblyjs/wasm-edit" "1.8.5" + "@webassemblyjs/wasm-parser" "1.8.5" acorn "^6.0.5" acorn-dynamic-import "^4.0.0" ajv "^6.1.0" @@ -14189,13 +13866,6 @@ which@1, which@^1.2.10, which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1.3.0 dependencies: isexe "^2.0.0" -which@~1.2.11: - version "1.2.14" - resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5" - integrity sha1-mofEN48D6CfOyvGs31bHNsAcFOU= - dependencies: - isexe "^2.0.0" - wide-align@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" @@ -14217,13 +13887,6 @@ wif@^2.0.6: dependencies: bs58check "<3.0.0" -win-release@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/win-release/-/win-release-1.1.1.tgz#5fa55e02be7ca934edfc12665632e849b72e5209" - integrity sha1-X6VeAr58qTTt/BJmVjLoSbcuUgk= - dependencies: - semver "^5.0.1" - window-size@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876" @@ -14246,9 +13909,9 @@ winston-compat@^0.1.4: triple-beam "^1.2.0" winston-daily-rotate-file@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/winston-daily-rotate-file/-/winston-daily-rotate-file-3.6.0.tgz#93b18c62ee778611494955ed0740f3a3dd67ba19" - integrity sha512-J02YBlG+NJkgpcz0xOlziaNUCEXB/Rer9L3h1b3IClzEH/XsGDpttmyxaXN4pECBxkOSTyk2YDFiqGlTIlxmbw== + version "3.8.0" + resolved "https://registry.yarnpkg.com/winston-daily-rotate-file/-/winston-daily-rotate-file-3.8.0.tgz#887de6dcfad798374d4f4ed0598afd587541ffa4" + integrity sha512-k3usQWe2Iqudi4Ys/tAiGJODSXvqMF+esOIiMJRpWNYnrbuAXBccpaODttDP3GiGVx3H8tE/pS8K3CvkNMqXiw== dependencies: file-stream-rotator "^0.4.1" object-hash "^1.3.0" @@ -14280,13 +13943,6 @@ winston@^3.2.1: triple-beam "^1.3.0" winston-transport "^4.3.0" -wkx@^0.4.1: - version "0.4.6" - resolved "https://registry.yarnpkg.com/wkx/-/wkx-0.4.6.tgz#228ab592e6457382ea6fb79fc825058d07fce523" - integrity sha512-LHxXlzRCYQXA9ZHgs8r7Gafh0gVOE8o3QmudM1PIkOdkXXjW7Thcl+gb2P2dRuKgW8cqkitCRZkkjtmWzpHi7A== - dependencies: - "@types/node" "*" - wordwrap@~0.0.2: version "0.0.3" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" @@ -14329,20 +13985,29 @@ wrap-ansi@^4.0.0: string-width "^2.1.1" strip-ansi "^4.0.0" -wrappy@1, wrappy@~1.0.2: +wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= wreck@14.x.x, wreck@^14.0.2: - version "14.1.4" - resolved "https://registry.yarnpkg.com/wreck/-/wreck-14.1.4.tgz#c635358f4f5c99c2b977978258443193830e8274" - integrity sha512-gW04OgnCDjF/V4JRILqz/vLN/Ywlls8HUPKtXzUH0EClb1SkwyebFBpO71Wp2hFtKCgZJfodzFYa+CZoWrYflA== + version "14.2.0" + resolved "https://registry.yarnpkg.com/wreck/-/wreck-14.2.0.tgz#0064a5b930fc675f57830c1fd28342da1a70b0fc" + integrity sha512-NFFft3SMgqrJbXEVfYifh+QDWFxni+98/I7ut7rLbz3F0XOypluHsdo3mdEYssGSirMobM3fGlqhyikbWKDn2Q== dependencies: boom "7.x.x" bourne "1.x.x" hoek "6.x.x" +write-file-atomic@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.1.tgz#d0b05463c188ae804396fd5ab2a370062af87529" + integrity sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg== + dependencies: + graceful-fs "^4.1.11" + imurmurhash "^0.1.4" + signal-exit "^3.0.2" + write-file-atomic@^2.0.0, write-file-atomic@^2.1.0, write-file-atomic@^2.3.0: version "2.4.2" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.2.tgz#a7181706dfba17855d221140a9c06e15fcdd87b9" @@ -14352,15 +14017,6 @@ write-file-atomic@^2.0.0, write-file-atomic@^2.1.0, write-file-atomic@^2.3.0: imurmurhash "^0.1.4" signal-exit "^3.0.2" -write-file-atomic@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-1.2.0.tgz#14c66d4e4cb3ca0565c28cf3b7a6f3e4d5938fab" - integrity sha1-FMZtTkyzygVlwozzt6bz5NWTj6s= - dependencies: - graceful-fs "^4.1.2" - imurmurhash "^0.1.4" - slide "^1.1.5" - write-json-file@^2.2.0, write-json-file@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-2.3.0.tgz#2b64c8a33004d54b8698c76d585a77ceb61da32f" @@ -14388,13 +14044,6 @@ ws@^5.2.0: dependencies: async-limiter "~1.0.0" -ws@^6.0.0: - version "6.1.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.3.tgz#d2d2e5f0e3c700ef2de89080ebc0ac6e1bf3a72d" - integrity sha512-tbSxiT+qJI223AP4iLfQbkbxkwdFcneYinM2+x46Gx2wgvbaOMO36czfdfVUBRTHvzAMRhDd98sA5d/BuWbQdg== - dependencies: - async-limiter "~1.0.0" - xcase@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/xcase/-/xcase-2.0.1.tgz#c7fa72caa0f440db78fd5673432038ac984450b9" @@ -14428,10 +14077,10 @@ xregexp@2.0.0: resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943" integrity sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM= -xstate@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/xstate/-/xstate-4.3.1.tgz#54554b2116470fe630ca3b7e92ff1ce74b508ff9" - integrity sha512-Vhezs4f7iQ8677N8YjKhLR9P9O2QCRvnP++mnrxBDN39SC9N99/ZWdiLp+TnF4EwUt7DsgCIm2BDBVSiMCwvEw== +xstate@^4.3.3: + version "4.3.3" + resolved "https://registry.yarnpkg.com/xstate/-/xstate-4.3.3.tgz#e7712aa48285c8900cd62020953313a0aac8d82e" + integrity sha512-63os3bXeN0oaEsYwtHuqR6N71m6Ro/dZpVNgef9vez7skeabbaVh5hOCVLXPwZqQ9QLUlxAQuc38wlsIibdFkw== xtend@^4.0.0, xtend@~4.0.1: version "4.0.1" @@ -14498,7 +14147,7 @@ yargs@^11.0.0: y18n "^3.2.1" yargs-parser "^9.0.2" -yargs@^12.0.1, yargs@^12.0.4, yargs@^12.0.5: +yargs@^12.0.1, yargs@^12.0.2, yargs@^12.0.4: version "12.0.5" resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13" integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw== @@ -14545,15 +14194,3 @@ yup@^0.26.10: property-expr "^1.5.0" synchronous-promise "^2.0.5" toposort "^2.0.2" - -zen-observable-ts@^0.8.15: - version "0.8.15" - resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.15.tgz#6cf7df6aa619076e4af2f707ccf8a6290d26699b" - integrity sha512-sXKPWiw6JszNEkRv5dQ+lQCttyjHM2Iks74QU5NP8mMPS/NrzTlHDr780gf/wOBqmHkPO6NCLMlsa+fAQ8VE8w== - dependencies: - zen-observable "^0.8.0" - -zen-observable@^0.8.0: - version "0.8.13" - resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.13.tgz#a9f1b9dbdfd2d60a08761ceac6a861427d44ae2e" - integrity sha512-fa+6aDUVvavYsefZw0zaZ/v3ckEtMgCFi30sn91SEZea4y/6jQp05E3omjkX91zV6RVdn15fqnFZ6RKjRGbp2g==