diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml
index ad09fd8b98..9797ef05e7 100644
--- a/.github/workflows/benchmark.yml
+++ b/.github/workflows/benchmark.yml
@@ -8,22 +8,21 @@ on:
pull_request:
types: [ready_for_review, synchronize, opened]
+permissions:
+ contents: read
+
jobs:
benchmark:
runs-on: ubuntu-latest
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
- - name: Set up Python 2.7
- uses: actions/setup-python@v2
- with:
- python-version: '2.7'
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 94aef583d6..2718c95e1e 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -10,21 +10,23 @@ on:
schedule:
- cron: "0 14 * * 3"
+permissions:
+ contents: read
+
jobs:
scan:
+ permissions:
+ actions: read # for github/codeql-action/init to get workflow details
+ contents: read # for actions/checkout to fetch code
+ security-events: write # for github/codeql-action/analyze to upload SARIF results
runs-on: ubuntu-latest
steps:
- name: Checkout repository
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
with:
fetch-depth: 2
- - name: Set up Python 2.7
- uses: actions/setup-python@v2
- with:
- python-version: '2.7'
-
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
diff --git a/.github/workflows/devnet-sync.yml b/.github/workflows/devnet-sync.yml
index 2be4e7df3f..95e118ec5d 100644
--- a/.github/workflows/devnet-sync.yml
+++ b/.github/workflows/devnet-sync.yml
@@ -4,16 +4,19 @@ on:
schedule:
- cron: "0 10 * * *"
+permissions:
+ contents: read
+
jobs:
devnet-sync:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
with:
ref: develop
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
index 74374defc7..a918dfe451 100644
--- a/.github/workflows/e2e.yml
+++ b/.github/workflows/e2e.yml
@@ -8,18 +8,21 @@ on:
pull_request:
types: [ready_for_review, synchronize, opened]
+permissions:
+ contents: read
+
jobs:
e2e:
runs-on: ubuntu-latest
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-e2e-${{ hashFiles('**/yarn.lock') }}
@@ -31,21 +34,21 @@ jobs:
- name: Install and build packages
run: yarn setup
- name: Docker compose up
- run: cd __tests__/e2e/lib/config && docker-compose up -d
+ run: cd __tests__/e2e/lib/config && docker compose up -d
- name: Let the network run during 5min
run: sleep 300
- name: Show logs - node0
if: always()
- run: docker logs config_core0_1
+ run: docker logs config-core0-1
- name: Show logs - node1
if: always()
- run: docker logs config_core1_1
+ run: docker logs config-core1-1
- name: Show logs - node2
if: always()
- run: docker logs config_core2_1
+ run: docker logs config-core2-1
- name: Show logs - node3
if: always()
- run: docker logs config_core3_1
+ run: docker logs config-core3-1
- name: Show logs - node4
if: always()
- run: docker logs config_core4_1
+ run: docker logs config-core4-1
diff --git a/.github/workflows/functional.yml b/.github/workflows/functional.yml
index a99b0a8515..e31c6257fe 100644
--- a/.github/workflows/functional.yml
+++ b/.github/workflows/functional.yml
@@ -8,6 +8,9 @@ on:
pull_request:
types: [ready_for_review, synchronize, opened]
+permissions:
+ contents: read
+
jobs:
core-database:
runs-on: ubuntu-latest
@@ -25,7 +28,7 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
env:
CORE_DB_DATABASE: ark_unitnet
@@ -35,13 +38,9 @@ jobs:
POSTGRES_DB: ark_unitnet
steps:
- - uses: actions/checkout@v2
- - name: Set up Python 2.7
- uses: actions/setup-python@v2
- with:
- python-version: "2.7"
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -67,7 +66,7 @@ jobs:
run: yarn test __tests__/functional/core-database/ --coverage --coverageDirectory .coverage/functional/${{ github.job }}
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/functional/${{ github.job }}/lcov.info
@@ -88,16 +87,12 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
- - name: Set up Python 2.7
- uses: actions/setup-python@v2
- with:
- python-version: "2.7"
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -126,7 +121,7 @@ jobs:
CORE_DB_USERNAME: ark
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/functional/${{ github.job }}/lcov.info
@@ -147,16 +142,12 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
- - name: Set up Python 2.7
- uses: actions/setup-python@v2
- with:
- python-version: "2.7"
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -185,7 +176,7 @@ jobs:
CORE_DB_USERNAME: ark
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/functional/${{ github.job }}/lcov.info
@@ -206,16 +197,12 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
- - name: Set up Python 2.7
- uses: actions/setup-python@v2
- with:
- python-version: "2.7"
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -244,7 +231,7 @@ jobs:
CORE_DB_USERNAME: ark
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/functional/${{ github.job }}/lcov.info
@@ -265,16 +252,12 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
- - name: Set up Python 2.7
- uses: actions/setup-python@v2
- with:
- python-version: "2.7"
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -303,7 +286,7 @@ jobs:
CORE_DB_USERNAME: ark
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/functional/${{ github.job }}/lcov.info
@@ -324,16 +307,12 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
- - name: Set up Python 2.7
- uses: actions/setup-python@v2
- with:
- python-version: "2.7"
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -362,7 +341,7 @@ jobs:
CORE_DB_USERNAME: ark
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/functional/${{ github.job }}/lcov.info
@@ -383,16 +362,12 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
- - name: Set up Python 2.7
- uses: actions/setup-python@v2
- with:
- python-version: "2.7"
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -421,7 +396,7 @@ jobs:
CORE_DB_USERNAME: ark
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/functional/${{ github.job }}/lcov.info
@@ -442,16 +417,12 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
- - name: Set up Python 2.7
- uses: actions/setup-python@v2
- with:
- python-version: "2.7"
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -480,7 +451,7 @@ jobs:
CORE_DB_USERNAME: ark
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/functional/${{ github.job }}/lcov.info
@@ -501,16 +472,12 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
- - name: Set up Python 2.7
- uses: actions/setup-python@v2
- with:
- python-version: "2.7"
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -539,7 +506,7 @@ jobs:
CORE_DB_USERNAME: ark
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/functional/${{ github.job }}/lcov.info
@@ -560,16 +527,12 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
- - name: Set up Python 2.7
- uses: actions/setup-python@v2
- with:
- python-version: "2.7"
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -598,7 +561,7 @@ jobs:
CORE_DB_USERNAME: ark
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/functional/${{ github.job }}/lcov.info
@@ -619,16 +582,12 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
- - name: Set up Python 2.7
- uses: actions/setup-python@v2
- with:
- python-version: "2.7"
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -657,7 +616,7 @@ jobs:
CORE_DB_USERNAME: ark
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/functional/${{ github.job }}/lcov.info
@@ -678,16 +637,12 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
- - name: Set up Python 2.7
- uses: actions/setup-python@v2
- with:
- python-version: "2.7"
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -716,7 +671,7 @@ jobs:
CORE_DB_USERNAME: ark
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/functional/${{ github.job }}/lcov.info
@@ -737,16 +692,12 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
- - name: Set up Python 2.7
- uses: actions/setup-python@v2
- with:
- python-version: "2.7"
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -775,7 +726,7 @@ jobs:
CORE_DB_USERNAME: ark
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/functional/${{ github.job }}/lcov.info
@@ -796,16 +747,12 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
- - name: Set up Python 2.7
- uses: actions/setup-python@v2
- with:
- python-version: "2.7"
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -834,7 +781,7 @@ jobs:
CORE_DB_USERNAME: ark
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/functional/${{ github.job }}/lcov.info
@@ -855,16 +802,12 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
- - name: Set up Python 2.7
- uses: actions/setup-python@v2
- with:
- python-version: "2.7"
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -908,16 +851,12 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
- - name: Set up Python 2.7
- uses: actions/setup-python@v2
- with:
- python-version: "2.7"
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -961,16 +900,12 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
- - name: Set up Python 2.7
- uses: actions/setup-python@v2
- with:
- python-version: "2.7"
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -1014,12 +949,12 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -1063,12 +998,12 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -1112,12 +1047,12 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -1161,16 +1096,12 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
- - name: Set up Python 2.7
- uses: actions/setup-python@v2
- with:
- python-version: "2.7"
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 6552f6177b..3cfde55b08 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -8,6 +8,9 @@ on:
pull_request:
types: [ready_for_review, synchronize, opened]
+permissions:
+ contents: read
+
jobs:
integration:
runs-on: ubuntu-latest
@@ -25,7 +28,7 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
env:
CORE_DB_DATABASE: ark_unitnet
@@ -35,10 +38,10 @@ jobs:
POSTGRES_DB: ark_unitnet
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 6f6941b428..3824b69c82 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -8,19 +8,22 @@ on:
pull_request:
types: [ready_for_review, synchronize, opened]
+permissions:
+ contents: read
+
jobs:
source:
runs-on: ubuntu-latest
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -32,28 +35,7 @@ jobs:
node-version: ${{ matrix.node-version }}
- name: Install & Build
- run: yarn install && yarn bootstrap
+ run: yarn install
- name: Lint Source
run: yarn lint
- # tests:
- # runs-on: ubuntu-latest
- # strategy:
- # matrix:
- # node-version: [12.x]
- # steps:
- # - uses: actions/checkout@v2
- # - name: Cache node modules
- # uses: actions/cache@v1
- # with:
- # path: node_modules
- # key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
- # restore-keys: ${{ runner.os }}-node-
- # - name: Use Node.js ${{ matrix.node-version }}
- # uses: actions/setup-node@v1
- # with:
- # node-version: ${{ matrix.node-version }}
- # - name: Install & Build
- # run: yarn install && yarn bootstrap
- # - name: Lint Source
- # run: yarn lint:tests
diff --git a/.github/workflows/publish-develop.yml b/.github/workflows/publish-develop.yml
index c5f16fe38a..d9c1629b0c 100644
--- a/.github/workflows/publish-develop.yml
+++ b/.github/workflows/publish-develop.yml
@@ -5,6 +5,9 @@ on:
branches:
- "develop"
+permissions:
+ contents: read
+
jobs:
build:
if: "contains(github.event.head_commit.message, 'release:')"
@@ -12,11 +15,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- uses: actions/setup-node@v1
with:
- node-version: 12
+ node-version: 16
registry-url: https://registry.npmjs.org/
- name: Install & Build
diff --git a/.github/workflows/publish-master.yml b/.github/workflows/publish-master.yml
index 7c7ab1c90e..c9710861ed 100644
--- a/.github/workflows/publish-master.yml
+++ b/.github/workflows/publish-master.yml
@@ -5,6 +5,9 @@ on:
branches:
- "master"
+permissions:
+ contents: read
+
jobs:
build:
if: "contains(github.event.head_commit.message, 'release:')"
@@ -12,16 +15,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
-
- - name: Set up Python 2.7
- uses: actions/setup-python@v2
- with:
- python-version: "2.7"
-
+ - uses: actions/checkout@v4
- uses: actions/setup-node@v1
with:
- node-version: 12
+ node-version: 16
registry-url: https://registry.npmjs.org/
- name: Install & Build
diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml
index 78448edb6d..f637637ce5 100644
--- a/.github/workflows/unit.yml
+++ b/.github/workflows/unit.yml
@@ -8,19 +8,22 @@ on:
pull_request:
types: [ready_for_review, synchronize, opened]
+permissions:
+ contents: read
+
jobs:
core:
runs-on: ubuntu-latest
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -38,7 +41,7 @@ jobs:
run: yarn test __tests__/unit/core/ --coverage --coverageDirectory .coverage/unit/${{ github.job }}
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/unit/${{ github.job }}/lcov.info
@@ -48,13 +51,13 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -72,7 +75,7 @@ jobs:
run: yarn test:parallel __tests__/unit/core-api/ --coverage --coverageDirectory .coverage/unit/${{ github.job }}
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/unit/${{ github.job }}/lcov.info
@@ -82,13 +85,13 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -106,7 +109,7 @@ jobs:
run: yarn test:parallel __tests__/unit/core-blockchain/ --coverage --coverageDirectory .coverage/unit/${{ github.job }}
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/unit/${{ github.job }}/lcov.info
@@ -116,13 +119,13 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -140,7 +143,7 @@ jobs:
run: yarn test:parallel __tests__/unit/core-cli/ --coverage --coverageDirectory .coverage/unit/${{ github.job }}
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/unit/${{ github.job }}/lcov.info
@@ -150,13 +153,13 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -174,7 +177,7 @@ jobs:
run: yarn test:parallel __tests__/unit/core-database/ --coverage --coverageDirectory .coverage/unit/${{ github.job }}
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/unit/${{ github.job }}/lcov.info
@@ -184,13 +187,13 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -208,7 +211,7 @@ jobs:
run: yarn test:parallel __tests__/unit/core-forger/ --coverage --coverageDirectory .coverage/unit/${{ github.job }}
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/unit/${{ github.job }}/lcov.info
@@ -218,13 +221,13 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -242,7 +245,7 @@ jobs:
run: yarn test:parallel __tests__/unit/core-kernel/ --coverage --coverageDirectory .coverage/unit/${{ github.job }}
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/unit/${{ github.job }}/lcov.info
@@ -252,13 +255,13 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -276,7 +279,7 @@ jobs:
run: yarn test:parallel __tests__/unit/core-logger-pino/ --coverage --coverageDirectory .coverage/unit/${{ github.job }}
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/unit/${{ github.job }}/lcov.info
@@ -286,13 +289,13 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -310,7 +313,7 @@ jobs:
run: yarn test:parallel __tests__/unit/core-magistrate-api/ --coverage --coverageDirectory .coverage/unit/${{ github.job }}
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/unit/${{ github.job }}/lcov.info
@@ -320,13 +323,13 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -344,7 +347,7 @@ jobs:
run: yarn test:parallel __tests__/unit/core-magistrate-crypto/ --coverage --coverageDirectory .coverage/unit/${{ github.job }}
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/unit/${{ github.job }}/lcov.info
@@ -354,13 +357,13 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -378,7 +381,7 @@ jobs:
run: yarn test:parallel __tests__/unit/core-magistrate-transactions/ --coverage --coverageDirectory .coverage/unit/${{ github.job }}
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/unit/${{ github.job }}/lcov.info
@@ -388,13 +391,13 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -412,7 +415,7 @@ jobs:
run: yarn test __tests__/unit/core-p2p/ --coverage --coverageDirectory .coverage/unit/${{ github.job }}
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/unit/${{ github.job }}/lcov.info
@@ -422,13 +425,13 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -446,7 +449,7 @@ jobs:
run: yarn test:parallel __tests__/unit/core-snapshots/ --coverage --coverageDirectory .coverage/unit/${{ github.job }}
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/unit/${{ github.job }}/lcov.info
@@ -456,13 +459,13 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -480,7 +483,7 @@ jobs:
run: yarn test:parallel __tests__/unit/core-state/ --coverage --coverageDirectory .coverage/unit/${{ github.job }}
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/unit/${{ github.job }}/lcov.info
@@ -490,13 +493,13 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -514,7 +517,7 @@ jobs:
run: yarn test:parallel __tests__/unit/core-test-framework/ --coverage --coverageDirectory .coverage/unit/${{ github.job }}
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/unit/${{ github.job }}/lcov.info
@@ -524,13 +527,13 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -548,7 +551,7 @@ jobs:
run: yarn test:parallel __tests__/unit/core-transaction-pool/ --coverage --coverageDirectory .coverage/unit/${{ github.job }}
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/unit/${{ github.job }}/lcov.info
@@ -558,13 +561,13 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -582,7 +585,7 @@ jobs:
run: yarn test:parallel __tests__/unit/core-transactions/ --coverage --coverageDirectory .coverage/unit/${{ github.job }}
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/unit/${{ github.job }}/lcov.info
@@ -592,13 +595,13 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -616,7 +619,7 @@ jobs:
run: yarn test:parallel __tests__/unit/core-webhooks/ --coverage --coverageDirectory .coverage/unit/${{ github.job }}
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/unit/${{ github.job }}/lcov.info
@@ -626,13 +629,13 @@ jobs:
strategy:
matrix:
- node-version: [12.x]
+ node-version: [16.x]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Cache node modules
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@@ -649,7 +652,7 @@ jobs:
- name: Test
run: yarn test:parallel __tests__/unit/crypto/ --coverage --coverage --coverageDirectory .coverage/unit/${{ github.job }}
- name: Archive code coverage
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-coverage
path: .coverage/unit/${{ github.job }}/lcov.info
diff --git a/CODEOWNERS b/CODEOWNERS
index b3f9a66177..8b675c3833 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -1 +1 @@
-* @faustbrian @air1one @rainydio @sebastijankuzner
+* @itsanametoo @sebastijankuzner @boldninja @oXtxNt9U
diff --git a/README.md b/README.md
index 31d44e4ec1..c235707f5d 100644
--- a/README.md
+++ b/README.md
@@ -4,30 +4,26 @@
-[](https://github.com/ArkEcosystem/core/actions)
[](https://opensource.org/licenses/MIT)
-> Lead Maintainer: [Erwann Gentric](https://github.com/air1one)
+> **Note**
+> Please checkout [MainSail](https://github.com/ArkEcosystem/mainsail) for the next generation of the ARK-Core blockchain protocol featuring a new DPoS consensus engine that is more reliable and efficient. Currently in development.
## Introduction
> This repository contains all plugins that make up the ARK Core.
-Check our [dedicated documentation site](https://learn.ark.dev) for information about all available plugins and [How to write a Core Plugin
-](https://learn.ark.dev/application-development/how-to-write-core-dapps) if you want to get started with developing your own plugins.
+Check our [dedicated documentation site](https://ark.dev/docs/core) for information about all available plugins and [How to write a Core Plugin
+](https://ark.dev/docs/core/development/plugins/intro) if you want to get started with developing your own plugins.
## Documentation
-- Development : https://learn.ark.dev/core-getting-started/setting-up-your-development-environment
-- Docker : https://guides.ark.dev/devops-guides/how-to-setup-a-node-with-docker
+- Development : https://ark.dev/docs/core/installation/intro
+- Docker : https://ark.dev/docs/core/development/docker
## API Documentation
-- API v2 : https://api.ark.dev
-
-## GitHub Development Bounty
-
-- Get involved with the development and start earning ARK : https://bounty.ark.io
+- API v2 : https://ark.dev/docs/api
## Security
diff --git a/__tests__/e2e/README.md b/__tests__/e2e/README.md
index 79e429fe64..e80f783773 100644
--- a/__tests__/e2e/README.md
+++ b/__tests__/e2e/README.md
@@ -16,8 +16,8 @@ You can launch and stop a network like this :
```bash
cd lib/config
-docker-compose up -d # launches the network
-docker-compose down -v # shuts down the network
+docker compose up -d # launches the network
+docker compose down -v # shuts down the network
```
This will launch a network of 5 nodes on testnet.
diff --git a/__tests__/functional/transaction-forging/entity-register.test.ts b/__tests__/functional/transaction-forging/entity-register.test.ts
index 12ddcf7a76..9d58622c87 100644
--- a/__tests__/functional/transaction-forging/entity-register.test.ts
+++ b/__tests__/functional/transaction-forging/entity-register.test.ts
@@ -75,48 +75,10 @@ describe("Transaction Forging - Entity registration", () => {
await expect(entityRegistration.id).not.toBeForged();
await expect(entityRegistration).not.entityRegistered();
});
-
- it("should reject entity registration, because entity type is > 255 [Signed with 1 Passphrase]", async () => {
- // entity registration
- const entityRegistration = TransactionFactory.initialize(app).entity({
- type: 256,
- subType: 1,
- action: Enums.EntityAction.Register,
- data: {
- name: "name256",
- },
- })
- .withPassphrase(secrets[0])
- .createOne();
-
- await expect(entityRegistration).toBeRejected();
- await snoozeForBlock(1);
- await expect(entityRegistration.id).not.toBeForged();
- await expect(entityRegistration).not.entityRegistered();
- });
-
- it("should reject entity registration, because entity sub type is > 255 [Signed with 1 Passphrase]", async () => {
- // entity registration
- const entityRegistration = TransactionFactory.initialize(app).entity({
- type: 1,
- subType: 256,
- action: Enums.EntityAction.Register,
- data: {
- name: "namesubtype256",
- },
- })
- .withPassphrase(secrets[0])
- .createOne();
-
- await expect(entityRegistration).toBeRejected();
- await snoozeForBlock(1);
- await expect(entityRegistration.id).not.toBeForged();
- await expect(entityRegistration).not.entityRegistered();
- });
});
describe("Signed with 2 Passphrases", () => {
- it("should broadcast, accept and forge it [Signed with 2 Passphrases] ", async () => {
+ it("should broadcast, accept and forge it [Signed with 2 Passphrases]", async () => {
// Prepare a fresh wallet for the tests
const passphrase = generateMnemonic();
const secondPassphrase = generateMnemonic();
diff --git a/__tests__/integration/core-api/handlers/delegates.test.ts b/__tests__/integration/core-api/handlers/delegates.test.ts
index 7075a642f4..55c980443a 100644
--- a/__tests__/integration/core-api/handlers/delegates.test.ts
+++ b/__tests__/integration/core-api/handlers/delegates.test.ts
@@ -60,7 +60,7 @@ describe("API 2.0 - Delegates", () => {
const response = await api.request("GET", `delegates`, { address });
expect(response).toBeSuccessfulResponse();
expect(response.data.data).toBeArray();
- expect(response.data.data.map(d => d.address).sort()).toEqual(address.sort());
+ expect(response.data.data.map((d) => d.address).sort()).toEqual(address.sort());
});
});
@@ -74,7 +74,11 @@ describe("API 2.0 - Delegates", () => {
// save a new block so that we can make the request with generatorPublicKey
await app
- .get(Container.Identifiers.DatabaseBlockRepository)
+ .getTagged(
+ Container.Identifiers.DatabaseBlockRepository,
+ "connection",
+ "api",
+ )
.saveBlocks([block2]);
const response = await api.request("GET", `delegates/${block2.data.generatorPublicKey}/blocks`);
@@ -86,7 +90,11 @@ describe("API 2.0 - Delegates", () => {
}
await app
- .get(Container.Identifiers.DatabaseBlockRepository)
+ .getTagged(
+ Container.Identifiers.DatabaseBlockRepository,
+ "connection",
+ "api",
+ )
.deleteBlocks([block2.data]); // reset to genesis block
});
diff --git a/__tests__/integration/core-api/handlers/locks.test.ts b/__tests__/integration/core-api/handlers/locks.test.ts
index fc824d2a44..a11c3c58d2 100644
--- a/__tests__/integration/core-api/handlers/locks.test.ts
+++ b/__tests__/integration/core-api/handlers/locks.test.ts
@@ -68,8 +68,10 @@ describe("API 2.0 - Locks", () => {
})
.build()[0];
- const transactionRepository = app.get(
+ const transactionRepository = app.getTagged(
Container.Identifiers.DatabaseTransactionRepository,
+ "connection",
+ "api",
);
jest.spyOn(transactionRepository, "listByExpression").mockResolvedValueOnce({
diff --git a/__tests__/integration/core-api/handlers/transactions.test.ts b/__tests__/integration/core-api/handlers/transactions.test.ts
index 4f6ebc01d8..180c1329d0 100644
--- a/__tests__/integration/core-api/handlers/transactions.test.ts
+++ b/__tests__/integration/core-api/handlers/transactions.test.ts
@@ -208,7 +208,7 @@ describe("API 2.0 - Transactions", () => {
const response = await api.request("GET", "transactions", { recipientId: recipientAddress });
expect(response).toBeSuccessfulResponse();
expect(response.data.data).toBeArray();
- expect(response.data.data).toHaveLength(3);
+ expect(response.data.data).toHaveLength(2);
for (const transaction of response.data.data) {
api.expectTransaction(transaction);
@@ -224,7 +224,7 @@ describe("API 2.0 - Transactions", () => {
expect(response).toBeSuccessfulResponse();
expect(response.data.data).toBeArray();
- expect(response.data.data).toHaveLength(6);
+ expect(response.data.data).toHaveLength(4);
for (const transaction of response.data.data) {
api.expectTransaction(transaction);
diff --git a/__tests__/integration/core-api/services/__support__/setup.ts b/__tests__/integration/core-api/services/__support__/setup.ts
index 023d6506c4..3e4e63e5c8 100644
--- a/__tests__/integration/core-api/services/__support__/setup.ts
+++ b/__tests__/integration/core-api/services/__support__/setup.ts
@@ -29,7 +29,13 @@ export const setUp = async (): Promise => {
await transactionsServiceProvider.register();
await apiServiceProvider.register();
- app.rebind(Container.Identifiers.StateStore).toConstantValue(null);
+ app.rebind(Container.Identifiers.StateStore).toConstantValue({
+ getLastBlock: jest.fn().mockReturnValue({
+ data: {
+ height: 1,
+ },
+ }),
+ });
const walletAttributes = app.get(Container.Identifiers.WalletAttributes);
walletAttributes.set("delegate.username");
diff --git a/__tests__/integration/core-api/services/delegate-search-service.test.ts b/__tests__/integration/core-api/services/delegate-search-service.test.ts
index f333fc5ff2..1dfdca1ce7 100644
--- a/__tests__/integration/core-api/services/delegate-search-service.test.ts
+++ b/__tests__/integration/core-api/services/delegate-search-service.test.ts
@@ -94,7 +94,7 @@ describe("DelegateSearchService.getDelegate", () => {
});
describe("DelegateSearchService.getDelegatesPage", () => {
- it("should get all delegates when criteria is empty", () => {
+ it("should get all delegates when criteria is empty", async () => {
const wallet1 = walletRepository.findByPublicKey(Identities.PublicKey.fromPassphrase("secret1"));
wallet1.setAttribute("delegate", delegateAttribute1);
walletRepository.index(wallet1);
@@ -103,7 +103,7 @@ describe("DelegateSearchService.getDelegatesPage", () => {
wallet2.setAttribute("delegate", delegateAttribute2);
walletRepository.index(wallet2);
- const delegatesPage = delegateSearchService.getDelegatesPage({ offset: 0, limit: 100 }, [
+ const delegatesPage = await delegateSearchService.getDelegatesPage({ offset: 0, limit: 100 }, [
{ property: "username", direction: "asc" },
]);
@@ -112,7 +112,7 @@ describe("DelegateSearchService.getDelegatesPage", () => {
expect(delegatesPage.results[1].username).toBe("cryptology");
});
- it("should get delegates that match criteria", () => {
+ it("should get delegates that match criteria", async () => {
const wallet1 = walletRepository.findByPublicKey(Identities.PublicKey.fromPassphrase("secret1"));
wallet1.setAttribute("delegate", delegateAttribute1);
walletRepository.index(wallet1);
@@ -121,7 +121,7 @@ describe("DelegateSearchService.getDelegatesPage", () => {
wallet2.setAttribute("delegate", delegateAttribute2);
walletRepository.index(wallet2);
- const delegatesPage = delegateSearchService.getDelegatesPage(
+ const delegatesPage = await delegateSearchService.getDelegatesPage(
{ offset: 0, limit: 100 },
[{ property: "username", direction: "asc" }],
{ username: "binance_staking" },
diff --git a/__tests__/integration/core-api/services/lock-search-service.test.ts b/__tests__/integration/core-api/services/lock-search-service.test.ts
index b7047e9337..331ced8c86 100644
--- a/__tests__/integration/core-api/services/lock-search-service.test.ts
+++ b/__tests__/integration/core-api/services/lock-search-service.test.ts
@@ -59,13 +59,13 @@ beforeEach(async () => {
});
describe("LockSearchService.getLock", () => {
- it("should return lock resource by id", () => {
+ it("should return lock resource by id", async () => {
const wallet1 = walletRepository.findByPublicKey(Identities.PublicKey.fromPassphrase("wallet1"));
wallet1.setAttribute("htlc.locks", { [lockId1]: lockAttribute1 });
walletRepository.index(wallet1);
stateStore.getLastBlock.mockReturnValue({ data: { height: 1000 } } as any);
- const lockResource1 = lockSearchService.getLock(lockId1);
+ const lockResource1 = await lockSearchService.getLock(lockId1);
expect(lockResource1.lockId).toEqual(lockId1);
});
@@ -77,7 +77,7 @@ describe("LockSearchService.getLock", () => {
});
describe("LockSearchService.getLocksPage", () => {
- it("should return all locks", () => {
+ it("should return all locks", async () => {
const wallet1 = walletRepository.findByPublicKey(Identities.PublicKey.fromPassphrase("wallet1"));
wallet1.setAttribute("htlc.locks", { [lockId1]: lockAttribute1 });
walletRepository.index(wallet1);
@@ -87,7 +87,7 @@ describe("LockSearchService.getLocksPage", () => {
walletRepository.index(wallet2);
stateStore.getLastBlock.mockReturnValue({ data: { height: 1000 } } as any);
- const locksPage = lockSearchService.getLocksPage({ offset: 0, limit: 100 }, [
+ const locksPage = await lockSearchService.getLocksPage({ offset: 0, limit: 100 }, [
{ property: "amount", direction: "asc" },
]);
@@ -96,7 +96,7 @@ describe("LockSearchService.getLocksPage", () => {
expect(locksPage.results[1].lockId).toBe(lockId2);
});
- it("should return locks that match criteria", () => {
+ it("should return locks that match criteria", async () => {
const wallet1 = walletRepository.findByPublicKey(Identities.PublicKey.fromPassphrase("wallet1"));
wallet1.setAttribute("htlc.locks", { [lockId1]: lockAttribute1 });
walletRepository.index(wallet1);
@@ -106,7 +106,7 @@ describe("LockSearchService.getLocksPage", () => {
walletRepository.index(wallet2);
stateStore.getLastBlock.mockReturnValue({ data: { height: 1000 } } as any);
- const locksPage = lockSearchService.getLocksPage(
+ const locksPage = await lockSearchService.getLocksPage(
{ offset: 0, limit: 100 },
[{ property: "amount", direction: "asc" }],
{ amount: "1000" },
@@ -118,13 +118,13 @@ describe("LockSearchService.getLocksPage", () => {
});
describe("LockSearchService.getWalletLocksPage", () => {
- it("should return all wallet locks", () => {
+ it("should return all wallet locks", async () => {
const wallet1 = walletRepository.findByPublicKey(Identities.PublicKey.fromPassphrase("wallet1"));
wallet1.setAttribute("htlc.locks", { [lockId1]: lockAttribute1, [lockId2]: lockAttribute2 });
walletRepository.index(wallet1);
stateStore.getLastBlock.mockReturnValue({ data: { height: 1000 } } as any);
- const locksPage = lockSearchService.getWalletLocksPage(
+ const locksPage = await lockSearchService.getWalletLocksPage(
{ offset: 0, limit: 100 },
[{ property: "amount", direction: "asc" }],
wallet1.getAddress(),
@@ -135,13 +135,13 @@ describe("LockSearchService.getWalletLocksPage", () => {
expect(locksPage.results[1].lockId).toBe(lockId2);
});
- it("should return all wallet locks that match criteria", () => {
+ it("should return all wallet locks that match criteria", async () => {
const wallet1 = walletRepository.findByPublicKey(Identities.PublicKey.fromPassphrase("wallet1"));
wallet1.setAttribute("htlc.locks", { [lockId1]: lockAttribute1, [lockId2]: lockAttribute2 });
walletRepository.index(wallet1);
stateStore.getLastBlock.mockReturnValue({ data: { height: 1000 } } as any);
- const locksPage = lockSearchService.getWalletLocksPage(
+ const locksPage = await lockSearchService.getWalletLocksPage(
{ offset: 0, limit: 100 },
[{ property: "amount", direction: "asc" }],
wallet1.getAddress(),
@@ -152,7 +152,7 @@ describe("LockSearchService.getWalletLocksPage", () => {
expect(locksPage.results[0].lockId).toBe(lockId1);
});
- it("should return only wallet locks", () => {
+ it("should return only wallet locks", async () => {
const wallet1 = walletRepository.findByPublicKey(Identities.PublicKey.fromPassphrase("wallet1"));
wallet1.setAttribute("htlc.locks", { [lockId1]: lockAttribute1 });
walletRepository.index(wallet1);
@@ -162,7 +162,7 @@ describe("LockSearchService.getWalletLocksPage", () => {
walletRepository.index(wallet2);
stateStore.getLastBlock.mockReturnValue({ data: { height: 1000 } } as any);
- const locksPage = lockSearchService.getWalletLocksPage(
+ const locksPage = await lockSearchService.getWalletLocksPage(
{ offset: 0, limit: 100 },
[{ property: "amount", direction: "asc" }],
wallet1.getAddress(),
diff --git a/__tests__/integration/core-api/services/wallet-search-service.test.ts b/__tests__/integration/core-api/services/wallet-search-service.test.ts
index c4e9660bd0..004655319b 100644
--- a/__tests__/integration/core-api/services/wallet-search-service.test.ts
+++ b/__tests__/integration/core-api/services/wallet-search-service.test.ts
@@ -68,7 +68,7 @@ describe("WalletSearchService.getWallet", () => {
});
describe("WalletSearchService.getWalletsPage", () => {
- it("should get first three wallets sorted by balance:desc", () => {
+ it("should get first three wallets sorted by balance:desc", async () => {
const wallet1 = walletRepository.findByPublicKey(Identities.PublicKey.fromPassphrase("secret1"));
const wallet2 = walletRepository.findByPublicKey(Identities.PublicKey.fromPassphrase("secret2"));
const wallet3 = walletRepository.findByPublicKey(Identities.PublicKey.fromPassphrase("secret3"));
@@ -81,7 +81,7 @@ describe("WalletSearchService.getWalletsPage", () => {
wallet4.setBalance(Utils.BigNumber.make("40"));
wallet5.setBalance(Utils.BigNumber.make("50"));
- const page = walletSearchService.getWalletsPage({ limit: 3, offset: 0 }, []);
+ const page = await walletSearchService.getWalletsPage({ limit: 3, offset: 0 }, []);
expect(page.results.length).toBe(3);
expect(page.results[0].address).toBe(wallet3.getAddress());
@@ -90,7 +90,7 @@ describe("WalletSearchService.getWalletsPage", () => {
expect(page.totalCount).toBe(5);
});
- it("should get last two wallets sorted by balance:desc", () => {
+ it("should get last two wallets sorted by balance:desc", async () => {
const wallet1 = walletRepository.findByPublicKey(Identities.PublicKey.fromPassphrase("secret1"));
const wallet2 = walletRepository.findByPublicKey(Identities.PublicKey.fromPassphrase("secret2"));
const wallet3 = walletRepository.findByPublicKey(Identities.PublicKey.fromPassphrase("secret3"));
@@ -103,7 +103,7 @@ describe("WalletSearchService.getWalletsPage", () => {
wallet4.setBalance(Utils.BigNumber.make("40"));
wallet5.setBalance(Utils.BigNumber.make("50"));
- const page = walletSearchService.getWalletsPage({ limit: 3, offset: 3 }, []);
+ const page = await walletSearchService.getWalletsPage({ limit: 3, offset: 3 }, []);
expect(page.results.length).toBe(2);
expect(page.results[0].address).toBe(wallet5.getAddress());
@@ -111,7 +111,7 @@ describe("WalletSearchService.getWalletsPage", () => {
expect(page.totalCount).toBe(5);
});
- it("should get only three wallets sorted by balance:desc", () => {
+ it("should get only three wallets sorted by balance:desc", async () => {
const wallet1 = walletRepository.findByPublicKey(Identities.PublicKey.fromPassphrase("secret1"));
const wallet2 = walletRepository.findByPublicKey(Identities.PublicKey.fromPassphrase("secret2"));
const wallet3 = walletRepository.findByPublicKey(Identities.PublicKey.fromPassphrase("secret3"));
@@ -124,7 +124,9 @@ describe("WalletSearchService.getWalletsPage", () => {
wallet4.setBalance(Utils.BigNumber.make("40"));
wallet5.setBalance(Utils.BigNumber.make("50"));
- const page = walletSearchService.getWalletsPage({ limit: 5, offset: 0 }, [], { balance: { from: "100" } });
+ const page = await walletSearchService.getWalletsPage({ limit: 5, offset: 0 }, [], {
+ balance: { from: "100" },
+ });
expect(page.results.length).toBe(3);
expect(page.results[0].address).toBe(wallet3.getAddress());
@@ -135,7 +137,7 @@ describe("WalletSearchService.getWalletsPage", () => {
});
describe("WalletSearchService.getActiveWalletsPage", () => {
- it("should only get wallets with public keys", () => {
+ it("should only get wallets with public keys", async () => {
const wallet1 = walletRepository.findByPublicKey(Identities.PublicKey.fromPassphrase("secret1"));
const wallet2 = walletRepository.findByPublicKey(Identities.PublicKey.fromPassphrase("secret2"));
const wallet3 = walletRepository.findByAddress(Identities.Address.fromPassphrase("secret3"));
@@ -148,7 +150,7 @@ describe("WalletSearchService.getActiveWalletsPage", () => {
wallet4.setBalance(Utils.BigNumber.make("400"));
wallet5.setBalance(Utils.BigNumber.make("500"));
- const page = walletSearchService.getActiveWalletsPage({ offset: 0, limit: 100 }, [
+ const page = await walletSearchService.getActiveWalletsPage({ offset: 0, limit: 100 }, [
{ property: "balance", direction: "asc" },
]);
diff --git a/__tests__/integration/core-magistrate-api/services/entity-search-service.test.ts b/__tests__/integration/core-magistrate-api/services/entity-search-service.test.ts
index b69f8ee4b0..794d3cbc7a 100644
--- a/__tests__/integration/core-magistrate-api/services/entity-search-service.test.ts
+++ b/__tests__/integration/core-magistrate-api/services/entity-search-service.test.ts
@@ -1,11 +1,11 @@
-import { EntitySearchService, Identifiers as MagistrateApiIdentifiers } from "@arkecosystem/core-magistrate-api/src";
import { Container, Contracts } from "@arkecosystem/core-kernel";
-
-import { setUp } from "./__support__/setup";
-import { setIndexes } from "../__support__/set-indexes";
+import { EntitySearchService, Identifiers as MagistrateApiIdentifiers } from "@arkecosystem/core-magistrate-api/src";
import { Enums } from "@arkecosystem/core-magistrate-crypto";
import { Identities } from "@arkecosystem/crypto";
+import { setIndexes } from "../__support__/set-indexes";
+import { setUp } from "./__support__/setup";
+
const entityId1 = "2a2498072a798318f5e321b312dfc7dcb87f33e10a251baf07ed8f950bd499ec";
const entityAttribute1 = {
type: Enums.EntityType.Business,
@@ -56,7 +56,7 @@ describe("EntitySearchService.getEntity", () => {
});
describe("EntitySearchService.getEntitiesPage", () => {
- it("should return all entities", () => {
+ it("should return all entities", async () => {
const wallet1 = walletRepository.findByPublicKey(Identities.PublicKey.fromPassphrase("wallet1"));
wallet1.setAttribute("entities", { [entityId1]: entityAttribute1 });
setIndexes(walletRepository, wallet1);
@@ -65,14 +65,14 @@ describe("EntitySearchService.getEntitiesPage", () => {
wallet2.setAttribute("entities", { [entityId2]: entityAttribute2 });
setIndexes(walletRepository, wallet2);
- const entitiesPage = entitySearchService.getEntitiesPage({ offset: 0, limit: 100 }, []);
+ const entitiesPage = await entitySearchService.getEntitiesPage({ offset: 0, limit: 100 }, []);
expect(entitiesPage.totalCount).toBe(2);
expect(entitiesPage.results[0].data.name).toBe("entity 1");
expect(entitiesPage.results[1].data.name).toBe("entity 2");
});
- it("should entities that match criteria", () => {
+ it("should entities that match criteria", async () => {
const wallet1 = walletRepository.findByPublicKey(Identities.PublicKey.fromPassphrase("wallet1"));
wallet1.setAttribute("entities", { [entityId1]: entityAttribute1 });
setIndexes(walletRepository, wallet1);
@@ -81,7 +81,7 @@ describe("EntitySearchService.getEntitiesPage", () => {
wallet2.setAttribute("entities", { [entityId2]: entityAttribute2 });
setIndexes(walletRepository, wallet2);
- const entitiesPage = entitySearchService.getEntitiesPage({ offset: 0, limit: 100 }, [], {
+ const entitiesPage = await entitySearchService.getEntitiesPage({ offset: 0, limit: 100 }, [], {
data: { name: "entity 1" },
});
diff --git a/__tests__/integration/core-state/__support__/setup.ts b/__tests__/integration/core-state/__support__/setup.ts
index c448410035..144de24372 100644
--- a/__tests__/integration/core-state/__support__/setup.ts
+++ b/__tests__/integration/core-state/__support__/setup.ts
@@ -7,6 +7,7 @@ EventEmitter.prototype.constructor = Object.prototype.constructor;
const sandbox: Sandbox = new Sandbox();
+const transactionPoolIndexRegistry = null;
const transactionPoolQuery = null;
const transactionPoolService = null;
const peerNetworkMonitor = {
@@ -45,6 +46,9 @@ export const setUp = async (): Promise => {
},
})
.boot(async ({ app }) => {
+ app.bind(Container.Identifiers.TransactionPoolMempoolIndexRegistry).toConstantValue(
+ transactionPoolIndexRegistry,
+ );
app.bind(Container.Identifiers.TransactionPoolQuery).toConstantValue(transactionPoolQuery);
app.bind(Container.Identifiers.TransactionPoolService).toConstantValue(transactionPoolService);
app.bind(Container.Identifiers.PeerNetworkMonitor).toConstantValue(peerNetworkMonitor);
diff --git a/__tests__/integration/core-state/block-state/lock-then-claim.test.ts b/__tests__/integration/core-state/block-state/lock-then-claim.test.ts
index 9c516b71ac..3ad592ee8c 100644
--- a/__tests__/integration/core-state/block-state/lock-then-claim.test.ts
+++ b/__tests__/integration/core-state/block-state/lock-then-claim.test.ts
@@ -33,8 +33,10 @@ test("BlockState handling [lock], [claim] blocks", async () => {
"blockchain",
);
- const transactionRepository = app.get(
+ const transactionRepository = app.getTagged(
Container.Identifiers.DatabaseTransactionRepository,
+ "connection",
+ "default",
);
const lockSecret = Buffer.from("a".repeat(32), "utf-8");
diff --git a/__tests__/integration/core-state/block-state/lock-then-refund.test.ts b/__tests__/integration/core-state/block-state/lock-then-refund.test.ts
index c8a6c001ea..b466af13af 100644
--- a/__tests__/integration/core-state/block-state/lock-then-refund.test.ts
+++ b/__tests__/integration/core-state/block-state/lock-then-refund.test.ts
@@ -33,8 +33,10 @@ test("BlockState handling [lock], [claim] blocks", async () => {
"blockchain",
);
- const transactionRepository = app.get(
+ const transactionRepository = app.getTagged(
Container.Identifiers.DatabaseTransactionRepository,
+ "connection",
+ "default",
);
const lockSecret = Buffer.from("a".repeat(32), "utf-8");
diff --git a/__tests__/unit/core-api/__support__/app.ts b/__tests__/unit/core-api/__support__/app.ts
index 8384fd56b8..5d463e4266 100644
--- a/__tests__/unit/core-api/__support__/app.ts
+++ b/__tests__/unit/core-api/__support__/app.ts
@@ -19,11 +19,12 @@ import {
} from "@packages/core-state/src/wallets/indexers/indexers";
import { Mocks } from "@packages/core-test-framework";
import passphrases from "@packages/core-test-framework/src/internal/passphrases.json";
+import { MempoolIndexRegistry } from "@packages/core-transaction-pool/src/mempool-index-registry";
import { One, Two } from "@packages/core-transactions/src/handlers";
import { TransactionHandlerProvider } from "@packages/core-transactions/src/handlers/handler-provider";
import { TransactionHandlerRegistry } from "@packages/core-transactions/src/handlers/handler-registry";
-import { Identities, Utils } from "@packages/crypto";
import { ServiceProvider } from "@packages/core-transactions/src/service-provider";
+import { Identities, Utils } from "@packages/crypto";
export type PaginatedResponse = {
totalCount: number;
@@ -84,6 +85,11 @@ export const initApp = (): Application => {
app.bind(Container.Identifiers.EventDispatcherService).toConstantValue({});
+ app.bind(Container.Identifiers.TransactionSecondSignatureVerification).toConstantValue({});
+ app.bind(Container.Identifiers.TransactionMultiSignatureVerification).toConstantValue({});
+
+ app.bind(Container.Identifiers.TransactionPoolMempoolIndexRegistry).to(MempoolIndexRegistry).inSingletonScope();
+
app.bind(Identifiers.TransactionHandler).to(One.TransferTransactionHandler);
app.bind(Identifiers.TransactionHandler).to(Two.TransferTransactionHandler);
app.bind(Identifiers.TransactionHandler).to(One.SecondSignatureRegistrationTransactionHandler);
diff --git a/__tests__/unit/core-api/controllers/delegates.test.ts b/__tests__/unit/core-api/controllers/delegates.test.ts
index fca2e28c13..c72b1023eb 100644
--- a/__tests__/unit/core-api/controllers/delegates.test.ts
+++ b/__tests__/unit/core-api/controllers/delegates.test.ts
@@ -97,16 +97,16 @@ const voterWalletResource: Resources.WalletResource = {
describe("DelegatesController", () => {
describe("Index", () => {
- it("should get criteria from query and return delegates page from DelegateSearchService", () => {
+ it("should get criteria from query and return delegates page from DelegateSearchService", async () => {
const delegatesPage: Contracts.Search.ResultsPage = {
results: [delegateResource],
totalCount: 1,
meta: { totalCountIsEstimate: false },
};
- delegateSearchService.getDelegatesPage.mockReturnValueOnce(delegatesPage);
+ delegateSearchService.getDelegatesPage.mockResolvedValueOnce(delegatesPage);
const delegatesController = container.resolve(DelegatesController);
- const result = delegatesController.index({
+ const result = await delegatesController.index({
query: {
page: 1,
limit: 100,
@@ -174,7 +174,7 @@ describe("DelegatesController", () => {
});
describe("Voters", () => {
- it("should get delegate id from pathname and criteria from query and return voter wallets from WalletSearchService", () => {
+ it("should get delegate id from pathname and criteria from query and return voter wallets from WalletSearchService", async () => {
walletSearchService.getWallet.mockReturnValueOnce(delegateWalletResource);
delegateSearchService.getDelegate.mockReturnValueOnce(delegateResource);
@@ -183,10 +183,10 @@ describe("DelegatesController", () => {
totalCount: 1,
meta: { totalCountIsEstimate: false },
};
- walletSearchService.getActiveWalletsPage.mockReturnValueOnce(voterWalletsPage);
+ walletSearchService.getActiveWalletsPage.mockResolvedValueOnce(voterWalletsPage);
const delegatesController = container.resolve(DelegatesController);
- const result = delegatesController.voters({
+ const result = await delegatesController.voters({
params: {
id: "biz_classic",
},
@@ -211,11 +211,11 @@ describe("DelegatesController", () => {
expect(result).toBe(voterWalletsPage);
});
- it("should return 404 when wallet wasn't found", () => {
+ it("should return 404 when wallet wasn't found", async () => {
walletSearchService.getWallet.mockReturnValueOnce(undefined);
const delegatesController = container.resolve(DelegatesController);
- const result = delegatesController.voters({
+ const result = await delegatesController.voters({
params: {
id: "non-existing-wallet-id",
},
@@ -231,12 +231,12 @@ describe("DelegatesController", () => {
expect(result).toBeInstanceOf(Boom);
});
- it("should return 404 when wallet isn't delegate", () => {
+ it("should return 404 when wallet isn't delegate", async () => {
walletSearchService.getWallet.mockReturnValueOnce(voterWalletResource);
delegateSearchService.getDelegate.mockReturnValueOnce(undefined);
const delegatesController = container.resolve(DelegatesController);
- const result = delegatesController.voters({
+ const result = await delegatesController.voters({
params: {
id: "ATL9kyo71wjPPXqvGMUD89t5RazmQfQMc6",
},
diff --git a/__tests__/unit/core-api/controllers/locks.test.ts b/__tests__/unit/core-api/controllers/locks.test.ts
index e9805aaf3c..03ea93ef52 100644
--- a/__tests__/unit/core-api/controllers/locks.test.ts
+++ b/__tests__/unit/core-api/controllers/locks.test.ts
@@ -57,16 +57,16 @@ const lockResource1: Resources.LockResource = {
describe("LocksController", () => {
describe("Index", () => {
- it("should get criteria from query and return locks page from LockSearchService", () => {
+ it("should get criteria from query and return locks page from LockSearchService", async () => {
const locksPage: Contracts.Search.ResultsPage = {
results: [lockResource1],
totalCount: 1,
meta: { totalCountIsEstimate: false },
};
- lockSearchService.getLocksPage.mockReturnValueOnce(locksPage);
+ lockSearchService.getLocksPage.mockResolvedValueOnce(locksPage);
const locksController = container.resolve(LocksController);
- const result = locksController.index({
+ const result = await locksController.index({
query: {
page: 1,
limit: 100,
diff --git a/__tests__/unit/core-api/controllers/transactions.test.ts b/__tests__/unit/core-api/controllers/transactions.test.ts
index 28fdb9b533..62c0950e9a 100644
--- a/__tests__/unit/core-api/controllers/transactions.test.ts
+++ b/__tests__/unit/core-api/controllers/transactions.test.ts
@@ -1,8 +1,8 @@
import "jest-extended";
-import { Contracts } from "@packages/core-kernel";
import Hapi from "@hapi/hapi";
import { TransactionsController } from "@packages/core-api/src/controllers/transactions";
+import { Contracts } from "@packages/core-kernel";
import { Application, Utils } from "@packages/core-kernel";
import { Identifiers } from "@packages/core-kernel/src/ioc";
import { Transactions as MagistrateTransactions } from "@packages/core-magistrate-crypto";
@@ -11,9 +11,9 @@ import { Generators } from "@packages/core-test-framework/src";
import passphrases from "@packages/core-test-framework/src/internal/passphrases.json";
import { TransactionHandlerRegistry } from "@packages/core-transactions/src/handlers/handler-registry";
import { Identities, Interfaces, Managers, Transactions } from "@packages/crypto";
+import { BuilderFactory } from "@packages/crypto/dist/transactions";
import { TransactionType } from "@packages/crypto/src/enums";
import { configManager } from "@packages/crypto/src/managers";
-import { BuilderFactory } from "@packages/crypto/src/transactions";
import { initApp, ItemResponse, PaginatedResponse } from "../__support__";
diff --git a/__tests__/unit/core-api/controllers/votes.test.ts b/__tests__/unit/core-api/controllers/votes.test.ts
index 1909f71101..5b01938a12 100644
--- a/__tests__/unit/core-api/controllers/votes.test.ts
+++ b/__tests__/unit/core-api/controllers/votes.test.ts
@@ -9,7 +9,7 @@ import { Mocks } from "@packages/core-test-framework";
import passphrases from "@packages/core-test-framework/src/internal/passphrases.json";
import { TransactionHandlerRegistry } from "@packages/core-transactions/src/handlers/handler-registry";
import { Identities, Interfaces, Transactions } from "@packages/crypto";
-import { BuilderFactory } from "@packages/crypto/src/transactions";
+import { BuilderFactory } from "@packages/crypto/dist/transactions";
import { initApp, ItemResponse, PaginatedResponse } from "../__support__";
diff --git a/__tests__/unit/core-api/controllers/wallets.test.ts b/__tests__/unit/core-api/controllers/wallets.test.ts
index a47c4d2ad2..2c54e14a60 100644
--- a/__tests__/unit/core-api/controllers/wallets.test.ts
+++ b/__tests__/unit/core-api/controllers/wallets.test.ts
@@ -83,16 +83,16 @@ const wallet1LockResource1: Resources.LockResource = {
describe("WalletsController", () => {
describe("Index", () => {
- it("should get criteria from query and return wallets page from WalletSearchService", () => {
+ it("should get criteria from query and return wallets page from WalletSearchService", async () => {
const walletsPage: Contracts.Search.ResultsPage = {
results: [walletResource1],
totalCount: 1,
meta: { totalCountIsEstimate: false },
};
- walletSearchService.getWalletsPage.mockReturnValueOnce(walletsPage);
+ walletSearchService.getWalletsPage.mockResolvedValueOnce(walletsPage);
const walletsController = container.resolve(WalletsController);
- const result = walletsController.index({
+ const result = await walletsController.index({
query: {
page: 1,
limit: 100,
@@ -114,16 +114,16 @@ describe("WalletsController", () => {
describe("Top", () => {
// it is exact duplicate of WalletsController.index
- it("should get criteria from query and return wallets page from WalletSearchService", () => {
+ it("should get criteria from query and return wallets page from WalletSearchService", async () => {
const walletsPage: Contracts.Search.ResultsPage = {
results: [walletResource1],
totalCount: 1,
meta: { totalCountIsEstimate: false },
};
- walletSearchService.getWalletsPage.mockReturnValueOnce(walletsPage);
+ walletSearchService.getWalletsPage.mockResolvedValueOnce(walletsPage);
const walletsController = container.resolve(WalletsController);
- const result = walletsController.top({
+ const result = await walletsController.top({
query: {
page: 1,
limit: 100,
@@ -173,7 +173,7 @@ describe("WalletsController", () => {
});
describe("Locks", () => {
- it("should get wallet id from pathname and criteria from query and return locks page from LockSearchService", () => {
+ it("should get wallet id from pathname and criteria from query and return locks page from LockSearchService", async () => {
walletSearchService.getWallet.mockReturnValueOnce(walletResource1);
const locksPage: Contracts.Search.ResultsPage = {
@@ -181,10 +181,10 @@ describe("WalletsController", () => {
totalCount: 1,
meta: { totalCountIsEstimate: false },
};
- lockSearchService.getWalletLocksPage.mockReturnValueOnce(locksPage);
+ lockSearchService.getWalletLocksPage.mockResolvedValueOnce(locksPage);
const walletsController = container.resolve(WalletsController);
- const result = walletsController.locks({
+ const result = await walletsController.locks({
params: {
id: walletResource1.publicKey,
},
@@ -208,11 +208,11 @@ describe("WalletsController", () => {
expect(result).toBe(locksPage);
});
- it("should return 404 when wallet wasn't found", () => {
+ it("should return 404 when wallet wasn't found", async () => {
walletSearchService.getWallet.mockReturnValueOnce(undefined);
const walletsController = container.resolve(WalletsController);
- const result = walletsController.locks({
+ const result = await walletsController.locks({
params: {
id: "non-existing-wallet-id",
},
diff --git a/__tests__/unit/core-api/plugins/log.test.ts b/__tests__/unit/core-api/plugins/log.test.ts
new file mode 100644
index 0000000000..6eec6c3fa2
--- /dev/null
+++ b/__tests__/unit/core-api/plugins/log.test.ts
@@ -0,0 +1,59 @@
+import { log } from "@packages/core-api/src/plugins/log";
+
+const logger = {
+ debug: jest.fn(),
+};
+
+const server = {
+ ext: jest.fn(),
+ app: {
+ app: {
+ get: jest.fn().mockReturnValue(logger),
+ },
+ },
+};
+
+beforeEach(() => {
+ jest.clearAllMocks();
+});
+
+describe("Log", () => {
+ it("should register extension when enabled", () => {
+ log.register(server, { enabled: true, trustProxy: false });
+
+ expect(server.ext).toBeCalled();
+ });
+
+ it("should not register extension when disabled", () => {
+ log.register(server, { enabled: false, trustProxy: false });
+
+ expect(server.ext).not.toBeCalled();
+ });
+
+ it("should log request and continue", () => {
+ const request = {
+ path: "api/method",
+ url: { search: "?param=value" },
+ info: {
+ remoteAddress: "127.0.0.1",
+ },
+ };
+
+ const h = {
+ continue: Symbol,
+ };
+
+ log.register(server, { enabled: true, trustProxy: false });
+
+ expect(server.ext).toBeCalled();
+
+ const onRequest = server.ext.mock.calls[0][1];
+
+ const ret = onRequest(request, h);
+
+ expect(ret).toBe(h.continue);
+ expect(logger.debug).toHaveBeenCalledWith(
+ `API request on: "${request.path}" from: "${request.info.remoteAddress}" with query: "${request.url.search}"`,
+ );
+ });
+});
diff --git a/__tests__/unit/core-api/plugins/semaphore.test.ts b/__tests__/unit/core-api/plugins/semaphore.test.ts
new file mode 100644
index 0000000000..f7e83003a8
--- /dev/null
+++ b/__tests__/unit/core-api/plugins/semaphore.test.ts
@@ -0,0 +1,392 @@
+import "jest-extended";
+
+import { blockQueryLevelOptions, blockSortingSchema } from "@packages/core-api/src/resources-new";
+import * as Schemas from "@packages/core-api/src/schemas.ts";
+import { Application } from "@packages/core-kernel";
+import Joi from "joi";
+
+import { initApp, initServer } from "../__support__";
+
+let app: Application;
+
+beforeEach(() => {
+ app = initApp();
+});
+
+describe("Semaphore", () => {
+ let defaults: any;
+ let customResponse: any;
+ let customRoute: any;
+ let injectOptions: any;
+
+ let resolve: () => void;
+ let promise: Promise;
+
+ beforeEach(() => {
+ defaults = {
+ plugins: {
+ pagination: {
+ limit: 100,
+ },
+ socketTimeout: 5000,
+ semaphore: {
+ enabled: true,
+ database: {
+ levelOne: {
+ concurrency: 2,
+ queueLimit: 0,
+ maxOffset: 10_000,
+ },
+ levelTwo: {
+ concurrency: 1,
+ queueLimit: 0,
+ },
+ },
+ memory: {
+ levelOne: {
+ concurrency: 2,
+ queueLimit: 0,
+ maxOffset: 100,
+ },
+ levelTwo: {
+ concurrency: 3,
+ queueLimit: 0,
+ },
+ },
+ },
+ },
+ };
+
+ customResponse = {
+ results: [],
+ totalCount: 0,
+ meta: { totalCountIsEstimate: false },
+ };
+
+ promise = new Promise((res) => {
+ resolve = res;
+ });
+
+ customRoute = {
+ method: "GET",
+ path: "/test",
+ handler: jest.fn().mockImplementation(async () => {
+ await promise;
+ return customResponse;
+ }),
+ options: {
+ validate: {
+ query: Joi.object({
+ ...Schemas.blockCriteriaSchemas,
+ orderBy: Schemas.blocksOrderBy,
+ transform: Joi.bool().default(true),
+ })
+ .concat(blockSortingSchema)
+ .concat(Schemas.pagination),
+ },
+ plugins: {
+ pagination: {
+ enabled: true,
+ },
+ semaphore: {
+ enabled: true,
+ queryLevelOptions: blockQueryLevelOptions,
+ },
+ },
+ },
+ };
+
+ injectOptions = {
+ method: "GET",
+ url: "/test",
+ };
+ });
+
+ it("should not use semaphore if semaphore plugin is not on route", async () => {
+ customRoute.options.plugins = {};
+
+ const server = await initServer(app, defaults, customRoute);
+
+ const responses: Promise[] = [server.inject(injectOptions), server.inject(injectOptions)];
+
+ resolve();
+
+ for (let i = 0; i < 2; i++) {
+ expect((await responses[i]).statusCode).toBe(200);
+ }
+ expect(customRoute.handler).toHaveBeenCalledTimes(2);
+ });
+
+ it("should not use semaphore if not enabled", async () => {
+ customRoute.options.plugins.semaphore = {
+ ...customRoute.options.plugins.semaphore,
+ enabled: false,
+ };
+
+ const server = await initServer(app, defaults, customRoute);
+
+ const responses: Promise[] = [server.inject(injectOptions), server.inject(injectOptions)];
+
+ resolve();
+
+ for (let i = 0; i < 2; i++) {
+ expect((await responses[i]).statusCode).toBe(200);
+ }
+ expect(customRoute.handler).toHaveBeenCalledTimes(2);
+ });
+
+ it("should not use semaphore if levelTwoFields fields are not in semaphore options", async () => {
+ customRoute.options.plugins.semaphore = {};
+
+ const server = await initServer(app, defaults, customRoute);
+
+ const responses: Promise[] = [server.inject(injectOptions), server.inject(injectOptions)];
+
+ resolve();
+
+ for (let i = 0; i < 2; i++) {
+ expect((await responses[i]).statusCode).toBe(200);
+ }
+ expect(customRoute.handler).toHaveBeenCalledTimes(2);
+ });
+
+ const levelOneQueries = [
+ "/test", // Default
+ "/test?timestamp=1", // Indexed field value
+ "/test?timestamp.from=1", // Indexed field from
+ "/test?timestamp.to=1", // Indexed field to
+ "/test?version=0", // Indexed field value
+ "/test?version=0,1,2", // Indexed field from
+ "/test?orderBy=version:asc", // Indexed field asc = true
+ "/test?orderBy=version:desc", // Indexed field desc = true
+ "/test?orderBy=timestamp:asc,version:asc", // allowSecondOrderBy = true
+ "/test?orderBy=payloadHash:asc&height=2", // Uses diverse index direct value
+ ];
+
+ const levelTwoOffsetQueries = [
+ "/test?page=101&limit=100", // Offset is > 10_000
+ "/test?offset=10001", // Offset is > 10_000
+ ];
+
+ const levelTwoQueries = [
+ "/test?payloadLength=1", // Non indexed field value
+ "/test?payloadLength.from=1", // Non indexed field from
+ "/test?payloadLength.to=1", // Non indexed field to
+ "/test?payloadHash=abc", // Non indexed field to
+ "/test?payloadHash=abc,def", // Non indexed field to
+ "/test?orderBy=id:asc", // Indexed field asc = false
+ "/test?orderBy=id:desc", // Indexed field desc = false
+ "/test?orderBy=payloadHash:asc", // Non indexed field orderBy asc
+ "/test?orderBy=payloadHash:desc", // Non indexed field orderBy desc,
+ "/test?orderBy=version:asc,timestamp:asc", // allowSecondOrderBy = false
+ "/test?orderBy=payloadHash:asc&height.from=2", // Uses diverse index indirect value
+ "/test?orderBy=payloadHash:asc&version=0", // Uses non-diverse index
+ ];
+
+ it.each(levelOneQueries)("should use level 1 semaphore", async (url) => {
+ const server = await initServer(app, defaults, customRoute);
+
+ const customInjectOptions = {
+ ...injectOptions,
+ url: url,
+ };
+
+ const responses: Promise[] = [
+ server.inject(customInjectOptions),
+ server.inject(customInjectOptions),
+ server.inject(customInjectOptions),
+ ];
+
+ resolve();
+
+ for (let i = 0; i < 2; i++) {
+ expect((await responses[i]).statusCode).toBe(200);
+ }
+ expect((await responses[2]).statusCode).toBe(429);
+ expect(customRoute.handler).toHaveBeenCalledTimes(2);
+ });
+
+ it.each([...levelTwoOffsetQueries, ...levelTwoQueries])("should use level 2 semaphore", async (url) => {
+ const server = await initServer(app, defaults, customRoute);
+
+ const customInjectOptions = {
+ ...injectOptions,
+ url: url,
+ };
+
+ const responses: Promise[] = [server.inject(customInjectOptions), server.inject(customInjectOptions)];
+
+ resolve();
+
+ expect((await responses[0]).statusCode).toBe(200);
+ expect((await responses[1]).statusCode).toBe(429);
+ expect(customRoute.handler).toHaveBeenCalledTimes(1);
+ });
+
+ // Skip offsets
+ it.each(levelTwoQueries)("should use level 1 semaphore when queryLevelSchema is not defined", async (url) => {
+ customRoute = {
+ method: "GET",
+ path: "/test",
+ handler: jest.fn().mockImplementation(async () => {
+ await promise;
+ return customResponse;
+ }),
+ options: {
+ validate: {
+ query: Joi.object({
+ ...Schemas.blockCriteriaSchemas,
+ orderBy: Schemas.blocksOrderBy,
+ transform: Joi.bool().default(true),
+ })
+ .concat(blockSortingSchema)
+ .concat(Schemas.pagination),
+ },
+ plugins: {
+ pagination: {
+ enabled: true,
+ },
+ semaphore: {
+ enabled: true,
+ },
+ },
+ },
+ };
+
+ const server = await initServer(app, defaults, customRoute);
+
+ const customInjectOptions = {
+ ...injectOptions,
+ url: url,
+ };
+
+ const responses: Promise[] = [
+ server.inject(customInjectOptions),
+ server.inject(customInjectOptions),
+ server.inject(customInjectOptions),
+ ];
+
+ resolve();
+
+ for (let i = 0; i < 2; i++) {
+ expect((await responses[i]).statusCode).toBe(200);
+ }
+ expect((await responses[2]).statusCode).toBe(429);
+ expect(customRoute.handler).toHaveBeenCalledTimes(2);
+ });
+
+ it("should use memory semaphore when type = memory", async () => {
+ customRoute = {
+ method: "GET",
+ path: "/test",
+ handler: jest.fn().mockImplementation(async () => {
+ await promise;
+ return customResponse;
+ }),
+ options: {
+ validate: {
+ query: Joi.object({
+ ...Schemas.blockCriteriaSchemas,
+ orderBy: Schemas.blocksOrderBy,
+ transform: Joi.bool().default(true),
+ })
+ .concat(blockSortingSchema)
+ .concat(Schemas.pagination),
+ },
+ plugins: {
+ pagination: {
+ enabled: true,
+ },
+ semaphore: {
+ enabled: true,
+ type: "memory",
+ },
+ },
+ },
+ };
+
+ const server = await initServer(app, defaults, customRoute);
+
+ const customInjectOptions = {
+ ...injectOptions,
+ url: "/test?page=10&limit=11", // Offset is > 100,
+ };
+
+ const responses: Promise[] = [
+ server.inject(customInjectOptions),
+ server.inject(customInjectOptions),
+ server.inject(customInjectOptions),
+ server.inject(customInjectOptions),
+ ];
+
+ resolve();
+
+ expect((await responses[0]).statusCode).toBe(200);
+ expect((await responses[1]).statusCode).toBe(200);
+ expect((await responses[2]).statusCode).toBe(200);
+ expect((await responses[3]).statusCode).toBe(429);
+ expect(customRoute.handler).toHaveBeenCalledTimes(3);
+ });
+
+ it("should should respond with 429 when queue is full", async () => {
+ const server = await initServer(
+ app,
+ (defaults = {
+ plugins: {
+ pagination: {
+ limit: 100,
+ },
+ socketTimeout: 5000,
+ semaphore: {
+ enabled: true,
+ database: {
+ levelOne: {
+ concurrency: 2,
+ queueLimit: 2,
+ maxOffset: 10_000,
+ },
+ levelTwo: {
+ concurrency: 1,
+ queueLimit: 0,
+ },
+ },
+ memory: {
+ levelOne: {
+ concurrency: 2,
+ queueLimit: 2,
+ maxOffset: 100,
+ },
+ levelTwo: {
+ concurrency: 1,
+ queueLimit: 0,
+ },
+ },
+ },
+ },
+ }),
+ customRoute,
+ );
+
+ const customInjectOptions = {
+ method: "GET",
+ url: "/test",
+ };
+
+ const responses: Promise[] = [
+ server.inject(customInjectOptions),
+ server.inject(customInjectOptions),
+ server.inject(customInjectOptions),
+ server.inject(customInjectOptions),
+ server.inject(customInjectOptions),
+ ];
+
+ resolve();
+
+ // 2 x concurrent + 2x queue
+ for (let i = 0; i < 4; i++) {
+ expect((await responses[i]).statusCode).toBe(200);
+ }
+ expect((await responses[4]).statusCode).toBe(429);
+ expect(customRoute.handler).toHaveBeenCalledTimes(2);
+ });
+});
diff --git a/__tests__/unit/core-api/service-provider.test.ts b/__tests__/unit/core-api/service-provider.test.ts
index 38ad03f46c..cbc347b366 100644
--- a/__tests__/unit/core-api/service-provider.test.ts
+++ b/__tests__/unit/core-api/service-provider.test.ts
@@ -188,10 +188,24 @@ describe("ServiceProvider", () => {
expect(result.value.server.https.tls.key).toBeUndefined();
expect(result.value.server.https.tls.cert).toBeUndefined();
+ expect(result.value.plugins.log.enabled).toBeFalse();
+
expect(result.value.plugins.cache.enabled).toBeFalse();
expect(result.value.plugins.cache.stdTTL).toBeNumber();
expect(result.value.plugins.cache.checkperiod).toBeNumber();
+ expect(result.value.plugins.semaphore.enabled).toBeTrue();
+ expect(result.value.plugins.semaphore.database.levelOne.concurrency).toBeNumber();
+ expect(result.value.plugins.semaphore.database.levelOne.queueLimit).toBeNumber();
+ expect(result.value.plugins.semaphore.database.levelOne.maxOffset).toBeNumber();
+ expect(result.value.plugins.semaphore.database.levelTwo.concurrency).toBeNumber();
+ expect(result.value.plugins.semaphore.database.levelTwo.queueLimit).toBeNumber();
+ expect(result.value.plugins.semaphore.memory.levelOne.concurrency).toBeNumber();
+ expect(result.value.plugins.semaphore.memory.levelOne.queueLimit).toBeNumber();
+ expect(result.value.plugins.semaphore.memory.levelOne.maxOffset).toBeNumber();
+ expect(result.value.plugins.semaphore.memory.levelTwo.concurrency).toBeNumber();
+ expect(result.value.plugins.semaphore.memory.levelTwo.queueLimit).toBeNumber();
+
expect(result.value.plugins.rateLimit.enabled).toBeTrue();
expect(result.value.plugins.rateLimit.points).toBeNumber();
expect(result.value.plugins.rateLimit.duration).toBeNumber();
@@ -333,6 +347,30 @@ describe("ServiceProvider", () => {
});
});
+ describe("process.env.CORE_API_LOG", () => {
+ it("should return false if process.env.CORE_API_LOG is undefined", async () => {
+ jest.resetModules();
+ const result = (coreApiServiceProvider.configSchema() as AnySchema).validate(
+ (await import("@packages/core-api/src/defaults")).defaults,
+ );
+
+ expect(result.error).toBeUndefined();
+ expect(result.value.plugins.log.enabled).toEqual(false);
+ });
+
+ it("should return true if process.env.CORE_API_LOG is defined", async () => {
+ process.env.CORE_API_LOG = "true";
+
+ jest.resetModules();
+ const result = (coreApiServiceProvider.configSchema() as AnySchema).validate(
+ (await import("@packages/core-api/src/defaults")).defaults,
+ );
+
+ expect(result.error).toBeUndefined();
+ expect(result.value.plugins.log.enabled).toEqual(true);
+ });
+ });
+
describe("process.env.CORE_API_CACHE", () => {
it("should return false if process.env.CORE_API_CACHE is undefined", async () => {
jest.resetModules();
@@ -357,6 +395,30 @@ describe("ServiceProvider", () => {
});
});
+ describe("process.env.CORE_API_SEMAPHORE_DISABLED", () => {
+ it("should return true if process.env.CORE_API_SEMAPHORE_DISABLED is undefined", async () => {
+ jest.resetModules();
+ const result = (coreApiServiceProvider.configSchema() as AnySchema).validate(
+ (await import("@packages/core-api/src/defaults")).defaults,
+ );
+
+ expect(result.error).toBeUndefined();
+ expect(result.value.plugins.semaphore.enabled).toEqual(true);
+ });
+
+ it("should return false if process.env.CORE_API_SEMAPHORE_DISABLED is defined", async () => {
+ process.env.CORE_API_SEMAPHORE_DISABLED = "true";
+
+ jest.resetModules();
+ const result = (coreApiServiceProvider.configSchema() as AnySchema).validate(
+ (await import("@packages/core-api/src/defaults")).defaults,
+ );
+
+ expect(result.error).toBeUndefined();
+ expect(result.value.plugins.semaphore.enabled).toEqual(false);
+ });
+ });
+
describe("process.env.CORE_API_RATE_LIMIT_DISABLED", () => {
it("should return true if process.env.CORE_API_RATE_LIMIT_DISABLED is undefined", async () => {
jest.resetModules();
@@ -782,6 +844,368 @@ describe("ServiceProvider", () => {
expect(result.error!.message).toEqual('"plugins.cache.checkperiod" is required');
});
+ it("plugins.semaphore is required && is object", async () => {
+ defaults.plugins.semaphore = false;
+ let result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"plugins.semaphore" must be of type object');
+
+ delete defaults.plugins.semaphore;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"plugins.semaphore" is required');
+ });
+
+ it("plugins.semaphore.database is required && is object", async () => {
+ defaults.plugins.semaphore.database = false;
+ let result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"plugins.semaphore.database" must be of type object');
+
+ delete defaults.plugins.semaphore.database;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"plugins.semaphore.database" is required');
+ });
+
+ it("plugins.semaphore.database.levelOne is required && is object", async () => {
+ defaults.plugins.semaphore.database.levelOne = false;
+ let result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"plugins.semaphore.database.levelOne" must be of type object');
+
+ delete defaults.plugins.semaphore.database.levelOne;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"plugins.semaphore.database.levelOne" is required');
+ });
+
+ it("plugins.semaphore.database.levelOne.concurrency is required && is integer && >= 0", async () => {
+ defaults.plugins.semaphore.database.levelOne.concurrency = false;
+ let result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.database.levelOne.concurrency" must be a number',
+ );
+
+ defaults.plugins.semaphore.database.levelOne.concurrency = 1.12;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.database.levelOne.concurrency" must be an integer',
+ );
+
+ defaults.plugins.semaphore.database.levelOne.concurrency = -1;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.database.levelOne.concurrency" must be greater than or equal to 0',
+ );
+
+ delete defaults.plugins.semaphore.database.levelOne.concurrency;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"plugins.semaphore.database.levelOne.concurrency" is required');
+ });
+
+ it("plugins.semaphore.database.levelOne.queueLimit is required && is integer && >= 0", async () => {
+ defaults.plugins.semaphore.database.levelOne.queueLimit = false;
+ let result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.database.levelOne.queueLimit" must be a number',
+ );
+
+ defaults.plugins.semaphore.database.levelOne.queueLimit = 1.12;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.database.levelOne.queueLimit" must be an integer',
+ );
+
+ defaults.plugins.semaphore.database.levelOne.queueLimit = -1;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.database.levelOne.queueLimit" must be greater than or equal to 0',
+ );
+
+ delete defaults.plugins.semaphore.database.levelOne.queueLimit;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"plugins.semaphore.database.levelOne.queueLimit" is required');
+ });
+
+ it("plugins.semaphore.database.levelOne.maxOffset is required && is integer && >= 0", async () => {
+ defaults.plugins.semaphore.database.levelOne.maxOffset = false;
+ let result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.database.levelOne.maxOffset" must be a number',
+ );
+
+ defaults.plugins.semaphore.database.levelOne.maxOffset = 1.12;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.database.levelOne.maxOffset" must be an integer',
+ );
+
+ defaults.plugins.semaphore.database.levelOne.maxOffset = -1;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.database.levelOne.maxOffset" must be greater than or equal to 0',
+ );
+
+ delete defaults.plugins.semaphore.database.levelOne.maxOffset;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"plugins.semaphore.database.levelOne.maxOffset" is required');
+ });
+
+ it("plugins.semaphore.database.levelTwo is required && is object", async () => {
+ defaults.plugins.semaphore.database.levelTwo = false;
+ let result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"plugins.semaphore.database.levelTwo" must be of type object');
+
+ delete defaults.plugins.semaphore.database.levelTwo;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"plugins.semaphore.database.levelTwo" is required');
+ });
+
+ it("plugins.semaphore.database.levelTwo.concurrency is required && is integer && >= 0", async () => {
+ defaults.plugins.semaphore.database.levelTwo.concurrency = false;
+ let result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.database.levelTwo.concurrency" must be a number',
+ );
+
+ defaults.plugins.semaphore.database.levelTwo.concurrency = 1.12;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.database.levelTwo.concurrency" must be an integer',
+ );
+
+ defaults.plugins.semaphore.database.levelTwo.concurrency = -1;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.database.levelTwo.concurrency" must be greater than or equal to 0',
+ );
+
+ delete defaults.plugins.semaphore.database.levelTwo.concurrency;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"plugins.semaphore.database.levelTwo.concurrency" is required');
+ });
+
+ it("plugins.semaphore.database.levelTwo.queueLimit is required && is integer && >= 0", async () => {
+ defaults.plugins.semaphore.database.levelTwo.queueLimit = false;
+ let result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.database.levelTwo.queueLimit" must be a number',
+ );
+
+ defaults.plugins.semaphore.database.levelTwo.queueLimit = 1.12;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.database.levelTwo.queueLimit" must be an integer',
+ );
+
+ defaults.plugins.semaphore.database.levelTwo.queueLimit = -1;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.database.levelTwo.queueLimit" must be greater than or equal to 0',
+ );
+
+ delete defaults.plugins.semaphore.database.levelTwo.queueLimit;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"plugins.semaphore.database.levelTwo.queueLimit" is required');
+ });
+
+ it("plugins.semaphore.memory is required && is object", async () => {
+ defaults.plugins.semaphore.memory = false;
+ let result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"plugins.semaphore.memory" must be of type object');
+
+ delete defaults.plugins.semaphore.memory;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"plugins.semaphore.memory" is required');
+ });
+
+ it("plugins.semaphore.memory.levelOne is required && is object", async () => {
+ defaults.plugins.semaphore.memory.levelOne = false;
+ let result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"plugins.semaphore.memory.levelOne" must be of type object');
+
+ delete defaults.plugins.semaphore.memory.levelOne;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"plugins.semaphore.memory.levelOne" is required');
+ });
+
+ it("plugins.semaphore.memory.levelOne.concurrency is required && is integer && >= 0", async () => {
+ defaults.plugins.semaphore.memory.levelOne.concurrency = false;
+ let result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.memory.levelOne.concurrency" must be a number',
+ );
+
+ defaults.plugins.semaphore.memory.levelOne.concurrency = 1.12;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.memory.levelOne.concurrency" must be an integer',
+ );
+
+ defaults.plugins.semaphore.memory.levelOne.concurrency = -1;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.memory.levelOne.concurrency" must be greater than or equal to 0',
+ );
+
+ delete defaults.plugins.semaphore.memory.levelOne.concurrency;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"plugins.semaphore.memory.levelOne.concurrency" is required');
+ });
+
+ it("plugins.semaphore.memory.levelOne.maxOffset is required && is integer && >= 0", async () => {
+ defaults.plugins.semaphore.memory.levelOne.maxOffset = false;
+ let result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"plugins.semaphore.memory.levelOne.maxOffset" must be a number');
+
+ defaults.plugins.semaphore.memory.levelOne.maxOffset = 1.12;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.memory.levelOne.maxOffset" must be an integer',
+ );
+
+ defaults.plugins.semaphore.memory.levelOne.maxOffset = -1;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.memory.levelOne.maxOffset" must be greater than or equal to 0',
+ );
+
+ delete defaults.plugins.semaphore.memory.levelOne.maxOffset;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"plugins.semaphore.memory.levelOne.maxOffset" is required');
+ });
+
+ it("plugins.semaphore.memory.levelOne.queueLimit is required && is integer && >= 0", async () => {
+ defaults.plugins.semaphore.memory.levelOne.queueLimit = false;
+ let result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.memory.levelOne.queueLimit" must be a number',
+ );
+
+ defaults.plugins.semaphore.memory.levelOne.queueLimit = 1.12;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.memory.levelOne.queueLimit" must be an integer',
+ );
+
+ defaults.plugins.semaphore.memory.levelOne.queueLimit = -1;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.memory.levelOne.queueLimit" must be greater than or equal to 0',
+ );
+
+ delete defaults.plugins.semaphore.memory.levelOne.queueLimit;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"plugins.semaphore.memory.levelOne.queueLimit" is required');
+ });
+
+ it("plugins.semaphore.memory.levelTwo is required && is object", async () => {
+ defaults.plugins.semaphore.memory.levelTwo = false;
+ let result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"plugins.semaphore.memory.levelTwo" must be of type object');
+
+ delete defaults.plugins.semaphore.memory.levelTwo;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"plugins.semaphore.memory.levelTwo" is required');
+ });
+
+ it("plugins.semaphore.memory.levelTwo.concurrency is required && is integer && >= 0", async () => {
+ defaults.plugins.semaphore.memory.levelTwo.concurrency = false;
+ let result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.memory.levelTwo.concurrency" must be a number',
+ );
+
+ defaults.plugins.semaphore.memory.levelTwo.concurrency = 1.12;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.memory.levelTwo.concurrency" must be an integer',
+ );
+
+ defaults.plugins.semaphore.memory.levelTwo.concurrency = -1;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.memory.levelTwo.concurrency" must be greater than or equal to 0',
+ );
+
+ delete defaults.plugins.semaphore.memory.levelTwo.concurrency;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"plugins.semaphore.memory.levelTwo.concurrency" is required');
+ });
+
+ it("plugins.semaphore.memory.levelTwo.queueLimit is required && is integer && >= 0", async () => {
+ defaults.plugins.semaphore.memory.levelTwo.queueLimit = false;
+ let result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.memory.levelTwo.queueLimit" must be a number',
+ );
+
+ defaults.plugins.semaphore.memory.levelTwo.queueLimit = 1.12;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.memory.levelTwo.queueLimit" must be an integer',
+ );
+
+ defaults.plugins.semaphore.memory.levelTwo.queueLimit = -1;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual(
+ '"plugins.semaphore.memory.levelTwo.queueLimit" must be greater than or equal to 0',
+ );
+
+ delete defaults.plugins.semaphore.memory.levelTwo.queueLimit;
+ result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"plugins.semaphore.memory.levelTwo.queueLimit" is required');
+ });
+
it("plugins.rateLimit is required && is object", async () => {
defaults.plugins.rateLimit = false;
let result = (coreApiServiceProvider.configSchema() as AnySchema).validate(defaults);
diff --git a/__tests__/unit/core-api/services/delegate-search-service.test.ts b/__tests__/unit/core-api/services/delegate-search-service.test.ts
index 8faaebb1ff..2c703d8bef 100644
--- a/__tests__/unit/core-api/services/delegate-search-service.test.ts
+++ b/__tests__/unit/core-api/services/delegate-search-service.test.ts
@@ -13,8 +13,17 @@ const standardCriteriaService = {
testStandardCriterias: jest.fn(),
};
+const stateStore = {
+ getLastBlock: jest.fn().mockReturnValue({
+ data: {
+ height: 100,
+ },
+ }),
+};
+
const container = new Container.Container();
container.bind(Container.Identifiers.WalletRepository).toConstantValue(walletRepository);
+container.bind(Container.Identifiers.StateStore).toConstantValue(stateStore);
container.bind(Container.Identifiers.StandardCriteriaService).toConstantValue(standardCriteriaService);
container.bind(Container.Identifiers.PaginationService).to(Services.Search.PaginationService);
@@ -22,7 +31,11 @@ const delegateSearchService = container.resolve(DelegateSearchService);
let attributeMap;
+let spyOnCalculateApproval;
+
beforeEach(() => {
+ spyOnCalculateApproval = jest.spyOn(AppUtils.delegateCalculator, "calculateApproval");
+
const attributeSet = new Services.Attributes.AttributeSet();
attributeSet.set("delegate");
attributeSet.set("delegate.approval");
@@ -63,6 +76,8 @@ describe("DelegateSearchService", () => {
expect(delegateSearchService.getDelegate("ANBkoGqWeTSiaEVgVzSKZd3jS7UWzv9PSo")).toEqual(
Delegates.delegateResource,
);
+
+ expect(spyOnCalculateApproval).toHaveBeenCalledWith(delegate, 100);
});
it("should return delegate by wallet address with produced blocks", () => {
@@ -88,6 +103,7 @@ describe("DelegateSearchService", () => {
expect(delegateSearchService.getDelegate("ANBkoGqWeTSiaEVgVzSKZd3jS7UWzv9PSo")).toEqual(
Delegates.delegateResourceWithLastBlock,
);
+ expect(spyOnCalculateApproval).toHaveBeenCalledWith(delegate, 100);
});
it("should return undefined if walled is not delegate", () => {
@@ -101,7 +117,7 @@ describe("DelegateSearchService", () => {
});
describe("getDelegatesPage", () => {
- it("should return results with delegate", () => {
+ it("should return results with delegate", async () => {
const delegate = new Wallets.Wallet("ANBkoGqWeTSiaEVgVzSKZd3jS7UWzv9PSo", attributeMap);
delegate.setPublicKey("03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37");
@@ -117,7 +133,7 @@ describe("DelegateSearchService", () => {
walletRepository.allByUsername.mockReturnValue([delegate]);
standardCriteriaService.testStandardCriterias.mockReturnValue(true);
- const result = delegateSearchService.getDelegatesPage(
+ const result = await delegateSearchService.getDelegatesPage(
{
offset: 0,
limit: 100,
@@ -130,9 +146,10 @@ describe("DelegateSearchService", () => {
expect(walletRepository.allByUsername).toHaveBeenCalled();
expect(standardCriteriaService.testStandardCriterias).toHaveBeenCalled();
+ expect(spyOnCalculateApproval).toHaveBeenCalledWith(delegate, 100);
});
- it("should return empty array if all tested criterias are false", () => {
+ it("should return empty array if all tested criterias are false", async () => {
const delegate = new Wallets.Wallet("ANBkoGqWeTSiaEVgVzSKZd3jS7UWzv9PSo", attributeMap);
delegate.setPublicKey("03287bfebba4c7881a0509717e71b34b63f31e40021c321f89ae04f84be6d6ac37");
@@ -148,7 +165,7 @@ describe("DelegateSearchService", () => {
walletRepository.allByUsername.mockReturnValue([delegate]);
standardCriteriaService.testStandardCriterias.mockReturnValue(false);
- const result = delegateSearchService.getDelegatesPage(
+ const result = await delegateSearchService.getDelegatesPage(
{
offset: 0,
limit: 100,
diff --git a/__tests__/unit/core-api/services/lock-search-service.test.ts b/__tests__/unit/core-api/services/lock-search-service.test.ts
index ad03ed24fd..784603171e 100644
--- a/__tests__/unit/core-api/services/lock-search-service.test.ts
+++ b/__tests__/unit/core-api/services/lock-search-service.test.ts
@@ -94,11 +94,11 @@ describe("LockSearchService", () => {
});
});
- it("should check all locks in locks index", () => {
+ it("should check all locks in locks index", async () => {
standardCriteriaService.testStandardCriterias.mockReturnValue(true);
AppUtils.expirationCalculator.calculateLockExpirationStatus = jest.fn().mockReturnValue(false);
- const result = lockSearchService.getLocksPage(
+ const result = await lockSearchService.getLocksPage(
{
offset: 0,
limit: 100,
@@ -110,11 +110,11 @@ describe("LockSearchService", () => {
expect(result.results).toEqual([Locks.lockResource]);
});
- it("should return empty array if all tested criterias are false", () => {
+ it("should return empty array if all tested criterias are false", async () => {
standardCriteriaService.testStandardCriterias.mockReturnValue(false);
AppUtils.expirationCalculator.calculateLockExpirationStatus = jest.fn().mockReturnValue(false);
- const result = lockSearchService.getLocksPage(
+ const result = await lockSearchService.getLocksPage(
{
offset: 0,
limit: 100,
@@ -140,11 +140,11 @@ describe("LockSearchService", () => {
walletRepository.findByAddress.mockReturnValue(walletWithLock);
});
- it("should check and return all wallet locks", () => {
+ it("should check and return all wallet locks", async () => {
standardCriteriaService.testStandardCriterias.mockReturnValue(true);
AppUtils.expirationCalculator.calculateLockExpirationStatus = jest.fn().mockReturnValue(false);
- const result = lockSearchService.getWalletLocksPage(
+ const result = await lockSearchService.getWalletLocksPage(
{
offset: 0,
limit: 100,
@@ -157,11 +157,11 @@ describe("LockSearchService", () => {
expect(result.results).toEqual([Locks.lockResource]);
});
- it("should return empty array if all tested criterias are false", () => {
+ it("should return empty array if all tested criterias are false", async () => {
standardCriteriaService.testStandardCriterias.mockReturnValue(false);
AppUtils.expirationCalculator.calculateLockExpirationStatus = jest.fn().mockReturnValue(false);
- const result = lockSearchService.getWalletLocksPage(
+ const result = await lockSearchService.getWalletLocksPage(
{
offset: 0,
limit: 100,
diff --git a/__tests__/unit/core-api/services/wallet-search-service.test.ts b/__tests__/unit/core-api/services/wallet-search-service.test.ts
index 4864f87f00..581f8e5188 100644
--- a/__tests__/unit/core-api/services/wallet-search-service.test.ts
+++ b/__tests__/unit/core-api/services/wallet-search-service.test.ts
@@ -135,11 +135,11 @@ describe("WalletSearchService", () => {
});
describe("getWalletsPage", () => {
- it("should return wallets page with wallet on positive criteria tests", () => {
+ it("should return wallets page with wallet on positive criteria tests", async () => {
walletRepository.allByAddress.mockReturnValue([wallet]);
standardCriteriaService.testStandardCriterias.mockReturnValue(true);
- const result = walletSearchService.getWalletsPage(
+ const result = await walletSearchService.getWalletsPage(
{
offset: 0,
limit: 100,
@@ -151,11 +151,11 @@ describe("WalletSearchService", () => {
expect(result.results).toEqual([walletResource]);
});
- it("should return wallets page with empty array on negative criteria tests", () => {
+ it("should return wallets page with empty array on negative criteria tests", async () => {
walletRepository.allByAddress.mockReturnValue([wallet]);
standardCriteriaService.testStandardCriterias.mockReturnValue(false);
- const result = walletSearchService.getWalletsPage(
+ const result = await walletSearchService.getWalletsPage(
{
offset: 0,
limit: 100,
@@ -169,11 +169,11 @@ describe("WalletSearchService", () => {
});
describe("getActiveWalletsPage", () => {
- it("should return wallets page with wallet on positive criteria tests", () => {
+ it("should return wallets page with wallet on positive criteria tests", async () => {
walletRepository.allByPublicKey.mockReturnValue([wallet]);
standardCriteriaService.testStandardCriterias.mockReturnValue(true);
- const result = walletSearchService.getActiveWalletsPage(
+ const result = await walletSearchService.getActiveWalletsPage(
{
offset: 0,
limit: 100,
@@ -185,11 +185,11 @@ describe("WalletSearchService", () => {
expect(result.results).toEqual([walletResource]);
});
- it("should return wallets page with empty array on negative criteria tests", () => {
+ it("should return wallets page with empty array on negative criteria tests", async () => {
walletRepository.allByPublicKey.mockReturnValue([wallet]);
standardCriteriaService.testStandardCriterias.mockReturnValue(false);
- const result = walletSearchService.getActiveWalletsPage(
+ const result = await walletSearchService.getActiveWalletsPage(
{
offset: 0,
limit: 100,
diff --git a/__tests__/unit/core-blockchain/blockchain.test.ts b/__tests__/unit/core-blockchain/blockchain.test.ts
index dbc7d97331..7487682044 100644
--- a/__tests__/unit/core-blockchain/blockchain.test.ts
+++ b/__tests__/unit/core-blockchain/blockchain.test.ts
@@ -18,7 +18,9 @@ EventEmitter.prototype.constructor = Object.prototype.constructor;
describe("Blockchain", () => {
let sandbox: Sandbox;
- const configuration: any = {};
+ const configuration: any = {
+ getRequired: jest.fn(),
+ };
const logService: any = {};
const stateStore: any = {};
const databaseService: any = {};
@@ -59,11 +61,11 @@ describe("Blockchain", () => {
.get(Container.Identifiers.TriggerService)
.bind("getActiveDelegates", new GetActiveDelegatesAction(sandbox.app));
- sandbox.app
- .bind(Identifiers.QueueFactory)
- .toFactory((context: interfaces.Context) => async (name?: string): Promise =>
- sandbox.app.resolve(MemoryQueue).make(),
- );
+ sandbox.app.bind(Identifiers.QueueFactory).toFactory(
+ (context: interfaces.Context) =>
+ async (name?: string): Promise =>
+ sandbox.app.resolve(MemoryQueue).make(),
+ );
Managers.configManager.setFromPreset("testnet");
});
@@ -278,14 +280,18 @@ describe("Blockchain", () => {
it("should set wakeUpTimeout on stateStore", () => {
const blockchain = sandbox.app.resolve(Blockchain);
+ configuration.getRequired.mockReturnValueOnce(false);
+
blockchain.setWakeUp();
expect(stateStore.setWakeUpTimeout).toHaveBeenCalled();
});
- it("should dispatch WAKEUP when wake up function is called", () => {
+ it("should dispatch WAKEUP when wake up function is called after 60 second", () => {
const blockchain = sandbox.app.resolve(Blockchain);
const spyDispatch = jest.spyOn(blockchain, "dispatch");
+ configuration.getRequired.mockReturnValueOnce(false);
+
blockchain.setWakeUp();
expect(spyDispatch).toBeCalledTimes(0);
@@ -297,6 +303,24 @@ describe("Blockchain", () => {
expect(spyDispatch).toBeCalledTimes(1);
expect(spyDispatch).toBeCalledWith("WAKEUP");
});
+
+ it("should dispatch WAKEUP when wake up function is called after 8 second, when fastSync === true", () => {
+ const blockchain = sandbox.app.resolve(Blockchain);
+ const spyDispatch = jest.spyOn(blockchain, "dispatch");
+
+ configuration.getRequired.mockReturnValueOnce(true);
+
+ blockchain.setWakeUp();
+ expect(spyDispatch).toBeCalledTimes(0);
+
+ expect(stateStore.setWakeUpTimeout).toHaveBeenCalledWith(expect.toBeFunction(), 8000);
+
+ // Call given callback function
+ stateStore.setWakeUpTimeout.mock.calls[0][0]();
+
+ expect(spyDispatch).toBeCalledTimes(1);
+ expect(spyDispatch).toBeCalledWith("WAKEUP");
+ });
});
describe("resetWakeUp", () => {
@@ -829,12 +853,24 @@ describe("Blockchain", () => {
it("should return false if last block is more than 3 blocktimes away from current slot time", () => {
const blockchain = sandbox.app.resolve(Blockchain);
+ configuration.getRequired.mockReturnValueOnce(false);
peerRepository.hasPeers = jest.fn().mockReturnValue(true);
const mockBlock = { data: { id: "123", height: 444, timestamp: Crypto.Slots.getTime() - 25 } };
stateStore.getLastBlock = jest.fn().mockReturnValue(mockBlock);
expect(blockchain.isSynced()).toBeFalse();
});
+
+ it("should return false if last block is more than 1 blocktimes away from current slot time and fastSync === true", () => {
+ const blockchain = sandbox.app.resolve(Blockchain);
+
+ configuration.getRequired.mockReturnValueOnce(true);
+ peerRepository.hasPeers = jest.fn().mockReturnValue(true);
+ const mockBlock = { data: { id: "123", height: 444, timestamp: Crypto.Slots.getTime() - 8 } };
+ stateStore.getLastBlock = jest.fn().mockReturnValue(mockBlock);
+
+ expect(blockchain.isSynced()).toBeFalse();
+ });
});
describe("getLastBlock", () => {
diff --git a/__tests__/unit/core-blockchain/process-blocks-job.test.ts b/__tests__/unit/core-blockchain/process-blocks-job.test.ts
index 915559e563..0004ae7b1d 100644
--- a/__tests__/unit/core-blockchain/process-blocks-job.test.ts
+++ b/__tests__/unit/core-blockchain/process-blocks-job.test.ts
@@ -16,6 +16,10 @@ describe("Blockchain", () => {
getState: jest.fn(),
};
const blockProcessor: any = {};
+ const transactionPool: any = {
+ applyBlock: jest.fn(),
+ cleanUp: jest.fn(),
+ };
const stateStore: any = {};
const databaseService: any = {};
const databaseBlockRepository: any = {};
@@ -40,6 +44,7 @@ describe("Blockchain", () => {
sandbox.app.bind(Container.Identifiers.DatabaseInteraction).toConstantValue(databaseInteraction);
sandbox.app.bind(Container.Identifiers.PeerNetworkMonitor).toConstantValue(peerNetworkMonitor);
sandbox.app.bind(Container.Identifiers.LogService).toConstantValue(logService);
+ sandbox.app.bind(Container.Identifiers.TransactionPoolService).toConstantValue(transactionPool);
sandbox.app.bind(Container.Identifiers.TriggerService).to(Services.Triggers.Triggers).inSingletonScope();
sandbox.app
@@ -92,6 +97,8 @@ describe("Blockchain", () => {
expect(databaseBlockRepository.saveBlocks).toHaveBeenCalledTimes(1);
expect(stateStore.setLastStoredBlockHeight).toHaveBeenCalledTimes(1);
expect(stateStore.setLastStoredBlockHeight).toHaveBeenCalledWith(currentBlock.height);
+ expect(transactionPool.applyBlock).toHaveBeenCalledTimes(1);
+ expect(transactionPool.cleanUp).toHaveBeenCalledTimes(1);
});
it("should process a valid block already known", async () => {
@@ -104,6 +111,8 @@ describe("Blockchain", () => {
expect(blockchainService.clearQueue).toBeCalledTimes(1);
expect(blockchainService.resetLastDownloadedBlock).toBeCalledTimes(1);
+ expect(transactionPool.applyBlock).not.toBeCalled();
+ expect(transactionPool.cleanUp).not.toBeCalled();
});
it("should not process the remaining blocks if one is not accepted (BlockProcessorResult.Rollback)", async () => {
@@ -117,6 +126,8 @@ describe("Blockchain", () => {
expect(blockProcessor.process).toBeCalledTimes(1); // only 1 out of the 2 blocks
expect(blockchainService.forkBlock).toBeCalledTimes(1); // because Rollback
+ expect(transactionPool.applyBlock).not.toBeCalled();
+ expect(transactionPool.cleanUp).not.toBeCalled();
});
it("should not process the remaining blocks if one is not accepted (BlockProcessorResult.Rejected)", async () => {
@@ -136,6 +147,8 @@ describe("Blockchain", () => {
expect(blockProcessor.process).toBeCalledTimes(1);
expect(blockchainService.clearQueue).toBeCalledTimes(1);
expect(blockchainService.resetLastDownloadedBlock).toBeCalledTimes(1);
+ expect(transactionPool.applyBlock).not.toBeCalled();
+ expect(transactionPool.cleanUp).not.toBeCalled();
});
it("should not process the remaining blocks if second is not accepted (BlockProcessorResult.Rejected)", async () => {
@@ -168,6 +181,8 @@ describe("Blockchain", () => {
expect(blockchainService.resetLastDownloadedBlock).toBeCalledTimes(1);
expect(stateStore.setLastStoredBlockHeight).toBeCalledTimes(1);
expect(stateStore.setLastStoredBlockHeight).toHaveBeenCalledWith(lastBlock.height);
+ expect(transactionPool.applyBlock).toBeCalledTimes(1);
+ expect(transactionPool.cleanUp).toBeCalledTimes(1);
});
it("should not process the remaining blocks if one is not accepted (BlockProcessorResult.Corrupted)", async () => {
@@ -190,6 +205,8 @@ describe("Blockchain", () => {
expect(blockProcessor.process).toBeCalledTimes(1);
expect(blockchainService.clearQueue).not.toBeCalled();
expect(blockchainService.resetLastDownloadedBlock).not.toBeCalled();
+ expect(transactionPool.applyBlock).not.toBeCalled();
+ expect(transactionPool.cleanUp).not.toBeCalled();
expect(process.exit).toHaveBeenCalled();
});
@@ -220,6 +237,8 @@ describe("Blockchain", () => {
expect(databaseInteraction.restoreCurrentRound).toBeCalledTimes(1);
expect(databaseService.deleteRound).toBeCalledTimes(1);
expect(stateStore.setLastStoredBlockHeight).not.toBeCalled();
+ expect(transactionPool.applyBlock).not.toBeCalled();
+ expect(transactionPool.cleanUp).not.toBeCalled();
});
it("should stop app when revertBlockHandler return Corrupted", async () => {
@@ -251,6 +270,8 @@ describe("Blockchain", () => {
expect(databaseInteraction.restoreCurrentRound).toBeCalledTimes(1);
expect(databaseService.deleteRound).toBeCalledTimes(1);
expect(stateStore.setLastStoredBlockHeight).not.toHaveBeenCalled();
+ expect(transactionPool.applyBlock).not.toBeCalled();
+ expect(transactionPool.cleanUp).not.toBeCalled();
expect(process.exit).toHaveBeenCalled();
});
@@ -296,6 +317,8 @@ describe("Blockchain", () => {
expect(stateStore.setLastStoredBlockHeight).toBeCalledWith(block.height);
expect(peerNetworkMonitor.broadcastBlock).toBeCalledTimes(1);
+ expect(transactionPool.applyBlock).toBeCalledTimes(1);
+ expect(transactionPool.cleanUp).toBeCalledTimes(1);
});
it("should skip broadcasting if state is downloadFinished", async () => {
@@ -337,6 +360,8 @@ describe("Blockchain", () => {
expect(databaseBlockRepository.saveBlocks).toBeCalledTimes(1);
expect(stateStore.setLastStoredBlockHeight).toBeCalledTimes(1);
expect(stateStore.setLastStoredBlockHeight).toBeCalledWith(block.height);
+ expect(transactionPool.applyBlock).toBeCalledTimes(1);
+ expect(transactionPool.cleanUp).toBeCalledTimes(1);
expect(peerNetworkMonitor.broadcastBlock).toBeCalledTimes(0);
});
diff --git a/__tests__/unit/core-blockchain/processor/block-processor.test.ts b/__tests__/unit/core-blockchain/processor/block-processor.test.ts
index af32ec0a51..77ba2863a5 100644
--- a/__tests__/unit/core-blockchain/processor/block-processor.test.ts
+++ b/__tests__/unit/core-blockchain/processor/block-processor.test.ts
@@ -33,6 +33,7 @@ describe("BlockProcessor", () => {
const walletRepository = {
findByPublicKey: jest.fn(),
+ hasByPublicKey: jest.fn(),
getNonce: jest.fn(),
};
const transactionHandlerRegistry = {
@@ -310,6 +311,8 @@ describe("BlockProcessor", () => {
const generatorWallet = {
getAttribute: jest.fn().mockReturnValue("generatorusername"),
};
+
+ walletRepository.hasByPublicKey = jest.fn().mockReturnValue(true);
walletRepository.findByPublicKey = jest
.fn()
.mockReturnValueOnce(generatorWallet)
@@ -331,6 +334,30 @@ describe("BlockProcessor", () => {
await expect(blockProcessor.process(block)).toResolve();
expect(InvalidGeneratorHandler.prototype.execute).toBeCalledTimes(1);
+ expect(walletRepository.hasByPublicKey).toHaveBeenCalledWith(block.data.generatorPublicKey);
+ expect(walletRepository.findByPublicKey).toHaveBeenCalledWith(block.data.generatorPublicKey);
+ expect(walletRepository.findByPublicKey).toHaveBeenCalledTimes(2);
+ expect(generatorWallet.getAttribute).toHaveBeenCalledTimes(2);
+ });
+
+ it("should execute InvalidGeneratorHandler when wallet doesn't exists", async () => {
+ const block = {
+ ...chainedBlock,
+ };
+ blockchain.getLastBlock = jest.fn().mockReturnValueOnce(baseBlock);
+ const generatorWallet = {
+ getAttribute: jest.fn().mockReturnValue("generatorusername"),
+ };
+ walletRepository.hasByPublicKey = jest.fn().mockReturnValue(false);
+ walletRepository.findByPublicKey = jest.fn().mockReturnValueOnce(generatorWallet);
+
+ const blockProcessor = sandbox.app.resolve(BlockProcessor);
+
+ await expect(blockProcessor.process(block)).toResolve();
+
+ expect(InvalidGeneratorHandler.prototype.execute).toBeCalledTimes(1);
+ expect(walletRepository.hasByPublicKey).toHaveBeenCalledWith(block.data.generatorPublicKey);
+ expect(walletRepository.findByPublicKey).not.toBeCalled();
});
it("should execute InvalidGeneratorHandler when generatorWallet.getAttribute() throws", async () => {
@@ -343,6 +370,7 @@ describe("BlockProcessor", () => {
throw new Error("oops");
}),
};
+ walletRepository.hasByPublicKey = jest.fn().mockReturnValue(true);
walletRepository.findByPublicKey = jest
.fn()
.mockReturnValueOnce(generatorWallet)
@@ -358,6 +386,10 @@ describe("BlockProcessor", () => {
await blockProcessor.process(block);
expect(InvalidGeneratorHandler.prototype.execute).toBeCalledTimes(1);
+
+ expect(walletRepository.hasByPublicKey).toHaveBeenCalledWith(block.data.generatorPublicKey);
+ expect(walletRepository.findByPublicKey).toHaveBeenCalledWith(block.data.generatorPublicKey);
+ expect(generatorWallet.getAttribute).toHaveBeenCalledTimes(1);
});
});
@@ -379,6 +411,7 @@ describe("BlockProcessor", () => {
const generatorWallet = {
getAttribute: jest.fn().mockReturnValue("generatorusername"),
};
+ walletRepository.hasByPublicKey = jest.fn().mockReturnValue(true);
walletRepository.findByPublicKey = jest.fn().mockReturnValueOnce(generatorWallet);
stateStore.getLastBlock.mockReturnValueOnce(baseBlock);
stateStore.getLastStoredBlockHeight.mockReturnValueOnce(baseBlock.data.height);
@@ -415,6 +448,7 @@ describe("BlockProcessor", () => {
const generatorWallet = {
getAttribute: jest.fn().mockReturnValue("generatorusername"),
};
+ walletRepository.hasByPublicKey = jest.fn().mockReturnValue(true);
walletRepository.findByPublicKey = jest.fn().mockReturnValueOnce(generatorWallet);
stateStore.getLastBlock.mockReturnValueOnce({ data: { height: 2 } });
stateStore.getLastBlocks.mockReturnValueOnce([
@@ -439,6 +473,7 @@ describe("BlockProcessor", () => {
const generatorWallet = {
getAttribute: jest.fn().mockReturnValue("generatorusername"),
};
+ walletRepository.hasByPublicKey = jest.fn().mockReturnValue(true);
walletRepository.findByPublicKey = jest.fn().mockReturnValueOnce(generatorWallet);
const blockProcessor = sandbox.app.resolve(BlockProcessor);
diff --git a/__tests__/unit/core-blockchain/processor/handlers/accept-block-handler.test.ts b/__tests__/unit/core-blockchain/processor/handlers/accept-block-handler.test.ts
index 46f440300b..edcce12830 100644
--- a/__tests__/unit/core-blockchain/processor/handlers/accept-block-handler.test.ts
+++ b/__tests__/unit/core-blockchain/processor/handlers/accept-block-handler.test.ts
@@ -18,7 +18,6 @@ describe("AcceptBlockHandler", () => {
setForkedBlock: jest.fn(),
clearForkedBlock: jest.fn(),
};
- const transactionPool = { removeForgedTransaction: jest.fn() };
const databaseInteractions = {
walletRepository: {
getNonce: jest.fn(),
@@ -43,7 +42,6 @@ describe("AcceptBlockHandler", () => {
container.bind(Container.Identifiers.BlockchainService).toConstantValue(blockchain);
container.bind(Container.Identifiers.StateStore).toConstantValue(state);
container.bind(Container.Identifiers.DatabaseInteraction).toConstantValue(databaseInteractions);
- container.bind(Container.Identifiers.TransactionPoolService).toConstantValue(transactionPool);
});
beforeEach(() => {
@@ -68,10 +66,6 @@ describe("AcceptBlockHandler", () => {
expect(databaseInteractions.applyBlock).toHaveBeenCalledWith(block);
expect(blockchain.resetWakeUp).toBeCalledTimes(1);
-
- expect(transactionPool.removeForgedTransaction).toBeCalledTimes(2);
- expect(transactionPool.removeForgedTransaction).toHaveBeenCalledWith(block.transactions[0]);
- expect(transactionPool.removeForgedTransaction).toHaveBeenCalledWith(block.transactions[1]);
});
it("should clear forkedBlock if incoming block has same height", async () => {
diff --git a/__tests__/unit/core-blockchain/processor/handlers/revert-block-handler.test.ts b/__tests__/unit/core-blockchain/processor/handlers/revert-block-handler.test.ts
index da86ad6c18..8fbb78700f 100644
--- a/__tests__/unit/core-blockchain/processor/handlers/revert-block-handler.test.ts
+++ b/__tests__/unit/core-blockchain/processor/handlers/revert-block-handler.test.ts
@@ -11,7 +11,6 @@ describe("AcceptBlockHandler", () => {
getLastBlocks: jest.fn(),
setLastBlock: jest.fn(),
};
- const transactionPool = { addTransaction: jest.fn() };
const databaseInteractions = {
revertBlock: jest.fn(),
};
@@ -25,7 +24,6 @@ describe("AcceptBlockHandler", () => {
container.bind(Container.Identifiers.StateStore).toConstantValue(state);
container.bind(Container.Identifiers.DatabaseInteraction).toConstantValue(databaseInteractions);
container.bind(Container.Identifiers.DatabaseService).toConstantValue(databaseService);
- container.bind(Container.Identifiers.TransactionPoolService).toConstantValue(transactionPool);
});
beforeEach(() => {
@@ -59,10 +57,6 @@ describe("AcceptBlockHandler", () => {
expect(databaseInteractions.revertBlock).toBeCalledTimes(1);
expect(databaseInteractions.revertBlock).toHaveBeenCalledWith(block);
- expect(transactionPool.addTransaction).toBeCalledTimes(2);
- expect(transactionPool.addTransaction).toHaveBeenCalledWith(block.transactions[0]);
- expect(transactionPool.addTransaction).toHaveBeenCalledWith(block.transactions[1]);
-
expect(databaseService.getLastBlock).not.toHaveBeenCalled();
expect(state.setLastBlock).toHaveBeenCalledWith(previousBlock);
});
@@ -80,10 +74,6 @@ describe("AcceptBlockHandler", () => {
expect(databaseInteractions.revertBlock).toBeCalledTimes(1);
expect(databaseInteractions.revertBlock).toHaveBeenCalledWith(block);
- expect(transactionPool.addTransaction).toBeCalledTimes(2);
- expect(transactionPool.addTransaction).toHaveBeenCalledWith(block.transactions[0]);
- expect(transactionPool.addTransaction).toHaveBeenCalledWith(block.transactions[1]);
-
expect(databaseService.getLastBlock).toHaveBeenCalledTimes(1);
expect(state.setLastBlock).toHaveBeenCalledWith(previousBlock);
});
@@ -114,10 +104,6 @@ describe("AcceptBlockHandler", () => {
expect(databaseInteractions.revertBlock).toBeCalledTimes(1);
expect(databaseInteractions.revertBlock).toHaveBeenCalledWith(block);
- expect(transactionPool.addTransaction).toBeCalledTimes(2);
- expect(transactionPool.addTransaction).toHaveBeenCalledWith(block.transactions[0]);
- expect(transactionPool.addTransaction).toHaveBeenCalledWith(block.transactions[1]);
-
expect(databaseService.getLastBlock).toHaveBeenCalledTimes(1);
expect(state.setLastBlock).not.toHaveBeenCalled();
});
diff --git a/__tests__/unit/core-cli/commands/discover-plugins.test.ts b/__tests__/unit/core-cli/commands/discover-plugins.test.ts
deleted file mode 100644
index d62edfa7fe..0000000000
--- a/__tests__/unit/core-cli/commands/discover-plugins.test.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { Console } from "@arkecosystem/core-test-framework";
-import { DiscoverPlugins } from "@packages/core-cli/src/commands";
-import { join } from "path";
-import { setGracefulCleanup } from "tmp";
-
-let cli;
-let cmd;
-
-beforeAll(() => setGracefulCleanup());
-
-beforeEach(() => {
- cli = new Console();
-
- cmd = cli.app.resolve(DiscoverPlugins);
-});
-
-describe("DiscoverPlugins", () => {
- describe("discover", () => {
- it("should discover packages containing package.json", async () => {
- const pluginsPath: string = join(__dirname, "./plugins");
-
- const plugins = await cmd.discover(pluginsPath);
-
- expect(plugins).toEqual([
- {
- name: "@namespace/package2",
- path: join(pluginsPath, "/namespace/package2"),
- version: "2.0.0",
- },
- {
- name: "package1",
- path: join(pluginsPath, "/package1"),
- version: "1.0.0",
- },
- ]);
- });
-
- it("should return empty array if path doesn't exist", async () => {
- const plugins = await cmd.discover("invalid/path");
-
- expect(plugins).toEqual([]);
- })
- });
-});
diff --git a/__tests__/unit/core-cli/services/installer.test.ts b/__tests__/unit/core-cli/services/installer.test.ts
index 71d8832bde..f8669b3d99 100644
--- a/__tests__/unit/core-cli/services/installer.test.ts
+++ b/__tests__/unit/core-cli/services/installer.test.ts
@@ -31,6 +31,10 @@ describe("Installer.install", () => {
installer.install("@arkecosystem/core");
+ expect(spySync).toHaveBeenCalledWith(
+ 'rm -f "`yarn global dir`"/node_modules/better-sqlite3/build/Release/better_sqlite3.node',
+ { shell: true },
+ );
expect(spySync).toHaveBeenCalledWith("yarn global add @arkecosystem/core@latest --force", { shell: true });
});
@@ -44,6 +48,10 @@ describe("Installer.install", () => {
installer.install("@arkecosystem/core", "3.0.0");
+ expect(spySync).toHaveBeenCalledWith(
+ 'rm -f "`yarn global dir`"/node_modules/better-sqlite3/build/Release/better_sqlite3.node',
+ { shell: true },
+ );
expect(spySync).toHaveBeenCalledWith("yarn global add @arkecosystem/core@3.0.0 --force", { shell: true });
});
@@ -57,6 +65,10 @@ describe("Installer.install", () => {
expect(() => installer.install("@arkecosystem/core")).toThrow("stderr");
+ expect(spySync).toHaveBeenCalledWith(
+ 'rm -f "`yarn global dir`"/node_modules/better-sqlite3/build/Release/better_sqlite3.node',
+ { shell: true },
+ );
expect(spySync).toHaveBeenCalledWith("yarn global add @arkecosystem/core@latest --force", { shell: true });
});
});
diff --git a/__tests__/unit/core-cli/services/plugin-manager.test.ts b/__tests__/unit/core-cli/services/plugin-manager.test.ts
new file mode 100644
index 0000000000..7996018740
--- /dev/null
+++ b/__tests__/unit/core-cli/services/plugin-manager.test.ts
@@ -0,0 +1,154 @@
+import { Console } from "@arkecosystem/core-test-framework";
+import { PluginManager } from "@packages/core-cli/src/services";
+import fs from "fs-extra";
+import { join } from "path";
+import { setGracefulCleanup } from "tmp";
+
+const token = "ark";
+const network = "testnet";
+const packageName = "dummyPackageName";
+const install = jest.fn();
+const exists = jest.fn().mockReturnValue(false);
+
+let npmUpdateCalled = false;
+let gitUpdateCalled = false;
+const updateNPM = () => (npmUpdateCalled = true);
+const updateGIT = () => (gitUpdateCalled = true);
+
+jest.mock("@packages/core-cli/src/services/source-providers/npm", () => ({
+ NPM: jest.fn().mockImplementation(() => ({
+ exists,
+ install,
+ update: updateNPM,
+ })),
+}));
+
+jest.mock("@packages/core-cli/src/services/source-providers/git", () => ({
+ Git: jest.fn().mockImplementation(() => ({
+ exists,
+ install,
+ update: updateGIT,
+ })),
+}));
+
+jest.mock("@packages/core-cli/src/services/source-providers/file", () => ({
+ File: jest.fn().mockImplementation(() => ({
+ exists,
+ install,
+ })),
+}));
+
+let cli;
+let pluginManager;
+
+beforeAll(() => setGracefulCleanup());
+
+beforeEach(() => {
+ gitUpdateCalled = false;
+ npmUpdateCalled = false;
+
+ cli = new Console();
+
+ pluginManager = cli.app.resolve(PluginManager);
+});
+
+describe("DiscoverPlugins", () => {
+ describe("discover", () => {
+ it("should discover packages containing package.json", async () => {
+ const pluginsPath: string = join(__dirname, "./plugins");
+
+ jest.spyOn(pluginManager, "getPluginsPath").mockReturnValue(pluginsPath);
+
+ const plugins = await pluginManager.list(token, network);
+
+ expect(plugins).toEqual([
+ {
+ name: "@namespace/package2",
+ path: join(pluginsPath, "/@namespace/package2"),
+ version: "2.0.0",
+ },
+ {
+ name: "package1",
+ path: join(pluginsPath, "/package1"),
+ version: "1.0.0",
+ },
+ ]);
+ });
+
+ it("should return empty array if path doesn't exist", async () => {
+ const plugins = await pluginManager.list(token, "undefined");
+
+ expect(plugins).toEqual([]);
+ });
+ });
+
+ describe("install", () => {
+ it("should throw an error when package doesn't exist", async () => {
+ const errorMessage = `The given package [${packageName}] is neither a git nor a npm package.`;
+ await expect(pluginManager.install(token, network, packageName)).rejects.toThrow(errorMessage);
+
+ expect(exists).toHaveBeenCalledWith(packageName, undefined);
+ expect(install).not.toHaveBeenCalled();
+ });
+
+ it("should call install on existing packages", async () => {
+ exists.mockReturnValue(true);
+
+ await expect(pluginManager.install(token, network, packageName)).toResolve();
+
+ expect(exists).toHaveBeenCalledWith(packageName, undefined);
+ expect(install).toHaveBeenCalledWith(packageName, undefined);
+ });
+
+ it("should call install on existing packages with version", async () => {
+ exists.mockReturnValue(true);
+
+ const version = "3.0.0";
+ await expect(pluginManager.install(token, network, packageName, version)).toResolve();
+
+ expect(exists).toHaveBeenCalledWith(packageName, version);
+ expect(install).toHaveBeenCalledWith(packageName, version);
+ });
+ });
+
+ describe("update", () => {
+ it("should throw when the plugin doesn't exist", async () => {
+ await expect(pluginManager.update(token, network, packageName)).rejects.toThrow(
+ `The package [${packageName}] does not exist.`,
+ );
+ });
+
+ it("if the plugin is a git directory, it should be updated", async () => {
+ expect(gitUpdateCalled).toEqual(false);
+
+ jest.spyOn(fs, "existsSync").mockReturnValueOnce(true).mockReturnValueOnce(true);
+
+ await expect(pluginManager.update(token, network, packageName)).toResolve();
+ expect(gitUpdateCalled).toEqual(true);
+ });
+
+ it("if the plugin is a NPM package, it should be updated on default path", async () => {
+ expect(npmUpdateCalled).toEqual(false);
+ jest.spyOn(fs, "existsSync").mockReturnValueOnce(true).mockReturnValueOnce(false);
+
+ await expect(pluginManager.update(token, network, packageName)).toResolve();
+ expect(npmUpdateCalled).toEqual(true);
+ });
+ });
+
+ describe("remove", () => {
+ it("should throw when the plugin doesn't exist", async () => {
+ await expect(pluginManager.remove(token, network, packageName)).rejects.toThrow(
+ `The package [${packageName}] does not exist.`,
+ );
+ });
+
+ it("remove plugin if exist", async () => {
+ jest.spyOn(fs, "existsSync").mockReturnValue(true);
+ const removeSync = jest.spyOn(fs, "removeSync");
+
+ await expect(pluginManager.remove(token, network, packageName)).toResolve();
+ expect(removeSync).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/__tests__/unit/core-cli/commands/plugins/namespace/package2/package.json b/__tests__/unit/core-cli/services/plugins/@namespace/package2/package.json
similarity index 100%
rename from __tests__/unit/core-cli/commands/plugins/namespace/package2/package.json
rename to __tests__/unit/core-cli/services/plugins/@namespace/package2/package.json
diff --git a/__tests__/unit/core-cli/commands/plugins/package1/package.json b/__tests__/unit/core-cli/services/plugins/package1/package.json
similarity index 100%
rename from __tests__/unit/core-cli/commands/plugins/package1/package.json
rename to __tests__/unit/core-cli/services/plugins/package1/package.json
diff --git a/__tests__/unit/core-cli/services/process-manager.test.ts b/__tests__/unit/core-cli/services/process-manager.test.ts
index c2b4aa0357..b913abc0b6 100644
--- a/__tests__/unit/core-cli/services/process-manager.test.ts
+++ b/__tests__/unit/core-cli/services/process-manager.test.ts
@@ -26,7 +26,7 @@ describe("ProcessManager", () => {
stdout: null,
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const processes: Contracts.ProcessDescription[] | undefined = processManager.list();
@@ -43,7 +43,7 @@ describe("ProcessManager", () => {
stdout: "\n",
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const processes: Contracts.ProcessDescription[] | undefined = processManager.list();
@@ -60,7 +60,7 @@ describe("ProcessManager", () => {
stdout: "{",
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const processes: Contracts.ProcessDescription[] | undefined = processManager.list();
@@ -92,7 +92,7 @@ describe("ProcessManager", () => {
stdout: '[{ "key": "value" }]',
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const processes: Contracts.ProcessDescription[] | undefined = processManager.list();
@@ -111,7 +111,7 @@ describe("ProcessManager", () => {
stdout: '[{ "id": "stub", "pm2_env": { "status": "unknown" } }]',
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const process: Contracts.ProcessDescription | undefined = processManager.describe("stub");
@@ -127,7 +127,7 @@ describe("ProcessManager", () => {
stdout: '[{ "id": "stub-other", "pm2_env": { "status": "unknown" } }]',
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const process: Contracts.ProcessDescription | undefined = processManager.describe("stub");
@@ -143,7 +143,7 @@ describe("ProcessManager", () => {
stdout: "[]",
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const process: Contracts.ProcessDescription | undefined = processManager.describe("stub");
@@ -175,7 +175,7 @@ describe("ProcessManager", () => {
stdout: null,
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const { failed } = processManager.start(
@@ -196,7 +196,7 @@ describe("ProcessManager", () => {
stdout: null,
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const { failed } = processManager.start(
@@ -221,7 +221,7 @@ describe("ProcessManager", () => {
stdout: null,
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const { failed } = processManager.start(
@@ -234,7 +234,9 @@ describe("ProcessManager", () => {
// Assert...
expect(failed).toBeFalse();
- expect(spySync).toHaveBeenCalledWith("pm2 start stub.js --name='stub' -- core:run --daemon", { shell: true });
+ expect(spySync).toHaveBeenCalledWith("pm2 start stub.js --name='stub' -- core:run --daemon", {
+ shell: true,
+ });
});
it("should ignore the flags if they are undefined", () => {
@@ -243,7 +245,7 @@ describe("ProcessManager", () => {
stdout: null,
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const { failed } = processManager.start(
@@ -266,7 +268,7 @@ describe("ProcessManager", () => {
stdout: null,
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const { failed } = processManager.stop("stub");
@@ -282,7 +284,7 @@ describe("ProcessManager", () => {
stdout: null,
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const { failed } = processManager.stop("stub", { key: "value" });
@@ -297,10 +299,11 @@ describe("ProcessManager", () => {
it("should be OK if failed is false", () => {
// Arrange...
const spySync: jest.SpyInstance = jest.spyOn(execa, "sync").mockReturnValue({
+ // @ts-ignore
stdout: null,
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const { failed } = processManager.restart("stub");
@@ -316,7 +319,7 @@ describe("ProcessManager", () => {
stdout: null,
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const { failed } = processManager.restart("stub", { key: "value" });
@@ -332,7 +335,7 @@ describe("ProcessManager", () => {
stdout: null,
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const { failed } = processManager.restart("stub", {});
@@ -350,7 +353,7 @@ describe("ProcessManager", () => {
stdout: null,
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const { failed } = processManager.reload("stub");
@@ -368,7 +371,7 @@ describe("ProcessManager", () => {
stdout: null,
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const { failed } = processManager.reset("stub");
@@ -386,7 +389,7 @@ describe("ProcessManager", () => {
stdout: null,
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const { failed } = processManager.delete("stub");
@@ -404,7 +407,7 @@ describe("ProcessManager", () => {
stdout: null,
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const { failed } = processManager.flush("stub");
@@ -422,7 +425,7 @@ describe("ProcessManager", () => {
stdout: null,
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const { failed } = processManager.reloadLogs();
@@ -440,7 +443,7 @@ describe("ProcessManager", () => {
stdout: null,
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const { failed } = processManager.ping();
@@ -458,7 +461,7 @@ describe("ProcessManager", () => {
stdout: null,
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const { failed } = processManager.update();
@@ -472,6 +475,7 @@ describe("ProcessManager", () => {
describe("#trigger", () => {
it(".trigger()", async () => {
// Arrange...
+ // @ts-ignore
execa.mockResolvedValue({
stdout: null,
stderr: undefined,
@@ -494,7 +498,7 @@ describe("ProcessManager", () => {
stdout: '[{ "id": "stub", "pm2_env": { "status": "online" } }]',
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const status = processManager.status("stub");
@@ -526,7 +530,7 @@ describe("ProcessManager", () => {
stdout: '[{ "id": "stub-other", "pm2_env": { "status": "online" } }]',
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const status = processManager.status("stub");
@@ -544,7 +548,7 @@ describe("ProcessManager", () => {
stdout: '[{ "id": "stub", "pm2_env": { "status": "online" } }]',
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const status = processManager.isOnline("stub");
@@ -562,7 +566,7 @@ describe("ProcessManager", () => {
stdout: '[{ "id": "stub", "pm2_env": { "status": "stopped" } }]',
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const status = processManager.isStopped("stub");
@@ -580,7 +584,7 @@ describe("ProcessManager", () => {
stdout: '[{ "id": "stub", "pm2_env": { "status": "stopping" } }]',
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const status = processManager.isStopping("stub");
@@ -598,7 +602,7 @@ describe("ProcessManager", () => {
stdout: '[{ "id": "stub", "pm2_env": { "status": "waiting restart" } }]',
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const status = processManager.isWaiting("stub");
@@ -616,7 +620,7 @@ describe("ProcessManager", () => {
stdout: '[{ "id": "stub", "pm2_env": { "status": "launching" } }]',
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const status = processManager.isLaunching("stub");
@@ -634,7 +638,7 @@ describe("ProcessManager", () => {
stdout: '[{ "id": "stub", "pm2_env": { "status": "errored" } }]',
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const status = processManager.isErrored("stub");
@@ -652,7 +656,7 @@ describe("ProcessManager", () => {
stdout: '[{ "id": "stub", "pm2_env": { "status": "one-launch-status" } }]',
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const status = processManager.isOneLaunch("stub");
@@ -670,7 +674,7 @@ describe("ProcessManager", () => {
stdout: '[{ "id": "stub", "pm2_env": { "status": "unknown" } }]',
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const status = processManager.isUnknown("stub");
@@ -686,7 +690,7 @@ describe("ProcessManager", () => {
stdout: '[{ "id": "stub", "pm2_env": { "status": "online" } }]',
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const status = processManager.isUnknown("stub");
@@ -718,7 +722,7 @@ describe("ProcessManager", () => {
stdout: 1,
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const status = processManager.has("stub");
@@ -734,7 +738,7 @@ describe("ProcessManager", () => {
stdout: "",
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const status = processManager.has("stub");
@@ -766,7 +770,7 @@ describe("ProcessManager", () => {
stdout: "",
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const status = processManager.missing("stub");
@@ -782,7 +786,7 @@ describe("ProcessManager", () => {
stdout: 1,
stderr: undefined,
failed: false,
- });
+ } as any);
// Act...
const status = processManager.missing("stub");
diff --git a/__tests__/unit/core/source-providers/file.test.ts b/__tests__/unit/core-cli/services/source-providers/file.test.ts
similarity index 97%
rename from __tests__/unit/core/source-providers/file.test.ts
rename to __tests__/unit/core-cli/services/source-providers/file.test.ts
index f95e521d90..d37593cc60 100644
--- a/__tests__/unit/core/source-providers/file.test.ts
+++ b/__tests__/unit/core-cli/services/source-providers/file.test.ts
@@ -1,6 +1,6 @@
import "jest-extended";
-import { File, Errors } from "@packages/core/src/source-providers";
+import { File, Errors } from "@packages/core-cli/src/services/source-providers";
import fs from "fs-extra";
import { dirSync, fileSync, setGracefulCleanup } from "tmp";
import { join } from "path";
diff --git a/__tests__/unit/core/source-providers/git.test.ts b/__tests__/unit/core-cli/services/source-providers/git.test.ts
similarity index 95%
rename from __tests__/unit/core/source-providers/git.test.ts
rename to __tests__/unit/core-cli/services/source-providers/git.test.ts
index e4b8418ed8..b6b5b97bf1 100644
--- a/__tests__/unit/core/source-providers/git.test.ts
+++ b/__tests__/unit/core-cli/services/source-providers/git.test.ts
@@ -1,11 +1,11 @@
import "jest-extended";
-import { Git } from "@packages/core/src/source-providers";
+import { Git } from "@packages/core-cli/src/services/source-providers";
import fs from "fs-extra";
import { dirSync, setGracefulCleanup } from "tmp";
import { join } from "path";
-import execa from "../../../../__mocks__/execa";
+import execa from "../../../../../__mocks__/execa";
let dataPath: string;
let tempPath: string;
diff --git a/__tests__/unit/core/source-providers/invalid-utils-0.9.1.tgz b/__tests__/unit/core-cli/services/source-providers/invalid-utils-0.9.1.tgz
similarity index 100%
rename from __tests__/unit/core/source-providers/invalid-utils-0.9.1.tgz
rename to __tests__/unit/core-cli/services/source-providers/invalid-utils-0.9.1.tgz
diff --git a/__tests__/unit/core/source-providers/missing-utils-0.9.1.tgz b/__tests__/unit/core-cli/services/source-providers/missing-utils-0.9.1.tgz
similarity index 100%
rename from __tests__/unit/core/source-providers/missing-utils-0.9.1.tgz
rename to __tests__/unit/core-cli/services/source-providers/missing-utils-0.9.1.tgz
diff --git a/__tests__/unit/core/source-providers/npm.test.ts b/__tests__/unit/core-cli/services/source-providers/npm.test.ts
similarity index 98%
rename from __tests__/unit/core/source-providers/npm.test.ts
rename to __tests__/unit/core-cli/services/source-providers/npm.test.ts
index e0f00476a0..6742bf0b36 100644
--- a/__tests__/unit/core/source-providers/npm.test.ts
+++ b/__tests__/unit/core-cli/services/source-providers/npm.test.ts
@@ -1,6 +1,6 @@
import "jest-extended";
-import { NPM } from "@packages/core/src/source-providers";
+import { NPM } from "@packages/core-cli/src/services/source-providers";
import execa from "execa";
import fs from "fs-extra";
import nock from "nock";
diff --git a/__tests__/unit/core/source-providers/utils-0.9.1.tgz b/__tests__/unit/core-cli/services/source-providers/utils-0.9.1.tgz
similarity index 100%
rename from __tests__/unit/core/source-providers/utils-0.9.1.tgz
rename to __tests__/unit/core-cli/services/source-providers/utils-0.9.1.tgz
diff --git a/__tests__/unit/core-database/database-service.test.ts b/__tests__/unit/core-database/database-service.test.ts
index b1932c5e9f..311050fd43 100644
--- a/__tests__/unit/core-database/database-service.test.ts
+++ b/__tests__/unit/core-database/database-service.test.ts
@@ -83,6 +83,18 @@ describe("DatabaseService.initialize", () => {
}
});
+ it("should analyze database", async () => {
+ try {
+ const databaseService = container.resolve(DatabaseService);
+
+ await databaseService.initialize();
+
+ expect(connection.query).toBeCalledWith("ANALYZE;");
+ } finally {
+ delete process.env.CORE_RESET_DATABASE;
+ }
+ });
+
it("should terminate app if exception was raised", async () => {
try {
const databaseService = container.resolve(DatabaseService);
diff --git a/__tests__/unit/core-database/service-provider.test.ts b/__tests__/unit/core-database/service-provider.test.ts
index d3d54f10dd..012f865e3b 100644
--- a/__tests__/unit/core-database/service-provider.test.ts
+++ b/__tests__/unit/core-database/service-provider.test.ts
@@ -43,7 +43,7 @@ describe("ServiceProvider.register", () => {
await serviceProvider.register();
expect(createConnection).toBeCalled();
- expect(getCustomRepository).toBeCalledTimes(3);
+ expect(getCustomRepository).toBeCalledTimes(6);
expect(events.dispatch).toBeCalled();
@@ -127,6 +127,7 @@ describe("ServiceProvider.configSchema", () => {
expect(result.value.connection.entityPrefix).toBeString();
expect(result.value.connection.synchronize).toBeFalse();
expect(result.value.connection.logging).toBeFalse();
+ expect(result.value.apiConnectionTimeout).toEqual(3000);
});
it("should allow configuration extension", async () => {
@@ -349,5 +350,27 @@ describe("ServiceProvider.configSchema", () => {
expect(result.error!.message).toEqual('"connection.logging" is required');
});
+
+ it("apiConnectionTimeout is required && is integer && is >= 1", async () => {
+ defaults.apiConnectionTimeout = false;
+ let result = (serviceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"apiConnectionTimeout" must be a number');
+
+ defaults.apiConnectionTimeout = 1.12;
+ result = (serviceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"apiConnectionTimeout" must be an integer');
+
+ defaults.apiConnectionTimeout = 0;
+ result = (serviceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"apiConnectionTimeout" must be greater than or equal to 1');
+
+ delete defaults.apiConnectionTimeout;
+ result = (serviceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error!.message).toEqual('"apiConnectionTimeout" is required');
+ });
});
});
diff --git a/__tests__/unit/core-database/transaction-filter.test.ts b/__tests__/unit/core-database/transaction-filter.test.ts
index 04668f37bf..5b766fa8ec 100644
--- a/__tests__/unit/core-database/transaction-filter.test.ts
+++ b/__tests__/unit/core-database/transaction-filter.test.ts
@@ -4,6 +4,7 @@ import { Enums, Utils } from "@packages/crypto";
const walletRepository = {
findByAddress: jest.fn(),
+ hasByAddress: jest.fn(),
};
const container = new Container.Container();
@@ -11,6 +12,7 @@ container.bind(Container.Identifiers.WalletRepository).toConstantValue(walletRep
beforeEach(() => {
walletRepository.findByAddress.mockReset();
+ walletRepository.hasByAddress.mockReset();
});
describe("TransactionFilter.getExpression", () => {
@@ -24,7 +26,8 @@ describe("TransactionFilter.getExpression", () => {
});
describe("TransactionCriteria.address", () => {
- it("should compare senderPublicKey, recipientId, multipayment recipientId, delegate registration sender", async () => {
+ it("should compare senderPublicKey, recipientId, multipayment recipientId", async () => {
+ walletRepository.hasByAddress.mockReturnValue(true);
walletRepository.findByAddress
.mockReturnValueOnce({
getPublicKey: () => {
@@ -40,49 +43,31 @@ describe("TransactionFilter.getExpression", () => {
const transactionFilter = container.resolve(TransactionFilter);
const expression = await transactionFilter.getExpression({ address: "123" });
+ expect(walletRepository.hasByAddress).toBeCalledWith("123");
expect(walletRepository.findByAddress).toBeCalledWith("123");
expect(expression).toEqual({
op: "or",
expressions: [
{ property: "senderPublicKey", op: "equal", value: "456" },
{ property: "recipientId", op: "equal", value: "123" },
- {
- op: "and",
- expressions: [
- { property: "typeGroup", op: "equal", value: Enums.TransactionTypeGroup.Core },
- { property: "type", op: "equal", value: Enums.TransactionType.MultiPayment },
- { property: "asset", op: "contains", value: { payments: [{ recipientId: "123" }] } },
- ],
- },
- {
- op: "and",
- expressions: [
- { property: "typeGroup", op: "equal", value: Enums.TransactionTypeGroup.Core },
- { property: "type", op: "equal", value: Enums.TransactionType.DelegateRegistration },
- { property: "senderPublicKey", op: "equal", value: "456" },
- ],
- },
+ { property: "asset", op: "contains", value: { payments: [{ recipientId: "123" }] } },
],
});
});
it("should compare recipientId, multipayment recipientId when wallet not found", async () => {
+ walletRepository.hasByAddress.mockReturnValue(false);
+
const transactionFilter = container.resolve(TransactionFilter);
const expression = await transactionFilter.getExpression({ address: "123" });
- expect(walletRepository.findByAddress).toBeCalledWith("123");
+ expect(walletRepository.hasByAddress).toBeCalledWith("123");
+ expect(walletRepository.findByAddress).not.toBeCalled();
expect(expression).toEqual({
op: "or",
expressions: [
{ property: "recipientId", op: "equal", value: "123" },
- {
- op: "and",
- expressions: [
- { property: "typeGroup", op: "equal", value: Enums.TransactionTypeGroup.Core },
- { property: "type", op: "equal", value: Enums.TransactionType.MultiPayment },
- { property: "asset", op: "contains", value: { payments: [{ recipientId: "123" }] } },
- ],
- },
+ { property: "asset", op: "contains", value: { payments: [{ recipientId: "123" }] } },
],
});
});
@@ -90,6 +75,7 @@ describe("TransactionFilter.getExpression", () => {
describe("TransactionCriteria.senderId", () => {
it("should compare senderPublicKey using equal expression", async () => {
+ walletRepository.hasByAddress.mockReturnValue(true);
walletRepository.findByAddress.mockReturnValueOnce({
getPublicKey: () => {
return "456";
@@ -99,21 +85,26 @@ describe("TransactionFilter.getExpression", () => {
const transactionFilter = container.resolve(TransactionFilter);
const expression = await transactionFilter.getExpression({ senderId: "123" });
+ expect(walletRepository.hasByAddress).toBeCalledWith("123");
expect(walletRepository.findByAddress).toBeCalledWith("123");
expect(expression).toEqual({ property: "senderPublicKey", op: "equal", value: "456" });
});
it("should produce false expression when wallet not found", async () => {
+ walletRepository.hasByAddress.mockReturnValue(false);
+
const transactionFilter = container.resolve(TransactionFilter);
const expression = await transactionFilter.getExpression({ senderId: "123" });
- expect(walletRepository.findByAddress).toBeCalledWith("123");
+ expect(walletRepository.hasByAddress).toBeCalledWith("123");
+ expect(walletRepository.findByAddress).not.toBeCalled();
expect(expression).toEqual({ op: "false" });
});
});
describe("TransactionCriteria.recipientId", () => {
- it("should compare using equal expression and include multipayment and include delegate registration transaction", async () => {
+ it("should compare using equal expression and include multipayment", async () => {
+ walletRepository.hasByAddress.mockReturnValue(true);
walletRepository.findByAddress.mockReturnValueOnce({
getPublicKey: () => {
return "456";
@@ -123,48 +114,11 @@ describe("TransactionFilter.getExpression", () => {
const transactionFilter = container.resolve(TransactionFilter);
const expression = await transactionFilter.getExpression({ recipientId: "123" });
- expect(walletRepository.findByAddress).toBeCalledWith("123");
expect(expression).toEqual({
op: "or",
expressions: [
{ property: "recipientId", op: "equal", value: "123" },
- {
- op: "and",
- expressions: [
- { property: "typeGroup", op: "equal", value: Enums.TransactionTypeGroup.Core },
- { property: "type", op: "equal", value: Enums.TransactionType.MultiPayment },
- { property: "asset", op: "contains", value: { payments: [{ recipientId: "123" }] } },
- ],
- },
- {
- op: "and",
- expressions: [
- { property: "typeGroup", op: "equal", value: Enums.TransactionTypeGroup.Core },
- { property: "type", op: "equal", value: Enums.TransactionType.DelegateRegistration },
- { property: "senderPublicKey", op: "equal", value: "456" },
- ],
- },
- ],
- });
- });
-
- it("should compare using equal expression and include multipayment when wallet not found", async () => {
- const transactionFilter = container.resolve(TransactionFilter);
- const expression = await transactionFilter.getExpression({ recipientId: "123" });
-
- expect(walletRepository.findByAddress).toBeCalledWith("123");
- expect(expression).toEqual({
- op: "or",
- expressions: [
- { property: "recipientId", op: "equal", value: "123" },
- {
- op: "and",
- expressions: [
- { property: "typeGroup", op: "equal", value: Enums.TransactionTypeGroup.Core },
- { property: "type", op: "equal", value: Enums.TransactionType.MultiPayment },
- { property: "asset", op: "contains", value: { payments: [{ recipientId: "123" }] } },
- ],
- },
+ { property: "asset", op: "contains", value: { payments: [{ recipientId: "123" }] } },
],
});
});
@@ -374,7 +328,7 @@ describe("TransactionFilter.getExpression", () => {
expect(expression).toEqual({
property: "vendorField",
op: "like",
- pattern: "%pattern%",
+ pattern: Buffer.from("%pattern%", "utf-8"),
});
});
});
diff --git a/__tests__/unit/core-forger/mocks/nes.ts b/__tests__/unit/core-forger/mocks/nes.ts
index 0a41803be6..9439018c60 100644
--- a/__tests__/unit/core-forger/mocks/nes.ts
+++ b/__tests__/unit/core-forger/mocks/nes.ts
@@ -1,5 +1,5 @@
export const nesClient = {
- connect: jest.fn().mockReturnValue(new Promise((resolve) => resolve())),
+ connect: jest.fn().mockReturnValue(new Promise((resolve) => resolve())),
disconnect: jest.fn(),
request: jest.fn().mockReturnValue({ payload: Buffer.from(JSON.stringify({})) }),
onError: jest.fn(),
diff --git a/__tests__/unit/core-kernel/services/schedule/block-job.test.ts b/__tests__/unit/core-kernel/services/schedule/block-job.test.ts
index d695174282..d22de56677 100644
--- a/__tests__/unit/core-kernel/services/schedule/block-job.test.ts
+++ b/__tests__/unit/core-kernel/services/schedule/block-job.test.ts
@@ -11,7 +11,7 @@ let job: BlockJob;
let eventDispatcher: MemoryEventDispatcher;
const delay = async (timeout) => {
- await new Promise((resolve) => {
+ await new Promise((resolve) => {
setTimeout(() => {
resolve();
}, timeout);
diff --git a/__tests__/unit/core-kernel/services/search/pagination-service.test.ts b/__tests__/unit/core-kernel/services/search/pagination-service.test.ts
index 6df57fbcfb..96841d2c6a 100644
--- a/__tests__/unit/core-kernel/services/search/pagination-service.test.ts
+++ b/__tests__/unit/core-kernel/services/search/pagination-service.test.ts
@@ -18,12 +18,12 @@ describe("PaginationService.getEmptyPage", () => {
});
describe("PaginationService.getPage", () => {
- it("should leave items order intact when sorting isn't provided", () => {
+ it("should leave items order intact when sorting isn't provided", async () => {
const paginationService = container.resolve(PaginationService);
const pagination = { offset: 0, limit: 100 };
const items = [{ v: 1 }, { v: 3 }, {}, { v: 2 }];
const sorting = [];
- const resultsPage = paginationService.getPage(pagination, sorting, items);
+ const resultsPage = await paginationService.getPage(pagination, sorting, items);
expect(resultsPage).toEqual({
results: [{ v: 1 }, { v: 3 }, {}, { v: 2 }],
@@ -32,12 +32,26 @@ describe("PaginationService.getPage", () => {
});
});
- it("should return items with undefined properties at the end", () => {
+ it("should leave items order intact when sorting by undefined property", async () => {
+ const paginationService = container.resolve(PaginationService);
+ const pagination = { offset: 0, limit: 100 };
+ const items = [{ v: 1 }, { v: 3 }, {}, { v: 2 }];
+ const sorting = [{ property: "dummy", direction: "asc" as const }];
+ const resultsPage = await paginationService.getPage(pagination, sorting, items);
+
+ expect(resultsPage).toEqual({
+ results: [{ v: 1 }, { v: 3 }, {}, { v: 2 }],
+ totalCount: 4,
+ meta: { totalCountIsEstimate: false },
+ });
+ });
+
+ it("should return items with undefined properties at the end", async () => {
const paginationService = container.resolve(PaginationService);
const pagination = { offset: 0, limit: 100 };
const items = [{ v: 1 }, {}, { v: 3 }, {}, { v: 2 }];
const sorting = [{ property: "v", direction: "asc" as const }];
- const resultsPage = paginationService.getPage(pagination, sorting, items);
+ const resultsPage = await paginationService.getPage(pagination, sorting, items);
expect(resultsPage).toEqual({
results: [{ v: 1 }, { v: 2 }, { v: 3 }, {}, {}],
@@ -46,12 +60,12 @@ describe("PaginationService.getPage", () => {
});
});
- it("should return items with undefined properties at the end regardless of direction", () => {
+ it("should return items with undefined properties at the end regardless of direction", async () => {
const paginationService = container.resolve(PaginationService);
const pagination = { offset: 0, limit: 100 };
const items = [{ v: 1 }, { v: 3 }, {}, { v: 2 }];
const sorting = [{ property: "v", direction: "desc" as const }];
- const resultsPage = paginationService.getPage(pagination, sorting, items);
+ const resultsPage = await paginationService.getPage(pagination, sorting, items);
expect(resultsPage).toEqual({
results: [{ v: 3 }, { v: 2 }, { v: 1 }, {}],
@@ -60,12 +74,12 @@ describe("PaginationService.getPage", () => {
});
});
- it("should return items with null properties at the end before items with undefined properties", () => {
+ it("should return items with null properties at the end before items with undefined properties", async () => {
const paginationService = container.resolve(PaginationService);
const pagination = { offset: 0, limit: 100 };
const items = [{ v: 1 }, { v: 3 }, {}, { v: null }, { v: 2 }, { v: null }];
const sorting = [{ property: "v", direction: "asc" as const }];
- const resultsPage = paginationService.getPage(pagination, sorting, items);
+ const resultsPage = await paginationService.getPage(pagination, sorting, items);
expect(resultsPage).toEqual({
results: [{ v: 1 }, { v: 2 }, { v: 3 }, { v: null }, { v: null }, {}],
@@ -74,12 +88,12 @@ describe("PaginationService.getPage", () => {
});
});
- it("should return items with null properties at the end before items with undefined properties regardless of direction", () => {
+ it("should return items with null properties at the end before items with undefined properties regardless of direction", async () => {
const paginationService = container.resolve(PaginationService);
const pagination = { offset: 0, limit: 100 };
const items = [{ v: 1 }, { v: null }, { v: 3 }, {}, { v: 2 }];
const sorting = [{ property: "v", direction: "desc" as const }];
- const resultsPage = paginationService.getPage(pagination, sorting, items);
+ const resultsPage = await paginationService.getPage(pagination, sorting, items);
expect(resultsPage).toEqual({
results: [{ v: 3 }, { v: 2 }, { v: 1 }, { v: null }, {}],
@@ -88,7 +102,7 @@ describe("PaginationService.getPage", () => {
});
});
- it("should sort using second sorting instruction when first properties are equal booleans", () => {
+ it("should sort using second sorting instruction when first properties are equal booleans", async () => {
const paginationService = container.resolve(PaginationService);
const pagination = { offset: 0, limit: 100 };
const items = [
@@ -100,7 +114,7 @@ describe("PaginationService.getPage", () => {
{ property: "a", direction: "asc" as const },
{ property: "b", direction: "asc" as const },
];
- const resultsPage = paginationService.getPage(pagination, sorting, items);
+ const resultsPage = await paginationService.getPage(pagination, sorting, items);
expect(resultsPage).toEqual({
results: [
@@ -113,7 +127,7 @@ describe("PaginationService.getPage", () => {
});
});
- it("should sort using second sorting instruction when first properties are equal strings", () => {
+ it("should sort using second sorting instruction when first properties are equal strings", async () => {
const paginationService = container.resolve(PaginationService);
const pagination = { offset: 0, limit: 100 };
const items = [
@@ -125,7 +139,7 @@ describe("PaginationService.getPage", () => {
{ property: "a", direction: "asc" as const },
{ property: "b", direction: "asc" as const },
];
- const resultsPage = paginationService.getPage(pagination, sorting, items);
+ const resultsPage = await paginationService.getPage(pagination, sorting, items);
expect(resultsPage).toEqual({
results: [
@@ -138,7 +152,7 @@ describe("PaginationService.getPage", () => {
});
});
- it("should sort using second sorting instruction when first properties are equal numbers", () => {
+ it("should sort using second sorting instruction when first properties are equal numbers", async () => {
const paginationService = container.resolve(PaginationService);
const pagination = { offset: 0, limit: 100 };
const items = [
@@ -150,7 +164,7 @@ describe("PaginationService.getPage", () => {
{ property: "a", direction: "asc" as const },
{ property: "b", direction: "asc" as const },
];
- const resultsPage = paginationService.getPage(pagination, sorting, items);
+ const resultsPage = await paginationService.getPage(pagination, sorting, items);
expect(resultsPage).toEqual({
results: [
@@ -163,7 +177,7 @@ describe("PaginationService.getPage", () => {
});
});
- it("should sort using second sorting instruction when first properties are equal bigints", () => {
+ it("should sort using second sorting instruction when first properties are equal bigints", async () => {
const paginationService = container.resolve(PaginationService);
const pagination = { offset: 0, limit: 100 };
const items = [
@@ -175,7 +189,7 @@ describe("PaginationService.getPage", () => {
{ property: "a", direction: "asc" as const },
{ property: "b", direction: "asc" as const },
];
- const resultsPage = paginationService.getPage(pagination, sorting, items);
+ const resultsPage = await paginationService.getPage(pagination, sorting, items);
expect(resultsPage).toEqual({
results: [
@@ -188,7 +202,7 @@ describe("PaginationService.getPage", () => {
});
});
- it("should sort using second sorting instruction when first properties are equal Utils.BigNumber instances", () => {
+ it("should sort using second sorting instruction when first properties are equal Utils.BigNumber instances", async () => {
const paginationService = container.resolve(PaginationService);
const pagination = { offset: 0, limit: 100 };
const items = [
@@ -200,7 +214,7 @@ describe("PaginationService.getPage", () => {
{ property: "a", direction: "asc" as const },
{ property: "b", direction: "asc" as const },
];
- const resultsPage = paginationService.getPage(pagination, sorting, items);
+ const resultsPage = await paginationService.getPage(pagination, sorting, items);
expect(resultsPage).toEqual({
results: [
@@ -213,12 +227,62 @@ describe("PaginationService.getPage", () => {
});
});
- it("should sort by Utils.BigNumber property", () => {
+ it("should sort using second sorting instruction when first properties are null", async () => {
+ const paginationService = container.resolve(PaginationService);
+ const pagination = { offset: 0, limit: 100 };
+ const items = [
+ { a: null, b: 101 },
+ { a: true, b: 100 },
+ { a: null, b: 200 },
+ ];
+ const sorting = [
+ { property: "a", direction: "asc" as const },
+ { property: "b", direction: "asc" as const },
+ ];
+ const resultsPage = await paginationService.getPage(pagination, sorting, items);
+
+ expect(resultsPage).toEqual({
+ results: [
+ { a: true, b: 100 },
+ { a: null, b: 101 },
+ { a: null, b: 200 },
+ ],
+ totalCount: 3,
+ meta: { totalCountIsEstimate: false },
+ });
+ });
+
+ it("should sort using second sorting instruction when first properties are undefined", async () => {
+ const paginationService = container.resolve(PaginationService);
+ const pagination = { offset: 0, limit: 100 };
+ const items = [
+ { a: undefined, b: 200 },
+ { a: undefined, b: 101 },
+ { a: true, b: 100 },
+ ];
+ const sorting = [
+ { property: "a", direction: "asc" as const },
+ { property: "b", direction: "asc" as const },
+ ];
+ const resultsPage = await paginationService.getPage(pagination, sorting, items);
+
+ expect(resultsPage).toEqual({
+ results: [
+ { a: true, b: 100 },
+ { a: undefined, b: 101 },
+ { a: undefined, b: 200 },
+ ],
+ totalCount: 3,
+ meta: { totalCountIsEstimate: false },
+ });
+ });
+
+ it("should sort by Utils.BigNumber property", async () => {
const paginationService = container.resolve(PaginationService);
const pagination = { offset: 0, limit: 100 };
const items = [{ v: Utils.BigNumber.make(1) }, { v: Utils.BigNumber.make(3) }, { v: Utils.BigNumber.make(2) }];
const sorting = [{ property: "v", direction: "asc" as const }];
- const resultsPage = paginationService.getPage(pagination, sorting, items);
+ const resultsPage = await paginationService.getPage(pagination, sorting, items);
expect(resultsPage).toEqual({
results: [{ v: Utils.BigNumber.make(1) }, { v: Utils.BigNumber.make(2) }, { v: Utils.BigNumber.make(3) }],
@@ -227,23 +291,25 @@ describe("PaginationService.getPage", () => {
});
});
- it("should throw when sorting over property with union type", () => {
+ it("should throw when sorting over property with union type", async () => {
const paginationService = container.resolve(PaginationService);
const pagination = { offset: 0, limit: 100 };
const items: { v: number | string }[] = [{ v: 1 }, { v: "2" }];
const sorting = [{ property: "v", direction: "asc" as const }];
- expect(() => paginationService.getPage(pagination, sorting, items)).toThrowError(
+ await expect(() => paginationService.getPage(pagination, sorting, items)).rejects.toThrowError(
"Mismatched types 'string' and 'number' at 'v'",
);
});
- it("should throw when sorting over property with invalid type", () => {
+ it("should throw when sorting over property with invalid type", async () => {
const paginationService = container.resolve(PaginationService);
const pagination = { offset: 0, limit: 100 };
const items = [{ v: Symbol() }, { v: Symbol() }];
const sorting = [{ property: "v", direction: "asc" as const }];
- expect(() => paginationService.getPage(pagination, sorting, items)).toThrowError("Unexpected type at 'v'");
+ await expect(() => paginationService.getPage(pagination, sorting, items)).rejects.toThrowError(
+ "Unexpected type at 'v'",
+ );
});
});
diff --git a/__tests__/unit/core-kernel/utils/lock.test.ts b/__tests__/unit/core-kernel/utils/lock.test.ts
index 29b369df8c..a8d365954b 100644
--- a/__tests__/unit/core-kernel/utils/lock.test.ts
+++ b/__tests__/unit/core-kernel/utils/lock.test.ts
@@ -3,7 +3,7 @@ import { Lock } from "../../../../packages/core-kernel/src/utils/lock";
describe("Lock", () => {
it("should run exclusive executions in series", async () => {
let resolve: () => void;
- const promise = new Promise((r) => (resolve = r));
+ const promise = new Promise((r) => (resolve = r));
let executions = 0;
const fn = async () => {
@@ -21,7 +21,7 @@ describe("Lock", () => {
it("should run non-exclusive executions in parallel", async () => {
let resolve: () => void;
- const promise = new Promise((r) => (resolve = r));
+ const promise = new Promise((r) => (resolve = r));
let executions = 0;
const fn = async () => {
@@ -39,7 +39,7 @@ describe("Lock", () => {
it("should run exclusive execution after non-exclusive had finished", async () => {
let resolve: () => void;
- const promise = new Promise((r) => (resolve = r));
+ const promise = new Promise((r) => (resolve = r));
let executions = 0;
const fn = async () => {
@@ -57,7 +57,7 @@ describe("Lock", () => {
it("should run non-exclusive execution after exclusive had finished", async () => {
let resolve: () => void;
- const promise = new Promise((r) => (resolve = r));
+ const promise = new Promise((r) => (resolve = r));
let executions = 0;
const fn = async () => {
diff --git a/__tests__/unit/core-logger-pino/driver.test.ts b/__tests__/unit/core-logger-pino/driver.test.ts
index 573dc725d0..690947e195 100644
--- a/__tests__/unit/core-logger-pino/driver.test.ts
+++ b/__tests__/unit/core-logger-pino/driver.test.ts
@@ -157,6 +157,8 @@ describe("Logger", () => {
await sleep(100);
expect(message).toMatch("File stream closed due to an error: Error: Test error");
+
+ await expect(logger.dispose()).toResolve();
});
it("should rotate the log 3 times", async () => {
@@ -188,4 +190,38 @@ describe("Logger", () => {
expect(files.filter((file) => file.endsWith(".log.gz"))).toHaveLength(3);
expect(files).toHaveLength(5);
});
+
+ describe("make", () => {
+ it("should create a file stream if level is valid", () => {
+ // @ts-ignore
+ expect(logger.combinedFileStream).toBeDefined();
+ });
+
+ it("should not create a file stream if level not is valid", async () => {
+ const logger = await app.resolve(PinoLogger).make({
+ levels: {
+ console: process.env.CORE_LOG_LEVEL || "debug",
+ file: "invalid",
+ },
+ fileRotator: {
+ interval: "1d",
+ },
+ });
+
+ // @ts-ignore
+ expect(logger.combinedFileStream).not.toBeDefined();
+ });
+ });
+
+ describe("dispose", () => {
+ it("should dispose before make", async () => {
+ const logger = await app.resolve(PinoLogger);
+
+ await expect(logger.dispose()).toResolve();
+ });
+
+ it("should dispose after make", async () => {
+ await expect(logger.dispose()).toResolve();
+ });
+ });
});
diff --git a/__tests__/unit/core-logger-pino/service-provider.test.ts b/__tests__/unit/core-logger-pino/service-provider.test.ts
index ea1fc029f8..16d4a93713 100644
--- a/__tests__/unit/core-logger-pino/service-provider.test.ts
+++ b/__tests__/unit/core-logger-pino/service-provider.test.ts
@@ -5,6 +5,8 @@ import { ServiceProvider } from "@packages/core-logger-pino/src";
import { defaults } from "@packages/core-logger-pino/src/defaults";
import { AnySchema } from "joi";
import { dirSync } from "tmp";
+import { Identifiers, interfaces } from "@arkecosystem/core-kernel/dist/ioc";
+import { LogManager } from "@arkecosystem/core-kernel/dist/services/log";
let app: Application;
@@ -37,6 +39,23 @@ describe("ServiceProvider", () => {
});
it("should be disposable", async () => {
+ app.bind(Container.Identifiers.LogManager)
+ .to(Services.Log.LogManager)
+ .inSingletonScope();
+
+ await app.get(Container.Identifiers.LogManager).boot();
+
+ serviceProvider.setConfig(app.resolve(Providers.PluginConfiguration).merge(defaults));
+
+ app.bind(Container.Identifiers.ApplicationNamespace).toConstantValue("token-network");
+ app.bind("path.log").toConstantValue(dirSync().name);
+
+ app.bind(Identifiers.LogService).toDynamicValue((context: interfaces.Context) =>
+ context.container.get(Identifiers.LogManager).driver(),
+ );
+
+ await expect(serviceProvider.register()).toResolve();
+
await expect(serviceProvider.dispose()).toResolve();
});
diff --git a/__tests__/unit/core-magistrate-api/controllers/entities.test.ts b/__tests__/unit/core-magistrate-api/controllers/entities.test.ts
index 543e359b09..c89b0e02c5 100644
--- a/__tests__/unit/core-magistrate-api/controllers/entities.test.ts
+++ b/__tests__/unit/core-magistrate-api/controllers/entities.test.ts
@@ -39,17 +39,17 @@ const entityResource1 = {
};
describe("EntityController.index", () => {
- it("should get criteria from query and return page from EntitySearchService", () => {
+ it("should get criteria from query and return page from EntitySearchService", async () => {
const entitiesPage: Contracts.Search.ResultsPage = {
results: [entityResource1],
totalCount: 1,
meta: { totalCountIsEstimate: false },
};
- entitySearchService.getEntitiesPage.mockReturnValueOnce(entitiesPage);
+ entitySearchService.getEntitiesPage.mockResolvedValueOnce(entitiesPage);
const entityController = container.resolve(EntityController);
- const result = entityController.index({
+ const result = await entityController.index({
query: {
type: Enums.EntityType.Business,
page: 1,
diff --git a/__tests__/unit/core-magistrate-crypto/fixtures/entity/schemas/register.ts b/__tests__/unit/core-magistrate-crypto/fixtures/entity/schemas/register.ts
index 83aaa0f6c2..6742654f98 100644
--- a/__tests__/unit/core-magistrate-crypto/fixtures/entity/schemas/register.ts
+++ b/__tests__/unit/core-magistrate-crypto/fixtures/entity/schemas/register.ts
@@ -1,4 +1,5 @@
-import { Enums, Interfaces } from "@arkecosystem/core-magistrate-crypto/src";
+import { Enums, Interfaces } from "@packages/core-magistrate-crypto/src";
+
import { invalidAssetData, validAssetData } from "./utils";
export const validRegisters: Interfaces.IEntityAsset[] = [
@@ -10,7 +11,7 @@ export const validRegisters: Interfaces.IEntityAsset[] = [
name: "my_plugin_for_desktop_wallet",
},
},
- ...validAssetData.map(data => ({
+ ...validAssetData.map((data) => ({
type: Enums.EntityType.Module,
subType: 0,
action: Enums.EntityAction.Register,
@@ -29,32 +30,44 @@ export const invalidRegisters: Interfaces.IEntityAsset[] = [
data: {}, // should have at least a name
},
{
- type: 256, // max 255
+ type: Enums.EntityType.Plugin,
subType: 1,
action: Enums.EntityAction.Register,
data: {
- name: "validname",
+ name: "my plugin for desktop wallet that has a name too loong", // name max 40 characters
},
},
{
- type: -1, // min 0
+ type: Enums.EntityType.Plugin,
subType: 1,
action: Enums.EntityAction.Register,
data: {
- name: "validname",
+ name: "invalid \u0000 char",
},
},
+ ...invalidAssetData.map((data) => ({
+ type: Enums.EntityType.Module,
+ subType: 0,
+ action: Enums.EntityAction.Update,
+ data: {
+ name: "my_module",
+ ...data,
+ },
+ })),
+];
+
+export const unserializableRegisters: Interfaces.IEntityAsset[] = [
{
- type: Enums.EntityType.Plugin,
- subType: 256, // max 255
+ type: 256, // max 255
+ subType: 1,
action: Enums.EntityAction.Register,
data: {
name: "validname",
},
},
{
- type: Enums.EntityType.Plugin,
- subType: -1, // min 0
+ type: -1, // min 0
+ subType: 1,
action: Enums.EntityAction.Register,
data: {
name: "validname",
@@ -62,27 +75,18 @@ export const invalidRegisters: Interfaces.IEntityAsset[] = [
},
{
type: Enums.EntityType.Plugin,
- subType: 1,
+ subType: 256, // max 255
action: Enums.EntityAction.Register,
data: {
- name: "my plugin for desktop wallet that has a name too loong", // name max 40 characters
+ name: "validname",
},
},
{
type: Enums.EntityType.Plugin,
- subType: 1,
+ subType: -1, // min 0
action: Enums.EntityAction.Register,
data: {
- name: "invalid \u0000 char",
+ name: "validname",
},
},
- ...invalidAssetData.map(data => ({
- type: Enums.EntityType.Module,
- subType: 0,
- action: Enums.EntityAction.Update,
- data: {
- name: "my_module",
- ...data,
- },
- })),
];
diff --git a/__tests__/unit/core-magistrate-crypto/fixtures/entity/schemas/resign.ts b/__tests__/unit/core-magistrate-crypto/fixtures/entity/schemas/resign.ts
index f87d11a9ef..1bba9fdc66 100644
--- a/__tests__/unit/core-magistrate-crypto/fixtures/entity/schemas/resign.ts
+++ b/__tests__/unit/core-magistrate-crypto/fixtures/entity/schemas/resign.ts
@@ -1,4 +1,5 @@
-import { Enums, Interfaces } from "@arkecosystem/core-magistrate-crypto/src";
+import { Enums, Interfaces } from "@packages/core-magistrate-crypto/src";
+
import { invalidAssetData, validAssetData } from "./utils";
export const validResigns: Interfaces.IEntityAsset[] = [
@@ -28,6 +29,22 @@ export const invalidResigns: Interfaces.IEntityAsset[] = [
name: "why a name", // no property allowed in data for resign
},
},
+ ...invalidAssetData.map((data) => ({
+ type: Enums.EntityType.Module,
+ subType: 0,
+ action: Enums.EntityAction.Update,
+ data,
+ })),
+ ...validAssetData.map((data) => ({
+ // even "valid" data are invalid for resign as we allow no property in data
+ type: Enums.EntityType.Module,
+ subType: 0,
+ action: Enums.EntityAction.Update,
+ data,
+ })),
+];
+
+export const unserializableResigns: Interfaces.IEntityAsset[] = [
{
type: Enums.EntityType.Plugin,
subType: 256, // max 255
@@ -56,17 +73,4 @@ export const invalidResigns: Interfaces.IEntityAsset[] = [
registrationId: "e77a1d1d080ebce113dd27e1cb0a242ec8600fb72cd62ace4e46148bee1d3acc",
data: {},
},
- ...invalidAssetData.map(data => ({
- type: Enums.EntityType.Module,
- subType: 0,
- action: Enums.EntityAction.Update,
- data,
- })),
- ...validAssetData.map(data => ({
- // even "valid" data are invalid for resign as we allow no property in data
- type: Enums.EntityType.Module,
- subType: 0,
- action: Enums.EntityAction.Update,
- data,
- })),
];
diff --git a/__tests__/unit/core-magistrate-crypto/fixtures/entity/schemas/update.ts b/__tests__/unit/core-magistrate-crypto/fixtures/entity/schemas/update.ts
index 636ea66bf9..ec93a0617d 100644
--- a/__tests__/unit/core-magistrate-crypto/fixtures/entity/schemas/update.ts
+++ b/__tests__/unit/core-magistrate-crypto/fixtures/entity/schemas/update.ts
@@ -1,4 +1,5 @@
-import { Enums, Interfaces } from "@arkecosystem/core-magistrate-crypto/src";
+import { Enums, Interfaces } from "@packages/core-magistrate-crypto/src";
+
import { invalidAssetData, validAssetData } from "./utils";
export const validUpdates: Interfaces.IEntityAsset[] = [
@@ -11,7 +12,7 @@ export const validUpdates: Interfaces.IEntityAsset[] = [
ipfsData: "Qmbw6QmF6tuZpyV6WyEsTmExkEG3rW4khattQidPfbpmNZ",
},
},
- ...validAssetData.map(data => ({
+ ...validAssetData.map((data) => ({
type: Enums.EntityType.Module,
subType: 0,
action: Enums.EntityAction.Update,
@@ -29,6 +30,15 @@ export const invalidUpdates: Interfaces.IEntityAsset[] = [
name: "name cannot be updated", // name is the only prop that cannot be updated
},
},
+ ...invalidAssetData.map((data) => ({
+ type: Enums.EntityType.Module,
+ subType: 0,
+ action: Enums.EntityAction.Update,
+ data,
+ })),
+];
+
+export const unserializableUpdates: Interfaces.IEntityAsset[] = [
{
type: Enums.EntityType.Plugin,
subType: 256, // max 255
@@ -65,10 +75,4 @@ export const invalidUpdates: Interfaces.IEntityAsset[] = [
ipfsData: "Qmbw6QmF6tuZpyV6WyEsTmExkEG3rW4khattQidPfbpmNZ",
},
},
- ...invalidAssetData.map(data => ({
- type: Enums.EntityType.Module,
- subType: 0,
- action: Enums.EntityAction.Update,
- data,
- })),
];
diff --git a/__tests__/unit/core-magistrate-crypto/transactions/bridgechain-registration.test.ts b/__tests__/unit/core-magistrate-crypto/transactions/bridgechain-registration.test.ts
index 3cae29f9cc..3812e9a5f1 100644
--- a/__tests__/unit/core-magistrate-crypto/transactions/bridgechain-registration.test.ts
+++ b/__tests__/unit/core-magistrate-crypto/transactions/bridgechain-registration.test.ts
@@ -26,9 +26,8 @@ describe("Bridgechain registration transaction", () => {
.sign("passphrase")
.getStruct();
- const serialized = Transactions.TransactionFactory.fromData(bridgechainRegistration).serialized.toString(
- "hex",
- );
+ const serialized =
+ Transactions.TransactionFactory.fromData(bridgechainRegistration).serialized.toString("hex");
const deserialized = Transactions.Deserializer.deserialize(serialized);
checkCommonFields(deserialized, bridgechainRegistration);
@@ -45,9 +44,8 @@ describe("Bridgechain registration transaction", () => {
.sign("passphrase")
.getStruct();
- const serialized = Transactions.TransactionFactory.fromData(bridgechainRegistration).serialized.toString(
- "hex",
- );
+ const serialized =
+ Transactions.TransactionFactory.fromData(bridgechainRegistration).serialized.toString("hex");
const deserialized = Transactions.Deserializer.deserialize(serialized);
checkCommonFields(deserialized, bridgechainRegistration);
@@ -133,7 +131,7 @@ describe("Bridgechain registration transaction", () => {
});
describe("should test edge cases of bridgechain genesisHash", () => {
- it("should fail because genesisHash is to short (63chars) ", () => {
+ it("should fail because genesisHash is to short (63chars)", () => {
const bridgechainRegistration = builder
.bridgechainRegistrationAsset({
name: "google",
@@ -148,7 +146,7 @@ describe("Bridgechain registration transaction", () => {
expect(error).not.toBeUndefined();
});
- it("should fail because genesisHash is to long (65chars) ", () => {
+ it("should fail because genesisHash is to long (65chars)", () => {
const bridgechainRegistration = builder
.bridgechainRegistrationAsset({
name: "google",
diff --git a/__tests__/unit/core-magistrate-crypto/transactions/bridgechain-update.test.ts b/__tests__/unit/core-magistrate-crypto/transactions/bridgechain-update.test.ts
index 287758c256..80e20f7571 100644
--- a/__tests__/unit/core-magistrate-crypto/transactions/bridgechain-update.test.ts
+++ b/__tests__/unit/core-magistrate-crypto/transactions/bridgechain-update.test.ts
@@ -1,10 +1,8 @@
import "jest-extended";
-import ByteBuffer from "bytebuffer";
-
import { BridgechainUpdateBuilder } from "@packages/core-magistrate-crypto/src/builders";
-import { Managers, Transactions, Validation } from "@packages/crypto";
import { BridgechainUpdateTransaction } from "@packages/core-magistrate-crypto/src/transactions";
+import { Managers, Transactions, Validation } from "@packages/crypto";
import { bridgechainUpdateAsset1, checkCommonFields } from "../helper";
@@ -28,7 +26,7 @@ describe("Bridgechain update ser/deser", () => {
.bridgechainUpdateAsset({
bridgechainId: genesisHash,
seedNodes: ["74.125.224.72"],
- ports: {"@arkecosystem/core-api": 12345},
+ ports: { "@arkecosystem/core-api": 12345 },
bridgechainRepository: "http://www.repository.com/myorg/myrepo",
bridgechainAssetRepository: "http://www.repository.com/myorg/myassetrepo",
})
@@ -47,7 +45,7 @@ describe("Bridgechain update ser/deser", () => {
.bridgechainUpdateAsset({
bridgechainId: genesisHash,
seedNodes: ["74.125.224.72"],
- ports: {"@arkecosystem/core-api": 12345},
+ ports: { "@arkecosystem/core-api": 12345 },
bridgechainRepository: "http://www.repository.com/myorg/myrepo",
bridgechainAssetRepository: "http://www.repository.com/myorg/myassetrepo",
})
@@ -56,14 +54,13 @@ describe("Bridgechain update ser/deser", () => {
const transaction = Transactions.TransactionFactory.fromData(businessResignation);
-
transaction.data.asset!.seedNodes = [];
transaction.data.asset!.ports = {};
transaction.data.asset!.bridgechainRepository = "";
transaction.data.asset!.bridgechainAssetRepository = "";
- const serialized = ByteBuffer.fromHex(transaction.serialize().toString("hex"));
-
+ const serialized = transaction.serialize()!;
+ serialized.reset();
transaction.deserialize(serialized);
});
@@ -73,7 +70,7 @@ describe("Bridgechain update ser/deser", () => {
.bridgechainUpdateAsset({
bridgechainId: genesisHash,
seedNodes: ["74.125.224.72"],
- ports: {"@arkecosystem/core-api": 12345},
+ ports: { "@arkecosystem/core-api": 12345 },
bridgechainRepository: "http://www.repository.com/myorg/myrepo",
bridgechainAssetRepository: "http://www.repository.com/myorg/myassetrepo",
})
diff --git a/__tests__/unit/core-magistrate-crypto/transactions/business-registration.test.ts b/__tests__/unit/core-magistrate-crypto/transactions/business-registration.test.ts
index 60639852b5..70b439e14d 100644
--- a/__tests__/unit/core-magistrate-crypto/transactions/business-registration.test.ts
+++ b/__tests__/unit/core-magistrate-crypto/transactions/business-registration.test.ts
@@ -32,9 +32,8 @@ describe("Business registration transaction", () => {
.sign("passphrase")
.getStruct();
- const serialized = Transactions.TransactionFactory.fromData(businessRegistration).serialized.toString(
- "hex",
- );
+ const serialized =
+ Transactions.TransactionFactory.fromData(businessRegistration).serialized.toString("hex");
const deserialized = Transactions.Deserializer.deserialize(serialized);
checkCommonFields(deserialized, businessRegistration);
@@ -48,9 +47,8 @@ describe("Business registration transaction", () => {
.sign("passphrase")
.getStruct();
- const serialized = Transactions.TransactionFactory.fromData(businessRegistration).serialized.toString(
- "hex",
- );
+ const serialized =
+ Transactions.TransactionFactory.fromData(businessRegistration).serialized.toString("hex");
const deserialized = Transactions.Deserializer.deserialize(serialized);
checkCommonFields(deserialized, businessRegistration);
@@ -64,9 +62,8 @@ describe("Business registration transaction", () => {
.sign("passphrase")
.getStruct();
- const serialized = Transactions.TransactionFactory.fromData(businessRegistration).serialized.toString(
- "hex",
- );
+ const serialized =
+ Transactions.TransactionFactory.fromData(businessRegistration).serialized.toString("hex");
const deserialized = Transactions.Deserializer.deserialize(serialized);
checkCommonFields(deserialized, businessRegistration);
@@ -80,9 +77,8 @@ describe("Business registration transaction", () => {
.sign("passphrase")
.getStruct();
- const serialized = Transactions.TransactionFactory.fromData(businessRegistration).serialized.toString(
- "hex",
- );
+ const serialized =
+ Transactions.TransactionFactory.fromData(businessRegistration).serialized.toString("hex");
const deserialized = Transactions.Deserializer.deserialize(serialized);
checkCommonFields(deserialized, businessRegistration);
@@ -115,7 +111,7 @@ describe("Business registration transaction", () => {
transactionSchema = BusinessRegistrationTransaction.getSchema();
});
- it("should not throw any error ", () => {
+ it("should not throw any error", () => {
const businessRegistration = builder
.businessRegistrationAsset(businessRegistrationAsset1)
.network(23)
@@ -124,7 +120,7 @@ describe("Business registration transaction", () => {
const { error } = Ajv.validator.validate(transactionSchema, businessRegistration.getStruct());
expect(error).toBeUndefined();
});
- it("should not throw any error ", () => {
+ it("should not throw any error", () => {
const businessRegistration = builder
.businessRegistrationAsset(businessRegistrationAsset2)
.network(23)
@@ -133,7 +129,7 @@ describe("Business registration transaction", () => {
const { error } = Ajv.validator.validate(transactionSchema, businessRegistration.getStruct());
expect(error).toBeUndefined();
});
- it("should not throw any error ", () => {
+ it("should not throw any error", () => {
const businessRegistration = builder
.businessRegistrationAsset(businessRegistrationAsset3)
.network(23)
@@ -142,7 +138,7 @@ describe("Business registration transaction", () => {
const { error } = Ajv.validator.validate(transactionSchema, businessRegistration.getStruct());
expect(error).toBeUndefined();
});
- it("should not throw any error ", () => {
+ it("should not throw any error", () => {
const businessRegistration = builder
.businessRegistrationAsset(businessRegistrationAsset4)
.network(23)
diff --git a/__tests__/unit/core-magistrate-crypto/transactions/business-resgination.test.ts b/__tests__/unit/core-magistrate-crypto/transactions/business-resgination.test.ts
index 2834927bc6..2ee13e408f 100644
--- a/__tests__/unit/core-magistrate-crypto/transactions/business-resgination.test.ts
+++ b/__tests__/unit/core-magistrate-crypto/transactions/business-resgination.test.ts
@@ -17,6 +17,7 @@ describe("Business resignation ser/deser", () => {
beforeEach(() => {
builder = new BusinessResignationBuilder();
});
+
it("should ser/deserialize giving back original fields", () => {
const businessResignation = builder.network(23).version(2).sign("passphrase").getStruct();
diff --git a/__tests__/unit/core-magistrate-crypto/transactions/business-update.test.ts b/__tests__/unit/core-magistrate-crypto/transactions/business-update.test.ts
index a80490c285..9edbe8ee71 100644
--- a/__tests__/unit/core-magistrate-crypto/transactions/business-update.test.ts
+++ b/__tests__/unit/core-magistrate-crypto/transactions/business-update.test.ts
@@ -99,7 +99,7 @@ describe("Business update transaction", () => {
transactionSchema = BusinessUpdateTransaction.getSchema();
});
- it("should not throw any error ", () => {
+ it("should not throw any error", () => {
const businessUpdateBuilder = builder
.businessUpdateAsset(businessUpdateAsset1)
.network(23)
@@ -109,7 +109,7 @@ describe("Business update transaction", () => {
expect(error).toBeUndefined();
});
- it("should not throw any error ", () => {
+ it("should not throw any error", () => {
const businessUpdateBuilder = builder
.businessUpdateAsset(businessUpdateAsset2)
.network(23)
@@ -119,7 +119,7 @@ describe("Business update transaction", () => {
expect(error).toBeUndefined();
});
- it("should not throw any error ", () => {
+ it("should not throw any error", () => {
const businessUpdateBuilder = builder
.businessUpdateAsset(businessUpdateAsset3)
.network(23)
diff --git a/__tests__/unit/core-magistrate-crypto/transactions/entity.test.ts b/__tests__/unit/core-magistrate-crypto/transactions/entity.test.ts
index b1cd15120b..077d2c28e2 100644
--- a/__tests__/unit/core-magistrate-crypto/transactions/entity.test.ts
+++ b/__tests__/unit/core-magistrate-crypto/transactions/entity.test.ts
@@ -6,9 +6,9 @@ import { EntityTransaction } from "@arkecosystem/core-magistrate-crypto/src/tran
import { Managers, Transactions, Validation } from "@arkecosystem/crypto";
import { generateAssets, generateSpecialAssets } from "../fixtures/entity/assets/generate";
-import { invalidRegisters, validRegisters } from "../fixtures/entity/schemas/register";
-import { invalidResigns, validResigns } from "../fixtures/entity/schemas/resign";
-import { invalidUpdates, validUpdates } from "../fixtures/entity/schemas/update";
+import { invalidRegisters, unserializableRegisters, validRegisters } from "../fixtures/entity/schemas/register";
+import { invalidResigns, unserializableResigns, validResigns } from "../fixtures/entity/schemas/resign";
+import { invalidUpdates, unserializableUpdates, validUpdates } from "../fixtures/entity/schemas/update";
import { checkCommonFields } from "../helper";
let builder: EntityBuilder;
@@ -61,6 +61,14 @@ describe("Entity transaction", () => {
mockVerifySchema.mockRestore();
},
);
+
+ it.each(
+ [...unserializableRegisters, ...unserializableResigns, ...unserializableUpdates].map((asset) => [asset]),
+ )("should throw error", (asset: Interfaces.IEntityAsset) => {
+ expect(() => {
+ builder.asset(asset).sign("passphrase");
+ }).toThrowError();
+ });
});
describe("Schema tests", () => {
@@ -80,16 +88,16 @@ describe("Entity transaction", () => {
entityRegistration.getStruct(),
);
expect(error).toBeUndefined();
- expect(value.asset).toEqual(asset);
+ expect(value!.asset).toEqual(asset);
},
);
it.each([...invalidRegisters, ...invalidResigns, ...invalidUpdates].map((asset) => [asset]))(
"should give validation error",
(asset: Interfaces.IEntityAsset) => {
- const entityRegistration = builder.asset(asset).sign("passphrase");
+ const entity = builder.asset(asset).sign("passphrase");
- const { error } = Validation.validator.validate(transactionSchema, entityRegistration.getStruct());
+ const { error } = Validation.validator.validate(transactionSchema, entity.getStruct());
expect(error).not.toBeUndefined();
},
);
diff --git a/__tests__/unit/core-magistrate-transactions/__support__/app.ts b/__tests__/unit/core-magistrate-transactions/__support__/app.ts
index 5861c55062..8e33600cef 100644
--- a/__tests__/unit/core-magistrate-transactions/__support__/app.ts
+++ b/__tests__/unit/core-magistrate-transactions/__support__/app.ts
@@ -27,6 +27,8 @@ import {
RevertTransactionAction,
ThrowIfCannotEnterPoolAction,
VerifyTransactionAction,
+ OnPoolEnterAction,
+ OnPoolLeaveAction,
} from "@packages/core-transaction-pool/src/actions";
import { DynamicFeeMatcher } from "@packages/core-transaction-pool/src/dynamic-fee-matcher";
import { ExpirationService } from "@packages/core-transaction-pool/src/expiration-service";
@@ -40,6 +42,11 @@ import { TransactionHandlerRegistry } from "@packages/core-transactions/src/hand
import { Identities, Utils } from "@packages/crypto";
import { IMultiSignatureAsset } from "@packages/crypto/src/interfaces";
import { ServiceProvider } from "@packages/core-transactions/src/service-provider";
+import {
+ SecondSignatureVerificationMemoized,
+ MultiSignatureVerificationMemoized,
+} from "@packages/core-transactions/src/verification";
+import { MempoolIndexRegistry } from "@packages/core-transaction-pool/src/mempool-index-registry";
const logger = {
notice: jest.fn(),
@@ -132,6 +139,7 @@ export const initApp = (): Application => {
"maxTransactionsPerSender",
300,
);
+ app.get(Container.Identifiers.PluginConfiguration).set("memoizerCacheSize", 20000);
app.bind(Container.Identifiers.StateStore).to(StateStore).inTransientScope();
@@ -143,6 +151,7 @@ export const initApp = (): Application => {
app.bind(Container.Identifiers.TransactionPoolDynamicFeeMatcher).to(DynamicFeeMatcher);
app.bind(Container.Identifiers.TransactionPoolExpirationService).to(ExpirationService);
+ app.bind(Container.Identifiers.TransactionPoolMempoolIndexRegistry).to(MempoolIndexRegistry).inSingletonScope();
app.bind(Container.Identifiers.TransactionPoolSenderMempool).to(SenderMempool);
app.bind(Container.Identifiers.TransactionPoolSenderMempoolFactory).toAutoFactory(
Container.Identifiers.TransactionPoolSenderMempool,
@@ -157,6 +166,14 @@ export const initApp = (): Application => {
app.bind(Identifiers.DatabaseTransactionRepository).toConstantValue(Mocks.TransactionRepository.instance);
+ app.bind(Identifiers.TransactionSecondSignatureVerification)
+ .to(SecondSignatureVerificationMemoized)
+ .inSingletonScope();
+
+ app.bind(Identifiers.TransactionMultiSignatureVerification)
+ .to(MultiSignatureVerificationMemoized)
+ .inSingletonScope();
+
app.bind(Identifiers.TransactionHandler).to(One.TransferTransactionHandler);
app.bind(Identifiers.TransactionHandler).to(Two.TransferTransactionHandler);
app.bind(Identifiers.TransactionHandler).to(One.SecondSignatureRegistrationTransactionHandler);
@@ -202,6 +219,16 @@ export const initApp = (): Application => {
new RevertTransactionAction(),
);
+ app.get(Container.Identifiers.TriggerService).bind(
+ "onPoolEnter",
+ new OnPoolEnterAction(),
+ );
+
+ app.get(Container.Identifiers.TriggerService).bind(
+ "onPoolLeave",
+ new OnPoolLeaveAction(),
+ );
+
return app;
};
diff --git a/__tests__/unit/core-magistrate-transactions/handlers/entity.test.ts b/__tests__/unit/core-magistrate-transactions/handlers/entity.test.ts
index 8cfe420ea6..16624f5c2a 100644
--- a/__tests__/unit/core-magistrate-transactions/handlers/entity.test.ts
+++ b/__tests__/unit/core-magistrate-transactions/handlers/entity.test.ts
@@ -1,11 +1,12 @@
import "jest-extended";
-import { Container, Utils } from "@packages/core-kernel";
+import { Container, Contracts, Utils } from "@packages/core-kernel";
import { PoolError } from "@packages/core-kernel/dist/contracts/transaction-pool";
import { Enums } from "@packages/core-magistrate-crypto";
import { EntityBuilder } from "@packages/core-magistrate-crypto/src/builders";
import { EntityAction, EntityType } from "@packages/core-magistrate-crypto/src/enums";
import { EntityTransaction } from "@packages/core-magistrate-crypto/src/transactions";
+import { MempoolIndexes } from "@packages/core-magistrate-transactions/src/enums";
import {
EntityAlreadyRegisteredError,
EntityAlreadyResignedError,
@@ -18,9 +19,9 @@ import {
StaticFeeMismatchError,
} from "@packages/core-magistrate-transactions/src/errors";
import { EntityTransactionHandler } from "@packages/core-magistrate-transactions/src/handlers/entity";
+import { MempoolIndexRegistry } from "@packages/core-transaction-pool/src/mempool-index-registry";
import { Utils as CryptoUtils } from "@packages/crypto";
import { Managers, Transactions } from "@packages/crypto";
-import { cloneDeep } from "lodash";
import { validRegisters, validResigns, validUpdates } from "./__fixtures__/entity";
import { walletRepository } from "./__mocks__/wallet-repository";
@@ -67,6 +68,11 @@ describe("Entity handler", () => {
container.unbindAll();
container.bind(Container.Identifiers.TransactionHistoryService).toConstantValue(transactionHistoryService);
container.bind(Container.Identifiers.TransactionPoolQuery).toConstantValue(poolQuery);
+ container.bind(Container.Identifiers.TransactionPoolMempoolIndex).toConstantValue(MempoolIndexes.EntityName);
+ container
+ .bind(Container.Identifiers.TransactionPoolMempoolIndexRegistry)
+ .to(MempoolIndexRegistry)
+ .inSingletonScope();
});
let wallet, walletAttributes;
@@ -196,39 +202,191 @@ describe("Entity handler", () => {
});
it("should resolve", async () => {
+ const mempoolIndexRegistry = container.get(
+ Container.Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+ const spyOnIndexHas = jest.spyOn(mempoolIndexRegistry.get(MempoolIndexes.EntityName), "has");
+
await expect(entityHandler.throwIfCannotEnterPool(transaction)).toResolve();
- expect(poolQuery.getAll).toHaveBeenCalled();
+
+ expect(spyOnIndexHas).toHaveBeenCalledTimes(1);
+ expect(spyOnIndexHas).toHaveBeenCalledWith(transaction.data.asset.data.name.toLowerCase());
});
it("should resolve if transaction action is update or resign", async () => {
+ const mempoolIndexRegistry = container.get(
+ Container.Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+ const spyOnIndexHas = jest.spyOn(mempoolIndexRegistry.get(MempoolIndexes.EntityName), "has");
+
transaction.data.asset!.action = Enums.EntityAction.Update;
await expect(entityHandler.throwIfCannotEnterPool(transaction)).toResolve();
- expect(poolQuery.getAll).not.toHaveBeenCalled();
+ expect(spyOnIndexHas).not.toHaveBeenCalled();
transaction.data.asset!.action = Enums.EntityAction.Resign;
await expect(entityHandler.throwIfCannotEnterPool(transaction)).toResolve();
- expect(poolQuery.getAll).not.toHaveBeenCalled();
+ expect(spyOnIndexHas).not.toHaveBeenCalled();
});
it("should throw if transaction with same type and name is already in the pool", async () => {
- poolQuery.has.mockReturnValue(true);
+ const mempoolIndexRegistry = container.get(
+ Container.Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+ const spyOnIndexHas = jest
+ .spyOn(mempoolIndexRegistry.get(MempoolIndexes.EntityName), "has")
+ .mockReturnValue(true);
await expect(entityHandler.throwIfCannotEnterPool(transaction)).rejects.toBeInstanceOf(PoolError);
- expect(poolQuery.wherePredicate).toHaveBeenCalledTimes(2);
+ expect(spyOnIndexHas).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe("onPoolEnter", () => {
+ let transaction: EntityTransaction;
+ let entityHandler: EntityTransactionHandler;
+
+ beforeEach(() => {
+ const builder = new EntityBuilder();
+ transaction = builder.asset(validRegisters[0]).sign("passphrase").build() as EntityTransaction;
+
+ entityHandler = container.resolve(EntityTransactionHandler);
+ });
+
+ it("should set name on EntityName index, when action is Register", () => {
+ const mempoolIndexRegistry = container.get(
+ Container.Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+ const spyOnIndexSet = jest.spyOn(mempoolIndexRegistry.get(MempoolIndexes.EntityName), "set");
+
+ expect(entityHandler.onPoolEnter(transaction)).toResolve();
+
+ expect(spyOnIndexSet).toBeCalledTimes(1);
+ expect(spyOnIndexSet).toBeCalledWith(transaction.data.asset.data.name.toLowerCase(), transaction);
+ });
+
+ it("should not set name on EntityName index, when action is not Register", () => {
+ const mempoolIndexRegistry = container.get(
+ Container.Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+ const spyOnIndexSet = jest.spyOn(mempoolIndexRegistry.get(MempoolIndexes.EntityName), "set");
+
+ transaction.data.asset.action = Enums.EntityAction.Update;
+ expect(entityHandler.onPoolEnter(transaction)).toResolve();
+ expect(spyOnIndexSet).not.toBeCalled();
+
+ transaction.data.asset.action = Enums.EntityAction.Resign;
+ expect(entityHandler.onPoolEnter(transaction)).toResolve();
+ expect(spyOnIndexSet).not.toBeCalled();
+ });
+ });
+
+ describe("onPoolLeave", () => {
+ let transaction: EntityTransaction;
+ let entityHandler: EntityTransactionHandler;
+
+ beforeEach(() => {
+ const builder = new EntityBuilder();
+ transaction = builder.asset(validRegisters[0]).sign("passphrase").build() as EntityTransaction;
+
+ entityHandler = container.resolve(EntityTransactionHandler);
+ });
+
+ it("should forget name on EntityName index, when action is Register", () => {
+ const mempoolIndexRegistry = container.get(
+ Container.Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+ const spyOnIndexSet = jest.spyOn(mempoolIndexRegistry.get(MempoolIndexes.EntityName), "forget");
+
+ expect(entityHandler.onPoolLeave(transaction)).toResolve();
+ expect(spyOnIndexSet).toBeCalledTimes(1);
+ expect(spyOnIndexSet).toBeCalledWith(transaction.data.asset.data.name.toLowerCase());
+ });
+
+ it("should not forget name on EntityName index, when action is not Register", () => {
+ const mempoolIndexRegistry = container.get(
+ Container.Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+ const spyOnIndexSet = jest.spyOn(mempoolIndexRegistry.get(MempoolIndexes.EntityName), "forget");
+
+ transaction.data.asset.action = Enums.EntityAction.Update;
+ expect(entityHandler.onPoolLeave(transaction)).toResolve();
+ expect(spyOnIndexSet).not.toBeCalled();
+
+ transaction.data.asset.action = Enums.EntityAction.Resign;
+ expect(entityHandler.onPoolLeave(transaction)).toResolve();
+ expect(spyOnIndexSet).not.toBeCalled();
+ });
+ });
+
+ describe("getInvalidPoolTransactions", () => {
+ let transaction: EntityTransaction;
+ let invalidTransaction: EntityTransaction;
+ let entityHandler: EntityTransactionHandler;
+
+ beforeEach(() => {
+ const builder = new EntityBuilder();
+ transaction = builder.asset(validRegisters[0]).sign("passphrase").build() as EntityTransaction;
+ invalidTransaction = builder
+ .asset(validRegisters[0])
+ .sign("another_passphrase")
+ .build() as EntityTransaction;
+
+ entityHandler = container.resolve(EntityTransactionHandler);
+ });
+
+ it("should return empty array if there are no invalid transactions", async () => {
+ const mempoolIndexRegistry = container.get(
+ Container.Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+
+ const spyOnIndexHas = jest
+ .spyOn(mempoolIndexRegistry.get(MempoolIndexes.EntityName), "has")
+ .mockReturnValueOnce(false);
+
+ await expect(entityHandler.getInvalidPoolTransactions(transaction)).resolves.toEqual([]);
+ expect(spyOnIndexHas).toBeCalledTimes(1);
+ expect(spyOnIndexHas).toBeCalledWith(transaction.data.asset.data.name.toLowerCase());
+ });
+
+ it("should return invalid transaction if transaction with same username is indexed", async () => {
+ const mempoolIndexRegistry = container.get(
+ Container.Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+
+ const spyOnIndexHas = jest
+ .spyOn(mempoolIndexRegistry.get(MempoolIndexes.EntityName), "has")
+ .mockReturnValueOnce(true);
+
+ transaction.data.asset.action = Enums.EntityAction.Update;
+ await expect(entityHandler.getInvalidPoolTransactions(transaction)).resolves.toEqual([]);
+ expect(spyOnIndexHas).not.toBeCalled();
+
+ transaction.data.asset.action = Enums.EntityAction.Resign;
+ await expect(entityHandler.getInvalidPoolTransactions(transaction)).resolves.toEqual([]);
+ expect(spyOnIndexHas).not.toBeCalled();
+ });
+
+ it("should return empty array if transaction is not Register", async () => {
+ const mempoolIndexRegistry = container.get(
+ Container.Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
- const transactionClone = cloneDeep(transaction);
- const wherePredicateCallback1 = poolQuery.wherePredicate.mock.calls[0][0];
- const wherePredicateCallback2 = poolQuery.wherePredicate.mock.calls[1][0];
+ const spyOnIndexHas = jest
+ .spyOn(mempoolIndexRegistry.get(MempoolIndexes.EntityName), "has")
+ .mockReturnValueOnce(true);
- expect(wherePredicateCallback1(transactionClone)).toBeTrue();
- expect(wherePredicateCallback2(transactionClone)).toBeTrue();
+ const spyOnIndexGet = jest
+ .spyOn(mempoolIndexRegistry.get(MempoolIndexes.EntityName), "get")
+ .mockReturnValueOnce(invalidTransaction);
- delete transactionClone.data.asset;
- expect(wherePredicateCallback1(transactionClone)).toBeFalse();
- expect(wherePredicateCallback2(transactionClone)).toBeFalse();
+ await expect(entityHandler.getInvalidPoolTransactions(transaction)).resolves.toEqual([invalidTransaction]);
+ expect(spyOnIndexHas).toBeCalledTimes(1);
+ expect(spyOnIndexHas).toBeCalledWith(transaction.data.asset.data.name.toLowerCase());
+ expect(spyOnIndexGet).toBeCalledTimes(1);
+ expect(spyOnIndexGet).toBeCalledWith(transaction.data.asset.data.name.toLowerCase());
});
});
diff --git a/__tests__/unit/core-p2p/actions/validate-and-accept-peer.test.ts b/__tests__/unit/core-p2p/actions/validate-and-accept-peer.test.ts
index 31921de9a6..424a314cba 100644
--- a/__tests__/unit/core-p2p/actions/validate-and-accept-peer.test.ts
+++ b/__tests__/unit/core-p2p/actions/validate-and-accept-peer.test.ts
@@ -14,10 +14,11 @@ describe("ValidateAndAcceptPeerAction", () => {
it("should call peerProcessor.validateAndAcceptPeer with arguments provided", async () => {
const peer = { ip: "187.165.33.2", port: 4000 };
const options = { someParam: 1 };
- await validateAndAcceptPeerAction.execute({ peer, options });
+ const headers = { version: "3.0.0" };
+ await validateAndAcceptPeerAction.execute({ peer, options, headers });
expect(peerProcessor.validateAndAcceptPeer).toBeCalledTimes(1);
- expect(peerProcessor.validateAndAcceptPeer).toBeCalledWith(peer, options);
+ expect(peerProcessor.validateAndAcceptPeer).toBeCalledWith(peer, headers, options);
});
});
});
diff --git a/__tests__/unit/core-p2p/hapi-nes/client.test.ts b/__tests__/unit/core-p2p/hapi-nes/client.test.ts
index 21a1181e83..8ced280278 100644
--- a/__tests__/unit/core-p2p/hapi-nes/client.test.ts
+++ b/__tests__/unit/core-p2p/hapi-nes/client.test.ts
@@ -969,7 +969,7 @@ describe("Client", () => {
await expect(client.request("/")).rejects.toThrowError("Request timed out");
- await new Promise((resolve) => {
+ await new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 300);
diff --git a/__tests__/unit/core-p2p/hapi-nes/socket.test.ts b/__tests__/unit/core-p2p/hapi-nes/socket.test.ts
index 78a64f0e1f..f6f54395ab 100644
--- a/__tests__/unit/core-p2p/hapi-nes/socket.test.ts
+++ b/__tests__/unit/core-p2p/hapi-nes/socket.test.ts
@@ -223,7 +223,7 @@ describe("Socket", () => {
const client = new Ws("http://localhost:" + server.info.port);
client.onerror = Hoek.ignore;
- const sendInvalid = async () => new Promise((resolve, reject) => {
+ const sendInvalid = async () => new Promise((resolve, reject) => {
client.on("open", () => {
client.send("{", {} as any, () => resolve());
});
@@ -301,7 +301,7 @@ describe("Socket", () => {
client.onerror = Hoek.ignore;
client.on("open", () => client.send(stringifyNesMessage({ id: 1, type: "??", version: "2" }), Hoek.ignore));
-
+
await delay(1000);
expect(client.readyState).toEqual(client.CLOSED);
@@ -337,7 +337,7 @@ describe("Socket", () => {
client.onerror = Hoek.ignore;
client.on("open", () => client.send(stringifyNesMessage({ id: 1, type: "hello" }), Hoek.ignore));
-
+
await delay(1000);
expect(client.readyState).toEqual(client.CLOSED);
@@ -359,7 +359,7 @@ describe("Socket", () => {
const client = new Ws("http://localhost:" + server.info.port);
client.onerror = Hoek.ignore;
- const sendPingOrPong = async () => new Promise((resolve, reject) => {
+ const sendPingOrPong = async () => new Promise((resolve, reject) => {
client.on("open", () => {
client[method]("", true, () => resolve());
});
diff --git a/__tests__/unit/core-p2p/network-monitor.test.ts b/__tests__/unit/core-p2p/network-monitor.test.ts
index 88396ea25a..f81a71e64d 100644
--- a/__tests__/unit/core-p2p/network-monitor.test.ts
+++ b/__tests__/unit/core-p2p/network-monitor.test.ts
@@ -353,7 +353,7 @@ describe("NetworkMonitor", () => {
});
});
- it("should schedule the next updateNetworkStatus only once", async () => {
+ it.skip("should schedule the next updateNetworkStatus only once", async () => {
repository.getPeers.mockReturnValue([]);
let sleeping = true;
@@ -400,7 +400,7 @@ describe("NetworkMonitor", () => {
expect(communicator.ping).toBeCalledTimes(peers.length);
for (const peer of peers) {
- expect(communicator.ping).toBeCalledWith(peer, config.verifyTimeout, expect.anything());
+ expect(communicator.ping).toBeCalledWith(peer, config.verifyTimeout, expect.anything(), false);
}
});
@@ -412,7 +412,7 @@ describe("NetworkMonitor", () => {
expect(communicator.ping).toBeCalledTimes(peers.length);
for (const peer of peers) {
- expect(communicator.ping).toBeCalledWith(peer, config.verifyTimeout, expect.anything());
+ expect(communicator.ping).toBeCalledWith(peer, config.verifyTimeout, expect.anything(), false);
}
});
@@ -1120,7 +1120,7 @@ describe("NetworkMonitor", () => {
);
describe("when blockPing.last - blockPing.first < 500ms", () => {
- it("should not wait if block is from forger", async () => {
+ it.skip("should not wait if block is from forger", async () => {
blockchain.getBlockPing = jest
.fn()
.mockReturnValue({ block: block.data, last: 10500, first: 10200, count: 2, fromForger: true });
@@ -1132,7 +1132,7 @@ describe("NetworkMonitor", () => {
expect(spySleep).toBeCalledTimes(0);
});
- it("should wait until 500ms have elapsed between blockPing.last and blockPing.first before broadcasting", async () => {
+ it.skip("should wait until 500ms have elapsed between blockPing.last and blockPing.first before broadcasting", async () => {
blockchain.getBlockPing = jest
.fn()
.mockReturnValue({ block: block.data, last: 10500, first: 10200, count: 2 });
@@ -1145,7 +1145,7 @@ describe("NetworkMonitor", () => {
expect(spySleep).toBeCalledWith(200); // 500 - (last - first)
});
- it("should not broadcast if during waiting we have received a new block", async () => {
+ it.skip("should not broadcast if during waiting we have received a new block", async () => {
blockchain.getBlockPing = jest
.fn()
.mockReturnValueOnce({ block: block.data, last: 10500, first: 10200, count: 2 })
diff --git a/__tests__/unit/core-p2p/network-state.test.ts b/__tests__/unit/core-p2p/network-state.test.ts
index 76ef3bc935..979f5c38ab 100644
--- a/__tests__/unit/core-p2p/network-state.test.ts
+++ b/__tests__/unit/core-p2p/network-state.test.ts
@@ -2,7 +2,7 @@ import { Container, Utils as KernelUtils } from "@packages/core-kernel";
import { NetworkStateStatus } from "@packages/core-p2p/src/enums";
import { NetworkState } from "@packages/core-p2p/src/network-state";
import { Peer } from "@packages/core-p2p/src/peer";
-import { PeerVerificationResult } from "@packages/core-p2p/src/peer-verifier";
+import { FastPeerVerificationResult } from "@packages/core-p2p/src/peer-verifier";
import { Blocks, Crypto, Utils } from "@packages/crypto";
describe("NetworkState", () => {
@@ -99,7 +99,7 @@ describe("NetworkState", () => {
peer3.state = { header: {}, height: 8, forgingAllowed: true, currentSlot: currentSlot }; // same height
const peer4 = new Peer("184.168.65.65", 4000);
peer4.state = { header: {}, height: 6, forgingAllowed: false, currentSlot: currentSlot - 2 }; // below height
- peer4.verificationResult = new PeerVerificationResult(8, 6, 4); // forked
+ peer4.fastVerificationResult = new FastPeerVerificationResult(8, 6); // forked
const peer5 = new Peer("185.168.65.65", 4000);
peer5.state = { header: {}, height: 6, forgingAllowed: false, currentSlot: currentSlot - 2 }; // below height, not forked
peers = [peer1, peer2, peer3, peer4, peer5];
diff --git a/__tests__/unit/core-p2p/peer-communicator.test.ts b/__tests__/unit/core-p2p/peer-communicator.test.ts
index a1e004c2f3..5484ed52db 100644
--- a/__tests__/unit/core-p2p/peer-communicator.test.ts
+++ b/__tests__/unit/core-p2p/peer-communicator.test.ts
@@ -33,7 +33,7 @@ describe("PeerCommunicator", () => {
const logger = { warning: jest.fn(), debug: jest.fn(), error: jest.fn(), info: jest.fn() };
const configuration = { getOptional: jest.fn(), getRequired: jest.fn() };
- const peerVerifier = { initialize: () => {}, checkState: jest.fn() };
+ const peerVerifier = { initialize: () => {}, checkState: jest.fn(), checkStateFast: jest.fn() };
peerVerifier.initialize = () => peerVerifier;
const version = "3.0.0";
const headers = { version };
@@ -249,6 +249,23 @@ describe("PeerCommunicator", () => {
const pingResult = await peerCommunicator.ping(peer, 6000);
+ expect(peerVerifier.checkState).toBeCalledTimes(1);
+ expect(connector.emit).toBeCalledTimes(1);
+ expect(connector.emit).toBeCalledWith(peer, event, { headers }, 5000);
+ expect(pingResult).toEqual(baseGetStatusResponse.state);
+ });
+
+ it("should not throw otherwise using fast option", async () => {
+ const event = "p2p.peer.getStatus";
+ const peer = new Peer("187.168.65.65", 4000);
+ const pingResponse = baseGetStatusResponse;
+ connector.emit = jest.fn().mockReturnValueOnce({ payload: pingResponse });
+ configuration.getOptional = jest.fn().mockReturnValueOnce([baseGetStatusResponse.config.version]); // minimumVersions
+ peerVerifier.checkStateFast = jest.fn().mockReturnValueOnce(new PeerVerificationResult(1, 1, 1));
+
+ const pingResult = await peerCommunicator.ping(peer, 6000, false, true);
+
+ expect(peerVerifier.checkStateFast).toBeCalledTimes(1);
expect(connector.emit).toBeCalledTimes(1);
expect(connector.emit).toBeCalledWith(peer, event, { headers }, 5000);
expect(pingResult).toEqual(baseGetStatusResponse.state);
diff --git a/__tests__/unit/core-p2p/peer-processor.test.ts b/__tests__/unit/core-p2p/peer-processor.test.ts
index 163d4d06c7..0d7d8a6a79 100644
--- a/__tests__/unit/core-p2p/peer-processor.test.ts
+++ b/__tests__/unit/core-p2p/peer-processor.test.ts
@@ -68,7 +68,7 @@ describe("PeerProcessor", () => {
const peer = new Peer("178.165.55.55", 4000);
peerRepository.getSameSubnetPeers = jest.fn().mockReturnValueOnce([]);
- await peerProcessor.validateAndAcceptPeer(peer);
+ await peerProcessor.validateAndAcceptPeer(peer, {});
expect(peerRepository.setPendingPeer).toBeCalledTimes(1);
expect(peerCommunicator.ping).toBeCalledTimes(1);
@@ -82,7 +82,7 @@ describe("PeerProcessor", () => {
const peer = new Peer("178.165.55.55", 4000);
peerRepository.getSameSubnetPeers = jest.fn().mockReturnValueOnce([]);
- await peerProcessor.validateAndAcceptPeer(peer);
+ await peerProcessor.validateAndAcceptPeer(peer, {});
expect(peerRepository.setPendingPeer).toBeCalledTimes(1);
expect(peerCommunicator.ping).toBeCalledTimes(1);
@@ -96,7 +96,7 @@ describe("PeerProcessor", () => {
const peer = new Peer("178.165.55.55", 4000);
peerRepository.getSameSubnetPeers = jest.fn().mockReturnValueOnce([]);
- await peerProcessor.validateAndAcceptPeer(peer);
+ await peerProcessor.validateAndAcceptPeer(peer, {});
expect(peerRepository.setPendingPeer).toBeCalledTimes(1);
expect(peerCommunicator.ping).toBeCalledTimes(1);
@@ -108,7 +108,7 @@ describe("PeerProcessor", () => {
peerRepository.getSameSubnetPeers = jest.fn().mockReturnValueOnce([]);
peerCommunicator.ping = jest.fn().mockRejectedValueOnce(new Error("ping threw"));
- await peerProcessor.validateAndAcceptPeer(peer);
+ await peerProcessor.validateAndAcceptPeer(peer, {});
expect(peerRepository.setPendingPeer).toBeCalledTimes(1);
expect(peerCommunicator.ping).toBeCalledTimes(1);
@@ -121,7 +121,7 @@ describe("PeerProcessor", () => {
peerRepository.getSameSubnetPeers = jest.fn().mockReturnValueOnce([]);
peerRepository.hasPeer = jest.fn().mockReturnValueOnce(true);
- await peerProcessor.validateAndAcceptPeer(peer);
+ await peerProcessor.validateAndAcceptPeer(peer, {});
expect(peerRepository.setPendingPeer).toBeCalledTimes(0);
expect(peerCommunicator.ping).toBeCalledTimes(0);
diff --git a/__tests__/unit/core-p2p/peer-verifier.test.ts b/__tests__/unit/core-p2p/peer-verifier.test.ts
index f8f1d1efe8..582e4b5185 100644
--- a/__tests__/unit/core-p2p/peer-verifier.test.ts
+++ b/__tests__/unit/core-p2p/peer-verifier.test.ts
@@ -1,6 +1,6 @@
import { Application, Container, Contracts } from "@packages/core-kernel";
import { Peer } from "@packages/core-p2p/src/peer";
-import { PeerVerificationResult, PeerVerifier } from "@packages/core-p2p/src/peer-verifier";
+import { PeerVerificationResult, FastPeerVerificationResult, PeerVerifier } from "@packages/core-p2p/src/peer-verifier";
import { Blocks } from "@packages/crypto";
describe("PeerVerifier", () => {
@@ -395,7 +395,7 @@ describe("PeerVerifier", () => {
stateStore.getLastHeight = jest
.fn()
.mockReturnValueOnce(claimedState.height)
- .mockRejectedValueOnce(undefined);
+ .mockReturnValueOnce(undefined);
stateStore.getLastBlocks = jest
.fn()
.mockReturnValueOnce([{ data: { height: claimedState.height }, getHeader: () => ourHeader }]);
@@ -807,4 +807,349 @@ describe("PeerVerifier", () => {
});
});
});
+
+ describe("checkStateFast", () => {
+ describe("when claimed state block header does not match claimed state height", () => {
+ it("should return undefined", async () => {
+ const claimedState: Contracts.P2P.PeerState = {
+ height: 18,
+ forgingAllowed: false,
+ currentSlot: 18,
+ header: {
+ height: 19,
+ id: "13965046748333390338",
+ },
+ };
+
+ expect(await peerVerifier.checkState(claimedState, Date.now() + 2000)).toBeUndefined();
+ });
+ });
+
+ describe("when Case1. Peer height > our height and our highest block is part of the peer's chain", () => {
+ it("should return PeerVerificationResult forked", async () => {
+ const generatorPublicKey = "03c5282b639d0e8f94cfac6c0ed242d1634d8a2c93cbd76c6ed2856a9f19cf6a13";
+ const claimedState: Contracts.P2P.PeerState = {
+ height: 18,
+ forgingAllowed: false,
+ currentSlot: 18,
+ header: {
+ height: 18,
+ id: "13965046748333390338",
+ generatorPublicKey,
+ },
+ };
+ const ourHeader = {
+ height: 15,
+ id: "11165046748333390338",
+ };
+
+ stateStore.getLastHeight = jest.fn().mockReturnValue(ourHeader.height);
+ stateStore.getLastBlocks = jest
+ .fn()
+ .mockReturnValue([{ data: { height: ourHeader.height }, getHeader: () => ourHeader }]);
+ databaseInterceptor.getBlocksByHeight = jest.fn().mockImplementation((blockHeights) =>
+ blockHeights.map((height: number) => ({
+ height,
+ id: height.toString().padStart(2, "0").repeat(20), // just using height to mock the id
+ })),
+ );
+ peerCommunicator.hasCommonBlocks = jest.fn().mockImplementation((_, ids) => ({
+ id: ids[ids.length - 1],
+ height: parseInt(ids[ids.length - 1].slice(0, 2)),
+ }));
+ trigger.call = jest.fn().mockReturnValue([
+ {
+ getPublicKey: () => {
+ return generatorPublicKey;
+ },
+ },
+ ]);
+ const spyFromData = jest
+ .spyOn(Blocks.BlockFactory, "fromData")
+ .mockImplementation(blockWithIdFromDataMock);
+
+ const result = await peerVerifier.checkStateFast(claimedState, Date.now() + 2000);
+
+ expect(result).toBeInstanceOf(FastPeerVerificationResult);
+ expect(result.forked).toBeTrue();
+ expect(result.myHeight).toEqual(15);
+ expect(result.hisHeight).toEqual(18);
+ expect(result.highestCommonHeight).toBeUndefined();
+
+ spyFromData.mockRestore();
+ peerCommunicator.getPeerBlocks = jest.fn();
+ databaseInterceptor.getBlocksByHeight = jest.fn();
+ peerCommunicator.hasCommonBlocks = jest.fn();
+ stateStore.getLastHeight = jest.fn();
+ trigger.call = jest.fn();
+ stateStore.getLastBlocks = jest.fn();
+ });
+ });
+
+ describe("when Case2. Peer height > our height and our highest block is not part of the peer's chain", () => {
+ const claimedState: Contracts.P2P.PeerState = {
+ height: 18,
+ forgingAllowed: false,
+ currentSlot: 18,
+ header: {
+ height: 18,
+ id: "13965046748333390338",
+ },
+ };
+ const ourHeader = {
+ height: 15,
+ id: "11165046748333390338",
+ };
+
+ it("should return PeerVerificationResult forked", async () => {
+ const generatorPublicKey = "03c5282b639d0e8f94cfac6c0ed242d1634d8a2c93cbd76c6ed2856a9f19cf6a13";
+ stateStore.getLastHeight = jest
+ .fn()
+ .mockReturnValueOnce(ourHeader.height)
+ .mockReturnValueOnce(ourHeader.height);
+ stateStore.getLastBlocks = jest
+ .fn()
+ .mockReturnValueOnce([{ data: { height: ourHeader.height }, getHeader: () => ourHeader }]);
+ databaseInterceptor.getBlocksByHeight = jest.fn().mockImplementation((blockHeights) =>
+ blockHeights.map((height: number) => ({
+ height,
+ id: height.toString().padStart(2, "0").repeat(20), // just using height to mock the id
+ })),
+ );
+ trigger.call = jest.fn().mockReturnValue([
+ {
+ getPublicKey: () => {
+ return generatorPublicKey;
+ },
+ },
+ ]); // getActiveDelegates mock
+ const spyFromData = jest.spyOn(Blocks.BlockFactory, "fromData").mockImplementation(blockFromDataMock);
+
+ const result = await peerVerifier.checkStateFast(claimedState, Date.now() + 2000);
+
+ expect(result).toBeInstanceOf(FastPeerVerificationResult);
+ expect(result.forked).toBeTrue();
+ expect(result.myHeight).toEqual(15);
+ expect(result.hisHeight).toEqual(18);
+ expect(result.highestCommonHeight).toBeUndefined();
+
+ spyFromData.mockRestore();
+ peerCommunicator.getPeerBlocks = jest.fn();
+ databaseInterceptor.getBlocksByHeight = jest.fn();
+ peerCommunicator.hasCommonBlocks = jest.fn();
+ });
+ });
+
+ describe("when Case3. Peer height == our height and our latest blocks are the same", () => {
+ const claimedState: Contracts.P2P.PeerState = {
+ height: 15,
+ forgingAllowed: false,
+ currentSlot: 15,
+ header: {
+ height: 15,
+ id: "13965046748333390338",
+ },
+ };
+
+ it("should return PeerVerificationResult not forked", async () => {
+ stateStore.getLastHeight = jest.fn().mockReturnValueOnce(claimedState.height);
+ stateStore.getLastBlocks = jest
+ .fn()
+ .mockReturnValueOnce([
+ { data: { height: claimedState.height }, getHeader: () => claimedState.header },
+ ]);
+ databaseInterceptor.getBlocksByHeight = jest.fn().mockReturnValueOnce([{ id: claimedState.header.id }]);
+
+ const result = await peerVerifier.checkStateFast(claimedState, Date.now() + 2000);
+
+ expect(result).toBeInstanceOf(FastPeerVerificationResult);
+ expect(result.forked).toBeFalse();
+ expect(result.myHeight).toEqual(15);
+ expect(result.hisHeight).toEqual(15);
+ expect(result.highestCommonHeight).toEqual(15);
+ });
+ });
+
+ describe("when Case4. Peer height == our height and our latest blocks differ", () => {
+ const claimedState: Contracts.P2P.PeerState = {
+ height: 15,
+ forgingAllowed: false,
+ currentSlot: 15,
+ header: {
+ height: 15,
+ id: "13965046748333390338",
+ },
+ };
+ const ourHeader = {
+ height: 15,
+ id: "11165046748333390338",
+ };
+
+ it.each([[true], [false]])(
+ "should return PeerVerificationResult forked when claimed state block header is valid",
+ async (delegatesEmpty) => {
+ const generatorPublicKey = "03c5282b639d0e8f94cfac6c0ed242d1634d8a2c93cbd76c6ed2856a9f19cf6a13";
+ stateStore.getLastHeight = jest
+ .fn()
+ .mockReturnValueOnce(claimedState.height)
+ .mockReturnValueOnce(claimedState.height);
+ stateStore.getLastBlocks = jest
+ .fn()
+ .mockReturnValueOnce([{ data: { height: claimedState.height }, getHeader: () => ourHeader }]);
+ databaseInterceptor.getBlocksByHeight = jest
+ .fn()
+ .mockReturnValueOnce([{ id: ourHeader.id }])
+ .mockImplementation((blockHeights) =>
+ blockHeights.map((height: number) => ({
+ height,
+ id: height.toString().padStart(2, "0").repeat(20), // just using height to mock the id
+ })),
+ );
+
+ if (delegatesEmpty) {
+ // getActiveDelegates return empty array, should still work using dpos state
+ trigger.call = jest.fn().mockReturnValue([]); // getActiveDelegates mock
+ dposState.getRoundInfo = jest
+ .fn()
+ .mockReturnValueOnce({ round: 1, maxDelegates: 51 })
+ .mockReturnValueOnce({ round: 1, maxDelegates: 51 });
+ dposState.getRoundDelegates = jest
+ .fn()
+ .mockReturnValueOnce([
+ {
+ getPublicKey: () => {
+ return generatorPublicKey;
+ },
+ },
+ ])
+ .mockReturnValueOnce([
+ {
+ getPublicKey: () => {
+ return generatorPublicKey;
+ },
+ },
+ ]);
+ } else {
+ trigger.call = jest.fn().mockReturnValue([
+ {
+ getPublicKey: () => {
+ return generatorPublicKey;
+ },
+ },
+ ]); // getActiveDelegates mock
+ }
+
+ const spyFromData = jest
+ .spyOn(Blocks.BlockFactory, "fromData")
+ .mockImplementation(blockFromDataMock);
+
+ const result = await peerVerifier.checkStateFast(claimedState, Date.now() + 2000);
+
+ expect(result).toBeInstanceOf(FastPeerVerificationResult);
+ expect(result.forked).toBeTrue();
+ expect(result.myHeight).toEqual(15);
+ expect(result.hisHeight).toEqual(15);
+ expect(result.highestCommonHeight).toBeUndefined();
+
+ spyFromData.mockRestore();
+ peerCommunicator.getPeerBlocks = jest.fn();
+ databaseInterceptor.getBlocksByHeight = jest.fn();
+ peerCommunicator.hasCommonBlocks = jest.fn();
+ },
+ );
+ });
+
+ describe("when Case5. Peer height < our height and peer's latest block is part of our chain", () => {
+ const claimedState: Contracts.P2P.PeerState = {
+ height: 15,
+ forgingAllowed: false,
+ currentSlot: 15,
+ header: {
+ height: 15,
+ id: "13965046748333390338",
+ },
+ };
+ const ourHeight = claimedState.height + 2;
+ const ourHeader = {
+ height: ourHeight,
+ id: "6857401089891373446",
+ };
+
+ it("should return PeerVerificationResult not forked", async () => {
+ stateStore.getLastHeight = jest.fn().mockReturnValueOnce(ourHeight);
+ stateStore.getLastBlocks = jest.fn().mockReturnValueOnce([
+ { data: { height: ourHeight }, getHeader: () => ourHeader },
+ { data: { height: claimedState.height }, getHeader: () => claimedState.header },
+ ]);
+ databaseInterceptor.getBlocksByHeight = jest.fn().mockReturnValueOnce([{ id: claimedState.header.id }]);
+
+ const result = await peerVerifier.checkStateFast(claimedState, Date.now() + 2000);
+
+ expect(result).toBeInstanceOf(FastPeerVerificationResult);
+ expect(result.forked).toBeFalse();
+ expect(result.myHeight).toEqual(17);
+ expect(result.hisHeight).toEqual(15);
+ expect(result.highestCommonHeight).toEqual(15);
+ });
+ });
+
+ describe("when Case6. Peer height < our height and peer's latest block is not part of our chain", () => {
+ const generatorPublicKey = "03c5282b639d0e8f94cfac6c0ed242d1634d8a2c93cbd76c6ed2856a9f19cf6a13";
+ const claimedState: Contracts.P2P.PeerState = {
+ height: 12,
+ forgingAllowed: false,
+ currentSlot: 12,
+ header: {
+ height: 12,
+ id: "13965046748333390338",
+ generatorPublicKey,
+ },
+ };
+ const ourHeader = {
+ height: 15,
+ id: "11165046748333390338",
+ };
+
+ it("should return PeerVerificationResult forked", async () => {
+ stateStore.getLastHeight = jest
+ .fn()
+ .mockReturnValueOnce(ourHeader.height)
+ .mockReturnValueOnce(ourHeader.height);
+ stateStore.getLastBlocks = jest
+ .fn()
+ .mockReturnValueOnce([{ data: { height: ourHeader.height }, getHeader: () => ourHeader }]);
+ databaseInterceptor.getBlocksByHeight = jest
+ .fn()
+ .mockReturnValueOnce([{ id: ourHeader.id }])
+ .mockImplementation((blockHeights) =>
+ blockHeights.map((height: number) => ({
+ height,
+ id: height.toString().padStart(2, "0").repeat(20), // just using height to mock the id
+ })),
+ );
+ trigger.call = jest.fn().mockReturnValue([
+ {
+ getPublicKey: () => {
+ return generatorPublicKey;
+ },
+ },
+ ]);
+
+ const spyFromData = jest.spyOn(Blocks.BlockFactory, "fromData").mockImplementation(blockFromDataMock);
+
+ const result = await peerVerifier.checkStateFast(claimedState, Date.now() + 2000);
+
+ expect(result).toBeInstanceOf(FastPeerVerificationResult);
+ expect(result.forked).toBeTrue();
+ expect(result.myHeight).toEqual(15);
+ expect(result.hisHeight).toEqual(12);
+ expect(result.highestCommonHeight).toBeUndefined();
+
+ spyFromData.mockRestore();
+ peerCommunicator.getPeerBlocks = jest.fn();
+ databaseInterceptor.getBlocksByHeight = jest.fn();
+ peerCommunicator.hasCommonBlocks = jest.fn();
+ });
+ });
+ });
});
diff --git a/__tests__/unit/core-p2p/socket-server/controllers/blocks.test.ts b/__tests__/unit/core-p2p/socket-server/controllers/blocks.test.ts
index 596675664a..291fde99b4 100644
--- a/__tests__/unit/core-p2p/socket-server/controllers/blocks.test.ts
+++ b/__tests__/unit/core-p2p/socket-server/controllers/blocks.test.ts
@@ -4,6 +4,7 @@ import { BlocksController } from "@packages/core-p2p/src/socket-server/controlle
import { TooManyTransactionsError } from "@packages/core-p2p/src/socket-server/errors";
import { Sandbox } from "@packages/core-test-framework";
import { Blocks, Identities, Managers, Networks, Transactions, Utils } from "@packages/crypto";
+import { cloneDeep } from "lodash";
Managers.configManager.getMilestone().aip11 = true; // for creating aip11 v2 transactions
@@ -65,11 +66,10 @@ describe("BlocksController", () => {
.build(),
],
} as Blocks.Block;
- const deepClone = (obj) => JSON.parse(JSON.stringify(obj));
describe("when block contains too many transactions", () => {
it("should throw TooManyTransactionsError when numberOfTransactions is too much", async () => {
- const blockTooManyTxs = deepClone(block);
+ const blockTooManyTxs = cloneDeep(block);
blockTooManyTxs.data.numberOfTransactions = 350;
const blockSerialized = Blocks.Serializer.serializeWithTransactions({
...blockTooManyTxs.data,
@@ -87,7 +87,7 @@ describe("BlocksController", () => {
blockchain.getLastHeight.mockReturnValueOnce(100);
blockchain.getLastDownloadedBlock.mockReturnValueOnce(Networks.testnet.genesisBlock);
- const blockUnchained = deepClone(block);
+ const blockUnchained = cloneDeep(block);
blockUnchained.data.height = 9;
const blockSerialized = Blocks.Serializer.serializeWithTransactions({
...blockUnchained.data,
diff --git a/__tests__/unit/core-p2p/socket-server/controllers/internal.test.ts b/__tests__/unit/core-p2p/socket-server/controllers/internal.test.ts
index 4e88119715..2df66acc2c 100644
--- a/__tests__/unit/core-p2p/socket-server/controllers/internal.test.ts
+++ b/__tests__/unit/core-p2p/socket-server/controllers/internal.test.ts
@@ -42,7 +42,7 @@ describe("InternalController", () => {
await internalController.acceptNewPeer({ payload: { ip } }, {});
expect(peerProcessor.validateAndAcceptPeer).toBeCalledTimes(1);
- expect(peerProcessor.validateAndAcceptPeer).toBeCalledWith({ ip });
+ expect(peerProcessor.validateAndAcceptPeer).toBeCalledWith({ ip }, {});
});
});
diff --git a/__tests__/unit/core-p2p/socket-server/plugins/accept-peer.test.ts b/__tests__/unit/core-p2p/socket-server/plugins/accept-peer.test.ts
index 3347ba030a..239398f31b 100644
--- a/__tests__/unit/core-p2p/socket-server/plugins/accept-peer.test.ts
+++ b/__tests__/unit/core-p2p/socket-server/plugins/accept-peer.test.ts
@@ -66,7 +66,7 @@ describe("AcceptPeerPlugin", () => {
expect(JSON.parse(responseValid.payload)).toEqual(responsePayload);
expect(responseValid.statusCode).toBe(200);
expect(peerProcessor.validateAndAcceptPeer).toBeCalledTimes(1);
- expect(peerProcessor.validateAndAcceptPeer).toBeCalledWith({ ip: remoteAddress });
+ expect(peerProcessor.validateAndAcceptPeer).toBeCalledWith({ ip: remoteAddress }, {});
});
it("should not be called on another route", async () => {
diff --git a/__tests__/unit/core-p2p/utils/is-valid-version.test.ts b/__tests__/unit/core-p2p/utils/is-valid-version.test.ts
index 6b33153050..3930a2e70b 100644
--- a/__tests__/unit/core-p2p/utils/is-valid-version.test.ts
+++ b/__tests__/unit/core-p2p/utils/is-valid-version.test.ts
@@ -5,53 +5,104 @@ import { Peer } from "@arkecosystem/core-p2p/src/peer";
import { Managers } from "@arkecosystem/crypto";
let peerMock: Peer;
+const getOptional = jest.fn().mockReturnValue([]);
const app = {
getTagged: jest.fn().mockReturnValue({
- getOptional: jest.fn().mockReturnValue(["^2.6.0"]),
+ getOptional,
}),
+ get: jest.fn().mockReturnValue("mainnet"),
} as any;
beforeEach(async () => {
peerMock = new Peer("1.0.0.99", 4002);
});
-describe.each([[true], [false]])("isValidVersion", (withMilestones) => {
- let spyGetMilestone = jest.spyOn(Managers.configManager, "getMilestone");
- beforeEach(() => {
- if (withMilestones) {
- spyGetMilestone = jest.spyOn(Managers.configManager, "getMilestone").mockReturnValue({
- p2p: {
- minimumVersions: ["^2.6.0"]
- }
- });
- }
- })
- afterEach(() => {
- spyGetMilestone.mockRestore();
- })
-
- it.each([["2.6.0"], ["2.6.666"], ["2.7.0"], ["2.8.0"], ["2.9.0"], ["2.9.934"]])(
- "should be a valid version",
- (version) => {
- peerMock.version = version;
- expect(isValidVersion(app, peerMock)).toBeTrue();
- },
- );
+describe("isValidVersion", () => {
+ it("should return false if version is not defined", () => {
+ expect(isValidVersion(app, peerMock)).toBeFalse();
+ });
- it.each([
- [undefined]
- ["2.4.0"],
- ["2.5.0"],
- ["1.0.0"],
- ["---aaa"],
- ["2490"],
- [2],
- [-10.2],
- [{}],
- [true],
- [() => 1],
- ["2.0.0.0"],
- ])("should be an invalid version", (version: any) => {
+ it.each(["a", "3", "3.6", "-3.4.0", "3.0.0.next.1"])("should return false if version is not valid", (version) => {
peerMock.version = version;
expect(isValidVersion(app, peerMock)).toBeFalse();
});
+
+ it.each(["3.0.0", "3.0.0-next.1"])("should return true if minVersion is not set", (version) => {
+ peerMock.version = version;
+ expect(isValidVersion(app, peerMock)).toBeTrue();
+ });
+
+ const tests = [
+ { network: "mainnet", version: "3.0.0", minVersion: ["3.0.0"], result: true },
+ { network: "mainnet", version: "3.0.0", minVersion: ["3.0.0", "2.0.0"], result: true },
+ { network: "mainnet", version: "4.0.0", minVersion: [">=3.0.0 || <=5.0.0"], result: true },
+ { network: "mainnet", version: "3.0.1", minVersion: ["3.0.0"], result: false },
+ { network: "mainnet", version: "3.0.13", minVersion: [">=3.0.0"], result: true },
+ { network: "mainnet", version: "4.0.0", minVersion: [">=3.0.0"], result: true },
+ { network: "mainnet", version: "3.3.3", minVersion: ["^3.0.0"], result: true },
+ { network: "mainnet", version: "4.0.0", minVersion: ["^3.0.0"], result: false },
+ { network: "mainnet", version: "4.0.0-next.1", minVersion: [">=3.0.0"], result: false },
+ { network: "devnet", version: "4.0.0-next.1", minVersion: [">=3.0.0"], result: true },
+ { network: "mainnet", version: "3.0.0", minVersion: [], result: true },
+ ];
+ it.each(tests)("minVersion in milestones", (test) => {
+ const spyGet = jest.spyOn(Managers.configManager, "get").mockReturnValue(test.network);
+ const spyGetMilestone = jest.spyOn(Managers.configManager, "getMilestone").mockReturnValue({
+ p2p: {
+ minimumVersions: test.minVersion,
+ },
+ });
+
+ peerMock.version = test.version;
+ expect(isValidVersion(app, peerMock)).toEqual(test.result);
+ expect(spyGet).toBeCalled();
+ expect(spyGetMilestone).toBeCalled();
+ });
+
+ it.each(tests)("minVersion in config", (test) => {
+ const spyGet = jest.spyOn(Managers.configManager, "get").mockReturnValue(test.network);
+ getOptional.mockReturnValue(test.minVersion);
+
+ peerMock.version = test.version;
+ expect(isValidVersion(app, peerMock)).toEqual(test.result);
+ expect(spyGet).toBeCalled();
+ expect(getOptional).toBeCalled();
+ });
+
+ it.each([
+ {
+ network: "mainnet",
+ version: "3.0.0",
+ minConfigVersion: ["3.0.0"],
+ minMilestonesVersion: ["3.0.0"],
+ result: true,
+ },
+ {
+ network: "mainnet",
+ version: "3.0.0",
+ minConfigVersion: ["4.0.0"],
+ minMilestonesVersion: ["3.0.0"],
+ result: false,
+ },
+ {
+ network: "mainnet",
+ version: "3.0.0",
+ minConfigVersion: ["3.0.0"],
+ minMilestonesVersion: ["4.0.0"],
+ result: false,
+ },
+ ])("should pass configuration and milestone minVersion", (test) => {
+ const spyGet = jest.spyOn(Managers.configManager, "get").mockReturnValue(test.network);
+ const spyGetMilestone = jest.spyOn(Managers.configManager, "getMilestone").mockReturnValue({
+ p2p: {
+ minimumVersions: test.minMilestonesVersion,
+ },
+ });
+ getOptional.mockReturnValue(test.minConfigVersion);
+
+ peerMock.version = test.version;
+ expect(isValidVersion(app, peerMock)).toEqual(test.result);
+ expect(spyGet).toBeCalled();
+ expect(spyGetMilestone).toBeCalled();
+ expect(getOptional).toBeCalled();
+ });
});
diff --git a/__tests__/unit/core-snapshots/workers/actions/test-worker-action.test.ts b/__tests__/unit/core-snapshots/workers/actions/test-worker-action.test.ts
index 60d19d739e..a9b51a6159 100644
--- a/__tests__/unit/core-snapshots/workers/actions/test-worker-action.test.ts
+++ b/__tests__/unit/core-snapshots/workers/actions/test-worker-action.test.ts
@@ -45,7 +45,7 @@ describe("TestWorkerAction", () => {
const promise = testWorkerAction.start();
- await new Promise((resolve) => {
+ await new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 10);
@@ -53,7 +53,7 @@ describe("TestWorkerAction", () => {
testWorkerAction.sync({});
- await new Promise((resolve) => {
+ await new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 10);
diff --git a/__tests__/unit/core-snapshots/workers/actions/worker-action.test.ts b/__tests__/unit/core-snapshots/workers/actions/worker-action.test.ts
index 4ea85ff70d..fe99cbb6f1 100644
--- a/__tests__/unit/core-snapshots/workers/actions/worker-action.test.ts
+++ b/__tests__/unit/core-snapshots/workers/actions/worker-action.test.ts
@@ -212,7 +212,7 @@ describe("WorkerAction", () => {
await expect(waitForMessage(verifyWorkerAction, "start", undefined)).toResolve();
- await new Promise((resolve) => {
+ await new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 10);
@@ -242,7 +242,7 @@ describe("WorkerAction", () => {
await expect(waitForMessage(restoreWorkerAction, "start", undefined)).toResolve();
- await new Promise((resolve) => {
+ await new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 10);
@@ -272,7 +272,7 @@ describe("WorkerAction", () => {
await expect(waitForMessage(restoreWorkerAction, "start", undefined)).toResolve();
- await new Promise((resolve) => {
+ await new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 10);
diff --git a/__tests__/unit/core-snapshots/workers/read-processor.test.ts b/__tests__/unit/core-snapshots/workers/read-processor.test.ts
index 62fb30a153..f84d08c95d 100644
--- a/__tests__/unit/core-snapshots/workers/read-processor.test.ts
+++ b/__tests__/unit/core-snapshots/workers/read-processor.test.ts
@@ -69,7 +69,7 @@ describe("ReadProcessor", () => {
await wait;
- await new Promise((resolve) => {
+ await new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 1000);
@@ -83,7 +83,7 @@ describe("ReadProcessor", () => {
await waitUntilSynchronized();
- await new Promise((resolve) => {
+ await new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 1000);
diff --git a/__tests__/unit/core-snapshots/workers/worker-thread.test.ts b/__tests__/unit/core-snapshots/workers/worker-thread.test.ts
index 713e49739d..30c0edac56 100644
--- a/__tests__/unit/core-snapshots/workers/worker-thread.test.ts
+++ b/__tests__/unit/core-snapshots/workers/worker-thread.test.ts
@@ -149,7 +149,7 @@ describe("Worker", () => {
data: {},
};
- await new Promise((resolve) => {
+ await new Promise((resolve) => {
worker.postMessage(message);
worker.once("message", (data) => {
diff --git a/__tests__/unit/core-snapshots/workers/worker-wrapper.test.ts b/__tests__/unit/core-snapshots/workers/worker-wrapper.test.ts
index 567333f952..bc29ead9fd 100644
--- a/__tests__/unit/core-snapshots/workers/worker-wrapper.test.ts
+++ b/__tests__/unit/core-snapshots/workers/worker-wrapper.test.ts
@@ -41,7 +41,7 @@ describe("WorkerWrapper", () => {
const promise = workerWrapper.start();
- await new Promise((resolve) => {
+ await new Promise((resolve) => {
setTimeout(() => {
mockWorker.emit("message", {
action: "started",
@@ -59,7 +59,7 @@ describe("WorkerWrapper", () => {
const promise = workerWrapper.start();
- await new Promise((resolve) => {
+ await new Promise((resolve) => {
setTimeout(() => {
mockWorker.emit("exit", 0);
resolve();
@@ -74,7 +74,7 @@ describe("WorkerWrapper", () => {
const promise = workerWrapper.start();
- await new Promise((resolve) => {
+ await new Promise((resolve) => {
setTimeout(() => {
mockWorker.emit("message", {
action: "exception",
@@ -92,7 +92,7 @@ describe("WorkerWrapper", () => {
const promise = workerWrapper.start();
- await new Promise((resolve) => {
+ await new Promise((resolve) => {
setTimeout(() => {
mockWorker.emit("message", {
action: "any",
@@ -110,7 +110,7 @@ describe("WorkerWrapper", () => {
const promise = workerWrapper.start();
- await new Promise((resolve) => {
+ await new Promise((resolve) => {
setTimeout(() => {
mockWorker.emit("error", {});
resolve();
@@ -131,7 +131,7 @@ describe("WorkerWrapper", () => {
dummy: "dummy_data",
};
- await new Promise((resolve) => {
+ await new Promise((resolve) => {
setTimeout(() => {
mockWorker.emit("message", {
action: "synchronized",
@@ -149,7 +149,7 @@ describe("WorkerWrapper", () => {
const promise = workerWrapper.sync({});
- await new Promise((resolve) => {
+ await new Promise((resolve) => {
setTimeout(() => {
mockWorker.emit("exit", 0);
resolve();
@@ -175,7 +175,7 @@ describe("WorkerWrapper", () => {
const promise = workerWrapper.sync({});
- await new Promise((resolve) => {
+ await new Promise((resolve) => {
setTimeout(() => {
mockWorker.emit("message", {
action: "exception",
@@ -193,7 +193,7 @@ describe("WorkerWrapper", () => {
const promise = workerWrapper.sync({});
- await new Promise((resolve) => {
+ await new Promise((resolve) => {
setTimeout(() => {
mockWorker.emit("message", {
action: "any",
@@ -211,7 +211,7 @@ describe("WorkerWrapper", () => {
const promise = workerWrapper.sync({});
- await new Promise((resolve) => {
+ await new Promise((resolve) => {
setTimeout(() => {
mockWorker.emit("error", {});
resolve();
diff --git a/__tests__/unit/core-state/__utils__/build-delegate-and-vote-balances.ts b/__tests__/unit/core-state/__utils__/build-delegate-and-vote-balances.ts
index 26f0e282fc..0bd0142429 100644
--- a/__tests__/unit/core-state/__utils__/build-delegate-and-vote-balances.ts
+++ b/__tests__/unit/core-state/__utils__/build-delegate-and-vote-balances.ts
@@ -1,5 +1,5 @@
import { Wallet, WalletRepository } from "@packages/core-state/src/wallets";
-import { Identities, Utils as CryptoUtils } from "@packages/crypto/src";
+import { Identities, Utils as CryptoUtils } from "@packages/crypto";
import { SATOSHI } from "@packages/crypto/src/constants";
export const buildDelegateAndVoteWallets = (numberDelegates: number, walletRepo: WalletRepository): Wallet[] => {
diff --git a/__tests__/unit/core-state/__utils__/make-chained-block.ts b/__tests__/unit/core-state/__utils__/make-chained-block.ts
index fc72193585..3d1ed551f9 100644
--- a/__tests__/unit/core-state/__utils__/make-chained-block.ts
+++ b/__tests__/unit/core-state/__utils__/make-chained-block.ts
@@ -1,7 +1,7 @@
-import { IBlock } from "@packages/crypto/src/interfaces";
+import { Interfaces } from "@packages/crypto";
-export const makeChainedBlocks = (length: number, blockFactory): IBlock[] => {
- const entitites: IBlock[] = [];
+export const makeChainedBlocks = (length: number, blockFactory): Interfaces.IBlock[] => {
+ const entitites: Interfaces.IBlock[] = [];
let previousBlock; // first case uses genesis IBlockData
const getPreviousBlock = () => previousBlock;
@@ -9,7 +9,7 @@ export const makeChainedBlocks = (length: number, blockFactory): IBlock[] => {
if (previousBlock) {
blockFactory.withOptions({ getPreviousBlock });
}
- const entity: IBlock = blockFactory.make();
+ const entity: Interfaces.IBlock = blockFactory.make();
entitites.push(entity);
previousBlock = entity.data;
}
diff --git a/__tests__/unit/core-state/__utils__/make-vote-transactions.ts b/__tests__/unit/core-state/__utils__/make-vote-transactions.ts
index d3a7839135..f8694eae43 100644
--- a/__tests__/unit/core-state/__utils__/make-vote-transactions.ts
+++ b/__tests__/unit/core-state/__utils__/make-vote-transactions.ts
@@ -1,5 +1,5 @@
-import { Transactions } from "@packages/crypto/src";
-import { ITransaction } from "@packages/crypto/src/interfaces";
+import { Transactions } from "@packages/crypto";
+import { ITransaction } from "@packages/crypto/dist/interfaces";
export const makeVoteTransactions = (length: number, voteAssets: string[]): ITransaction[] => {
const txs: ITransaction[] = [];
diff --git a/__tests__/unit/core-state/__utils__/transactions.ts b/__tests__/unit/core-state/__utils__/transactions.ts
index 6df3a7a4d9..a6b144e624 100644
--- a/__tests__/unit/core-state/__utils__/transactions.ts
+++ b/__tests__/unit/core-state/__utils__/transactions.ts
@@ -1,4 +1,4 @@
-import { IBlock, ITransaction } from "@packages/crypto/src/interfaces";
+import { IBlock, ITransaction } from "@packages/crypto/dist/interfaces";
export const addTransactionsToBlock = (txs: ITransaction[], block: IBlock) => {
const { data } = block;
diff --git a/__tests__/unit/core-state/block-state.test.ts b/__tests__/unit/core-state/block-state.test.ts
index d0c0c06b56..6060871f61 100644
--- a/__tests__/unit/core-state/block-state.test.ts
+++ b/__tests__/unit/core-state/block-state.test.ts
@@ -6,9 +6,7 @@ import { StateStore } from "@packages/core-state/src/stores/state";
import { Wallet } from "@packages/core-state/src/wallets";
import { WalletRepository } from "@packages/core-state/src/wallets";
import { Factories, FactoryBuilder } from "@packages/core-test-framework/src/factories";
-import { Enums, Utils } from "@packages/crypto/src";
-import { ITransaction } from "@packages/crypto/src/interfaces";
-import { IBlock } from "@packages/crypto/src/interfaces";
+import { Enums, Interfaces, Utils } from "@packages/crypto";
import { makeChainedBlocks } from "./__utils__/make-chained-block";
import { makeVoteTransactions } from "./__utils__/make-vote-transactions";
@@ -18,7 +16,7 @@ import { setUp, setUpDefaults } from "./setup";
let blockState: BlockState;
let stateStore: StateStore;
let factory: FactoryBuilder;
-let blocks: IBlock[];
+let blocks: Interfaces.IBlock[];
let walletRepo: WalletRepository;
let forgingWallet: Contracts.State.Wallet;
let votingWallet: Contracts.State.Wallet;
@@ -261,6 +259,7 @@ describe("BlockState", () => {
it("should create forger wallet if it doesn't exist genesis block", async () => {
//@ts-ignore
const spyApplyBlockToForger = jest.spyOn(blockState, "applyBlockToForger");
+ //@ts-ignore
spyApplyBlockToForger.mockImplementationOnce(() => {});
const spyCreateWallet = jest.spyOn(walletRepo, "createWallet");
blocks[0].data.height = 1;
@@ -404,7 +403,7 @@ describe("BlockState", () => {
});
describe("Multipayment", () => {
- let multiPaymentTransaction: ITransaction;
+ let multiPaymentTransaction: Interfaces.ITransaction;
let sendersDelegate: Contracts.State.Wallet;
let amount: Utils.BigNumber;
@@ -636,13 +635,13 @@ describe("BlockState", () => {
// @ts-ignore
delete voteTransaction.data.asset;
- await expect(blockState.applyTransaction(voteTransaction as ITransaction)).toReject();
+ await expect(blockState.applyTransaction(voteTransaction as Interfaces.ITransaction)).toReject();
});
});
describe("htlc transaction", () => {
- let htlcClaimTransaction: ITransaction;
- let htlcLock: ITransaction;
+ let htlcClaimTransaction: Interfaces.ITransaction;
+ let htlcLock: Interfaces.ITransaction;
let lockData;
let lockID;
diff --git a/__tests__/unit/core-state/dpos/dpos-previous-round.test.ts b/__tests__/unit/core-state/dpos/dpos-previous-round.test.ts
index b09810db41..dcb843e41e 100644
--- a/__tests__/unit/core-state/dpos/dpos-previous-round.test.ts
+++ b/__tests__/unit/core-state/dpos/dpos-previous-round.test.ts
@@ -5,7 +5,7 @@ import { RoundInfo } from "@packages/core-kernel/src/contracts/shared";
import { DposPreviousRoundStateProvider } from "@packages/core-kernel/src/contracts/state";
import { DposState } from "@packages/core-state/src/dpos/dpos";
import { WalletRepository } from "@packages/core-state/src/wallets";
-import { IBlock } from "@packages/crypto/src/interfaces";
+import { Interfaces } from "@packages/crypto";
import { buildDelegateAndVoteWallets } from "../__utils__/build-delegate-and-vote-balances";
import { makeChainedBlocks } from "../__utils__/make-chained-block";
@@ -36,7 +36,7 @@ afterEach(() => jest.clearAllMocks());
describe("dposPreviousRound", () => {
let round: RoundInfo;
- let blocks: IBlock[];
+ let blocks: Interfaces.IBlock[];
beforeEach(async () => {
walletRepo.reset();
diff --git a/__tests__/unit/core-state/round-state.test.ts b/__tests__/unit/core-state/round-state.test.ts
index 142dab6d96..be63c57cdf 100644
--- a/__tests__/unit/core-state/round-state.test.ts
+++ b/__tests__/unit/core-state/round-state.test.ts
@@ -630,6 +630,7 @@ describe("RoundState", () => {
const spyOnCalcPreviousActiveDelegates = jest
// @ts-ignore
.spyOn(roundState, "calcPreviousActiveDelegates")
+ // @ts-ignore
.mockReturnValue(delegates);
// @ts-ignore
@@ -667,6 +668,7 @@ describe("RoundState", () => {
const spyOnCalcPreviousActiveDelegates = jest
// @ts-ignore
.spyOn(roundState, "calcPreviousActiveDelegates")
+ // @ts-ignore
.mockReturnValue(delegates);
databaseService.deleteRound.mockImplementation(async () => {
diff --git a/__tests__/unit/core-state/stores/blocks.test.ts b/__tests__/unit/core-state/stores/blocks.test.ts
index 7a355d2501..fdf4389d50 100644
--- a/__tests__/unit/core-state/stores/blocks.test.ts
+++ b/__tests__/unit/core-state/stores/blocks.test.ts
@@ -1,5 +1,5 @@
-import { Interfaces } from "@packages/crypto/src";
import { BlockStore } from "@packages/core-state/src/stores/blocks";
+import { Interfaces } from "@packages/crypto";
describe("BlockStore", () => {
it("should push and get a block", () => {
diff --git a/__tests__/unit/core-state/stores/state.test.ts b/__tests__/unit/core-state/stores/state.test.ts
index a2194da958..0174033848 100644
--- a/__tests__/unit/core-state/stores/state.test.ts
+++ b/__tests__/unit/core-state/stores/state.test.ts
@@ -3,7 +3,7 @@ import "jest-extended";
import { Container } from "@packages/core-kernel/src";
import { StateStore } from "@packages/core-state/src/stores/state";
import { FactoryBuilder } from "@packages/core-test-framework/src/factories";
-import { IBlock, IBlockData, ITransactionData } from "@packages/crypto/src/interfaces";
+import { IBlock, IBlockData, ITransactionData } from "@packages/crypto/dist/interfaces";
import delay from "delay";
import { makeChainedBlocks } from "../__utils__/make-chained-block";
diff --git a/__tests__/unit/core-state/wallets/wallet-repository-copy-on-write.test.ts b/__tests__/unit/core-state/wallets/wallet-repository-copy-on-write.test.ts
index 4c175b10c8..2951b5b31c 100644
--- a/__tests__/unit/core-state/wallets/wallet-repository-copy-on-write.test.ts
+++ b/__tests__/unit/core-state/wallets/wallet-repository-copy-on-write.test.ts
@@ -49,7 +49,7 @@ describe("Wallet Repository Copy On Write", () => {
it("should find wallets by address", () => {
const spyFindByAddress = jest.spyOn(walletRepo, "findByAddress");
const clonedWallet = walletRepoCopyOnWrite.findByAddress("notexisting");
- expect(spyFindByAddress).toHaveBeenCalledWith("notexisting");
+ expect(spyFindByAddress).not.toBeCalled();
const originalWallet = walletRepo.findByAddress(clonedWallet.getAddress());
expect(originalWallet).not.toBe(clonedWallet);
});
diff --git a/__tests__/unit/core-transaction-pool/mempool-index-registry.test.ts b/__tests__/unit/core-transaction-pool/mempool-index-registry.test.ts
new file mode 100644
index 0000000000..9795075f5b
--- /dev/null
+++ b/__tests__/unit/core-transaction-pool/mempool-index-registry.test.ts
@@ -0,0 +1,48 @@
+import "jest-extended";
+
+import { Application, Container } from "@packages/core-kernel";
+
+import { MempoolIndex } from "../../../packages/core-transaction-pool/src/mempool-index";
+import { MempoolIndexRegistry } from "../../../packages/core-transaction-pool/src/mempool-index-registry";
+
+describe("MempoolIndex", () => {
+ let app: Application;
+
+ beforeEach(() => {
+ app = new Application(new Container.Container());
+ });
+
+ it("should initialize indexes", () => {
+ const index1 = "index1";
+ const index2 = "index2";
+
+ app.bind(Container.Identifiers.TransactionPoolMempoolIndex).toConstantValue(index1);
+ app.bind(Container.Identifiers.TransactionPoolMempoolIndex).toConstantValue(index2);
+
+ const mempoolIndexRegistry = app.resolve(MempoolIndexRegistry);
+
+ expect(mempoolIndexRegistry.get(index1)).toBeInstanceOf(MempoolIndex);
+ expect(mempoolIndexRegistry.get(index2)).toBeInstanceOf(MempoolIndex);
+ });
+
+ it("get should throw if index is not registered", () => {
+ const index1 = "index1";
+
+ const mempoolIndexRegistry = app.resolve(MempoolIndexRegistry);
+
+ expect(() => mempoolIndexRegistry.get(index1)).toThrowError(`Index ${index1} does not exists`);
+ });
+
+ it("clear should clear indexes", () => {
+ const spyOnClear = jest.spyOn(MempoolIndex.prototype, "clear");
+
+ const index1 = "index1";
+ app.bind(Container.Identifiers.TransactionPoolMempoolIndex).toConstantValue(index1);
+
+ const mempoolIndexRegistry = app.resolve(MempoolIndexRegistry);
+
+ mempoolIndexRegistry.clear();
+
+ expect(spyOnClear).toBeCalledTimes(1);
+ });
+});
diff --git a/__tests__/unit/core-transaction-pool/mempool-index.test.ts b/__tests__/unit/core-transaction-pool/mempool-index.test.ts
new file mode 100644
index 0000000000..639b16f770
--- /dev/null
+++ b/__tests__/unit/core-transaction-pool/mempool-index.test.ts
@@ -0,0 +1,63 @@
+import "jest-extended";
+
+import { Interfaces } from "@arkecosystem/crypto";
+
+import { MempoolIndex } from "../../../packages/core-transaction-pool/src/mempool-index";
+
+describe("MempoolIndex", () => {
+ let index: MempoolIndex;
+ let transaction: Interfaces.ITransaction;
+
+ beforeEach(() => {
+ index = new MempoolIndex();
+ transaction = {} as Interfaces.ITransaction;
+ });
+
+ it("should set key", () => {
+ const key = "key";
+
+ expect(index.has(key)).toBeFalse();
+
+ index.set(key, transaction);
+
+ expect(index.has(key)).toBeTrue();
+ });
+
+ it("should get key", () => {
+ const key = "key";
+
+ index.set(key, transaction);
+
+ expect(index.get(key)).toEqual(transaction);
+
+ index.forget(key);
+
+ expect(() => {
+ index.get(key);
+ }).toThrow();
+ });
+
+ it("should forget key", () => {
+ const key = "key";
+
+ index.set(key, transaction);
+
+ expect(index.has(key)).toBeTrue();
+
+ index.forget(key);
+
+ expect(index.has(key)).toBeFalse();
+ });
+
+ it("should clear all keys", () => {
+ const key = "key";
+
+ index.set(key, transaction);
+
+ expect(index.has(key)).toBeTrue();
+
+ index.clear();
+
+ expect(index.has(key)).toBeFalse();
+ });
+});
diff --git a/__tests__/unit/core-transaction-pool/mempool.test.ts b/__tests__/unit/core-transaction-pool/mempool.test.ts
index 946cafebbf..dce63b36b9 100644
--- a/__tests__/unit/core-transaction-pool/mempool.test.ts
+++ b/__tests__/unit/core-transaction-pool/mempool.test.ts
@@ -1,14 +1,19 @@
-import { Container } from "@arkecosystem/core-kernel";
-import { Identities, Interfaces } from "@arkecosystem/crypto";
-
-import { Mempool } from "../../../packages/core-transaction-pool/src/mempool";
+import { Application, Container } from "@packages/core-kernel";
+import { Mempool } from "@packages/core-transaction-pool/src/mempool";
+import { Identities, Interfaces } from "@packages/crypto";
const createSenderMempool = jest.fn();
const logger = { debug: jest.fn() };
+const mempoolIndexRegistry = { clear: jest.fn() };
+const handlerRegistry = {
+ getActivatedHandlerForData: jest.fn(),
+};
-const container = new Container.Container();
-container.bind(Container.Identifiers.TransactionPoolSenderMempoolFactory).toConstantValue(createSenderMempool);
-container.bind(Container.Identifiers.LogService).toConstantValue(logger);
+const app = new Application(new Container.Container());
+app.bind(Container.Identifiers.TransactionPoolSenderMempoolFactory).toConstantValue(createSenderMempool);
+app.bind(Container.Identifiers.TransactionPoolMempoolIndexRegistry).toConstantValue(mempoolIndexRegistry);
+app.bind(Container.Identifiers.LogService).toConstantValue(logger);
+app.bind(Container.Identifiers.TransactionHandlerRegistry).toConstantValue(handlerRegistry);
beforeEach(() => {
createSenderMempool.mockReset();
@@ -45,7 +50,7 @@ describe("Mempool.getSize", () => {
data: { senderPublicKey: Identities.PublicKey.fromPassphrase("sender2") },
} as Interfaces.ITransaction;
- const memory = container.resolve(Mempool);
+ const memory = app.resolve(Mempool);
await memory.addTransaction(transaction1);
await memory.addTransaction(transaction2);
const size = memory.getSize();
@@ -68,7 +73,7 @@ describe("Mempool.hasSenderMempool", () => {
data: { senderPublicKey: Identities.PublicKey.fromPassphrase("sender") },
} as Interfaces.ITransaction;
- const memory = container.resolve(Mempool);
+ const memory = app.resolve(Mempool);
await memory.addTransaction(transaction);
const has = memory.hasSenderMempool(transaction.data.senderPublicKey);
@@ -88,7 +93,7 @@ describe("Mempool.hasSenderMempool", () => {
data: { senderPublicKey: Identities.PublicKey.fromPassphrase("sender") },
} as Interfaces.ITransaction;
- const memory = container.resolve(Mempool);
+ const memory = app.resolve(Mempool);
await memory.addTransaction(transaction);
const has = memory.hasSenderMempool(Identities.PublicKey.fromPassphrase("not sender"));
@@ -110,7 +115,7 @@ describe("Mempool.getSenderMempool", () => {
data: { senderPublicKey: Identities.PublicKey.fromPassphrase("sender") },
} as Interfaces.ITransaction;
- const memory = container.resolve(Mempool);
+ const memory = app.resolve(Mempool);
await memory.addTransaction(transaction);
expect(memory.getSenderMempool(transaction.data.senderPublicKey)).toBe(senderMempool);
@@ -129,7 +134,7 @@ describe("Mempool.getSenderMempool", () => {
data: { senderPublicKey: Identities.PublicKey.fromPassphrase("sender") },
} as Interfaces.ITransaction;
- const memory = container.resolve(Mempool);
+ const memory = app.resolve(Mempool);
await memory.addTransaction(transaction);
const cb = () => memory.getSenderMempool(Identities.PublicKey.fromPassphrase("not sender"));
@@ -163,7 +168,7 @@ describe("Mempool.getSenderMempools", () => {
data: { senderPublicKey: Identities.PublicKey.fromPassphrase("sender2") },
} as Interfaces.ITransaction;
- const memory = container.resolve(Mempool);
+ const memory = app.resolve(Mempool);
await memory.addTransaction(transaction1);
await memory.addTransaction(transaction2);
const senderMempools = memory.getSenderMempools();
@@ -186,7 +191,7 @@ describe("Mempool.addTransaction", () => {
data: { senderPublicKey: Identities.PublicKey.fromPassphrase("sender1") },
} as Interfaces.ITransaction;
- const memory = container.resolve(Mempool);
+ const memory = app.resolve(Mempool);
await memory.addTransaction(transaction);
expect(senderMempool.addTransaction).toBeCalledWith(transaction);
@@ -209,7 +214,7 @@ describe("Mempool.addTransaction", () => {
data: { senderPublicKey: Identities.PublicKey.fromPassphrase("sender1") },
} as Interfaces.ITransaction;
- const memory = container.resolve(Mempool);
+ const memory = app.resolve(Mempool);
const promise = memory.addTransaction(transaction);
await expect(promise).rejects.toThrow(error);
const has = memory.hasSenderMempool(transaction.data.senderPublicKey);
@@ -226,7 +231,7 @@ describe("Mempool.removeTransaction", () => {
data: { senderPublicKey: Identities.PublicKey.fromPassphrase("sender1") },
} as Interfaces.ITransaction;
- const memory = container.resolve(Mempool);
+ const memory = app.resolve(Mempool);
const removedTransactions = await memory.removeTransaction(transaction.data.senderPublicKey, transaction.id);
expect(removedTransactions).toStrictEqual([]);
@@ -247,7 +252,7 @@ describe("Mempool.removeTransaction", () => {
senderMempool.isDisposable.mockReturnValue(false);
createSenderMempool.mockReturnValueOnce(senderMempool);
- const memory = container.resolve(Mempool);
+ const memory = app.resolve(Mempool);
await memory.addTransaction(transaction);
const removedTransactions = await memory.removeTransaction(transaction.data.senderPublicKey, transaction.id);
@@ -272,7 +277,7 @@ describe("Mempool.removeTransaction", () => {
senderMempool.isDisposable.mockReturnValueOnce(false).mockReturnValueOnce(true);
createSenderMempool.mockReturnValueOnce(senderMempool);
- const memory = container.resolve(Mempool);
+ const memory = app.resolve(Mempool);
await memory.addTransaction(transaction);
const promise = memory.removeTransaction(transaction.data.senderPublicKey, transaction.id);
await expect(promise).rejects.toThrow(error);
@@ -283,68 +288,263 @@ describe("Mempool.removeTransaction", () => {
});
});
-describe("Mempool.removeForgedTransaction", () => {
- it("should return empty array when accepting transaction of sender that wasn't previously added", async () => {
- const memory = container.resolve(Mempool);
- const removedTransactions = await memory.removeForgedTransaction(
- Identities.PublicKey.fromPassphrase("sender1"),
- "none",
- );
+describe("applyBlock", () => {
+ it("should return empty list when block doesn't contains transactions", async () => {
+ const block = {
+ transactions: [],
+ } as Interfaces.IBlock;
- expect(removedTransactions).toStrictEqual([]);
+ const mempool = app.resolve(Mempool);
+
+ await expect(mempool.applyBlock(block)).resolves.toEqual([]);
});
- it("should remove previously added transaction and return list of removed transactions", async () => {
+ it("should return empty list when block contains transactions that are not in pool", async () => {
+ const handler = {
+ onPoolLeave: jest.fn(),
+ getInvalidPoolTransactions: jest.fn().mockResolvedValue([]),
+ };
+
+ handlerRegistry.getActivatedHandlerForData.mockReturnValue(handler);
+
const transaction = {
id: "transaction-id",
data: { senderPublicKey: Identities.PublicKey.fromPassphrase("sender1") },
} as Interfaces.ITransaction;
+ const block = {
+ transactions: [transaction],
+ } as Interfaces.IBlock;
+
+ const mempool = app.resolve(Mempool);
+
+ await expect(mempool.applyBlock(block)).resolves.toEqual([]);
+
+ expect(handler.onPoolLeave).not.toBeCalled();
+ expect(handler.getInvalidPoolTransactions).toBeCalledTimes(1);
+ });
+
+ it("should return empty list when block contains transactions that are in pool", async () => {
+ const handler = {
+ onPoolLeave: jest.fn().mockImplementation(async () => {}),
+ getInvalidPoolTransactions: jest.fn().mockResolvedValue([]),
+ };
+
+ handlerRegistry.getActivatedHandlerForData.mockReturnValue(handler);
+
const senderMempool = {
addTransaction: jest.fn(),
- removeForgedTransaction: jest.fn(),
- isDisposable: jest.fn(),
+ isDisposable: jest.fn().mockReturnValueOnce(false).mockReturnValueOnce(true),
+ removeForgedTransaction: jest.fn().mockReturnValue(true),
};
- senderMempool.removeForgedTransaction.mockReturnValue([transaction]);
- senderMempool.isDisposable.mockReturnValue(false);
createSenderMempool.mockReturnValueOnce(senderMempool);
- const memory = container.resolve(Mempool);
- await memory.addTransaction(transaction);
- const removedTransactions = await memory.removeForgedTransaction(
- transaction.data.senderPublicKey,
- transaction.id,
- );
+ const transaction = {
+ id: "transaction-id",
+ data: { senderPublicKey: Identities.PublicKey.fromPassphrase("sender1") },
+ } as Interfaces.ITransaction;
- expect(senderMempool.removeForgedTransaction).toBeCalledWith(transaction.id);
- expect(removedTransactions).toEqual([transaction]);
- expect(logger.debug).toHaveBeenCalledTimes(1);
+ const mempool = app.resolve(Mempool);
+ await mempool.addTransaction(transaction);
+
+ const block = {
+ transactions: [transaction],
+ } as Interfaces.IBlock;
+
+ await expect(mempool.applyBlock(block)).resolves.toEqual([]);
+
+ expect(handler.onPoolLeave).toBeCalledTimes(1);
+ expect(handler.getInvalidPoolTransactions).toBeCalledTimes(1);
+ expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining("Removed forged"));
});
- it("should forget sender state if it's empty even if error was thrown", async () => {
- const error = new Error("Something went horribly wrong");
+ it("should return list of invalid transactions when forged transaction is not first in the list", async () => {
const transaction = {
id: "transaction-id",
data: { senderPublicKey: Identities.PublicKey.fromPassphrase("sender1") },
} as Interfaces.ITransaction;
+ const invalidTransaction = {
+ id: "invalid-transaction-id",
+ data: { senderPublicKey: Identities.PublicKey.fromPassphrase("sender1") },
+ } as Interfaces.ITransaction;
+
+ const block = {
+ transactions: [transaction],
+ } as Interfaces.IBlock;
+
+ const handler = {
+ onPoolLeave: jest.fn(),
+ getInvalidPoolTransactions: jest.fn().mockResolvedValue([]),
+ };
+
+ handlerRegistry.getActivatedHandlerForData.mockReturnValue(handler);
+
const senderMempool = {
addTransaction: jest.fn(),
- removeForgedTransaction: jest.fn(),
- isDisposable: jest.fn(),
+ isDisposable: jest.fn().mockReturnValue(false),
+ removeForgedTransaction: jest.fn().mockReturnValue(false),
+ getFromLatest: jest.fn().mockReturnValue([invalidTransaction]),
+ getFromEarliest: jest.fn().mockReturnValue([invalidTransaction]),
};
- senderMempool.removeForgedTransaction.mockRejectedValueOnce(error);
- senderMempool.isDisposable.mockReturnValueOnce(false).mockReturnValueOnce(true);
- createSenderMempool.mockReturnValueOnce(senderMempool);
- const memory = container.resolve(Mempool);
- await memory.addTransaction(transaction);
- const promise = memory.removeForgedTransaction(transaction.data.senderPublicKey, transaction.id);
- await expect(promise).rejects.toThrow(error);
- const has = memory.hasSenderMempool(transaction.data.senderPublicKey);
+ const newSenderMempool = {
+ addTransaction: jest.fn().mockImplementation(() => {
+ throw new Error("Transaction error");
+ }),
+ getSize: jest.fn().mockReturnValue(0),
+ };
+ createSenderMempool.mockReturnValueOnce(senderMempool).mockReturnValueOnce(newSenderMempool);
- expect(logger.debug).toHaveBeenCalledTimes(2);
- expect(has).toBe(false);
+ const mempool = app.resolve(Mempool);
+
+ await mempool.addTransaction(invalidTransaction);
+
+ await expect(mempool.applyBlock(block)).resolves.toEqual([invalidTransaction]);
+
+ expect(handler.onPoolLeave).toBeCalledTimes(1);
+ expect(handler.onPoolLeave).toBeCalledWith(invalidTransaction);
+ expect(handler.getInvalidPoolTransactions).toBeCalledTimes(1);
+ expect(handler.getInvalidPoolTransactions).toBeCalledWith(transaction);
+ expect(newSenderMempool.addTransaction).toBeCalledTimes(1);
+ expect(newSenderMempool.addTransaction).toBeCalledWith(invalidTransaction);
+ expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining("Removed invalid"));
+ });
+
+ it("should return list of invalid transactions when getInvalidTransactions returns transaction", async () => {
+ const transaction = {
+ id: "transaction-id",
+ data: { senderPublicKey: Identities.PublicKey.fromPassphrase("sender1") },
+ } as Interfaces.ITransaction;
+
+ const invalidTransaction = {
+ id: "invalid-transaction-id",
+ data: { senderPublicKey: Identities.PublicKey.fromPassphrase("sender2") },
+ } as Interfaces.ITransaction;
+
+ const block = {
+ transactions: [transaction],
+ } as Interfaces.IBlock;
+
+ const handler = {
+ onPoolLeave: jest.fn(),
+ getInvalidPoolTransactions: jest.fn().mockResolvedValue([invalidTransaction]),
+ };
+
+ handlerRegistry.getActivatedHandlerForData.mockReturnValue(handler);
+
+ const senderMempool = {
+ addTransaction: jest.fn(),
+ isDisposable: jest.fn().mockReturnValueOnce(false).mockReturnValueOnce(true),
+ removeForgedTransaction: jest.fn().mockReturnValue(true),
+ };
+
+ const secondSenderMempool = {
+ addTransaction: jest.fn(),
+ isDisposable: jest.fn().mockReturnValue(false),
+ getFromLatest: jest.fn().mockReturnValue([invalidTransaction]),
+ getFromEarliest: jest.fn().mockReturnValue([invalidTransaction]),
+ };
+
+ const newSenderMempool = {
+ addTransaction: jest.fn().mockImplementation(() => {
+ throw new Error("Transaction error");
+ }),
+ getSize: jest.fn().mockReturnValue(0),
+ };
+ createSenderMempool
+ .mockReturnValueOnce(senderMempool)
+ .mockReturnValueOnce(secondSenderMempool)
+ .mockReturnValueOnce(newSenderMempool);
+
+ const mempool = app.resolve(Mempool);
+
+ await mempool.addTransaction(transaction);
+ await mempool.addTransaction(invalidTransaction);
+
+ await expect(mempool.applyBlock(block)).resolves.toEqual([invalidTransaction]);
+
+ expect(handler.onPoolLeave).toBeCalledTimes(2);
+ expect(handler.onPoolLeave).toBeCalledWith(transaction);
+ expect(handler.onPoolLeave).toBeCalledWith(invalidTransaction);
+ expect(handler.getInvalidPoolTransactions).toBeCalledTimes(1);
+ expect(handler.getInvalidPoolTransactions).toBeCalledWith(transaction);
+ expect(newSenderMempool.addTransaction).toBeCalledTimes(1);
+ expect(newSenderMempool.addTransaction).toBeCalledWith(invalidTransaction);
+ expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining("Removed invalid"));
+ });
+
+ it("should return list of invalid transactions when getInvalidTransactions returns transaction and try to readd sender transactions", async () => {
+ const transaction = {
+ id: "transaction-id",
+ data: { senderPublicKey: Identities.PublicKey.fromPassphrase("sender1") },
+ } as Interfaces.ITransaction;
+
+ const secondTransaction = {
+ id: "second-transaction-id",
+ data: { senderPublicKey: Identities.PublicKey.fromPassphrase("sender2") },
+ } as Interfaces.ITransaction;
+
+ const invalidTransaction = {
+ id: "invalid-transaction-id",
+ data: { senderPublicKey: Identities.PublicKey.fromPassphrase("sender2") },
+ } as Interfaces.ITransaction;
+
+ const block = {
+ transactions: [transaction],
+ } as Interfaces.IBlock;
+
+ const handler = {
+ onPoolLeave: jest.fn(),
+ getInvalidPoolTransactions: jest.fn().mockResolvedValue([invalidTransaction]),
+ };
+
+ handlerRegistry.getActivatedHandlerForData.mockReturnValue(handler);
+
+ const senderMempool = {
+ addTransaction: jest.fn(),
+ isDisposable: jest.fn().mockReturnValueOnce(false).mockReturnValueOnce(true),
+ removeForgedTransaction: jest.fn().mockReturnValue(true),
+ };
+
+ const secondSenderMempool = {
+ addTransaction: jest.fn(),
+ isDisposable: jest.fn().mockReturnValue(false),
+ getFromLatest: jest.fn().mockReturnValue([invalidTransaction, secondTransaction]),
+ getFromEarliest: jest.fn().mockReturnValue([secondTransaction, invalidTransaction]),
+ };
+
+ const newSenderMempool = {
+ addTransaction: jest
+ .fn()
+ .mockImplementationOnce(() => {})
+ .mockImplementation(() => {
+ throw new Error("Transaction error");
+ }),
+ getSize: jest.fn().mockReturnValue(1),
+ };
+ createSenderMempool
+ .mockReturnValueOnce(senderMempool)
+ .mockReturnValueOnce(secondSenderMempool)
+ .mockReturnValueOnce(newSenderMempool);
+
+ const mempool = app.resolve(Mempool);
+
+ await mempool.addTransaction(transaction);
+ await mempool.addTransaction(secondTransaction);
+
+ await expect(mempool.applyBlock(block)).resolves.toEqual([invalidTransaction]);
+
+ expect(handler.onPoolLeave).toBeCalledTimes(3);
+ expect(handler.onPoolLeave).toBeCalledWith(transaction);
+ expect(handler.onPoolLeave).toBeCalledWith(secondTransaction);
+ expect(handler.onPoolLeave).toBeCalledWith(invalidTransaction);
+ expect(handler.getInvalidPoolTransactions).toBeCalledTimes(1);
+ expect(handler.getInvalidPoolTransactions).toBeCalledWith(transaction);
+ expect(newSenderMempool.addTransaction).toBeCalledTimes(2);
+ expect(newSenderMempool.addTransaction).toBeCalledWith(secondTransaction);
+ expect(newSenderMempool.addTransaction).toBeCalledWith(invalidTransaction);
+ expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining("Removed invalid"));
});
});
@@ -362,11 +562,12 @@ describe("Mempool.flush", () => {
data: { senderPublicKey: Identities.PublicKey.fromPassphrase("sender1") },
} as Interfaces.ITransaction;
- const memory = container.resolve(Mempool);
+ const memory = app.resolve(Mempool);
await memory.addTransaction(transaction);
memory.flush();
const has = memory.hasSenderMempool(transaction.data.senderPublicKey);
expect(has).toBe(false);
+ expect(mempoolIndexRegistry.clear).toBeCalled();
});
});
diff --git a/__tests__/unit/core-transaction-pool/sender-mempool.test.ts b/__tests__/unit/core-transaction-pool/sender-mempool.test.ts
index b163e8e988..ec58622bf8 100644
--- a/__tests__/unit/core-transaction-pool/sender-mempool.test.ts
+++ b/__tests__/unit/core-transaction-pool/sender-mempool.test.ts
@@ -1,7 +1,8 @@
-import { Container, Contracts } from "@arkecosystem/core-kernel";
-import { Identities, Managers, Transactions } from "@arkecosystem/crypto";
+import "jest-extended";
-import { SenderMempool } from "../../../packages/core-transaction-pool/src/sender-mempool";
+import { Container, Contracts } from "@packages/core-kernel";
+import { SenderMempool } from "@packages/core-transaction-pool/src/sender-mempool";
+import { Identities, Managers, Transactions } from "@packages/crypto";
const configuration = { getRequired: jest.fn(), getOptional: jest.fn() };
const senderState = { apply: jest.fn(), revert: jest.fn() };
@@ -192,32 +193,29 @@ describe("SenderMempool.removeTransaction", () => {
});
describe("SenderMempool.removeForgedTransaction", () => {
- it("should return all transactions that were added before transaction being accepted", async () => {
- configuration.getRequired.mockReturnValueOnce(10); // maxTransactionsPerSender
+ it("should throw an error if there is no transactions in sender mempool", async () => {
+ const senderMempool = container.resolve(SenderMempool);
+
+ await expect(senderMempool.removeForgedTransaction(transaction1.id)).rejects.toThrowError(
+ "No transactions in sender mempool",
+ );
+ });
+ it("should return true when forged transaction is earliest", async () => {
const senderMempool = container.resolve(SenderMempool);
+
await senderMempool.addTransaction(transaction1);
await senderMempool.addTransaction(transaction2);
- await senderMempool.addTransaction(transaction3);
-
- const removedTransactions = await senderMempool.removeForgedTransaction(transaction2.id);
- const remainingTransactions = senderMempool.getFromEarliest();
- expect(removedTransactions).toStrictEqual([transaction1, transaction2]);
- expect(remainingTransactions).toStrictEqual([transaction3]);
+ await expect(senderMempool.removeForgedTransaction(transaction1.id)).resolves.toBeTrue();
});
- it("should return no transactions when accepting unknown transaction", async () => {
- configuration.getRequired.mockReturnValueOnce(10); // maxTransactionsPerSender
-
+ it("should return false when forged transaction is not earliest", async () => {
const senderMempool = container.resolve(SenderMempool);
+
await senderMempool.addTransaction(transaction1);
await senderMempool.addTransaction(transaction2);
- const removedTransactions = await senderMempool.removeForgedTransaction(transaction3.id);
- const remainingTransactions = senderMempool.getFromEarliest();
-
- expect(removedTransactions).toStrictEqual([]);
- expect(remainingTransactions).toStrictEqual([transaction1, transaction2]);
+ await expect(senderMempool.removeForgedTransaction(transaction2.id)).resolves.toBeFalse();
});
});
diff --git a/__tests__/unit/core-transaction-pool/sender-state.test.ts b/__tests__/unit/core-transaction-pool/sender-state.test.ts
index e079ebd0d6..2d7fe2e8fc 100644
--- a/__tests__/unit/core-transaction-pool/sender-state.test.ts
+++ b/__tests__/unit/core-transaction-pool/sender-state.test.ts
@@ -177,6 +177,8 @@ describe("SenderState.apply", () => {
expect(triggers.call).toBeCalledWith("verifyTransaction", { handler, transaction });
expect(triggers.call).toBeCalledWith("throwIfCannotEnterPool", { handler, transaction });
expect(triggers.call).toBeCalledWith("applyTransaction", { handler, transaction });
+
+ expect(triggers.call).not.toBeCalledWith("onPoolEnter", { handler, transaction });
});
it("should call handler to apply transaction", async () => {
@@ -198,6 +200,7 @@ describe("SenderState.apply", () => {
expect(triggers.call).toBeCalledWith("verifyTransaction", { handler, transaction });
expect(triggers.call).toBeCalledWith("throwIfCannotEnterPool", { handler, transaction });
expect(triggers.call).toBeCalledWith("applyTransaction", { handler, transaction });
+ expect(triggers.call).toBeCalledWith("onPoolEnter", { handler, transaction });
});
});
@@ -213,5 +216,6 @@ describe("SenderState.revert", () => {
expect(handlerRegistry.getActivatedHandlerForData).toBeCalledWith(transaction.data);
expect(triggers.call).toBeCalledWith("revertTransaction", { handler, transaction });
+ expect(triggers.call).toBeCalledWith("onPoolLeave", { handler, transaction });
});
});
diff --git a/__tests__/unit/core-transaction-pool/service.test.ts b/__tests__/unit/core-transaction-pool/service.test.ts
index 3e3fc1e0fb..981d93eaf0 100644
--- a/__tests__/unit/core-transaction-pool/service.test.ts
+++ b/__tests__/unit/core-transaction-pool/service.test.ts
@@ -1,7 +1,8 @@
-import { Container, Contracts, Enums } from "@arkecosystem/core-kernel";
-import { Identities, Managers, Transactions } from "@arkecosystem/crypto";
+import "jest-extended";
-import { Service } from "../../../packages/core-transaction-pool/src/service";
+import { Container, Contracts, Enums } from "@packages/core-kernel";
+import { Service } from "@packages/core-transaction-pool/src/service";
+import { Identities, Interfaces, Managers, Transactions } from "@packages/crypto";
const configuration = {
getRequired: jest.fn(),
@@ -25,8 +26,15 @@ const mempool = {
addTransaction: jest.fn(),
removeTransaction: jest.fn(),
removeForgedTransaction: jest.fn(),
+ applyBlock: jest.fn(),
flush: jest.fn(),
};
+const secondSignatureVerification = {
+ clear: jest.fn(),
+};
+const multiSignatureVerification = {
+ clear: jest.fn(),
+};
const poolQuery = {
getAll: jest.fn(),
getFromLowestPriority: jest.fn(),
@@ -54,6 +62,10 @@ container.bind(Container.Identifiers.TransactionPoolStorage).toConstantValue(sto
container.bind(Container.Identifiers.TransactionPoolMempool).toConstantValue(mempool);
container.bind(Container.Identifiers.TransactionPoolQuery).toConstantValue(poolQuery);
container.bind(Container.Identifiers.TransactionPoolExpirationService).toConstantValue(expirationService);
+container
+ .bind(Container.Identifiers.TransactionSecondSignatureVerification)
+ .toConstantValue(secondSignatureVerification);
+container.bind(Container.Identifiers.TransactionMultiSignatureVerification).toConstantValue(multiSignatureVerification);
container.bind(Container.Identifiers.EventDispatcherService).toConstantValue(events);
container.bind(Container.Identifiers.LogService).toConstantValue(logger);
@@ -106,9 +118,7 @@ describe("Service.boot", () => {
const service = container.resolve(Service);
await service.boot();
- expect(events.listen).toBeCalledWith(Enums.StateEvent.BuilderFinished, service);
expect(events.listen).toBeCalledWith(Enums.CryptoEvent.MilestoneChanged, service);
- expect(events.listen).toBeCalledWith(Enums.BlockEvent.Applied, service);
});
});
@@ -117,22 +127,11 @@ describe("Service.dispose", () => {
const service = container.resolve(Service);
service.dispose();
- expect(events.forget).toBeCalledWith(Enums.StateEvent.BuilderFinished, service);
expect(events.forget).toBeCalledWith(Enums.CryptoEvent.MilestoneChanged, service);
- expect(events.forget).toBeCalledWith(Enums.BlockEvent.Applied, service);
});
});
describe("Service.handle", () => {
- it("should re-add transactions after state builder had finished", async () => {
- const service = container.resolve(Service);
- jest.spyOn(service, "readdTransactions").mockImplementation(() => Promise.resolve());
-
- await service.handle({ name: Enums.StateEvent.BuilderFinished });
-
- expect(service.readdTransactions).toBeCalled();
- });
-
it("should re-add transactions after milestone had changed", async () => {
const service = container.resolve(Service);
jest.spyOn(service, "readdTransactions").mockImplementation(() => Promise.resolve());
@@ -141,15 +140,6 @@ describe("Service.handle", () => {
expect(service.readdTransactions).toBeCalled();
});
-
- it("should cleanup transactions after block is applied", async () => {
- const service = container.resolve(Service);
- jest.spyOn(service, "cleanUp").mockImplementation(() => Promise.resolve());
-
- await service.handle({ name: Enums.BlockEvent.Applied });
-
- expect(service.cleanUp).toBeCalled();
- });
});
describe("Service.getPoolSize", () => {
@@ -215,6 +205,8 @@ describe("Service.addTransaction", () => {
serialized: transaction1.serialized,
});
expect(storage.removeTransaction).toBeCalledWith(transaction1.id);
+ expect(secondSignatureVerification.clear).toBeCalledWith(transaction1.id);
+ expect(multiSignatureVerification.clear).toBeCalledWith(transaction1.id);
expect(events.dispatch).toHaveBeenCalledWith(Enums.TransactionEvent.RejectedByPool, expect.anything());
});
@@ -238,6 +230,8 @@ describe("Service.addTransaction", () => {
serialized: transaction1.serialized,
});
expect(storage.removeTransaction).toBeCalledWith(transaction1.id);
+ expect(secondSignatureVerification.clear).toBeCalledWith(transaction1.id);
+ expect(multiSignatureVerification.clear).toBeCalledWith(transaction1.id);
expect(events.dispatch).toHaveBeenCalledWith(Enums.TransactionEvent.RejectedByPool, expect.anything());
});
@@ -261,6 +255,8 @@ describe("Service.addTransaction", () => {
expect(storage.getOldTransactions).toBeCalledWith(900);
expect(mempool.removeTransaction).toBeCalledWith(transaction2.data.senderPublicKey, transaction2.id);
expect(storage.removeTransaction).toBeCalledWith(transaction2.id);
+ expect(secondSignatureVerification.clear).toBeCalledWith(transaction2.id);
+ expect(multiSignatureVerification.clear).toBeCalledWith(transaction2.id);
expect(events.dispatch).toHaveBeenCalledWith(Enums.TransactionEvent.Expired, expect.anything());
});
@@ -282,6 +278,8 @@ describe("Service.addTransaction", () => {
expect(expirationService.isExpired).toBeCalledWith(transaction2);
expect(mempool.removeTransaction).toBeCalledWith(transaction2.data.senderPublicKey, transaction2.id);
expect(storage.removeTransaction).toBeCalledWith(transaction2.id);
+ expect(secondSignatureVerification.clear).toBeCalledWith(transaction2.id);
+ expect(multiSignatureVerification.clear).toBeCalledWith(transaction2.id);
expect(events.dispatch).toHaveBeenCalledWith(Enums.TransactionEvent.Expired, expect.anything());
});
@@ -331,6 +329,8 @@ describe("Service.addTransaction", () => {
expect(mempool.removeTransaction).toBeCalledWith(transaction1.data.senderPublicKey, transaction1.id);
expect(storage.removeTransaction).toBeCalledWith(transaction1.id);
+ expect(secondSignatureVerification.clear).toBeCalledWith(transaction1.id);
+ expect(multiSignatureVerification.clear).toBeCalledWith(transaction1.id);
expect(events.dispatch).toHaveBeenCalledWith(Enums.TransactionEvent.RemovedFromPool, expect.anything());
});
@@ -356,6 +356,10 @@ describe("Service.removeTransaction", () => {
expect(mempool.removeTransaction).toBeCalledWith(transaction1.data.senderPublicKey, transaction1.id);
expect(storage.removeTransaction).toBeCalledWith(transaction1.id);
expect(storage.removeTransaction).toBeCalledWith(transaction2.id);
+ expect(secondSignatureVerification.clear).toBeCalledWith(transaction1.id);
+ expect(multiSignatureVerification.clear).toBeCalledWith(transaction1.id);
+ expect(secondSignatureVerification.clear).toBeCalledWith(transaction2.id);
+ expect(multiSignatureVerification.clear).toBeCalledWith(transaction2.id);
expect(events.dispatch).toHaveBeenCalledWith(Enums.TransactionEvent.RemovedFromPool, expect.anything());
});
@@ -370,42 +374,10 @@ describe("Service.removeTransaction", () => {
expect(mempool.removeTransaction).toBeCalledWith(transaction1.data.senderPublicKey, transaction1.id);
expect(storage.removeTransaction).toBeCalledWith(transaction1.id);
expect(storage.removeTransaction).toBeCalledWith(transaction2.id);
- expect(logger.error).toBeCalled();
- });
-});
-
-describe("Service.removeForgedTransaction", () => {
- it("should do nothing if transaction wasn't added previously", async () => {
- storage.hasTransaction.mockReturnValueOnce(false);
-
- const service = container.resolve(Service);
- await service.removeForgedTransaction(transaction1);
-
- expect(mempool.removeForgedTransaction).not.toBeCalled();
- });
-
- it("should remove from storage every transaction removed by mempool", async () => {
- storage.hasTransaction.mockReturnValueOnce(true);
- mempool.removeForgedTransaction.mockReturnValueOnce([transaction1, transaction2]);
-
- const service = container.resolve(Service);
- await service.removeForgedTransaction(transaction1);
-
- expect(mempool.removeForgedTransaction).toBeCalledWith(transaction1.data.senderPublicKey, transaction1.id);
- expect(storage.removeTransaction).toBeCalledWith(transaction1.id);
- expect(storage.removeTransaction).toBeCalledWith(transaction2.id);
- });
-
- it("should log error if transaction wasn't found in mempool", async () => {
- storage.hasTransaction.mockReturnValueOnce(true);
- mempool.removeForgedTransaction.mockReturnValueOnce([transaction2]);
-
- const service = container.resolve(Service);
- await service.removeForgedTransaction(transaction1);
-
- expect(mempool.removeForgedTransaction).toBeCalledWith(transaction1.data.senderPublicKey, transaction1.id);
- expect(storage.removeTransaction).toBeCalledWith(transaction1.id);
- expect(storage.removeTransaction).toBeCalledWith(transaction2.id);
+ expect(secondSignatureVerification.clear).toBeCalledWith(transaction1.id);
+ expect(multiSignatureVerification.clear).toBeCalledWith(transaction1.id);
+ expect(secondSignatureVerification.clear).toBeCalledWith(transaction2.id);
+ expect(multiSignatureVerification.clear).toBeCalledWith(transaction2.id);
expect(logger.error).toBeCalled();
});
});
@@ -445,6 +417,8 @@ describe("Service.readdTransactions", () => {
expect(mempool.addTransaction).toBeCalledWith(transaction3);
expect(storage.removeTransaction).toBeCalledTimes(0);
+ expect(secondSignatureVerification.clear).toBeCalledTimes(0);
+ expect(multiSignatureVerification.clear).toBeCalledTimes(0);
});
it("should remove transaction from storage that cannot be added to mempool", async () => {
@@ -470,6 +444,9 @@ describe("Service.readdTransactions", () => {
expect(storage.removeTransaction).toBeCalledTimes(1);
expect(storage.removeTransaction).toBeCalledWith(transaction1.id);
+
+ expect(secondSignatureVerification.clear).toBeCalledWith(transaction1.id);
+ expect(multiSignatureVerification.clear).toBeCalledWith(transaction1.id);
});
it("should remove all transactions from storage that cannot be added to mempool", async () => {
@@ -500,6 +477,10 @@ describe("Service.readdTransactions", () => {
expect(storage.removeTransaction).toBeCalledTimes(2);
expect(storage.removeTransaction).toBeCalledWith(transaction1.id);
expect(storage.removeTransaction).toBeCalledWith(transaction2.id);
+ expect(secondSignatureVerification.clear).toBeCalledWith(transaction1.id);
+ expect(multiSignatureVerification.clear).toBeCalledWith(transaction1.id);
+ expect(secondSignatureVerification.clear).toBeCalledWith(transaction2.id);
+ expect(multiSignatureVerification.clear).toBeCalledWith(transaction2.id);
});
it("should add previously forged transactions first", async () => {
@@ -539,6 +520,8 @@ describe("Service.readdTransactions", () => {
});
expect(storage.removeTransaction).toBeCalledTimes(0);
+ expect(secondSignatureVerification.clear).toBeCalledTimes(0);
+ expect(multiSignatureVerification.clear).toBeCalledTimes(0);
});
it("should ignore error when adding previously forged transactions", async () => {
@@ -573,6 +556,8 @@ describe("Service.readdTransactions", () => {
});
expect(storage.removeTransaction).toBeCalledTimes(0);
+ expect(secondSignatureVerification.clear).toBeCalledWith(transaction1.id);
+ expect(multiSignatureVerification.clear).toBeCalledWith(transaction1.id);
});
it("should ignore all errors when adding previously forged", async () => {
@@ -602,6 +587,10 @@ describe("Service.readdTransactions", () => {
expect(storage.addTransaction).toBeCalledTimes(0);
expect(storage.removeTransaction).toBeCalledTimes(0);
+ expect(secondSignatureVerification.clear).toBeCalledWith(transaction1.id);
+ expect(multiSignatureVerification.clear).toBeCalledWith(transaction1.id);
+ expect(secondSignatureVerification.clear).toBeCalledWith(transaction2.id);
+ expect(multiSignatureVerification.clear).toBeCalledWith(transaction2.id);
});
});
@@ -627,6 +616,8 @@ describe("Service.cleanUp", () => {
expect(mempool.removeTransaction).toBeCalledWith(transaction3.data.senderPublicKey, transaction3.id);
expect(storage.removeTransaction).toBeCalledWith(transaction3.id);
+ expect(secondSignatureVerification.clear).toBeCalledWith(transaction3.id);
+ expect(multiSignatureVerification.clear).toBeCalledWith(transaction3.id);
expect(events.dispatch).toHaveBeenCalledWith(Enums.TransactionEvent.Expired, expect.anything());
});
@@ -651,6 +642,8 @@ describe("Service.cleanUp", () => {
expect(mempool.removeTransaction).toBeCalledWith(transaction2.data.senderPublicKey, transaction2.id);
expect(storage.removeTransaction).toBeCalledWith(transaction2.id);
+ expect(secondSignatureVerification.clear).toBeCalledWith(transaction2.id);
+ expect(multiSignatureVerification.clear).toBeCalledWith(transaction2.id);
expect(events.dispatch).toHaveBeenCalledWith(Enums.TransactionEvent.Expired, expect.anything());
});
@@ -679,5 +672,35 @@ describe("Service.cleanUp", () => {
expect(mempool.removeTransaction).toBeCalledWith(transaction1.data.senderPublicKey, transaction1.id);
expect(storage.removeTransaction).toBeCalledWith(transaction1.id);
+ expect(secondSignatureVerification.clear).toBeCalledWith(transaction1.id);
+ expect(multiSignatureVerification.clear).toBeCalledWith(transaction1.id);
+ });
+});
+
+describe("Service.applyBlock", () => {
+ it("should remove invalid and block transactions from store and dispatch event for removed transactions", async () => {
+ const block = {
+ transactions: [transaction1],
+ } as Interfaces.IBlock;
+
+ mempool.applyBlock.mockResolvedValueOnce([transaction2]);
+
+ const service = container.resolve(Service);
+
+ await expect(service.applyBlock(block)).toResolve();
+
+ expect(storage.removeTransaction).toBeCalledTimes(2);
+ expect(storage.removeTransaction).toBeCalledWith(transaction1.id);
+ expect(storage.removeTransaction).toBeCalledWith(transaction2.id);
+ expect(secondSignatureVerification.clear).toBeCalledWith(transaction1.id);
+ expect(multiSignatureVerification.clear).toBeCalledWith(transaction1.id);
+ expect(secondSignatureVerification.clear).toBeCalledWith(transaction2.id);
+ expect(multiSignatureVerification.clear).toBeCalledWith(transaction2.id);
+
+ expect(events.dispatch).toBeCalledTimes(1);
+ expect(events.dispatch).toBeCalledWith(Enums.TransactionEvent.RemovedFromPool, transaction2.data);
+
+ expect(logger.warning).toBeCalledTimes(1);
+ expect(logger.warning).toBeCalledWith("1 previously stored transactions failed re-adding");
});
});
diff --git a/__tests__/unit/core-transactions/handlers/__support__/app.ts b/__tests__/unit/core-transactions/handlers/__support__/app.ts
index bea4eebf4c..19982de7b7 100644
--- a/__tests__/unit/core-transactions/handlers/__support__/app.ts
+++ b/__tests__/unit/core-transactions/handlers/__support__/app.ts
@@ -20,11 +20,14 @@ import {
RevertTransactionAction,
ThrowIfCannotEnterPoolAction,
VerifyTransactionAction,
+ OnPoolEnterAction,
+ OnPoolLeaveAction,
} from "@packages/core-transaction-pool/src/actions";
import { DynamicFeeMatcher } from "@packages/core-transaction-pool/src/dynamic-fee-matcher";
import { ExpirationService } from "@packages/core-transaction-pool/src/expiration-service";
import { Mempool } from "@packages/core-transaction-pool/src/mempool";
import { Query } from "@packages/core-transaction-pool/src/query";
+import { MempoolIndexRegistry } from "@packages/core-transaction-pool/src/mempool-index-registry";
import { SenderMempool } from "@packages/core-transaction-pool/src/sender-mempool";
import { SenderState } from "@packages/core-transaction-pool/src/sender-state";
import { One, Two } from "@packages/core-transactions/src/handlers";
@@ -33,6 +36,10 @@ import { TransactionHandlerRegistry } from "@packages/core-transactions/src/hand
import { Identities, Utils } from "@packages/crypto";
import { IMultiSignatureAsset } from "@packages/crypto/src/interfaces";
import { ServiceProvider } from "@packages/core-transactions/src/service-provider";
+import {
+ SecondSignatureVerificationMemoized,
+ MultiSignatureVerificationMemoized,
+} from "@packages/core-transactions/src/verification";
const logger = {
notice: jest.fn(),
@@ -101,6 +108,7 @@ export const initApp = (): Application => {
"maxTransactionsPerSender",
300,
);
+ app.get(Container.Identifiers.PluginConfiguration).set("memoizerCacheSize", 20000);
app.bind(Container.Identifiers.StateStore).to(StateStore).inTransientScope();
@@ -112,6 +120,7 @@ export const initApp = (): Application => {
app.bind(Container.Identifiers.TransactionPoolDynamicFeeMatcher).to(DynamicFeeMatcher);
app.bind(Container.Identifiers.TransactionPoolExpirationService).to(ExpirationService);
+ app.bind(Container.Identifiers.TransactionPoolMempoolIndexRegistry).to(MempoolIndexRegistry).inSingletonScope();
app.bind(Container.Identifiers.TransactionPoolSenderMempool).to(SenderMempool);
app.bind(Container.Identifiers.TransactionPoolSenderMempoolFactory).toAutoFactory(
Container.Identifiers.TransactionPoolSenderMempool,
@@ -126,6 +135,14 @@ export const initApp = (): Application => {
app.bind(Identifiers.DatabaseTransactionRepository).toConstantValue(Mocks.TransactionRepository.instance);
+ app.bind(Identifiers.TransactionSecondSignatureVerification)
+ .to(SecondSignatureVerificationMemoized)
+ .inSingletonScope();
+
+ app.bind(Identifiers.TransactionMultiSignatureVerification)
+ .to(MultiSignatureVerificationMemoized)
+ .inSingletonScope();
+
app.bind(Identifiers.TransactionHandler).to(One.TransferTransactionHandler);
app.bind(Identifiers.TransactionHandler).to(Two.TransferTransactionHandler);
app.bind(Identifiers.TransactionHandler).to(One.SecondSignatureRegistrationTransactionHandler);
@@ -171,6 +188,16 @@ export const initApp = (): Application => {
new RevertTransactionAction(),
);
+ app.get(Container.Identifiers.TriggerService).bind(
+ "onPoolEnter",
+ new OnPoolEnterAction(),
+ );
+
+ app.get(Container.Identifiers.TriggerService).bind(
+ "onPoolLeave",
+ new OnPoolLeaveAction(),
+ );
+
return app;
};
@@ -235,3 +262,23 @@ export const buildMultiSignatureWallet = (): Wallets.Wallet => {
return wallet;
};
+
+export const buildMultiSignatureRecipientWallet = (): Wallets.Wallet => {
+ const multiSignatureAsset: IMultiSignatureAsset = {
+ publicKeys: [
+ Identities.PublicKey.fromPassphrase(passphrases[1]),
+ Identities.PublicKey.fromPassphrase(passphrases[2]),
+ Identities.PublicKey.fromPassphrase(passphrases[3]),
+ ],
+ min: 2,
+ };
+
+ const wallet = new Wallets.Wallet(
+ Identities.Address.fromMultiSignatureAsset(multiSignatureAsset),
+ new Services.Attributes.AttributeMap(getWalletAttributeSet()),
+ );
+ wallet.setPublicKey(Identities.PublicKey.fromMultiSignatureAsset(multiSignatureAsset));
+ wallet.setAttribute("multiSignature", multiSignatureAsset);
+
+ return wallet;
+};
diff --git a/__tests__/unit/core-transactions/handlers/handler-registry.test.ts b/__tests__/unit/core-transactions/handlers/handler-registry.test.ts
index 0ebeedfef9..8b66e9fc2e 100644
--- a/__tests__/unit/core-transactions/handlers/handler-registry.test.ts
+++ b/__tests__/unit/core-transactions/handlers/handler-registry.test.ts
@@ -1,8 +1,8 @@
import "jest-extended";
-import { Services } from "@packages/core-kernel";
+import { Container, Providers, Services } from "@packages/core-kernel";
import { Application } from "@packages/core-kernel/src/application";
-import { Container, Identifiers } from "@packages/core-kernel/src/ioc";
+import { Identifiers } from "@packages/core-kernel/src/ioc";
import {
DeactivatedTransactionHandlerError,
InvalidTransactionTypeError,
@@ -10,6 +10,10 @@ import {
import { One, TransactionHandler, TransactionHandlerConstructor, Two } from "@packages/core-transactions/src/handlers";
import { TransactionHandlerProvider } from "@packages/core-transactions/src/handlers/handler-provider";
import { TransactionHandlerRegistry } from "@packages/core-transactions/src/handlers/handler-registry";
+import {
+ MultiSignatureVerificationMemoized,
+ SecondSignatureVerificationMemoized,
+} from "@packages/core-transactions/src/verification";
import { ServiceProvider } from "@packages/core-transactions/src/service-provider";
import { Crypto, Enums, Identities, Interfaces, Managers, Transactions, Utils } from "@packages/crypto";
import { TransactionSchema } from "@packages/crypto/src/transactions/types/schemas";
@@ -114,7 +118,7 @@ class TestWithDependencyTransactionHandler extends TransactionHandler {
}
beforeEach(() => {
- app = new Application(new Container());
+ app = new Application(new Container.Container());
app.bind(Identifiers.TransactionHistoryService).toConstantValue(null);
app.bind(Identifiers.ApplicationNamespace).toConstantValue("ark-unitnet");
app.bind(Identifiers.LogService).toConstantValue({});
@@ -126,6 +130,13 @@ beforeEach(() => {
app.bind(Identifiers.DatabaseTransactionRepository).toConstantValue({});
app.bind(Identifiers.WalletRepository).toConstantValue({});
app.bind(Identifiers.TransactionPoolQuery).toConstantValue({});
+ app.bind(Container.Identifiers.TransactionPoolMempoolIndexRegistry).toConstantValue({});
+ app.bind(Identifiers.TransactionSecondSignatureVerification)
+ .to(SecondSignatureVerificationMemoized)
+ .inSingletonScope();
+ app.bind(Identifiers.TransactionMultiSignatureVerification)
+ .to(MultiSignatureVerificationMemoized)
+ .inSingletonScope();
app.bind(Identifiers.TransactionHandler).to(One.TransferTransactionHandler);
app.bind(Identifiers.TransactionHandler).to(Two.TransferTransactionHandler);
@@ -150,7 +161,16 @@ beforeEach(() => {
ServiceProvider.getTransactionHandlerConstructorsBinding(),
);
+ const pluginConfiguration = app.resolve(Providers.PluginConfiguration);
+ const pluginConfigurationInstance: Providers.PluginConfiguration = pluginConfiguration.from("core-transactions", {
+ memoizerCacheSize: 20000,
+ });
+ app.bind(Identifiers.PluginConfiguration)
+ .toConstantValue(pluginConfigurationInstance)
+ .when(Container.Selectors.anyAncestorOrTargetTaggedFirst("plugin", "@arkecosystem/core-transactions"));
+
Managers.configManager.getMilestone().aip11 = false;
+ Managers.configManager.getMilestone().multiSignatureRegistrationEnabled = true;
});
afterEach(() => {
diff --git a/__tests__/unit/core-transactions/handlers/one/multi-signature-registration.test.ts b/__tests__/unit/core-transactions/handlers/one/multi-signature-registration.test.ts
index c8ca5c3d77..872e5a56f3 100644
--- a/__tests__/unit/core-transactions/handlers/one/multi-signature-registration.test.ts
+++ b/__tests__/unit/core-transactions/handlers/one/multi-signature-registration.test.ts
@@ -16,9 +16,9 @@ import {
import { TransactionHandler } from "@packages/core-transactions/src/handlers";
import { TransactionHandlerRegistry } from "@packages/core-transactions/src/handlers/handler-registry";
import { Crypto, Enums, Identities, Interfaces, Managers, Transactions, Utils } from "@packages/crypto";
+import { BuilderFactory } from "@packages/crypto/dist/transactions";
import { IMultiSignatureAsset } from "@packages/crypto/src/interfaces";
import { configManager } from "@packages/crypto/src/managers";
-import { BuilderFactory } from "@packages/crypto/src/transactions";
import {
buildMultiSignatureWallet,
@@ -243,7 +243,7 @@ describe("MultiSignatureRegistrationTransaction", () => {
});
});
- describe("applyToSender", () => {
+ describe.skip("applyToSender", () => {
it("should be ok", async () => {
await expect(handler.applyToSender(multiSignatureTransaction)).rejects.toThrow(LegacyMultiSignatureError);
});
diff --git a/__tests__/unit/core-transactions/handlers/two/delegate-registration.test.ts b/__tests__/unit/core-transactions/handlers/two/delegate-registration.test.ts
index e022c546c4..6e2ba97ca4 100644
--- a/__tests__/unit/core-transactions/handlers/two/delegate-registration.test.ts
+++ b/__tests__/unit/core-transactions/handlers/two/delegate-registration.test.ts
@@ -10,6 +10,7 @@ import { Generators } from "@packages/core-test-framework/src";
import { Factories, FactoryBuilder } from "@packages/core-test-framework/src/factories";
import passphrases from "@packages/core-test-framework/src/internal/passphrases.json";
import { Mempool } from "@packages/core-transaction-pool/src/mempool";
+import { MempoolIndexes } from "@packages/core-transactions/src/enums";
import {
InsufficientBalanceError,
NotSupportedForMultiSignatureWalletError,
@@ -20,9 +21,9 @@ import {
import { TransactionHandler } from "@packages/core-transactions/src/handlers";
import { TransactionHandlerRegistry } from "@packages/core-transactions/src/handlers/handler-registry";
import { Crypto, Enums, Identities, Interfaces, Managers, Transactions, Utils } from "@packages/crypto";
+import { BuilderFactory } from "@packages/crypto/dist/transactions";
import { IMultiSignatureAsset } from "@packages/crypto/src/interfaces";
import { configManager } from "@packages/crypto/src/managers";
-import { BuilderFactory } from "@packages/crypto/src/transactions";
import {
buildMultiSignatureWallet,
@@ -57,6 +58,7 @@ beforeEach(() => {
Managers.configManager.setConfig(config);
app = initApp();
+ app.bind(Identifiers.TransactionPoolMempoolIndex).toConstantValue(MempoolIndexes.DelegateUsername);
app.bind(Identifiers.TransactionHistoryService).toConstantValue(transactionHistoryService);
walletRepository = app.get(Identifiers.WalletRepository);
@@ -477,6 +479,81 @@ describe("DelegateRegistrationTransaction", () => {
});
});
+ describe("onPoolEnter", () => {
+ it("should set username on DelegateUsername index", async () => {
+ const mempoolIndexRegistry = app.get(
+ Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+
+ const spyOnIndexSet = jest.spyOn(mempoolIndexRegistry.get(MempoolIndexes.DelegateUsername), "set");
+
+ await expect(handler.onPoolEnter(delegateRegistrationTransaction)).toResolve();
+ expect(spyOnIndexSet).toBeCalledTimes(1);
+ expect(spyOnIndexSet).toBeCalledWith(
+ delegateRegistrationTransaction.data.asset.delegate.username,
+ delegateRegistrationTransaction,
+ );
+ });
+ });
+
+ describe("onPoolLeave", () => {
+ it("should forget username on DelegateUsername index", async () => {
+ const mempoolIndexRegistry = app.get(
+ Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+
+ const spyOnIndexSet = jest.spyOn(mempoolIndexRegistry.get(MempoolIndexes.DelegateUsername), "forget");
+
+ await expect(handler.onPoolLeave(delegateRegistrationTransaction)).toResolve();
+ expect(spyOnIndexSet).toBeCalledTimes(1);
+ expect(spyOnIndexSet).toBeCalledWith(delegateRegistrationTransaction.data.asset.delegate.username);
+ });
+ });
+
+ describe("getInvalidPoolTransactions", () => {
+ it("should return empty array if there are no invalid transactions", async () => {
+ const mempoolIndexRegistry = app.get(
+ Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+
+ const spyOnIndexHas = jest
+ .spyOn(mempoolIndexRegistry.get(MempoolIndexes.DelegateUsername), "has")
+ .mockReturnValueOnce(false);
+
+ await expect(handler.getInvalidPoolTransactions(delegateRegistrationTransaction)).resolves.toEqual([]);
+ expect(spyOnIndexHas).toBeCalledTimes(1);
+ expect(spyOnIndexHas).toBeCalledWith(delegateRegistrationTransaction.data.asset.delegate.username);
+ });
+
+ it("should return invalid transaction if transaction with same username is indexed", async () => {
+ const invalidDelegateRegistrationTransaction = BuilderFactory.delegateRegistration()
+ .usernameAsset("dummy")
+ .nonce("1")
+ .sign(passphrases[1])
+ .build();
+
+ const mempoolIndexRegistry = app.get(
+ Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+
+ const spyOnIndexHas = jest
+ .spyOn(mempoolIndexRegistry.get(MempoolIndexes.DelegateUsername), "has")
+ .mockReturnValueOnce(true);
+
+ const spyOnIndexGet = jest
+ .spyOn(mempoolIndexRegistry.get(MempoolIndexes.DelegateUsername), "get")
+ .mockReturnValueOnce(invalidDelegateRegistrationTransaction);
+
+ await expect(handler.getInvalidPoolTransactions(delegateRegistrationTransaction)).resolves.toEqual([
+ invalidDelegateRegistrationTransaction,
+ ]);
+ expect(spyOnIndexHas).toBeCalledTimes(1);
+ expect(spyOnIndexHas).toBeCalledWith(delegateRegistrationTransaction.data.asset.delegate.username);
+ expect(spyOnIndexGet).toBeCalledTimes(1);
+ expect(spyOnIndexGet).toBeCalledWith(delegateRegistrationTransaction.data.asset.delegate.username);
+ });
+ });
+
describe("apply and revert", () => {
it("should resolve", async () => {
const walletBalance = senderWallet.getBalance();
diff --git a/__tests__/unit/core-transactions/handlers/two/delegate-resignation.test.ts b/__tests__/unit/core-transactions/handlers/two/delegate-resignation.test.ts
index 3798536dc4..f0f9dae35b 100644
--- a/__tests__/unit/core-transactions/handlers/two/delegate-resignation.test.ts
+++ b/__tests__/unit/core-transactions/handlers/two/delegate-resignation.test.ts
@@ -19,8 +19,8 @@ import {
import { TransactionHandler } from "@packages/core-transactions/src/handlers";
import { TransactionHandlerRegistry } from "@packages/core-transactions/src/handlers/handler-registry";
import { Crypto, Enums, Interfaces, Managers, Transactions, Utils } from "@packages/crypto";
+import { BuilderFactory } from "@packages/crypto/dist/transactions";
import { configManager } from "@packages/crypto/src/managers";
-import { BuilderFactory } from "@packages/crypto/src/transactions";
import {
buildMultiSignatureWallet,
diff --git a/__tests__/unit/core-transactions/handlers/two/htlc-claim.test.ts b/__tests__/unit/core-transactions/handlers/two/htlc-claim.test.ts
index 0655ef9566..a2f3fe9bc0 100644
--- a/__tests__/unit/core-transactions/handlers/two/htlc-claim.test.ts
+++ b/__tests__/unit/core-transactions/handlers/two/htlc-claim.test.ts
@@ -9,6 +9,7 @@ import { Generators } from "@packages/core-test-framework/src";
import { Factories, FactoryBuilder } from "@packages/core-test-framework/src/factories";
import passphrases from "@packages/core-test-framework/src/internal/passphrases.json";
import { Mempool } from "@packages/core-transaction-pool/src/mempool";
+import { MempoolIndexes } from "@packages/core-transactions/src/enums";
import {
HtlcLockExpiredError,
HtlcLockTransactionNotFoundError,
@@ -17,8 +18,8 @@ import {
import { TransactionHandler } from "@packages/core-transactions/src/handlers";
import { TransactionHandlerRegistry } from "@packages/core-transactions/src/handlers/handler-registry";
import { Crypto, Enums, Interfaces, Managers, Transactions, Utils } from "@packages/crypto";
+import { BuilderFactory } from "@packages/crypto/dist/transactions";
import { configManager } from "@packages/crypto/src/managers";
-import { BuilderFactory } from "@packages/crypto/src/transactions";
import { htlcSecretHashHex, htlcSecretHex } from "../__fixtures__/htlc-secrets";
import {
@@ -58,6 +59,7 @@ beforeEach(() => {
Managers.configManager.setConfig(config);
app = initApp();
+ app.bind(Identifiers.TransactionPoolMempoolIndex).toConstantValue(MempoolIndexes.HtlcClaimTransactionId);
app.bind(Identifiers.TransactionHistoryService).toConstantValue(null);
walletRepository = app.get(Identifiers.WalletRepository);
@@ -414,6 +416,90 @@ describe("Htlc claim", () => {
});
});
+ describe("onPoolEnter", () => {
+ it("should set lockTransactionId on HtlcClaimTransactionId index", () => {
+ const mempoolIndexRegistry = app.get(
+ Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+
+ const spyOnIndexSet = jest.spyOn(
+ mempoolIndexRegistry.get(MempoolIndexes.HtlcClaimTransactionId),
+ "set",
+ );
+
+ expect(handler.onPoolEnter(htlcClaimTransaction)).toResolve();
+ expect(spyOnIndexSet).toBeCalledTimes(1);
+ expect(spyOnIndexSet).toBeCalledWith(
+ htlcClaimTransaction.data.asset.claim.lockTransactionId,
+ htlcClaimTransaction,
+ );
+ });
+ });
+
+ describe("onPoolLeave", () => {
+ it("should forget lockTransactionId on HtlcClaimTransactionId index", () => {
+ const mempoolIndexRegistry = app.get(
+ Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+
+ const spyOnIndexSet = jest.spyOn(
+ mempoolIndexRegistry.get(MempoolIndexes.HtlcClaimTransactionId),
+ "forget",
+ );
+
+ expect(handler.onPoolLeave(htlcClaimTransaction)).toResolve();
+ expect(spyOnIndexSet).toBeCalledTimes(1);
+ expect(spyOnIndexSet).toBeCalledWith(htlcClaimTransaction.data.asset.claim.lockTransactionId);
+ });
+ });
+
+ describe("getInvalidPoolTransactions", () => {
+ it("should return empty array if there are no invalid transactions", async () => {
+ const mempoolIndexRegistry = app.get(
+ Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+
+ const spyOnIndexHas = jest
+ .spyOn(mempoolIndexRegistry.get(MempoolIndexes.HtlcClaimTransactionId), "has")
+ .mockReturnValueOnce(false);
+
+ await expect(handler.getInvalidPoolTransactions(htlcClaimTransaction)).resolves.toEqual([]);
+ expect(spyOnIndexHas).toBeCalledTimes(1);
+ expect(spyOnIndexHas).toBeCalledWith(htlcClaimTransaction.data.asset.claim.lockTransactionId);
+ });
+
+ it("should return invalid transaction if transaction with same transaction id is indexed", async () => {
+ const invalidHtlcClaimTransaction = BuilderFactory.htlcClaim()
+ .htlcClaimAsset({
+ unlockSecret: htlcSecretHex,
+ lockTransactionId: htlcLockTransaction.id!,
+ })
+ .nonce("2")
+ .sign(claimPassphrase)
+ .build();
+
+ const mempoolIndexRegistry = app.get(
+ Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+
+ const spyOnIndexHas = jest
+ .spyOn(mempoolIndexRegistry.get(MempoolIndexes.HtlcClaimTransactionId), "has")
+ .mockReturnValueOnce(true);
+
+ const spyOnIndexGet = jest
+ .spyOn(mempoolIndexRegistry.get(MempoolIndexes.HtlcClaimTransactionId), "get")
+ .mockReturnValueOnce(invalidHtlcClaimTransaction);
+
+ await expect(handler.getInvalidPoolTransactions(htlcClaimTransaction)).resolves.toEqual([
+ invalidHtlcClaimTransaction,
+ ]);
+ expect(spyOnIndexHas).toBeCalledTimes(1);
+ expect(spyOnIndexHas).toBeCalledWith(htlcClaimTransaction.data.asset.claim.lockTransactionId);
+ expect(spyOnIndexGet).toBeCalledTimes(1);
+ expect(spyOnIndexGet).toBeCalledWith(htlcClaimTransaction.data.asset.claim.lockTransactionId);
+ });
+ });
+
describe("apply", () => {
let pubKeyHash: number;
diff --git a/__tests__/unit/core-transactions/handlers/two/htlc-lock.test.ts b/__tests__/unit/core-transactions/handlers/two/htlc-lock.test.ts
index 48503e9bd2..038600ee23 100644
--- a/__tests__/unit/core-transactions/handlers/two/htlc-lock.test.ts
+++ b/__tests__/unit/core-transactions/handlers/two/htlc-lock.test.ts
@@ -8,12 +8,16 @@ import { Mapper, Mocks } from "@packages/core-test-framework";
import { Generators } from "@packages/core-test-framework/src";
import { Factories, FactoryBuilder } from "@packages/core-test-framework/src/factories";
import passphrases from "@packages/core-test-framework/src/internal/passphrases.json";
-import { HtlcLockExpiredError, InsufficientBalanceError } from "@packages/core-transactions/src/errors";
+import {
+ HtlcLockExpiredError,
+ InsufficientBalanceError,
+ DisabledMultiSignatureSending,
+} from "@packages/core-transactions/src/errors";
import { TransactionHandler } from "@packages/core-transactions/src/handlers";
import { TransactionHandlerRegistry } from "@packages/core-transactions/src/handlers/handler-registry";
import { Crypto, Enums, Interfaces, Managers, Transactions, Utils } from "@packages/crypto";
+import { BuilderFactory } from "@packages/crypto/dist/transactions";
import { configManager } from "@packages/crypto/src/managers";
-import { BuilderFactory } from "@packages/crypto/src/transactions";
import { htlcSecretHashHex } from "../__fixtures__/htlc-secrets";
import {
@@ -229,12 +233,20 @@ describe("Htlc lock", () => {
).toResolve();
});
- it("should not throw - multi sign", async () => {
+ it("should not throw - multi sign if and multiSignatureSendingEnabled=true", async () => {
+ Managers.configManager.getMilestone().multiSignatureSendingEnabled = true;
await expect(
handler.throwIfCannotBeApplied(multiSignatureHtlcLockTransaction, multiSignatureWallet),
).toResolve();
});
+ it("should throw - multi sign if and multiSignatureSendingEnabled=false", async () => {
+ Managers.configManager.getMilestone().multiSignatureSendingEnabled = false;
+ await expect(
+ handler.throwIfCannotBeApplied(multiSignatureHtlcLockTransaction, multiSignatureWallet),
+ ).rejects.toThrow(DisabledMultiSignatureSending);
+ });
+
it("should throw if asset is undefined", async () => {
htlcLockTransaction.data.asset = undefined;
diff --git a/__tests__/unit/core-transactions/handlers/two/htlc-refund.test.ts b/__tests__/unit/core-transactions/handlers/two/htlc-refund.test.ts
index 1704cfc0ed..2d5dbe2a46 100644
--- a/__tests__/unit/core-transactions/handlers/two/htlc-refund.test.ts
+++ b/__tests__/unit/core-transactions/handlers/two/htlc-refund.test.ts
@@ -9,12 +9,13 @@ import { Generators } from "@packages/core-test-framework/src";
import { Factories, FactoryBuilder } from "@packages/core-test-framework/src/factories";
import passphrases from "@packages/core-test-framework/src/internal/passphrases.json";
import { Mempool } from "@packages/core-transaction-pool/src/mempool";
+import { MempoolIndexes } from "@packages/core-transactions/src/enums";
import { HtlcLockNotExpiredError, HtlcLockTransactionNotFoundError } from "@packages/core-transactions/src/errors";
import { TransactionHandler } from "@packages/core-transactions/src/handlers";
import { TransactionHandlerRegistry } from "@packages/core-transactions/src/handlers/handler-registry";
import { Crypto, Enums, Interfaces, Managers, Transactions, Utils } from "@packages/crypto";
+import { BuilderFactory } from "@packages/crypto/dist/transactions";
import { configManager } from "@packages/crypto/src/managers";
-import { BuilderFactory } from "@packages/crypto/src/transactions";
import { htlcSecretHashHex } from "../__fixtures__/htlc-secrets";
import {
@@ -54,6 +55,7 @@ beforeEach(() => {
Managers.configManager.setConfig(config);
app = initApp();
+ app.bind(Identifiers.TransactionPoolMempoolIndex).toConstantValue(MempoolIndexes.HtlcRefundTransactionId);
app.bind(Identifiers.TransactionHistoryService).toConstantValue(null);
walletRepository = app.get(Identifiers.WalletRepository);
@@ -360,6 +362,89 @@ describe("Htlc refund", () => {
});
});
+ describe("onPoolEnter", () => {
+ it("should set lockTransactionId on HtlcRefundTransactionId index", () => {
+ const mempoolIndexRegistry = app.get(
+ Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+
+ const spyOnIndexSet = jest.spyOn(
+ mempoolIndexRegistry.get(MempoolIndexes.HtlcRefundTransactionId),
+ "set",
+ );
+
+ expect(handler.onPoolEnter(htlcRefundTransaction)).toResolve();
+ expect(spyOnIndexSet).toBeCalledTimes(1);
+ expect(spyOnIndexSet).toBeCalledWith(
+ htlcRefundTransaction.data.asset.refund.lockTransactionId,
+ htlcRefundTransaction,
+ );
+ });
+ });
+
+ describe("onPoolLeave", () => {
+ it("should forget lockTransactionId on HtlcRefundTransactionId index", () => {
+ const mempoolIndexRegistry = app.get(
+ Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+
+ const spyOnIndexSet = jest.spyOn(
+ mempoolIndexRegistry.get(MempoolIndexes.HtlcRefundTransactionId),
+ "forget",
+ );
+
+ expect(handler.onPoolLeave(htlcRefundTransaction)).toResolve();
+ expect(spyOnIndexSet).toBeCalledTimes(1);
+ expect(spyOnIndexSet).toBeCalledWith(htlcRefundTransaction.data.asset.refund.lockTransactionId);
+ });
+ });
+
+ describe("getInvalidPoolTransactions", () => {
+ it("should return empty array if there are no invalid transactions", async () => {
+ const mempoolIndexRegistry = app.get(
+ Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+
+ const spyOnIndexHas = jest
+ .spyOn(mempoolIndexRegistry.get(MempoolIndexes.HtlcRefundTransactionId), "has")
+ .mockReturnValueOnce(false);
+
+ await expect(handler.getInvalidPoolTransactions(htlcRefundTransaction)).resolves.toEqual([]);
+ expect(spyOnIndexHas).toBeCalledTimes(1);
+ expect(spyOnIndexHas).toBeCalledWith(htlcRefundTransaction.data.asset.refund.lockTransactionId);
+ });
+
+ it("should return invalid transaction if transaction with same transactionId is indexed", async () => {
+ const invalidRefundTransaction = BuilderFactory.htlcRefund()
+ .htlcRefundAsset({
+ lockTransactionId: htlcLockTransaction.id!,
+ })
+ .nonce("2")
+ .sign(lockPassphrase)
+ .build();
+
+ const mempoolIndexRegistry = app.get(
+ Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+
+ const spyOnIndexHas = jest
+ .spyOn(mempoolIndexRegistry.get(MempoolIndexes.HtlcRefundTransactionId), "has")
+ .mockReturnValueOnce(true);
+
+ const spyOnIndexGet = jest
+ .spyOn(mempoolIndexRegistry.get(MempoolIndexes.HtlcRefundTransactionId), "get")
+ .mockReturnValueOnce(invalidRefundTransaction);
+
+ await expect(handler.getInvalidPoolTransactions(htlcRefundTransaction)).resolves.toEqual([
+ invalidRefundTransaction,
+ ]);
+ expect(spyOnIndexHas).toBeCalledTimes(1);
+ expect(spyOnIndexHas).toBeCalledWith(htlcRefundTransaction.data.asset.refund.lockTransactionId);
+ expect(spyOnIndexGet).toBeCalledTimes(1);
+ expect(spyOnIndexGet).toBeCalledWith(htlcRefundTransaction.data.asset.refund.lockTransactionId);
+ });
+ });
+
describe("apply", () => {
let pubKeyHash: number;
diff --git a/__tests__/unit/core-transactions/handlers/two/ipfs.test.ts b/__tests__/unit/core-transactions/handlers/two/ipfs.test.ts
index 2daaf34c29..4a22eb07fe 100644
--- a/__tests__/unit/core-transactions/handlers/two/ipfs.test.ts
+++ b/__tests__/unit/core-transactions/handlers/two/ipfs.test.ts
@@ -8,12 +8,13 @@ import { Generators } from "@packages/core-test-framework/src";
import { Factories, FactoryBuilder } from "@packages/core-test-framework/src/factories";
import passphrases from "@packages/core-test-framework/src/internal/passphrases.json";
import { Mempool } from "@packages/core-transaction-pool";
-import { InsufficientBalanceError, IpfsHashAlreadyExists } from "@packages/core-transactions/src/errors";
+import { MempoolIndexes } from "@packages/core-transactions/src/enums";
+import { InsufficientBalanceError, IpfsHashAlreadyExists, DisabledMultiSignatureSending } from "@packages/core-transactions/src/errors";
import { TransactionHandler } from "@packages/core-transactions/src/handlers";
import { TransactionHandlerRegistry } from "@packages/core-transactions/src/handlers/handler-registry";
import { Crypto, Enums, Interfaces, Managers, Transactions, Utils } from "@packages/crypto";
-import { configManager } from "@packages/crypto/src/managers";
-import { BuilderFactory } from "@packages/crypto/src/transactions";
+import { configManager } from "@packages/crypto/dist/managers";
+import { BuilderFactory } from "@packages/crypto/dist/transactions";
import {
buildMultiSignatureWallet,
@@ -49,6 +50,7 @@ beforeEach(() => {
Managers.configManager.setConfig(config);
app = initApp();
+ app.bind(Identifiers.TransactionPoolMempoolIndex).toConstantValue(MempoolIndexes.Ipfs);
app.bind(Identifiers.TransactionHistoryService).toConstantValue(transactionHistoryService);
walletRepository = app.get(Identifiers.WalletRepository);
@@ -185,7 +187,7 @@ describe("Ipfs", () => {
it("should return true when aip11 === true", async () => {
await expect(handler.isActivated()).resolves.toBeTrue();
- jest.spyOn(Managers.configManager, "getMilestone").mockReturnValue({});
+ Managers.configManager.getMilestone().aip11 = false;
await expect(handler.isActivated()).resolves.toBeFalse();
});
@@ -213,6 +215,78 @@ describe("Ipfs", () => {
});
});
+ describe("onPoolEnter", () => {
+ it("should set lockTransactionId on HtlcClaimTransactionId index", () => {
+ const mempoolIndexRegistry = app.get(
+ Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+
+ const spyOnIndexSet = jest.spyOn(mempoolIndexRegistry.get(MempoolIndexes.Ipfs), "set");
+
+ expect(handler.onPoolEnter(ipfsTransaction)).toResolve();
+ expect(spyOnIndexSet).toBeCalledTimes(1);
+ expect(spyOnIndexSet).toBeCalledWith(ipfsTransaction.data.asset.ipfs, ipfsTransaction);
+ });
+ });
+
+ describe("onPoolLeave", () => {
+ it("should forget lockTransactionId on HtlcClaimTransactionId index", () => {
+ const mempoolIndexRegistry = app.get(
+ Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+
+ const spyOnIndexSet = jest.spyOn(mempoolIndexRegistry.get(MempoolIndexes.Ipfs), "forget");
+
+ expect(handler.onPoolLeave(ipfsTransaction)).toResolve();
+ expect(spyOnIndexSet).toBeCalledTimes(1);
+ expect(spyOnIndexSet).toBeCalledWith(ipfsTransaction.data.asset.ipfs);
+ });
+ });
+
+ describe("getInvalidPoolTransactions", () => {
+ it("should return empty array if there are no invalid transactions", async () => {
+ const mempoolIndexRegistry = app.get(
+ Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+
+ const spyOnIndexHas = jest
+ .spyOn(mempoolIndexRegistry.get(MempoolIndexes.Ipfs), "has")
+ .mockReturnValueOnce(false);
+
+ await expect(handler.getInvalidPoolTransactions(ipfsTransaction)).resolves.toEqual([]);
+ expect(spyOnIndexHas).toBeCalledTimes(1);
+ expect(spyOnIndexHas).toBeCalledWith(ipfsTransaction.data.asset.ipfs);
+ });
+
+ it("should return invalid transaction if transaction with same ipfs address is indexed", async () => {
+ const invalidIpfsTransaction = BuilderFactory.ipfs()
+ .ipfsAsset("QmR45FmbVVrixReBwJkhEKde2qwHYaQzGxu4ZoDeswuF9w")
+ .nonce("1")
+ .sign(passphrases[0])
+ .build();
+
+ const mempoolIndexRegistry = app.get(
+ Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+
+ const spyOnIndexHas = jest
+ .spyOn(mempoolIndexRegistry.get(MempoolIndexes.Ipfs), "has")
+ .mockReturnValueOnce(true);
+
+ const spyOnIndexGet = jest
+ .spyOn(mempoolIndexRegistry.get(MempoolIndexes.Ipfs), "get")
+ .mockReturnValueOnce(invalidIpfsTransaction);
+
+ await expect(handler.getInvalidPoolTransactions(ipfsTransaction)).resolves.toEqual([
+ invalidIpfsTransaction,
+ ]);
+ expect(spyOnIndexHas).toBeCalledTimes(1);
+ expect(spyOnIndexHas).toBeCalledWith(ipfsTransaction.data.asset.ipfs);
+ expect(spyOnIndexGet).toBeCalledTimes(1);
+ expect(spyOnIndexGet).toBeCalledWith(ipfsTransaction.data.asset.ipfs);
+ });
+ });
+
describe("throwIfCannotBeApplied", () => {
let pubKeyHash: number;
@@ -229,25 +303,26 @@ describe("Ipfs", () => {
await expect(handler.throwIfCannotBeApplied(ipfsTransaction, senderWallet)).toResolve();
});
- it("should not throw defined as exception", async () => {
- configManager.set("network.pubKeyHash", 99);
- configManager.set("exceptions.transactions", [ipfsTransaction.id]);
-
- await expect(handler.throwIfCannotBeApplied(ipfsTransaction, senderWallet)).toResolve();
- });
-
it("should not throw - second sign", async () => {
await expect(
handler.throwIfCannotBeApplied(secondSignatureIpfsTransaction, secondSignatureWallet),
).toResolve();
});
- it("should not throw - multi sign", async () => {
+ it("should not throw - multi sign if and multiSignatureSendingEnabled=true", async () => {
+ Managers.configManager.getMilestone().multiSignatureSendingEnabled = true;
await expect(
handler.throwIfCannotBeApplied(multiSignatureIpfsTransaction, multiSignatureWallet),
).toResolve();
});
+ it("should throw - multi sign if and multiSignatureSendingEnabled=false", async () => {
+ Managers.configManager.getMilestone().multiSignatureSendingEnabled = false;
+ await expect(
+ handler.throwIfCannotBeApplied(multiSignatureIpfsTransaction, multiSignatureWallet),
+ ).rejects.toThrow(DisabledMultiSignatureSending);
+ });
+
it("should throw if wallet has insufficient funds", async () => {
senderWallet.setBalance(Utils.BigNumber.ZERO);
@@ -271,6 +346,13 @@ describe("Ipfs", () => {
IpfsHashAlreadyExists,
);
});
+
+ it("should not throw defined as exception", async () => {
+ configManager.set("network.pubKeyHash", 99);
+ configManager.set("exceptions.transactions", [ipfsTransaction.id]);
+
+ await expect(handler.throwIfCannotBeApplied(ipfsTransaction, senderWallet)).toResolve();
+ });
});
describe("apply", () => {
diff --git a/__tests__/unit/core-transactions/handlers/two/multi-payment.test.ts b/__tests__/unit/core-transactions/handlers/two/multi-payment.test.ts
index 9d06ab3712..12bfff936f 100644
--- a/__tests__/unit/core-transactions/handlers/two/multi-payment.test.ts
+++ b/__tests__/unit/core-transactions/handlers/two/multi-payment.test.ts
@@ -7,17 +7,18 @@ import { StateStore } from "@packages/core-state/src/stores/state";
import { Generators } from "@packages/core-test-framework/src";
import { Factories, FactoryBuilder } from "@packages/core-test-framework/src/factories";
import passphrases from "@packages/core-test-framework/src/internal/passphrases.json";
-import { InsufficientBalanceError } from "@packages/core-transactions/src/errors";
+import { InsufficientBalanceError, DisabledMultiSignatureSending, DisabledMultiSignatureReceiving } from "@packages/core-transactions/src/errors";
import { TransactionHandler } from "@packages/core-transactions/src/handlers";
import { TransactionHandlerRegistry } from "@packages/core-transactions/src/handlers/handler-registry";
import { Crypto, Enums, Interfaces, Managers, Transactions, Utils } from "@packages/crypto";
+import { BuilderFactory } from "@packages/crypto/dist/transactions";
import { configManager } from "@packages/crypto/src/managers";
-import { BuilderFactory } from "@packages/crypto/src/transactions";
import {
buildMultiSignatureWallet,
buildRecipientWallet,
buildSecondSignatureWallet,
+ buildMultiSignatureRecipientWallet,
buildSenderWallet,
initApp,
} from "../__support__/app";
@@ -26,6 +27,7 @@ let app: Application;
let senderWallet: Wallets.Wallet;
let secondSignatureWallet: Wallets.Wallet;
let multiSignatureWallet: Wallets.Wallet;
+let multiSignatureRecipientWallet: Wallets.Wallet;
let recipientWallet: Wallets.Wallet;
let walletRepository: Contracts.State.WalletRepository;
let factoryBuilder: FactoryBuilder;
@@ -59,11 +61,13 @@ beforeEach(() => {
senderWallet = buildSenderWallet(factoryBuilder);
secondSignatureWallet = buildSecondSignatureWallet(factoryBuilder);
multiSignatureWallet = buildMultiSignatureWallet();
+ multiSignatureRecipientWallet = buildMultiSignatureRecipientWallet();
recipientWallet = buildRecipientWallet(factoryBuilder);
walletRepository.index(senderWallet);
walletRepository.index(secondSignatureWallet);
walletRepository.index(multiSignatureWallet);
+ walletRepository.index(multiSignatureRecipientWallet);
walletRepository.index(recipientWallet);
});
@@ -156,12 +160,22 @@ describe("MultiPaymentTransaction", () => {
).toResolve();
});
- it("should not throw - multi sign", async () => {
+ it("should not throw - multi sign if and multiSignatureSendingEnabled=true", async () => {
+ Managers.configManager.getMilestone().multiSignatureSendingEnabled = true;
await expect(
handler.throwIfCannotBeApplied(multiSignatureMultiPaymentTransaction, multiSignatureWallet),
).toResolve();
});
+ it("should throw - multi sign if and multiSignatureSendingEnabled=false", async () => {
+ Managers.configManager.getMilestone().multiSignatureSendingEnabled = false;
+ await expect(
+ handler.throwIfCannotBeApplied(multiSignatureMultiPaymentTransaction, multiSignatureWallet),
+ ).rejects.toThrow(
+ DisabledMultiSignatureSending,
+ );
+ });
+
it("should throw if asset is undefined", async () => {
multiPaymentTransaction.data.asset = undefined;
@@ -183,6 +197,35 @@ describe("MultiPaymentTransaction", () => {
InsufficientBalanceError,
);
});
+
+ it("should not throw if recipient is multisignature wallet and multiSignatureReceivingEnabled=true", async () => {
+ Managers.configManager.getMilestone().multiSignatureReceivingEnabled = true;
+ const multiPaymentTransaction = BuilderFactory.multiPayment()
+ .addPayment("ARYJmeYHSUTgbxaiqsgoPwf6M3CYukqdKN", "10")
+ .addPayment("AFyjB5jULQiYNsp37wwipCm9c7V1xEzTJD", "20")
+ .addPayment(multiSignatureRecipientWallet.getAddress(), "20")
+ .nonce("1")
+ .sign(passphrases[0])
+ .build();
+
+ await expect(handler.throwIfCannotBeApplied(multiPaymentTransaction, senderWallet)).toResolve();
+ });
+
+ it("should throw if recipient is multisignature wallet and multiSignatureReceivingEnabled=false", async () => {
+ Managers.configManager.getMilestone().multiSignatureReceivingEnabled = false;
+ const multiPaymentTransaction = BuilderFactory.multiPayment()
+ .addPayment("ARYJmeYHSUTgbxaiqsgoPwf6M3CYukqdKN", "10")
+ .addPayment("AFyjB5jULQiYNsp37wwipCm9c7V1xEzTJD", "20")
+ .addPayment(multiSignatureRecipientWallet.getAddress(), "20")
+ .nonce("1")
+ .sign(passphrases[0])
+ .build();
+
+ await expect(handler.throwIfCannotBeApplied(multiPaymentTransaction, senderWallet)).rejects.toThrow(
+ DisabledMultiSignatureReceiving,
+ );
+ });
+
});
describe("apply", () => {
diff --git a/__tests__/unit/core-transactions/handlers/two/multi-signature-registration.test.ts b/__tests__/unit/core-transactions/handlers/two/multi-signature-registration.test.ts
index ec054a1ee6..7815fdbfcd 100644
--- a/__tests__/unit/core-transactions/handlers/two/multi-signature-registration.test.ts
+++ b/__tests__/unit/core-transactions/handlers/two/multi-signature-registration.test.ts
@@ -9,6 +9,7 @@ import { Factories, FactoryBuilder } from "@packages/core-test-framework/src/fac
import passphrases from "@packages/core-test-framework/src/internal/passphrases.json";
import { getWalletAttributeSet } from "@packages/core-test-framework/src/internal/wallet-attributes";
import { Mempool } from "@packages/core-transaction-pool/src/mempool";
+import { MempoolIndexes } from "@packages/core-transactions/src/enums";
import {
InsufficientBalanceError,
InvalidMultiSignatureError,
@@ -20,9 +21,9 @@ import {
import { TransactionHandler } from "@packages/core-transactions/src/handlers";
import { TransactionHandlerRegistry } from "@packages/core-transactions/src/handlers/handler-registry";
import { Crypto, Enums, Errors, Identities, Interfaces, Managers, Transactions, Utils } from "@packages/crypto";
+import { BuilderFactory } from "@packages/crypto/dist/transactions";
import { IMultiSignatureAsset, IMultiSignatureLegacyAsset } from "@packages/crypto/src/interfaces";
import { configManager } from "@packages/crypto/src/managers";
-import { BuilderFactory } from "@packages/crypto/src/transactions";
import { buildRecipientWallet, buildSecondSignatureWallet, buildSenderWallet, initApp } from "../__support__/app";
@@ -51,6 +52,7 @@ beforeEach(() => {
Managers.configManager.setConfig(config);
app = initApp();
+ app.bind(Identifiers.TransactionPoolMempoolIndex).toConstantValue(MempoolIndexes.MultiSignatureAddress);
app.bind(Identifiers.TransactionHistoryService).toConstantValue(transactionHistoryService);
walletRepository = app.get(Identifiers.WalletRepository);
@@ -399,6 +401,94 @@ describe("MultiSignatureRegistrationTransaction", () => {
});
});
+ describe("onPoolEnter", () => {
+ it("should set multiSignatureAddress on MultiSignatureAddress index", () => {
+ const mempoolIndexRegistry = app.get(
+ Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+
+ const spyOnIndexSet = jest.spyOn(mempoolIndexRegistry.get(MempoolIndexes.MultiSignatureAddress), "set");
+
+ expect(handler.onPoolEnter(multiSignatureTransaction)).toResolve();
+ expect(spyOnIndexSet).toBeCalledTimes(1);
+ expect(spyOnIndexSet).toBeCalledWith(
+ Identities.Address.fromMultiSignatureAsset(multiSignatureTransaction.data.asset.multiSignature),
+ multiSignatureTransaction,
+ );
+ });
+ });
+
+ describe("onPoolLeave", () => {
+ it("should forget multiSignatureAddress on MultiSignatureAddress index", () => {
+ const mempoolIndexRegistry = app.get(
+ Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+
+ const spyOnIndexSet = jest.spyOn(mempoolIndexRegistry.get(MempoolIndexes.MultiSignatureAddress), "forget");
+
+ expect(handler.onPoolLeave(multiSignatureTransaction)).toResolve();
+ expect(spyOnIndexSet).toBeCalledTimes(1);
+ expect(spyOnIndexSet).toBeCalledWith(
+ Identities.Address.fromMultiSignatureAsset(multiSignatureTransaction.data.asset.multiSignature),
+ );
+ });
+ });
+
+ describe("getInvalidPoolTransactions", () => {
+ it("should return empty array if there are no invalid transactions", async () => {
+ const mempoolIndexRegistry = app.get(
+ Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+
+ const spyOnIndexHas = jest
+ .spyOn(mempoolIndexRegistry.get(MempoolIndexes.MultiSignatureAddress), "has")
+ .mockReturnValueOnce(false);
+
+ await expect(handler.getInvalidPoolTransactions(multiSignatureTransaction)).resolves.toEqual([]);
+ expect(spyOnIndexHas).toBeCalledTimes(1);
+ expect(spyOnIndexHas).toBeCalledWith(
+ Identities.Address.fromMultiSignatureAsset(multiSignatureTransaction.data.asset.multiSignature),
+ );
+ });
+
+ it("should return invalid transaction if transaction with same multi signature address is indexed", async () => {
+ const invalidMultiSignatureTransaction = BuilderFactory.multiSignature()
+ .multiSignatureAsset(multiSignatureAsset)
+ .senderPublicKey(senderWallet.getPublicKey()!)
+ .nonce("1")
+ .recipientId(recipientWallet.getPublicKey()!)
+ .multiSign(passphrases[0], 0)
+ .multiSign(passphrases[1], 1)
+ .multiSign(passphrases[2], 2)
+ .sign(passphrases[0])
+ .build();
+
+ const mempoolIndexRegistry = app.get(
+ Identifiers.TransactionPoolMempoolIndexRegistry,
+ );
+
+ const spyOnIndexHas = jest
+ .spyOn(mempoolIndexRegistry.get(MempoolIndexes.MultiSignatureAddress), "has")
+ .mockReturnValueOnce(true);
+
+ const spyOnIndexGet = jest
+ .spyOn(mempoolIndexRegistry.get(MempoolIndexes.MultiSignatureAddress), "get")
+ .mockReturnValueOnce(invalidMultiSignatureTransaction);
+
+ await expect(handler.getInvalidPoolTransactions(multiSignatureTransaction)).resolves.toEqual([
+ invalidMultiSignatureTransaction,
+ ]);
+ expect(spyOnIndexHas).toBeCalledTimes(1);
+ expect(spyOnIndexHas).toBeCalledWith(
+ Identities.Address.fromMultiSignatureAsset(multiSignatureTransaction.data.asset.multiSignature),
+ );
+ expect(spyOnIndexGet).toBeCalledTimes(1);
+ expect(spyOnIndexGet).toBeCalledWith(
+ Identities.Address.fromMultiSignatureAsset(multiSignatureTransaction.data.asset.multiSignature),
+ );
+ });
+ });
+
describe("apply", () => {
it("should be ok", async () => {
recipientWallet.forgetAttribute("multiSignature");
diff --git a/__tests__/unit/core-transactions/handlers/two/second-signature-registration.test.ts b/__tests__/unit/core-transactions/handlers/two/second-signature-registration.test.ts
index 935be8083c..fd0095e42a 100644
--- a/__tests__/unit/core-transactions/handlers/two/second-signature-registration.test.ts
+++ b/__tests__/unit/core-transactions/handlers/two/second-signature-registration.test.ts
@@ -16,9 +16,9 @@ import {
import { TransactionHandler } from "@packages/core-transactions/src/handlers";
import { TransactionHandlerRegistry } from "@packages/core-transactions/src/handlers/handler-registry";
import { Crypto, Enums, Identities, Interfaces, Managers, Transactions, Utils } from "@packages/crypto";
+import { BuilderFactory } from "@packages/crypto/dist/transactions";
import { IMultiSignatureAsset } from "@packages/crypto/src/interfaces";
import { configManager } from "@packages/crypto/src/managers";
-import { BuilderFactory } from "@packages/crypto/src/transactions";
import {
buildMultiSignatureWallet,
diff --git a/__tests__/unit/core-transactions/handlers/two/transaction.test.ts b/__tests__/unit/core-transactions/handlers/two/transaction.test.ts
index 3b8c1bde06..b172d27205 100644
--- a/__tests__/unit/core-transactions/handlers/two/transaction.test.ts
+++ b/__tests__/unit/core-transactions/handlers/two/transaction.test.ts
@@ -23,10 +23,9 @@ import {
import { TransactionHandler, TransactionHandlerConstructor } from "@packages/core-transactions/src/handlers";
import { TransactionHandlerRegistry } from "@packages/core-transactions/src/handlers/handler-registry";
import { Crypto, Enums, Identities, Interfaces, Managers, Transactions, Utils } from "@packages/crypto";
+import { BuilderFactory } from "@packages/crypto/dist/transactions";
import { IMultiSignatureAsset } from "@packages/crypto/src/interfaces";
import { configManager } from "@packages/crypto/src/managers";
-import { BuilderFactory } from "@packages/crypto/src/transactions";
-import ByteBuffer from "bytebuffer";
import {
buildMultiSignatureWallet,
@@ -62,8 +61,8 @@ class TestTransaction extends Transactions.Transaction {
};
}
- public serialize(options?: any): ByteBuffer | undefined {
- return new ByteBuffer(0);
+ public serialize(options?: any): Utils.ByteBuffer | undefined {
+ return new Utils.ByteBuffer(Buffer.alloc(0));
}
public deserialize(buf) {
@@ -416,12 +415,6 @@ describe("General Tests", () => {
);
});
- it("should resolve defined as exception", async () => {
- configManager.set("exceptions.transactions", [transferTransaction.id]);
- configManager.set("network.pubKeyHash", 99);
- await expect(handler.apply(transferTransaction)).toResolve();
- });
-
it("should resolve with V1", async () => {
configManager.getMilestone().aip11 = false;
@@ -445,6 +438,12 @@ describe("General Tests", () => {
senderWallet.setBalance(Utils.BigNumber.ZERO);
await expect(handler.apply(transferTransaction)).rejects.toThrow(InsufficientBalanceError);
});
+
+ it("should resolve defined as exception", async () => {
+ configManager.set("exceptions.transactions", [transferTransaction.id]);
+ configManager.set("network.pubKeyHash", 99);
+ await expect(handler.apply(transferTransaction)).toResolve();
+ });
});
describe("revert", () => {
diff --git a/__tests__/unit/core-transactions/handlers/two/transfer.test.ts b/__tests__/unit/core-transactions/handlers/two/transfer.test.ts
index ef13b92ddf..98b4baf920 100644
--- a/__tests__/unit/core-transactions/handlers/two/transfer.test.ts
+++ b/__tests__/unit/core-transactions/handlers/two/transfer.test.ts
@@ -12,18 +12,21 @@ import {
ColdWalletError,
InsufficientBalanceError,
SenderWalletMismatchError,
+ DisabledMultiSignatureReceiving,
+ DisabledMultiSignatureSending,
} from "@packages/core-transactions/src/errors";
import { TransactionHandler } from "@packages/core-transactions/src/handlers";
import { TransactionHandlerRegistry } from "@packages/core-transactions/src/handlers/handler-registry";
import { TransferTransactionHandler } from "@packages/core-transactions/src/handlers/one";
import { Crypto, Enums, Interfaces, Managers, Transactions, Utils } from "@packages/crypto";
+import { BuilderFactory } from "@packages/crypto/dist/transactions";
import { configManager } from "@packages/crypto/src/managers";
-import { BuilderFactory } from "@packages/crypto/src/transactions";
import {
buildMultiSignatureWallet,
buildRecipientWallet,
buildSecondSignatureWallet,
+ buildMultiSignatureRecipientWallet,
buildSenderWallet,
initApp,
} from "../__support__/app";
@@ -32,6 +35,7 @@ let app: Application;
let senderWallet: Wallets.Wallet;
let secondSignatureWallet: Wallets.Wallet;
let multiSignatureWallet: Wallets.Wallet;
+let multiSignatureRecipientWallet: Wallets.Wallet;
let recipientWallet: Wallets.Wallet;
let walletRepository: Contracts.State.WalletRepository;
let factoryBuilder: FactoryBuilder;
@@ -59,11 +63,13 @@ beforeEach(() => {
senderWallet = buildSenderWallet(factoryBuilder);
secondSignatureWallet = buildSecondSignatureWallet(factoryBuilder);
multiSignatureWallet = buildMultiSignatureWallet();
+ multiSignatureRecipientWallet = buildMultiSignatureRecipientWallet();
recipientWallet = buildRecipientWallet(factoryBuilder);
walletRepository.index(senderWallet);
walletRepository.index(secondSignatureWallet);
walletRepository.index(multiSignatureWallet);
+ walletRepository.index(multiSignatureRecipientWallet);
walletRepository.index(recipientWallet);
});
@@ -116,6 +122,7 @@ describe("TransferTransaction", () => {
afterEach(async () => {
Managers.configManager.set("network.pubKeyHash", pubKeyHash);
+
});
describe("bootstrap", () => {
@@ -142,12 +149,23 @@ describe("TransferTransaction", () => {
).toResolve();
});
- it("should not throw - multi sign", async () => {
+ it("should not throw - multi sign if and multiSignatureSendingEnabled=true", async () => {
+ Managers.configManager.getMilestone().multiSignatureSendingEnabled = true;
await expect(
handler.throwIfCannotBeApplied(multiSignatureTransferTransaction, multiSignatureWallet),
).toResolve();
});
+
+ it("should throw - multi sign if and multiSignatureSendingEnabled=false", async () => {
+ Managers.configManager.getMilestone().multiSignatureSendingEnabled = false;
+ await expect(
+ handler.throwIfCannotBeApplied(multiSignatureTransferTransaction, multiSignatureWallet),
+ ).rejects.toThrow(
+ DisabledMultiSignatureSending,
+ );
+ });
+
it("should throw", async () => {
transferTransaction.data.senderPublicKey = "a".repeat(66);
await expect(handler.throwIfCannotBeApplied(transferTransaction, senderWallet)).rejects.toThrow(
@@ -185,6 +203,34 @@ describe("TransferTransaction", () => {
);
});
+ it("should pass if recipient is multi signature wallet and multiSignatureReceivingEnabled=true", async () => {
+ Managers.configManager.getMilestone().multiSignatureReceivingEnabled = true;
+
+ transferTransaction = BuilderFactory.transfer()
+ .recipientId(multiSignatureRecipientWallet.getAddress())
+ .amount("10000000")
+ .sign(passphrases[0])
+ .nonce("1")
+ .build();
+
+ await expect(handler.throwIfCannotBeApplied(transferTransaction, senderWallet)).toResolve();
+ });
+
+ it("should throw if recipient is multi signature wallet and multiSignatureReceivingEnabled=false", async () => {
+ Managers.configManager.getMilestone().multiSignatureReceivingEnabled = false;
+
+ transferTransaction = BuilderFactory.transfer()
+ .recipientId(multiSignatureRecipientWallet.getAddress())
+ .amount("10000000")
+ .sign(passphrases[0])
+ .nonce("1")
+ .build();
+
+ await expect(handler.throwIfCannotBeApplied(transferTransaction, senderWallet)).rejects.toThrow(
+ DisabledMultiSignatureReceiving,
+ );
+ });
+
it("should not throw if recipient is cold wallet", async () => {
const coldWallet: Wallets.Wallet = factoryBuilder
.get("Wallet")
diff --git a/__tests__/unit/core-transactions/handlers/two/vote.test.ts b/__tests__/unit/core-transactions/handlers/two/vote.test.ts
index de6c52f239..2072b5c85c 100644
--- a/__tests__/unit/core-transactions/handlers/two/vote.test.ts
+++ b/__tests__/unit/core-transactions/handlers/two/vote.test.ts
@@ -13,13 +13,14 @@ import {
InsufficientBalanceError,
NoVoteError,
UnvoteMismatchError,
+ DisabledMultiSignatureSending,
VotedForNonDelegateError,
} from "@packages/core-transactions/src/errors";
import { TransactionHandler } from "@packages/core-transactions/src/handlers";
import { TransactionHandlerRegistry } from "@packages/core-transactions/src/handlers/handler-registry";
import { Crypto, Enums, Interfaces, Managers, Transactions, Utils } from "@packages/crypto";
+import { BuilderFactory } from "@packages/crypto/dist/transactions";
import { configManager } from "@packages/crypto/src/managers";
-import { BuilderFactory } from "@packages/crypto/src/transactions";
import {
buildMultiSignatureWallet,
@@ -297,12 +298,20 @@ describe("VoteTransaction", () => {
).toResolve();
});
- it("should not throw - multi sign vote", async () => {
+ it("should not throw - multi sign if and multiSignatureSendingEnabled=true", async () => {
+ Managers.configManager.getMilestone().multiSignatureSendingEnabled = true;
await expect(
handler.throwIfCannotBeApplied(multiSignatureVoteTransaction, multiSignatureWallet),
).toResolve();
});
+ it("should throw - multi sign if and multiSignatureSendingEnabled=false", async () => {
+ Managers.configManager.getMilestone().multiSignatureSendingEnabled = false;
+ await expect(
+ handler.throwIfCannotBeApplied(multiSignatureVoteTransaction, multiSignatureWallet),
+ ).rejects.toThrow(DisabledMultiSignatureSending);
+ });
+
it("should not throw if the unvote is valid and the wallet has voted", async () => {
senderWallet.setAttribute("vote", delegateWallet1.getPublicKey());
await expect(handler.throwIfCannotBeApplied(unvoteTransaction, senderWallet)).toResolve();
diff --git a/__tests__/unit/core-transactions/service-provider.test.ts b/__tests__/unit/core-transactions/service-provider.test.ts
index e1656b3ad7..b3444c9e2b 100644
--- a/__tests__/unit/core-transactions/service-provider.test.ts
+++ b/__tests__/unit/core-transactions/service-provider.test.ts
@@ -2,6 +2,7 @@ import "jest-extended";
import { Application, Container } from "@packages/core-kernel/src";
import { ServiceProvider } from "@packages/core-transactions/src/service-provider";
+import { AnySchema } from "joi";
let app: Application;
@@ -23,4 +24,66 @@ describe("ServiceProvider", () => {
it("should be required", async () => {
await expect(serviceProvider.required()).resolves.toBeTrue();
});
+
+ describe("configSchema", () => {
+ beforeEach(() => {
+ serviceProvider = app.resolve(ServiceProvider);
+
+ for (const key of Object.keys(process.env)) {
+ if (key.includes("CORE_TRANSACTION")) {
+ delete process.env[key];
+ }
+ }
+ });
+
+ it("should validate schema using defaults", async () => {
+ jest.resetModules();
+ const result = (serviceProvider.configSchema() as AnySchema).validate(
+ (await import("@packages/core-transactions/src/defaults")).defaults,
+ );
+
+ expect(result.error).toBeUndefined();
+
+ expect(result.value.memoizerCacheSize).toBeNumber();
+ });
+
+ it("should allow configuration extension", async () => {
+ jest.resetModules();
+ const defaults = (await import("@packages/core-transactions/src/defaults")).defaults;
+
+ // @ts-ignore
+ defaults.customField = "dummy";
+
+ const result = (serviceProvider.configSchema() as AnySchema).validate(defaults);
+
+ expect(result.error).toBeUndefined();
+ expect(result.value.customField).toEqual("dummy");
+ });
+
+ describe("process.env.CORE_TRANSACTIONS_MEMOIZER_CACHE_SIZE", () => {
+ it("should parse process.env.CORE_TRANSACTIONS_MEMOIZER_CACHE_SIZE", async () => {
+ process.env.CORE_TRANSACTIONS_MEMOIZER_CACHE_SIZE = "4000";
+
+ jest.resetModules();
+ const result = (serviceProvider.configSchema() as AnySchema).validate(
+ (await import("@packages/core-transactions/src/defaults")).defaults,
+ );
+
+ expect(result.error).toBeUndefined();
+ expect(result.value.memoizerCacheSize).toEqual(4000);
+ });
+
+ it("should throw if process.env.CORE_TRANSACTIONS_MEMOIZER_CACHE_SIZE is not number", async () => {
+ process.env.CORE_TRANSACTIONS_MEMOIZER_CACHE_SIZE = "false";
+
+ jest.resetModules();
+ const result = (serviceProvider.configSchema() as AnySchema).validate(
+ (await import("@packages/core-transactions/src/defaults")).defaults,
+ );
+
+ expect(result.error).toBeDefined();
+ expect(result.error!.message).toEqual('"memoizerCacheSize" must be a number');
+ });
+ });
+ });
});
diff --git a/__tests__/unit/core-transactions/verification/multi-signature-verification-memoized.test.ts b/__tests__/unit/core-transactions/verification/multi-signature-verification-memoized.test.ts
new file mode 100644
index 0000000000..3c6a9947e0
--- /dev/null
+++ b/__tests__/unit/core-transactions/verification/multi-signature-verification-memoized.test.ts
@@ -0,0 +1,112 @@
+import "jest-extended";
+
+import { Application, Container } from "@packages/core-kernel";
+import passphrases from "@packages/core-test-framework/src/internal/passphrases.json";
+import { MultiSignatureVerificationMemoized } from "@packages/core-transactions/src/verification/multi-signature-verification-memoized";
+import { Identities, Interfaces, Transactions } from "@packages/crypto";
+import { BuilderFactory } from "@packages/crypto/dist/transactions";
+
+describe("MultiSignatureVerificationMemoized", () => {
+ let transaction: Interfaces.ITransaction;
+ let multiSignatureAsset: Interfaces.IMultiSignatureAsset;
+
+ let app: Application;
+ let verification: MultiSignatureVerificationMemoized;
+
+ const createTransaction = (amount: string) => {
+ return BuilderFactory.transfer()
+ .recipientId(Identities.Address.fromPassphrase(passphrases[1]))
+ .amount(amount)
+ .sign(passphrases[0])
+ .build();
+ };
+
+ beforeEach(() => {
+ transaction = createTransaction("1");
+ multiSignatureAsset = {
+ min: 2,
+ publicKeys: ["publicKeys1", "publicKeys2"],
+ };
+
+ app = new Application(new Container.Container());
+
+ verification = app.resolve(MultiSignatureVerificationMemoized);
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe("verifySignatures", () => {
+ it("should call verifier if no cached value is found", () => {
+ const spyOnVerifier = jest.spyOn(Transactions.Verifier, "verifySignatures");
+
+ verification.verifySignatures(transaction.data, multiSignatureAsset);
+
+ expect(spyOnVerifier).toBeCalled();
+ });
+
+ it("should take cached value if called with same parameters", () => {
+ const spyOnVerifier = jest.spyOn(Transactions.Verifier, "verifySignatures");
+
+ const result1 = verification.verifySignatures(transaction.data, multiSignatureAsset);
+ const result2 = verification.verifySignatures(transaction.data, multiSignatureAsset);
+
+ expect(spyOnVerifier).toBeCalledTimes(1);
+ expect(result1).toEqual(result2);
+ });
+
+ it("should not take cached value if called with different parameters", () => {
+ const spyOnVerifier = jest.spyOn(Transactions.Verifier, "verifySignatures");
+
+ verification.verifySignatures(transaction.data, multiSignatureAsset);
+ verification.verifySignatures(transaction.data, { ...multiSignatureAsset, min: 3 });
+
+ expect(spyOnVerifier).toBeCalledTimes(2);
+ });
+
+ it("should return correct values", () => {
+ const spyOnVerifier = jest
+ .spyOn(Transactions.Verifier, "verifySignatures")
+ .mockReturnValueOnce(false)
+ .mockReturnValueOnce(true);
+
+ const transaction2 = createTransaction("2");
+
+ expect(verification.verifySignatures(transaction.data, multiSignatureAsset)).toEqual(false);
+ expect(verification.verifySignatures(transaction2.data, multiSignatureAsset)).toEqual(true);
+
+ expect(spyOnVerifier).toBeCalledTimes(2);
+
+ // Values from cache
+ expect(verification.verifySignatures(transaction.data, multiSignatureAsset)).toEqual(false);
+ expect(verification.verifySignatures(transaction2.data, multiSignatureAsset)).toEqual(true);
+
+ expect(spyOnVerifier).toBeCalledTimes(2);
+ });
+
+ it("should throw error if transaction.id is undefined", () => {
+ transaction.data.id = undefined;
+ expect(() => verification.verifySignatures(transaction.data, multiSignatureAsset)).toThrowError();
+ });
+ });
+
+ describe("clear", () => {
+ it("should remove cached value by transaction id", () => {
+ const spyOnVerifier = jest.spyOn(Transactions.Verifier, "verifySignatures");
+
+ const result1 = verification.verifySignatures(transaction.data, multiSignatureAsset);
+ const result2 = verification.verifySignatures(transaction.data, multiSignatureAsset);
+
+ expect(spyOnVerifier).toBeCalledTimes(1);
+ expect(result1).toEqual(result2);
+
+ verification.clear(transaction.data.id);
+
+ const result3 = verification.verifySignatures(transaction.data, multiSignatureAsset);
+
+ expect(spyOnVerifier).toBeCalledTimes(2);
+ expect(result1).toEqual(result3);
+ });
+ });
+});
diff --git a/__tests__/unit/core-transactions/verification/multi-signature-verification.test.ts b/__tests__/unit/core-transactions/verification/multi-signature-verification.test.ts
new file mode 100644
index 0000000000..fbb0d512d0
--- /dev/null
+++ b/__tests__/unit/core-transactions/verification/multi-signature-verification.test.ts
@@ -0,0 +1,55 @@
+import "jest-extended";
+
+import { Application, Container, Exceptions } from "@packages/core-kernel";
+import passphrases from "@packages/core-test-framework/src/internal/passphrases.json";
+import { MultiSignatureVerification } from "@packages/core-transactions/src/verification";
+import { Identities, Interfaces, Transactions } from "@packages/crypto";
+import { BuilderFactory } from "@packages/crypto/dist/transactions";
+
+describe("MultiSignatureVerificationMemoized", () => {
+ let transaction: Interfaces.ITransaction;
+ let multiSignatureAsset: Interfaces.IMultiSignatureAsset;
+
+ let app: Application;
+ let verification: MultiSignatureVerification;
+
+ const createTransaction = (amount: string) => {
+ return BuilderFactory.transfer()
+ .recipientId(Identities.Address.fromPassphrase(passphrases[1]))
+ .amount(amount)
+ .sign(passphrases[0])
+ .build();
+ };
+
+ beforeEach(() => {
+ transaction = createTransaction("1");
+ multiSignatureAsset = {
+ min: 2,
+ publicKeys: ["publicKeys1", "publicKeys2"],
+ };
+
+ app = new Application(new Container.Container());
+
+ verification = app.resolve(MultiSignatureVerification);
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe("verifySignatures", () => {
+ it("should call verifySignatures", () => {
+ const spyOnVerifier = jest.spyOn(Transactions.Verifier, "verifySignatures");
+
+ verification.verifySignatures(transaction.data, multiSignatureAsset);
+
+ expect(spyOnVerifier).toBeCalledWith(transaction.data, multiSignatureAsset);
+ });
+ });
+
+ describe("clear", () => {
+ it("should throw error", () => {
+ expect(() => verification.clear(transaction.data.id)).toThrowError(Exceptions.Runtime.NotImplemented);
+ });
+ });
+});
diff --git a/__tests__/unit/core-transactions/verification/second-signature-verification-memoized.test.ts b/__tests__/unit/core-transactions/verification/second-signature-verification-memoized.test.ts
new file mode 100644
index 0000000000..f1210dfa3d
--- /dev/null
+++ b/__tests__/unit/core-transactions/verification/second-signature-verification-memoized.test.ts
@@ -0,0 +1,107 @@
+import "jest-extended";
+
+import { Application, Container } from "@packages/core-kernel";
+import passphrases from "@packages/core-test-framework/src/internal/passphrases.json";
+import { SecondSignatureVerificationMemoized } from "@packages/core-transactions/src/verification/second-signature-verification-memoized";
+import { Identities, Interfaces, Transactions } from "@packages/crypto";
+import { BuilderFactory } from "@packages/crypto/dist/transactions";
+
+describe("SecondSignatureVerificationMemoized", () => {
+ let transaction: Interfaces.ITransaction;
+
+ let app: Application;
+ let verification: SecondSignatureVerificationMemoized;
+
+ const createTransaction = (amount: string) => {
+ return BuilderFactory.transfer()
+ .recipientId(Identities.Address.fromPassphrase(passphrases[1]))
+ .amount(amount)
+ .sign(passphrases[0])
+ .build();
+ };
+
+ beforeEach(() => {
+ transaction = createTransaction("1");
+
+ app = new Application(new Container.Container());
+
+ verification = app.resolve(SecondSignatureVerificationMemoized);
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe("verifySecondSignature", () => {
+ it("should call verifier if no cached value is found", () => {
+ const spyOnVerifier = jest.spyOn(Transactions.Verifier, "verifySecondSignature");
+
+ verification.verifySecondSignature(transaction.data, "publicKey");
+
+ expect(spyOnVerifier).toBeCalled();
+ });
+
+ it("should take cached value if called with same parameters", () => {
+ const spyOnVerifier = jest.spyOn(Transactions.Verifier, "verifySecondSignature");
+
+ const result1 = verification.verifySecondSignature(transaction.data, "publicKey");
+ const result2 = verification.verifySecondSignature(transaction.data, "publicKey");
+
+ expect(spyOnVerifier).toBeCalledTimes(1);
+ expect(result1).toEqual(result2);
+ });
+
+ it("should not take cached value if called with different parameters", () => {
+ const spyOnVerifier = jest.spyOn(Transactions.Verifier, "verifySecondSignature");
+
+ verification.verifySecondSignature(transaction.data, "publicKey");
+ verification.verifySecondSignature(transaction.data, "differentPublicKey");
+
+ expect(spyOnVerifier).toBeCalledTimes(2);
+ });
+
+ it("should return correct values", () => {
+ const spyOnVerifier = jest
+ .spyOn(Transactions.Verifier, "verifySecondSignature")
+ .mockReturnValueOnce(false)
+ .mockReturnValueOnce(true);
+
+ const transaction2 = createTransaction("2");
+
+ expect(verification.verifySecondSignature(transaction.data, "publicKey")).toEqual(false);
+ expect(verification.verifySecondSignature(transaction2.data, "publicKey")).toEqual(true);
+
+ expect(spyOnVerifier).toBeCalledTimes(2);
+
+ // Values from cache
+ expect(verification.verifySecondSignature(transaction.data, "publicKey")).toEqual(false);
+ expect(verification.verifySecondSignature(transaction2.data, "publicKey")).toEqual(true);
+
+ expect(spyOnVerifier).toBeCalledTimes(2);
+ });
+
+ it("should throw error if transaction.id is undefined", () => {
+ transaction.data.id = undefined;
+ expect(() => verification.verifySecondSignature(transaction.data, "publicKey")).toThrowError();
+ });
+ });
+
+ describe("clear", () => {
+ it("should remove cached value by transaction id", () => {
+ const spyOnVerifier = jest.spyOn(Transactions.Verifier, "verifySecondSignature");
+
+ const result1 = verification.verifySecondSignature(transaction.data, "publicKey");
+ const result2 = verification.verifySecondSignature(transaction.data, "publicKey");
+
+ expect(spyOnVerifier).toBeCalledTimes(1);
+ expect(result1).toEqual(result2);
+
+ verification.clear(transaction.data.id);
+
+ const result3 = verification.verifySecondSignature(transaction.data, "publicKey");
+
+ expect(spyOnVerifier).toBeCalledTimes(2);
+ expect(result1).toEqual(result3);
+ });
+ });
+});
diff --git a/__tests__/unit/core-transactions/verification/second-signature-verification.test.ts b/__tests__/unit/core-transactions/verification/second-signature-verification.test.ts
new file mode 100644
index 0000000000..7b0b3922da
--- /dev/null
+++ b/__tests__/unit/core-transactions/verification/second-signature-verification.test.ts
@@ -0,0 +1,50 @@
+import "jest-extended";
+
+import { Application, Container, Exceptions } from "@packages/core-kernel";
+import passphrases from "@packages/core-test-framework/src/internal/passphrases.json";
+import { SecondSignatureVerification } from "@packages/core-transactions/src/verification";
+import { Identities, Interfaces, Transactions } from "@packages/crypto";
+import { BuilderFactory } from "@packages/crypto/dist/transactions";
+
+describe("SecondSignatureVerificationMemoized", () => {
+ let transaction: Interfaces.ITransaction;
+
+ let app: Application;
+ let verification: SecondSignatureVerification;
+
+ const createTransaction = (amount: string) => {
+ return BuilderFactory.transfer()
+ .recipientId(Identities.Address.fromPassphrase(passphrases[1]))
+ .amount(amount)
+ .sign(passphrases[0])
+ .build();
+ };
+
+ beforeEach(() => {
+ transaction = createTransaction("1");
+
+ app = new Application(new Container.Container());
+
+ verification = app.resolve(SecondSignatureVerification);
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe("verifySecondSignature", () => {
+ it("should call verifySecondSignature", () => {
+ const spyOnVerifier = jest.spyOn(Transactions.Verifier, "verifySecondSignature");
+
+ verification.verifySecondSignature(transaction.data, "publicKey");
+
+ expect(spyOnVerifier).toBeCalledWith(transaction.data, "publicKey");
+ });
+ });
+
+ describe("clear", () => {
+ it("should throw error", () => {
+ expect(() => verification.clear(transaction.data.id)).toThrowError(Exceptions.Runtime.NotImplemented);
+ });
+ });
+});
diff --git a/__tests__/unit/core-webhooks/listener.test.ts b/__tests__/unit/core-webhooks/listener.test.ts
index 70b79e7609..8f58e1f394 100644
--- a/__tests__/unit/core-webhooks/listener.test.ts
+++ b/__tests__/unit/core-webhooks/listener.test.ts
@@ -27,6 +27,10 @@ const mockEventDispatcher = {
dispatch: jest.fn(),
};
+const webhooksConfiguration = {
+ getRequired: jest.fn().mockReturnValue(2000),
+};
+
let spyOnPost: jest.SpyInstance;
const expectFinishedEventData = () => {
@@ -54,6 +58,7 @@ beforeEach(() => {
sandbox.app.bind(Identifiers.Database).to(Database).inSingletonScope();
sandbox.app.bind(Container.Identifiers.LogService).toConstantValue(logger);
+ sandbox.app.bind(Container.Identifiers.PluginConfiguration).toConstantValue(webhooksConfiguration);
database = sandbox.app.get(Identifiers.Database);
database.boot();
diff --git a/__tests__/unit/core-webhooks/service-provider.test.ts b/__tests__/unit/core-webhooks/service-provider.test.ts
index 9d40ca86e6..471f9767d0 100644
--- a/__tests__/unit/core-webhooks/service-provider.test.ts
+++ b/__tests__/unit/core-webhooks/service-provider.test.ts
@@ -2,7 +2,7 @@ import "jest-extended";
import { Identifiers, Server, ServiceProvider as CoreApiServiceProvider } from "@packages/core-api/src";
import { defaults } from "@packages/core-api/src/defaults";
-import { Application, Container, Providers } from "@packages/core-kernel";
+import { Application, Container, Contracts, Providers } from "@packages/core-kernel";
import { NullEventDispatcher } from "@packages/core-kernel/src/services/events/drivers/null";
import { ServiceProvider } from "@packages/core-webhooks/src";
import { defaults as webhooksDefaults } from "@packages/core-webhooks/src/defaults";
@@ -54,7 +54,7 @@ beforeEach(() => {
app.bind(Container.Identifiers.PaginationService).toConstantValue({});
- app.bind(Container.Identifiers.EventDispatcherService).to(NullEventDispatcher);
+ app.bind(Container.Identifiers.EventDispatcherService).to(NullEventDispatcher).inSingletonScope();
app.bind(Container.Identifiers.LogService).toConstantValue(logger);
@@ -79,6 +79,11 @@ describe("ServiceProvider", () => {
});
it("should register", async () => {
+ const spyOnListen = jest.spyOn(
+ app.get(Container.Identifiers.EventDispatcherService),
+ "listen",
+ );
+
await expect(coreApiServiceProvider.register()).toResolve();
expect(app.isBound(Identifiers.HTTP)).toBeTrue();
@@ -89,6 +94,29 @@ describe("ServiceProvider", () => {
serviceProvider.setConfig(instance);
await expect(serviceProvider.register()).toResolve();
+
+ expect(spyOnListen).not.toBeCalled();
+ });
+
+ it("should register and start listening, when enabled", async () => {
+ const spyOnListen = jest.spyOn(
+ app.get(Container.Identifiers.EventDispatcherService),
+ "listen",
+ );
+
+ await expect(coreApiServiceProvider.register()).toResolve();
+
+ expect(app.isBound(Identifiers.HTTP)).toBeTrue();
+
+ const pluginConfiguration = app.get(Container.Identifiers.PluginConfiguration);
+ webhooksDefaults.enabled = true;
+
+ const instance = pluginConfiguration.from("core-webhooks", webhooksDefaults);
+ serviceProvider.setConfig(instance);
+
+ await expect(serviceProvider.register()).toResolve();
+
+ expect(spyOnListen).toBeCalled();
});
it("should boot", async () => {
diff --git a/__tests__/unit/core/cli.test.ts b/__tests__/unit/core/cli.test.ts
index d2166c5e71..69f9d0f7fe 100644
--- a/__tests__/unit/core/cli.test.ts
+++ b/__tests__/unit/core/cli.test.ts
@@ -3,7 +3,6 @@ import "jest-extended";
import { Commands, Services } from "@packages/core-cli";
import { CommandLineInterface } from "@packages/core/src/cli";
import envPaths from "env-paths";
-import { join } from "path";
import prompts from "prompts";
beforeEach(() => {
@@ -61,7 +60,7 @@ describe("CLI", () => {
describe("discover plugins", () => {
it("should load CLI plugins from folder using provided token and network", async () => {
- const spyOnDiscover = jest.spyOn(Commands.DiscoverPlugins.prototype, "discover").mockResolvedValueOnce([
+ const spyOnList = jest.spyOn(Services.PluginManager.prototype, "list").mockResolvedValueOnce([
// @ts-ignore
{
path: "test/path",
@@ -79,32 +78,26 @@ describe("CLI", () => {
const cli = new CommandLineInterface(["help", `--token=${token}`, `--network=${network}`]);
await expect(cli.execute("./packages/core/dist")).toResolve();
- expect(spyOnDiscover).toHaveBeenCalledWith(
- join(envPaths(token, { suffix: "core" }).data, network, "plugins"),
- );
+ expect(spyOnList).toHaveBeenCalledWith(token, network);
expect(spyOnFrom).toHaveBeenCalled();
});
it("should load CLI plugins from folder using CORE_PATH_CONFIG", async () => {
- const spyOnDiscover = jest.spyOn(Commands.DiscoverPlugins.prototype, "discover").mockResolvedValueOnce([]);
-
+ const spyOnList = jest.spyOn(Services.PluginManager.prototype, "list").mockResolvedValueOnce([]);
process.env.CORE_PATH_CONFIG = __dirname;
const cli = new CommandLineInterface(["help"]);
await expect(cli.execute("./packages/core/dist")).toResolve();
- expect(spyOnDiscover).toHaveBeenCalledWith(
- join(envPaths("dummyToken", { suffix: "core" }).data, "testnet", "plugins"),
- );
+ expect(spyOnList).toHaveBeenCalledWith("dummyToken", "testnet");
delete process.env.CORE_PATH_CONFIG;
});
it("should load CLI plugins from folder using detected network folder", async () => {
- const spyOnDiscoverPlugins = jest
- .spyOn(Commands.DiscoverPlugins.prototype, "discover")
- .mockResolvedValueOnce([]);
+ const spyOnList = jest.spyOn(Services.PluginManager.prototype, "list").mockResolvedValueOnce([]);
+
const spyOnDiscoverNetwork = jest
.spyOn(Commands.DiscoverNetwork.prototype, "discover")
.mockResolvedValueOnce("testnet");
@@ -112,10 +105,19 @@ describe("CLI", () => {
const cli = new CommandLineInterface(["help"]);
await expect(cli.execute("./packages/core/dist")).toResolve();
- expect(spyOnDiscoverPlugins).toHaveBeenCalledWith(
- join(envPaths("ark", { suffix: "core" }).data, "testnet", "plugins"),
- );
+ expect(spyOnList).toHaveBeenCalledWith("ark", "testnet");
expect(spyOnDiscoverNetwork).toHaveBeenCalledWith(envPaths("ark", { suffix: "core" }).config);
});
+
+ it("should not load CLI plugins if network is not provided", async () => {
+ const spyOnList = jest.spyOn(Services.PluginManager.prototype, "list").mockResolvedValueOnce([]);
+
+ const token = "dummy";
+
+ const cli = new CommandLineInterface(["help", `--token=${token}`]);
+ await expect(cli.execute("./packages/core/dist")).toResolve();
+
+ expect(spyOnList).not.toHaveBeenCalled();
+ });
});
});
diff --git a/__tests__/unit/core/commands/core-run.test.ts b/__tests__/unit/core/commands/core-run.test.ts
index b23632debc..6219ae1053 100644
--- a/__tests__/unit/core/commands/core-run.test.ts
+++ b/__tests__/unit/core/commands/core-run.test.ts
@@ -34,7 +34,7 @@ describe("RunCommand", () => {
executeCommand(Command);
- await new Promise((resolve) => {
+ await new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 200);
diff --git a/__tests__/unit/core/commands/core-start.test.ts b/__tests__/unit/core/commands/core-start.test.ts
index 2262be844b..b6dc56cb42 100644
--- a/__tests__/unit/core/commands/core-start.test.ts
+++ b/__tests__/unit/core/commands/core-start.test.ts
@@ -30,7 +30,7 @@ describe("StartCommand", () => {
expect(spyStart).toHaveBeenCalledWith(
{
- args: "core:run --token='ark' --network='testnet' --v=0 --env='production'",
+ args: "core:run --token='ark' --network='testnet' --v=0 --env='production' --skipPrompts=false",
env: {
CORE_ENV: "production",
NODE_ENV: "production",
diff --git a/__tests__/unit/core/commands/forger-run.test.ts b/__tests__/unit/core/commands/forger-run.test.ts
index 6000d2a05d..dab41738ab 100644
--- a/__tests__/unit/core/commands/forger-run.test.ts
+++ b/__tests__/unit/core/commands/forger-run.test.ts
@@ -34,7 +34,7 @@ describe("RunCommand", () => {
executeCommand(Command);
- await new Promise((resolve) => {
+ await new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 200);
diff --git a/__tests__/unit/core/commands/forger-start.test.ts b/__tests__/unit/core/commands/forger-start.test.ts
index bae4f57c71..9db10cc2fb 100644
--- a/__tests__/unit/core/commands/forger-start.test.ts
+++ b/__tests__/unit/core/commands/forger-start.test.ts
@@ -30,7 +30,7 @@ describe("StartCommand", () => {
expect(spyStart).toHaveBeenCalledWith(
{
- args: "forger:run --token='ark' --network='testnet' --v=0 --env='production'",
+ args: "forger:run --token='ark' --network='testnet' --v=0 --env='production' --skipPrompts=false",
env: {
CORE_ENV: "production",
NODE_ENV: "production",
diff --git a/__tests__/unit/core/commands/plugin-install.test.ts b/__tests__/unit/core/commands/plugin-install.test.ts
index 5591118616..bb47e2434d 100644
--- a/__tests__/unit/core/commands/plugin-install.test.ts
+++ b/__tests__/unit/core/commands/plugin-install.test.ts
@@ -1,89 +1,38 @@
import "jest-extended";
-import { Console } from "@arkecosystem/core-test-framework";
+import { Console } from "@packages/core-test-framework";
import { Command } from "@packages/core/src/commands/plugin-install";
-import { setGracefulCleanup } from "tmp";
+import { Container } from "@packages/core-cli";
+let cli;
+let spyOnInstall;
const packageName = "dummyPackageName";
-const install = jest.fn();
-const exists = jest.fn().mockReturnValue(false);
-
-jest.mock("@packages/core/src/source-providers/npm", () => ({
- NPM: jest.fn().mockImplementation(() => ({
- exists,
- install,
- })),
-}));
-
-jest.mock("@packages/core/src/source-providers/git", () => ({
- Git: jest.fn().mockImplementation(() => ({
- exists,
- install,
- })),
-}));
+const token = "ark";
+const network = "testnet";
-jest.mock("@packages/core/src/source-providers/file", () => ({
- File: jest.fn().mockImplementation(() => ({
- exists,
- install,
- })),
-}));
-
-let cli;
beforeEach(() => {
process.argv = ["", "test"];
cli = new Console();
+ const pluginManager = cli.app.get(Container.Identifiers.PluginManager);
+ spyOnInstall = jest.spyOn(pluginManager, "install").mockImplementation(async () => {});
});
afterEach(() => {
jest.clearAllMocks();
- setGracefulCleanup();
});
describe("PluginInstallCommand", () => {
it("should throw an error when package name is not provided", async () => {
- const errorMessage = `"package" is required`;
- await expect(cli.execute(Command)).rejects.toThrow(errorMessage);
+ await expect(cli.execute(Command)).rejects.toThrow(`"package" is required`);
- expect(exists).not.toHaveBeenCalled();
- expect(install).not.toHaveBeenCalled();
+ expect(spyOnInstall).not.toHaveBeenCalled();
});
- it("should throw an error when package doesn't exist", async () => {
- const errorMessage = `The given package [${packageName}] is neither a git nor a npm package.`;
- await expect(cli.withArgs([packageName]).execute(Command)).rejects.toThrow(errorMessage);
-
- expect(exists).toHaveBeenCalledWith(packageName, undefined);
- expect(install).not.toHaveBeenCalled();
- });
-
- it("should throw any errors while installing", async () => {
- exists.mockReturnValue(true);
-
- jest.spyOn(cli.app, "getCorePath").mockImplementationOnce(() => {
- throw Error("Fake Error");
- });
-
- await expect(cli.withArgs([packageName]).execute(Command)).rejects.toThrow("Fake Error");
- });
-
- it("should call install on existing packages", async () => {
- exists.mockReturnValue(true);
-
- await expect(cli.withArgs([packageName]).execute(Command)).toResolve();
-
- expect(exists).toHaveBeenCalledWith(packageName, undefined);
- expect(install).toHaveBeenCalledWith(packageName, undefined);
- });
-
- it("should call install on existing packages with --version flag", async () => {
- exists.mockReturnValue(true);
-
+ it("should call install", async () => {
const version = "3.0.0";
- await expect(cli.withArgs([packageName]).withFlags({ version }).execute(Command)).toResolve();
+ await expect(cli.withArgs([packageName]).withFlags({ version, token, network }).execute(Command)).toResolve();
- expect(exists).toHaveBeenCalledWith(packageName, version);
- expect(install).toHaveBeenCalledWith(packageName, version);
+ expect(spyOnInstall).toHaveBeenCalledWith(token, network, packageName, version);
});
});
diff --git a/__tests__/unit/core/commands/plugin-remove.test.ts b/__tests__/unit/core/commands/plugin-remove.test.ts
index 396cdbba71..080f43c1f0 100644
--- a/__tests__/unit/core/commands/plugin-remove.test.ts
+++ b/__tests__/unit/core/commands/plugin-remove.test.ts
@@ -2,37 +2,36 @@ import "jest-extended";
import { Console } from "@packages/core-test-framework";
import { Command } from "@packages/core/src/commands/plugin-remove";
-import fs from "fs-extra";
+import { Container } from "@arkecosystem/core-cli";
+let cli;
+let spyOnRemove;
const packageName = "dummyPackageName";
+const token = "ark";
+const network = "testnet";
-let cli;
beforeEach(() => {
cli = new Console();
+
+ const pluginManager = cli.app.get(Container.Identifiers.PluginManager);
+ spyOnRemove = jest.spyOn(pluginManager, "remove").mockImplementation(async () => {});
});
afterEach(() => {
jest.clearAllMocks();
-})
+});
describe("PluginRemoveCommand", () => {
it("should throw when package name is not provided", async () => {
- jest.spyOn(cli.app, "getCorePath").mockReturnValueOnce(null);
await expect(cli.execute(Command)).rejects.toThrow(`"package" is required`);
- });
- it("should throw when the plugin doesn't exist", async () => {
- jest.spyOn(cli.app, "getCorePath").mockReturnValueOnce(null);
- await expect(cli.withArgs([packageName]).execute(Command)).rejects.toThrow(
- `The package [${packageName}] does not exist.`,
- );
+ expect(spyOnRemove).not.toHaveBeenCalled();
});
- it("remove plugin if exist", async () => {
- jest.spyOn(fs, "existsSync").mockReturnValue(true);
- const removeSync = jest.spyOn(fs, "removeSync");
+ it("should call remove", async () => {
+ jest.spyOn(cli.app, "getCorePath").mockReturnValueOnce(null);
+ await expect(cli.withArgs([packageName]).withFlags({ token, network }).execute(Command)).toResolve();
- await expect(cli.withArgs([packageName]).execute(Command)).toResolve();
- expect(removeSync).toHaveBeenCalled();
+ expect(spyOnRemove).toHaveBeenCalledWith(token, network, packageName);
});
});
diff --git a/__tests__/unit/core/commands/plugin-update.test.ts b/__tests__/unit/core/commands/plugin-update.test.ts
index b7183467aa..dd8dd893eb 100644
--- a/__tests__/unit/core/commands/plugin-update.test.ts
+++ b/__tests__/unit/core/commands/plugin-update.test.ts
@@ -2,32 +2,19 @@ import "jest-extended";
import { Console } from "@packages/core-test-framework";
import { Command } from "@packages/core/src/commands/plugin-update";
-import fs from "fs-extra";
+import { Container } from "@arkecosystem/core-cli";
-let npmUpdateCalled = false;
-let gitUpdateCalled = false;
+let cli;
+let spyOnUpdate;
const packageName = "dummyPackageName";
-const updateNPM = () => (npmUpdateCalled = true);
-const updateGIT = () => (gitUpdateCalled = true);
-
-jest.mock("@packages/core/src/source-providers/npm", () => ({
- NPM: jest.fn().mockImplementation(() => ({
- update: updateNPM,
- })),
-}));
-
-jest.mock("@packages/core/src/source-providers/git", () => ({
- Git: jest.fn().mockImplementation(() => ({
- update: updateGIT,
- })),
-}));
+const token = "ark";
+const network = "testnet";
-let cli;
beforeEach(() => {
- gitUpdateCalled = false;
- npmUpdateCalled = false;
-
cli = new Console();
+
+ const pluginManager = cli.app.get(Container.Identifiers.PluginManager);
+ spyOnUpdate = jest.spyOn(pluginManager, "update").mockImplementation(async () => {});
});
afterEach(() => {
@@ -36,32 +23,14 @@ afterEach(() => {
describe("PluginUpdateCommand", () => {
it("should throw when package name is not provided", async () => {
- jest.spyOn(cli.app, "getCorePath").mockReturnValueOnce(null);
await expect(cli.execute(Command)).rejects.toThrow(`"package" is required`);
- });
-
- it("should throw when the plugin doesn't exist", async () => {
- jest.spyOn(cli.app, "getCorePath").mockReturnValueOnce(__dirname);
- await expect(cli.withArgs([packageName]).execute(Command)).rejects.toThrow(
- `The package [${packageName}] does not exist.`,
- );
- });
- it("if the plugin is a git directory, it should be updated", async () => {
- expect(gitUpdateCalled).toEqual(false);
-
- jest.spyOn(fs, "existsSync").mockReturnValueOnce(true).mockReturnValueOnce(true);
-
- await expect(cli.withArgs([packageName]).execute(Command)).toResolve();
- expect(gitUpdateCalled).toEqual(true);
+ expect(spyOnUpdate).not.toHaveBeenCalled();
});
+ it("should call update", async () => {
+ await expect(cli.withArgs([packageName]).withFlags({ token, network }).execute(Command)).toResolve();
- it("if the plugin is a NPM package, it should be updated on default path", async () => {
- expect(npmUpdateCalled).toEqual(false);
- jest.spyOn(fs, "existsSync").mockReturnValueOnce(true).mockReturnValueOnce(false);
-
- await expect(cli.withArgs([packageName]).execute(Command)).toResolve();
- expect(npmUpdateCalled).toEqual(true);
+ expect(spyOnUpdate).toHaveBeenCalledWith(token, network, packageName);
});
});
diff --git a/__tests__/unit/core/commands/pool-clear.test.ts b/__tests__/unit/core/commands/pool-clear.test.ts
index c75de86b1b..28aa8911d9 100644
--- a/__tests__/unit/core/commands/pool-clear.test.ts
+++ b/__tests__/unit/core/commands/pool-clear.test.ts
@@ -19,7 +19,7 @@ describe("PoolClearCommand", () => {
prompts.inject([true]);
jest.spyOn(cli.app, "getCorePath").mockResolvedValueOnce(null);
await expect(cli.execute(Command)).toResolve();
- expect(removeSync).toHaveBeenCalled();
+ expect(removeSync).toHaveBeenCalledTimes(3);
});
it("should throw any errors", async () => {
@@ -48,6 +48,6 @@ describe("PoolClearCommand", () => {
prompts.inject([true]);
jest.spyOn(cli.app, "getCorePath").mockResolvedValueOnce(null);
await expect(cli.withFlags({ false: true }).execute(Command)).toResolve();
- expect(removeSync).toHaveBeenCalled();
+ expect(removeSync).toHaveBeenCalledTimes(3);
});
});
diff --git a/__tests__/unit/core/commands/reinstall.test.ts b/__tests__/unit/core/commands/reinstall.test.ts
index b44100b776..1ac4ccb783 100644
--- a/__tests__/unit/core/commands/reinstall.test.ts
+++ b/__tests__/unit/core/commands/reinstall.test.ts
@@ -25,7 +25,7 @@ describe("ReinstallCommand", () => {
await cli.withFlags({ force: true }).execute(Command);
// yarn info peerDependencies > yarn global add > pm2 update > check core > check relay > check forger
- expect(sync).toHaveBeenCalledTimes(6);
+ expect(sync).toHaveBeenCalledTimes(7);
sync.mockReset();
});
@@ -42,7 +42,7 @@ describe("ReinstallCommand", () => {
await cli.execute(Command);
// yarn info peerDependencies > yarn global add > pm2 update > check core > check relay > check forger
- expect(sync).toHaveBeenCalledTimes(6);
+ expect(sync).toHaveBeenCalledTimes(7);
sync.mockReset();
});
diff --git a/__tests__/unit/core/commands/relay-run.test.ts b/__tests__/unit/core/commands/relay-run.test.ts
index 56386674b2..2f0c1f6a40 100644
--- a/__tests__/unit/core/commands/relay-run.test.ts
+++ b/__tests__/unit/core/commands/relay-run.test.ts
@@ -25,7 +25,7 @@ describe("RunCommand", () => {
executeCommand(Command);
- await new Promise((resolve) => {
+ await new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 50);
diff --git a/__tests__/unit/core/commands/snapshot-dump.test.ts b/__tests__/unit/core/commands/snapshot-dump.test.ts
index 7fba20ec0a..3ea681bca4 100644
--- a/__tests__/unit/core/commands/snapshot-dump.test.ts
+++ b/__tests__/unit/core/commands/snapshot-dump.test.ts
@@ -8,6 +8,15 @@ let mockSnapshotService;
let mockEventListener;
let spyOnTerminate;
+jest.mock("@packages/core-cli", () => {
+ const original = jest.requireActual("@packages/core-cli");
+ return {
+ __esModule: true,
+ ...original,
+ Utils: { ...original.Utils, buildApplication: jest.fn() },
+ };
+});
+
beforeEach(() => {
cli = new Console();
diff --git a/__tests__/unit/core/commands/snapshot-restore.test.ts b/__tests__/unit/core/commands/snapshot-restore.test.ts
index bc93a4c99a..e883fd1550 100644
--- a/__tests__/unit/core/commands/snapshot-restore.test.ts
+++ b/__tests__/unit/core/commands/snapshot-restore.test.ts
@@ -8,6 +8,15 @@ let mockSnapshotService;
let mockEventListener;
let spyOnTerminate;
+jest.mock("@packages/core-cli", () => {
+ const original = jest.requireActual("@packages/core-cli");
+ return {
+ __esModule: true,
+ ...original,
+ Utils: { ...original.Utils, buildApplication: jest.fn() },
+ };
+});
+
beforeEach(() => {
cli = new Console();
diff --git a/__tests__/unit/core/commands/snapshot-rollback.test.ts b/__tests__/unit/core/commands/snapshot-rollback.test.ts
index 62a9138ce1..bd40fa12cf 100644
--- a/__tests__/unit/core/commands/snapshot-rollback.test.ts
+++ b/__tests__/unit/core/commands/snapshot-rollback.test.ts
@@ -7,6 +7,15 @@ let cli;
let mockSnapshotService;
let spyOnTerminate;
+jest.mock("@packages/core-cli", () => {
+ const original = jest.requireActual("@packages/core-cli");
+ return {
+ __esModule: true,
+ ...original,
+ Utils: { ...original.Utils, buildApplication: jest.fn() },
+ };
+});
+
beforeEach(() => {
cli = new Console();
diff --git a/__tests__/unit/core/commands/snapshot-truncate.test.ts b/__tests__/unit/core/commands/snapshot-truncate.test.ts
index 84e384691d..a782077068 100644
--- a/__tests__/unit/core/commands/snapshot-truncate.test.ts
+++ b/__tests__/unit/core/commands/snapshot-truncate.test.ts
@@ -7,6 +7,15 @@ let cli;
let mockSnapshotService;
let spyOnTerminate;
+jest.mock("@packages/core-cli", () => {
+ const original = jest.requireActual("@packages/core-cli");
+ return {
+ __esModule: true,
+ ...original,
+ Utils: { ...original.Utils, buildApplication: jest.fn() },
+ };
+});
+
beforeEach(() => {
cli = new Console();
diff --git a/__tests__/unit/core/commands/snapshot-verify.test.ts b/__tests__/unit/core/commands/snapshot-verify.test.ts
index e8db1262cf..caa112d5df 100644
--- a/__tests__/unit/core/commands/snapshot-verify.test.ts
+++ b/__tests__/unit/core/commands/snapshot-verify.test.ts
@@ -8,6 +8,15 @@ let mockSnapshotService;
let mockEventListener;
let spyOnTerminate;
+jest.mock("@packages/core-cli", () => {
+ const original = jest.requireActual("@packages/core-cli");
+ return {
+ __esModule: true,
+ ...original,
+ Utils: { ...original.Utils, buildApplication: jest.fn() },
+ };
+});
+
beforeEach(() => {
cli = new Console();
diff --git a/__tests__/unit/core/commands/update.test.ts b/__tests__/unit/core/commands/update.test.ts
index 5081b89d11..354e3c7165 100644
--- a/__tests__/unit/core/commands/update.test.ts
+++ b/__tests__/unit/core/commands/update.test.ts
@@ -75,7 +75,7 @@ describe("UpdateCommand", () => {
// yarn info peerDependencies
// yarn global add
// pm2 update
- expect(sync).toHaveBeenCalledTimes(3);
+ expect(sync).toHaveBeenCalledTimes(4);
});
it("should update and reset without a prompt if the [--force --reset] flag is present", async () => {
@@ -100,7 +100,7 @@ describe("UpdateCommand", () => {
// restart core
// restart relay
// restart forger
- expect(sync).toHaveBeenCalledTimes(3);
+ expect(sync).toHaveBeenCalledTimes(4);
});
it("should update with a prompt if the [--force] flag is not present", async () => {
@@ -148,7 +148,7 @@ describe("UpdateCommand", () => {
// restart core
// restart relay
// restart forger
- expect(sync).toHaveBeenCalledTimes(6);
+ expect(sync).toHaveBeenCalledTimes(7);
});
it("should update and restart the core process if the [--restartCore] flag is present", async () => {
@@ -168,7 +168,7 @@ describe("UpdateCommand", () => {
await cli.withFlags({ force: true, restartCore: true, updateProcessManager: true }).execute(Command);
- expect(sync).toHaveBeenCalledTimes(3);
+ expect(sync).toHaveBeenCalledTimes(4);
expect(isOnline).toHaveBeenCalled();
expect(restart).toHaveBeenCalledTimes(1);
expect(restart).toHaveBeenCalledWith("ark-core");
@@ -191,7 +191,7 @@ describe("UpdateCommand", () => {
await cli.withFlags({ force: true, restartRelay: true, updateProcessManager: true }).execute(Command);
- expect(sync).toHaveBeenCalledTimes(3);
+ expect(sync).toHaveBeenCalledTimes(4);
expect(isOnline).toHaveBeenCalled();
expect(restart).toHaveBeenCalledTimes(1);
expect(restart).toHaveBeenCalledWith("ark-relay");
@@ -214,7 +214,7 @@ describe("UpdateCommand", () => {
await cli.withFlags({ force: true, restartForger: true, updateProcessManager: true }).execute(Command);
- expect(sync).toHaveBeenCalledTimes(3);
+ expect(sync).toHaveBeenCalledTimes(4);
expect(isOnline).toHaveBeenCalled();
expect(restart).toHaveBeenCalledTimes(1);
expect(restart).toHaveBeenCalledWith("ark-forger");
@@ -243,7 +243,7 @@ describe("UpdateCommand", () => {
await cli.execute(Command);
- expect(sync).toHaveBeenCalledTimes(2);
+ expect(sync).toHaveBeenCalledTimes(3);
expect(isOnline).toHaveBeenCalled();
expect(restart).toHaveBeenCalledTimes(3);
});
diff --git a/__tests__/unit/core/internal/crypto.test.ts b/__tests__/unit/core/internal/crypto.test.ts
index e684f66d60..f90104cfdd 100644
--- a/__tests__/unit/core/internal/crypto.test.ts
+++ b/__tests__/unit/core/internal/crypto.test.ts
@@ -1,3 +1,4 @@
+import { Crypto } from "@packages/core/src/exceptions";
import { buildBIP38 } from "@packages/core/src/internal/crypto";
import { writeJSONSync } from "fs-extra";
import prompts from "prompts";
@@ -37,7 +38,7 @@ describe("buildBIP38", () => {
it("should throw if the delegate configuration does not exist", async () => {
await expect(buildBIP38({ token: "ark", network: "mainnet" })).rejects.toThrow(
- `The ${process.env.CORE_PATH_CONFIG}/delegates.json file does not exist.`,
+ new Crypto.MissingConfigFile(process.env.CORE_PATH_CONFIG + "/delegates.json"),
);
});
@@ -56,7 +57,7 @@ describe("buildBIP38", () => {
writeJSONSync(`${process.env.CORE_PATH_CONFIG}/delegates.json`, { secrets: [] });
await expect(buildBIP38({ token: "ark", network: "mainnet" })).rejects.toThrow(
- "We were unable to detect a BIP38 or BIP39 passphrase.",
+ new Crypto.PassphraseNotDetected(),
);
});
@@ -64,7 +65,15 @@ describe("buildBIP38", () => {
writeJSONSync(`${process.env.CORE_PATH_CONFIG}/delegates.json`, {});
await expect(buildBIP38({ token: "ark", network: "mainnet" })).rejects.toThrow(
- "We were unable to detect a BIP38 or BIP39 passphrase.",
+ new Crypto.PassphraseNotDetected(),
+ );
+ });
+
+ it("should throw if no bip38 password is provided and skipPrompts is true", async () => {
+ writeJSONSync(`${process.env.CORE_PATH_CONFIG}/delegates.json`, { bip38: "bip38" });
+
+ await expect(buildBIP38({ token: "ark", network: "mainnet", skipPrompts: true })).rejects.toThrow(
+ new Crypto.InvalidPassword(),
);
});
@@ -73,9 +82,7 @@ describe("buildBIP38", () => {
prompts.inject([null, true]);
- await expect(buildBIP38({ token: "ark", network: "mainnet" })).rejects.toThrow(
- "We've detected that you are using BIP38 but have not provided a valid password.",
- );
+ await expect(buildBIP38({ token: "ark", network: "mainnet" })).rejects.toThrow(new Crypto.InvalidPassword());
});
it("should throw if no confirmation is provided", async () => {
diff --git a/__tests__/unit/crypto/blocks/block.test.ts b/__tests__/unit/crypto/blocks/block.test.ts
index 933fe18a3c..01f65585f0 100644
--- a/__tests__/unit/crypto/blocks/block.test.ts
+++ b/__tests__/unit/crypto/blocks/block.test.ts
@@ -1,11 +1,10 @@
import "jest-extended";
-import { Interfaces, Managers, Utils } from "@arkecosystem/crypto";
import { BIP39 } from "@packages/core-forger/src/methods/bip39";
import { TransactionFactory } from "@packages/core-test-framework/src/utils/transaction-factory";
+import { Managers, Utils } from "@packages/crypto";
import { Block, BlockFactory, Deserializer, Serializer } from "@packages/crypto/src/blocks";
import { Slots } from "@packages/crypto/src/crypto";
-import { IBlock } from "@packages/crypto/src/interfaces";
import { configManager } from "@packages/crypto/src/managers";
import * as networks from "@packages/crypto/src/networks";
import { NetworkName } from "@packages/crypto/src/types";
@@ -17,7 +16,7 @@ const { outlookTable } = configManager.getPreset("mainnet").exceptions;
beforeEach(() => configManager.setFromPreset("devnet"));
-afterEach(() => jest.resetAllMocks());
+afterEach(() => jest.clearAllMocks());
describe("Block", () => {
const data = {
@@ -59,7 +58,7 @@ describe("Block", () => {
expect(block.verification.verified).toBeTrue();
});
- it("should fail to verify the block ", () => {
+ it("should fail to verify the block", () => {
const block = BlockFactory.fromData(data);
expect(block.verification.verified).toBeFalse();
@@ -103,7 +102,7 @@ describe("Block", () => {
.withPassphrase("super cool passphrase")
.create(210);
- const block: IBlock = delegate.forge(transactions, optionsDefault);
+ const block = delegate.forge(transactions, optionsDefault);
expect(block.verification.verified).toBeFalse();
expect(block.verification.errors).toContain("Transactions length is too high");
@@ -126,7 +125,7 @@ describe("Block", () => {
.withPassphrase("super cool passphrase")
.create();
- const block: IBlock = delegate.forge([transactions[0], transactions[0]], optionsDefault);
+ const block = delegate.forge([transactions[0], transactions[0]], optionsDefault);
expect(block.verification.verified).toBeFalse();
expect(block.verification.errors).toContain(`Encountered duplicate transaction: ${transactions[0].id}`);
@@ -136,7 +135,7 @@ describe("Block", () => {
jest.spyOn(configManager, "getMilestone").mockImplementation((height) => ({
block: {
version: 0,
- maxTransactions: 200,
+ maxTransactions: dummyBlock.numberOfTransactions,
maxPayload: dummyBlockSize - 1,
},
reward: 200000000,
@@ -151,7 +150,7 @@ describe("Block", () => {
jest.spyOn(configManager, "getMilestone").mockImplementation((height) => ({
block: {
version: 0,
- maxTransactions: 200,
+ maxTransactions: dummyBlock.numberOfTransactions,
maxPayload: dummyBlockSize,
},
reward: 200000000,
@@ -186,7 +185,7 @@ describe("Block", () => {
transactions[0].expiration = 102;
- const block: IBlock = delegate.forge(transactions, optionsDefault);
+ const block = delegate.forge(transactions, optionsDefault);
expect(block.verification.verified).toBeTrue();
});
@@ -209,7 +208,7 @@ describe("Block", () => {
.withPassphrase("super cool passphrase")
.create();
- const block: IBlock = delegate.forge(transactions, optionsDefault);
+ const block = delegate.forge(transactions, optionsDefault);
expect(block.verification.verified).toBeFalse();
expect(block.verification.errors).toContain(`Encountered expired transaction: ${transactions[0].id}`);
});
@@ -235,7 +234,7 @@ describe("Block", () => {
.create();
Managers.configManager.getMilestone().aip11 = false;
- const block: IBlock = delegate.forge(transactions, optionsDefault);
+ const block = delegate.forge(transactions, optionsDefault);
expect(block.verification.verified).toBeFalse();
expect(block.verification.errors).toContain(`Encountered expired transaction: ${transactions[0].id}`);
Managers.configManager.getMilestone().aip11 = true;
@@ -266,7 +265,7 @@ describe("Block", () => {
.create();
Managers.configManager.getMilestone().aip11 = false;
- const block: IBlock = delegate.forge(transactions, optionsDefault);
+ const block = delegate.forge(transactions, optionsDefault);
expect(block.verification.verified).toBeTrue();
Managers.configManager.getMilestone().aip11 = true;
});
@@ -296,7 +295,7 @@ describe("Block", () => {
.create();
Managers.configManager.getMilestone().aip11 = false;
- const block: IBlock = delegate.forge(transactions, optionsDefault);
+ const block = delegate.forge(transactions, optionsDefault);
expect(block.verification.verified).toBeFalse();
expect(block.verification.errors).toContain(`Encountered future transaction: ${transactions[0].id}`);
Managers.configManager.getMilestone().aip11 = true;
@@ -326,7 +325,7 @@ describe("Block", () => {
.withPassphrase("super cool passphrase")
.create();
- const block: IBlock = delegate.forge(transactions, optionsDefault);
+ const block = delegate.forge(transactions, optionsDefault);
expect(block.verification.verified).toBeTrue();
expect(block.verification.errors).toBeEmpty();
});
@@ -354,7 +353,7 @@ describe("Block", () => {
.withPassphrase("super cool passphrase")
.create();
- const block: IBlock = delegate.forge(transactions, optionsDefault);
+ const block = delegate.forge(transactions, optionsDefault);
expect(block.verification.verified).toBeFalse();
expect(block.verification.errors).toContain(`Encountered future transaction: ${transactions[0].id}`);
});
@@ -715,7 +714,7 @@ describe("Block", () => {
configManager.setFromPreset(network);
configManager.getMilestone().aip11 = false;
- const block: Interfaces.IBlock = BlockFactory.fromJson(networks[network].genesisBlock);
+ const block = BlockFactory.fromJson(networks[network].genesisBlock);
expect(block.serialized).toHaveLength(length);
expect(block.verifySignature()).toBeTrue();
diff --git a/__tests__/unit/crypto/transactions/builders/transactions/transfer.test.ts b/__tests__/unit/crypto/transactions/builders/transactions/transfer.test.ts
index 46c6676586..ed486813f2 100644
--- a/__tests__/unit/crypto/transactions/builders/transactions/transfer.test.ts
+++ b/__tests__/unit/crypto/transactions/builders/transactions/transfer.test.ts
@@ -1,7 +1,7 @@
import "jest-extended";
-import { Utils } from "@arkecosystem/crypto";
-import { Factories, Generators } from "@packages/core-test-framework/src";
+import { Factories, Generators } from "@packages/core-test-framework";
+import { Utils } from "@packages/crypto";
import { TransactionType } from "@packages/crypto/src/enums";
import { Keys, WIF } from "@packages/crypto/src/identities";
import { configManager } from "@packages/crypto/src/managers";
diff --git a/__tests__/unit/crypto/transactions/deserializer.test.ts b/__tests__/unit/crypto/transactions/deserializer.test.ts
index e0ba6d2243..95be666def 100644
--- a/__tests__/unit/crypto/transactions/deserializer.test.ts
+++ b/__tests__/unit/crypto/transactions/deserializer.test.ts
@@ -1,23 +1,23 @@
import "jest-extended";
import { Generators } from "@packages/core-test-framework/src";
-import ByteBuffer from "bytebuffer";
-
-import { Enums, Errors, Utils } from "../../../../packages/crypto/src";
-import { Hash } from "../../../../packages/crypto/src/crypto";
+import { Enums, Errors, Utils } from "@packages/crypto/src";
+import { Hash } from "@packages/crypto/src/crypto";
import {
InvalidTransactionBytesError,
TransactionSchemaError,
TransactionVersionError,
UnkownTransactionError,
-} from "../../../../packages/crypto/src/errors";
-import { Address, Keys, PublicKey } from "../../../../packages/crypto/src/identities";
-import { IKeyPair, ITransaction, ITransactionData } from "../../../../packages/crypto/src/interfaces";
-import { configManager } from "../../../../packages/crypto/src/managers";
-import { TransactionFactory, Utils as TransactionUtils, Verifier } from "../../../../packages/crypto/src/transactions";
-import { BuilderFactory } from "../../../../packages/crypto/src/transactions/builders";
-import { Deserializer } from "../../../../packages/crypto/src/transactions/deserializer";
-import { Serializer } from "../../../../packages/crypto/src/transactions/serializer";
+} from "@packages/crypto/src/errors";
+import { Address, Keys, PublicKey } from "@packages/crypto/src/identities";
+import { IKeyPair, ITransaction, ITransactionData } from "@packages/crypto/src/interfaces";
+import { configManager } from "@packages/crypto/src/managers";
+import { TransactionFactory, Utils as TransactionUtils, Verifier } from "@packages/crypto/src/transactions";
+import { BuilderFactory } from "@packages/crypto/src/transactions/builders";
+import { Deserializer } from "@packages/crypto/src/transactions/deserializer";
+import { Serializer } from "@packages/crypto/src/transactions/serializer";
+import ByteBuffer from "bytebuffer";
+
import { htlcSecretHashHex, htlcSecretHex } from "./__fixtures__/htlc";
import { legacyMultiSignatureRegistration } from "./__fixtures__/transaction";
@@ -38,447 +38,488 @@ describe("Transaction serializer / deserializer", () => {
expect(Verifier.verify(deserialized.data)).toBeTrue();
};
- describe("ser/deserialize - transfer", () => {
- const transfer = BuilderFactory.transfer()
- .recipientId("D5q7YfEFDky1JJVQQEy4MGyiUhr5cGg47F")
- .amount("10000")
- .fee("50000000")
- .vendorField("cool vendor field")
- .network(30)
- .sign("dummy passphrase")
- .getStruct();
-
- it("should ser/deserialize giving back original fields", () => {
- const serialized = TransactionFactory.fromData(transfer).serialized.toString("hex");
- const deserialized = Deserializer.deserialize(serialized);
-
- checkCommonFields(deserialized, transfer);
-
- expect(deserialized.data.vendorField).toBe(transfer.vendorField);
- expect(deserialized.data.recipientId).toBe(transfer.recipientId);
+ describe("Version 1", () => {
+ beforeEach(() => {
+ configManager.getMilestone().aip11 = false;
});
- it("should ser/deserialize with long vendorfield when vendorFieldLength=255 milestone is active", () => {
- configManager.getMilestone().vendorFieldLength = 255;
-
- const transferWithLongVendorfield = BuilderFactory.transfer()
+ describe("ser/deserialize - transfer", () => {
+ const transfer = BuilderFactory.transfer()
.recipientId("D5q7YfEFDky1JJVQQEy4MGyiUhr5cGg47F")
.amount("10000")
.fee("50000000")
- .vendorField("y".repeat(255))
+ .timestamp(148354645)
+ .vendorField("cool vendor field")
.network(30)
.sign("dummy passphrase")
.getStruct();
- const serialized = TransactionUtils.toBytes(transferWithLongVendorfield);
- const deserialized = TransactionFactory.fromBytes(serialized);
+ it("should ser/deserialize giving back original fields", () => {
+ const serialized = TransactionFactory.fromData(transfer).serialized.toString("hex");
+ expect(serialized).toEqual(
+ "ff011e0055b6d70802a47a2f594635737d2ce9898680812ff7fa6aaa64ddea1360474c110e9985a08780f0fa020000000011636f6f6c2076656e646f72206669656c641027000000000000000000001e07917aa042bf600339e13ed57c5364a71eebb8c33044022013287e3d1713e1a407068af0054412dc523476a8786823b8744d2ba8a3daa144022059f30896ad610aecb145275bd89de58ddaeb7c703d31fdab2a02efa3ea4ae1bd",
+ );
- expect(deserialized.verified).toBeTrue();
- expect(deserialized.data.vendorField).toHaveLength(255);
- expect(deserialized.data.vendorField).toEqual("y".repeat(255));
+ const deserialized = Deserializer.deserialize(serialized);
- configManager.getMilestone().vendorFieldLength = 64;
- });
+ checkCommonFields(deserialized, transfer);
- it("should not ser/deserialize long vendorfield when vendorFieldLength=255 milestone is not active", () => {
- const transferWithLongVendorfield = BuilderFactory.transfer()
- .recipientId("D5q7YfEFDky1JJVQQEy4MGyiUhr5cGg47F")
- .amount("10000")
- .fee("50000000")
- .network(30)
- .sign("dummy passphrase")
- .getStruct();
+ expect(deserialized.data.vendorField).toBe(transfer.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 = BuilderFactory.transfer()
+ .recipientId("D5q7YfEFDky1JJVQQEy4MGyiUhr5cGg47F")
+ .amount("10000")
+ .fee("50000000")
+ .timestamp(148354645)
+ .vendorField("y".repeat(255))
+ .network(30)
+ .sign("dummy passphrase")
+ .getStruct();
- transferWithLongVendorfield.vendorField = "y".repeat(255);
- expect(() => {
const serialized = TransactionUtils.toBytes(transferWithLongVendorfield);
- TransactionFactory.fromBytes(serialized);
- }).toThrow(TransactionSchemaError);
- });
- });
+ expect(serialized.toString("hex")).toEqual(
+ "ff011e0055b6d70802a47a2f594635737d2ce9898680812ff7fa6aaa64ddea1360474c110e9985a08780f0fa0200000000ff7979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979797979791027000000000000000000001e07917aa042bf600339e13ed57c5364a71eebb8c330450221008c7278bf5a3a0f79afade499f3c2c574a54d936708e078408030e222fb4aaea102203559198c9b99a4fe679e40c48c8d25390e5bdf42ec2aafe6a2c85ab637e34748",
+ );
+ const deserialized = TransactionFactory.fromBytes(serialized);
- describe("ser/deserialize - second signature", () => {
- it("should ser/deserialize giving back original fields", () => {
- const secondSignature = BuilderFactory.secondSignature()
- .signatureAsset("signature")
- .fee("50000000")
- .network(30)
- .sign("dummy passphrase")
- .getStruct();
+ expect(deserialized.verified).toBeTrue();
+ expect(deserialized.data.vendorField).toHaveLength(255);
+ expect(deserialized.data.vendorField).toEqual("y".repeat(255));
- const serialized = TransactionFactory.fromData(secondSignature).serialized.toString("hex");
- const deserialized = Deserializer.deserialize(serialized);
+ configManager.getMilestone().vendorFieldLength = 64;
+ });
- checkCommonFields(deserialized, secondSignature);
+ it("should not ser/deserialize long vendorfield when vendorFieldLength=255 milestone is not active", () => {
+ const transferWithLongVendorfield = BuilderFactory.transfer()
+ .recipientId("D5q7YfEFDky1JJVQQEy4MGyiUhr5cGg47F")
+ .amount("10000")
+ .fee("50000000")
+ .network(30)
+ .sign("dummy passphrase")
+ .getStruct();
- expect(deserialized.data.asset).toEqual(secondSignature.asset);
+ transferWithLongVendorfield.vendorField = "y".repeat(255);
+ expect(() => {
+ const serialized = TransactionUtils.toBytes(transferWithLongVendorfield);
+ TransactionFactory.fromBytes(serialized);
+ }).toThrow(TransactionSchemaError);
+ });
});
- });
- describe("ser/deserialize - delegate registration", () => {
- it("should ser/deserialize giving back original fields", () => {
- const delegateRegistration = BuilderFactory.delegateRegistration()
- .usernameAsset("homer")
- .network(30)
- .sign("dummy passphrase")
- .getStruct();
+ describe("ser/deserialize - second signature", () => {
+ it("should ser/deserialize giving back original fields", () => {
+ const secondSignature = BuilderFactory.secondSignature()
+ .signatureAsset("signature")
+ .fee("50000000")
+ .timestamp(148354645)
+ .network(30)
+ .sign("dummy passphrase")
+ .getStruct();
- const serialized = TransactionFactory.fromData(delegateRegistration).serialized.toString("hex");
- const deserialized = Deserializer.deserialize(serialized);
+ const serialized = TransactionFactory.fromData(secondSignature).serialized.toString("hex");
+ expect(serialized).toEqual(
+ "ff011e0155b6d70802a47a2f594635737d2ce9898680812ff7fa6aaa64ddea1360474c110e9985a08780f0fa02000000000002314387e7f065bc95bb23197b39179f6fb8b9a23771e228a65326604f2c9860a13044022066b9d53702e38f2cf416fed043d30da6fa1adc140d144bfd563ba0be48f2de16022007c82133bc9c23a81390c4ed2237865a57c662bd536e5f1fcab2a3ae14f06287",
+ );
+ const deserialized = Deserializer.deserialize(serialized);
- checkCommonFields(deserialized, delegateRegistration);
+ checkCommonFields(deserialized, secondSignature);
- expect(deserialized.data.asset).toEqual(delegateRegistration.asset);
+ expect(deserialized.data.asset).toEqual(secondSignature.asset);
+ });
});
- });
- describe("ser/deserialize - vote", () => {
- it("should ser/deserialize giving back original fields", () => {
- const vote = BuilderFactory.vote()
- .votesAsset(["+02bcfa0951a92e7876db1fb71996a853b57f996972ed059a950d910f7d541706c9"])
- .fee("50000000")
- .network(30)
- .sign("dummy passphrase")
- .getStruct();
+ describe("ser/deserialize - delegate registration", () => {
+ it("should ser/deserialize giving back original fields", () => {
+ const delegateRegistration = BuilderFactory.delegateRegistration()
+ .usernameAsset("homer")
+ .timestamp(148354645)
+ .network(30)
+ .sign("dummy passphrase")
+ .getStruct();
- const serialized = TransactionFactory.fromData(vote).serialized.toString("hex");
- const deserialized = Deserializer.deserialize(serialized);
+ const serialized = TransactionFactory.fromData(delegateRegistration).serialized.toString("hex");
+ expect(serialized).toEqual(
+ "ff011e0255b6d70802a47a2f594635737d2ce9898680812ff7fa6aaa64ddea1360474c110e9985a08700f90295000000000005686f6d6572304402207752a833bd57722134a07796a44eb2a132e37a873e6fdb51c1bf217116f6293d02204ee141716b142e49e62488ee9c97458a38942177a7ef8f7737b702c896245655",
+ );
+ const deserialized = Deserializer.deserialize(serialized);
- checkCommonFields(deserialized, vote);
+ checkCommonFields(deserialized, delegateRegistration);
- expect(deserialized.data.asset).toEqual(vote.asset);
+ expect(deserialized.data.asset).toEqual(delegateRegistration.asset);
+ });
});
- });
- describe("ser/deserialize - multi signature (LEGACY)", () => {
- it.skip("should ser/deserialize a legacy multisig registration", () => {
- const deserialized = TransactionFactory.fromHex(legacyMultiSignatureRegistration.serialized);
+ describe("ser/deserialize - vote", () => {
+ it("should ser/deserialize giving back original fields", () => {
+ const vote = BuilderFactory.vote()
+ .votesAsset(["+02bcfa0951a92e7876db1fb71996a853b57f996972ed059a950d910f7d541706c9"])
+ .timestamp(148354645)
+ .fee("50000000")
+ .network(30)
+ .sign("dummy passphrase")
+ .getStruct();
- expect(deserialized.id).toEqual(legacyMultiSignatureRegistration.data.id);
- expect(deserialized.toJson()).toMatchObject(legacyMultiSignatureRegistration.data);
- });
- });
+ const serialized = TransactionFactory.fromData(vote).serialized.toString("hex");
+ expect(serialized).toEqual(
+ "ff011e0355b6d70802a47a2f594635737d2ce9898680812ff7fa6aaa64ddea1360474c110e9985a08780f0fa020000000000010102bcfa0951a92e7876db1fb71996a853b57f996972ed059a950d910f7d541706c93045022100b80680e9368e830663c10e52ab69b4adbbf4d55b9701a301cec5849640109fb102202c42de6a55792a16c8394d0981a852b0dea314b14378cdd3b4026e6b2e840074",
+ );
+ const deserialized = Deserializer.deserialize(serialized);
- describe("ser/deserialize - multi signature", () => {
- let multiSignatureRegistration;
+ checkCommonFields(deserialized, vote);
- beforeEach(() => {
- // todo: completely wrap this into a function to hide the generation and setting of the config?
- configManager.setConfig(Generators.generateCryptoConfigRaw());
+ expect(deserialized.data.asset).toEqual(vote.asset);
+ });
+ });
- const participant1 = Keys.fromPassphrase("secret 1");
- const participant2 = Keys.fromPassphrase("secret 2");
- const participant3 = Keys.fromPassphrase("secret 3");
+ describe("ser/deserialize - multi signature (LEGACY)", () => {
+ it.skip("should ser/deserialize a legacy multisig registration", () => {
+ const deserialized = TransactionFactory.fromHex(legacyMultiSignatureRegistration.serialized);
- multiSignatureRegistration = BuilderFactory.multiSignature()
- .senderPublicKey(participant1.publicKey)
- .network(23)
- .participant(participant1.publicKey)
- .participant(participant2.publicKey)
- .participant(participant3.publicKey)
- .min(3)
- .multiSign("secret 1", 0)
- .multiSign("secret 2", 1)
- .multiSign("secret 3", 2)
- .sign("secret 1")
- .getStruct();
+ expect(deserialized.id).toEqual(legacyMultiSignatureRegistration.data.id);
+ expect(deserialized.toJson()).toMatchObject(legacyMultiSignatureRegistration.data);
+ });
});
- it("should ser/deserialize a multisig registration", () => {
- const transaction = TransactionFactory.fromData(multiSignatureRegistration);
- const deserialized = TransactionFactory.fromBytes(transaction.serialized);
+ describe("ser/deserialize - multi signature", () => {
+ let multiSignatureRegistration;
+
+ beforeEach(() => {
+ // todo: completely wrap this into a function to hide the generation and setting of the config?
+ configManager.setConfig(Generators.generateCryptoConfigRaw());
+
+ const participant1 = Keys.fromPassphrase("secret 1");
+ const participant2 = Keys.fromPassphrase("secret 2");
+ const participant3 = Keys.fromPassphrase("secret 3");
+
+ multiSignatureRegistration = BuilderFactory.multiSignature()
+ .senderPublicKey(participant1.publicKey)
+ .network(23)
+ .timestamp(148354645)
+ .participant(participant1.publicKey)
+ .participant(participant2.publicKey)
+ .participant(participant3.publicKey)
+ .min(3)
+ .multiSign("secret 1", 0)
+ .multiSign("secret 2", 1)
+ .multiSign("secret 3", 2)
+ .sign("secret 1")
+ .getStruct();
+ });
- expect(transaction.isVerified).toBeTrue();
- expect(deserialized.isVerified).toBeTrue();
- expect(deserialized.data.asset).toEqual(multiSignatureRegistration.asset);
- expect(transaction.data.signatures).toEqual(deserialized.data.signatures);
- checkCommonFields(deserialized, multiSignatureRegistration);
- });
+ it("should ser/deserialize a multisig registration", () => {
+ const transaction = TransactionFactory.fromData(multiSignatureRegistration);
+ expect(transaction.serialized.toString("hex")).toEqual(
+ "ff02170100000004000000000000000000039180ea4a8a803ee11ecb462bb8f9613fcdb5fe917e292dbcc73409f0e98f8f220094357700000000000303039180ea4a8a803ee11ecb462bb8f9613fcdb5fe917e292dbcc73409f0e98f8f22028d3611c4f32feca3e6713992ae9387e18a0e01954046511878fe078703324dc0021d3932ab673230486d0f956d05b9e88791ee298d9af2d6df7d9ed5bb861c92dd5e3b61f2d6a2589b9abb4ed1dfd7253a1d2ac586d383a8a17237928d4648cd6d3ff1c8b025c7e150282f73ecd8a13f86254b508940ebb23d4f6f4f473b3a15de007dbeace4f00c9d5961a6bd65b4dbc6ea2b4c6278adc351e6da9eeba69478a7784e17754eff20a3ccd68cd48d87847d40768785852594e73cc6b01105064b6b5701d1c435978899abafd485f5e2dfe737781368efc1c4d02f4009a650849a434b923b2e6b79164181df493c9a05e2e6664c2ac0059d9194c792c1b1a85695b6215e0286b1d2b6abf7ca5090c66598e898aa08ba2de90dbd2ae9fe728b8b2d29504c6ad5a96ca1f0365e0390d0cf508550ded10a5e111db5bbf2b0f87972215ee69f9f",
+ );
+ const deserialized = TransactionFactory.fromBytes(transaction.serialized);
+
+ expect(transaction.isVerified).toBeTrue();
+ expect(deserialized.isVerified).toBeTrue();
+ expect(deserialized.data.asset).toEqual(multiSignatureRegistration.asset);
+ expect(transaction.data.signatures).toEqual(deserialized.data.signatures);
+ checkCommonFields(deserialized, multiSignatureRegistration);
+ });
- it("should fail to verify", () => {
- const transaction = TransactionFactory.fromData(multiSignatureRegistration);
- configManager.getMilestone().aip11 = false;
- expect(transaction.verify()).toBeFalse();
- configManager.getMilestone().aip11 = true;
- expect(transaction.verify()).toBeTrue();
- });
+ it("should fail to verify", () => {
+ const transaction = TransactionFactory.fromData(multiSignatureRegistration);
+ configManager.getMilestone().aip11 = false;
+ expect(transaction.verify()).toBeFalse();
+ configManager.getMilestone().aip11 = true;
+ expect(transaction.verify()).toBeTrue();
+ });
- it("should not deserialize a malformed signature", () => {
- const transaction = TransactionFactory.fromData(multiSignatureRegistration);
- transaction.serialized = transaction.serialized.slice(0, transaction.serialized.length - 2);
+ it("should not deserialize a malformed signature", () => {
+ const transaction = TransactionFactory.fromData(multiSignatureRegistration);
+ transaction.serialized = transaction.serialized.slice(0, transaction.serialized.length - 2);
- expect(() => TransactionFactory.fromBytes(transaction.serialized)).toThrowError(
- InvalidTransactionBytesError,
- );
+ expect(() => TransactionFactory.fromBytes(transaction.serialized)).toThrowError(
+ InvalidTransactionBytesError,
+ );
+ });
});
});
- describe("ser/deserialize - ipfs", () => {
- let ipfsTransaction;
+ describe("Version 2", () => {
+ beforeEach(() => {
+ configManager.getMilestone().aip11 = true;
+ });
- const ipfsIds = [
- "QmR45FmbVVrixReBwJkhEKde2qwHYaQzGxu4ZoDeswuF9w",
- "QmYSK2JyM3RyDyB52caZCTKFR3HKniEcMnNJYdk8DQ6KKB",
- "QmQeUqdjFmaxuJewStqCLUoKrR9khqb4Edw9TfRQQdfWz3",
- "Qma98bk1hjiRZDTmYmfiUXDj8hXXt7uGA5roU5mfUb3sVG",
- ];
+ describe("ser/deserialize - ipfs", () => {
+ let ipfsTransaction;
- beforeAll(() => {
- // todo: completely wrap this into a function to hide the generation and setting of the config?
- configManager.setConfig(Generators.generateCryptoConfigRaw());
- });
+ const ipfsIds = [
+ "QmR45FmbVVrixReBwJkhEKde2qwHYaQzGxu4ZoDeswuF9w",
+ "QmYSK2JyM3RyDyB52caZCTKFR3HKniEcMnNJYdk8DQ6KKB",
+ "QmQeUqdjFmaxuJewStqCLUoKrR9khqb4Edw9TfRQQdfWz3",
+ "Qma98bk1hjiRZDTmYmfiUXDj8hXXt7uGA5roU5mfUb3sVG",
+ ];
- beforeEach(() => {
- ipfsTransaction = BuilderFactory.ipfs()
- .fee("50000000")
- .version(2)
- .network(23)
- .ipfsAsset(ipfsIds[0])
- .sign("dummy passphrase")
- .getStruct();
- });
+ beforeAll(() => {
+ // todo: completely wrap this into a function to hide the generation and setting of the config?
+ configManager.setConfig(Generators.generateCryptoConfigRaw());
+ });
- it("should ser/deserialize giving back original fields", () => {
- const serialized = TransactionFactory.fromData(ipfsTransaction).serialized.toString("hex");
- const deserialized = Deserializer.deserialize(serialized);
+ beforeEach(() => {
+ ipfsTransaction = BuilderFactory.ipfs()
+ .fee("50000000")
+ .version(2)
+ .network(23)
+ .timestamp(148354645)
+ .ipfsAsset(ipfsIds[0])
+ .sign("dummy passphrase")
+ .getStruct();
+ });
- checkCommonFields(deserialized, ipfsTransaction);
+ it("should ser/deserialize giving back original fields", () => {
+ const serialized = TransactionFactory.fromData(ipfsTransaction).serialized.toString("hex");
+ expect(serialized).toEqual(
+ "ff0217010000000500000000000000000002a47a2f594635737d2ce9898680812ff7fa6aaa64ddea1360474c110e9985a08780f0fa02000000000012202853f0f11ab91d73b73a2a86606103f45dd469ad2e89ec6f9a25febe8758d3fed28df5c7334e86d67074330c8e4418c47ca82a6aff823431c9690213b6983dd82569730fb267ad96750a5249b7f751d2beb3b0958ed48b0517223531d80eaf89",
+ );
+ const deserialized = Deserializer.deserialize(serialized);
- expect(deserialized.data.asset).toEqual(ipfsTransaction.asset);
- });
+ checkCommonFields(deserialized, ipfsTransaction);
- it("should fail to verify", () => {
- const transaction = TransactionFactory.fromData(ipfsTransaction);
- configManager.getMilestone().aip11 = false;
- expect(transaction.verify()).toBeFalse();
- configManager.getMilestone().aip11 = true;
- expect(transaction.verify()).toBeTrue();
+ expect(deserialized.data.asset).toEqual(ipfsTransaction.asset);
+ });
+
+ it("should fail to verify", () => {
+ const transaction = TransactionFactory.fromData(ipfsTransaction);
+ configManager.getMilestone().aip11 = false;
+ expect(transaction.verify()).toBeFalse();
+ configManager.getMilestone().aip11 = true;
+ expect(transaction.verify()).toBeTrue();
+ });
});
- });
- describe("ser/deserialize - delegate resignation", () => {
- it("should ser/deserialize giving back original fields", () => {
- const delegateResignation = BuilderFactory.delegateResignation()
- .fee("50000000")
- .network(23)
- .sign("dummy passphrase")
- .getStruct();
+ describe("ser/deserialize - delegate resignation", () => {
+ it("should ser/deserialize giving back original fields", () => {
+ const delegateResignation = BuilderFactory.delegateResignation()
+ .fee("50000000")
+ .network(23)
+ .sign("dummy passphrase")
+ .getStruct();
- const serialized = TransactionFactory.fromData(delegateResignation).serialized.toString("hex");
- const deserialized = Deserializer.deserialize(serialized);
+ const serialized = TransactionFactory.fromData(delegateResignation).serialized.toString("hex");
+ const deserialized = Deserializer.deserialize(serialized);
- checkCommonFields(deserialized, delegateResignation);
- });
+ checkCommonFields(deserialized, delegateResignation);
+ });
- it("should fail to verify", () => {
- const delegateResignation = BuilderFactory.delegateResignation()
- .fee("50000000")
- .network(23)
- .sign("dummy passphrase")
- .build();
+ it("should fail to verify", () => {
+ const delegateResignation = BuilderFactory.delegateResignation()
+ .fee("50000000")
+ .network(23)
+ .sign("dummy passphrase")
+ .build();
- configManager.getMilestone().aip11 = false;
- expect(delegateResignation.verify()).toBeFalse();
- configManager.getMilestone().aip11 = true;
- expect(delegateResignation.verify()).toBeTrue();
+ configManager.getMilestone().aip11 = false;
+ expect(delegateResignation.verify()).toBeFalse();
+ configManager.getMilestone().aip11 = true;
+ expect(delegateResignation.verify()).toBeTrue();
+ });
});
- });
- describe("ser/deserialize - multi payment", () => {
- beforeAll(() => {
- // todo: completely wrap this into a function to hide the generation and setting of the config?
- configManager.setConfig(Generators.generateCryptoConfigRaw());
- });
+ describe("ser/deserialize - multi payment", () => {
+ beforeAll(() => {
+ // todo: completely wrap this into a function to hide the generation and setting of the config?
+ configManager.setConfig(Generators.generateCryptoConfigRaw());
+ });
- it("should ser/deserialize giving back original fields", () => {
- const multiPayment = BuilderFactory.multiPayment()
- .fee("50000000")
- .network(23)
- .addPayment("AW5wtiimZntaNvxH6QBi7bBpH2rDtFeD8C", "1555")
- .addPayment("AW5wtiimZntaNvxH6QBi7bBpH2rDtFeD8C", "5000")
- .vendorField("Multipayment")
- .sign("dummy passphrase")
- .getStruct();
+ it("should ser/deserialize giving back original fields", () => {
+ const multiPayment = BuilderFactory.multiPayment()
+ .fee("50000000")
+ .network(23)
+ .addPayment("AW5wtiimZntaNvxH6QBi7bBpH2rDtFeD8C", "1555")
+ .addPayment("AW5wtiimZntaNvxH6QBi7bBpH2rDtFeD8C", "5000")
+ .vendorField("Multipayment")
+ .sign("dummy passphrase")
+ .getStruct();
- const serialized = TransactionFactory.fromData(multiPayment).serialized.toString("hex");
- const deserialized = Deserializer.deserialize(serialized);
+ const serialized = TransactionFactory.fromData(multiPayment).serialized.toString("hex");
+ const deserialized = Deserializer.deserialize(serialized);
- checkCommonFields(deserialized, multiPayment);
- });
+ checkCommonFields(deserialized, multiPayment);
+ });
- it("should fail to verify", () => {
- const multiPayment = BuilderFactory.multiPayment()
- .fee("50000000")
- .network(23)
- .addPayment("AW5wtiimZntaNvxH6QBi7bBpH2rDtFeD8C", "1555")
- .addPayment("AW5wtiimZntaNvxH6QBi7bBpH2rDtFeD8C", "5000")
- .sign("dummy passphrase")
- .build();
+ it("should fail to verify", () => {
+ const multiPayment = BuilderFactory.multiPayment()
+ .fee("50000000")
+ .network(23)
+ .addPayment("AW5wtiimZntaNvxH6QBi7bBpH2rDtFeD8C", "1555")
+ .addPayment("AW5wtiimZntaNvxH6QBi7bBpH2rDtFeD8C", "5000")
+ .sign("dummy passphrase")
+ .build();
- configManager.getMilestone().aip11 = false;
- expect(multiPayment.verify()).toBeFalse();
- configManager.getMilestone().aip11 = true;
- expect(multiPayment.verify()).toBeTrue();
- });
+ configManager.getMilestone().aip11 = false;
+ expect(multiPayment.verify()).toBeFalse();
+ configManager.getMilestone().aip11 = true;
+ expect(multiPayment.verify()).toBeTrue();
+ });
- it("should fail if more than hardcoded maximum of payments", () => {
- const multiPayment = BuilderFactory.multiPayment().fee("50000000").network(23);
+ it("should fail if more than hardcoded maximum of payments", () => {
+ const multiPayment = BuilderFactory.multiPayment().fee("50000000").network(23);
- for (let i = 0; i < configManager.getMilestone().multiPaymentLimit; i++) {
- multiPayment.addPayment(Address.fromPassphrase(`recipient-${i}`), "1");
- }
+ for (let i = 0; i < configManager.getMilestone().multiPaymentLimit; i++) {
+ multiPayment.addPayment(Address.fromPassphrase(`recipient-${i}`), "1");
+ }
- expect(() => multiPayment.addPayment(Address.fromPassphrase("recipientBad"), "1")).toThrow(
- Errors.MaximumPaymentCountExceededError,
- );
+ expect(() => multiPayment.addPayment(Address.fromPassphrase("recipientBad"), "1")).toThrow(
+ Errors.MaximumPaymentCountExceededError,
+ );
- const transaction = multiPayment.sign("dummy passphrase").build();
- expect(transaction.verify()).toBeTrue();
- expect(TransactionFactory.fromBytes(transaction.serialized, true).verify()).toBeTrue();
- });
+ const transaction = multiPayment.sign("dummy passphrase").build();
+ expect(transaction.verify()).toBeTrue();
+ expect(TransactionFactory.fromBytes(transaction.serialized, true).verify()).toBeTrue();
+ });
- it("should fail if recipient on different network", () => {
- expect(() =>
- BuilderFactory.multiPayment()
- .fee("50000000")
- .addPayment("DBzGiUk8UVjB2dKCfGRixknB7Ki3Zhqthp", "1555")
- .addPayment("AJWRd23HNEhPLkK1ymMnwnDBX2a7QBZqff", "1555")
- .sign("dummy passphrase")
- .build(),
- ).toThrow(InvalidTransactionBytesError);
+ it("should fail if recipient on different network", () => {
+ expect(() =>
+ BuilderFactory.multiPayment()
+ .fee("50000000")
+ .addPayment("DBzGiUk8UVjB2dKCfGRixknB7Ki3Zhqthp", "1555")
+ .addPayment("AJWRd23HNEhPLkK1ymMnwnDBX2a7QBZqff", "1555")
+ .sign("dummy passphrase")
+ .build(),
+ ).toThrow(InvalidTransactionBytesError);
+ });
});
- });
- describe("ser/deserialize - htlc lock", () => {
- const htlcLockAsset = {
- secretHash: htlcSecretHashHex,
- expiration: {
- type: Enums.HtlcLockExpirationType.EpochTimestamp,
- value: Math.floor(Date.now() / 1000),
- },
- };
+ describe("ser/deserialize - htlc lock", () => {
+ const htlcLockAsset = {
+ secretHash: htlcSecretHashHex,
+ expiration: {
+ type: Enums.HtlcLockExpirationType.EpochTimestamp,
+ value: Math.floor(Date.now() / 1000),
+ },
+ };
- beforeAll(() => {
- // todo: completely wrap this into a function to hide the generation and setting of the config?
- configManager.setConfig(Generators.generateCryptoConfigRaw());
- });
+ beforeAll(() => {
+ // todo: completely wrap this into a function to hide the generation and setting of the config?
+ configManager.setConfig(Generators.generateCryptoConfigRaw());
+ });
- it("should ser/deserialize giving back original fields", () => {
- const htlcLock = BuilderFactory.htlcLock()
- .recipientId("AJWRd23HNEhPLkK1ymMnwnDBX2a7QBZqff")
- .amount("10000")
- .fee("50000000")
- .network(23)
- .vendorField("HTLC")
- .htlcLockAsset(htlcLockAsset)
- .sign("dummy passphrase")
- .getStruct();
+ it("should ser/deserialize giving back original fields", () => {
+ const htlcLock = BuilderFactory.htlcLock()
+ .recipientId("AJWRd23HNEhPLkK1ymMnwnDBX2a7QBZqff")
+ .amount("10000")
+ .fee("50000000")
+ .network(23)
+ .vendorField("HTLC")
+ .htlcLockAsset(htlcLockAsset)
+ .sign("dummy passphrase")
+ .getStruct();
- const serialized = TransactionFactory.fromData(htlcLock).serialized.toString("hex");
- const deserialized = Deserializer.deserialize(serialized);
+ const serialized = TransactionFactory.fromData(htlcLock).serialized.toString("hex");
+ const deserialized = Deserializer.deserialize(serialized);
- checkCommonFields(deserialized, htlcLock);
+ checkCommonFields(deserialized, htlcLock);
- expect(deserialized.data.asset).toEqual(htlcLock.asset);
- });
+ expect(deserialized.data.asset).toEqual(htlcLock.asset);
+ });
- it("should fail to verify", () => {
- const htlcLock = BuilderFactory.htlcLock()
- .recipientId("AJWRd23HNEhPLkK1ymMnwnDBX2a7QBZqff")
- .amount("10000")
- .fee("50000000")
- .network(23)
- .htlcLockAsset(htlcLockAsset)
- .sign("dummy passphrase")
- .build();
+ it("should fail to verify", () => {
+ const htlcLock = BuilderFactory.htlcLock()
+ .recipientId("AJWRd23HNEhPLkK1ymMnwnDBX2a7QBZqff")
+ .amount("10000")
+ .fee("50000000")
+ .network(23)
+ .htlcLockAsset(htlcLockAsset)
+ .sign("dummy passphrase")
+ .build();
- configManager.getMilestone().aip11 = false;
- expect(htlcLock.verify()).toBeFalse();
- configManager.getMilestone().aip11 = true;
- expect(htlcLock.verify()).toBeTrue();
+ configManager.getMilestone().aip11 = false;
+ expect(htlcLock.verify()).toBeFalse();
+ configManager.getMilestone().aip11 = true;
+ expect(htlcLock.verify()).toBeTrue();
+ });
});
- });
- describe("ser/deserialize - htlc claim", () => {
- const htlcClaimAsset = {
- lockTransactionId: "943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4",
- unlockSecret: htlcSecretHex,
- };
+ describe("ser/deserialize - htlc claim", () => {
+ const htlcClaimAsset = {
+ lockTransactionId: "943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4",
+ unlockSecret: htlcSecretHex,
+ };
- beforeAll(() => {
- // todo: completely wrap this into a function to hide the generation and setting of the config?
- configManager.setConfig(Generators.generateCryptoConfigRaw());
- });
+ beforeAll(() => {
+ // todo: completely wrap this into a function to hide the generation and setting of the config?
+ configManager.setConfig(Generators.generateCryptoConfigRaw());
+ });
- it("should ser/deserialize giving back original fields", () => {
- const htlcClaim = BuilderFactory.htlcClaim()
- .fee("0")
- .network(23)
- .htlcClaimAsset(htlcClaimAsset)
- .sign("dummy passphrase")
- .getStruct();
+ it("should ser/deserialize giving back original fields", () => {
+ const htlcClaim = BuilderFactory.htlcClaim()
+ .fee("0")
+ .network(23)
+ .htlcClaimAsset(htlcClaimAsset)
+ .sign("dummy passphrase")
+ .getStruct();
- const serialized = TransactionFactory.fromData(htlcClaim).serialized.toString("hex");
- const deserialized = Deserializer.deserialize(serialized);
+ const serialized = TransactionFactory.fromData(htlcClaim).serialized.toString("hex");
+ const deserialized = Deserializer.deserialize(serialized);
- checkCommonFields(deserialized, htlcClaim);
+ checkCommonFields(deserialized, htlcClaim);
- expect(deserialized.data.asset).toEqual(htlcClaim.asset);
- });
+ expect(deserialized.data.asset).toEqual(htlcClaim.asset);
+ });
- it("should fail to verify", () => {
- const htlcClaim = BuilderFactory.htlcClaim()
- .fee("0")
- .network(23)
- .htlcClaimAsset(htlcClaimAsset)
- .sign("dummy passphrase")
- .build();
+ it("should fail to verify", () => {
+ const htlcClaim = BuilderFactory.htlcClaim()
+ .fee("0")
+ .network(23)
+ .htlcClaimAsset(htlcClaimAsset)
+ .sign("dummy passphrase")
+ .build();
- configManager.getMilestone().aip11 = false;
- expect(htlcClaim.verify()).toBeFalse();
- configManager.getMilestone().aip11 = true;
- expect(htlcClaim.verify()).toBeTrue();
+ configManager.getMilestone().aip11 = false;
+ expect(htlcClaim.verify()).toBeFalse();
+ configManager.getMilestone().aip11 = true;
+ expect(htlcClaim.verify()).toBeTrue();
+ });
});
- });
- describe("ser/deserialize - htlc refund", () => {
- const htlcRefundAsset = {
- lockTransactionId: "943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4",
- };
+ describe("ser/deserialize - htlc refund", () => {
+ const htlcRefundAsset = {
+ lockTransactionId: "943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4",
+ };
- beforeAll(() => {
- // todo: completely wrap this into a function to hide the generation and setting of the config?
- configManager.setConfig(Generators.generateCryptoConfigRaw());
- });
+ beforeAll(() => {
+ // todo: completely wrap this into a function to hide the generation and setting of the config?
+ configManager.setConfig(Generators.generateCryptoConfigRaw());
+ });
- it("should ser/deserialize giving back original fields", () => {
- const htlcRefund = BuilderFactory.htlcRefund()
- .fee("0")
- .network(23)
- .htlcRefundAsset(htlcRefundAsset)
- .sign("dummy passphrase")
- .getStruct();
+ it("should ser/deserialize giving back original fields", () => {
+ const htlcRefund = BuilderFactory.htlcRefund()
+ .fee("0")
+ .network(23)
+ .htlcRefundAsset(htlcRefundAsset)
+ .sign("dummy passphrase")
+ .getStruct();
- const serialized = TransactionFactory.fromData(htlcRefund).serialized.toString("hex");
- const deserialized = Deserializer.deserialize(serialized);
+ const serialized = TransactionFactory.fromData(htlcRefund).serialized.toString("hex");
+ const deserialized = Deserializer.deserialize(serialized);
- checkCommonFields(deserialized, htlcRefund);
+ checkCommonFields(deserialized, htlcRefund);
- expect(deserialized.data.asset).toEqual(htlcRefund.asset);
- });
+ expect(deserialized.data.asset).toEqual(htlcRefund.asset);
+ });
- it("should fail to verify", () => {
- const htlcRefund = BuilderFactory.htlcRefund()
- .fee("0")
- .network(23)
- .htlcRefundAsset(htlcRefundAsset)
- .sign("dummy passphrase")
- .build();
+ it("should fail to verify", () => {
+ const htlcRefund = BuilderFactory.htlcRefund()
+ .fee("0")
+ .network(23)
+ .htlcRefundAsset(htlcRefundAsset)
+ .sign("dummy passphrase")
+ .build();
- configManager.getMilestone().aip11 = false;
- expect(htlcRefund.verify()).toBeFalse();
- configManager.getMilestone().aip11 = true;
- expect(htlcRefund.verify()).toBeTrue();
+ configManager.getMilestone().aip11 = false;
+ expect(htlcRefund.verify()).toBeFalse();
+ configManager.getMilestone().aip11 = true;
+ expect(htlcRefund.verify()).toBeTrue();
+ });
});
});
@@ -497,7 +538,7 @@ describe("Transaction serializer / deserializer", () => {
buffer.writeByte(transaction.network);
buffer.writeUint32(Enums.TransactionTypeGroup.Core);
buffer.writeUint16(transaction.type);
- buffer.writeUint64(transaction.nonce.toFixed());
+ buffer.writeUint64(transaction.nonce!.toFixed());
buffer.append(transaction.senderPublicKey, "hex");
buffer.writeUint64(Utils.BigNumber.make(transaction.fee).toFixed());
buffer.writeByte(0x00);
@@ -520,6 +561,10 @@ describe("Transaction serializer / deserializer", () => {
});
describe("deserialize Schnorr / ECDSA", () => {
+ beforeEach(() => {
+ configManager.getMilestone().aip11 = true;
+ });
+
const builderWith = (
hasher: (buffer: Buffer, keys: IKeyPair) => string,
hasher2?: (buffer: Buffer, keys: IKeyPair) => string,
@@ -557,7 +602,7 @@ describe("Transaction serializer / deserializer", () => {
let transaction: ITransaction;
expect(builder.data.version).toBe(2);
expect(() => (transaction = builder.build())).not.toThrow();
- expect(transaction.verify()).toBeTrue();
+ expect(transaction!.verify()).toBeTrue();
});
it("should deserialize a V2 transaction signed with ECDSA", () => {
@@ -567,7 +612,7 @@ describe("Transaction serializer / deserializer", () => {
expect(builder.data.version).toBe(2);
expect(builder.data.signature).not.toHaveLength(64);
expect(() => (transaction = builder.build())).not.toThrow();
- expect(transaction.verify()).toBeTrue();
+ expect(transaction!.verify()).toBeTrue();
});
it("should deserialize a V2 transaction when signed with Schnorr/Schnorr", () => {
@@ -577,9 +622,9 @@ describe("Transaction serializer / deserializer", () => {
expect(builder.data.version).toBe(2);
expect(() => (transaction = builder.build())).not.toThrow();
- expect(transaction.verify()).toBeTrue();
- expect(Verifier.verifySecondSignature(transaction.data, PublicKey.fromPassphrase("secret 2"))).toBeTrue();
- expect(Verifier.verifySecondSignature(transaction.data, PublicKey.fromPassphrase("secret 3"))).toBeFalse();
+ expect(transaction!.verify()).toBeTrue();
+ expect(Verifier.verifySecondSignature(transaction!.data, PublicKey.fromPassphrase("secret 2"))).toBeTrue();
+ expect(Verifier.verifySecondSignature(transaction!.data, PublicKey.fromPassphrase("secret 3"))).toBeFalse();
});
it("should throw when V2 transaction is signed with Schnorr and ECDSA", () => {
@@ -692,7 +737,7 @@ describe("Transaction serializer / deserializer", () => {
// signature: '618a54975212ead93df8c881655c625544bce8ed7ccdfe6f08a42eecfb1adebd051307be5014bb051617baf7815d50f62129e70918190361e5d4dd4796541b0a',
// secondSignature: '618a54975212ead93df8c881655c625544bce8ed7ccdfe6f08a42eecfb1adebd051307be5014bb051617baf7815d50f62129e70918190361e5d4dd4796541b0a'
// }
-
+ //
// bytes = crypto.getBytes(transaction)
// expect(bytes).toBeObject()
// expect(bytes.toString('hex') + transaction.signature + transaction.secondSignature).toHaveLength(420)
diff --git a/__tests__/unit/crypto/transactions/factory.test.ts b/__tests__/unit/crypto/transactions/factory.test.ts
index 6dbe39f2a7..41e455328e 100644
--- a/__tests__/unit/crypto/transactions/factory.test.ts
+++ b/__tests__/unit/crypto/transactions/factory.test.ts
@@ -1,20 +1,20 @@
import "jest-extended";
-import { Interfaces, Utils } from "@arkecosystem/crypto";
-
+import { Interfaces, Utils } from "@packages/crypto";
import {
InvalidTransactionBytesError,
TransactionSchemaError,
UnkownTransactionError,
-} from "../../../../packages/crypto/src/errors";
-import { ITransactionData } from "../../../../packages/crypto/src/interfaces";
-import { configManager } from "../../../../packages/crypto/src/managers";
+} from "@packages/crypto/src/errors";
+import { ITransactionData } from "@packages/crypto/src/interfaces";
+import { configManager } from "@packages/crypto/src/managers";
import {
Serializer,
Transaction,
TransactionFactory,
Utils as TransactionUtils,
-} from "../../../../packages/crypto/src/transactions";
+} from "@packages/crypto/src/transactions";
+
import { transaction as transactionFixture } from "../fixtures/transaction";
import { transaction as transactionDataFixture } from "../fixtures/transaction";
import { createRandomTx } from "./__support__";
@@ -36,7 +36,7 @@ beforeEach(() => {
};
});
-const transaction: Interfaces.ITransaction = TransactionFactory.fromData(transactionFixture);
+const transaction = TransactionFactory.fromData(transactionFixture);
const transactionJson: Interfaces.ITransactionJson = transaction.toJson();
const transactionSerialized: Buffer = Serializer.serialize(transaction);
diff --git a/__tests__/unit/crypto/transactions/serde.test.ts b/__tests__/unit/crypto/transactions/serde.test.ts
new file mode 100644
index 0000000000..303f3338fe
--- /dev/null
+++ b/__tests__/unit/crypto/transactions/serde.test.ts
@@ -0,0 +1,282 @@
+import { Address, PublicKey } from "@packages/crypto/src/identities";
+import { Keys } from "@packages/crypto/src/identities";
+import { ITransaction } from "@packages/crypto/src/interfaces";
+import { configManager } from "@packages/crypto/src/managers";
+import {
+ Deserializer,
+ InternalTransactionType,
+ Serializer,
+ Signer,
+ Transaction,
+ TransactionTypeFactory,
+} from "@packages/crypto/src/transactions";
+import { BigNumber, ByteBuffer } from "@packages/crypto/src/utils";
+
+configManager.getMilestone().aip11 = true;
+
+class TestTransaction extends Transaction {
+ public hasVendorField(): boolean {
+ return true;
+ }
+
+ public serialize(): ByteBuffer {
+ const { data } = this;
+ const buffer: ByteBuffer = new ByteBuffer(Buffer.alloc(33));
+ buffer.writeBigUInt64LE(data.amount.toBigInt());
+ buffer.writeUInt32LE(data.expiration || 0);
+
+ if (data.recipientId) {
+ const { addressBuffer } = Address.toBuffer(data.recipientId);
+
+ buffer.writeBuffer(addressBuffer);
+ }
+
+ return buffer;
+ }
+
+ public deserialize(buf: ByteBuffer): void {
+ const { data } = this;
+ data.amount = BigNumber.make(buf.readBigInt64LE().toString());
+ data.expiration = buf.readUInt32LE();
+ data.recipientId = Address.fromBuffer(buf.readBuffer(21));
+ }
+}
+
+type TransactionConstructorMap = Map;
+
+const registerTransactionTypes = (transaction: typeof Transaction) => {
+ const testInternalTransactionType = InternalTransactionType.from(1, 3);
+
+ const transactionConstructors: TransactionConstructorMap = new Map();
+ transactionConstructors.set(1, transaction);
+
+ const transactionTypes: Map = new Map();
+ transactionTypes.set(testInternalTransactionType, transactionConstructors);
+
+ TransactionTypeFactory.initialize(transactionTypes);
+};
+
+// @ts-ignore
+const checkCommonFields = (deserialized: ITransaction, expected) => {
+ // const fieldsToCheck = ["version", "network", "type", "senderPublicKey", "fee", "amount"];
+ const fieldsToCheck = ["version", "type", "senderPublicKey", "recipientId", "fee"];
+ if (deserialized.data.version === 1) {
+ fieldsToCheck.push("timestamp");
+ } else {
+ fieldsToCheck.push("typeGroup");
+ fieldsToCheck.push("nonce");
+ }
+
+ for (const field of fieldsToCheck) {
+ expect(deserialized.data[field].toString()).toEqual(expected[field].toString());
+ }
+};
+
+const checkV2Fields = (deserialized: ITransaction, expected) => {
+ const fieldsToCheck = ["version", "type", "senderPublicKey", "recipientId", "fee", "typeGroup", "nonce", "amount"];
+
+ for (const field of fieldsToCheck) {
+ expect(deserialized.data[field].toString()).toEqual(expected[field].toString());
+ }
+};
+
+describe("Transaction serializer / deserializer", () => {
+ describe("signatures", () => {
+ it("should ser/deser single sing", () => {
+ registerTransactionTypes(TestTransaction);
+
+ const transaction = new TestTransaction();
+ transaction.data = {
+ amount: new BigNumber(100),
+ fee: new BigNumber(200),
+ nonce: new BigNumber(1),
+ senderPublicKey: PublicKey.fromPassphrase("sender passphrase"),
+ recipientId: Address.fromPassphrase("recipient passphrase"),
+ timestamp: 0,
+ type: 1,
+ expiration: 0,
+ typeGroup: 3,
+ version: 2,
+ };
+
+ Signer.sign(transaction.data, Keys.fromPassphrase("sender passphrase"));
+
+ const serialized = Serializer.serialize(transaction);
+ expect(serialized.toString("hex")).toEqual(
+ "ff" + // Header
+ "02" + // Version
+ "1e" + // Network
+ "03000000" + // Typegroup
+ "0100" + // Type
+ "0100000000000000" + // Nonce
+ "0316a0b23be3408a4af227280bb005e9a136e0dbdd86860516850e5c29c1829113" + // senderPublicKey
+ "c800000000000000" + // Fee
+ "00" + // VendorField lenght
+ "6400000000000000" + // Amount
+ "00000000" + // Expiration
+ "1e66314f1ccb9741d6f1a6f9b26ed563c439fc1a94" + // RecipientId
+ "fa17823c3e591586626426b494c8038d0eb10a67bcf06f2e6959a76b40898124c13e68a6ad36fd131affdeac64fc8c2b85c371b194ffc6e3ba868070fe677df2", // Signature
+ );
+
+ const deserialized = Deserializer.deserialize(serialized);
+ checkV2Fields(deserialized, transaction.data);
+ });
+
+ it("should ser/deser second sign", () => {
+ registerTransactionTypes(TestTransaction);
+
+ const transaction = new TestTransaction();
+ transaction.data = {
+ amount: new BigNumber(100),
+ fee: new BigNumber(200),
+ nonce: new BigNumber(1),
+ senderPublicKey: PublicKey.fromPassphrase("sender passphrase"),
+ recipientId: Address.fromPassphrase("recipient passphrase"),
+ timestamp: 0,
+ type: 1,
+ expiration: 0,
+ typeGroup: 3,
+ version: 2,
+ };
+
+ Signer.sign(transaction.data, Keys.fromPassphrase("sender passphrase"));
+ Signer.secondSign(transaction.data, Keys.fromPassphrase("second passphrase"));
+
+ const serialized = Serializer.serialize(transaction);
+ expect(serialized.toString("hex")).toEqual(
+ "ff" + // Header
+ "02" + // Version
+ "1e" + // Network
+ "03000000" + // TypeGroup
+ "0100" + // Type
+ "0100000000000000" + // Nonce
+ "0316a0b23be3408a4af227280bb005e9a136e0dbdd86860516850e5c29c1829113" + // senderPublicKey
+ "c800000000000000" + // Fee
+ "00" + // VendorField Length
+ "6400000000000000" + // Amount
+ "00000000" + // Expiration
+ "1e66314f1ccb9741d6f1a6f9b26ed563c439fc1a94" + // RecipientId
+ "fa17823c3e591586626426b494c8038d0eb10a67bcf06f2e6959a76b40898124c13e68a6ad36fd131affdeac64fc8c2b85c371b194ffc6e3ba868070fe677df2" + // Signature
+ "11816c200a139458c257fbb14c2f07c4dbb2ebf42ab9b98fbb8a702729c0427192dec75c2933db09333e2f790df652ee3a27d1e1730b8f28e786821fd9cf7587", // Second Signature
+ );
+
+ const deserialized = Deserializer.deserialize(serialized);
+ checkV2Fields(deserialized, transaction.data);
+ });
+
+ it("should ser/deser multi sign", () => {
+ registerTransactionTypes(TestTransaction);
+
+ const transaction = new TestTransaction();
+ transaction.data = {
+ amount: new BigNumber(100),
+ fee: new BigNumber(200),
+ nonce: new BigNumber(1),
+ senderPublicKey: PublicKey.fromPassphrase("sender passphrase"),
+ recipientId: Address.fromPassphrase("recipient passphrase"),
+ timestamp: 0,
+ type: 1,
+ expiration: 0,
+ typeGroup: 3,
+ version: 2,
+ };
+
+ Signer.multiSign(transaction.data, Keys.fromPassphrase("passphrase 0"), 0);
+ Signer.multiSign(transaction.data, Keys.fromPassphrase("passphrase 1"), 1);
+ Signer.multiSign(transaction.data, Keys.fromPassphrase("passphrase 2"), 2);
+
+ const serialized = Serializer.serialize(transaction);
+ expect(serialized.toString("hex")).toEqual(
+ "ff" + // Header
+ "02" + // Version
+ "1e" + // Network
+ "03000000" + // TypeGroup
+ "0100" + // Type
+ "0100000000000000" + // Nonce
+ "0316a0b23be3408a4af227280bb005e9a136e0dbdd86860516850e5c29c1829113" + // SenderPublicKey
+ "c800000000000000" + // Fee
+ "00" + // VendorField lenght
+ "6400000000000000" + // Amount
+ "00000000" + // Expiration
+ "1e66314f1ccb9741d6f1a6f9b26ed563c439fc1a94" + // RecipientId
+ "00090bb29f6301ad77b1159b430af42fa228d21187025fc0fb347e0dc83a9eb0a21341c772f956d7349eb867072f6a09d44c828d38cb6bf52df0a06dc099bb13e2" + // 1st signature
+ "01f1241bbd5be038302c00fb6cdaf6c4da0a860fa82ef601510efaf0ca13ddd6f098db62e30d79d7190705c49366d091c136845416a295daad96a0d03149520a65" + // 2nd signature
+ "02bda0a17aa62c9c5ed904eae3915dd414e1faf67905c32c260f9c6ffbdaa9ace8b270051d9a81b243a15b4448ce97199562814cbc7c23a6375b411e9616090a67", // 3rd signature
+ );
+
+ const deserialized = Deserializer.deserialize(serialized);
+ checkV2Fields(deserialized, transaction.data);
+ });
+ });
+
+ describe("other", () => {
+ it("should ser/deser vendorField", () => {
+ registerTransactionTypes(TestTransaction);
+
+ const transaction = new TestTransaction();
+ transaction.data = {
+ amount: new BigNumber(100),
+ fee: new BigNumber(200),
+ nonce: new BigNumber(1),
+ senderPublicKey: PublicKey.fromPassphrase("sender passphrase"),
+ recipientId: Address.fromPassphrase("recipient passphrase"),
+ timestamp: 0,
+ vendorField: "vendorField",
+ type: 1,
+ expiration: 0,
+ typeGroup: 3,
+ version: 2,
+ };
+
+ Signer.sign(transaction.data, Keys.fromPassphrase("sender passphrase"));
+
+ const serialized = Serializer.serialize(transaction);
+ expect(serialized.toString("hex")).toEqual(
+ "ff" + // Header
+ "02" + // Version
+ "1e" + // Network
+ "03000000" + // Typegroup
+ "0100" + // Type
+ "0100000000000000" + // Nonce
+ "0316a0b23be3408a4af227280bb005e9a136e0dbdd86860516850e5c29c1829113" + // senderPublicKey
+ "c800000000000000" + // Fee
+ "0b" + // VendorField length
+ "76656e646f724669656c64" + // VendorField length
+ "6400000000000000" + // Amount
+ "00000000" + // Expiration
+ "1e66314f1ccb9741d6f1a6f9b26ed563c439fc1a94" + // RecipientId
+ "95a6303660af0a43d71d9d7434aababfd5fd9679c690164c3aea32ff3793ce00dc50868a41f379eee5470498e4c0a125f9dd5c6c12d1c5b1d7e708be6883ca31", // Signature
+ );
+
+ const deserialized = Deserializer.deserialize(serialized);
+ checkV2Fields(deserialized, transaction.data);
+ });
+
+ it("should ser/deser V1", () => {
+ configManager.getMilestone().aip11 = false;
+
+ registerTransactionTypes(TestTransaction);
+
+ const transaction = new TestTransaction();
+ transaction.data = {
+ amount: new BigNumber(100),
+ fee: new BigNumber(200),
+ nonce: new BigNumber(1),
+ senderPublicKey: PublicKey.fromPassphrase("sender passphrase"),
+ recipientId: Address.fromPassphrase("recipient passphrase"),
+ timestamp: 0,
+ type: 1,
+ expiration: 0,
+ typeGroup: 3,
+ version: 1,
+ };
+
+ Signer.sign(transaction.data, Keys.fromPassphrase("sender passphrase"));
+ const serialized = Serializer.serialize(transaction);
+
+ expect(serialized.toString("hex")).toEqual(
+ "ff011e01000000000316a0b23be3408a4af227280bb005e9a136e0dbdd86860516850e5c29c1829113c800000000000000006400000000000000000000001e66314f1ccb9741d6f1a6f9b26ed563c439fc1a9430450221008067d1beb623f2e7dbcb8580c0036a2d4a59c1a64fc3ba66f05fcea5a0614c6302203e94b41d50ca69498e4970d8a289c431474728e26652db8889bec4d8d0001144",
+ );
+ });
+ });
+});
diff --git a/__tests__/unit/crypto/utils/byte-buffer.test.ts b/__tests__/unit/crypto/utils/byte-buffer.test.ts
new file mode 100644
index 0000000000..bd04efc55b
--- /dev/null
+++ b/__tests__/unit/crypto/utils/byte-buffer.test.ts
@@ -0,0 +1,633 @@
+import { ByteBuffer } from "@packages/crypto/src/utils";
+import { Buffer } from "buffer";
+
+describe("ByteBuffer", () => {
+ describe("result", () => {
+ it("should return valid result & result length", () => {
+ const buffer = Buffer.alloc(2);
+ const byteBuffer = new ByteBuffer(buffer);
+
+ expect(byteBuffer.getResultLength()).toEqual(0);
+ expect(Buffer.alloc(0).compare(byteBuffer.getResult())).toEqual(0);
+
+ byteBuffer.writeInt8(1);
+
+ expect(byteBuffer.getResultLength()).toEqual(1);
+ const tmpBuffer1 = Buffer.alloc(1);
+ tmpBuffer1.writeInt8(1);
+ expect(tmpBuffer1.compare(byteBuffer.getResult())).toEqual(0);
+
+ byteBuffer.writeInt8(2);
+
+ expect(byteBuffer.getResultLength()).toEqual(2);
+ const tmpBuffer2 = Buffer.alloc(2);
+ tmpBuffer2.writeInt8(1);
+ tmpBuffer2.writeInt8(2, 1);
+ expect(tmpBuffer2.compare(byteBuffer.getResult())).toEqual(0);
+ });
+ });
+
+ describe("reminder", () => {
+ it("should return valid remainders and remainder length", () => {
+ const buffer = Buffer.alloc(2);
+ const byteBuffer = new ByteBuffer(buffer);
+
+ byteBuffer.writeInt8(1);
+ byteBuffer.writeInt8(2);
+ byteBuffer.reset();
+
+ expect(byteBuffer.getRemainderLength()).toEqual(2);
+ const tmpBuffer1 = Buffer.alloc(2);
+ tmpBuffer1.writeInt8(1);
+ tmpBuffer1.writeInt8(2, 1);
+ expect(tmpBuffer1.compare(byteBuffer.getRemainder())).toEqual(0);
+
+ byteBuffer.readInt8();
+
+ expect(byteBuffer.getRemainderLength()).toEqual(1);
+ const tmpBuffer2 = Buffer.alloc(1);
+ tmpBuffer2.writeInt8(2);
+ expect(tmpBuffer2.compare(byteBuffer.getRemainder())).toEqual(0);
+
+ byteBuffer.readInt8();
+
+ expect(byteBuffer.getRemainderLength()).toEqual(0);
+ expect(Buffer.alloc(0).compare(byteBuffer.getRemainder())).toEqual(0);
+ });
+ });
+
+ describe("jump", () => {
+ it("should jump", () => {
+ const buffer = Buffer.alloc(1);
+ const byteBuffer = new ByteBuffer(buffer);
+
+ expect(byteBuffer.getResultLength()).toEqual(0);
+
+ byteBuffer.jump(1);
+ expect(byteBuffer.getResultLength()).toEqual(1);
+
+ byteBuffer.jump(-1);
+ expect(byteBuffer.getResultLength()).toEqual(0);
+ });
+
+ it("should throw error when jumping outside boundary", () => {
+ const buffer = Buffer.alloc(1);
+ const byteBuffer = new ByteBuffer(buffer);
+
+ expect(() => {
+ byteBuffer.jump(2);
+ }).toThrowError("Jump over buffer boundary.");
+
+ expect(() => {
+ byteBuffer.jump(-1);
+ }).toThrowError("Jump over buffer boundary.");
+ });
+ });
+
+ describe("Int8", () => {
+ const bufferSize = 1;
+ const min = -128;
+ const max = 127;
+ const validValues = [min, max];
+ const invalidValues = [min - 1, max + 1];
+
+ it.each(validValues)("should write and read value: %s", (value: number) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ byteBuffer.writeInt8(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+
+ byteBuffer.reset();
+ expect(byteBuffer.readInt8()).toEqual(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+ });
+
+ it.each(invalidValues)("should fail writing value: %s", (value: number) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ expect(() => {
+ byteBuffer.writeInt8(value);
+ }).toThrowError(
+ new RangeError(
+ `The value of "value" is out of range. It must be >= ${min} and <= ${max}. Received ${value}`,
+ ),
+ );
+ expect(byteBuffer.getResultLength()).toEqual(0);
+ });
+ });
+
+ describe("UInt8", () => {
+ const bufferSize = 1;
+ const min = 0;
+ const max = 255;
+ const validValues = [min, max];
+ const invalidValues = [min - 1, max + 1];
+
+ it.each(validValues)("should write and read value: %s", (value: number) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ byteBuffer.writeUInt8(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+
+ byteBuffer.reset();
+ expect(byteBuffer.readUInt8()).toEqual(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+ });
+
+ it.each(invalidValues)("should fail writing value: %s", (value: number) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ expect(() => {
+ byteBuffer.writeUInt8(value);
+ }).toThrowError(
+ new RangeError(
+ `The value of "value" is out of range. It must be >= ${min} and <= ${max}. Received ${value}`,
+ ),
+ );
+ expect(byteBuffer.getResultLength()).toEqual(0);
+ });
+ });
+
+ describe("Int16BE", () => {
+ const bufferSize = 2;
+ const min = -32768;
+ const max = 32767;
+ const validValues = [min, max];
+ const invalidValues = [min - 1, max + 1];
+
+ it.each(validValues)("should write and read value: %s", (value: number) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ byteBuffer.writeInt16BE(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+
+ byteBuffer.reset();
+ expect(byteBuffer.readInt16BE()).toEqual(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+ });
+
+ it.each(invalidValues)("should fail writing value: %s", (value: number) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ expect(() => {
+ byteBuffer.writeInt16BE(value);
+ }).toThrowError(
+ new RangeError(
+ `The value of "value" is out of range. It must be >= ${min} and <= ${max}. Received ${value}`,
+ ),
+ );
+ expect(byteBuffer.getResultLength()).toEqual(0);
+ });
+ });
+
+ describe("UInt16BE", () => {
+ const bufferSize = 2;
+ const min = 0;
+ const max = 65535;
+ const validValues = [min, max];
+ const invalidValues = [min - 1, max + 1];
+
+ it.each(validValues)("should write and read value: %s", (value: number) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ byteBuffer.writeUInt16BE(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+
+ byteBuffer.reset();
+ expect(byteBuffer.readUInt16BE()).toEqual(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+ });
+
+ it.each(invalidValues)("should fail writing value: %s", (value: number) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ expect(() => {
+ byteBuffer.writeUInt16BE(value);
+ }).toThrowError(
+ new RangeError(
+ `The value of "value" is out of range. It must be >= ${min} and <= ${max}. Received ${value}`,
+ ),
+ );
+ expect(byteBuffer.getResultLength()).toEqual(0);
+ });
+ });
+
+ describe("Int16LE", () => {
+ const bufferSize = 2;
+ const min = -32768;
+ const max = 32767;
+ const validValues = [min, max];
+ const invalidValues = [min - 1, max + 1];
+
+ it.each(validValues)("should write and read value: %s", (value: number) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ byteBuffer.writeInt16LE(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+
+ byteBuffer.reset();
+ expect(byteBuffer.readInt16LE()).toEqual(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+ });
+
+ it.each(invalidValues)("should fail writing value: %s", (value: number) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ expect(() => {
+ byteBuffer.writeInt16LE(value);
+ }).toThrowError(
+ new RangeError(
+ `The value of "value" is out of range. It must be >= ${min} and <= ${max}. Received ${value}`,
+ ),
+ );
+ expect(byteBuffer.getResultLength()).toEqual(0);
+ });
+ });
+
+ describe("UInt16LE", () => {
+ const bufferSize = 2;
+ const min = 0;
+ const max = 65535;
+ const validValues = [min, max];
+ const invalidValues = [min - 1, max + 1];
+
+ it.each(validValues)("should write and read value: %s", (value: number) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ byteBuffer.writeUInt16LE(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+
+ byteBuffer.reset();
+ expect(byteBuffer.readUInt16LE()).toEqual(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+ });
+
+ it.each(invalidValues)("should fail writing value: %s", (value: number) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ expect(() => {
+ byteBuffer.writeUInt16LE(value);
+ }).toThrowError(
+ new RangeError(
+ `The value of "value" is out of range. It must be >= ${min} and <= ${max}. Received ${value}`,
+ ),
+ );
+ expect(byteBuffer.getResultLength()).toEqual(0);
+ });
+ });
+
+ describe("Int32BE", () => {
+ const bufferSize = 4;
+ const min = -2147483648;
+ const max = 2147483647;
+ const validValues = [min, max];
+ const invalidValues = [min - 1, max + 1];
+
+ it.each(validValues)("should write and read value: %s", (value: number) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ byteBuffer.writeInt32BE(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+
+ byteBuffer.reset();
+ expect(byteBuffer.readInt32BE()).toEqual(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+ });
+
+ it.each(invalidValues)("should fail writing value: %s", (value: number) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ expect(() => {
+ byteBuffer.writeInt32BE(value);
+ }).toThrowError(
+ new RangeError(
+ `The value of "value" is out of range. It must be >= ${min} and <= ${max}. Received ${value}`,
+ ),
+ );
+ expect(byteBuffer.getResultLength()).toEqual(0);
+ });
+ });
+
+ describe("UInt32BE", () => {
+ const bufferSize = 4;
+ const min = 0;
+ const max = 4294967295;
+ const validValues = [min, max];
+ const invalidValues = [min - 1, max + 1];
+
+ it.each(validValues)("should write and read value: %s", (value: number) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ byteBuffer.writeUInt32BE(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+
+ byteBuffer.reset();
+ expect(byteBuffer.readUInt32BE()).toEqual(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+ });
+
+ it.each(invalidValues)("should fail writing value: %s", (value: number) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ expect(() => {
+ byteBuffer.writeUInt32BE(value);
+ }).toThrowError(
+ new RangeError(
+ `The value of "value" is out of range. It must be >= ${min} and <= ${max}. Received ${value}`,
+ ),
+ );
+ expect(byteBuffer.getResultLength()).toEqual(0);
+ });
+ });
+
+ describe("Int32LE", () => {
+ const bufferSize = 4;
+ const min = -2147483648;
+ const max = 2147483647;
+ const validValues = [min, max];
+ const invalidValues = [min - 1, max + 1];
+
+ it.each(validValues)("should write and read value: %s", (value: number) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ byteBuffer.writeInt32LE(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+
+ byteBuffer.reset();
+ expect(byteBuffer.readInt32LE()).toEqual(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+ });
+
+ it.each(invalidValues)("should fail writing value: %s", (value: number) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ expect(() => {
+ byteBuffer.writeInt32LE(value);
+ }).toThrowError(
+ new RangeError(
+ `The value of "value" is out of range. It must be >= ${min} and <= ${max}. Received ${value}`,
+ ),
+ );
+ expect(byteBuffer.getResultLength()).toEqual(0);
+ });
+ });
+
+ describe("UInt32LE", () => {
+ const bufferSize = 4;
+ const min = 0;
+ const max = 4294967295;
+ const validValues = [min, max];
+ const invalidValues = [min - 1, max + 1];
+
+ it.each(validValues)("should write and read value: %s", (value: number) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ byteBuffer.writeUInt32LE(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+
+ byteBuffer.reset();
+ expect(byteBuffer.readUInt32LE()).toEqual(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+ });
+
+ it.each(invalidValues)("should fail writing value: %s", (value: number) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ expect(() => {
+ byteBuffer.writeUInt32LE(value);
+ }).toThrowError(
+ new RangeError(
+ `The value of "value" is out of range. It must be >= ${min} and <= ${max}. Received ${value}`,
+ ),
+ );
+ expect(byteBuffer.getResultLength()).toEqual(0);
+ });
+ });
+
+ describe("BigInt64BE", () => {
+ const bufferSize = 8;
+ const min = -9223372036854775808n;
+ const max = 9223372036854775807n;
+ const validValues = [min, max];
+ const invalidValues = [min - 1n, max + 1n];
+
+ it.each(validValues)("should write and read value: %s", (value: bigint) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ byteBuffer.writeBigInt64BE(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+
+ byteBuffer.reset();
+ expect(byteBuffer.readBigInt64BE()).toEqual(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+ });
+
+ it.each(invalidValues)("should fail writing value: %s", (value: bigint) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ expect(() => {
+ byteBuffer.writeBigInt64BE(value);
+ }).toThrowError(
+ new RangeError(
+ `The value of "value" is out of range. It must be >= -(2n ** 63n) and < 2n ** 63n. Received ${value
+ .toLocaleString()
+ .replace(new RegExp(",", "g"), "_")}n`,
+ ),
+ );
+ expect(byteBuffer.getResultLength()).toEqual(0);
+ });
+ });
+
+ describe("BigUInt64BE", () => {
+ const bufferSize = 8;
+ const min = 0n;
+ const max = 18_446_744_073_709_551_615n;
+ const validValues = [min, max];
+ const invalidValues = [min - 1n, max + 1n];
+
+ it.each(validValues)("should write and read value: %s", (value: bigint) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ byteBuffer.writeBigUInt64BE(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+
+ byteBuffer.reset();
+ expect(byteBuffer.readBigUInt64BE()).toEqual(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+ });
+
+ it.each(invalidValues)("should fail writing value: %s", (value: bigint) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ expect(() => {
+ byteBuffer.writeBigUInt64BE(value);
+ }).toThrowError(
+ new RangeError(
+ `The value of "value" is out of range. It must be >= 0n and < 2n ** 64n. Received ${value
+ .toLocaleString()
+ .replace(new RegExp(",", "g"), "_")}n`,
+ ),
+ );
+ expect(byteBuffer.getResultLength()).toEqual(0);
+ });
+ });
+
+ describe("BigInt64LE", () => {
+ const bufferSize = 8;
+ const min = -9223372036854775808n;
+ const max = 9223372036854775807n;
+ const validValues = [min, max];
+ const invalidValues = [min - 1n, max + 1n];
+
+ it.each(validValues)("should write and read value: %s", (value: bigint) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ byteBuffer.writeBigInt64LE(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+
+ byteBuffer.reset();
+ expect(byteBuffer.readBigInt64LE()).toEqual(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+ });
+
+ it.each(invalidValues)("should fail writing value: %s", (value: bigint) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ expect(() => {
+ byteBuffer.writeBigInt64BE(value);
+ }).toThrowError(
+ new RangeError(
+ `The value of "value" is out of range. It must be >= -(2n ** 63n) and < 2n ** 63n. Received ${value
+ .toLocaleString()
+ .replace(new RegExp(",", "g"), "_")}n`,
+ ),
+ );
+ expect(byteBuffer.getResultLength()).toEqual(0);
+ });
+ });
+
+ describe("BigUInt64LE", () => {
+ const bufferSize = 8;
+ const min = 0n;
+ const max = 18_446_744_073_709_551_615n;
+ const validValues = [min, max];
+ const invalidValues = [min - 1n, max + 1n];
+
+ it.each(validValues)("should write and read value: %s", (value: bigint) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ byteBuffer.writeBigUInt64LE(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+
+ byteBuffer.reset();
+ expect(byteBuffer.readBigUInt64LE()).toEqual(value);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+ });
+
+ it.each(invalidValues)("should fail writing value: %s", (value: bigint) => {
+ const buffer = Buffer.alloc(bufferSize);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ expect(() => {
+ byteBuffer.writeBigUInt64LE(value);
+ }).toThrowError(
+ new RangeError(
+ `The value of "value" is out of range. It must be >= 0n and < 2n ** 64n. Received ${value
+ .toLocaleString()
+ .replace(new RegExp(",", "g"), "_")}n`,
+ ),
+ );
+ expect(byteBuffer.getResultLength()).toEqual(0);
+ });
+ });
+
+ describe("buffer", () => {
+ it("should write and read value", () => {
+ const bufferSize = 5;
+ const buffer = Buffer.alloc(bufferSize);
+ const bufferToCompare = Buffer.alloc(bufferSize).fill(1);
+
+ const byteBuffer = new ByteBuffer(buffer);
+
+ byteBuffer.writeBuffer(bufferToCompare);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+
+ byteBuffer.reset();
+ expect(bufferToCompare.compare(byteBuffer.readBuffer(bufferSize))).toEqual(0);
+ expect(byteBuffer.getResultLength()).toEqual(bufferSize);
+ });
+
+ it("should throw when writing over boundary", () => {
+ const buffer = Buffer.alloc(5);
+ const byteBuffer = new ByteBuffer(buffer);
+
+ expect(() => {
+ byteBuffer.writeBuffer(Buffer.alloc(6));
+ }).toThrowError("Write over buffer boundary.");
+ });
+
+ it("should throw reading writing over boundary", () => {
+ const buffer = Buffer.alloc(5);
+ const byteBuffer = new ByteBuffer(buffer);
+
+ expect(() => {
+ byteBuffer.readBuffer(6);
+ }).toThrowError("Read over buffer boundary.");
+ });
+ });
+});
diff --git a/benchmark/crypto/hash-algorithms.js b/benchmark/crypto/hash-algorithms.js
index 4b644135b6..9109bf246e 100644
--- a/benchmark/crypto/hash-algorithms.js
+++ b/benchmark/crypto/hash-algorithms.js
@@ -1,50 +1,59 @@
-const {
- Crypto,
- Transactions
-} = require('@arkecosystem/crypto')
+const { Crypto, Transactions, Utils } = require("@arkecosystem/crypto");
const createHash = require("create-hash");
-const nodeSha256 = (bytes) => createHash("sha256").update(bytes).digest()
+const nodeSha256 = (bytes) => createHash("sha256").update(bytes).digest();
-const data = require('../helpers').getJSONFixture('transaction/deserialized/0');
+const prepareData = (data) => {
+ const bigNumbers = ["fee", "amount", "nonce"];
+
+ for (const [key, value] of Object.entries(data)) {
+ if (bigNumbers.includes(key)) {
+ data[key] = new Utils.BigNumber(value);
+ }
+ }
+
+ return data;
+};
+
+const data = prepareData(require("../helpers").getJSONFixture("transaction/deserialized/0"));
const transactionBytes = Transactions.Utils.toBytes(data);
-exports['bcrypto.sha256'] = () => {
+exports["bcrypto.sha256"] = () => {
Crypto.HashAlgorithms.sha256(transactionBytes);
};
-exports['node.sha256'] = () => {
+exports["node.sha256"] = () => {
nodeSha256(transactionBytes);
};
-exports['bcrypto.sha1'] = () => {
+exports["bcrypto.sha1"] = () => {
Crypto.HashAlgorithms.sha1(transactionBytes);
};
-exports['node.sha1'] = () => {
+exports["node.sha1"] = () => {
createHash("sha1").update(transactionBytes).digest();
};
-exports['bcrypto.ripemd160'] = () => {
+exports["bcrypto.ripemd160"] = () => {
Crypto.HashAlgorithms.ripemd160(transactionBytes);
};
-exports['node.ripemd160'] = () => {
+exports["node.ripemd160"] = () => {
createHash("ripemd160").update(transactionBytes).digest();
};
-exports['bcrypto.hash160'] = () => {
+exports["bcrypto.hash160"] = () => {
Crypto.HashAlgorithms.hash160(transactionBytes);
};
-exports['node.hash160'] = () => {
+exports["node.hash160"] = () => {
createHash("ripemd160").update(nodeSha256(transactionBytes)).digest();
};
-exports['bcrypto.hash256'] = () => {
+exports["bcrypto.hash256"] = () => {
Crypto.HashAlgorithms.hash256(transactionBytes);
};
-exports['node.hash256'] = () => {
+exports["node.hash256"] = () => {
nodeSha256(nodeSha256(transactionBytes));
};
diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile
index 59f7639dc5..e19b560953 100644
--- a/docker/production/Dockerfile
+++ b/docker/production/Dockerfile
@@ -1,4 +1,4 @@
-FROM node:14-alpine
+FROM node:18-alpine
WORKDIR /home/node/core
@@ -6,10 +6,11 @@ ADD docker/production/entrypoint.sh /entrypoint.sh
ARG core_channel=latest
-RUN apk add --no-cache --virtual .build-deps make gcc g++ python git \
+RUN apk add --no-cache --virtual .build-deps make gcc g++ python3 git \
&& apk add --no-cache bash sudo git openntpd openssl \
&& echo "servers pool.ntp.org" > /etc/ntpd.conf \
&& echo "servers time.google.com" >> /etc/ntpd.conf \
+ && su node -c "yarn global add node-gyp" \
&& su node -c "yarn global add @arkecosystem/core@${core_channel}" \
&& su node -c "yarn cache clean" \
&& apk del .build-deps \
diff --git a/docker/production/README.md b/docker/production/README.md
index 31a616d1c8..b0cebe64ef 100644
--- a/docker/production/README.md
+++ b/docker/production/README.md
@@ -23,7 +23,7 @@ Run Relay only node using [Docker Compose](https://docs.docker.com/compose/)
**_DevNet_**
-> Create file `docker-compose.yml` with the following content:
+> Create file `docker compose.yml` with the following content:
```bash
version: '2'
@@ -97,7 +97,7 @@ CORE_WEBHOOKS_PORT=4004
**_MainNet_**
-> Create file `docker-compose.yml` with the following content:
+> Create file `docker compose.yml` with the following content:
```bash
version: '2'
@@ -174,7 +174,7 @@ _If you prefer to use custom DB Name, DB User and DB Password simply adjust vari
> _Time to start the relay node_:
```bash
-docker-compose up -d
+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://guides.ark.dev/devops-guides/how-to-setup-a-node-with-docker#production-setup)._
diff --git a/docker/production/entrypoint.sh b/docker/production/entrypoint.sh
index 25870fd18e..74b4972fd3 100755
--- a/docker/production/entrypoint.sh
+++ b/docker/production/entrypoint.sh
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
-sudo /usr/sbin/ntpd -s
+sudo /usr/sbin/ntpd
sudo rm -rf /home/node/.config/ark-core/*
sudo rm -rf /home/node/.local/state/ark-core/*
diff --git a/install-next.sh b/install-next.sh
index d9871470e0..876b1a8896 100644
--- a/install-next.sh
+++ b/install-next.sh
@@ -110,13 +110,13 @@ 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_14.x ${DEB_ID} main" | sudo tee /etc/apt/sources.list.d/nodesource.list)
+ (echo "deb https://deb.nodesource.com/node_16.x ${DEB_ID} 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_14.x | sudo -E bash - > /dev/null 2>&1
+ curl -sL https://rpm.nodesource.com/setup_16.x | sudo -E bash - > /dev/null 2>&1
fi
success "Installed node.js & npm!"
diff --git a/install.sh b/install.sh
index b1aab5b085..4c27e18979 100644
--- a/install.sh
+++ b/install.sh
@@ -48,7 +48,11 @@ RPM=$(which yum || :)
SYS=$([[ -L "/sbin/init" ]] && echo 'SystemD' || echo 'SystemV')
# Detect Debian/Ubuntu derivative
-DEB_ID=$( (grep DISTRIB_CODENAME /etc/upstream-release/lsb-release || grep DISTRIB_CODENAME /etc/lsb-release) 2>/dev/null | cut -d'=' -f2 )
+DEB_ID=$( (grep DISTRIB_CODENAME /etc/upstream-release/lsb-release || grep DISTRIB_CODENAME /etc/lsb-release || grep VERSION_CODENAME /etc/os-release) 2>/dev/null | cut -d'=' -f2 )
+
+#APT Vars
+APT_ENV="DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a"
+
if [[ ! -z $DEB ]]; then
success "Running install for Debian derivative"
@@ -95,7 +99,7 @@ heading "Installing system dependencies..."
if [[ ! -z $DEB ]]; then
sudo apt-get update
- sudo apt-get install -y git curl apt-transport-https update-notifier
+ sudo $APT_ENV apt-get install git curl apt-transport-https bc wget gnupg -yq
elif [[ ! -z $RPM ]]; then
sudo yum update -y
sudo yum install git curl epel-release --skip-broken -y
@@ -109,14 +113,15 @@ sudo rm -rf /usr/local/{lib/node{,/.npm,_modules},bin,share/man}/{npm*,node*,man
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_14.x ${DEB_ID} main" | sudo tee /etc/apt/sources.list.d/nodesource.list)
+ (echo -e "Package: nodejs\nPin: origin deb.nodesource.com\nPin-Priority: 999" | sudo tee /etc/apt/preferences.d/nodesource)
+ curl -sL https://deb.nodesource.com/gpgkey/nodesource.gpg.key | gpg --dearmor | sudo tee /usr/share/keyrings/nodesource.gpg >/dev/null
+ (echo "deb [signed-by=/usr/share/keyrings/nodesource.gpg] https://deb.nodesource.com/node_16.x ${DEB_ID} main" | sudo tee /etc/apt/sources.list.d/nodesource.list)
sudo apt-get update
- sudo apt-get install nodejs -y
+ sudo $APT_ENV apt-get install nodejs -yq
elif [[ ! -z $RPM ]]; then
sudo yum install gcc-c++ make -y
- curl -sL https://rpm.nodesource.com/setup_14.x | sudo -E bash - > /dev/null 2>&1
+ curl -sL https://rpm.nodesource.com/setup_16.x | sudo -E bash - > /dev/null 2>&1
fi
success "Installed node.js & npm!"
@@ -124,11 +129,10 @@ 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)
-
+ curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | sudo tee /usr/share/keyrings/yarnkey.gpg >/dev/null
+ (echo "deb [signed-by=/usr/share/keyrings/yarnkey.gpg] 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
+ sudo $APT_ENV apt-get install yarn -yq
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
@@ -146,22 +150,13 @@ 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 libjemalloc-dev -y
-elif [[ ! -z $RPM ]]; then
- sudo yum groupinstall "Development Tools" -y -q
- sudo yum install postgresql-devel jq jemalloc-devel -y -q
-fi
-
-success "Installed program dependencies!"
-
heading "Installing PostgreSQL..."
if [[ ! -z $DEB ]]; then
+ curl -sL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | sudo tee /usr/share/keyrings/pgdg.gpg >/dev/null
+ (echo "deb [signed-by=/usr/share/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt ${DEB_ID}-pgdg main" | sudo tee /etc/apt/sources.list.d/pgdg.list )
sudo apt-get update
- sudo apt-get install postgresql postgresql-contrib -y
+ sudo $APT_ENV apt-get install postgresql -yq
elif [[ ! -z $RPM ]]; then
sudo yum install postgresql-server postgresql-contrib -y
@@ -177,12 +172,24 @@ fi
success "Installed PostgreSQL!"
+heading "Installing program dependencies..."
+
+if [[ ! -z $DEB ]]; then
+ sudo $APT_ENV apt-get install build-essential pkg-config libtool autoconf automake libpq-dev jq libjemalloc-dev -yq
+elif [[ ! -z $RPM ]]; then
+ sudo yum groupinstall "Development Tools" -y -q
+ sudo yum install postgresql-devel jq jemalloc-devel -y -q
+fi
+
+success "Installed program dependencies!"
+
+
heading "Installing NTP..."
sudo timedatectl set-ntp off > /dev/null 2>&1 || true # disable the default systemd timesyncd service
if [[ ! -z $DEB ]]; then
- sudo apt-get install ntp -yyq
+ sudo $APT_ENV apt-get install ntp -yq
if [ -z "$(sudo service ntp status |grep running)" ] ; then
sudo ntpd -gq
fi
@@ -201,9 +208,9 @@ 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_ENV apt-get upgrade -yq
+ sudo $APT_ENV apt-get dist-upgrade -yq
+ sudo apt-get autoremove -yq
sudo apt-get autoclean -yq
elif [[ ! -z $RPM ]]; then
sudo yum update
diff --git a/lerna.json b/lerna.json
index 8d202c4aed..f296a1f53d 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,10 +1,9 @@
{
- "lerna": "3.5.0",
- "npmClient": "yarn",
- "packages": [
- "packages/*",
- "plugins/*"
- ],
- "useWorkspaces": true,
- "version": "3.0.6"
+ "npmClient": "yarn",
+ "packages": [
+ "packages/*",
+ "plugins/*"
+ ],
+ "version": "3.12.0",
+ "$schema": "node_modules/lerna/schemas/lerna-schema.json"
}
diff --git a/package.json b/package.json
index c7490b8d86..1e4b433a31 100644
--- a/package.json
+++ b/package.json
@@ -1,109 +1,98 @@
{
- "private": true,
- "name": "core",
- "description": "The packages that make up the ARK Core",
- "scripts": {
- "lerna": "node ./node_modules/lerna/cli.js",
- "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",
- "lint": "./node_modules/eslint/bin/eslint.js packages --ext .ts --fix",
- "lint:tests": "./node_modules/eslint/bin/eslint.js __tests__ --ext .ts --fix",
- "prettier": "prettier --write \"./*.{ts,js,json,md}\" \"./packages/**/*.{ts,js,json,md}\" \"./__tests__/**/*.{ts,js,json,md}\"",
- "format": "yarn lint && yarn prettier",
- "test": "cross-env CORE_ENV=test jest --runInBand --forceExit",
- "test:parallel": "cross-env CORE_ENV=test jest --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 jest __tests__/unit/** --forceExit",
- "test:unit:coverage": "cross-env jest __tests__/unit --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$'",
- "test:unit:debug": "cross-env node --inspect-brk ./node_modules/.bin/jest __tests__/unit",
- "test:unit:watch": "cross-env 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",
- "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",
- "deps": "cross-env-shell ./scripts/deps/update.sh",
- "deps:missing": "node ./scripts/deps/missing.js",
- "deps:unused": "node ./scripts/deps/unused.js",
- "deps:types": "./node_modules/typesync/bin/typesync",
- "madge:circular": "node ./scripts/circular.js",
- "madge:graph": "./node_modules/madge/bin/cli.js --image circular-graph.svg --extensions ts ./packages/**/src",
- "docker": "node ./scripts/docker/generate-docker.js",
- "bench": "node benchmark/index.js"
- },
- "devDependencies": {
- "@babel/core": "7.15.5",
- "@babel/preset-env": "7.15.4",
- "@commitlint/cli": "11.0.0",
- "@commitlint/config-conventional": "11.0.0",
- "@faustbrian/benchmarker": "0.1.2",
- "@oclif/dev-cli": "1.26.0",
- "@types/babel__core": "7.1.16",
- "@types/create-hash": "1.2.2",
- "@types/depcheck": "0.9.1",
- "@types/jest": "26.0.24",
- "@types/js-yaml": "3.12.7",
- "@types/node": "13.9.5",
- "@types/prettier": "2.3.2",
- "@types/rimraf": "3.0.2",
- "@types/uuid": "8.3.1",
- "@typescript-eslint/eslint-plugin": "3.10.1",
- "@typescript-eslint/parser": "3.10.1",
- "babel-loader": "8.2.2",
- "capture-console": "1.0.1",
- "cpy-cli": "3.1.1",
- "create-hash": "1.2.0",
- "cross-env": "7.0.3",
- "del-cli": "3.0.1",
- "depcheck": "1.4.2",
- "eslint": "7.32.0",
- "eslint-config-prettier": "6.15.0",
- "eslint-plugin-jest": "24.4.0",
- "eslint-plugin-prettier": "3.4.1",
- "eslint-plugin-simple-import-sort": "5.0.3",
- "get-port": "5.1.1",
- "husky": "4.3.8",
- "jest": "26.6.3",
- "jest-extended": "0.11.5",
- "js-yaml": "3.14.1",
- "lerna": "3.22.1",
- "lint-staged": "10.5.4",
- "madge": "4.0.1",
- "moment": "2.29.1",
- "moment-timezone": "0.5.33",
- "nock": "13.1.3",
- "npm-check-updates": "9.2.4",
- "prettier": "2.4.0",
- "rimraf": "3.0.2",
- "sinon": "9.2.4",
- "tmp": "0.2.1",
- "ts-jest": "26.5.6",
- "typedoc": "0.21.9",
- "typescript": "3.8.3",
- "typesync": "0.8.0",
- "uuid": "8.3.2"
- },
- "workspaces": [
- "packages/*",
- "plugins/*"
- ]
+ "private": true,
+ "name": "core",
+ "description": "The packages that make up the ARK Core",
+ "scripts": {
+ "setup": "yarn && yarn build",
+ "setup:clean": "yarn && yarn clean && yarn build",
+ "clean": "yarn lerna clean --yes",
+ "build": "yarn lerna run build",
+ "lint": "./node_modules/eslint/bin/eslint.js packages --ext .ts --fix",
+ "lint:tests": "./node_modules/eslint/bin/eslint.js __tests__ --ext .ts --fix",
+ "prettier": "prettier --write \"./*.{ts,js,json,md}\" \"./packages/**/*.{ts,js,json,md}\" \"./__tests__/**/*.{ts,js,json,md}\"",
+ "format": "yarn lint && yarn prettier",
+ "test": "cross-env CORE_ENV=test jest --runInBand --forceExit",
+ "test:parallel": "cross-env CORE_ENV=test jest --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 jest __tests__/unit/** --forceExit",
+ "test:unit:coverage": "cross-env jest __tests__/unit --coverage --coveragePathIgnorePatterns='/(defaults.ts|index.ts)$'",
+ "test:unit:debug": "cross-env node --inspect-brk ./node_modules/.bin/jest __tests__/unit",
+ "test:unit:watch": "cross-env 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",
+ "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",
+ "deps": "cross-env-shell ./scripts/deps/update.sh",
+ "deps:missing": "node ./scripts/deps/missing.js",
+ "deps:unused": "node ./scripts/deps/unused.js",
+ "deps:types": "./node_modules/typesync/bin/typesync",
+ "madge:circular": "node ./scripts/circular.js",
+ "madge:graph": "./node_modules/madge/bin/cli.js --image circular-graph.svg --extensions ts ./packages/**/src",
+ "docker": "node ./scripts/docker/generate-docker.js",
+ "bench": "node benchmark/index.js"
+ },
+ "devDependencies": {
+ "@babel/core": "7.15.5",
+ "@babel/preset-env": "7.15.4",
+ "@commitlint/cli": "11.0.0",
+ "@commitlint/config-conventional": "11.0.0",
+ "@faustbrian/benchmarker": "0.1.2",
+ "@oclif/dev-cli": "1.26.0",
+ "@types/babel__core": "7.1.16",
+ "@types/create-hash": "1.2.2",
+ "@types/depcheck": "0.9.1",
+ "@types/jest": "26.0.24",
+ "@types/node": "13.9.5",
+ "@types/prettier": "2.3.2",
+ "@types/rimraf": "3.0.2",
+ "@typescript-eslint/eslint-plugin": "3.10.1",
+ "@typescript-eslint/parser": "3.10.1",
+ "babel-loader": "8.2.2",
+ "capture-console": "1.0.2",
+ "create-hash": "1.2.0",
+ "cross-env": "7.0.3",
+ "depcheck": "1.4.7",
+ "eslint-config-prettier": "6.15.0",
+ "eslint-plugin-jest": "24.4.0",
+ "eslint-plugin-prettier": "3.4.1",
+ "eslint-plugin-simple-import-sort": "5.0.3",
+ "eslint": "7.32.0",
+ "fs-extra": "8.1.0",
+ "jest-extended": "0.11.5",
+ "jest": "26.6.3",
+ "lerna": "7.4.2",
+ "madge": "6.1.0",
+ "moment-timezone": "0.5.44",
+ "nock": "13.5.1",
+ "npm-check-updates": "16.14.14",
+ "prettier": "2.4.0",
+ "rimraf": "5.0.5",
+ "sinon": "17.0.1",
+ "tmp": "0.2.1",
+ "ts-jest": "26.5.6",
+ "typedoc": "0.22.17",
+ "typescript": "4.1.6",
+ "typesync": "0.8.0"
+ },
+ "workspaces": [
+ "packages/*",
+ "plugins/*"
+ ]
}
diff --git a/packages/core-api/package.json b/packages/core-api/package.json
index 942c8b9d67..638ff46e56 100644
--- a/packages/core-api/package.json
+++ b/packages/core-api/package.json
@@ -1,6 +1,6 @@
{
"name": "@arkecosystem/core-api",
- "version": "3.0.6",
+ "version": "3.12.0",
"description": "Public API for ARK Core",
"license": "MIT",
"contributors": [
@@ -22,25 +22,25 @@
"pretest": "bash ../../scripts/pre-test.sh"
},
"dependencies": {
- "@arkecosystem/core-database": "3.0.6",
- "@arkecosystem/core-kernel": "3.0.6",
- "@arkecosystem/core-transactions": "3.0.6",
- "@arkecosystem/crypto": "3.0.6",
+ "@arkecosystem/core-database": "3.12.0",
+ "@arkecosystem/core-kernel": "3.12.0",
+ "@arkecosystem/core-transactions": "3.12.0",
+ "@arkecosystem/crypto": "3.12.0",
"@hapi/boom": "9.1.4",
"@hapi/hapi": "20.1.5",
"@hapi/hoek": "9.2.0",
- "joi": "17.4.2",
+ "joi": "17.12.1",
"nanomatch": "1.2.13",
"node-cache": "5.1.2",
- "rate-limiter-flexible": "1.3.2",
- "semver": "6.3.0"
+ "qs": "6.11.2",
+ "rate-limiter-flexible": "4.0.1",
+ "semaphore": "1.1.0",
+ "semver": "7.5.4"
},
"devDependencies": {
"@types/hapi__boom": "7.4.1",
- "@types/hapi__joi": "17.1.7",
- "@types/ip": "1.1.0",
- "@types/semver": "6.2.3",
- "lodash.clonedeep": "4.5.0"
+ "@types/semaphore": "1.1.4",
+ "@types/semver": "7.5.6"
},
"engines": {
"node": ">=10.x"
diff --git a/packages/core-api/src/controllers/blocks.ts b/packages/core-api/src/controllers/blocks.ts
index 7fd69ccf27..ec05ff7c93 100644
--- a/packages/core-api/src/controllers/blocks.ts
+++ b/packages/core-api/src/controllers/blocks.ts
@@ -12,9 +12,11 @@ export class BlocksController extends Controller {
private readonly blockchain!: Contracts.Blockchain.Blockchain;
@Container.inject(Container.Identifiers.BlockHistoryService)
+ @Container.tagged("connection", "api")
private readonly blockHistoryService!: Contracts.Shared.BlockHistoryService;
@Container.inject(Container.Identifiers.TransactionHistoryService)
+ @Container.tagged("connection", "api")
private readonly transactionHistoryService!: Contracts.Shared.TransactionHistoryService;
@Container.inject(Container.Identifiers.StateStore)
@@ -46,7 +48,11 @@ export class BlocksController extends Controller {
const block = this.stateStore.getGenesisBlock();
if (request.query.transform) {
- return this.respondWithResource(block, BlockWithTransactionsResource, true);
+ return this.respondWithResource(
+ { data: block.data, transactions: block.transactions.map((t) => t.data) },
+ BlockWithTransactionsResource,
+ true,
+ );
} else {
return this.respondWithResource(block.data, BlockResource, false);
}
@@ -56,7 +62,11 @@ export class BlocksController extends Controller {
const block = this.blockchain.getLastBlock();
if (request.query.transform) {
- return this.respondWithResource(block, BlockWithTransactionsResource, true);
+ return this.respondWithResource(
+ { data: block.data, transactions: block.transactions.map((t) => t.data) },
+ BlockWithTransactionsResource,
+ true,
+ );
} else {
return this.respondWithResource(block.data, BlockResource, false);
}
diff --git a/packages/core-api/src/controllers/controller.ts b/packages/core-api/src/controllers/controller.ts
index ceddc9cf12..6afd39d733 100644
--- a/packages/core-api/src/controllers/controller.ts
+++ b/packages/core-api/src/controllers/controller.ts
@@ -58,10 +58,12 @@ export class Controller {
const orderBy = Array.isArray(request.query.orderBy) ? request.query.orderBy : request.query.orderBy.split(",");
- return orderBy.map((s: string) => ({
- property: s.split(":")[0],
- direction: s.split(":")[1] === "desc" ? "desc" : "asc",
- }));
+ return orderBy
+ .flatMap((o: object) => o)
+ .map((s: string) => ({
+ property: s.split(":")[0],
+ direction: s.split(":")[1] === "desc" ? "desc" : "asc",
+ }));
}
protected getListingOptions(): Contracts.Search.Options {
diff --git a/packages/core-api/src/controllers/delegates.ts b/packages/core-api/src/controllers/delegates.ts
index 5468012a45..488317f32c 100644
--- a/packages/core-api/src/controllers/delegates.ts
+++ b/packages/core-api/src/controllers/delegates.ts
@@ -6,6 +6,7 @@ import Hapi from "@hapi/hapi";
import { Identifiers } from "../identifiers";
import { BlockResource, BlockWithTransactionsResource } from "../resources";
import {
+ blockCriteriaSchemaObject,
DelegateCriteria,
delegateCriteriaSchemaObject,
DelegateResource,
@@ -25,9 +26,10 @@ export class DelegatesController extends Controller {
private readonly walletSearchService!: WalletSearchService;
@Container.inject(Container.Identifiers.BlockHistoryService)
+ @Container.tagged("connection", "api")
private readonly blockHistoryService!: Contracts.Shared.BlockHistoryService;
- public index(request: Hapi.Request): Contracts.Search.ResultsPage {
+ public async index(request: Hapi.Request): Promise> {
const pagination = this.getQueryPagination(request.query);
const sorting = request.query.orderBy as Contracts.Search.Sorting;
const criteria = this.getQueryCriteria(request.query, delegateCriteriaSchemaObject) as DelegateCriteria;
@@ -51,7 +53,7 @@ export class DelegatesController extends Controller {
return { data: delegateResource };
}
- public voters(request: Hapi.Request): Contracts.Search.ResultsPage | Boom {
+ public async voters(request: Hapi.Request): Promise | Boom> {
const walletId = request.params.id as string;
const walletResource = this.walletSearchService.getWallet(walletId);
@@ -88,8 +90,12 @@ export class DelegatesController extends Controller {
return notFound("Delegate not found");
}
+ const blockCriteria = {
+ ...(this.getQueryCriteria(request.query, blockCriteriaSchemaObject) as Contracts.Shared.OrBlockCriteria),
+ generatorPublicKey: delegateResource.publicKey,
+ };
+
if (request.query.transform) {
- const blockCriteria = { generatorPublicKey: delegateResource.publicKey };
const blockWithSomeTransactionsListResult = await this.blockHistoryService.listByCriteriaJoinTransactions(
blockCriteria,
{ typeGroup: Enums.TransactionTypeGroup.Core, type: Enums.TransactionType.MultiPayment },
@@ -100,7 +106,6 @@ export class DelegatesController extends Controller {
return this.toPagination(blockWithSomeTransactionsListResult, BlockWithTransactionsResource, true);
} else {
- const blockCriteria = { generatorPublicKey: delegateResource.publicKey };
const blockListResult = await this.blockHistoryService.listByCriteria(
blockCriteria,
this.getListingOrder(request),
diff --git a/packages/core-api/src/controllers/locks.ts b/packages/core-api/src/controllers/locks.ts
index 751ed6f7e3..c8f4abe8e2 100644
--- a/packages/core-api/src/controllers/locks.ts
+++ b/packages/core-api/src/controllers/locks.ts
@@ -15,9 +15,10 @@ export class LocksController extends Controller {
private readonly lockSearchService!: LockSearchService;
@Container.inject(Container.Identifiers.TransactionHistoryService)
+ @Container.tagged("connection", "api")
private readonly transactionHistoryService!: Contracts.Shared.TransactionHistoryService;
- public index(request: Hapi.Request): Contracts.Search.ResultsPage {
+ public async index(request: Hapi.Request): Promise> {
const pagination = this.getQueryPagination(request.query);
const sorting = request.query.orderBy as Contracts.Search.Sorting;
const criteria = this.getQueryCriteria(request.query, lockCriteriaSchemaObject) as LockCriteria;
diff --git a/packages/core-api/src/controllers/node.ts b/packages/core-api/src/controllers/node.ts
index 50f6da36bc..92f50bea2a 100644
--- a/packages/core-api/src/controllers/node.ts
+++ b/packages/core-api/src/controllers/node.ts
@@ -27,6 +27,7 @@ export class NodeController extends Controller {
private readonly networkMonitor!: Contracts.P2P.NetworkMonitor;
@Container.inject(Container.Identifiers.DatabaseTransactionRepository)
+ @Container.tagged("connection", "api")
private readonly transactionRepository!: Repositories.TransactionRepository;
public async status(request: Hapi.Request, h: Hapi.ResponseToolkit) {
@@ -80,15 +81,12 @@ export class NodeController extends Controller {
constants: Managers.configManager.getMilestone(this.blockchain.getLastHeight()),
transactionPool: {
dynamicFees: dynamicFees.enabled ? dynamicFees : { enabled: false },
- maxTransactionsInPool: this.transactionPoolConfiguration.getRequired(
- "maxTransactionsInPool",
- ),
- maxTransactionsPerSender: this.transactionPoolConfiguration.getRequired(
- "maxTransactionsPerSender",
- ),
- maxTransactionsPerRequest: this.transactionPoolConfiguration.getRequired(
- "maxTransactionsPerRequest",
- ),
+ maxTransactionsInPool:
+ this.transactionPoolConfiguration.getRequired("maxTransactionsInPool"),
+ maxTransactionsPerSender:
+ this.transactionPoolConfiguration.getRequired("maxTransactionsPerSender"),
+ maxTransactionsPerRequest:
+ this.transactionPoolConfiguration.getRequired("maxTransactionsPerRequest"),
maxTransactionAge: this.transactionPoolConfiguration.getRequired("maxTransactionAge"),
maxTransactionBytes: this.transactionPoolConfiguration.getRequired("maxTransactionBytes"),
},
@@ -106,12 +104,11 @@ export class NodeController extends Controller {
// @ts-ignore
const handlers = this.nullHandlerRegistry.getRegisteredHandlers();
const handlersKey = {};
- const txsTypes: Array<{ type: number, typeGroup: number }> = [];
+ const txsTypes: Array<{ type: number; typeGroup: number }> = [];
for (const handler of handlers) {
- handlersKey[
- `${handler.getConstructor().type}-${handler.getConstructor().typeGroup}`
- ] = handler.getConstructor().key;
- txsTypes.push({ type: handler.getConstructor().type!, typeGroup: handler.getConstructor().typeGroup!});
+ handlersKey[`${handler.getConstructor().type}-${handler.getConstructor().typeGroup}`] =
+ handler.getConstructor().key;
+ txsTypes.push({ type: handler.getConstructor().type!, typeGroup: handler.getConstructor().typeGroup! });
}
const results = await this.transactionRepository.getFeeStatistics(txsTypes, request.query.days);
diff --git a/packages/core-api/src/controllers/rounds.ts b/packages/core-api/src/controllers/rounds.ts
index a1cd44fad2..2e5602fe5a 100644
--- a/packages/core-api/src/controllers/rounds.ts
+++ b/packages/core-api/src/controllers/rounds.ts
@@ -8,6 +8,7 @@ import { Controller } from "./controller";
export class RoundsController extends Controller {
@Container.inject(Container.Identifiers.DatabaseRoundRepository)
+ @Container.tagged("connection", "api")
private readonly roundRepository!: Repositories.RoundRepository;
public async delegates(request: Hapi.Request, h: Hapi.ResponseToolkit) {
diff --git a/packages/core-api/src/controllers/transactions.ts b/packages/core-api/src/controllers/transactions.ts
index ec7783080d..399ce06123 100644
--- a/packages/core-api/src/controllers/transactions.ts
+++ b/packages/core-api/src/controllers/transactions.ts
@@ -20,9 +20,11 @@ export class TransactionsController extends Controller {
private readonly poolQuery!: Contracts.TransactionPool.Query;
@Container.inject(Container.Identifiers.TransactionHistoryService)
+ @Container.tagged("connection", "api")
private readonly transactionHistoryService!: Contracts.Shared.TransactionHistoryService;
@Container.inject(Container.Identifiers.BlockHistoryService)
+ @Container.tagged("connection", "api")
private readonly blockHistoryService!: Contracts.Shared.BlockHistoryService;
@Container.inject(Container.Identifiers.TransactionPoolProcessor)
diff --git a/packages/core-api/src/controllers/votes.ts b/packages/core-api/src/controllers/votes.ts
index fa1a504fa3..52309f61f8 100644
--- a/packages/core-api/src/controllers/votes.ts
+++ b/packages/core-api/src/controllers/votes.ts
@@ -9,6 +9,7 @@ import { Controller } from "./controller";
@Container.injectable()
export class VotesController extends Controller {
@Container.inject(Container.Identifiers.TransactionHistoryService)
+ @Container.tagged("connection", "api")
private readonly transactionHistoryService!: Contracts.Shared.TransactionHistoryService;
public async index(request: Hapi.Request, h: Hapi.ResponseToolkit) {
diff --git a/packages/core-api/src/controllers/wallets.ts b/packages/core-api/src/controllers/wallets.ts
index 46cf708d03..4044b1b65f 100644
--- a/packages/core-api/src/controllers/wallets.ts
+++ b/packages/core-api/src/controllers/wallets.ts
@@ -28,9 +28,10 @@ export class WalletsController extends Controller {
private readonly lockSearchService!: LockSearchService;
@Container.inject(Container.Identifiers.TransactionHistoryService)
+ @Container.tagged("connection", "api")
private readonly transactionHistoryService!: Contracts.Shared.TransactionHistoryService;
- public index(request: Hapi.Request): Contracts.Search.ResultsPage {
+ public async index(request: Hapi.Request): Promise> {
const pagination = this.getQueryPagination(request.query);
const sorting = request.query.orderBy as Contracts.Search.Sorting;
const criteria = this.getQueryCriteria(request.query, walletCriteriaSchemaObject) as WalletCriteria;
@@ -38,7 +39,7 @@ export class WalletsController extends Controller {
return this.walletSearchService.getWalletsPage(pagination, sorting, criteria);
}
- public top(request: Hapi.Request): Contracts.Search.ResultsPage {
+ public async top(request: Hapi.Request): Promise> {
const pagination = this.getQueryPagination(request.query);
const sorting = request.query.orderBy as Contracts.Search.Sorting;
const criteria = this.getQueryCriteria(request.query, walletCriteriaSchemaObject) as WalletCriteria;
@@ -57,7 +58,7 @@ export class WalletsController extends Controller {
return { data: walletResource };
}
- public locks(request: Hapi.Request): Contracts.Search.ResultsPage | Boom {
+ public async locks(request: Hapi.Request): Promise | Boom> {
const walletId = request.params.id as string;
const walletResource = this.walletSearchService.getWallet(walletId);
diff --git a/packages/core-api/src/defaults.ts b/packages/core-api/src/defaults.ts
index bcb2e7ab95..db1ba3ba16 100644
--- a/packages/core-api/src/defaults.ts
+++ b/packages/core-api/src/defaults.ts
@@ -17,11 +17,39 @@ export const defaults = {
},
},
plugins: {
+ log: {
+ enabled: !!process.env.CORE_API_LOG,
+ },
cache: {
enabled: !!process.env.CORE_API_CACHE,
stdTTL: 8,
checkperiod: 120,
},
+ semaphore: {
+ enabled: !process.env.CORE_API_SEMAPHORE_DISABLED,
+ database: {
+ levelOne: {
+ concurrency: 10,
+ queueLimit: 100,
+ maxOffset: 10_000,
+ },
+ levelTwo: {
+ concurrency: 1,
+ queueLimit: 5,
+ },
+ },
+ memory: {
+ levelOne: {
+ concurrency: 3,
+ queueLimit: 30,
+ maxOffset: 1_000,
+ },
+ levelTwo: {
+ concurrency: 1,
+ queueLimit: 5,
+ },
+ },
+ },
rateLimit: {
enabled: !process.env.CORE_API_RATE_LIMIT_DISABLED,
points: process.env.CORE_API_RATE_LIMIT_USER_LIMIT || 100,
diff --git a/packages/core-api/src/plugins/index.ts b/packages/core-api/src/plugins/index.ts
index d93a15a26a..2b0fa6e19f 100644
--- a/packages/core-api/src/plugins/index.ts
+++ b/packages/core-api/src/plugins/index.ts
@@ -1,7 +1,9 @@
import { commaArrayQuery } from "./comma-array-query";
import { dotSeparatedQuery } from "./dot-separated-query";
import { hapiAjv } from "./hapi-ajv";
+import { log } from "./log";
import { responseHeaders } from "./response-headers";
+import { semaphore } from "./semaphore";
import { whitelist } from "./whitelist";
export const preparePlugins = (config) => [
@@ -13,8 +15,16 @@ export const preparePlugins = (config) => [
},
},
{ plugin: hapiAjv },
+ {
+ plugin: log,
+ options: {
+ ...config.log,
+ trustProxy: config.trustProxy,
+ },
+ },
{ plugin: commaArrayQuery },
{ plugin: dotSeparatedQuery },
+ { plugin: semaphore, options: config.semaphore },
{
plugin: require("./cache"),
options: config.cache,
diff --git a/packages/core-api/src/plugins/log.ts b/packages/core-api/src/plugins/log.ts
new file mode 100644
index 0000000000..55caf27b69
--- /dev/null
+++ b/packages/core-api/src/plugins/log.ts
@@ -0,0 +1,33 @@
+import { Container, Contracts } from "@arkecosystem/core-kernel";
+import Hapi from "@hapi/hapi";
+
+import { getIp } from "../utils";
+
+export const log = {
+ name: "log",
+ version: "1.0.0",
+
+ register(
+ server: Hapi.Server,
+ options: {
+ enabled: boolean;
+ trustProxy: boolean;
+ },
+ ): void {
+ if (!options.enabled) {
+ return;
+ }
+
+ const logger = server.app.app.get(Container.Identifiers.LogService);
+
+ server.ext("onRequest", (request: Hapi.Request, h: Hapi.ResponseToolkit) => {
+ logger.debug(
+ `API request on: "${request.path}" from: "${getIp(request, options.trustProxy)}" with query: "${
+ request.url.search
+ }"`,
+ );
+
+ return h.continue;
+ });
+ },
+};
diff --git a/packages/core-api/src/plugins/pagination/ext.ts b/packages/core-api/src/plugins/pagination/ext.ts
index ff02438251..c03003ad38 100644
--- a/packages/core-api/src/plugins/pagination/ext.ts
+++ b/packages/core-api/src/plugins/pagination/ext.ts
@@ -2,7 +2,7 @@
import { Utils } from "@arkecosystem/core-kernel";
import Hoek from "@hapi/hoek";
-import Qs from "querystring";
+import qs from "qs";
export class Ext {
private readonly routePathPrefix = "/api";
@@ -54,7 +54,8 @@ export class Ext {
// strip prefix in baseUri, we want a "clean" relative path
const baseUri = request.url.pathname.slice(this.routePathPrefix.length) + "?";
- const { query } = request;
+ const query = { ...request.query };
+ delete query.orderBy;
const currentPage = query.page;
const currentLimit = query.limit;
@@ -68,8 +69,14 @@ export class Ext {
const getUri = (page: number | null): string | null =>
/* istanbul ignore next */
- // tslint:disable-next-line: no-null-keyword
- page ? baseUri + Qs.stringify(Hoek.applyToDefaults({ ...query, ...request.orig.query }, { page })) : null;
+ page
+ ? baseUri +
+ qs.stringify(Hoek.applyToDefaults({ ...query, ...request.orig.query }, { page }), {
+ allowDots: true,
+ arrayFormat: "comma",
+ encode: false,
+ })
+ : null;
const newSource = {
meta: {
@@ -78,14 +85,10 @@ export class Ext {
count: results.length,
pageCount: pageCount,
totalCount: totalCount ? totalCount : 0,
-
- // tslint:disable-next-line: no-null-keyword
/* istanbul ignore next */
next: totalCount && currentPage < pageCount ? getUri(currentPage + 1) : null,
previous:
- // tslint:disable-next-line: no-null-keyword
totalCount && currentPage > 1 && currentPage <= pageCount + 1 ? getUri(currentPage - 1) : null,
-
self: getUri(currentPage),
first: getUri(1),
last: getUri(pageCount),
diff --git a/packages/core-api/src/plugins/rate-limit.ts b/packages/core-api/src/plugins/rate-limit.ts
index 233b432bb9..fb718f0eed 100644
--- a/packages/core-api/src/plugins/rate-limit.ts
+++ b/packages/core-api/src/plugins/rate-limit.ts
@@ -47,10 +47,10 @@ export = {
limiter: new RateLimiterMemory({ points: options.points, duration: options.duration }),
whiteList: options.whitelist || ["*"],
blackList: options.blacklist || [],
- isWhite: (ip: string) => {
+ isWhiteListed: (ip: string) => {
return isListed(ip, options.whitelist);
},
- isBlack: (ip: string) => {
+ isBlackListed: (ip: string) => {
return isListed(ip, options.blacklist);
},
runActionAnyway: false,
diff --git a/packages/core-api/src/plugins/semaphore.ts b/packages/core-api/src/plugins/semaphore.ts
new file mode 100644
index 0000000000..c6b3779346
--- /dev/null
+++ b/packages/core-api/src/plugins/semaphore.ts
@@ -0,0 +1,221 @@
+import Boom from "@hapi/boom";
+import Hapi from "@hapi/hapi";
+import makeSemaphore, { Semaphore } from "semaphore";
+
+type PluginOptions = {
+ enabled: boolean;
+ database: {
+ levelOne: LevelOneOptions;
+ levelTwo: LevelOptions;
+ };
+ memory: {
+ levelOne: LevelOneOptions;
+ levelTwo: LevelOptions;
+ };
+};
+
+type LevelOptions = {
+ concurrency: number;
+ queueLimit: number;
+};
+
+type LevelOneOptions = LevelOptions & {
+ maxOffset: number;
+};
+
+type QueryLevelOptions = {
+ field: string;
+ asc: boolean;
+ desc: boolean;
+ allowSecondOrderBy: boolean;
+ diverse: boolean;
+};
+
+type SemaphoreOptions = {
+ enabled: boolean;
+ type: "database" | "memory";
+ queryLevelOptions?: QueryLevelOptions[];
+};
+
+type FullSemaphoreOptions = Required;
+
+enum Level {
+ One = 1,
+ Two = 2,
+}
+
+export const semaphore = {
+ name: "onPreHandler",
+ version: "1.0.0",
+ register(server: Hapi.Server, pluginOptions: PluginOptions): void {
+ if (!pluginOptions.enabled) {
+ return;
+ }
+
+ const semaphores = { database: new Map(), memory: new Map() };
+ semaphores.database.set(Level.One, makeSemaphore(pluginOptions.database.levelOne.concurrency));
+ semaphores.database.set(Level.Two, makeSemaphore(pluginOptions.database.levelTwo.concurrency));
+ semaphores.memory.set(Level.One, makeSemaphore(pluginOptions.memory.levelOne.concurrency));
+ semaphores.memory.set(Level.Two, makeSemaphore(pluginOptions.memory.levelTwo.concurrency));
+
+ const semaphoresConcurrency = { database: new Map(), memory: new Map() };
+ semaphoresConcurrency.database.set(Level.One, pluginOptions.database.levelOne.concurrency);
+ semaphoresConcurrency.database.set(Level.Two, pluginOptions.database.levelTwo.concurrency);
+ semaphoresConcurrency.memory.set(Level.One, pluginOptions.memory.levelOne.concurrency);
+ semaphoresConcurrency.memory.set(Level.Two, pluginOptions.memory.levelTwo.concurrency);
+
+ const semaphoresQueueLimit = { database: new Map(), memory: new Map() };
+ semaphoresQueueLimit.database.set(Level.One, pluginOptions.database.levelOne.queueLimit);
+ semaphoresQueueLimit.database.set(Level.Two, pluginOptions.database.levelTwo.queueLimit);
+ semaphoresQueueLimit.memory.set(Level.One, pluginOptions.memory.levelOne.queueLimit);
+ semaphoresQueueLimit.memory.set(Level.Two, pluginOptions.memory.levelTwo.queueLimit);
+
+ const requestLevels = new Map();
+
+ server.ext("onPreHandler", async (request: Hapi.Request, h: Hapi.ResponseToolkit) => {
+ const options = getRouteSemaphoreOptions(request);
+
+ if (options.enabled) {
+ const level = getLevel(request, options);
+ const sem = semaphores[options.type].get(level)!;
+
+ if (
+ // @ts-ignore
+ sem.queue.length + (sem.current - semaphoresConcurrency[options.type].get(level)!) >=
+ semaphoresQueueLimit[options.type].get(level)!
+ ) {
+ return Boom.tooManyRequests();
+ }
+
+ requestLevels.set(request, level);
+
+ await new Promise((resolve) => {
+ sem.take(() => {
+ resolve();
+ });
+ });
+ }
+
+ return h.continue;
+ });
+
+ server.events.on("response", (request: Hapi.Request) => {
+ const options = getRouteSemaphoreOptions(request);
+
+ const level = requestLevels.get(request);
+
+ if (level) {
+ requestLevels.delete(request);
+ const sem = semaphores[options.type].get(level)!;
+
+ sem.leave();
+ }
+ });
+
+ const isFullSemaphoreOptions = (b: SemaphoreOptions): b is FullSemaphoreOptions => {
+ return !!b.queryLevelOptions;
+ };
+
+ const getLevel = (request: Hapi.Request, options: SemaphoreOptions): Level => {
+ if (isFullSemaphoreOptions(options)) {
+ if (usesDiverseIndex(request, options)) {
+ return Level.One;
+ }
+
+ const levels = [
+ orderByLevel(request, options),
+ offsetLevel(request, options),
+ queryLevel(request, options),
+ ];
+
+ return levels.includes(Level.Two) ? Level.Two : Level.One;
+ }
+
+ return offsetLevel(request, options);
+ };
+
+ const usesDiverseIndex = (request: Hapi.Request, options: FullSemaphoreOptions): boolean => {
+ const distributedIndices = options.queryLevelOptions
+ .filter((option) => option.diverse)
+ .map((option) => option.field);
+
+ for (const key of Object.keys(request.query)) {
+ if (distributedIndices.includes(key) && ["number", "string"].includes(typeof request.query[key])) {
+ return true;
+ }
+ }
+
+ return false;
+ };
+
+ const orderByLevel = (request: Hapi.Request, options: FullSemaphoreOptions): Level => {
+ if (request.query.orderBy && request.query.orderBy.length) {
+ const orderBy = Array.isArray(request.query.orderBy) ? request.query.orderBy : [request.query.orderBy];
+
+ const [field, sortOrder]: [string, "asc" | "desc"] = orderBy[0].split(":");
+
+ const fieldOptions = options.queryLevelOptions.find((options) => options.field === field);
+
+ if (!fieldOptions) {
+ return Level.Two;
+ }
+
+ if (!fieldOptions[sortOrder]) {
+ return Level.Two;
+ }
+
+ if (!fieldOptions.allowSecondOrderBy && orderBy.length > 1) {
+ return Level.Two;
+ }
+ }
+
+ return Level.One;
+ };
+
+ const offsetLevel = (request: Hapi.Request, options: SemaphoreOptions): Level => {
+ const offset = pluginOptions[options.type].levelOne.maxOffset;
+
+ if (request.query.offset && request.query.offset > offset) {
+ return Level.Two;
+ }
+
+ if (request.query.page && request.query.limit) {
+ if (request.query.page * request.query.limit > offset) {
+ return Level.Two;
+ }
+ }
+
+ return Level.One;
+ };
+
+ const queryLevel = (request: Hapi.Request, options: FullSemaphoreOptions): Level => {
+ const reservedFields = ["page", "limit", "transform", "offset", "orderBy"];
+ const indices = options.queryLevelOptions.map((options) => options.field);
+
+ for (const key of Object.keys(request.query)) {
+ if (reservedFields.includes(key)) {
+ continue;
+ }
+
+ if (!indices.includes(key)) {
+ return Level.Two;
+ }
+ }
+
+ return Level.One;
+ };
+
+ const getRouteSemaphoreOptions = (request): SemaphoreOptions => {
+ const result: SemaphoreOptions = {
+ enabled: false,
+ type: "database",
+ };
+
+ if (request.route.settings.plugins.semaphore) {
+ return { ...result, ...request.route.settings.plugins.semaphore };
+ }
+
+ return result;
+ };
+ },
+};
diff --git a/packages/core-api/src/resources-new/block.ts b/packages/core-api/src/resources-new/block.ts
index 44a56c2b7c..0983b02054 100644
--- a/packages/core-api/src/resources-new/block.ts
+++ b/packages/core-api/src/resources-new/block.ts
@@ -1,5 +1,7 @@
import Joi from "joi";
+import * as Schemas from "../schemas";
+
const blockHeightSchema = Joi.number().integer().min(1);
const blockIdSchema = Joi.alternatives(
Joi.string()
@@ -30,3 +32,16 @@ export const blockCriteriaSchemaObject = {
};
export const blockParamSchema = Joi.alternatives(blockIdSchema, blockHeightSchema);
+export const blockSortingSchema = Schemas.createSortingSchema(Schemas.blockCriteriaSchemas, [], false);
+
+export const blockQueryLevelOptions = [
+ { field: "version", asc: true, desc: true, allowSecondOrderBy: false, diverse: false },
+ { field: "timestamp", asc: true, desc: true, allowSecondOrderBy: true, diverse: true },
+ { field: "height", asc: true, desc: true, allowSecondOrderBy: true, diverse: true },
+ { field: "numberOfTransactions", asc: true, desc: false, allowSecondOrderBy: false, diverse: false },
+ { field: "totalAmount", asc: true, desc: false, allowSecondOrderBy: false, diverse: false },
+ { field: "totalFee", asc: true, desc: false, allowSecondOrderBy: false, diverse: false },
+ { field: "reward", asc: true, desc: true, allowSecondOrderBy: false, diverse: false },
+ { field: "id", asc: false, desc: false, allowSecondOrderBy: false, diverse: true },
+ { field: "previousBlock", asc: false, desc: false, allowSecondOrderBy: false, diverse: true },
+];
diff --git a/packages/core-api/src/resources-new/transaction.ts b/packages/core-api/src/resources-new/transaction.ts
index 8b249606a5..fd488a91d8 100644
--- a/packages/core-api/src/resources-new/transaction.ts
+++ b/packages/core-api/src/resources-new/transaction.ts
@@ -1,5 +1,6 @@
import Joi from "joi";
+import * as Schemas from "../schemas";
import { walletCriteriaSchemaObject } from "./wallet";
export const transactionIdSchema = Joi.string().hex().length(64);
@@ -17,3 +18,19 @@ export const transactionCriteriaSchemaObject = {
};
export const transactionParamSchema = transactionIdSchema;
+export const transactionSortingSchema = Schemas.createSortingSchema(Schemas.transactionCriteriaSchemas, [], false);
+
+export const transactionQueryLevelOptions = [
+ { field: "version", asc: true, desc: true, allowSecondOrderBy: false, diverse: false },
+ { field: "timestamp", asc: true, desc: true, allowSecondOrderBy: true, diverse: true },
+ { field: "type", asc: true, desc: false, allowSecondOrderBy: false, diverse: false },
+ { field: "amount", asc: true, desc: false, allowSecondOrderBy: false, diverse: false },
+ { field: "fee", asc: true, desc: false, allowSecondOrderBy: false, diverse: false },
+ { field: "typeGroup", asc: true, desc: true, allowSecondOrderBy: false, diverse: false },
+ { field: "nonce", asc: true, desc: true, allowSecondOrderBy: false, diverse: false },
+ { field: "id", asc: false, desc: false, allowSecondOrderBy: false, diverse: true },
+ { field: "blockId", asc: false, desc: false, allowSecondOrderBy: false, diverse: true },
+ { field: "senderPublicKey", asc: false, desc: false, allowSecondOrderBy: false, diverse: true },
+ { field: "recipientId", asc: false, desc: false, allowSecondOrderBy: false, diverse: true },
+ { field: "address", asc: false, desc: false, allowSecondOrderBy: false, diverse: true },
+];
diff --git a/packages/core-api/src/resources/transaction.ts b/packages/core-api/src/resources/transaction.ts
index 660b84ab02..f46037aa99 100644
--- a/packages/core-api/src/resources/transaction.ts
+++ b/packages/core-api/src/resources/transaction.ts
@@ -56,7 +56,7 @@ export class TransactionResource implements Resource {
confirmations: 0, // ! resource.block ? lastBlock.data.height - resource.block.height + 1 : 0
timestamp:
typeof resource.timestamp !== "undefined" ? AppUtils.formatTimestamp(resource.timestamp) : undefined,
- nonce: resource.nonce!.toFixed(),
+ nonce: resource.nonce?.toFixed(),
};
}
}
diff --git a/packages/core-api/src/routes/blocks.ts b/packages/core-api/src/routes/blocks.ts
index 7ed626005f..b2500e0c77 100644
--- a/packages/core-api/src/routes/blocks.ts
+++ b/packages/core-api/src/routes/blocks.ts
@@ -2,6 +2,7 @@ import Hapi from "@hapi/hapi";
import Joi from "joi";
import { BlocksController } from "../controllers/blocks";
+import { blockQueryLevelOptions, blockSortingSchema, transactionSortingSchema } from "../resources-new";
import * as Schemas from "../schemas";
export const register = (server: Hapi.Server): void => {
@@ -18,9 +19,16 @@ export const register = (server: Hapi.Server): void => {
...server.app.schemas.blockCriteriaSchemas,
orderBy: server.app.schemas.blocksOrderBy,
transform: Joi.bool().default(true),
- }).concat(Schemas.pagination),
+ })
+ .concat(blockSortingSchema)
+ .concat(Schemas.pagination),
},
plugins: {
+ semaphore: {
+ enabled: true,
+ type: "database",
+ queryLevelOptions: blockQueryLevelOptions,
+ },
pagination: {
enabled: true,
},
@@ -67,6 +75,12 @@ export const register = (server: Hapi.Server): void => {
transform: Joi.bool().default(true),
}),
},
+ plugins: {
+ semaphore: {
+ enabled: true,
+ type: "database",
+ },
+ },
},
});
@@ -83,9 +97,15 @@ export const register = (server: Hapi.Server): void => {
...server.app.schemas.transactionCriteriaSchemas,
orderBy: server.app.schemas.transactionsOrderBy,
transform: Joi.bool().default(true),
- }).concat(Schemas.pagination),
+ })
+ .concat(transactionSortingSchema)
+ .concat(Schemas.pagination),
},
plugins: {
+ semaphore: {
+ enabled: true,
+ type: "database",
+ },
pagination: {
enabled: true,
},
diff --git a/packages/core-api/src/routes/delegates.ts b/packages/core-api/src/routes/delegates.ts
index f6e0ad92ee..9937b5b4c8 100644
--- a/packages/core-api/src/routes/delegates.ts
+++ b/packages/core-api/src/routes/delegates.ts
@@ -3,6 +3,8 @@ import Joi from "joi";
import { DelegatesController } from "../controllers/delegates";
import {
+ blockQueryLevelOptions,
+ blockSortingSchema,
delegateCriteriaSchema,
delegateSortingSchema,
walletCriteriaSchema,
@@ -75,9 +77,16 @@ export const register = (server: Hapi.Server): void => {
...server.app.schemas.blockCriteriaSchemas,
orderBy: server.app.schemas.blocksOrderBy,
transform: Joi.bool().default(true),
- }).concat(Schemas.pagination),
+ })
+ .concat(blockSortingSchema)
+ .concat(Schemas.pagination),
},
plugins: {
+ semaphore: {
+ enabled: true,
+ type: "database",
+ queryLevelOptions: blockQueryLevelOptions,
+ },
pagination: {
enabled: true,
},
diff --git a/packages/core-api/src/routes/locks.ts b/packages/core-api/src/routes/locks.ts
index 5828abba73..f049a956ca 100644
--- a/packages/core-api/src/routes/locks.ts
+++ b/packages/core-api/src/routes/locks.ts
@@ -18,6 +18,10 @@ export const register = (server: Hapi.Server): void => {
query: Joi.object().concat(lockCriteriaSchema).concat(lockSortingSchema).concat(Schemas.pagination),
},
plugins: {
+ semaphore: {
+ enabled: true,
+ type: "memory",
+ },
pagination: { enabled: true },
},
},
@@ -50,6 +54,10 @@ export const register = (server: Hapi.Server): void => {
}).required(),
},
plugins: {
+ semaphore: {
+ enabled: true,
+ type: "database",
+ },
pagination: {
enabled: true,
},
diff --git a/packages/core-api/src/routes/node.ts b/packages/core-api/src/routes/node.ts
index 36ec0b78ce..60ef9899b8 100644
--- a/packages/core-api/src/routes/node.ts
+++ b/packages/core-api/src/routes/node.ts
@@ -41,6 +41,12 @@ export const register = (server: Hapi.Server): void => {
days: Joi.number().integer().min(1).max(30),
}),
},
+ plugins: {
+ semaphore: {
+ enabled: true,
+ type: "database",
+ },
+ },
},
});
};
diff --git a/packages/core-api/src/routes/rounds.ts b/packages/core-api/src/routes/rounds.ts
index 3d824c1895..705cc15642 100644
--- a/packages/core-api/src/routes/rounds.ts
+++ b/packages/core-api/src/routes/rounds.ts
@@ -17,6 +17,12 @@ export const register = (server: Hapi.Server): void => {
id: Joi.number().integer().min(1),
}),
},
+ plugins: {
+ semaphore: {
+ enabled: true,
+ type: "database",
+ },
+ },
},
});
};
diff --git a/packages/core-api/src/routes/transactions.ts b/packages/core-api/src/routes/transactions.ts
index fd5e34f72c..5285049489 100644
--- a/packages/core-api/src/routes/transactions.ts
+++ b/packages/core-api/src/routes/transactions.ts
@@ -3,6 +3,7 @@ import Hapi from "@hapi/hapi";
import Joi from "joi";
import { TransactionsController } from "../controllers/transactions";
+import { transactionQueryLevelOptions, transactionSortingSchema } from "../resources-new";
import * as Schemas from "../schemas";
export const register = (server: Hapi.Server): void => {
@@ -19,9 +20,16 @@ export const register = (server: Hapi.Server): void => {
...server.app.schemas.transactionCriteriaSchemas,
orderBy: server.app.schemas.transactionsOrderBy,
transform: Joi.bool().default(true),
- }).concat(Schemas.pagination),
+ })
+ .concat(transactionSortingSchema)
+ .concat(Schemas.pagination),
},
plugins: {
+ semaphore: {
+ enabled: true,
+ type: "database",
+ queryLevelOptions: transactionQueryLevelOptions,
+ },
pagination: {
enabled: true,
},
@@ -72,6 +80,12 @@ export const register = (server: Hapi.Server): void => {
transform: Joi.bool().default(true),
}),
},
+ plugins: {
+ semaphore: {
+ enabled: true,
+ type: "database",
+ },
+ },
},
});
diff --git a/packages/core-api/src/routes/votes.ts b/packages/core-api/src/routes/votes.ts
index b33b2cf592..5dab045815 100644
--- a/packages/core-api/src/routes/votes.ts
+++ b/packages/core-api/src/routes/votes.ts
@@ -2,6 +2,7 @@ import Hapi from "@hapi/hapi";
import Joi from "joi";
import { VotesController } from "../controllers/votes";
+import { transactionQueryLevelOptions, transactionSortingSchema } from "../resources-new";
import * as Schemas from "../schemas";
export const register = (server: Hapi.Server): void => {
@@ -18,9 +19,16 @@ export const register = (server: Hapi.Server): void => {
...server.app.schemas.transactionCriteriaSchemas,
orderBy: server.app.schemas.transactionsOrderBy,
transform: Joi.bool().default(true),
- }).concat(Schemas.pagination),
+ })
+ .concat(transactionSortingSchema)
+ .concat(Schemas.pagination),
},
plugins: {
+ semaphore: {
+ enabled: true,
+ type: "database",
+ queryLevelOptions: transactionQueryLevelOptions,
+ },
pagination: {
enabled: true,
},
@@ -41,6 +49,12 @@ export const register = (server: Hapi.Server): void => {
transform: Joi.bool().default(true),
}),
},
+ plugins: {
+ semaphore: {
+ enabled: true,
+ type: "database",
+ },
+ },
},
});
};
diff --git a/packages/core-api/src/routes/wallets.ts b/packages/core-api/src/routes/wallets.ts
index a2ca9cdbb0..45691eccb3 100644
--- a/packages/core-api/src/routes/wallets.ts
+++ b/packages/core-api/src/routes/wallets.ts
@@ -5,6 +5,8 @@ import { WalletsController } from "../controllers/wallets";
import {
lockCriteriaSchema,
lockSortingSchema,
+ transactionQueryLevelOptions,
+ transactionSortingSchema,
walletCriteriaSchema,
walletParamSchema,
walletSortingSchema,
@@ -24,6 +26,10 @@ export const register = (server: Hapi.Server): void => {
query: Joi.object().concat(walletCriteriaSchema).concat(walletSortingSchema).concat(Schemas.pagination),
},
plugins: {
+ semaphore: {
+ enabled: true,
+ type: "memory",
+ },
pagination: { enabled: true },
},
},
@@ -38,6 +44,10 @@ export const register = (server: Hapi.Server): void => {
query: Joi.object().concat(walletCriteriaSchema).concat(walletSortingSchema).concat(Schemas.pagination),
},
plugins: {
+ semaphore: {
+ enabled: true,
+ type: "memory",
+ },
pagination: { enabled: true },
},
},
@@ -68,6 +78,10 @@ export const register = (server: Hapi.Server): void => {
query: Joi.object().concat(lockCriteriaSchema).concat(lockSortingSchema).concat(Schemas.pagination),
},
plugins: {
+ semaphore: {
+ enabled: true,
+ type: "memory",
+ },
pagination: { enabled: true },
},
},
@@ -86,9 +100,16 @@ export const register = (server: Hapi.Server): void => {
...server.app.schemas.transactionCriteriaSchemas,
orderBy: server.app.schemas.transactionsOrderBy,
transform: Joi.bool().default(true),
- }).concat(Schemas.pagination),
+ })
+ .concat(transactionSortingSchema)
+ .concat(Schemas.pagination),
},
plugins: {
+ semaphore: {
+ enabled: true,
+ type: "database",
+ queryLevelOptions: transactionQueryLevelOptions,
+ },
pagination: {
enabled: true,
},
@@ -109,9 +130,16 @@ export const register = (server: Hapi.Server): void => {
...server.app.schemas.transactionCriteriaSchemas,
orderBy: server.app.schemas.transactionsOrderBy,
transform: Joi.bool().default(true),
- }).concat(Schemas.pagination),
+ })
+ .concat(transactionSortingSchema)
+ .concat(Schemas.pagination),
},
plugins: {
+ semaphore: {
+ enabled: true,
+ type: "database",
+ queryLevelOptions: transactionQueryLevelOptions,
+ },
pagination: {
enabled: true,
},
@@ -132,9 +160,16 @@ export const register = (server: Hapi.Server): void => {
...server.app.schemas.transactionCriteriaSchemas,
orderBy: server.app.schemas.transactionsOrderBy,
transform: Joi.bool().default(true),
- }).concat(Schemas.pagination),
+ })
+ .concat(transactionSortingSchema)
+ .concat(Schemas.pagination),
},
plugins: {
+ semaphore: {
+ enabled: true,
+ type: "database",
+ queryLevelOptions: transactionQueryLevelOptions,
+ },
pagination: {
enabled: true,
},
@@ -155,9 +190,16 @@ export const register = (server: Hapi.Server): void => {
...server.app.schemas.transactionCriteriaSchemas,
orderBy: server.app.schemas.transactionsOrderBy,
transform: Joi.bool().default(true),
- }).concat(Schemas.pagination),
+ })
+ .concat(transactionSortingSchema)
+ .concat(Schemas.pagination),
},
plugins: {
+ semaphore: {
+ enabled: true,
+ type: "database",
+ queryLevelOptions: transactionQueryLevelOptions,
+ },
pagination: {
enabled: true,
},
diff --git a/packages/core-api/src/schemas.ts b/packages/core-api/src/schemas.ts
index ea6a3ca2e4..ab3833c72e 100644
--- a/packages/core-api/src/schemas.ts
+++ b/packages/core-api/src/schemas.ts
@@ -44,7 +44,11 @@ export const createRangeCriteriaSchema = (item: Joi.Schema): Joi.Schema => {
// Sorting
-export const createSortingSchema = (schemaObject: SchemaObject, wildcardPaths: string[] = []): Joi.ObjectSchema => {
+export const createSortingSchema = (
+ schemaObject: SchemaObject,
+ wildcardPaths: string[] = [],
+ transform: boolean = true,
+): Joi.ObjectSchema => {
const getObjectPaths = (object: SchemaObject): string[] => {
return Object.entries(object)
.map(([key, value]) => {
@@ -55,15 +59,17 @@ export const createSortingSchema = (schemaObject: SchemaObject, wildcardPaths: s
const exactPaths = getObjectPaths(schemaObject);
- return Joi.object({
- orderBy: Joi.custom((value, helpers) => {
- if (value === "") {
- return [];
- }
+ const orderBy = Joi.custom((value, helpers) => {
+ if (value === "") {
+ return [];
+ }
+
+ const sorting: Contracts.Search.Sorting = [];
- const sorting: Contracts.Search.Sorting = [];
+ const sortingCriteria: string[] = Array.isArray(value) ? value : [value];
- for (const item of value.split(",")) {
+ for (const criteria of sortingCriteria) {
+ for (const item of criteria.split(",")) {
const pair = item.split(":");
const property = String(pair[0]);
const direction = pair.length === 1 ? "asc" : pair[1];
@@ -80,12 +86,24 @@ export const createSortingSchema = (schemaObject: SchemaObject, wildcardPaths: s
});
}
- sorting.push({ property, direction: direction as "asc" | "desc" });
+ if (transform) {
+ sorting.push({ property, direction: direction as "asc" | "desc" });
+ }
}
+ }
- return sorting;
- }).default([]),
+ if (!transform) {
+ return value;
+ }
+
+ return sorting;
});
+
+ if (transform) {
+ return Joi.object({ orderBy: orderBy.default([]) });
+ } else {
+ return Joi.object({ orderBy });
+ }
};
// Pagination
diff --git a/packages/core-api/src/server.ts b/packages/core-api/src/server.ts
index 4b4926f3ff..df0a6f0bd9 100644
--- a/packages/core-api/src/server.ts
+++ b/packages/core-api/src/server.ts
@@ -73,13 +73,20 @@ export class Server {
this.server.app.app = this.app;
this.server.app.schemas = Schemas;
+ this.server.ext("onRequest", (request, h) => {
+ request.raw.res.on("finish", () => {
+ request.raw.req.socket?.destroy();
+ });
+ return h.continue;
+ });
+
this.server.ext("onPreHandler", (request, h) => {
request.headers["content-type"] = "application/json";
return h.continue;
});
this.server.ext("onPreResponse", (request, h) => {
- if (request.response.isBoom && request.response.isServer) {
+ if (request.response.isBoom && request.response.isServer && request.response.name !== "QueryFailedError") {
this.logger.error(request.response.stack);
}
return h.continue;
diff --git a/packages/core-api/src/service-provider.ts b/packages/core-api/src/service-provider.ts
index 2be2db1cdc..f5e5cf9240 100644
--- a/packages/core-api/src/service-provider.ts
+++ b/packages/core-api/src/service-provider.ts
@@ -61,11 +61,39 @@ export class ServiceProvider extends Providers.ServiceProvider {
}).required(),
}).required(),
plugins: Joi.object({
+ log: Joi.object({
+ enabled: Joi.bool().required(),
+ }).required(),
cache: Joi.object({
enabled: Joi.bool().required(),
stdTTL: Joi.number().integer().min(0).required(),
checkperiod: Joi.number().integer().min(0).required(),
}).required(),
+ semaphore: Joi.object({
+ enabled: Joi.bool().required(),
+ database: Joi.object({
+ levelOne: Joi.object({
+ concurrency: Joi.number().integer().min(0).required(),
+ queueLimit: Joi.number().integer().min(0).required(),
+ maxOffset: Joi.number().integer().min(0).required(),
+ }).required(),
+ levelTwo: Joi.object({
+ concurrency: Joi.number().integer().min(0).required(),
+ queueLimit: Joi.number().integer().min(0).required(),
+ }).required(),
+ }).required(),
+ memory: Joi.object({
+ levelOne: Joi.object({
+ concurrency: Joi.number().integer().min(0).required(),
+ queueLimit: Joi.number().integer().min(0).required(),
+ maxOffset: Joi.number().integer().min(0).required(),
+ }).required(),
+ levelTwo: Joi.object({
+ concurrency: Joi.number().integer().min(0).required(),
+ queueLimit: Joi.number().integer().min(0).required(),
+ }).required(),
+ }).required(),
+ }).required(),
rateLimit: Joi.object({
enabled: Joi.bool().required(),
points: Joi.number().integer().min(0).required(),
diff --git a/packages/core-api/src/services/delegate-search-service.ts b/packages/core-api/src/services/delegate-search-service.ts
index 9f66b32c71..06bf5a0c91 100644
--- a/packages/core-api/src/services/delegate-search-service.ts
+++ b/packages/core-api/src/services/delegate-search-service.ts
@@ -8,6 +8,9 @@ export class DelegateSearchService {
@Container.tagged("state", "blockchain")
private readonly walletRepository!: Contracts.State.WalletRepository;
+ @Container.inject(Container.Identifiers.StateStore)
+ private readonly stateStore!: Contracts.State.StateStore;
+
@Container.inject(Container.Identifiers.StandardCriteriaService)
private readonly standardCriteriaService!: Services.Search.StandardCriteriaService;
@@ -24,12 +27,12 @@ export class DelegateSearchService {
}
}
- public getDelegatesPage(
+ public async getDelegatesPage(
pagination: Contracts.Search.Pagination,
sorting: Contracts.Search.Sorting,
...criterias: DelegateCriteria[]
- ): Contracts.Search.ResultsPage {
- sorting = [...sorting, { property: "rank", direction: "asc" }];
+ ): Promise> {
+ sorting = [...sorting, { property: "rank", direction: "asc" }, { property: "publicKey", direction: "asc" }];
return this.paginationService.getPage(pagination, sorting, this.getDelegates(...criterias));
}
@@ -61,7 +64,10 @@ export class DelegateSearchService {
last: delegateLastBlock,
},
production: {
- approval: AppUtils.delegateCalculator.calculateApproval(wallet),
+ approval: AppUtils.delegateCalculator.calculateApproval(
+ wallet,
+ this.stateStore.getLastBlock().data.height,
+ ),
},
forged: {
fees: delegateAttribute.forgedFees,
diff --git a/packages/core-api/src/services/lock-search-service.ts b/packages/core-api/src/services/lock-search-service.ts
index 123ab3408a..dc9055aece 100644
--- a/packages/core-api/src/services/lock-search-service.ts
+++ b/packages/core-api/src/services/lock-search-service.ts
@@ -27,22 +27,22 @@ export class LockSearchService {
}
}
- public getLocksPage(
+ public async getLocksPage(
pagination: Contracts.Search.Pagination,
sorting: Contracts.Search.Sorting,
...criterias: LockCriteria[]
- ): Contracts.Search.ResultsPage {
+ ): Promise> {
sorting = [...sorting, { property: "timestamp.unix", direction: "desc" }];
return this.paginationService.getPage(pagination, sorting, this.getLocks(...criterias));
}
- public getWalletLocksPage(
+ public async getWalletLocksPage(
pagination: Contracts.Search.Pagination,
sorting: Contracts.Search.Sorting,
walletAddress: string,
...criterias: LockCriteria[]
- ): Contracts.Search.ResultsPage {
+ ): Promise> {
sorting = [...sorting, { property: "timestamp.unix", direction: "desc" }];
return this.paginationService.getPage(pagination, sorting, this.getWalletLocks(walletAddress, ...criterias));
diff --git a/packages/core-api/src/services/wallet-search-service.ts b/packages/core-api/src/services/wallet-search-service.ts
index 96afd63074..3a4d313d2f 100644
--- a/packages/core-api/src/services/wallet-search-service.ts
+++ b/packages/core-api/src/services/wallet-search-service.ts
@@ -36,21 +36,21 @@ export class WalletSearchService {
}
}
- public getWalletsPage(
+ public async getWalletsPage(
pagination: Contracts.Search.Pagination,
sorting: Contracts.Search.Sorting,
...criterias: WalletCriteria[]
- ): Contracts.Search.ResultsPage {
+ ): Promise> {
sorting = [...sorting, { property: "balance", direction: "desc" }];
return this.paginationService.getPage(pagination, sorting, this.getWallets(...criterias));
}
- public getActiveWalletsPage(
+ public async getActiveWalletsPage(
pagination: Contracts.Search.Pagination,
sorting: Contracts.Search.Sorting,
...criterias: WalletCriteria[]
- ): Contracts.Search.ResultsPage {
+ ): Promise> {
sorting = [...sorting, { property: "balance", direction: "desc" }];
return this.paginationService.getPage(pagination, sorting, this.getActiveWallets(...criterias));
diff --git a/packages/core-blockchain/package.json b/packages/core-blockchain/package.json
index 64973c54c5..d15f73f0cb 100644
--- a/packages/core-blockchain/package.json
+++ b/packages/core-blockchain/package.json
@@ -1,6 +1,6 @@
{
"name": "@arkecosystem/core-blockchain",
- "version": "3.0.6",
+ "version": "3.12.0",
"description": "Blockchain Manager for ARK Core",
"license": "MIT",
"contributors": [
@@ -23,16 +23,13 @@
"pretest": "bash ../../scripts/pre-test.sh"
},
"dependencies": {
- "@arkecosystem/core-database": "3.0.6",
- "@arkecosystem/core-kernel": "3.0.6",
- "@arkecosystem/core-state": "3.0.6",
- "@arkecosystem/core-transactions": "3.0.6",
- "@arkecosystem/crypto": "3.0.6",
- "joi": "17.4.2",
- "xstate": "4.23.4"
- },
- "devDependencies": {
- "@types/async": "3.2.7"
+ "@arkecosystem/core-database": "3.12.0",
+ "@arkecosystem/core-kernel": "3.12.0",
+ "@arkecosystem/core-state": "3.12.0",
+ "@arkecosystem/core-transactions": "3.12.0",
+ "@arkecosystem/crypto": "3.12.0",
+ "joi": "17.12.1",
+ "xstate": "4.38.3"
},
"engines": {
"node": ">=10.x"
diff --git a/packages/core-blockchain/src/blockchain.ts b/packages/core-blockchain/src/blockchain.ts
index 5ee33c6e56..fff6115e31 100644
--- a/packages/core-blockchain/src/blockchain.ts
+++ b/packages/core-blockchain/src/blockchain.ts
@@ -27,6 +27,7 @@ export class Blockchain implements Contracts.Blockchain.Blockchain {
private readonly database!: DatabaseService;
@Container.inject(Container.Identifiers.DatabaseBlockRepository)
+ @Container.tagged("connection", "default")
private readonly blockRepository!: Repositories.BlockRepository;
@Container.inject(Container.Identifiers.TransactionPoolService)
@@ -160,9 +161,12 @@ export class Blockchain implements Contracts.Blockchain.Blockchain {
* Set wakeup timeout to check the network for new blocks.
*/
public setWakeUp(): void {
- this.stateStore.setWakeUpTimeout(() => {
- this.dispatch("WAKEUP");
- }, 60000);
+ this.stateStore.setWakeUpTimeout(
+ () => {
+ this.dispatch("WAKEUP");
+ },
+ this.configuration.getRequired("fastSync") ? 8000 : 60000,
+ );
}
/**
@@ -436,8 +440,11 @@ export class Blockchain implements Contracts.Blockchain.Blockchain {
block = block || this.getLastBlock().data;
+ const blockCount = this.configuration.getRequired("fastSync") ? 1 : 3;
+
return (
- Crypto.Slots.getTime() - block.timestamp < 3 * Managers.configManager.getMilestone(block.height).blocktime
+ Crypto.Slots.getTime() - block.timestamp <
+ blockCount * Managers.configManager.getMilestone(block.height).blocktime
);
}
diff --git a/packages/core-blockchain/src/defaults.ts b/packages/core-blockchain/src/defaults.ts
index d61c4d7989..64209d2dc7 100644
--- a/packages/core-blockchain/src/defaults.ts
+++ b/packages/core-blockchain/src/defaults.ts
@@ -1,4 +1,5 @@
export const defaults = {
+ fastSync: !!process.env.CORE_BLOCKCHAIN_FAST_SYNC, // Improves sync rate for readonly nodes, that have a p2p port closed to public. Such node doesn't broadcast data
databaseRollback: {
maxBlockRewind: 10000,
steps: 1000,
diff --git a/packages/core-blockchain/src/process-blocks-job.ts b/packages/core-blockchain/src/process-blocks-job.ts
index b483dc68e8..868c228846 100644
--- a/packages/core-blockchain/src/process-blocks-job.ts
+++ b/packages/core-blockchain/src/process-blocks-job.ts
@@ -25,6 +25,7 @@ export class ProcessBlocksJob implements Contracts.Kernel.QueueJob {
private readonly database!: DatabaseService;
@Container.inject(Container.Identifiers.DatabaseBlockRepository)
+ @Container.tagged("connection", "default")
private readonly blockRepository!: Repositories.BlockRepository;
@Container.inject(Container.Identifiers.DatabaseInteraction)
@@ -33,6 +34,9 @@ export class ProcessBlocksJob implements Contracts.Kernel.QueueJob {
@Container.inject(Container.Identifiers.PeerNetworkMonitor)
private readonly networkMonitor!: Contracts.P2P.NetworkMonitor;
+ @Container.inject(Container.Identifiers.TransactionPoolService)
+ private readonly transactionPool!: Contracts.TransactionPool.Service;
+
@Container.inject(Container.Identifiers.TriggerService)
private readonly triggers!: Services.Triggers.Triggers;
@@ -174,6 +178,13 @@ export class ProcessBlocksJob implements Contracts.Kernel.QueueJob {
this.blockchain.resetLastDownloadedBlock();
}
+ for (const block of acceptedBlocks) {
+ await this.transactionPool.applyBlock(block);
+ }
+ if (acceptedBlocks.length) {
+ await this.transactionPool.cleanUp();
+ }
+
return;
}
diff --git a/packages/core-blockchain/src/processor/block-processor.ts b/packages/core-blockchain/src/processor/block-processor.ts
index 5fdde32da9..29a6b26b94 100644
--- a/packages/core-blockchain/src/processor/block-processor.ts
+++ b/packages/core-blockchain/src/processor/block-processor.ts
@@ -35,6 +35,7 @@ export class BlockProcessor {
private readonly blockchain!: Contracts.Blockchain.Blockchain;
@Container.inject(Container.Identifiers.DatabaseTransactionRepository)
+ @Container.tagged("connection", "default")
private readonly transactionRepository!: Repositories.TransactionRepository;
@Container.inject(Container.Identifiers.WalletRepository)
@@ -220,6 +221,25 @@ export class BlockProcessor {
}
private async validateGenerator(block: Interfaces.IBlock): Promise {
+ const walletRepository = this.app.getTagged(
+ Container.Identifiers.WalletRepository,
+ "state",
+ "blockchain",
+ );
+
+ if (!walletRepository.hasByPublicKey(block.data.generatorPublicKey)) {
+ return false;
+ }
+
+ const generatorWallet: Contracts.State.Wallet = walletRepository.findByPublicKey(block.data.generatorPublicKey);
+
+ let generatorUsername: string;
+ try {
+ generatorUsername = generatorWallet.getAttribute("delegate.username");
+ } catch {
+ return false;
+ }
+
const blockTimeLookup = await AppUtils.forgingInfoCalculator.getBlockTimeLookup(this.app, block.data.height);
const roundInfo: Contracts.Shared.RoundInfo = AppUtils.roundCalculator.calculateRound(block.data.height);
@@ -235,20 +255,6 @@ export class BlockProcessor {
const forgingDelegate: Contracts.State.Wallet = delegates[forgingInfo.currentForger];
- const walletRepository = this.app.getTagged(
- Container.Identifiers.WalletRepository,
- "state",
- "blockchain",
- );
- const generatorWallet: Contracts.State.Wallet = walletRepository.findByPublicKey(block.data.generatorPublicKey);
-
- let generatorUsername: string;
- try {
- generatorUsername = generatorWallet.getAttribute("delegate.username");
- } catch {
- return false;
- }
-
if (!forgingDelegate) {
this.logger.debug(
`Could not decide if delegate ${generatorUsername} (${
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 e9695ff43c..78e9fac538 100644
--- a/packages/core-blockchain/src/processor/handlers/accept-block-handler.ts
+++ b/packages/core-blockchain/src/processor/handlers/accept-block-handler.ts
@@ -23,9 +23,6 @@ export class AcceptBlockHandler implements BlockHandler {
@Container.inject(Container.Identifiers.DatabaseInteraction)
private readonly databaseInteraction!: DatabaseInteraction;
- @Container.inject(Container.Identifiers.TransactionPoolService)
- private readonly transactionPool!: Contracts.TransactionPool.Service;
-
public async execute(block: Interfaces.IBlock): Promise {
try {
await this.databaseInteraction.applyBlock(block);
@@ -37,10 +34,6 @@ export class AcceptBlockHandler implements BlockHandler {
this.state.clearForkedBlock();
}
- for (const transaction of block.transactions) {
- await this.transactionPool.removeForgedTransaction(transaction);
- }
-
// Reset wake-up timer after chaining a block, since there's no need to
// wake up at all if blocks arrive periodically. Only wake up when there are
// no new blocks.
diff --git a/packages/core-blockchain/src/processor/handlers/revert-block-handler.ts b/packages/core-blockchain/src/processor/handlers/revert-block-handler.ts
index 29792be195..ed1d143b80 100644
--- a/packages/core-blockchain/src/processor/handlers/revert-block-handler.ts
+++ b/packages/core-blockchain/src/processor/handlers/revert-block-handler.ts
@@ -20,18 +20,10 @@ export class RevertBlockHandler implements BlockHandler {
@Container.inject(Container.Identifiers.DatabaseService)
private readonly database!: DatabaseService;
- @Container.inject(Container.Identifiers.TransactionPoolService)
- private readonly transactionPool!: Contracts.TransactionPool.Service;
-
public async execute(block: Interfaces.IBlock): Promise {
try {
await this.databaseInteraction.revertBlock(block);
- // TODO: Check if same situation applies to fork revert
- for (const transaction of block.transactions) {
- await this.transactionPool.addTransaction(transaction);
- }
-
// Remove last block, take from DB if list is empty
let previousBlock: Interfaces.IBlock | undefined = this.state
.getLastBlocks()
diff --git a/packages/core-blockchain/src/service-provider.ts b/packages/core-blockchain/src/service-provider.ts
index 5698bc1166..91a5ce8fd3 100644
--- a/packages/core-blockchain/src/service-provider.ts
+++ b/packages/core-blockchain/src/service-provider.ts
@@ -29,6 +29,7 @@ export class ServiceProvider extends Providers.ServiceProvider {
public configSchema(): object {
return Joi.object({
+ fastSync: Joi.bool().required(),
databaseRollback: Joi.object({
maxBlockRewind: Joi.number().integer().min(1).required(),
steps: Joi.number().integer().min(1).required(),
diff --git a/packages/core-cli/package.json b/packages/core-cli/package.json
index 8d5b452e8d..4e5286e63c 100644
--- a/packages/core-cli/package.json
+++ b/packages/core-cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@arkecosystem/core-cli",
- "version": "3.0.6",
+ "version": "3.12.0",
"description": "Core of the ARK Blockchain",
"license": "MIT",
"contributors": [
@@ -21,38 +21,40 @@
"prepublishOnly": "yarn build"
},
"dependencies": {
- "@arkecosystem/core-kernel": "3.0.6",
- "@arkecosystem/crypto": "3.0.6",
- "@arkecosystem/utils": "1.3.0",
+ "@arkecosystem/core-kernel": "3.12.0",
+ "@arkecosystem/crypto": "3.12.0",
+ "@arkecosystem/utils": "1.3.1",
"boxen": "4.2.0",
"cli-table3": "0.6.0",
- "dayjs": "1.10.7",
+ "dayjs": "1.11.10",
"env-paths": "2.2.0",
"envfile": "5.2.0",
"execa": "3.4.0",
"fast-levenshtein": "2.0.6",
"fs-extra": "8.1.0",
"glob": "7.1.7",
+ "got": "11.8.5",
"inversify": "5.1.1",
- "joi": "17.4.2",
- "kleur": "4.0.0",
+ "joi": "17.12.1",
+ "kleur": "4.1.5",
"latest-version": "5.1.0",
"listr": "0.14.3",
"nodejs-tail": "1.1.1",
"ora": "4.1.1",
"prompts": "2.4.0",
"read-last-lines": "1.8.0",
- "reflect-metadata": "0.1.13",
- "semver": "6.3.0",
+ "reflect-metadata": "0.2.1",
+ "semver": "7.5.4",
+ "tar": "6.2.0",
"type-fest": "0.21.3",
- "yargs-parser": "20.2.9"
+ "yargs-parser": "21.1.1"
},
"devDependencies": {
"@types/fast-levenshtein": "0.0.2",
"@types/fs-extra": "8.1.2",
"@types/is-ci": "2.0.0",
"@types/listr": "0.14.4",
- "@types/semver": "6.2.3",
+ "@types/semver": "7.5.6",
"@types/yargs-parser": "20.2.1"
},
"engines": {
diff --git a/packages/core-cli/src/application-factory.ts b/packages/core-cli/src/application-factory.ts
index a74d071c34..6dfdb6ceca 100644
--- a/packages/core-cli/src/application-factory.ts
+++ b/packages/core-cli/src/application-factory.ts
@@ -46,7 +46,7 @@ import {
import { Input, InputValidator } from "./input";
import { Container, Identifiers, interfaces } from "./ioc";
import { Output } from "./output";
-import { Config, Environment, Installer, Logger, ProcessManager, Updater } from "./services";
+import { Config, Environment, Installer, Logger, PluginManager, ProcessManager, Updater } from "./services";
import { Process } from "./utils";
export class ApplicationFactory {
@@ -65,12 +65,13 @@ export class ApplicationFactory {
app.bind(Identifiers.ComponentFactory).to(ComponentFactory).inSingletonScope();
app.bind(Identifiers.ProcessFactory).toFactory(
- (context: interfaces.Context) => (token: string, type: string): Process => {
- const process: Process = context.container.resolve(Process);
- process.initialize(token, type);
+ (context: interfaces.Context) =>
+ (token: string, type: string): Process => {
+ const process: Process = context.container.resolve(Process);
+ process.initialize(token, type);
- return process;
- },
+ return process;
+ },
);
// Services
@@ -84,6 +85,8 @@ export class ApplicationFactory {
app.bind(Identifiers.ProcessManager).to(ProcessManager).inSingletonScope();
+ app.bind(Identifiers.PluginManager).to(PluginManager).inSingletonScope();
+
app.bind(Identifiers.Installer).to(Installer).inSingletonScope();
app.bind(Identifiers.Environment).to(Environment).inSingletonScope();
diff --git a/packages/core-cli/src/commands/command.ts b/packages/core-cli/src/commands/command.ts
index 36446405ce..800030cf2b 100644
--- a/packages/core-cli/src/commands/command.ts
+++ b/packages/core-cli/src/commands/command.ts
@@ -326,5 +326,5 @@ export abstract class Command {
* @returns {Promise}
* @memberof Command
*/
- public abstract async execute(): Promise;
+ public abstract execute(): Promise;
}
diff --git a/packages/core-cli/src/commands/discover-plugins.ts b/packages/core-cli/src/commands/discover-plugins.ts
deleted file mode 100644
index 5392be47b9..0000000000
--- a/packages/core-cli/src/commands/discover-plugins.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import { readJSONSync } from "fs-extra";
-import { join } from "path";
-import glob from "glob";
-
-import { injectable } from "../ioc";
-
-interface Plugin {
- path: string;
- name: string;
- version: string;
-}
-
-/**
- * @export
- * @class DiscoverPlugins
- */
-@injectable()
-export class DiscoverPlugins {
- /**
- * @returns {Promise}
- * @memberof DiscoverPlugins
- * @param path
- */
- public async discover(path: string): Promise {
- const plugins: Plugin[] = [];
-
- const packagePaths = glob
- .sync("{*/*/package.json,*/package.json}", { cwd: path })
- .map((packagePath) => join(path, packagePath).slice(0, -"/package.json".length));
-
- for (let packagePath of packagePaths) {
- const packageJson = readJSONSync(join(packagePath, "package.json"));
-
- plugins.push({
- path: packagePath,
- name: packageJson.name,
- version: packageJson.version,
- });
- }
-
- return plugins;
- }
-}
diff --git a/packages/core-cli/src/commands/index.ts b/packages/core-cli/src/commands/index.ts
index 1c344c2afb..6f2fd27be2 100644
--- a/packages/core-cli/src/commands/index.ts
+++ b/packages/core-cli/src/commands/index.ts
@@ -3,4 +3,3 @@ export * from "./command";
export * from "./discover-commands";
export * from "./discover-config";
export * from "./discover-network";
-export * from "./discover-plugins";
diff --git a/packages/core-cli/src/contracts.ts b/packages/core-cli/src/contracts.ts
index 106cb0a197..e144fc1a60 100644
--- a/packages/core-cli/src/contracts.ts
+++ b/packages/core-cli/src/contracts.ts
@@ -55,6 +55,22 @@ export interface Installer {
installFromChannel(pkg: string, channel: string): void;
}
+export interface Plugin {
+ path: string;
+ name: string;
+ version: string;
+}
+
+export interface PluginManager {
+ list(token: string, network: string): Promise;
+
+ install(token: string, network: string, pkg: string, version?: string): Promise;
+
+ update(token: string, network: string, pkg: string): Promise;
+
+ remove(token: string, network: string, pkg: string): Promise;
+}
+
export enum ProcessState {
Online = "online",
Stopped = "stopped",
diff --git a/packages/core-cli/src/input/parser.ts b/packages/core-cli/src/input/parser.ts
index 7d59df1ae4..b072375fe6 100644
--- a/packages/core-cli/src/input/parser.ts
+++ b/packages/core-cli/src/input/parser.ts
@@ -11,11 +11,13 @@ export class InputParser {
* @returns
* @memberof InputParser
*/
- public static parseArgv(args: string[]) {
+ public static parseArgv(args: string[]): { args: string[]; flags: yargs.Arguments } {
const parsed: yargs.Arguments = yargs(args, { count: ["v"] });
- const argv: string[] = parsed._;
+ const argv = parsed._.map((argument) => argument.toString());
+
+ // @ts-ignore
delete parsed._;
return { args: argv, flags: parsed };
diff --git a/packages/core-cli/src/ioc/identifiers.ts b/packages/core-cli/src/ioc/identifiers.ts
index a054350d3e..c1677fb42a 100644
--- a/packages/core-cli/src/ioc/identifiers.ts
+++ b/packages/core-cli/src/ioc/identifiers.ts
@@ -11,6 +11,7 @@ export const Identifiers = {
Output: Symbol.for("Output"),
Package: Symbol.for("Package"),
ProcessManager: Symbol.for("ProcessManager"),
+ PluginManager: Symbol.for("PluginManager"),
Updater: Symbol.for("Updater"),
// Input
InputValidator: Symbol.for("Input"),
diff --git a/packages/core-cli/src/services/config.ts b/packages/core-cli/src/services/config.ts
index 5f18981a36..6949e97def 100644
--- a/packages/core-cli/src/services/config.ts
+++ b/packages/core-cli/src/services/config.ts
@@ -148,7 +148,7 @@ export class Config {
* @memberof Config
*/
private getRegistryChannel(version: string): string {
- const channels: string[] = ["next"];
+ const channels: string[] = ["next", "rc"];
let channel = "latest";
for (const item of channels) {
diff --git a/packages/core-cli/src/services/index.ts b/packages/core-cli/src/services/index.ts
index a67807a6e9..5a7a94fc65 100644
--- a/packages/core-cli/src/services/index.ts
+++ b/packages/core-cli/src/services/index.ts
@@ -2,5 +2,6 @@ export * from "./config";
export * from "./environment";
export * from "./installer";
export * from "./logger";
+export * from "./plugin-manager";
export * from "./process-manager";
export * from "./updater";
diff --git a/packages/core-cli/src/services/installer.ts b/packages/core-cli/src/services/installer.ts
index 528fd61173..d229314823 100644
--- a/packages/core-cli/src/services/installer.ts
+++ b/packages/core-cli/src/services/installer.ts
@@ -16,6 +16,12 @@ export class Installer {
public install(pkg: string, tag: string = "latest"): void {
this.installPeerDependencies(pkg, tag);
+ try {
+ sync(`rm -f "\`yarn global dir\`"/node_modules/better-sqlite3/build/Release/better_sqlite3.node`, {
+ shell: true,
+ });
+ } catch {}
+
const { stdout, stderr, exitCode } = sync(`yarn global add ${pkg}@${tag} --force`, { shell: true });
if (exitCode !== 0) {
diff --git a/packages/core-cli/src/services/plugin-manager.ts b/packages/core-cli/src/services/plugin-manager.ts
new file mode 100644
index 0000000000..a8a35efb35
--- /dev/null
+++ b/packages/core-cli/src/services/plugin-manager.ts
@@ -0,0 +1,87 @@
+import { existsSync, readJSONSync, removeSync } from "fs-extra";
+import glob from "glob";
+import { join } from "path";
+
+import * as Contracts from "../contracts";
+import { Identifiers, inject, injectable } from "../ioc";
+import { Environment } from "./environment";
+import { File, Git, NPM, Source } from "./source-providers";
+
+@injectable()
+export class PluginManager implements Contracts.PluginManager {
+ @inject(Identifiers.Environment)
+ private readonly environment!: Environment;
+
+ public async list(token: string, network: string): Promise {
+ const plugins: Contracts.Plugin[] = [];
+
+ const path = this.getPluginsPath(token, network);
+
+ const packagePaths = glob
+ .sync("{@*/*/package.json,*/package.json}", { cwd: path })
+ .map((packagePath) => join(path, packagePath).slice(0, -"/package.json".length));
+
+ for (const packagePath of packagePaths) {
+ const packageJson = readJSONSync(join(packagePath, "package.json"));
+
+ plugins.push({
+ path: packagePath,
+ name: packageJson.name,
+ version: packageJson.version,
+ });
+ }
+
+ return plugins;
+ }
+
+ public async install(token: string, network: string, pkg: string, version?: string): Promise {
+ for (const Instance of [File, Git, NPM]) {
+ const source: Source = new Instance({
+ data: this.getPluginsPath(token, network),
+ temp: this.getTempPath(token, network),
+ });
+
+ if (await source.exists(pkg, version)) {
+ return source.install(pkg, version);
+ }
+ }
+
+ throw new Error(`The given package [${pkg}] is neither a git nor a npm package.`);
+ }
+
+ public async update(token: string, network: string, pkg: string): Promise {
+ const paths = {
+ data: this.getPluginsPath(token, network),
+ temp: this.getTempPath(token, network),
+ };
+ const directory: string = join(paths.data, pkg);
+
+ if (!existsSync(directory)) {
+ throw new Error(`The package [${pkg}] does not exist.`);
+ }
+
+ if (existsSync(`${directory}/.git`)) {
+ return new Git(paths).update(pkg);
+ }
+
+ return new NPM(paths).update(pkg);
+ }
+
+ public async remove(token: string, network: string, pkg): Promise {
+ const directory: string = join(this.getPluginsPath(token, network), pkg);
+
+ if (!existsSync(directory)) {
+ throw new Error(`The package [${pkg}] does not exist.`);
+ }
+
+ removeSync(directory);
+ }
+
+ private getPluginsPath(token: string, network: string): string {
+ return join(this.environment.getPaths(token, network).data, "plugins");
+ }
+
+ private getTempPath(token: string, network: string): string {
+ return join(this.environment.getPaths(token, network).temp, "plugins");
+ }
+}
diff --git a/packages/core/src/source-providers/abstract-source.ts b/packages/core-cli/src/services/source-providers/abstract-source.ts
similarity index 87%
rename from packages/core/src/source-providers/abstract-source.ts
rename to packages/core-cli/src/services/source-providers/abstract-source.ts
index 50d655f97c..7eccff21ac 100644
--- a/packages/core/src/source-providers/abstract-source.ts
+++ b/packages/core-cli/src/services/source-providers/abstract-source.ts
@@ -57,9 +57,9 @@ export abstract class AbstractSource implements Source {
removeSync(this.getDestPath(packageName));
}
- public abstract async exists(value: string, version?: string): Promise;
+ public abstract exists(value: string, version?: string): Promise;
- public abstract async update(value: string): Promise;
+ public abstract update(value: string): Promise;
- protected abstract async preparePackage(value: string, version?: string): Promise;
+ protected abstract preparePackage(value: string, version?: string): Promise;
}
diff --git a/packages/core/src/source-providers/contracts.ts b/packages/core-cli/src/services/source-providers/contracts.ts
similarity index 100%
rename from packages/core/src/source-providers/contracts.ts
rename to packages/core-cli/src/services/source-providers/contracts.ts
diff --git a/packages/core/src/source-providers/errors.ts b/packages/core-cli/src/services/source-providers/errors.ts
similarity index 100%
rename from packages/core/src/source-providers/errors.ts
rename to packages/core-cli/src/services/source-providers/errors.ts
diff --git a/packages/core/src/source-providers/file.ts b/packages/core-cli/src/services/source-providers/file.ts
similarity index 100%
rename from packages/core/src/source-providers/file.ts
rename to packages/core-cli/src/services/source-providers/file.ts
diff --git a/packages/core/src/source-providers/git.ts b/packages/core-cli/src/services/source-providers/git.ts
similarity index 100%
rename from packages/core/src/source-providers/git.ts
rename to packages/core-cli/src/services/source-providers/git.ts
diff --git a/packages/core/src/source-providers/index.ts b/packages/core-cli/src/services/source-providers/index.ts
similarity index 100%
rename from packages/core/src/source-providers/index.ts
rename to packages/core-cli/src/services/source-providers/index.ts
diff --git a/packages/core/src/source-providers/npm.ts b/packages/core-cli/src/services/source-providers/npm.ts
similarity index 100%
rename from packages/core/src/source-providers/npm.ts
rename to packages/core-cli/src/services/source-providers/npm.ts
diff --git a/packages/core-database/package.json b/packages/core-database/package.json
index d2adc6e446..f3d375e2cf 100644
--- a/packages/core-database/package.json
+++ b/packages/core-database/package.json
@@ -1,6 +1,6 @@
{
"name": "@arkecosystem/core-database",
- "version": "3.0.6",
+ "version": "3.12.0",
"description": "Database Interface for ARK Core",
"license": "MIT",
"contributors": [
@@ -22,13 +22,13 @@
"pretest": "bash ../../scripts/pre-test.sh"
},
"dependencies": {
- "@arkecosystem/core-kernel": "3.0.6",
- "@arkecosystem/crypto": "3.0.6",
- "@arkecosystem/utils": "1.3.0",
- "dayjs": "1.10.7",
- "joi": "17.4.2",
- "pg": "8.7.1",
- "reflect-metadata": "0.1.13",
+ "@arkecosystem/core-kernel": "3.12.0",
+ "@arkecosystem/crypto": "3.12.0",
+ "@arkecosystem/utils": "1.3.1",
+ "dayjs": "1.11.10",
+ "joi": "17.12.1",
+ "pg": "8.11.3",
+ "reflect-metadata": "0.2.1",
"typeorm": "0.2.25"
},
"engines": {
diff --git a/packages/core-database/src/database-service.ts b/packages/core-database/src/database-service.ts
index ded2b88196..1a0b4597cb 100644
--- a/packages/core-database/src/database-service.ts
+++ b/packages/core-database/src/database-service.ts
@@ -15,15 +15,19 @@ export class DatabaseService {
private readonly app!: Contracts.Kernel.Application;
@Container.inject(Container.Identifiers.DatabaseConnection)
+ @Container.tagged("connection", "default")
private readonly connection!: Connection;
@Container.inject(Container.Identifiers.DatabaseBlockRepository)
+ @Container.tagged("connection", "default")
private readonly blockRepository!: BlockRepository;
@Container.inject(Container.Identifiers.DatabaseTransactionRepository)
+ @Container.tagged("connection", "default")
private readonly transactionRepository!: TransactionRepository;
@Container.inject(Container.Identifiers.DatabaseRoundRepository)
+ @Container.tagged("connection", "default")
private readonly roundRepository!: RoundRepository;
@Container.inject(Container.Identifiers.LogService)
@@ -37,6 +41,8 @@ export class DatabaseService {
if (process.env.CORE_RESET_DATABASE) {
await this.reset();
}
+ this.logger.info("Analyzing database");
+ await this.analyze();
} catch (error) {
this.logger.error(error.stack);
this.app.terminate("Failed to initialize database service.", error);
@@ -58,11 +64,15 @@ export class DatabaseService {
await this.connection.query("TRUNCATE TABLE blocks, rounds, transactions RESTART IDENTITY;");
}
+ public async analyze(): Promise {
+ await this.connection.query("ANALYZE;");
+ }
+
public async getBlock(id: string): Promise {
// TODO: caching the last 1000 blocks, in combination with `saveBlock` could help to optimise
- const block: Interfaces.IBlockData = ((await this.blockRepository.findOne(
+ const block: Interfaces.IBlockData = (await this.blockRepository.findOne(
id,
- )) as unknown) as Interfaces.IBlockData;
+ )) as unknown as Interfaces.IBlockData;
if (!block) {
return undefined;
@@ -101,16 +111,16 @@ export class DatabaseService {
// ! method is identical to getBlocks, but skips faster stateStore.getLastBlocksByHeight
if (headersOnly) {
- return (this.blockRepository.findByHeightRange(offset, offset + limit - 1) as unknown) as Promise<
+ return this.blockRepository.findByHeightRange(offset, offset + limit - 1) as unknown as Promise<
Contracts.Shared.DownloadBlock[]
>;
}
// TODO: fix types
- return (this.blockRepository.findByHeightRangeWithTransactionsForDownload(
+ return this.blockRepository.findByHeightRangeWithTransactionsForDownload(
offset,
offset + limit - 1,
- ) as unknown) as Promise;
+ ) as unknown as Promise;
}
public async findBlockByHeights(heights: number[]) {
@@ -146,9 +156,9 @@ export class DatabaseService {
public async getTopBlocks(count: number): Promise {
// ! blockRepository.findTop returns blocks in reverse order
// ! where recent block is first in array
- const blocks: Interfaces.IBlockData[] = ((await this.blockRepository.findTop(
+ const blocks: Interfaces.IBlockData[] = (await this.blockRepository.findTop(
count,
- )) as unknown) as Interfaces.IBlockData[];
+ )) as unknown as Interfaces.IBlockData[];
await this.loadTransactionsForBlocks(blocks);
@@ -172,7 +182,7 @@ export class DatabaseService {
}
public async findBlockByID(ids: any[]): Promise {
- return ((await this.blockRepository.findByIds(ids)) as unknown) as Interfaces.IBlockData[];
+ return (await this.blockRepository.findByIds(ids)) as unknown as Interfaces.IBlockData[];
}
public async findRecentBlocks(limit: number): Promise<{ id: string }[]> {
@@ -279,9 +289,7 @@ export class DatabaseService {
}
}
- private async getTransactionsForBlocks(
- blocks: Interfaces.IBlockData[],
- ): Promise<
+ private async getTransactionsForBlocks(blocks: Interfaces.IBlockData[]): Promise<
Array<{
id: string;
blockId: string;
diff --git a/packages/core-database/src/defaults.ts b/packages/core-database/src/defaults.ts
index 3cb8050073..7383ff427b 100644
--- a/packages/core-database/src/defaults.ts
+++ b/packages/core-database/src/defaults.ts
@@ -10,4 +10,5 @@ export const defaults = {
synchronize: false,
logging: false,
},
+ apiConnectionTimeout: 3000,
};
diff --git a/packages/core-database/src/migrations/20220606000000-disable-fastupdate-on-gin-indexes.ts b/packages/core-database/src/migrations/20220606000000-disable-fastupdate-on-gin-indexes.ts
new file mode 100644
index 0000000000..0c0c444d7a
--- /dev/null
+++ b/packages/core-database/src/migrations/20220606000000-disable-fastupdate-on-gin-indexes.ts
@@ -0,0 +1,21 @@
+import { MigrationInterface, QueryRunner } from "typeorm";
+
+export class DisableFastupdateOnGinIndexes20220606000000 implements MigrationInterface {
+ public async up(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(`
+ DROP INDEX IF EXISTS transactions_asset;
+ DROP INDEX IF EXISTS transactions_asset_payments;
+ CREATE INDEX transactions_asset ON transactions USING GIN(asset) WITH (fastupdate = off);
+ CREATE INDEX transactions_asset_payments ON transactions USING GIN ((asset->'payments')) WITH (fastupdate = off);
+ `);
+ }
+
+ public async down(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(`
+ DROP INDEX IF EXISTS transactions_asset;
+ DROP INDEX IF EXISTS transactions_asset_payments;
+ CREATE INDEX transactions_asset ON transactions USING GIN(asset);
+ CREATE INDEX transactions_asset_payments ON transactions USING GIN ((asset->'payments'));
+ `);
+ }
+}
diff --git a/packages/core-database/src/service-provider.ts b/packages/core-database/src/service-provider.ts
index 48ce24b005..2a7e200dca 100644
--- a/packages/core-database/src/service-provider.ts
+++ b/packages/core-database/src/service-provider.ts
@@ -1,6 +1,6 @@
import { Container, Contracts, Providers } from "@arkecosystem/core-kernel";
-import { Connection, createConnection, getCustomRepository } from "typeorm";
import Joi from "joi";
+import { Connection, createConnection, getCustomRepository } from "typeorm";
import { BlockFilter } from "./block-filter";
import { BlockHistoryService } from "./block-history-service";
@@ -19,19 +19,51 @@ export class ServiceProvider extends Providers.ServiceProvider {
logger.info("Connecting to database: " + (this.config().all().connection as any).database);
- this.app.bind(Container.Identifiers.DatabaseConnection).toConstantValue(await this.connect());
+ this.app
+ .bind(Container.Identifiers.DatabaseConnection)
+ .toConstantValue(await this.connect())
+ .when(Container.Selectors.anyAncestorOrTargetTaggedFirst("connection", "default"));
+ this.app
+ .bind(Container.Identifiers.DatabaseConnection)
+ .toConstantValue(
+ await this.connect("api", {
+ options: `-c statement_timeout=${this.config().getRequired("apiConnectionTimeout")}ms`,
+ }),
+ )
+ .when(Container.Selectors.anyAncestorOrTargetTaggedFirst("connection", "api"));
logger.debug("Connection established.");
- this.app.bind(Container.Identifiers.DatabaseRoundRepository).toConstantValue(this.getRoundRepository());
+ this.app
+ .bind(Container.Identifiers.DatabaseRoundRepository)
+ .toConstantValue(this.getRoundRepository())
+ .when(Container.Selectors.anyAncestorOrTargetTaggedFirst("connection", "default"));
+ this.app
+ .bind(Container.Identifiers.DatabaseRoundRepository)
+ .toConstantValue(this.getRoundRepository("api"))
+ .when(Container.Selectors.anyAncestorOrTargetTaggedFirst("connection", "api"));
+
+ this.app
+ .bind(Container.Identifiers.DatabaseBlockRepository)
+ .toConstantValue(this.getBlockRepository())
+ .when(Container.Selectors.anyAncestorOrTargetTaggedFirst("connection", "default"));
+ this.app
+ .bind(Container.Identifiers.DatabaseBlockRepository)
+ .toConstantValue(this.getBlockRepository("api"))
+ .when(Container.Selectors.anyAncestorOrTargetTaggedFirst("connection", "api"));
- this.app.bind(Container.Identifiers.DatabaseBlockRepository).toConstantValue(this.getBlockRepository());
this.app.bind(Container.Identifiers.DatabaseBlockFilter).to(BlockFilter);
this.app.bind(Container.Identifiers.BlockHistoryService).to(BlockHistoryService);
this.app
.bind(Container.Identifiers.DatabaseTransactionRepository)
- .toConstantValue(this.getTransactionRepository());
+ .toConstantValue(this.getTransactionRepository())
+ .when(Container.Selectors.anyAncestorOrTargetTaggedFirst("connection", "default"));
+ this.app
+ .bind(Container.Identifiers.DatabaseTransactionRepository)
+ .toConstantValue(this.getTransactionRepository("api"))
+ .when(Container.Selectors.anyAncestorOrTargetTaggedFirst("connection", "api"));
+
this.app.bind(Container.Identifiers.DatabaseTransactionFilter).to(TransactionFilter);
this.app.bind(Container.Identifiers.TransactionHistoryService).to(TransactionHistoryService);
@@ -52,7 +84,7 @@ export class ServiceProvider extends Providers.ServiceProvider {
return true;
}
- public async connect(): Promise {
+ public async connect(connectionName = "default", extra = {}): Promise {
const connection: Record = this.config().all().connection as any;
this.app
.get(Container.Identifiers.EventDispatcherService)
@@ -65,24 +97,26 @@ export class ServiceProvider extends Providers.ServiceProvider {
return createConnection({
...(connection as any),
+ name: connectionName,
namingStrategy: new SnakeNamingStrategy(),
migrations: [__dirname + "/migrations/*.js"],
- migrationsRun: true,
+ migrationsRun: connectionName === "default",
+ extra,
// TODO: expose entities to allow extending the models by plugins
entities: [__dirname + "/models/*.js"],
});
}
- public getRoundRepository(): RoundRepository {
- return getCustomRepository(RoundRepository);
+ public getRoundRepository(connectionName = "default"): RoundRepository {
+ return getCustomRepository(RoundRepository, connectionName);
}
- public getBlockRepository(): BlockRepository {
- return getCustomRepository(BlockRepository);
+ public getBlockRepository(connectionName = "default"): BlockRepository {
+ return getCustomRepository(BlockRepository, connectionName);
}
- public getTransactionRepository(): TransactionRepository {
- return getCustomRepository(TransactionRepository);
+ public getTransactionRepository(connectionName = "default"): TransactionRepository {
+ return getCustomRepository(TransactionRepository, connectionName);
}
public configSchema(): object {
@@ -98,6 +132,7 @@ export class ServiceProvider extends Providers.ServiceProvider {
synchronize: Joi.bool().required(),
logging: Joi.bool().required(),
}).required(),
+ apiConnectionTimeout: Joi.number().integer().min(1).required(),
}).unknown(true);
}
}
diff --git a/packages/core-database/src/transaction-filter.ts b/packages/core-database/src/transaction-filter.ts
index ef7c0e1c78..19d90adc9e 100644
--- a/packages/core-database/src/transaction-filter.ts
+++ b/packages/core-database/src/transaction-filter.ts
@@ -3,13 +3,8 @@ import { Enums } from "@arkecosystem/crypto";
import { Transaction } from "./models/transaction";
-const {
- handleAndCriteria,
- handleOrCriteria,
- handleNumericCriteria,
- optimizeExpression,
- hasOrCriteria,
-} = AppUtils.Search;
+const { handleAndCriteria, handleOrCriteria, handleNumericCriteria, optimizeExpression, hasOrCriteria } =
+ AppUtils.Search;
@Container.injectable()
export class TransactionFilter implements Contracts.Database.TransactionFilter {
@@ -82,7 +77,7 @@ export class TransactionFilter implements Contracts.Database.TransactionFilter {
});
case "vendorField":
return handleOrCriteria(criteria.vendorField!, async (c) => {
- return { property: "vendorField", op: "like", pattern: c };
+ return { property: "vendorField", op: "like", pattern: Buffer.from(c, "utf-8") };
});
case "amount":
return handleOrCriteria(criteria.amount!, async (c) => {
@@ -118,13 +113,15 @@ export class TransactionFilter implements Contracts.Database.TransactionFilter {
private async handleSenderIdCriteria(
criteria: Contracts.Search.EqualCriteria,
): Promise> {
- const senderWallet = this.walletRepository.findByAddress(criteria);
+ if (this.walletRepository.hasByAddress(criteria)) {
+ const senderWallet = this.walletRepository.findByAddress(criteria);
- if (senderWallet && senderWallet.getPublicKey()) {
- return { op: "equal", property: "senderPublicKey", value: senderWallet.getPublicKey() };
- } else {
- return { op: "false" };
+ if (senderWallet.getPublicKey()) {
+ return { op: "equal", property: "senderPublicKey", value: senderWallet.getPublicKey() };
+ }
}
+
+ return { op: "false" };
}
private async handleRecipientIdCriteria(
@@ -136,36 +133,16 @@ export class TransactionFilter implements Contracts.Database.TransactionFilter {
value: criteria,
};
- const multipaymentRecipientIdExpression: Contracts.Search.AndExpression = {
- op: "and",
- expressions: [
- { op: "equal", property: "typeGroup", value: Enums.TransactionTypeGroup.Core },
- { op: "equal", property: "type", value: Enums.TransactionType.MultiPayment },
- { op: "contains", property: "asset", value: { payments: [{ recipientId: criteria }] } },
- ],
+ const multipaymentRecipientIdExpression: Contracts.Search.ContainsExpression = {
+ op: "contains",
+ property: "asset",
+ value: { payments: [{ recipientId: criteria }] },
};
- const recipientWallet = this.walletRepository.findByAddress(criteria);
- if (recipientWallet && recipientWallet.getPublicKey()) {
- const delegateRegistrationExpression: Contracts.Search.AndExpression = {
- op: "and",
- expressions: [
- { op: "equal", property: "typeGroup", value: Enums.TransactionTypeGroup.Core },
- { op: "equal", property: "type", value: Enums.TransactionType.DelegateRegistration },
- { op: "equal", property: "senderPublicKey", value: recipientWallet.getPublicKey() },
- ],
- };
-
- return {
- op: "or",
- expressions: [recipientIdExpression, multipaymentRecipientIdExpression, delegateRegistrationExpression],
- };
- } else {
- return {
- op: "or",
- expressions: [recipientIdExpression, multipaymentRecipientIdExpression],
- };
- }
+ return {
+ op: "or",
+ expressions: [recipientIdExpression, multipaymentRecipientIdExpression],
+ };
}
private async handleAssetCriteria(
diff --git a/packages/core-database/src/wallets-table-service.ts b/packages/core-database/src/wallets-table-service.ts
index 2ef739b996..10c5e040f3 100644
--- a/packages/core-database/src/wallets-table-service.ts
+++ b/packages/core-database/src/wallets-table-service.ts
@@ -4,6 +4,7 @@ import { Connection } from "typeorm";
@Container.injectable()
export class WalletsTableService implements Contracts.Database.WalletsTableService {
@Container.inject(Container.Identifiers.DatabaseConnection)
+ @Container.tagged("connection", "default")
private readonly connection!: Connection;
public async flush(): Promise {
@@ -38,7 +39,13 @@ export class WalletsTableService implements Contracts.Database.WalletsTableServi
const batchWallets = wallets.slice(i, i + batchSize);
const params = batchWallets
- .map((w) => [w.getAddress(), w.getPublicKey(), w.getBalance().toFixed(), w.getNonce().toFixed(), w.getAttributes()])
+ .map((w) => [
+ w.getAddress(),
+ w.getPublicKey(),
+ w.getBalance().toFixed(),
+ w.getNonce().toFixed(),
+ w.getAttributes(),
+ ])
.flat();
const values = batchWallets
diff --git a/packages/core-forger/package.json b/packages/core-forger/package.json
index 74cf0b8f4a..2096838890 100644
--- a/packages/core-forger/package.json
+++ b/packages/core-forger/package.json
@@ -1,6 +1,6 @@
{
"name": "@arkecosystem/core-forger",
- "version": "3.0.6",
+ "version": "3.12.0",
"description": "Forger for ARK Core",
"license": "MIT",
"contributors": [
@@ -22,16 +22,16 @@
"pretest": "bash ../../scripts/pre-test.sh"
},
"dependencies": {
- "@arkecosystem/core-kernel": "3.0.6",
- "@arkecosystem/core-p2p": "3.0.6",
- "@arkecosystem/core-transactions": "3.0.6",
- "@arkecosystem/crypto": "3.0.6",
- "joi": "17.4.2",
- "node-forge": "0.10.0",
+ "@arkecosystem/core-kernel": "3.12.0",
+ "@arkecosystem/core-p2p": "3.12.0",
+ "@arkecosystem/core-transactions": "3.12.0",
+ "@arkecosystem/crypto": "3.12.0",
+ "joi": "17.12.1",
+ "node-forge": "1.3.1",
"wif": "2.0.6"
},
"devDependencies": {
- "@types/node-forge": "0.10.4",
+ "@types/node-forge": "1.3.11",
"@types/wif": "2.0.2"
},
"engines": {
diff --git a/packages/core-kernel/package.json b/packages/core-kernel/package.json
index c86c7b01c3..f9da7baae4 100644
--- a/packages/core-kernel/package.json
+++ b/packages/core-kernel/package.json
@@ -1,7 +1,7 @@
{
"name": "@arkecosystem/core-kernel",
"description": "Kernel of ARK Core",
- "version": "3.0.6",
+ "version": "3.12.0",
"contributors": [
"Brian Faust "
],
@@ -21,35 +21,35 @@
"clean": "rimraf dist"
},
"dependencies": {
- "@arkecosystem/crypto": "3.0.6",
- "@arkecosystem/utils": "1.3.0",
- "@pm2/io": "4.3.5",
+ "@arkecosystem/crypto": "3.12.0",
+ "@arkecosystem/utils": "1.3.1",
+ "@pm2/io": "5.0.2",
"chalk": "4.1.2",
"cron": "1.8.2",
- "dayjs": "1.10.7",
- "deepmerge": "4.2.2",
+ "dayjs": "1.11.10",
+ "deepmerge": "4.3.1",
"env-paths": "2.2.0",
"fs-extra": "8.1.0",
"functional-red-black-tree": "1.0.1",
"glob": "7.1.7",
"import-fresh": "3.3.0",
"inversify": "5.1.1",
- "ipaddr.js": "2.0.1",
- "joi": "17.4.2",
+ "ipaddr.js": "2.1.0",
+ "joi": "17.12.1",
"log-process-errors": "5.1.2",
"nanomatch": "1.2.13",
- "nsfw": "2.1.2",
- "reflect-metadata": "0.1.13",
- "semver": "6.3.0",
+ "nsfw": "2.2.4",
+ "reflect-metadata": "0.2.1",
+ "semver": "7.5.4",
"type-fest": "0.21.3"
},
"devDependencies": {
"@types/cron": "1.7.3",
"@types/fs-extra": "8.1.2",
- "@types/functional-red-black-tree": "1.0.1",
+ "@types/functional-red-black-tree": "1.0.6",
"@types/got": "9.6.12",
"@types/log-process-errors": "4.1.0",
- "@types/semver": "6.2.3"
+ "@types/semver": "7.5.6"
},
"publishConfig": {
"access": "public"
diff --git a/packages/core-kernel/src/contracts/index.ts b/packages/core-kernel/src/contracts/index.ts
index 43d5205f14..3f1c7daccf 100644
--- a/packages/core-kernel/src/contracts/index.ts
+++ b/packages/core-kernel/src/contracts/index.ts
@@ -7,3 +7,4 @@ export * as Shared from "./shared";
export * as Snapshot from "./snapshot";
export * as State from "./state";
export * as TransactionPool from "./transaction-pool";
+export * as Transactions from "./transactions";
diff --git a/packages/core-kernel/src/contracts/kernel/log.ts b/packages/core-kernel/src/contracts/kernel/log.ts
index f4c436b344..080866e892 100644
--- a/packages/core-kernel/src/contracts/kernel/log.ts
+++ b/packages/core-kernel/src/contracts/kernel/log.ts
@@ -96,6 +96,14 @@ export interface Logger {
*/
suppressConsoleOutput(suppress: boolean): void;
+ /**
+ * Dispose logger.
+ *
+ * @returns {Promise}
+ * @memberof Logger
+ */
+ dispose(): Promise;
+
// /**
// * @param {Record} levels
// * @memberof Logger
diff --git a/packages/core-kernel/src/contracts/p2p/peer-processor.ts b/packages/core-kernel/src/contracts/p2p/peer-processor.ts
index 3ab104d18a..312da0eae0 100644
--- a/packages/core-kernel/src/contracts/p2p/peer-processor.ts
+++ b/packages/core-kernel/src/contracts/p2p/peer-processor.ts
@@ -1,5 +1,9 @@
import { Peer } from "./peer";
+export interface Headers {
+ version?: string;
+}
+
export interface AcceptNewPeerOptions {
seed?: boolean;
lessVerbose?: boolean;
@@ -8,7 +12,7 @@ export interface AcceptNewPeerOptions {
export interface PeerProcessor {
initialize();
- validateAndAcceptPeer(peer: Peer, options?: AcceptNewPeerOptions): Promise;
+ validateAndAcceptPeer(peer: Peer, headers: Headers, options?: AcceptNewPeerOptions): Promise;
validatePeerIp(peer, options?: AcceptNewPeerOptions): boolean;
diff --git a/packages/core-kernel/src/contracts/p2p/peer-verifier.ts b/packages/core-kernel/src/contracts/p2p/peer-verifier.ts
index 40876e1931..d267a82e67 100644
--- a/packages/core-kernel/src/contracts/p2p/peer-verifier.ts
+++ b/packages/core-kernel/src/contracts/p2p/peer-verifier.ts
@@ -1,7 +1,9 @@
-import { Peer, PeerState, PeerVerificationResult } from "./peer";
+import { FastPeerVerificationResult, Peer, PeerState, PeerVerificationResult } from "./peer";
export interface PeerVerifier {
initialize(peer: Peer);
checkState(claimedState: PeerState, deadline: number): Promise;
+
+ checkStateFast(claimedState: PeerState, deadline: number): Promise;
}
diff --git a/packages/core-kernel/src/contracts/p2p/peer.ts b/packages/core-kernel/src/contracts/p2p/peer.ts
index 0617c04b27..1706e21e79 100644
--- a/packages/core-kernel/src/contracts/p2p/peer.ts
+++ b/packages/core-kernel/src/contracts/p2p/peer.ts
@@ -23,6 +23,7 @@ export interface Peer {
lastPinged: Dayjs | undefined;
sequentialErrorCounter: number;
verificationResult: PeerVerificationResult | undefined;
+ fastVerificationResult: FastPeerVerificationResult | undefined;
isVerified(): boolean;
isForked(): boolean;
@@ -74,3 +75,9 @@ export interface PeerVerificationResult {
readonly highestCommonHeight: number;
readonly forked: boolean;
}
+
+export interface FastPeerVerificationResult {
+ readonly myHeight: number;
+ readonly hisHeight: number;
+ readonly forked: boolean;
+}
diff --git a/packages/core-kernel/src/contracts/transaction-pool/index.ts b/packages/core-kernel/src/contracts/transaction-pool/index.ts
index 2189f24335..60ebb1cfb7 100644
--- a/packages/core-kernel/src/contracts/transaction-pool/index.ts
+++ b/packages/core-kernel/src/contracts/transaction-pool/index.ts
@@ -2,7 +2,8 @@ export * from "./collator";
export * from "./dynamic-fee-matcher";
export * from "./errors";
export * from "./expiration-service";
-export * from "./worker";
+export * from "./mempool-index-registry";
+export * from "./mempool-index";
export * from "./mempool";
export * from "./processor";
export * from "./query";
@@ -10,3 +11,4 @@ export * from "./sender-mempool";
export * from "./sender-state";
export * from "./service";
export * from "./storage";
+export * from "./worker";
diff --git a/packages/core-kernel/src/contracts/transaction-pool/mempool-index-registry.ts b/packages/core-kernel/src/contracts/transaction-pool/mempool-index-registry.ts
new file mode 100644
index 0000000000..1efe855802
--- /dev/null
+++ b/packages/core-kernel/src/contracts/transaction-pool/mempool-index-registry.ts
@@ -0,0 +1,6 @@
+import { MempoolIndex } from "./mempool-index";
+
+export interface MempoolIndexRegistry {
+ get(indexName: string): MempoolIndex;
+ clear(): void;
+}
diff --git a/packages/core-kernel/src/contracts/transaction-pool/mempool-index.ts b/packages/core-kernel/src/contracts/transaction-pool/mempool-index.ts
new file mode 100644
index 0000000000..1e923a504d
--- /dev/null
+++ b/packages/core-kernel/src/contracts/transaction-pool/mempool-index.ts
@@ -0,0 +1,9 @@
+import { Interfaces } from "@arkecosystem/crypto";
+
+export interface MempoolIndex {
+ set(key: string, transaction: Interfaces.ITransaction): void;
+ has(key: string): boolean;
+ get(key: string): Interfaces.ITransaction;
+ forget(key: string): void;
+ clear(): void;
+}
diff --git a/packages/core-kernel/src/contracts/transaction-pool/mempool.ts b/packages/core-kernel/src/contracts/transaction-pool/mempool.ts
index 757ba754b2..60dfe3b656 100644
--- a/packages/core-kernel/src/contracts/transaction-pool/mempool.ts
+++ b/packages/core-kernel/src/contracts/transaction-pool/mempool.ts
@@ -9,9 +9,9 @@ export interface Mempool {
getSenderMempool(senderPublicKey: string): SenderMempool;
getSenderMempools(): Iterable;
+ applyBlock(block: Interfaces.IBlock): Promise;
addTransaction(transaction: Interfaces.ITransaction): Promise;
removeTransaction(senderPublicKey: string, id: string): Promise;
- removeForgedTransaction(senderPublicKey: string, id: string): Promise;
flush(): void;
}
diff --git a/packages/core-kernel/src/contracts/transaction-pool/sender-mempool.ts b/packages/core-kernel/src/contracts/transaction-pool/sender-mempool.ts
index 82373521b8..c7a7c9ea4c 100644
--- a/packages/core-kernel/src/contracts/transaction-pool/sender-mempool.ts
+++ b/packages/core-kernel/src/contracts/transaction-pool/sender-mempool.ts
@@ -9,7 +9,7 @@ export interface SenderMempool {
addTransaction(transaction: Interfaces.ITransaction): Promise;
removeTransaction(id: string): Promise;
- removeForgedTransaction(id: string): Promise;
+ removeForgedTransaction(id: string): Promise;
}
export type SenderMempoolFactory = () => SenderMempool;
diff --git a/packages/core-kernel/src/contracts/transaction-pool/service.ts b/packages/core-kernel/src/contracts/transaction-pool/service.ts
index feaac83c4c..52d76aa40d 100644
--- a/packages/core-kernel/src/contracts/transaction-pool/service.ts
+++ b/packages/core-kernel/src/contracts/transaction-pool/service.ts
@@ -3,10 +3,10 @@ import { Interfaces } from "@arkecosystem/crypto";
export interface Service {
getPoolSize(): number;
+ applyBlock(block: Interfaces.IBlock): Promise;
addTransaction(transaction: Interfaces.ITransaction): Promise;
readdTransactions(previouslyForgedTransactions?: Interfaces.ITransaction[]): Promise;
removeTransaction(transaction: Interfaces.ITransaction): Promise;
- removeForgedTransaction(transaction: Interfaces.ITransaction): Promise;
cleanUp(): Promise;
flush(): Promise;
}
diff --git a/packages/core-kernel/src/contracts/transaction-pool/worker.ts b/packages/core-kernel/src/contracts/transaction-pool/worker.ts
index bdb38e1388..a52a7cf0b0 100644
--- a/packages/core-kernel/src/contracts/transaction-pool/worker.ts
+++ b/packages/core-kernel/src/contracts/transaction-pool/worker.ts
@@ -12,7 +12,7 @@ export interface WorkerScriptHandler {
loadCryptoPackage(packageName: string): void;
setConfig(networkConfig: any): void;
setHeight(height: number): void;
- getTransactionFromData(transactionData: Interfaces.ITransactionData | Buffer): Promise;
+ getTransactionFromData(transactionData: Interfaces.ITransactionData | string): Promise;
}
export type WorkerIpcSubprocess = IpcSubprocess;
diff --git a/packages/core-kernel/src/contracts/transactions/index.ts b/packages/core-kernel/src/contracts/transactions/index.ts
new file mode 100644
index 0000000000..b61ab99a2b
--- /dev/null
+++ b/packages/core-kernel/src/contracts/transactions/index.ts
@@ -0,0 +1 @@
+export * from "./verification";
diff --git a/packages/core-kernel/src/contracts/transactions/verification.ts b/packages/core-kernel/src/contracts/transactions/verification.ts
new file mode 100644
index 0000000000..1587bfce0a
--- /dev/null
+++ b/packages/core-kernel/src/contracts/transactions/verification.ts
@@ -0,0 +1,16 @@
+import { Interfaces } from "@arkecosystem/crypto";
+
+export interface SecondSignatureVerification {
+ verifySecondSignature(transaction: Interfaces.ITransactionData, publicKey: string): boolean;
+
+ clear(transactionId: string): void;
+}
+
+export interface MultiSignatureVerification {
+ verifySignatures(
+ transaction: Interfaces.ITransactionData,
+ multiSignatureAsset: Interfaces.IMultiSignatureAsset,
+ ): boolean;
+
+ clear(transactionId: string): void;
+}
diff --git a/packages/core-kernel/src/ioc/identifiers.ts b/packages/core-kernel/src/ioc/identifiers.ts
index 61f61519da..7f6c87a96d 100644
--- a/packages/core-kernel/src/ioc/identifiers.ts
+++ b/packages/core-kernel/src/ioc/identifiers.ts
@@ -104,6 +104,8 @@ export const Identifiers = {
TransactionPoolService: Symbol.for("TransactionPool"),
TransactionPoolCleaner: Symbol.for("TransactionPool"),
TransactionPoolMempool: Symbol.for("TransactionPool"),
+ TransactionPoolMempoolIndex: Symbol.for("TransactionPool"),
+ TransactionPoolMempoolIndexRegistry: Symbol.for("TransactionPool"),
TransactionPoolStorage: Symbol.for("TransactionPool"),
TransactionPoolCollator: Symbol.for("TransactionPool"),
TransactionPoolQuery: Symbol.for("TransactionPool"),
@@ -128,6 +130,9 @@ export const Identifiers = {
// Registries
TransactionHandlerRegistry: Symbol.for("Registry"),
TransactionHandlerProvider: Symbol.for("Provider"),
+ // Verification
+ TransactionSecondSignatureVerification: Symbol.for("Transaction"),
+ TransactionMultiSignatureVerification: Symbol.for("Transaction