diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index f3c260f26..94029868a 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -13,8 +13,8 @@ on: - 'master' types: [opened, synchronize, reopened, closed] jobs: - build_and_test: - if: ${{ github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'edited') }} + build_and_unit_test: + if: ${{ github.event_name == 'pull_request' && github.event.action == 'opened' }} strategy: matrix: os: [ubuntu-latest, macos-latest] @@ -32,46 +32,6 @@ jobs: run: yarn install - name: run unittest run: yarn run test_unit - # integration test only run when master merging - - name: run blockchain integration test - if: github.event.action == 'opened' && github.event.pull_request.base.ref == 'master' - run: yarn run test_integration_blockchain - - name: run consensus integration test - if: github.event.action == 'opened' && github.event.pull_request.base.ref == 'master' - run: yarn run test_integration_consensus - - name: run dapp integration test - if: github.event.action == 'opened' && github.event.pull_request.base.ref == 'master' - run: yarn run test_integration_dapp - - name: run node integration test - if: github.event.action == 'opened' && github.event.pull_request.base.ref == 'master' - run: yarn run test_integration_node - - name: run sharding integration test - if: github.event.action == 'opened' && github.event.pull_request.base.ref == 'master' - run: yarn run test_integration_sharding - check_protocol_version: - if: ${{ github.event_name == 'push' }} - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - - name: test - if: github.event_name == 'push' - run: echo ${{github.event_name}} - - name: get current version - run: echo "VERSION=$(cat package.json | jq -r '.version')" >> $GITHUB_ENV - - name: get min/max versions - run: | - echo "MIN_VERSION=$(cat client/protocol_versions.json | jq -r --arg var "$VERSION" '.[$var].min')" >> $GITHUB_ENV - echo "MAX_VERSION=$(cat client/protocol_versions.json | jq -r --arg var "$VERSION" '.[$var].max')" >> $GITHUB_ENV - - name: send results - env: - SLACK_WEBHOOK_TOKEN: ${{ secrets.SLACK_WEBHOOK_TOKEN }} - run: | - curl -X POST https://hooks.slack.com/services/$SLACK_WEBHOOK_TOKEN \ - -H "Content-Type: application/json" \ - -d '{"username": "AIN-BLOCKCHAIN", - "channel": "blockchain-testnet-deploy", - "text": "New PR has just been merged(${{ github.ref }}, ${{ github.sha }}).\nCurrent version: '"$VERSION"', compatible with min('"$MIN_VERSION"'), max('"$MAX_VERSION"')", - "icon_emoji": ":gem:"}' performance_test: if: github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged && github.event.pull_request.base.ref == 'develop' runs-on: ubuntu-latest @@ -83,44 +43,4 @@ jobs: - name: send test start message to gcp run: |- gcloud compute ssh "${{ secrets.PERF_TEST_PIPELINE_GCE_INSTANCE }}" --zone "${{ secrets.PERF_TEST_PIPELINE_GCE_INSTANCE_ZONE }}" -- "cd ~/../workspace/testnet-performance-test-pipeline && nohup node start_performance_test.js ${{ secrets.PERF_TEST_PIPELINE_TEST_SEASON }} ${{ secrets.PERF_TEST_PIPELINE_TEST_BRANCH }} ${{ github.event.pull_request.head.ref }} >> test_log.txt 2>&1 &" & - sleep 60 - check_deployment: - if: github.event.pull_request.base.ref == 'master' - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: '16.x' - registry-url: 'https://registry.npmjs.org' - - name: setup key - env: - PRIV_KEY_FOR_DOWNLOAD: ${{ secrets.DEPLOYMENT_PRIV_KEY_FOR_DOWNLOAD }} - KNOWN_HOSTS: ${{ secrets.DEPLOYMENT_KNOWN_HOSTS }} - run: | - echo "$PRIV_KEY_FOR_DOWNLOAD" > ./tools/cicd/id_rsa - chmod 600 ./tools/cicd/id_rsa - echo $KNOWN_HOSTS >> ~/.ssh/known_hosts - - name: check apps - env: - ENV_ID: ${{ secrets.DEPLOYMENT_ENV_ID }} - CLIENT_EMAIL: ${{ secrets.DEPLOYMENT_CLIENT_EMAIL }} - PRIVATE_KEY: ${{ secrets.DEPLOYMENT_PRIVATE_KEY}} - GPT2: ${{ secrets.DEPLOYMENT_GPT2 }} - INSIGHT: ${{ secrets.DEPLOYMENT_INSIGHT }} - FAUCET: ${{ secrets.DEPLOYMENT_FAUCET }} - PIPELINE: ${{ secrets.DEPLOYMENT_PIPELINE }} - DATA: ${{ secrets.DEPLOYMENT_DATA }} - run: | - yarn add dotenv google-spreadsheet semver - node tools/cicd/deployment.js - - name: send slack message - env: - SLACK_WEBHOOK_TOKEN: ${{ secrets.SLACK_WEBHOOK_TOKEN }} - run: | - curl -X POST https://hooks.slack.com/services/$SLACK_WEBHOOK_TOKEN \ - -H "Content-Type: application/json" \ - -d '{"username": "APP_VERSION_CHECK", - "channel": "blockchain-testnet-deploy", - "text": "New blockchain version has just been released. Please check compatibility of app versions below:\n ${{ secrets.DEPLOYMENT_SHEET_URL }}", - "icon_emoji": ":bomb:"}' + sleep 60 \ No newline at end of file diff --git a/README.md b/README.md index 896a6a993..ec9707cd2 100644 --- a/README.md +++ b/README.md @@ -152,21 +152,21 @@ bash start_node_genesis_gcp.sh {dev|spring|summer} - Pull Docker image from [Docker Hub](https://hub.docker.com/repository/docker/ainblockchain/ain-blockchain) ``` -docker pull ainblockchain/ain-blockchain:dev -docker pull ainblockchain/ain-blockchain:dev-1.0.6 -docker pull ainblockchain/ain-blockchain:{mainnet|summer|spring|sandbox|staging|exp|dev}- +docker pull ainblockchain/ain-blockchain:latest +docker pull ainblockchain/ain-blockchain: ``` - Or build Docker image yourself ``` -docker build -t ain-blockchain --build-arg SEASON={mainnet|summer|spring|sandbox|staging|exp|dev} . +docker build -t ain-blockchain . ``` - Run with Docker image example ``` -docker run -e ACCOUNT_INJECTION_OPTION=private_key -e SYNC_MODE=peer -e STAKE=10000 --network="host" -d ainblockchain/ain-blockchain:dev -docker run -e ACCOUNT_INJECTION_OPTION=keystore -e SYNC_MODE=peer -e STAKE=10000 --network="host" -d ainblockchain/ain-blockchain:mainnet +docker run -e ACCOUNT_INJECTION_OPTION=private_key -e SYNC_MODE=peer -e STAKE=10000 -e SEASON=dev --network="host" -d ainblockchain/ain-blockchain:latest +docker run -e ACCOUNT_INJECTION_OPTION=keystore -e SYNC_MODE=peer -e STAKE=10000 -e SEASON=mainnet --network="host" -d ainblockchain/ain-blockchain:latest ``` You can use some environment variables, and these have the following options. ``` +-e SEASON={mainnet|summer|spring|sandbox|staging|exp|dev} -e ACCOUNT_INJECTION_OPTION={private_key|keystore|mnemonic} -e SYNC_MODE={fast|full|peer} -e STAKE= diff --git a/blockchain-configs/afan-shard/node_params.json b/blockchain-configs/afan-shard/node_params.json index 557b7e207..7c34fee9a 100644 --- a/blockchain-configs/afan-shard/node_params.json +++ b/blockchain-configs/afan-shard/node_params.json @@ -30,14 +30,18 @@ "ENABLE_TX_SIG_VERIF_WORKAROUND": false, "EVENT_HANDLER_PORT": 6000, "EVENT_HANDLER_HEARTBEAT_INTERVAL_MS": 15000, + "EXPRESS_RATE_LIMIT_WINDOW_SECS": 60, "FREE_TX_POOL_SIZE_LIMIT_RATIO": 0.1, "FREE_TX_POOL_SIZE_LIMIT_RATIO_PER_ACCOUNT": 0.1, "GET_OP_LIST_SIZE_LIMIT": 50, - "GET_RESP_BYTES_LIMIT": 200000000, - "GET_RESP_MAX_SIBLINGS": 50000, + "GET_RESP_BYTES_LIMIT": 10000000, + "GET_RESP_MAX_SIBLINGS": 1000, "HOSTING_ENV": "local", "LIGHTWEIGHT": false, + "MAX_BLOCKCHAIN_API_RATE_LIMIT": 20, "MAX_FINALIZED_BLOCK_INFO_ON_MEM": 1000, + "MAX_JSON_RPC_API_READ_RATE_LIMIT": 10, + "MAX_JSON_RPC_API_WRITE_RATE_LIMIT": 1, "MAX_NUM_EVENT_CHANNELS": 10, "MAX_NUM_EVENT_FILTERS": 20, "MAX_NUM_EVENT_FILTERS_PER_CHANNEL": 5, @@ -58,7 +62,7 @@ "PEER_WHITELIST": "*", "PORT": 8080, "REST_FUNCTION_CALL_TIMEOUT_MS": 10000, - "REQUEST_BODY_SIZE_LIMIT": "100mb", + "REQUEST_BODY_SIZE_LIMIT": "100kb", "SEND_SNAPSHOT_CHUNK_SLEEP_TIME_MS": 1000, "SNAPSHOTS_INTERVAL_BLOCK_NUMBER": 1000, "SYNC_MODE": "fast", diff --git a/blockchain-configs/base/node_params.json b/blockchain-configs/base/node_params.json index 838fcf42e..f39b47d42 100644 --- a/blockchain-configs/base/node_params.json +++ b/blockchain-configs/base/node_params.json @@ -30,14 +30,18 @@ "ENABLE_TX_SIG_VERIF_WORKAROUND": false, "EVENT_HANDLER_PORT": 6000, "EVENT_HANDLER_HEARTBEAT_INTERVAL_MS": 15000, + "EXPRESS_RATE_LIMIT_WINDOW_SECS": 60, "FREE_TX_POOL_SIZE_LIMIT_RATIO": 0.1, "FREE_TX_POOL_SIZE_LIMIT_RATIO_PER_ACCOUNT": 0.1, "GET_OP_LIST_SIZE_LIMIT": 50, - "GET_RESP_BYTES_LIMIT": 200000000, - "GET_RESP_MAX_SIBLINGS": 50000, + "GET_RESP_BYTES_LIMIT": 10000000, + "GET_RESP_MAX_SIBLINGS": 1000, "HOSTING_ENV": "local", "LIGHTWEIGHT": false, + "MAX_BLOCKCHAIN_API_RATE_LIMIT": 20, "MAX_FINALIZED_BLOCK_INFO_ON_MEM": 1000, + "MAX_JSON_RPC_API_READ_RATE_LIMIT": 10, + "MAX_JSON_RPC_API_WRITE_RATE_LIMIT": 1, "MAX_NUM_EVENT_CHANNELS": 10, "MAX_NUM_EVENT_FILTERS": 20, "MAX_NUM_EVENT_FILTERS_PER_CHANNEL": 5, @@ -58,7 +62,7 @@ "PEER_WHITELIST": "*", "PORT": 8080, "REST_FUNCTION_CALL_TIMEOUT_MS": 10000, - "REQUEST_BODY_SIZE_LIMIT": "100mb", + "REQUEST_BODY_SIZE_LIMIT": "100kb", "SEND_SNAPSHOT_CHUNK_SLEEP_TIME_MS": 1000, "SNAPSHOTS_INTERVAL_BLOCK_NUMBER": 1000, "SYNC_MODE": "fast", diff --git a/blockchain-configs/he-shard/node_params.json b/blockchain-configs/he-shard/node_params.json index e2b44d1b1..0916334fb 100644 --- a/blockchain-configs/he-shard/node_params.json +++ b/blockchain-configs/he-shard/node_params.json @@ -30,14 +30,18 @@ "ENABLE_TX_SIG_VERIF_WORKAROUND": false, "EVENT_HANDLER_PORT": 6000, "EVENT_HANDLER_HEARTBEAT_INTERVAL_MS": 15000, + "EXPRESS_RATE_LIMIT_WINDOW_SECS": 60, "FREE_TX_POOL_SIZE_LIMIT_RATIO": 0.1, "FREE_TX_POOL_SIZE_LIMIT_RATIO_PER_ACCOUNT": 0.1, "GET_OP_LIST_SIZE_LIMIT": 50, - "GET_RESP_BYTES_LIMIT": 200000000, - "GET_RESP_MAX_SIBLINGS": 50000, + "GET_RESP_BYTES_LIMIT": 10000000, + "GET_RESP_MAX_SIBLINGS": 1000, "HOSTING_ENV": "local", "LIGHTWEIGHT": false, + "MAX_BLOCKCHAIN_API_RATE_LIMIT": 20, "MAX_FINALIZED_BLOCK_INFO_ON_MEM": 1000, + "MAX_JSON_RPC_API_READ_RATE_LIMIT": 10, + "MAX_JSON_RPC_API_WRITE_RATE_LIMIT": 1, "MAX_NUM_EVENT_CHANNELS": 10, "MAX_NUM_EVENT_FILTERS": 20, "MAX_NUM_EVENT_FILTERS_PER_CHANNEL": 5, @@ -58,7 +62,7 @@ "PEER_WHITELIST": "*", "PORT": 8080, "REST_FUNCTION_CALL_TIMEOUT_MS": 10000, - "REQUEST_BODY_SIZE_LIMIT": "100mb", + "REQUEST_BODY_SIZE_LIMIT": "100kb", "SEND_SNAPSHOT_CHUNK_SLEEP_TIME_MS": 1000, "SNAPSHOTS_INTERVAL_BLOCK_NUMBER": 1000, "SYNC_MODE": "fast", diff --git a/blockchain-configs/mainnet-prod/node_params.json b/blockchain-configs/mainnet-prod/node_params.json index 10641ae62..d5d40942d 100644 --- a/blockchain-configs/mainnet-prod/node_params.json +++ b/blockchain-configs/mainnet-prod/node_params.json @@ -29,14 +29,18 @@ "ENABLE_TX_SIG_VERIF_WORKAROUND": false, "EVENT_HANDLER_PORT": 6000, "EVENT_HANDLER_HEARTBEAT_INTERVAL_MS": 15000, + "EXPRESS_RATE_LIMIT_WINDOW_SECS": 60, "FREE_TX_POOL_SIZE_LIMIT_RATIO": 0.1, "FREE_TX_POOL_SIZE_LIMIT_RATIO_PER_ACCOUNT": 0.1, "GET_OP_LIST_SIZE_LIMIT": 50, - "GET_RESP_BYTES_LIMIT": 200000000, - "GET_RESP_MAX_SIBLINGS": 50000, + "GET_RESP_BYTES_LIMIT": 10000000, + "GET_RESP_MAX_SIBLINGS": 1000, "HOSTING_ENV": "gcp", "LIGHTWEIGHT": false, + "MAX_BLOCKCHAIN_API_RATE_LIMIT": 20, "MAX_FINALIZED_BLOCK_INFO_ON_MEM": 1000, + "MAX_JSON_RPC_API_READ_RATE_LIMIT": 10, + "MAX_JSON_RPC_API_WRITE_RATE_LIMIT": 1, "MAX_NUM_EVENT_CHANNELS": 10, "MAX_NUM_EVENT_FILTERS": 20, "MAX_NUM_EVENT_FILTERS_PER_CHANNEL": 5, @@ -57,7 +61,7 @@ "PEER_WHITELIST": "*", "PORT": 8080, "REST_FUNCTION_CALL_TIMEOUT_MS": 10000, - "REQUEST_BODY_SIZE_LIMIT": "100mb", + "REQUEST_BODY_SIZE_LIMIT": "100kb", "SEND_SNAPSHOT_CHUNK_SLEEP_TIME_MS": 1000, "SNAPSHOTS_INTERVAL_BLOCK_NUMBER": 1000, "SYNC_MODE": "fast", diff --git a/blockchain-configs/sim-shard/node_params.json b/blockchain-configs/sim-shard/node_params.json index 5945f4128..6b5130014 100644 --- a/blockchain-configs/sim-shard/node_params.json +++ b/blockchain-configs/sim-shard/node_params.json @@ -30,14 +30,18 @@ "ENABLE_TX_SIG_VERIF_WORKAROUND": false, "EVENT_HANDLER_PORT": 6000, "EVENT_HANDLER_HEARTBEAT_INTERVAL_MS": 15000, + "EXPRESS_RATE_LIMIT_WINDOW_SECS": 60, "FREE_TX_POOL_SIZE_LIMIT_RATIO": 0.1, "FREE_TX_POOL_SIZE_LIMIT_RATIO_PER_ACCOUNT": 0.1, "GET_OP_LIST_SIZE_LIMIT": 50, - "GET_RESP_BYTES_LIMIT": 200000000, - "GET_RESP_MAX_SIBLINGS": 50000, + "GET_RESP_BYTES_LIMIT": 10000000, + "GET_RESP_MAX_SIBLINGS": 1000, "HOSTING_ENV": "local", "LIGHTWEIGHT": false, + "MAX_BLOCKCHAIN_API_RATE_LIMIT": 20, "MAX_FINALIZED_BLOCK_INFO_ON_MEM": 1000, + "MAX_JSON_RPC_API_READ_RATE_LIMIT": 10, + "MAX_JSON_RPC_API_WRITE_RATE_LIMIT": 1, "MAX_NUM_EVENT_CHANNELS": 10, "MAX_NUM_EVENT_FILTERS": 20, "MAX_NUM_EVENT_FILTERS_PER_CHANNEL": 5, @@ -58,7 +62,7 @@ "PEER_WHITELIST": "*", "PORT": 8080, "REST_FUNCTION_CALL_TIMEOUT_MS": 10000, - "REQUEST_BODY_SIZE_LIMIT": "100mb", + "REQUEST_BODY_SIZE_LIMIT": "100kb", "SEND_SNAPSHOT_CHUNK_SLEEP_TIME_MS": 1000, "SNAPSHOTS_INTERVAL_BLOCK_NUMBER": 1000, "SYNC_MODE": "fast", diff --git a/blockchain-configs/testnet-dev/node_params.json b/blockchain-configs/testnet-dev/node_params.json index 3205a7732..d2490a369 100644 --- a/blockchain-configs/testnet-dev/node_params.json +++ b/blockchain-configs/testnet-dev/node_params.json @@ -30,14 +30,18 @@ "ENABLE_TX_SIG_VERIF_WORKAROUND": false, "EVENT_HANDLER_PORT": 6000, "EVENT_HANDLER_HEARTBEAT_INTERVAL_MS": 15000, + "EXPRESS_RATE_LIMIT_WINDOW_SECS": 60, "FREE_TX_POOL_SIZE_LIMIT_RATIO": 0.1, "FREE_TX_POOL_SIZE_LIMIT_RATIO_PER_ACCOUNT": 0.1, "GET_OP_LIST_SIZE_LIMIT": 50, - "GET_RESP_BYTES_LIMIT": 200000000, - "GET_RESP_MAX_SIBLINGS": 50000, + "GET_RESP_BYTES_LIMIT": 10000000, + "GET_RESP_MAX_SIBLINGS": 1000, "HOSTING_ENV": "gcp", "LIGHTWEIGHT": false, + "MAX_BLOCKCHAIN_API_RATE_LIMIT": 20, "MAX_FINALIZED_BLOCK_INFO_ON_MEM": 1000, + "MAX_JSON_RPC_API_READ_RATE_LIMIT": 10, + "MAX_JSON_RPC_API_WRITE_RATE_LIMIT": 1, "MAX_NUM_EVENT_CHANNELS": 10, "MAX_NUM_EVENT_FILTERS": 20, "MAX_NUM_EVENT_FILTERS_PER_CHANNEL": 5, @@ -58,7 +62,7 @@ "PEER_WHITELIST": "*", "PORT": 8080, "REST_FUNCTION_CALL_TIMEOUT_MS": 10000, - "REQUEST_BODY_SIZE_LIMIT": "100mb", + "REQUEST_BODY_SIZE_LIMIT": "100kb", "SEND_SNAPSHOT_CHUNK_SLEEP_TIME_MS": 1000, "SNAPSHOTS_INTERVAL_BLOCK_NUMBER": 1000, "SYNC_MODE": "fast", diff --git a/blockchain-configs/testnet-exp/node_params.json b/blockchain-configs/testnet-exp/node_params.json index 9080b80fa..1c3e53dea 100644 --- a/blockchain-configs/testnet-exp/node_params.json +++ b/blockchain-configs/testnet-exp/node_params.json @@ -30,14 +30,18 @@ "ENABLE_TX_SIG_VERIF_WORKAROUND": false, "EVENT_HANDLER_PORT": 6000, "EVENT_HANDLER_HEARTBEAT_INTERVAL_MS": 15000, + "EXPRESS_RATE_LIMIT_WINDOW_SECS": 60, "FREE_TX_POOL_SIZE_LIMIT_RATIO": 0.1, "FREE_TX_POOL_SIZE_LIMIT_RATIO_PER_ACCOUNT": 0.1, "GET_OP_LIST_SIZE_LIMIT": 50, - "GET_RESP_BYTES_LIMIT": 200000000, - "GET_RESP_MAX_SIBLINGS": 50000, + "GET_RESP_BYTES_LIMIT": 10000000, + "GET_RESP_MAX_SIBLINGS": 1000, "HOSTING_ENV": "gcp", "LIGHTWEIGHT": false, + "MAX_BLOCKCHAIN_API_RATE_LIMIT": 20, "MAX_FINALIZED_BLOCK_INFO_ON_MEM": 1000, + "MAX_JSON_RPC_API_READ_RATE_LIMIT": 10, + "MAX_JSON_RPC_API_WRITE_RATE_LIMIT": 1, "MAX_NUM_EVENT_CHANNELS": 10, "MAX_NUM_EVENT_FILTERS": 20, "MAX_NUM_EVENT_FILTERS_PER_CHANNEL": 5, @@ -58,7 +62,7 @@ "PEER_WHITELIST": "*", "PORT": 8080, "REST_FUNCTION_CALL_TIMEOUT_MS": 10000, - "REQUEST_BODY_SIZE_LIMIT": "100mb", + "REQUEST_BODY_SIZE_LIMIT": "100kb", "SEND_SNAPSHOT_CHUNK_SLEEP_TIME_MS": 1000, "SNAPSHOTS_INTERVAL_BLOCK_NUMBER": 1000, "SYNC_MODE": "fast", diff --git a/blockchain-configs/testnet-prod/node_params.json b/blockchain-configs/testnet-prod/node_params.json index 88c8fbb74..d187863ad 100644 --- a/blockchain-configs/testnet-prod/node_params.json +++ b/blockchain-configs/testnet-prod/node_params.json @@ -30,14 +30,18 @@ "ENABLE_TX_SIG_VERIF_WORKAROUND": false, "EVENT_HANDLER_PORT": 6000, "EVENT_HANDLER_HEARTBEAT_INTERVAL_MS": 15000, + "EXPRESS_RATE_LIMIT_WINDOW_SECS": 60, "FREE_TX_POOL_SIZE_LIMIT_RATIO": 0.1, "FREE_TX_POOL_SIZE_LIMIT_RATIO_PER_ACCOUNT": 0.1, "GET_OP_LIST_SIZE_LIMIT": 50, - "GET_RESP_BYTES_LIMIT": 200000000, - "GET_RESP_MAX_SIBLINGS": 50000, + "GET_RESP_BYTES_LIMIT": 10000000, + "GET_RESP_MAX_SIBLINGS": 1000, "HOSTING_ENV": "gcp", "LIGHTWEIGHT": false, + "MAX_BLOCKCHAIN_API_RATE_LIMIT": 20, "MAX_FINALIZED_BLOCK_INFO_ON_MEM": 1000, + "MAX_JSON_RPC_API_READ_RATE_LIMIT": 10, + "MAX_JSON_RPC_API_WRITE_RATE_LIMIT": 1, "MAX_NUM_EVENT_CHANNELS": 10, "MAX_NUM_EVENT_FILTERS": 20, "MAX_NUM_EVENT_FILTERS_PER_CHANNEL": 5, @@ -58,7 +62,7 @@ "PEER_WHITELIST": "*", "PORT": 8080, "REST_FUNCTION_CALL_TIMEOUT_MS": 10000, - "REQUEST_BODY_SIZE_LIMIT": "100mb", + "REQUEST_BODY_SIZE_LIMIT": "100kb", "SEND_SNAPSHOT_CHUNK_SLEEP_TIME_MS": 1000, "SNAPSHOTS_INTERVAL_BLOCK_NUMBER": 1000, "SYNC_MODE": "fast", diff --git a/blockchain-configs/testnet-sandbox/node_params.json b/blockchain-configs/testnet-sandbox/node_params.json index 8f9e1e75e..b6594a885 100644 --- a/blockchain-configs/testnet-sandbox/node_params.json +++ b/blockchain-configs/testnet-sandbox/node_params.json @@ -30,14 +30,18 @@ "ENABLE_TX_SIG_VERIF_WORKAROUND": false, "EVENT_HANDLER_PORT": 6000, "EVENT_HANDLER_HEARTBEAT_INTERVAL_MS": 15000, + "EXPRESS_RATE_LIMIT_WINDOW_SECS": 60, "FREE_TX_POOL_SIZE_LIMIT_RATIO": 0.1, "FREE_TX_POOL_SIZE_LIMIT_RATIO_PER_ACCOUNT": 0.1, "GET_OP_LIST_SIZE_LIMIT": 50, - "GET_RESP_BYTES_LIMIT": 200000000, - "GET_RESP_MAX_SIBLINGS": 50000, + "GET_RESP_BYTES_LIMIT": 10000000, + "GET_RESP_MAX_SIBLINGS": 1000, "HOSTING_ENV": "gcp", "LIGHTWEIGHT": false, + "MAX_BLOCKCHAIN_API_RATE_LIMIT": 20, "MAX_FINALIZED_BLOCK_INFO_ON_MEM": 1000, + "MAX_JSON_RPC_API_READ_RATE_LIMIT": 10, + "MAX_JSON_RPC_API_WRITE_RATE_LIMIT": 1, "MAX_NUM_EVENT_CHANNELS": 10, "MAX_NUM_EVENT_FILTERS": 20, "MAX_NUM_EVENT_FILTERS_PER_CHANNEL": 5, @@ -58,7 +62,7 @@ "PEER_WHITELIST": "*", "PORT": 8080, "REST_FUNCTION_CALL_TIMEOUT_MS": 10000, - "REQUEST_BODY_SIZE_LIMIT": "100mb", + "REQUEST_BODY_SIZE_LIMIT": "100kb", "SEND_SNAPSHOT_CHUNK_SLEEP_TIME_MS": 1000, "SNAPSHOTS_INTERVAL_BLOCK_NUMBER": 1000, "SYNC_MODE": "fast", diff --git a/blockchain-configs/testnet-staging/node_params.json b/blockchain-configs/testnet-staging/node_params.json index a771e0d4f..8cac326a2 100644 --- a/blockchain-configs/testnet-staging/node_params.json +++ b/blockchain-configs/testnet-staging/node_params.json @@ -30,14 +30,18 @@ "ENABLE_TX_SIG_VERIF_WORKAROUND": false, "EVENT_HANDLER_PORT": 6000, "EVENT_HANDLER_HEARTBEAT_INTERVAL_MS": 15000, + "EXPRESS_RATE_LIMIT_WINDOW_SECS": 60, "FREE_TX_POOL_SIZE_LIMIT_RATIO": 0.1, "FREE_TX_POOL_SIZE_LIMIT_RATIO_PER_ACCOUNT": 0.1, "GET_OP_LIST_SIZE_LIMIT": 50, - "GET_RESP_BYTES_LIMIT": 200000000, - "GET_RESP_MAX_SIBLINGS": 50000, + "GET_RESP_BYTES_LIMIT": 10000000, + "GET_RESP_MAX_SIBLINGS": 1000, "HOSTING_ENV": "gcp", "LIGHTWEIGHT": false, + "MAX_BLOCKCHAIN_API_RATE_LIMIT": 20, "MAX_FINALIZED_BLOCK_INFO_ON_MEM": 1000, + "MAX_JSON_RPC_API_READ_RATE_LIMIT": 10, + "MAX_JSON_RPC_API_WRITE_RATE_LIMIT": 1, "MAX_NUM_EVENT_CHANNELS": 10, "MAX_NUM_EVENT_FILTERS": 20, "MAX_NUM_EVENT_FILTERS_PER_CHANNEL": 5, @@ -58,7 +62,7 @@ "PEER_WHITELIST": "*", "PORT": 8080, "REST_FUNCTION_CALL_TIMEOUT_MS": 10000, - "REQUEST_BODY_SIZE_LIMIT": "100mb", + "REQUEST_BODY_SIZE_LIMIT": "100kb", "SEND_SNAPSHOT_CHUNK_SLEEP_TIME_MS": 1000, "SNAPSHOTS_INTERVAL_BLOCK_NUMBER": 1000, "SYNC_MODE": "fast", diff --git a/client/index.js b/client/index.js index 97400698c..ad3ea84f7 100755 --- a/client/index.js +++ b/client/index.js @@ -2,22 +2,16 @@ const logger = new (require('../logger'))('CLIENT'); +const _ = require('lodash'); const express = require('express'); -const cors = require('cors'); // NOTE(liayoo): To use async/await (ref: https://github.com/tedeh/jayson#promises) const jayson = require('jayson/promise'); -const rateLimit = require('express-rate-limit'); -const ipWhitelist = require('ip-whitelist'); -const matchUrl = require('match-url-wildcard'); const BlockchainNode = require('../node'); const P2pClient = require('../p2p'); const EventHandler = require('../event-handler'); const CommonUtil = require('../common/common-util'); const VersionUtil = require('../common/version-util'); -const { - convertIpv6ToIpv4, - sendGetRequest -} = require('../common/network-util'); +const { sendGetRequest } = require('../common/network-util'); const { BlockchainConsts, NodeConfigs, @@ -27,25 +21,17 @@ const { DevFlags } = require('../common/constants'); const { DevClientApiResultCode } = require('../common/result-code'); +const Middleware = require('./middleware'); const MAX_BLOCKS = 20; const app = express(); -app.use(express.json({ limit: NodeConfigs.REQUEST_BODY_SIZE_LIMIT })); -app.use(express.urlencoded({ - extended: true, - limit: NodeConfigs.REQUEST_BODY_SIZE_LIMIT -})); -const corsOrigin = NodeConfigs.CORS_WHITELIST === '*' ? - NodeConfigs.CORS_WHITELIST : CommonUtil.getRegexpList(NodeConfigs.CORS_WHITELIST); -app.use(cors({ origin: corsOrigin })); -if (NodeConfigs.ENABLE_EXPRESS_RATE_LIMIT) { - const limiter = rateLimit({ - windowMs: 60 * 1000, // 1 minute - max: 60 // limit each IP to 60 requests per windowMs - }); - app.use(limiter); -} +// NOTE(minsulee2): complex express middleware is now built at middleware.js +const middleware = new Middleware(); +app.use(middleware.expressJsonRequestBodySizeLimiter()); +app.use(middleware.expressUrlencdedRequestBodySizeLimiter()); +app.use(middleware.corsLimiter()); +app.use(middleware.blockchainApiRateLimiter); const eventHandler = NodeConfigs.ENABLE_EVENT_HANDLER === true ? new EventHandler() : null; const node = new BlockchainNode(null, eventHandler); @@ -73,6 +59,7 @@ const jsonRpcApis = require('../json_rpc')( app.post( '/json-rpc', VersionUtil.validateVersion.bind({ minProtocolVersion, maxProtocolVersion }), + middleware.jsonRpcRateLimiter, jayson.server(jsonRpcApis).middleware() ); @@ -124,11 +111,8 @@ app.get('/last_block_number', (req, res, next) => { .end(); }); -app.use(ipWhitelist((ip) => { - return CommonUtil.isWildcard(NodeConfigs.DEV_CLIENT_API_IP_WHITELIST) || - matchUrl(ip, NodeConfigs.DEV_CLIENT_API_IP_WHITELIST) || - matchUrl(convertIpv6ToIpv4(ip), NodeConfigs.DEV_CLIENT_API_IP_WHITELIST); -})); +// NOTE(platfowner): This middleware should be placed after minimally required APIs (see above). +app.use(middleware.ipWhitelistLimiter()); /** * Dev Client GET APIs (available to whitelisted IPs) @@ -136,57 +120,57 @@ app.use(ipWhitelist((ip) => { app.get('/get_value', (req, res, next) => { const beginTime = Date.now(); - const result = node.db.getValue(req.query.ref, CommonUtil.toGetOptions(req.query, true)); + const retVal = node.db.getValueWithError(req.query.ref, CommonUtil.toGetOptions(req.query, true)); + if (retVal.code === undefined) { + retVal.code = DevClientApiResultCode.SUCCESS; + } const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.CLIENT_API_GET, latency); res.status(200) .set('Content-Type', 'application/json') - .send({ - code: result !== null ? DevClientApiResultCode.SUCCESS : DevClientApiResultCode.FAILURE, - result - }) + .send(retVal) .end(); }); app.get('/get_function', (req, res, next) => { const beginTime = Date.now(); - const result = node.db.getFunction(req.query.ref, CommonUtil.toGetOptions(req.query, true)); + const retVal = node.db.getFunctionWithError(req.query.ref, CommonUtil.toGetOptions(req.query, true)); + if (retVal.code === undefined) { + retVal.code = DevClientApiResultCode.SUCCESS; + } const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.CLIENT_API_GET, latency); res.status(200) .set('Content-Type', 'application/json') - .send({ - code: result !== null ? DevClientApiResultCode.SUCCESS : DevClientApiResultCode.FAILURE, - result - }) + .send(retVal) .end(); }); app.get('/get_rule', (req, res, next) => { const beginTime = Date.now(); - const result = node.db.getRule(req.query.ref, CommonUtil.toGetOptions(req.query, true)); + const retVal = node.db.getRuleWithError(req.query.ref, CommonUtil.toGetOptions(req.query, true)); + if (retVal.code === undefined) { + retVal.code = DevClientApiResultCode.SUCCESS; + } const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.CLIENT_API_GET, latency); res.status(200) .set('Content-Type', 'application/json') - .send({ - code: result !== null ? DevClientApiResultCode.SUCCESS : DevClientApiResultCode.FAILURE, - result - }) + .send(retVal) .end(); }); app.get('/get_owner', (req, res, next) => { const beginTime = Date.now(); - const result = node.db.getOwner(req.query.ref, CommonUtil.toGetOptions(req.query, true)); + const retVal = node.db.getOwnerWithError(req.query.ref, CommonUtil.toGetOptions(req.query, true)); + if (retVal.code === undefined) { + retVal.code = DevClientApiResultCode.SUCCESS; + } const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.CLIENT_API_GET, latency); res.status(200) .set('Content-Type', 'application/json') - .send({ - code: result !== null ? DevClientApiResultCode.SUCCESS : DevClientApiResultCode.FAILURE, - result - }) + .send(retVal) .end(); }); @@ -349,15 +333,15 @@ app.post('/eval_owner', (req, res, next) => { app.post('/get', (req, res, next) => { const beginTime = Date.now(); - const result = node.db.get(req.body.op_list); + const retVal = node.db.getWithError(req.body.op_list); + if (retVal.code === undefined) { + retVal.code = DevClientApiResultCode.SUCCESS; + } const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.CLIENT_API_GET, latency); res.status(200) .set('Content-Type', 'application/json') - .send({ - code: result !== null ? DevClientApiResultCode.SUCCESS : DevClientApiResultCode.FAILURE, - result - }) + .send(retVal) .end(); }); diff --git a/client/middleware.js b/client/middleware.js new file mode 100644 index 000000000..bc2981e5d --- /dev/null +++ b/client/middleware.js @@ -0,0 +1,75 @@ +const _ = require('lodash'); +const express = require('express'); +const cors = require('cors'); +const ipWhitelist = require('ip-whitelist'); +const rateLimit = require('express-rate-limit'); +const matchUrl = require('match-url-wildcard'); + +const { NodeConfigs } = require('../common/constants'); +const { + getRegexpList, + isWildcard +} = require('../common/common-util'); +const { JSON_RPC_SET_METHOD_SET } = require('../json_rpc/constants'); + +class Middleware { + constructor () { + this.allBlockchainApiRateLimiter = rateLimit({ + windowMs: NodeConfigs.EXPRESS_RATE_LIMIT_WINDOW_SECS * 1000, // 1 minute window + max: NodeConfigs.EXPRESS_RATE_LIMIT_WINDOW_SECS * NodeConfigs.MAX_BLOCKCHAIN_API_RATE_LIMIT + }); + this.jsonRpcReadRateLimiter = rateLimit({ + windowMs: NodeConfigs.EXPRESS_RATE_LIMIT_WINDOW_SECS * 1000, + max: NodeConfigs.EXPRESS_RATE_LIMIT_WINDOW_SECS * NodeConfigs.MAX_JSON_RPC_API_READ_RATE_LIMIT + }); + this.jsonRpcWriteRateLimiter = rateLimit({ + windowMs: NodeConfigs.EXPRESS_RATE_LIMIT_WINDOW_SECS * 1000, + max: NodeConfigs.EXPRESS_RATE_LIMIT_WINDOW_SECS * NodeConfigs.MAX_JSON_RPC_API_WRITE_RATE_LIMIT + }); + } + + expressJsonRequestBodySizeLimiter() { + return express.json({ limit: NodeConfigs.REQUEST_BODY_SIZE_LIMIT }); + } + + expressUrlencdedRequestBodySizeLimiter() { + return express.urlencoded({ + extended: true, + limit: NodeConfigs.REQUEST_BODY_SIZE_LIMIT + }); + } + + corsLimiter() { + return cors({ origin: NodeConfigs.CORS_WHITELIST === '*' ? + NodeConfigs.CORS_WHITELIST : getRegexpList(NodeConfigs.CORS_WHITELIST) }); + } + + ipWhitelistLimiter() { + return ipWhitelist((ip) => { + return isWildcard(NodeConfigs.DEV_CLIENT_API_IP_WHITELIST) || + matchUrl(ip, NodeConfigs.DEV_CLIENT_API_IP_WHITELIST); + }) + } + + blockchainApiRateLimiter = (req, res, next) => { + if (!NodeConfigs.ENABLE_EXPRESS_RATE_LIMIT) { + return next(); + } + return this.allBlockchainApiRateLimiter(req, res, next); + } + + jsonRpcRateLimiter = (req, res, next) => { + if (!NodeConfigs.ENABLE_EXPRESS_RATE_LIMIT) { + return next(); + } + const jsonRpcMethod = _.get(req, 'body.method'); + // NOTE(minsulee2): Write request is controlled tightest that is 1 tps per ip. + if (JSON_RPC_SET_METHOD_SET.has(jsonRpcMethod)) { + return this.jsonRpcWriteRateLimiter(req, res, next); + } else { + return this.jsonRpcReadRateLimiter(req, res, next); + } + } +} + +module.exports = Middleware; diff --git a/client/protocol_versions.json b/client/protocol_versions.json index 9c8810fea..de267a5c9 100644 --- a/client/protocol_versions.json +++ b/client/protocol_versions.json @@ -98,5 +98,8 @@ }, "1.0.7": { "min": "1.0.0" + }, + "1.0.8": { + "min": "1.0.0" } } \ No newline at end of file diff --git a/common/common-util.js b/common/common-util.js index 8f6b5517a..4c5acefd8 100644 --- a/common/common-util.js +++ b/common/common-util.js @@ -152,6 +152,14 @@ class CommonUtil { return ruleUtil.isValidPrivateUrl(url); } + static isValidIpV4(ipAddress) { + return ruleUtil.isValidIpV4(ipAddress); + } + + static isValidIpV6(ipAddress) { + return ruleUtil.isValidIpV6(ipAddress); + } + static isWildcard(value) { return value === '*'; } @@ -243,6 +251,15 @@ class CommonUtil { } if (args.is_shallow !== undefined) { options.isShallow = CommonUtil.toBool(args.is_shallow); + } else { + options.isShallow = false; + } + if (args.is_partial !== undefined) { + options.isPartial = CommonUtil.toBool(args.is_partial); + options.lastEndLabel = args.last_end_label !== undefined ? + CommonUtil.toString(args.last_end_label) : null; + } else { + options.isPartial = false; } if (args.include_version !== undefined) { options.includeVersion = CommonUtil.toBool(args.include_version); @@ -261,6 +278,9 @@ class CommonUtil { static toMatchOrEvalOptions(args, fromApi = false) { const options = {}; + // NOTE: Not allowed true values of isShallow or isPartial options in match/eval requests. + options.isShallow = false; + options.isPartial = false; if (args.is_global !== undefined) { options.isGlobal = CommonUtil.toBool(args.is_global); } diff --git a/common/constants.js b/common/constants.js index 0b1374a00..85e4d70d4 100644 --- a/common/constants.js +++ b/common/constants.js @@ -132,11 +132,11 @@ setNodeConfigs(); // ** Enums ** /** - * Message types for communication between nodes. + * Types of P2P network messages. * * @enum {string} */ -const MessageTypes = { +const P2pMessageTypes = { ADDRESS_REQUEST: 'ADDRESS_REQUEST', ADDRESS_RESPONSE: 'ADDRESS_RESPONSE', CHAIN_SEGMENT_REQUEST: 'CHAIN_SEGMENT_REQUEST', @@ -412,6 +412,7 @@ const FunctionTypes = { * @enum {string} */ const StateLabelProperties = { + END_LABEL: '#end_label', HAS_PARENT_STATE_NODE: '#has_parent_state_node', HASH_DELIMITER: '#', // Hash delimiter META_LABEL_PREFIX: '#', // Prefix of all meta labels @@ -425,9 +426,10 @@ const StateLabelProperties = { STATE_PROOF_HASH: '#state_ph', VARIABLE_LABEL_PREFIX: '$', // Prefix of variable labels VERSION: '#version', + TREE_BYTES: '#tree_bytes', TREE_HEIGHT: '#tree_height', + TREE_MAX_SIBLINGS: '#tree_max_siblings', TREE_SIZE: '#tree_size', - TREE_BYTES: '#tree_bytes', }; /** @@ -803,7 +805,7 @@ module.exports = { TimerFlagEnabledBandageMap, BlockchainParams, NodeConfigs, - MessageTypes, + P2pMessageTypes, BlockchainNodeStates, P2pNetworkStates, PredefinedDbPaths, diff --git a/common/network-util.js b/common/network-util.js index f9c4bbba8..c6b7e0db2 100644 --- a/common/network-util.js +++ b/common/network-util.js @@ -7,6 +7,7 @@ const ip = require('ip'); const extIp = require('ext-ip')(); const CommonUtil = require('../common/common-util'); const DB = require('../db'); +const { JSON_RPC_METHODS } = require('../json_rpc/constants'); const GCP_EXTERNAL_IP_URL = 'http://metadata.google.internal/computeMetadata/v1/instance' + '/network-interfaces/0/access-configs/0/external-ip'; const GCP_INTERNAL_IP_URL = 'http://metadata.google.internal/computeMetadata/v1/instance' + @@ -18,7 +19,7 @@ async function _waitUntilTxFinalize(endpoint, txHash) { while (true) { const confirmed = await sendGetRequest( endpoint, - 'ain_getTransactionByHash', + JSON_RPC_METHODS.AIN_GET_TRANSACTION_BY_HASH, { hash: txHash } ) .then((resp) => { @@ -50,7 +51,7 @@ async function sendSignedTx(endpoint, params) { return await axios.post( endpoint, { - method: 'ain_sendSignedTransaction', + method: JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, params, jsonrpc: '2.0', id: 0 @@ -119,15 +120,10 @@ function getIpAddress(internal = false) { }); } -function convertIpv6ToIpv4(address) { - return CommonUtil.isString(address) ? address.replace('::ffff:', '') : ''; -} - module.exports = { sendTxAndWaitForFinalization, sendSignedTx, signAndSendTx, sendGetRequest, - getIpAddress, - convertIpv6ToIpv4 + getIpAddress }; diff --git a/common/result-code.js b/common/result-code.js index 4d768dc85..3a1d2428a 100644 --- a/common/result-code.js +++ b/common/result-code.js @@ -3,6 +3,8 @@ * * @enum {number} */ +// NOTE(platfowner): The code values below need to be kept for the backward compatibility. +// If they are altered and deployed, the full sync of the blockchain nodes can fail. const TxResultCode = { // Common code SUCCESS: 0, @@ -46,6 +48,7 @@ const TxResultCode = { TX_INVALID_GAS_PRICE: 10714, TX_POOL_NOT_ENOUGH_FREE_ROOM: 10715, TX_POOL_NOT_ENOUGH_FREE_ROOM_FOR_ACCOUNT: 10716, + TX_SET_EXCEEDS_OP_LIST_SIZE_LIMIT: 30005, // Moved from JsonRpcApiResultCode.SET_EXCEEDS_OP_LIST_SIZE_LIMIT // Billing BILLING_INVALID_PARAM: 10801, BILLING_NO_ACCOUNT_PERMISSION: 10802, @@ -119,6 +122,8 @@ const FailedTxPrecheckCodeSet = new Set([ * * @enum {number} */ +// NOTE(platfowner): The code values below need to be kept for the backward compatibility. +// If they are altered and deployed, the full sync of the blockchain nodes can fail. const FunctionResultCode = { SUCCESS: 0, FAILURE: 20001, // Normal failure @@ -155,7 +160,7 @@ const JsonRpcApiResultCode = { GET_EXCEEDS_MAX_BYTES: 30002, GET_EXCEEDS_MAX_SIBLINGS: 30003, GET_EXCEEDS_OP_LIST_SIZE_LIMIT: 30004, - SET_EXCEEDS_OP_LIST_SIZE_LIMIT: 30005, + DEPRECATED_SET_EXCEEDS_OP_LIST_SIZE_LIMIT: 30005, // Moved to TxResultCode.TX_SET_EXCEEDS_OP_LIST_SIZE_LIMIT GET_INVALID_OP_LIST: 30006, // ain_checkProtocolVersion PROTO_VERSION_NOT_SPECIFIED: 30101, @@ -173,15 +178,16 @@ const JsonRpcApiResultCode = { BATCH_TX_MISSING_PROPERTIES: 30404, BATCH_TX_INVALID_FORMAT: 30405, BATCH_TX_INVALID_SIGNATURE: 30406, - // ain_addToDevClientApiIpWhitelist - INVALID_IP: 30501, - IP_ALREADY_IN_WHITELIST: 30502, - // ain_removeFromDevClientApiIpWhitelist - IP_NOT_IN_WHITELIST: 30601, + // Admin APIs + ADMIN_FORBIDDEN_REQUEST: 30501, + ADMIN_PARAM_INVALID: 30502, + ADMIN_VALUE_NOT_A_STRING_TYPE: 30503, + ADMIN_ALREADY_IN_WHITELIST: 30504, + ADMIN_NOT_IN_WHITELIST: 30505, // ain_validateAppName - INVALID_APP_NAME_FOR_STATE_LABEL: 30701, - INVALID_APP_NAME_FOR_SERVICE_NAME: 30702, - APP_NAME_ALREADY_IN_USE: 30703, + INVALID_APP_NAME_FOR_STATE_LABEL: 30601, + INVALID_APP_NAME_FOR_SERVICE_NAME: 30602, + APP_NAME_ALREADY_IN_USE: 30603, }; /** @@ -193,9 +199,6 @@ const DevClientApiResultCode = { // Common code SUCCESS: 0, FAILURE: 40001, - PROTO_VERSION_NOT_SPECIFIED: 40101, - INVALID_PROTO_VERSION: 40102, - INCOMPATIBLE_PROTO_VERSION: 40103, }; /** diff --git a/common/version-util.js b/common/version-util.js index dbac15cf8..2f7aa88f9 100644 --- a/common/version-util.js +++ b/common/version-util.js @@ -1,6 +1,7 @@ const semver = require('semver'); const { BlockchainConsts } = require('../common/constants'); -const { DevClientApiResultCode } = require('../common/result-code'); +const { DevClientApiResultCode, JsonRpcApiResultCode } = require('../common/result-code'); +const { JSON_RPC_METHODS } = require('../json_rpc/constants'); class VersionUtil { static isValidProtocolVersion(version) { @@ -42,14 +43,15 @@ class VersionUtil { version = req.body.params.protoVer; } const coercedVer = semver.coerce(version); - if (req.body.method === 'ain_getProtocolVersion' || - req.body.method === 'ain_checkProtocolVersion') { + if (req.body.method === JSON_RPC_METHODS.AIN_GET_PROTOCOL_VERSION || + req.body.method === JSON_RPC_METHODS.AIN_CHECK_PROTOCOL_VERSION) { next(); } else if (version === undefined) { res.status(200) .set('Content-Type', 'application/json') .send({ - code: DevClientApiResultCode.PROTO_VERSION_NOT_SPECIFIED, + result: null, + code: JsonRpcApiResultCode.PROTO_VERSION_NOT_SPECIFIED, message: 'Protocol version not specified.', protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }) @@ -58,7 +60,8 @@ class VersionUtil { res.status(200) .set('Content-Type', 'application/json') .send({ - code: DevClientApiResultCode.INVALID_PROTO_VERSION, + result: null, + code: JsonRpcApiResultCode.PROTO_VERSION_INVALID, message: 'Invalid protocol version.', protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }) @@ -68,7 +71,8 @@ class VersionUtil { res.status(200) .set('Content-Type', 'application/json') .send({ - code: DevClientApiResultCode.INCOMPATIBLE_PROTO_VERSION, + result: null, + code: JsonRpcApiResultCode.PROTO_VERSION_INCOMPATIBLE, message: 'Incompatible protocol version.', protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }) diff --git a/db/index.js b/db/index.js index 3925fe08e..087a2cd3e 100644 --- a/db/index.js +++ b/db/index.js @@ -497,25 +497,35 @@ class DB { static readFromStateRoot(stateRoot, rootLabel, refPath, options, shardingPath) { const isGlobal = options && options.isGlobal; - if (!stateRoot) return null; + if (!stateRoot) { + return { + result: null + }; + } const parsedPath = CommonUtil.parsePath(refPath); const localPath = isGlobal ? DB.toLocalPath(parsedPath, shardingPath) : parsedPath; if (localPath === null) { // No matched local path. - return null; + return { + result: null + }; } const fullPath = DB.getFullPath(localPath, rootLabel); const stateNode = DB.getRefForReadingFromStateRoot(stateRoot, fullPath); if (stateNode === null) { - return null; + return { + result: null + }; } if (options && options.fromApi) { - const limitChecked = DB.checkRespTreeLimits(stateNode); + const limitChecked = DB.checkRespTreeLimits(stateNode, options); if (limitChecked !== true) { - return limitChecked; + return Object.assign({ result: null }, limitChecked); } } - return stateNode.toStateSnapshot(options); + return { + result: stateNode.toStateSnapshot(options) + }; } readDatabase(refPath, rootLabel, options) { @@ -525,18 +535,34 @@ class DB { } getValue(valuePath, options) { + return this.getValueWithError(valuePath, options).result; + } + + getValueWithError(valuePath, options) { return this.readDatabase(valuePath, PredefinedDbPaths.VALUES_ROOT, options); } getFunction(functionPath, options) { + return this.getFunctionWithError(functionPath, options).result; + } + + getFunctionWithError(functionPath, options) { return this.readDatabase(functionPath, PredefinedDbPaths.FUNCTIONS_ROOT, options); } getRule(rulePath, options) { + return this.getRuleWithError(rulePath, options).result; + } + + getRuleWithError(rulePath, options) { return this.readDatabase(rulePath, PredefinedDbPaths.RULES_ROOT, options); } getOwner(ownerPath, options) { + return this.getOwnerWithError(ownerPath, options).result; + } + + getOwnerWithError(ownerPath, options) { return this.readDatabase(ownerPath, PredefinedDbPaths.OWNERS_ROOT, options); } @@ -560,7 +586,7 @@ class DB { static getValueFromStateRoot(stateRoot, statePath, isShallow = false) { return DB.readFromStateRoot( - stateRoot, PredefinedDbPaths.VALUES_ROOT, statePath, { isShallow }, []); + stateRoot, PredefinedDbPaths.VALUES_ROOT, statePath, { isShallow }, []).result; } /** @@ -578,6 +604,7 @@ class DB { [StateLabelProperties.TREE_HEIGHT]: stateNode.getTreeHeight(), [StateLabelProperties.TREE_SIZE]: stateNode.getTreeSize(), [StateLabelProperties.TREE_BYTES]: stateNode.getTreeBytes(), + [StateLabelProperties.TREE_MAX_SIBLINGS]: stateNode.getTreeMaxSiblings(), [StateLabelProperties.STATE_PROOF_HASH]: stateNode.getProofHash(), [StateLabelProperties.VERSION]: stateNode.getVersion(), }; @@ -618,7 +645,9 @@ class DB { } const limitChecked = this.checkRespTreeLimitsForEvalOrMatch( PredefinedDbPaths.FUNCTIONS_ROOT, localPath, options); - if (limitChecked !== true) return limitChecked; + if (limitChecked !== true) { + return limitChecked; + } return this.convertFunctionMatch( this.matchFunctionForParsedPath(localPath), isGlobal); } @@ -633,7 +662,9 @@ class DB { } const limitChecked = this.checkRespTreeLimitsForEvalOrMatch( PredefinedDbPaths.RULES_ROOT, localPath, options); - if (limitChecked !== true) return limitChecked; + if (limitChecked !== true) { + return limitChecked; + } const matched = this.matchRuleForParsedPath(localPath); return { write: this.convertRuleMatch(matched.write, isGlobal), @@ -651,7 +682,9 @@ class DB { } const limitChecked = this.checkRespTreeLimitsForEvalOrMatch( PredefinedDbPaths.OWNERS_ROOT, localPath, options); - if (limitChecked !== true) return limitChecked; + if (limitChecked !== true) { + return limitChecked; + } return this.convertOwnerMatch(this.matchOwnerForParsedPath(localPath), isGlobal); } @@ -665,7 +698,9 @@ class DB { } const limitChecked = this.checkRespTreeLimitsForEvalOrMatch( PredefinedDbPaths.RULES_ROOT, localPath, options); - if (limitChecked !== true) return limitChecked; + if (limitChecked !== true) { + return limitChecked; + } return this.getPermissionForValue(localPath, value, auth, options); } @@ -680,7 +715,9 @@ class DB { } const limitChecked = this.checkRespTreeLimitsForEvalOrMatch( PredefinedDbPaths.OWNERS_ROOT, localPath, options); - if (limitChecked !== true) return limitChecked; + if (limitChecked !== true) { + return limitChecked; + } if (permission === OwnerProperties.WRITE_RULE) { return this.getPermissionForRule(localPath, auth, options && options.isMerge); } else if (permission === OwnerProperties.WRITE_FUNCTION) { @@ -700,31 +737,37 @@ class DB { } // TODO(liayoo): Apply stricter limits to rule/function/owner state budgets - static checkRespTreeLimits(stateNode) { - if (stateNode.numChildren() > NodeConfigs.GET_RESP_MAX_SIBLINGS) { - return { - code: JsonRpcApiResultCode.GET_EXCEEDS_MAX_SIBLINGS, - message: `The data exceeds the max sibling limit of the requested node: ` + - `${stateNode.numChildren()} > ${NodeConfigs.GET_RESP_MAX_SIBLINGS}` - }; + static checkRespTreeLimits(stateNode, options) { + // NOTE: Skip sibling number limit check for isPartial = true cases. + if (!(options && options.isPartial)) { + if (stateNode.getTreeMaxSiblings() > NodeConfigs.GET_RESP_MAX_SIBLINGS) { + return { + code: JsonRpcApiResultCode.GET_EXCEEDS_MAX_SIBLINGS, + message: `The data exceeds the max sibling limit of the requested node: ` + + `${stateNode.getTreeMaxSiblings()} > ${NodeConfigs.GET_RESP_MAX_SIBLINGS}` + }; + } } - if (stateNode.getTreeBytes() > NodeConfigs.GET_RESP_BYTES_LIMIT) { - return { - code: JsonRpcApiResultCode.GET_EXCEEDS_MAX_BYTES, - message: `The data exceeds the max byte limit of the requested node: ` + - `${stateNode.getTreeBytes()} > ${NodeConfigs.GET_RESP_BYTES_LIMIT}` - }; + // NOTE: Skip bytes limit check for isShallow = true or isPartial = true cases. + if (!(options && (options.isShallow || options.isPartial))) { + if (stateNode.getTreeBytes() > NodeConfigs.GET_RESP_BYTES_LIMIT) { + return { + code: JsonRpcApiResultCode.GET_EXCEEDS_MAX_BYTES, + message: `The data exceeds the max byte limit of the requested node: ` + + `${stateNode.getTreeBytes()} > ${NodeConfigs.GET_RESP_BYTES_LIMIT}` + }; + } } return true; } checkRespTreeLimitsForEvalOrMatch(rootLabel, localPath, options) { if (options && options.fromApi) { - const targetStateRoot = options.isFinal ? this.stateManager.getFinalRoot() : this.stateRoot; + const targetStateRoot = options.isFinal ? this.stateManager.getFinalRoot() : this.stateRoot; const fullPath = DB.getFullPath(localPath, rootLabel); const stateNode = DB.getRefForReadingFromStateRoot(targetStateRoot, fullPath); if (stateNode !== null) { - const limitChecked = DB.checkRespTreeLimits(stateNode); + const limitChecked = DB.checkRespTreeLimits(stateNode, options); if (limitChecked !== true) { return limitChecked; } @@ -736,8 +779,13 @@ class DB { // TODO(platfowner): Add tests for op.fid. // NOTE(liayoo): This function is only for external uses (APIs). get(opList) { - if (!CommonUtil.isArray(opList)) { + return this.getWithError(opList).result; + } + + getWithError(opList) { + if (!CommonUtil.isArray(opList) || CommonUtil.isEmpty(opList)) { return { + result: null, code: JsonRpcApiResultCode.GET_INVALID_OP_LIST, message: `Invalid op_list given` }; @@ -745,6 +793,7 @@ class DB { if (CommonUtil.isNumber(NodeConfigs.GET_OP_LIST_SIZE_LIMIT) && opList.length > NodeConfigs.GET_OP_LIST_SIZE_LIMIT) { return { + result: null, code: JsonRpcApiResultCode.GET_EXCEEDS_OP_LIST_SIZE_LIMIT, message: `The request exceeds the max op_list size limit of the requested node: ` + `${opList.length} > ${NodeConfigs.GET_OP_LIST_SIZE_LIMIT}` @@ -753,19 +802,19 @@ class DB { const resultList = []; for (const op of opList) { if (op.type === undefined || op.type === ReadDbOperations.GET_VALUE) { - resultList.push(this.getValue(op.ref, CommonUtil.toGetOptions(op))); + resultList.push(this.getValue(op.ref, CommonUtil.toGetOptions(op, true))); } else if (op.type === ReadDbOperations.GET_RULE) { - resultList.push(this.getRule(op.ref, CommonUtil.toGetOptions(op))); + resultList.push(this.getRule(op.ref, CommonUtil.toGetOptions(op, true))); } else if (op.type === ReadDbOperations.GET_FUNCTION) { - resultList.push(this.getFunction(op.ref, CommonUtil.toGetOptions(op))); + resultList.push(this.getFunction(op.ref, CommonUtil.toGetOptions(op, true))); } else if (op.type === ReadDbOperations.GET_OWNER) { - resultList.push(this.getOwner(op.ref, CommonUtil.toGetOptions(op))); + resultList.push(this.getOwner(op.ref, CommonUtil.toGetOptions(op, true))); } else if (op.type === ReadDbOperations.MATCH_FUNCTION) { - resultList.push(this.matchFunction(op.ref, CommonUtil.toMatchOrEvalOptions(op))); + resultList.push(this.matchFunction(op.ref, CommonUtil.toMatchOrEvalOptions(op, true))); } else if (op.type === ReadDbOperations.MATCH_RULE) { - resultList.push(this.matchRule(op.ref, CommonUtil.toMatchOrEvalOptions(op))); + resultList.push(this.matchRule(op.ref, CommonUtil.toMatchOrEvalOptions(op, true))); } else if (op.type === ReadDbOperations.MATCH_OWNER) { - resultList.push(this.matchOwner(op.ref, CommonUtil.toMatchOrEvalOptions(op))); + resultList.push(this.matchOwner(op.ref, CommonUtil.toMatchOrEvalOptions(op, true))); } else if (op.type === ReadDbOperations.EVAL_RULE) { const auth = {}; if (op.address) { @@ -775,7 +824,7 @@ class DB { auth.fid = op.fid; } const timestamp = op.timestamp || Date.now(); - const options = Object.assign(CommonUtil.toMatchOrEvalOptions(op), { timestamp }); + const options = Object.assign(CommonUtil.toMatchOrEvalOptions(op, true), { timestamp }); resultList.push(this.evalRule(op.ref, op.value, auth, options)); } else if (op.type === ReadDbOperations.EVAL_OWNER) { const auth = {}; @@ -786,10 +835,12 @@ class DB { auth.fid = op.fid; } resultList.push(this.evalOwner( - op.ref, op.permission, auth, CommonUtil.toMatchOrEvalOptions(op))); + op.ref, op.permission, auth, CommonUtil.toMatchOrEvalOptions(op, true))); } } - return resultList; + return { + result: resultList, + }; } static getAccountNonceAndTimestampFromStateRoot(stateRoot, address) { @@ -835,10 +886,12 @@ class DB { return curAppValue === null; } + // TODO(platfowner): Remove is_valid once migration is completed. validateAppName(appName, blockNumber, stateLabelLengthLimit) { if (!isValidStateLabel(appName, stateLabelLengthLimit)) { return { is_valid: false, + result: false, code: JsonRpcApiResultCode.INVALID_APP_NAME_FOR_STATE_LABEL, message: `Invalid app name for state label: ${appName}` }; @@ -846,6 +899,7 @@ class DB { if (!isValidServiceName(appName, blockNumber)) { return { is_valid: false, + result: false, code: JsonRpcApiResultCode.INVALID_APP_NAME_FOR_SERVICE_NAME, message: `Invalid app name for service name: ${appName}` }; @@ -853,12 +907,14 @@ class DB { if (!this.isNonExistingApp(appName)) { return { is_valid: false, + result: false, code: JsonRpcApiResultCode.APP_NAME_ALREADY_IN_USE, message: `App name already in use: ${appName}` }; } return { is_valid: true, + result: true, code: JsonRpcApiResultCode.SUCCESS }; } @@ -1320,7 +1376,8 @@ class DB { 'resource/set_op_list_size_limit', blockNumber, this.stateRoot); if (blockNumber > 0 && opList.length > setOpListSizeLimit) { return { - code: JsonRpcApiResultCode.SET_EXCEEDS_OP_LIST_SIZE_LIMIT, + result_list: null, + code: TxResultCode.TX_SET_EXCEEDS_OP_LIST_SIZE_LIMIT, message: `The transaction exceeds the max op_list size limit: ` + `${opList.length} > ${setOpListSizeLimit}` }; @@ -2274,6 +2331,7 @@ class DB { return this.getSubtreeFunctionsRecursive(0, funcNode); } + // TODO(platfowner): Consider optimizing subtree function retrieval for many children. matchFunctionForParsedPath(parsedValuePath) { const matched = this.matchFunctionPath(parsedValuePath); const subtreeFunctions = this.getSubtreeFunctions(matched.matchedFunctionNode); @@ -2421,6 +2479,7 @@ class DB { return this.getSubtreeRulesRecursive(0, ruleNode, ruleProp); } + // TODO(platfowner): Consider optimizing subtree rule retrieval for many children. matchRuleForParsedPath(parsedValuePath) { const matchedWriteRule = this.matchRulePath(parsedValuePath, RuleProperties.WRITE); const matchedStateRule = this.matchRulePath(parsedValuePath, RuleProperties.STATE); @@ -2686,6 +2745,7 @@ class DB { return this.getSubtreeOwnersRecursive(0, ownerNode); } + // TODO(platfowner): Consider optimizing subtree owner retrieval for many children. matchOwnerForParsedPath(parsedRefPath) { const matched = this.matchOwnerPath(parsedRefPath); const subtreeOwners = matched.matchedOwnerNode ? diff --git a/db/radix-node.js b/db/radix-node.js index 73cf32b15..47951dc2c 100644 --- a/db/radix-node.js +++ b/db/radix-node.js @@ -29,6 +29,7 @@ class RadixNode { this.treeHeight = 0; this.treeSize = 0; this.treeBytes = 0; + this.treeMaxSiblings = 0; } reset() { @@ -44,11 +45,12 @@ class RadixNode { this.resetTreeHeight(); this.resetTreeSize(); this.resetTreeBytes(); + this.resetTreeMaxSiblings(); } static _create( version, serial, parentStateNode, childStateNode, labelRadix, labelSuffix, proofHash, - treeHeight, treeSize, treeBytes) { + treeHeight, treeSize, treeBytes, treeMaxSiblings) { const node = new RadixNode(version, serial, parentStateNode); if (childStateNode) { node.setChildStateNode(childStateNode); @@ -59,6 +61,7 @@ class RadixNode { node.setTreeHeight(treeHeight); node.setTreeSize(treeSize); node.setTreeBytes(treeBytes); + node.setTreeMaxSiblings(treeMaxSiblings); return node; } @@ -66,7 +69,7 @@ class RadixNode { const cloned = RadixNode._create( version, this.getSerial(), parentStateNode, this.getChildStateNode(), this.getLabelRadix(), this.getLabelSuffix(), this.getProofHash(), this.getTreeHeight(), this.getTreeSize(), - this.getTreeBytes()); + this.getTreeBytes(), this.getTreeMaxSiblings()); for (const child of this.getChildNodes()) { cloned.setChild(child.getLabelRadix(), child.getLabelSuffix(), child); } @@ -336,18 +339,65 @@ class RadixNode { return false; } - getChildStateNodeList() { + static compareRadixLabelWithPrefix(label, prefix) { + if (_.startsWith(prefix, label)) { + return 0; + } + return label.localeCompare(prefix); + } + + getChildStateNodeListWithEndLabel(maxListSize = null, lastEndLabel = null) { const stateNodeList = []; - if (this.hasChildStateNode()) { + let endLabel = null; + if (CommonUtil.isNumber(maxListSize) && maxListSize <= 0) { + return { + list: stateNodeList, + endLabel, + }; + } + if (this.hasChildStateNode() && !CommonUtil.isString(lastEndLabel)) { stateNodeList.push({ serial: this.getSerial(), stateNode: this.getChildStateNode() }); + endLabel = this.getLabel(); } + if (CommonUtil.isNumber(maxListSize) && stateNodeList.length >= maxListSize) { + return { + list: stateNodeList, + endLabel, + }; + } + const lastEndLabelForChild = CommonUtil.isString(lastEndLabel) ? + lastEndLabel.slice(this.getLabel().length) : null; for (const child of this.getChildNodes()) { - stateNodeList.push(...child.getChildStateNodeList()); + // NOTE: Whether the child's label is less than, greater than, or equals to the given last label prefix or not. + const labelComparison = CommonUtil.isString(lastEndLabelForChild) ? + RadixNode.compareRadixLabelWithPrefix(child.getLabel(), lastEndLabelForChild) : 1; + if (labelComparison < 0) { + continue; + } + const maxListSizeForChild = CommonUtil.isNumber(maxListSize) ? + maxListSize - stateNodeList.length : null; + const stateNodeListFromChild = child.getChildStateNodeListWithEndLabel( + maxListSizeForChild, + labelComparison > 0 ? null : lastEndLabelForChild); + if (stateNodeListFromChild.list.length > 0) { + stateNodeList.push(...stateNodeListFromChild.list); + endLabel = stateNodeListFromChild.endLabel !== null ? + this.getLabel() + stateNodeListFromChild.endLabel : null; + } + if (CommonUtil.isNumber(maxListSize) && stateNodeList.length >= maxListSize) { + return { + list: stateNodeList, + endLabel, + }; + } } - return stateNodeList; + return { + list: stateNodeList, + endLabel, + }; } getProofHash() { @@ -398,12 +448,25 @@ class RadixNode { this.setTreeBytes(0); } + getTreeMaxSiblings() { + return this.treeMaxSiblings; + } + + setTreeMaxSiblings(treeMaxSiblings) { + this.treeMaxSiblings = treeMaxSiblings; + } + + resetTreeMaxSiblings() { + this.setTreeMaxSiblings(0); + } + buildRadixInfo() { let treeInfo = { preimage: '', treeHeight: 0, treeSize: 0, treeBytes: 0, + treeMaxSiblings: 0, }; if (this.hasChildStateNode()) { const childStateNode = this.getChildStateNode(); @@ -414,6 +477,7 @@ class RadixNode { treeHeight: childStateNode.getTreeHeight(), treeSize: childStateNode.getTreeSize(), treeBytes: sizeof(childStateNodeLabel) + childStateNode.getTreeBytes(), + treeMaxSiblings: childStateNode.getTreeMaxSiblings(), }; } treeInfo.preimage += StateLabelProperties.HASH_DELIMITER; @@ -426,11 +490,13 @@ class RadixNode { const accTreeHeight = Math.max(acc.treeHeight, child.getTreeHeight()); const accTreeSize = acc.treeSize + child.getTreeSize(); const accTreeBytes = acc.treeBytes + child.getTreeBytes(); + const accTreeMaxSiblings = Math.max(acc.treeMaxSiblings, child.getTreeMaxSiblings()); return { preimage: accPreimage, treeHeight: accTreeHeight, treeSize: accTreeSize, treeBytes: accTreeBytes, + treeMaxSiblings: accTreeMaxSiblings, }; }, treeInfo); } @@ -440,6 +506,7 @@ class RadixNode { treeHeight: treeInfo.treeHeight, treeSize: treeInfo.treeSize, treeBytes: treeInfo.treeBytes, + treeMaxSiblings: treeInfo.treeMaxSiblings, }; } @@ -449,6 +516,7 @@ class RadixNode { this.setTreeHeight(treeInfo.treeHeight); this.setTreeSize(treeInfo.treeSize); this.setTreeBytes(treeInfo.treeBytes); + this.setTreeMaxSiblings(treeInfo.treeMaxSiblings); } verifyRadixInfo() { @@ -456,7 +524,8 @@ class RadixNode { return this.getProofHash() === treeInfo.proofHash && this.getTreeHeight() === treeInfo.treeHeight && this.getTreeSize() === treeInfo.treeSize && - this.getTreeBytes() === treeInfo.treeBytes; + this.getTreeBytes() === treeInfo.treeBytes && + this.getTreeMaxSiblings() === treeInfo.treeMaxSiblings; } updateRadixInfoForRadixTree() { @@ -688,6 +757,7 @@ class RadixNode { obj[StateLabelProperties.TREE_HEIGHT] = this.getTreeHeight(); obj[StateLabelProperties.TREE_SIZE] = this.getTreeSize(); obj[StateLabelProperties.TREE_BYTES] = this.getTreeBytes(); + obj[StateLabelProperties.TREE_MAX_SIBLINGS] = this.getTreeMaxSiblings(); } if (withNumParents) { obj[StateLabelProperties.NUM_PARENTS] = this.numParents(); diff --git a/db/radix-tree.js b/db/radix-tree.js index 844d13559..59fa80bc3 100644 --- a/db/radix-tree.js +++ b/db/radix-tree.js @@ -2,6 +2,7 @@ const logger = new (require('../logger'))('RADIX_TREE'); const CommonUtil = require('../common/common-util'); const { + NodeConfigs, StateLabelProperties, } = require('../common/constants'); const RadixNode = require('./radix-node'); @@ -377,17 +378,34 @@ class RadixTree { this.numChildStateNodes-- } - getChildStateLabels() { - const labelList = []; - for (const stateNode of this.getChildStateNodes()) { - labelList.push(stateNode.getLabel()); + getChildStateLabelsWithEndLabel(isPartial = false, lastEndLabel = null) { + const nodesWithEndLabel = this.getChildStateNodesWithEndLabel(isPartial, lastEndLabel); + const labelList = nodesWithEndLabel.list.map(entry => entry.getLabel()); + return { + list: labelList, + serialList: nodesWithEndLabel.serialList, + endLabel: nodesWithEndLabel.endLabel, + }; + } + + getChildStateNodesWithEndLabel(isPartial = false, lastEndLabel = null) { + const maxListSize = isPartial ? NodeConfigs.GET_RESP_MAX_SIBLINGS : null; + const nodeListWithEndLabel = + this.root.getChildStateNodeListWithEndLabel(maxListSize, lastEndLabel); + const sorted = CommonUtil.isString(lastEndLabel) ? + nodeListWithEndLabel.list : // Skip sorting + nodeListWithEndLabel.list.sort((a, b) => a.serial - b.serial); + const stateNodeList = []; + const serialList = []; + for (const entry of sorted) { + stateNodeList.push(entry.stateNode); + serialList.push(entry.serial); } - return labelList; - } - - getChildStateNodes() { - return this.root.getChildStateNodeList().sort((a, b) => a.serial - b.serial) - .map(entry => entry.stateNode); + return { + list: stateNodeList, + serialList, + endLabel: nodeListWithEndLabel.endLabel, + }; } hasChildStateNodes() { @@ -410,6 +428,10 @@ class RadixTree { return this.root.getTreeBytes(); } + getRootTreeMaxSiblings() { + return this.root.getTreeMaxSiblings(); + } + updateRadixInfoForRadixTree() { return this.root.updateRadixInfoForRadixTree(); } @@ -480,7 +502,7 @@ class RadixTree { tree.setRoot(root); tree.setNextSerial(obj[StateLabelProperties.NEXT_SERIAL]); // NOTE(platfowner): Need to recompute and set numChildStateNodes. - const numChildStateNodes = tree.getChildStateLabels().length; + const numChildStateNodes = tree.getChildStateLabelsWithEndLabel().list.length; tree.setNumChildStateNodes(numChildStateNodes); return tree; } diff --git a/db/rule-util.js b/db/rule-util.js index a5d954165..9872b79e0 100644 --- a/db/rule-util.js +++ b/db/rule-util.js @@ -399,6 +399,16 @@ class RuleUtil { return this.isString(url) ? privateUrlRegex.test(url) : false; } + isValidIpV4(ipAddress) { + const ipV4Regex = /^\b(25[0-5]|2[0-4][0-9]|[1][0-9][0-9]|[1-9][0-9]|[0-9])(\.(25[0-5]|2[0-4][0-9]|[1][0-9][0-9]|[1-9][0-9]|[0-9])){3}$/; + return this.isString(ipAddress) ? ipV4Regex.test(ipAddress) : false; + } + + isValidIpV6(ipAddress) { + const ipV6Regex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/; + return this.isString(ipAddress) ? ipV6Regex.test(ipAddress) : false; + } + validateRestFunctionsUrlWhitelistData(userAddr, data, newData, getValue) { const PathUtil = require('../common/path-util'); if (getValue(PathUtil.getDevelopersRestFunctionsUserWhitelistUserPath(userAddr)) !== true) { diff --git a/db/state-node.js b/db/state-node.js index 93e95a1fd..fe1ec63f2 100644 --- a/db/state-node.js +++ b/db/state-node.js @@ -24,6 +24,7 @@ class StateNode { this.treeHeight = 0; this.treeSize = 0; this.treeBytes = 0; + this.treeMaxSiblings = 0; } reset() { @@ -38,10 +39,11 @@ class StateNode { this.setTreeHeight(0); this.setTreeSize(0); this.setTreeBytes(0); + this.setTreeMaxSiblings(0); } static _create( - version, label, isLeaf, value, proofHash, treeHeight, treeSize, treeBytes) { + version, label, isLeaf, value, proofHash, treeHeight, treeSize, treeBytes, treeMaxSiblings) { const node = new StateNode(version); node.setLabel(label); node.setIsLeaf(isLeaf); @@ -50,6 +52,7 @@ class StateNode { node.setTreeHeight(treeHeight); node.setTreeSize(treeSize); node.setTreeBytes(treeBytes); + node.setTreeMaxSiblings(treeMaxSiblings); return node; } @@ -58,7 +61,7 @@ class StateNode { const versionToSet = version ? version : this.version; const cloned = StateNode._create( versionToSet, this.label, this.isLeaf, this.value, this.proofHash, - this.treeHeight, this.treeSize, this.treeBytes); + this.treeHeight, this.treeSize, this.treeBytes, this.treeMaxSiblings); if (!this.getIsLeaf()) { cloned.setRadixTree(this.radixTree.clone(versionToSet, cloned)); } @@ -69,6 +72,8 @@ class StateNode { // from this calculation, since their sizes can vary and affect the gas costs and // state proof hashes. // 4(isLeaf) + 132(proofHash) + 8(treeHeight) + 8(treeSize) + 8(treeBytes) = 160 + // NOTE(platfowner): treeMaxSiblings is not included in the node bytes computation since + // it was added later (see https://github.com/ainblockchain/ain-blockchain/issues/1067). computeNodeBytes() { return sizeof(this.value) + 160; } @@ -128,6 +133,9 @@ class StateNode { */ toStateSnapshot(options) { const isShallow = options && options.isShallow; + const isPartial = options && options.isPartial; + const lastEndLabel = (options && options.lastEndLabel !== undefined) ? + options.lastEndLabel : null; const includeVersion = options && options.includeVersion; const includeTreeInfo = options && options.includeTreeInfo; const includeProof = options && options.includeProof; @@ -135,7 +143,12 @@ class StateNode { return this.getValue(); } const obj = {}; - for (const label of this.getChildLabels()) { + const childLabelsWithEndLabel = this.getChildLabelsWithEndLabel(isPartial, lastEndLabel); + if (isPartial) { + obj[`${StateLabelProperties.END_LABEL}`] = childLabelsWithEndLabel.endLabel; + } + for (let i = 0; i < childLabelsWithEndLabel.list.length; i++) { + const label = childLabelsWithEndLabel.list[i]; const childNode = this.getChild(label); if (childNode.getIsLeaf()) { obj[label] = childNode.toStateSnapshot(options); @@ -148,14 +161,19 @@ class StateNode { obj[`${StateLabelProperties.TREE_HEIGHT}:${label}`] = childNode.getTreeHeight(); obj[`${StateLabelProperties.TREE_SIZE}:${label}`] = childNode.getTreeSize(); obj[`${StateLabelProperties.TREE_BYTES}:${label}`] = childNode.getTreeBytes(); + obj[`${StateLabelProperties.TREE_MAX_SIBLINGS}:${label}`] = childNode.getTreeMaxSiblings(); } if (includeProof) { obj[`${StateLabelProperties.STATE_PROOF_HASH}:${label}`] = childNode.getProofHash(); } } else { - obj[label] = isShallow ? + obj[label] = (isShallow || isPartial) ? { [`${StateLabelProperties.STATE_PROOF_HASH}`]: childNode.getProofHash() } : childNode.toStateSnapshot(options); + if (isPartial) { + const serial = childLabelsWithEndLabel.serialList[i]; + obj[label][`${StateLabelProperties.SERIAL}`] = serial; + } } } if (includeVersion) { @@ -167,6 +185,7 @@ class StateNode { obj[`${StateLabelProperties.TREE_HEIGHT}`] = this.getTreeHeight(); obj[`${StateLabelProperties.TREE_SIZE}`] = this.getTreeSize(); obj[`${StateLabelProperties.TREE_BYTES}`] = this.getTreeBytes(); + obj[`${StateLabelProperties.TREE_MAX_SIBLINGS}`] = this.getTreeMaxSiblings(); } if (includeProof) { obj[`${StateLabelProperties.STATE_PROOF_HASH}`] = this.getProofHash(); @@ -353,11 +372,19 @@ class StateNode { } getChildLabels() { - return [...this.radixTree.getChildStateLabels()]; + return this.getChildLabelsWithEndLabel().list; + } + + getChildLabelsWithEndLabel(isPartial = false, lastEndLabel = null) { + return this.radixTree.getChildStateLabelsWithEndLabel(isPartial, lastEndLabel); } getChildNodes() { - return [...this.radixTree.getChildStateNodes()]; + return this.getChildNodesWithEndLabel().list; + } + + getChildNodesWithEndLabel(isPartial = false, lastEndLabel = null) { + return this.radixTree.getChildStateNodesWithEndLabel(isPartial, lastEndLabel); } hasChildren() { @@ -415,6 +442,14 @@ class StateNode { this.treeBytes = treeBytes; } + getTreeMaxSiblings() { + return this.treeMaxSiblings; + } + + setTreeMaxSiblings(treeMaxSiblings) { + this.treeMaxSiblings = treeMaxSiblings; + } + /** * Returns newly buildt proof hash. If updatedChildLabel is given, it signifies that * only the child of the given child label among the children is not up-to-date now, @@ -434,7 +469,8 @@ class StateNode { proofHash, treeHeight: 0, treeSize: 1, - treeBytes: nodeBytes + treeBytes: nodeBytes, + treeMaxSiblings: 1 }; } else { if (shouldRebuildRadixInfo) { @@ -448,7 +484,8 @@ class StateNode { proofHash: this.radixTree.getRootProofHash(), treeHeight: 1 + this.radixTree.getRootTreeHeight(), treeSize: 1 + this.radixTree.getRootTreeSize(), - treeBytes: nodeBytes + this.radixTree.getRootTreeBytes() + treeBytes: nodeBytes + this.radixTree.getRootTreeBytes(), + treeMaxSiblings: Math.max(this.numChildren(), this.radixTree.getRootTreeMaxSiblings()) }; } } @@ -459,6 +496,7 @@ class StateNode { this.setTreeHeight(treeInfo.treeHeight); this.setTreeSize(treeInfo.treeSize); this.setTreeBytes(treeInfo.treeBytes); + this.setTreeMaxSiblings(treeInfo.treeMaxSiblings); } verifyStateInfo(updatedChildLabel = null) { @@ -466,7 +504,8 @@ class StateNode { return this.getProofHash() === treeInfo.proofHash && this.getTreeHeight() === treeInfo.treeHeight && this.getTreeSize() === treeInfo.treeSize && - this.getTreeBytes() === treeInfo.treeBytes; + this.getTreeBytes() === treeInfo.treeBytes && + this.getTreeMaxSiblings() === treeInfo.treeMaxSiblings; } getProofOfStateNode(childLabel = null, childProof = null) { diff --git a/db/state-util.js b/db/state-util.js index 7c8cf33a3..e271dec64 100644 --- a/db/state-util.js +++ b/db/state-util.js @@ -41,9 +41,13 @@ const WRITE_RULE_ID_TOKEN_WHITELIST_BASE = [ 'String', // type casting 'Boolean', // type casting ]; -const WRITE_RULE_PUNC_TOKEN_BLACKLIST = [ +const WRITE_RULE_KEYWORD_TOKEN_BLACKLIST_SET = new Set([ + 'function', +]); +const WRITE_RULE_PUNC_TOKEN_BLACKLIST_SET = new Set([ '=', // assignment -]; + '=>', // arrow function +]); function isEmptyNode(node) { return node.getIsLeaf() && node.getValue() === null; @@ -285,6 +289,13 @@ function getTopLevelIdTokens(tokenList) { }).map((token) => token.value); } +/** + * Extract keyword tokens from the given token list. + */ +function getKeywordTokens(tokenList) { + return tokenList.filter((token) => token.type === 'Keyword').map((token) => token.value); +} + /** * Extract punctuator tokens from the given token list. */ @@ -315,12 +326,18 @@ function isValidWriteRule(variableLabels, ruleString) { return false; } } - const puncTokenBlacklistSet = new Set([ - ...WRITE_RULE_PUNC_TOKEN_BLACKLIST - ]); + const keywordTokens = getKeywordTokens(tokenList); + for (const token of keywordTokens) { + if (WRITE_RULE_KEYWORD_TOKEN_BLACKLIST_SET.has(token)) { + logger.info( + `[${LOG_HEADER}] Rule includes a not-allowed keyword token (${token}) ` + + `in rule string: ${ruleString}`); + return false; + } + } const puncTokens = getPuncTokens(tokenList); for (const token of puncTokens) { - if (puncTokenBlacklistSet.has(token)) { + if (WRITE_RULE_PUNC_TOKEN_BLACKLIST_SET.has(token)) { logger.info( `[${LOG_HEADER}] Rule includes a not-allowed punctuator token (${token}) ` + `in rule string: ${ruleString}`); diff --git a/deploy_blockchain_genesis_gcp.sh b/deploy_blockchain_genesis_gcp.sh index 495359696..d2360562c 100644 --- a/deploy_blockchain_genesis_gcp.sh +++ b/deploy_blockchain_genesis_gcp.sh @@ -331,11 +331,10 @@ for node_index in `seq 0 $(( $NUM_NODES - 1 ))`; do if [[ $node_index -ge 5 ]]; then JSON_RPC_OPTION="--json-rpc" - UPDATE_FRONT_DB_OPTION="--update-front-db" else JSON_RPC_OPTION="" - UPDATE_FRONT_DB_OPTION="" fi + UPDATE_FRONT_DB_OPTION="--update-front-db" if [[ $node_index -ge 5 ]] && [[ $node_index -lt 8 ]]; then REST_FUNC_OPTION="--rest-func" else diff --git a/deploy_blockchain_incremental_gcp.sh b/deploy_blockchain_incremental_gcp.sh index 5febb0fd2..9c7385b24 100644 --- a/deploy_blockchain_incremental_gcp.sh +++ b/deploy_blockchain_incremental_gcp.sh @@ -218,11 +218,10 @@ function deploy_node() { if [[ $node_index -ge 5 ]]; then JSON_RPC_OPTION="--json-rpc" - UPDATE_FRONT_DB_OPTION="--update-front-db" else JSON_RPC_OPTION="" - UPDATE_FRONT_DB_OPTION="" fi + UPDATE_FRONT_DB_OPTION="--update-front-db" if [[ $node_index -ge 5 ]] && [[ $node_index -lt 8 ]]; then REST_FUNC_OPTION="--rest-func" else diff --git a/deploy_blockchain_sandbox_gcp.sh b/deploy_blockchain_sandbox_gcp.sh index bbaa44b8b..e232deda5 100644 --- a/deploy_blockchain_sandbox_gcp.sh +++ b/deploy_blockchain_sandbox_gcp.sh @@ -410,21 +410,22 @@ printf "KEEP_DATA_OPTION=$KEEP_DATA_OPTION\n" node_index=$START_NODE_IDX while [ $node_index -le $END_NODE_IDX ]; do + NODE_TARGET_ADDR=NODE_${node_index}_TARGET_ADDR + NODE_ZONE=NODE_${node_index}_ZONE + printf "\n\n##########################\n# Starting parent node $node_index #\n##########################\n\n" + if [[ $node_index -ge 5 ]]; then JSON_RPC_OPTION="--json-rpc" - UPDATE_FRONT_DB_OPTION="--update-front-db" else JSON_RPC_OPTION="" - UPDATE_FRONT_DB_OPTION="" fi + UPDATE_FRONT_DB_OPTION="--update-front-db" if [[ $node_index -ge 5 ]] && [[ $node_index -lt 8 ]]; then REST_FUNC_OPTION="--rest-func" else REST_FUNC_OPTION="" fi - NODE_TARGET_ADDR=NODE_${node_index}_TARGET_ADDR - NODE_ZONE=NODE_${node_index}_ZONE printf "KEEP_CODE_OPTION=$KEEP_CODE_OPTION\n" printf "KEEP_DATA_OPTION=$KEEP_DATA_OPTION\n" diff --git a/deploy_docker.sh b/deploy_docker.sh index b18a2560b..2c9379dc2 100644 --- a/deploy_docker.sh +++ b/deploy_docker.sh @@ -1,47 +1,29 @@ #!/bin/bash -if [[ "$#" -lt 1 ]]; then - printf "Usage: bash deploy_docker.sh [dev|staging|sandbox|exp|spring|summer|mainnet]\n" - printf "Example: bash deploy_docker.sh dev\n" +if [[ "$#" -lt 0 ]]; then + printf "Usage: bash deploy_docker.sh\n" + printf "Example: bash deploy_docker.sh\n" printf "\n" exit fi printf "\n[[[[[ deploy_docker.sh ]]]]]\n\n" -if [[ "$1" != 'dev' ]] && [[ "$1" != 'staging' ]] && [[ "$1" != 'sandbox' ]] && [[ "$1" != 'exp' ]] && [[ "$1" != 'spring' ]] && [[ "$1" != 'summer' ]] && [[ "$1" != 'mainnet' ]]; then - printf "Invalid season argument: $1\n" - exit -fi - -SEASON="$1" - # Get confirmation. -if [[ "$SEASON" = "mainnet" ]]; then - printf "\n" - printf "Do you want to proceed for $SEASON? Enter [mainnet]: " - read CONFIRM - printf "\n\n" - if [[ ! $CONFIRM = "mainnet" ]] - then - [[ "$0" = "$BASH_SOURCE" ]] && exit 1 || return 1 # handle exits from shell or function but don't exit interactive shell - fi -else - printf "\n" - read -p "Do you want to proceed for $SEASON? [y/N]: " -n 1 -r - printf "\n\n" - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - [[ "$0" = "$BASH_SOURCE" ]] && exit 1 || return 1 # handle exits from shell or function but don't exit interactive shell - fi +printf "\n" +printf "Do you want to proceed? Enter [deploy]: " +read CONFIRM +printf "\n\n" +if [[ ! $CONFIRM = "deploy" ]] +then + [[ "$0" = "$BASH_SOURCE" ]] && exit 1 || return 1 # handle exits from shell or function but don't exit interactive shell fi -IMAGE_NAME=ainblockchain/ain-blockchain:$SEASON PACKAGE_VERSION=$(jq -r '.version' < package.json) -IMAGE_NAME_WITH_VERSION=$IMAGE_NAME-$PACKAGE_VERSION +IMAGE_NAME=ainblockchain/ain-blockchain:$PACKAGE_VERSION +IMAGE_NAME_LATEST=ainblockchain/ain-blockchain:latest docker login -docker build -t $IMAGE_NAME --build-arg SEASON=$SEASON . -docker tag $IMAGE_NAME $IMAGE_NAME_WITH_VERSION -docker push $IMAGE_NAME -docker push $IMAGE_NAME_WITH_VERSION -docker image rm $IMAGE_NAME -docker image rm $IMAGE_NAME_WITH_VERSION +docker buildx create --use --driver docker-container +docker buildx build --push --platform linux/amd64,linux/arm64 -t $IMAGE_NAME . +docker buildx build --push --platform linux/amd64,linux/arm64 -t $IMAGE_NAME_LATEST . +docker buildx rm diff --git a/deploy_test_gcp.sh b/deploy_test_gcp.sh index 909bbaa83..c604d88bf 100644 --- a/deploy_test_gcp.sh +++ b/deploy_test_gcp.sh @@ -144,7 +144,7 @@ function deploy_test() { if [[ $FOREGROUND_OPTION = "--fg" ]]; then TEST_CMD="cd ./ain-blockchain; yarn run ${testing_option}" else - TEST_CMD="cd ./ain-blockchain; nohup yarn run ${testing_option} > test_log.txt &" + TEST_CMD="cd ./ain-blockchain; nohup yarn run ${testing_option} >test_log.txt 2>&1 &" fi printf "\nTEST_CMD=$TEST_CMD\n\n" gcloud compute ssh ${test_target_addr} --command "$TEST_CMD" --project $PROJECT_ID --zone ${TEST_ZONE} diff --git a/inject_account_gcp.js b/inject_account_gcp.js index c607ffaba..2946406e5 100644 --- a/inject_account_gcp.js +++ b/inject_account_gcp.js @@ -5,12 +5,13 @@ const ainUtil = require('@ainblockchain/ain-util'); const { BlockchainConsts } = require('./common/constants'); const { sleep } = require('./common/common-util'); const prompt = require('prompt'); +const { JSON_RPC_METHODS } = require('./json_rpc/constants'); async function sendGetBootstrapPubKeyRequest(endpointUrl) { return await axios.post( `${endpointUrl}/json-rpc`, { - method: 'ain_getBootstrapPubKey', + method: JSON_RPC_METHODS.AIN_GET_BOOTSTRAP_PUB_KEY, params: { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, }, @@ -103,13 +104,13 @@ async function injectAccount(endpointUrl, accountInjectionOption) { const params = {}; switch (accountInjectionOption) { case '--private-key': - method = 'ain_injectAccountFromPrivateKey'; + method = JSON_RPC_METHODS.AIN_INJECT_ACCOUNT_FROM_PRIVATE_KEY; Object.assign(params, { encryptedPrivateKey: await ainUtil.encryptWithPublicKey(bootstrapPubKey, input.privateKey) }) break; case '--keystore': - method = 'ain_injectAccountFromKeystore'; + method = JSON_RPC_METHODS.AIN_INJECT_ACCOUNT_FROM_KEYSTORE; const keystore = JSON.stringify(JSON.parse(fs.readFileSync(input.keystorePath))); Object.assign(params, { encryptedKeystore: await ainUtil.encryptWithPublicKey(bootstrapPubKey, keystore) @@ -119,7 +120,7 @@ async function injectAccount(endpointUrl, accountInjectionOption) { }) break; case '--mnemonic': - method = 'ain_injectAccountFromHDWallet'; + method = JSON_RPC_METHODS.AIN_INJECT_ACCOUNT_FROM_HD_WALLET; Object.assign(params, { encryptedMnemonic: await ainUtil.encryptWithPublicKey(bootstrapPubKey, input.mnemonic), index: input.index diff --git a/json_rpc/account.js b/json_rpc/account.js index 923e861de..c7773239e 100644 --- a/json_rpc/account.js +++ b/json_rpc/account.js @@ -4,10 +4,11 @@ const { } = require('../common/constants'); const PathUtil = require('../common/path-util'); const JsonRpcUtil = require('./json-rpc-util'); +const { JSON_RPC_METHODS } = require('./constants'); module.exports = function getAccountApis(node) { return { - ain_getAddress: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_ADDRESS]: function(args, done) { const beginTime = Date.now(); const result = node.account ? node.account.address : null; const latency = Date.now() - beginTime; @@ -15,7 +16,7 @@ module.exports = function getAccountApis(node) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - ain_getBalance: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_BALANCE]: function(args, done) { const beginTime = Date.now(); const address = args.address; const balance = node.db.getValue(PathUtil.getAccountBalancePath(address)) || 0; @@ -24,7 +25,7 @@ module.exports = function getAccountApis(node) { done(null, JsonRpcUtil.addProtocolVersion({ result: balance })); }, - ain_getNonce: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_NONCE]: function(args, done) { const beginTime = Date.now(); const result = node.getNonceForAddr(args.address, args.from === 'pending'); const latency = Date.now() - beginTime; @@ -32,7 +33,7 @@ module.exports = function getAccountApis(node) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - ain_getTimestamp: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_TIMESTAMP]: function(args, done) { const beginTime = Date.now(); const result = node.getTimestampForAddr(args.address, args.from === 'pending'); const latency = Date.now() - beginTime; @@ -40,7 +41,7 @@ module.exports = function getAccountApis(node) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - ain_getValidatorInfo: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_VALIDATOR_INFO]: function(args, done) { const beginTime = Date.now(); const addr = args.address; const isWhitelisted = node.db.getValue(PathUtil.getConsensusProposerWhitelistAddrPath(addr)) || false; diff --git a/json_rpc/admin.js b/json_rpc/admin.js index 90d27d113..2c65c3e84 100644 --- a/json_rpc/admin.js +++ b/json_rpc/admin.js @@ -1,4 +1,3 @@ -const net = require('net'); const _ = require('lodash'); const { NodeConfigs, @@ -8,122 +7,244 @@ const { const { JsonRpcApiResultCode } = require('../common/result-code'); const CommonUtil = require('../common/common-util'); const JsonRpcUtil = require('./json-rpc-util'); +const { JSON_RPC_METHODS } = require('./constants'); -module.exports = function getApiAccessApis(node) { +function convertValue(valueFromNodeParam, value) { + if (CommonUtil.isBool(valueFromNodeParam)) { + return CommonUtil.convertEnvVarInputToBool(value); + } else if (CommonUtil.isIntegerString(valueFromNodeParam) || CommonUtil.isFloatString(valueFromNodeParam)) { + return Number(value); + } else if (CommonUtil.isArray(valueFromNodeParam) || CommonUtil.isWildcard(valueFromNodeParam)) { + return CommonUtil.getWhitelistFromString(value); + } else { + return value; + } +} + +module.exports = function getAdminApis(node) { return { - ain_getDevClientApiIpWhitelist: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_NODE_PARAM]: function(args, done) { const beginTime = Date.now(); const verified = node.verifyNodeAccountSignature(args.message, args.signature); + if (_.get(args.message, 'method') !== JSON_RPC_METHODS.AIN_GET_NODE_PARAM || !verified) { + const latency = Date.now() - beginTime; + trafficStatsManager.addEvent(TrafficEventTypes.ACCESS_CONTROL_GET, latency); + done(null, JsonRpcUtil.addProtocolVersion({ + result: false, + code: JsonRpcApiResultCode.ADMIN_FORBIDDEN_REQUEST, + message: 'Forbidden request.' + })); + return; + } + + const param = args.message.param; const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.ACCESS_CONTROL_GET, latency); - if (_.get(args.message, 'method') === 'ain_getDevClientApiIpWhitelist' && verified) { - done(null, JsonRpcUtil.addProtocolVersion({ result: NodeConfigs.DEV_CLIENT_API_IP_WHITELIST })); + if (NodeConfigs[param] === undefined) { + done(null, JsonRpcUtil.addProtocolVersion({ + result: false, + code: JsonRpcApiResultCode.ADMIN_PARAM_INVALID, + message: `Param [${param}] does not exist.` + })); } else { - done({ code: 403, message: 'Forbidden' }); + done(null, JsonRpcUtil.addProtocolVersion({ result: NodeConfigs[param] })); } }, - ain_addToDevClientApiIpWhitelist: function(args, done) { + [JSON_RPC_METHODS.AIN_SET_NODE_PARAM]: function(args, done) { const beginTime = Date.now(); const verified = node.verifyNodeAccountSignature(args.message, args.signature); - if (_.get(args.message, 'method') !== 'ain_addToDevClientApiIpWhitelist' || !verified) { + if (_.get(args.message, 'method') !== JSON_RPC_METHODS.AIN_SET_NODE_PARAM || !verified) { const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.ACCESS_CONTROL_SET, latency); - done({ code: 403, message: 'Forbidden' }); + done(null, JsonRpcUtil.addProtocolVersion({ + result: false, + code: JsonRpcApiResultCode.ADMIN_FORBIDDEN_REQUEST, + message: 'Forbidden request.' + })); return; } - if (CommonUtil.isWildcard(args.message.ip)) { - NodeConfigs.DEV_CLIENT_API_IP_WHITELIST = '*'; + + const param = args.message.param; + if (NodeConfigs[param] === undefined) { const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.ACCESS_CONTROL_SET, latency); done(null, JsonRpcUtil.addProtocolVersion({ - result: { - code: JsonRpcApiResultCode.SUCCESS, - message: `Added IP (${args.message.ip}) to whitelist: ${JSON.stringify(NodeConfigs.DEV_CLIENT_API_IP_WHITELIST)}` - } + result: false, + code: JsonRpcApiResultCode.ADMIN_PARAM_INVALID, + message: `Param [${param}] does not exist.` })); return; } - if (!net.isIPv4(args.message.ip)) { + + if (!CommonUtil.isString(args.message.value)) { const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.ACCESS_CONTROL_SET, latency); done(null, JsonRpcUtil.addProtocolVersion({ - result: { - code: JsonRpcApiResultCode.INVALID_IP, - message: `Invalid IP: ${args.message.ip}` - } + result: false, + code: JsonRpcApiResultCode.ADMIN_VALUE_NOT_A_STRING_TYPE, + message: `Value '${args.message.value}' is not a string.` })); return; } - if (!CommonUtil.isArray(NodeConfigs.DEV_CLIENT_API_IP_WHITELIST)) { - // NOTE(liayoo): if the whitelist was "*" previously, adding an IP will no longer "allow-all". - NodeConfigs.DEV_CLIENT_API_IP_WHITELIST = []; + + NodeConfigs[param] = convertValue(NodeConfigs[param], args.message.value); + // TODO(kriii): Add a refresher for some params. + const latency = Date.now() - beginTime; + trafficStatsManager.addEvent(TrafficEventTypes.ACCESS_CONTROL_SET, latency); + done(null, JsonRpcUtil.addProtocolVersion({ + result: true, + code: JsonRpcApiResultCode.SUCCESS, + message: `Param [${param}] is now set as: ${JSON.stringify(NodeConfigs[param])}` + })); + }, + + [JSON_RPC_METHODS.AIN_ADD_TO_WHITELIST_NODE_PARAM]: function(args, done) { + const beginTime = Date.now(); + const verified = node.verifyNodeAccountSignature(args.message, args.signature); + if (_.get(args.message, 'method') !== JSON_RPC_METHODS.AIN_ADD_TO_WHITELIST_NODE_PARAM || !verified) { + const latency = Date.now() - beginTime; + trafficStatsManager.addEvent(TrafficEventTypes.ACCESS_CONTROL_SET, latency); + done(null, JsonRpcUtil.addProtocolVersion({ + result: false, + code: JsonRpcApiResultCode.ADMIN_FORBIDDEN_REQUEST, + message: 'Forbidden request.' + })); + return; } - if (NodeConfigs.DEV_CLIENT_API_IP_WHITELIST.includes(args.message.ip)) { + + const param = args.message.param; + if (NodeConfigs[param] === undefined) { const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.ACCESS_CONTROL_SET, latency); done(null, JsonRpcUtil.addProtocolVersion({ - result: { - code: JsonRpcApiResultCode.IP_ALREADY_IN_WHITELIST, - message: `IP (${args.message.ip}) already in whitelist: ${JSON.stringify(NodeConfigs.DEV_CLIENT_API_IP_WHITELIST)}` - } + result: false, + code: JsonRpcApiResultCode.ADMIN_PARAM_INVALID, + message: `Param [${param}] does not exist.` })); - } else { - NodeConfigs.DEV_CLIENT_API_IP_WHITELIST.push(args.message.ip); + return; + } + if (!CommonUtil.isArray(NodeConfigs[param]) && + !CommonUtil.isWildcard(NodeConfigs[param])) { const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.ACCESS_CONTROL_SET, latency); done(null, JsonRpcUtil.addProtocolVersion({ - result: { - code: JsonRpcApiResultCode.SUCCESS, - message: `Added IP (${args.message.ip}) to whitelist: ${JSON.stringify(NodeConfigs.DEV_CLIENT_API_IP_WHITELIST)}` - } + result: false, + code: JsonRpcApiResultCode.ADMIN_PARAM_INVALID, + message: `Param [${param}] is not a whitelist` })); + return; } + + if (!CommonUtil.isString(args.message.value)) { + const latency = Date.now() - beginTime; + trafficStatsManager.addEvent(TrafficEventTypes.ACCESS_CONTROL_SET, latency); + done(null, JsonRpcUtil.addProtocolVersion({ + result: false, + code: JsonRpcApiResultCode.ADMIN_VALUE_NOT_A_STRING_TYPE, + message: `Value '${args.message.value}' is not a string.` + })); + return; + } + + if (!CommonUtil.isArray(NodeConfigs[param])) { + // NOTE(liayoo): if the whitelist was "*" previously, adding an IP will no longer "allow-all". + NodeConfigs[param] = []; + } + if (NodeConfigs[param].includes(args.message.value)) { + const latency = Date.now() - beginTime; + trafficStatsManager.addEvent(TrafficEventTypes.ACCESS_CONTROL_SET, latency); + done(null, JsonRpcUtil.addProtocolVersion({ + result: false, + code: JsonRpcApiResultCode.ADMIN_ALREADY_IN_WHITELIST, + message: `(${args.message.value}) already in whitelist [${param}]: ${JSON.stringify(NodeConfigs[param])}` + })); + return; + } + + if (CommonUtil.isWildcard(args.message.value)) { + NodeConfigs[param] = '*'; + } else { + NodeConfigs[param].push(args.message.value); + } + const latency = Date.now() - beginTime; + trafficStatsManager.addEvent(TrafficEventTypes.ACCESS_CONTROL_SET, latency); + done(null, JsonRpcUtil.addProtocolVersion({ + result: true, + code: JsonRpcApiResultCode.SUCCESS, + message: `Added (${args.message.value}) to whitelist [${param}]: ${JSON.stringify(NodeConfigs[param])}` + })); }, - ain_removeFromDevClientApiIpWhitelist: function(args, done) { + [JSON_RPC_METHODS.AIN_REMOVE_FROM_WHITELIST_NODE_PARAM]: function(args, done) { const beginTime = Date.now(); const verified = node.verifyNodeAccountSignature(args.message, args.signature); - if (_.get(args.message, 'method') !== 'ain_removeFromDevClientApiIpWhitelist' || !verified) { + if (_.get(args.message, 'method') !== JSON_RPC_METHODS.AIN_REMOVE_FROM_WHITELIST_NODE_PARAM || !verified) { const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.ACCESS_CONTROL_SET, latency); - done({ code: 403, message: 'Forbidden' }); + done(null, JsonRpcUtil.addProtocolVersion({ + result: false, + code: JsonRpcApiResultCode.ADMIN_FORBIDDEN_REQUEST, + message: 'Forbidden request.' + })); + return; + } + + const param = args.message.param; + if (NodeConfigs[param] === undefined) { + const latency = Date.now() - beginTime; + trafficStatsManager.addEvent(TrafficEventTypes.ACCESS_CONTROL_SET, latency); + done(null, JsonRpcUtil.addProtocolVersion({ + result: false, + code: JsonRpcApiResultCode.ADMIN_PARAM_INVALID, + message: `Param [${param}] does not exist.` + })); return; } - if (CommonUtil.isWildcard(args.message.ip) - && CommonUtil.isWildcard(NodeConfigs.DEV_CLIENT_API_IP_WHITELIST)) { - NodeConfigs.DEV_CLIENT_API_IP_WHITELIST = []; + if (!CommonUtil.isArray(NodeConfigs[param]) && + !CommonUtil.isWildcard(NodeConfigs[param])) { const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.ACCESS_CONTROL_SET, latency); done(null, JsonRpcUtil.addProtocolVersion({ - result: { - code: JsonRpcApiResultCode.SUCCESS, - message: `Removed IP (${args.message.ip}) from whitelist: ${JSON.stringify(NodeConfigs.DEV_CLIENT_API_IP_WHITELIST)}` - } + result: false, + code: JsonRpcApiResultCode.ADMIN_PARAM_INVALID, + message: `Param [${param}] is not a whitelist` })); return; } - if (!CommonUtil.isArray(NodeConfigs.DEV_CLIENT_API_IP_WHITELIST) || - !NodeConfigs.DEV_CLIENT_API_IP_WHITELIST.includes(args.message.ip)) { + + if (!CommonUtil.isString(args.message.value)) { const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.ACCESS_CONTROL_SET, latency); done(null, JsonRpcUtil.addProtocolVersion({ - result: { - code: JsonRpcApiResultCode.IP_NOT_IN_WHITELIST, - message: `IP (${args.message.ip}) not in whitelist: ${JSON.stringify(NodeConfigs.DEV_CLIENT_API_IP_WHITELIST)}` - } + result: false, + code: JsonRpcApiResultCode.ADMIN_VALUE_NOT_A_STRING_TYPE, + message: `Value '${args.message.value}' is not a string.` })); return; } - NodeConfigs.DEV_CLIENT_API_IP_WHITELIST = NodeConfigs.DEV_CLIENT_API_IP_WHITELIST - .filter((ip) => ip !== args.message.ip); + + if (CommonUtil.isWildcard(args.message.value)) { + NodeConfigs[param] = []; + } else if (!CommonUtil.isArray(NodeConfigs[param]) || + !NodeConfigs[param].includes(args.message.value)) { + const latency = Date.now() - beginTime; + trafficStatsManager.addEvent(TrafficEventTypes.ACCESS_CONTROL_SET, latency); + done(null, JsonRpcUtil.addProtocolVersion({ + result: false, + code: JsonRpcApiResultCode.ADMIN_NOT_IN_WHITELIST, + message: `(${args.message.value}) not in whitelist [${param}]: ${JSON.stringify(NodeConfigs[param])}` + })); + return; + } else { + NodeConfigs[param] = NodeConfigs[param].filter((value) => value !== args.message.value); + } const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.ACCESS_CONTROL_SET, latency); done(null, JsonRpcUtil.addProtocolVersion({ - result: { - code: JsonRpcApiResultCode.SUCCESS, - message: `Removed IP (${args.message.ip}) from whitelist: ${JSON.stringify(NodeConfigs.DEV_CLIENT_API_IP_WHITELIST)}` - } + result: true, + code: JsonRpcApiResultCode.SUCCESS, + message: `Removed (${args.message.value}) from whitelist [${param}]: ${JSON.stringify(NodeConfigs[param])}` })); }, } diff --git a/json_rpc/app.js b/json_rpc/app.js index c5219b8b8..ee2223783 100644 --- a/json_rpc/app.js +++ b/json_rpc/app.js @@ -3,15 +3,16 @@ const { trafficStatsManager, } = require('../common/constants'); const JsonRpcUtil = require('./json-rpc-util'); +const { JSON_RPC_METHODS } = require('./constants'); module.exports = function getAppApis(node) { return { - ain_validateAppName: function(args, done) { + [JSON_RPC_METHODS.AIN_VALIDATE_APP_NAME]: function(args, done) { const beginTime = Date.now(); - const result = node.validateAppName(args.app_name); + const retVal = node.validateAppName(args.app_name); const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_GET, latency); - done(null, JsonRpcUtil.addProtocolVersion({ result })); + done(null, JsonRpcUtil.addProtocolVersion(retVal)); }, }; }; diff --git a/json_rpc/block.js b/json_rpc/block.js index 424f6a351..dfa70b412 100644 --- a/json_rpc/block.js +++ b/json_rpc/block.js @@ -3,10 +3,11 @@ const { trafficStatsManager, } = require('../common/constants'); const JsonRpcUtil = require('./json-rpc-util'); +const { JSON_RPC_METHODS } = require('./constants'); module.exports = function getBlockApis(node) { return { - ain_getBlockList: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_BLOCK_LIST]: function(args, done) { const beginTime = Date.now(); const blocks = node.bc.getBlockList(args.from, args.to); const latency = Date.now() - beginTime; @@ -14,7 +15,7 @@ module.exports = function getBlockApis(node) { done(null, JsonRpcUtil.addProtocolVersion({ result: blocks })); }, - ain_getLastBlock: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_LAST_BLOCK]: function(args, done) { const beginTime = Date.now(); const result = node.bc.lastBlock(); const latency = Date.now() - beginTime; @@ -22,7 +23,7 @@ module.exports = function getBlockApis(node) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - ain_getLastBlockNumber: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_LAST_BLOCK_NUMBER]: function(args, done) { const beginTime = Date.now(); const result = node.bc.lastBlockNumber(); const latency = Date.now() - beginTime; @@ -30,7 +31,7 @@ module.exports = function getBlockApis(node) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - ain_getBlockHeadersList: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_BLOCK_HEADERS_LIST]: function(args, done) { const beginTime = Date.now(); const blocks = node.bc.getBlockList(args.from, args.to); const blockHeaders = []; @@ -42,7 +43,7 @@ module.exports = function getBlockApis(node) { done(null, JsonRpcUtil.addProtocolVersion({ result: blockHeaders })); }, - ain_getBlockByHash: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_BLOCK_BY_HASH]: function(args, done) { const beginTime = Date.now(); const block = node.bc.getBlockByHash(args.hash); if (block && !args.getFullTransactions) { @@ -53,7 +54,7 @@ module.exports = function getBlockApis(node) { done(null, JsonRpcUtil.addProtocolVersion({ result: block })); }, - ain_getBlockByNumber: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_BLOCK_BY_NUMBER]: function(args, done) { const beginTime = Date.now(); const block = node.bc.getBlockByNumber(args.number); if (!block || args.getFullTransactions) { @@ -68,7 +69,7 @@ module.exports = function getBlockApis(node) { } }, - ain_getProposerByHash: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_PROPOSER_BY_HASH]: function(args, done) { const beginTime = Date.now(); const block = node.bc.getBlockByHash(args.hash); const result = block ? block.proposer : null; @@ -77,7 +78,7 @@ module.exports = function getBlockApis(node) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - ain_getProposerByNumber: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_PROPOSER_BY_NUMBER]: function(args, done) { const beginTime = Date.now(); const block = node.bc.getBlockByNumber(args.number); const result = block ? block.proposer : null; @@ -86,7 +87,7 @@ module.exports = function getBlockApis(node) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - ain_getValidatorsByNumber: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_VALIDATORS_BY_NUMBER]: function(args, done) { const beginTime = Date.now(); const block = node.bc.getBlockByNumber(args.number); const result = block ? block.validators : null; @@ -95,7 +96,7 @@ module.exports = function getBlockApis(node) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - ain_getValidatorsByHash: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_VALIDATORS_BY_HASH]: function(args, done) { const beginTime = Date.now(); const block = node.bc.getBlockByHash(args.hash); const result = block ? block.validators : null; @@ -104,7 +105,7 @@ module.exports = function getBlockApis(node) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - ain_getBlockTransactionCountByHash: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_BLOCK_TRANSACTION_COUNT_BY_HASH]: function(args, done) { const beginTime = Date.now(); const block = node.bc.getBlockByHash(args.hash); const result = block ? block.transactions.length : null; @@ -113,7 +114,7 @@ module.exports = function getBlockApis(node) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - ain_getBlockTransactionCountByNumber: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_BLOCK_TRANSACTION_COUNT_BY_NUMBER]: function(args, done) { const beginTime = Date.now(); const block = node.bc.getBlockByNumber(args.number); const result = block ? block.transactions.length : null; diff --git a/json_rpc/constants.js b/json_rpc/constants.js new file mode 100644 index 000000000..73e8fd99b --- /dev/null +++ b/json_rpc/constants.js @@ -0,0 +1,74 @@ +const JSON_RPC_METHODS = { + AIN_ADD_TO_WHITELIST_NODE_PARAM: 'ain_addToWhitelistNodeParam', + AIN_CHECK_PROTOCOL_VERSION: 'ain_checkProtocolVersion', + AIN_EVAL_RULE: 'ain_evalRule', + AIN_EVAL_OWNER: 'ain_evalOwner', + AIN_GET: 'ain_get', + AIN_GET_ADDRESS: 'ain_getAddress', + AIN_GET_BALANCE: 'ain_getBalance', + AIN_GET_BLOCK_BY_HASH: 'ain_getBlockByHash', + AIN_GET_BLOCK_BY_NUMBER: 'ain_getBlockByNumber', + AIN_GET_BLOCK_HEADERS_LIST: 'ain_getBlockHeadersList', + AIN_GET_BLOCK_LIST: 'ain_getBlockList', + AIN_GET_BLOCK_TRANSACTION_COUNT_BY_HASH: 'ain_getBlockTransactionCountByHash', + AIN_GET_BLOCK_TRANSACTION_COUNT_BY_NUMBER: 'ain_getBlockTransactionCountByNumber', + AIN_GET_BOOTSTRAP_PUB_KEY: 'ain_getBootstrapPubKey', + AIN_GET_EVENT_HANDLER_CHANNEL_INFO: 'ain_getEventHandlerChannelInfo', + AIN_GET_EVENT_HANDLER_FILTER_INFO: 'ain_getEventHandlerFilterInfo', + AIN_GET_LAST_BLOCK: 'ain_getLastBlock', + AIN_GET_LAST_BLOCK_NUMBER: 'ain_getLastBlockNumber', + AIN_GET_NODE_PARAM: 'ain_getNodeParam', + AIN_GET_NONCE: 'ain_getNonce', + AIN_GET_PENDING_TRANSACTIONS: 'ain_getPendingTransactions', + AIN_GET_PROOF_HASH: 'ain_getProofHash', + AIN_GET_PROPOSER_BY_HASH: 'ain_getProposerByHash', + AIN_GET_PROPOSER_BY_NUMBER: 'ain_getProposerByNumber', + AIN_GET_PROTOCOL_VERSION: 'ain_getProtocolVersion', + AIN_GET_STATE_INFO: 'ain_getStateInfo', + AIN_GET_STATE_PROOF: 'ain_getStateProof', + AIN_GET_STATE_USAGE: 'ain_getStateUsage', + AIN_GET_TRANSACTION_BY_BLOCK_HASH_AND_INDEX: 'ain_getTransactionByBlockHashAndIndex', + AIN_GET_TRANSACTION_BY_BLOCK_NUMBER_AND_INDEX: 'ain_getTransactionByBlockNumberAndIndex', + AIN_GET_TRANSACTION_BY_HASH: 'ain_getTransactionByHash', + AIN_GET_TRANSACTION_POOL_SIZE_UTILIZATION: 'ain_getTransactionPoolSizeUtilization', + AIN_GET_TIMESTAMP: 'ain_getTimestamp', + AIN_GET_VALIDATOR_INFO: 'ain_getValidatorInfo', + AIN_GET_VALIDATORS_BY_HASH: 'ain_getValidatorsByHash', + AIN_GET_VALIDATORS_BY_NUMBER: 'ain_getValidatorsByNumber', + AIN_INJECT_ACCOUNT_FROM_HD_WALLET: 'ain_injectAccountFromHDWallet', + AIN_INJECT_ACCOUNT_FROM_KEYSTORE: 'ain_injectAccountFromKeystore', + AIN_INJECT_ACCOUNT_FROM_PRIVATE_KEY: 'ain_injectAccountFromPrivateKey', + AIN_MATCH_FUNCTION: 'ain_matchFunction', + AIN_MATCH_OWNER: 'ain_matchOwner', + AIN_MATCH_RULE: 'ain_matchRule', + AIN_REMOVE_FROM_WHITELIST_NODE_PARAM: 'ain_removeFromWhitelistNodeParam', + AIN_SEND_SIGNED_TRANSACTION: 'ain_sendSignedTransaction', + AIN_SEND_SIGNED_TRANSACTION_BATCH: 'ain_sendSignedTransactionBatch', + AIN_SET_NODE_PARAM: 'ain_setNodeParam', + AIN_VALIDATE_APP_NAME: 'ain_validateAppName', + NET_CONSENSUS_STATUS: 'net_consensusStatus', + NET_GET_CHAIN_ID: 'net_getChainId', + NET_GET_EVENT_HANDLER_NETWORK_INFO: 'net_getEventHandlerNetworkInfo', + NET_GET_NETWORK_ID: 'net_getNetworkId', + NET_LISTENING: 'net_listening', + NET_PEER_COUNT: 'net_peerCount', + NET_RAW_CONSENSUS_STATUS: 'net_rawConsensusStatus', + NET_SYNCING: 'net_syncing', + P2P_GET_PEER_CANDIDATE_INFO: 'p2p_getPeerCandidateInfo', +} + +const JSON_RPC_SET_METHOD_SET = new Set([ + JSON_RPC_METHODS.AIN_ADD_TO_WHITELIST_NODE_PARAM, + JSON_RPC_METHODS.AIN_INJECT_ACCOUNT_FROM_HD_WALLET, + JSON_RPC_METHODS.AIN_INJECT_ACCOUNT_FROM_KEYSTORE, + JSON_RPC_METHODS.AIN_INJECT_ACCOUNT_FROM_PRIVATE_KEY, + JSON_RPC_METHODS.AIN_REMOVE_FROM_WHITELIST_NODE_PARAM, + JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, + JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION_BATCH, + JSON_RPC_METHODS.AIN_SET_NODE_PARAM +]); + +module.exports = { + JSON_RPC_METHODS, + JSON_RPC_SET_METHOD_SET +}; diff --git a/json_rpc/database.js b/json_rpc/database.js index 7605adcca..92e2c630e 100644 --- a/json_rpc/database.js +++ b/json_rpc/database.js @@ -1,3 +1,4 @@ +const _ = require('lodash'); const { ReadDbOperations, TrafficEventTypes, @@ -6,57 +7,40 @@ const { const { JsonRpcApiResultCode } = require('../common/result-code'); const CommonUtil = require('../common/common-util'); const JsonRpcUtil = require('./json-rpc-util'); +const { JSON_RPC_METHODS } = require('./constants'); + +function handleGetRequest(args, node) { + switch (args.type) { + case ReadDbOperations.GET_VALUE: + return node.db.getValueWithError(args.ref, CommonUtil.toGetOptions(args, true)); + case ReadDbOperations.GET_RULE: + return node.db.getRuleWithError(args.ref, CommonUtil.toGetOptions(args, true)); + case ReadDbOperations.GET_FUNCTION: + return node.db.getFunctionWithError(args.ref, CommonUtil.toGetOptions(args, true)); + case ReadDbOperations.GET_OWNER: + return node.db.getOwnerWithError(args.ref, CommonUtil.toGetOptions(args, true)); + case ReadDbOperations.GET: + return node.db.getWithError(args.op_list); + default: + return { + result: null, + code: JsonRpcApiResultCode.GET_INVALID_OPERATION, + message: 'Invalid get operation' + }; + } +} module.exports = function getDatabaseApis(node) { return { - ain_get: function(args, done) { + [JSON_RPC_METHODS.AIN_GET]: function(args, done) { const beginTime = Date.now(); - let result; - let latency; - switch (args.type) { - case ReadDbOperations.GET_VALUE: - result = node.db.getValue(args.ref, CommonUtil.toGetOptions(args, true)); - latency = Date.now() - beginTime; - trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_GET, latency); - done(null, JsonRpcUtil.addProtocolVersion({ result })); - return; - case ReadDbOperations.GET_RULE: - result = node.db.getRule(args.ref, CommonUtil.toGetOptions(args, true)); - latency = Date.now() - beginTime; - trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_GET, latency); - done(null, JsonRpcUtil.addProtocolVersion({ result })); - return; - case ReadDbOperations.GET_FUNCTION: - result = node.db.getFunction(args.ref, CommonUtil.toGetOptions(args, true)); - latency = Date.now() - beginTime; - trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_GET, latency); - done(null, JsonRpcUtil.addProtocolVersion({ result })); - return; - case ReadDbOperations.GET_OWNER: - result = node.db.getOwner(args.ref, CommonUtil.toGetOptions(args, true)); - latency = Date.now() - beginTime; - trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_GET, latency); - done(null, JsonRpcUtil.addProtocolVersion({ result })); - return; - case ReadDbOperations.GET: - result = node.db.get(args.op_list); - latency = Date.now() - beginTime; - trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_GET, latency); - done(null, JsonRpcUtil.addProtocolVersion({ result })); - return; - default: - latency = Date.now() - beginTime; - trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_GET, latency); - done(null, JsonRpcUtil.addProtocolVersion({ - result: { - code: JsonRpcApiResultCode.GET_INVALID_OPERATION, - message: 'Invalid get operation' - } - })); - } + const retVal = handleGetRequest(args, node); + const latency = Date.now() - beginTime; + trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_GET, latency); + done(null, JsonRpcUtil.addProtocolVersion(retVal)); }, - ain_matchFunction: function(args, done) { + [JSON_RPC_METHODS.AIN_MATCH_FUNCTION]: function(args, done) { const beginTime = Date.now(); const result = node.db.matchFunction(args.ref, CommonUtil.toMatchOrEvalOptions(args, true)); @@ -65,7 +49,7 @@ module.exports = function getDatabaseApis(node) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - ain_matchRule: function(args, done) { + [JSON_RPC_METHODS.AIN_MATCH_RULE]: function(args, done) { const beginTime = Date.now(); const result = node.db.matchRule(args.ref, CommonUtil.toMatchOrEvalOptions(args, true)); const latency = Date.now() - beginTime; @@ -73,7 +57,7 @@ module.exports = function getDatabaseApis(node) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - ain_matchOwner: function(args, done) { + [JSON_RPC_METHODS.AIN_MATCH_OWNER]: function(args, done) { const beginTime = Date.now(); const result = node.db.matchOwner(args.ref, CommonUtil.toMatchOrEvalOptions(args, true)); const latency = Date.now() - beginTime; @@ -81,7 +65,7 @@ module.exports = function getDatabaseApis(node) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - ain_evalRule: function(args, done) { + [JSON_RPC_METHODS.AIN_EVAL_RULE]: function(args, done) { const beginTime = Date.now(); const auth = {}; if (args.address) { @@ -98,7 +82,7 @@ module.exports = function getDatabaseApis(node) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - ain_evalOwner: function(args, done) { + [JSON_RPC_METHODS.AIN_EVAL_OWNER]: function(args, done) { const beginTime = Date.now(); const auth = {}; if (args.address) { @@ -114,7 +98,7 @@ module.exports = function getDatabaseApis(node) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - ain_getStateProof: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_STATE_PROOF]: function(args, done) { const beginTime = Date.now(); const result = node.db.getStateProof(args.ref); const latency = Date.now() - beginTime; @@ -122,7 +106,7 @@ module.exports = function getDatabaseApis(node) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - ain_getProofHash: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_PROOF_HASH]: function(args, done) { const beginTime = Date.now(); const result = node.db.getProofHash(args.ref); const latency = Date.now() - beginTime; @@ -130,7 +114,7 @@ module.exports = function getDatabaseApis(node) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - ain_getStateInfo: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_STATE_INFO]: function(args, done) { const beginTime = Date.now(); const result = node.db.getStateInfo(args.ref); const latency = Date.now() - beginTime; @@ -138,7 +122,7 @@ module.exports = function getDatabaseApis(node) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - ain_getStateUsage: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_STATE_USAGE]: function(args, done) { const beginTime = Date.now(); const result = node.getStateUsageWithStakingInfo(args.app_name); const latency = Date.now() - beginTime; diff --git a/json_rpc/event-handler.js b/json_rpc/event-handler.js index 0b6d31618..7141ab6a8 100644 --- a/json_rpc/event-handler.js +++ b/json_rpc/event-handler.js @@ -3,11 +3,12 @@ const { trafficStatsManager, } = require('../common/constants'); const JsonRpcUtil = require('./json-rpc-util'); +const { JSON_RPC_METHODS } = require('./constants'); module.exports = function getEventHandlerApis(eventHandler) { return { // NOTE(cshcomcom): Async function doesn't need a done parameter. (Ref: https://www.npmjs.com/package/jayson#promises) - net_getEventHandlerNetworkInfo: async function(args) { + [JSON_RPC_METHODS.NET_GET_EVENT_HANDLER_NETWORK_INFO]: async function(args) { const beginTime = Date.now(); const result = await eventHandler.eventChannelManager.getNetworkInfo(); const latency = Date.now() - beginTime; @@ -15,7 +16,7 @@ module.exports = function getEventHandlerApis(eventHandler) { return JsonRpcUtil.addProtocolVersion({ result }); }, - ain_getEventHandlerFilterInfo: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_EVENT_HANDLER_FILTER_INFO]: function(args, done) { const beginTime = Date.now(); const result = eventHandler.getFilterInfo(); const latency = Date.now() - beginTime; @@ -23,7 +24,7 @@ module.exports = function getEventHandlerApis(eventHandler) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - ain_getEventHandlerChannelInfo: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_EVENT_HANDLER_CHANNEL_INFO]: function(args, done) { const beginTime = Date.now(); const result = eventHandler.eventChannelManager.getChannelInfo(); const latency = Date.now() - beginTime; diff --git a/json_rpc/index.js b/json_rpc/index.js index 69cb41279..c7dd0f60b 100644 --- a/json_rpc/index.js +++ b/json_rpc/index.js @@ -11,6 +11,7 @@ const getEventHandlerApis = require('./event-handler'); const getNetworkApis = require('./network'); const getTransactionApis = require('./transaction'); const getVersionApis = require('./version'); +const { JSON_RPC_METHODS } = require('./constants'); /** * Defines the list of funtions which are accessibly to clients through the @@ -41,9 +42,8 @@ module.exports = function getApis(node, p2pServer, eventHandler, minProtocolVers ...getVersionApis(minProtocolVersion, maxProtocolVersion), }); } else { - Object.assign(apis, { - p2p_getPeerCandidateInfo: getNetworkApis(node, p2pServer).p2p_getPeerCandidateInfo, - }); + Object.assign(apis, { [JSON_RPC_METHODS.P2P_GET_PEER_CANDIDATE_INFO]: + getNetworkApis(node, p2pServer).p2p_getPeerCandidateInfo }); } if (eventHandler !== null) { Object.assign(apis, getEventHandlerApis(eventHandler)); diff --git a/json_rpc/injection.js b/json_rpc/injection.js index 61d6c4381..95b1c2d0e 100644 --- a/json_rpc/injection.js +++ b/json_rpc/injection.js @@ -3,10 +3,11 @@ const { trafficStatsManager, } = require('../common/constants'); const JsonRpcUtil = require('./json-rpc-util'); +const { JSON_RPC_METHODS } = require('./constants'); module.exports = function getInjectionApis(node, p2pServer) { return { - ain_getBootstrapPubKey: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_BOOTSTRAP_PUB_KEY]: function(args, done) { const beginTime = Date.now(); const result = node.bootstrapAccount ? node.bootstrapAccount.public_key : null; const latency = Date.now() - beginTime; @@ -14,7 +15,7 @@ module.exports = function getInjectionApis(node, p2pServer) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - ain_injectAccountFromPrivateKey: async function(args, done) { + [JSON_RPC_METHODS.AIN_INJECT_ACCOUNT_FROM_PRIVATE_KEY]: async function(args, done) { const beginTime = Date.now(); let result = false; if (await node.injectAccountFromPrivateKey(args.encryptedPrivateKey)) { @@ -26,7 +27,7 @@ module.exports = function getInjectionApis(node, p2pServer) { return JsonRpcUtil.addProtocolVersion({ result }); }, - ain_injectAccountFromKeystore: async function(args, done) { + [JSON_RPC_METHODS.AIN_INJECT_ACCOUNT_FROM_KEYSTORE]: async function(args, done) { const beginTime = Date.now(); let result = false; if (await node.injectAccountFromKeystore(args.encryptedKeystore, args.encryptedPassword)) { @@ -38,7 +39,7 @@ module.exports = function getInjectionApis(node, p2pServer) { return JsonRpcUtil.addProtocolVersion({ result }); }, - ain_injectAccountFromHDWallet: async function(args, done) { + [JSON_RPC_METHODS.AIN_INJECT_ACCOUNT_FROM_HD_WALLET]: async function(args, done) { const beginTime = Date.now(); let result = false; if (await node.injectAccountFromHDWallet(args.encryptedMnemonic, args.index)) { diff --git a/json_rpc/network.js b/json_rpc/network.js index ff201bc27..68c82c310 100644 --- a/json_rpc/network.js +++ b/json_rpc/network.js @@ -4,10 +4,11 @@ const { trafficStatsManager, } = require('../common/constants'); const JsonRpcUtil = require('./json-rpc-util'); +const { JSON_RPC_METHODS } = require('./constants'); module.exports = function getNetworkApis(node, p2pServer) { return { - net_listening: function(args, done) { + [JSON_RPC_METHODS.NET_LISTENING]: function(args, done) { const beginTime = Date.now(); const peerCount = Object.keys(p2pServer.inbound).length; const result = !!peerCount; @@ -16,7 +17,7 @@ module.exports = function getNetworkApis(node, p2pServer) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - net_peerCount: function(args, done) { + [JSON_RPC_METHODS.NET_PEER_COUNT]: function(args, done) { const beginTime = Date.now(); const peerCount = Object.keys(p2pServer.inbound).length; const latency = Date.now() - beginTime; @@ -24,7 +25,7 @@ module.exports = function getNetworkApis(node, p2pServer) { done(null, JsonRpcUtil.addProtocolVersion({ result: peerCount })); }, - net_syncing: function(args, done) { + [JSON_RPC_METHODS.NET_SYNCING]: function(args, done) { const beginTime = Date.now(); const result = (node.state === BlockchainNodeStates.CHAIN_SYNCING); const latency = Date.now() - beginTime; @@ -34,7 +35,7 @@ module.exports = function getNetworkApis(node, p2pServer) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - net_getNetworkId: function(args, done) { + [JSON_RPC_METHODS.NET_GET_NETWORK_ID]: function(args, done) { const beginTime = Date.now(); const result = node.getBlockchainParam('genesis/network_id'); const latency = Date.now() - beginTime; @@ -42,7 +43,7 @@ module.exports = function getNetworkApis(node, p2pServer) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - net_getChainId: function(args, done) { + [JSON_RPC_METHODS.NET_GET_CHAIN_ID]: function(args, done) { const beginTime = Date.now(); const result = node.getBlockchainParam('genesis/chain_id'); const latency = Date.now() - beginTime; @@ -50,7 +51,7 @@ module.exports = function getNetworkApis(node, p2pServer) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - net_consensusStatus: function(args, done) { + [JSON_RPC_METHODS.NET_CONSENSUS_STATUS]: function(args, done) { const beginTime = Date.now(); const result = p2pServer.consensus.getStatus(); const latency = Date.now() - beginTime; @@ -58,7 +59,7 @@ module.exports = function getNetworkApis(node, p2pServer) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - net_rawConsensusStatus: function(args, done) { + [JSON_RPC_METHODS.NET_RAW_CONSENSUS_STATUS]: function(args, done) { const beginTime = Date.now(); const result = p2pServer.consensus.getRawStatus(); const latency = Date.now() - beginTime; @@ -66,7 +67,7 @@ module.exports = function getNetworkApis(node, p2pServer) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - p2p_getPeerCandidateInfo: function(args, done) { + [JSON_RPC_METHODS.P2P_GET_PEER_CANDIDATE_INFO]: function(args, done) { const beginTime = Date.now(); const result = p2pServer.client.getPeerCandidateInfo(); const latency = Date.now() - beginTime; diff --git a/json_rpc/transaction.js b/json_rpc/transaction.js index 0532e51b1..e13177f3b 100644 --- a/json_rpc/transaction.js +++ b/json_rpc/transaction.js @@ -8,10 +8,11 @@ const { const { JsonRpcApiResultCode } = require('../common/result-code'); const CommonUtil = require('../common/common-util'); const Transaction = require('../tx-pool/transaction'); +const { JSON_RPC_METHODS } = require('./constants'); module.exports = function getTransactionApis(node, p2pServer) { return { - ain_getPendingTransactions: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_PENDING_TRANSACTIONS]: function(args, done) { const beginTime = Date.now(); const result = node.tp.transactions; const latency = Date.now() - beginTime; @@ -19,7 +20,7 @@ module.exports = function getTransactionApis(node, p2pServer) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - ain_getTransactionPoolSizeUtilization: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_TRANSACTION_POOL_SIZE_UTILIZATION]: function(args, done) { const beginTime = Date.now(); const address = args.address; const txPoolSizeUtil = node.getTxPoolSizeUtilization(address); @@ -28,7 +29,7 @@ module.exports = function getTransactionApis(node, p2pServer) { done(null, JsonRpcUtil.addProtocolVersion({ result: txPoolSizeUtil })); }, - ain_getTransactionByHash: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_TRANSACTION_BY_HASH]: function(args, done) { const beginTime = Date.now(); const transactionInfo = node.getTransactionByHash(args.hash); const latency = Date.now() - beginTime; @@ -36,7 +37,7 @@ module.exports = function getTransactionApis(node, p2pServer) { done(null, JsonRpcUtil.addProtocolVersion({ result: transactionInfo })); }, - ain_getTransactionByBlockHashAndIndex: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_TRANSACTION_BY_BLOCK_HASH_AND_INDEX]: function(args, done) { const beginTime = Date.now(); let result = null; if (args.block_hash && Number.isInteger(args.index)) { @@ -55,7 +56,7 @@ module.exports = function getTransactionApis(node, p2pServer) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - ain_getTransactionByBlockNumberAndIndex: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_TRANSACTION_BY_BLOCK_NUMBER_AND_INDEX]: function(args, done) { const beginTime = Date.now(); let result = null; if (Number.isInteger(args.block_number) && Number.isInteger(args.index)) { @@ -74,26 +75,24 @@ module.exports = function getTransactionApis(node, p2pServer) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - ain_sendSignedTransaction: function(args, done) { + [JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION]: function(args, done) { const beginTime = Date.now(); const txBytesLimit = node.getBlockchainParam('resource/tx_bytes_limit'); if (sizeof(args) > txBytesLimit) { const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_SET, latency); done(null, JsonRpcUtil.addProtocolVersion({ - result: { - code: JsonRpcApiResultCode.TX_EXCEEDS_SIZE_LIMIT, - message: `Transaction size exceeds its limit: ${txBytesLimit} bytes.` - } + result: null, + code: JsonRpcApiResultCode.TX_EXCEEDS_SIZE_LIMIT, + message: `Transaction size exceeds its limit: ${txBytesLimit} bytes.` })); } else if (!args.tx_body || !args.signature) { const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_SET, latency); done(null, JsonRpcUtil.addProtocolVersion({ - result: { - code: JsonRpcApiResultCode.TX_MISSING_PROPERTIES, - message: `Missing properties.` - } + result: null, + code: JsonRpcApiResultCode.TX_MISSING_PROPERTIES, + message: 'Missing properties.' })); } else { const chainId = node.getBlockchainParam('genesis/chain_id'); @@ -102,50 +101,47 @@ module.exports = function getTransactionApis(node, p2pServer) { const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_SET, latency); done(null, JsonRpcUtil.addProtocolVersion({ - result: { - code: JsonRpcApiResultCode.TX_INVALID_FORMAT, - message: `Invalid transaction format.` - } + result: null, + code: JsonRpcApiResultCode.TX_INVALID_FORMAT, + message: 'Invalid transaction format.' })); } else { if (!NodeConfigs.LIGHTWEIGHT && NodeConfigs.ENABLE_EARLY_TX_SIG_VERIF && !Transaction.verifyTransaction(createdTx, chainId)) { done(null, JsonRpcUtil.addProtocolVersion({ - result: { - code: JsonRpcApiResultCode.TX_INVALID_SIGNATURE, - message: `Invalid transaction signature.` - } + result: null, + code: JsonRpcApiResultCode.TX_INVALID_SIGNATURE, + message: 'Invalid transaction signature.' })); + } else { + const result = p2pServer.executeAndBroadcastTransaction(createdTx); + const latency = Date.now() - beginTime; + trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_SET, latency); + done(null, JsonRpcUtil.addProtocolVersion({ result })); } - const result = p2pServer.executeAndBroadcastTransaction(createdTx); - const latency = Date.now() - beginTime; - trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_SET, latency); - done(null, JsonRpcUtil.addProtocolVersion({ result })); } } }, - ain_sendSignedTransactionBatch: function(args, done) { + [JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION_BATCH]: function(args, done) { const beginTime = Date.now(); const batchTxListSizeLimit = node.getBlockchainParam('resource/batch_tx_list_size_limit'); - if (!args.tx_list || !CommonUtil.isArray(args.tx_list)) { + if (!CommonUtil.isArray(args.tx_list) || CommonUtil.isEmpty(args.tx_list)) { const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_SET, latency); done(null, JsonRpcUtil.addProtocolVersion({ - result: { - code: JsonRpcApiResultCode.BATCH_INVALID_FORMAT, - message: `Invalid batch transaction format.` - } + result: null, + code: JsonRpcApiResultCode.BATCH_INVALID_FORMAT, + message: 'Invalid batch transaction format.' })); } else if (args.tx_list.length > batchTxListSizeLimit) { const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_SET, latency); done(null, JsonRpcUtil.addProtocolVersion({ - result: { - code: JsonRpcApiResultCode.BATCH_TX_LIST_EXCEEDS_SIZE_LIMIT, - message: `Batch transaction list size exceeds its limit: ${batchTxListSizeLimit}.` - } + result: null, + code: JsonRpcApiResultCode.BATCH_TX_LIST_EXCEEDS_SIZE_LIMIT, + message: `Batch transaction list size exceeds its limit: ${batchTxListSizeLimit}.` })); } else { const txBytesLimit = node.getBlockchainParam('resource/tx_bytes_limit'); @@ -157,20 +153,18 @@ module.exports = function getTransactionApis(node, p2pServer) { const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_SET, latency); done(null, JsonRpcUtil.addProtocolVersion({ - result: { - code: JsonRpcApiResultCode.BATCH_TX_EXCEEDS_SIZE_LIMIT, - message: `Transaction[${i}]'s size exceededs its limit: ${txBytesLimit} bytes.` - } + result: null, + code: JsonRpcApiResultCode.BATCH_TX_EXCEEDS_SIZE_LIMIT, + message: `Transaction[${i}]'s size exceededs its limit: ${txBytesLimit} bytes.` })); return; } else if (!tx.tx_body || !tx.signature) { const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_SET, latency); done(null, JsonRpcUtil.addProtocolVersion({ - result: { - code: JsonRpcApiResultCode.BATCH_TX_MISSING_PROPERTIES, - message: `Missing properties of transaction[${i}].` - } + result: null, + code: JsonRpcApiResultCode.BATCH_TX_MISSING_PROPERTIES, + message: `Missing properties of transaction[${i}].` })); return; } @@ -179,10 +173,9 @@ module.exports = function getTransactionApis(node, p2pServer) { const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_SET, latency); done(null, JsonRpcUtil.addProtocolVersion({ - result: { - code: JsonRpcApiResultCode.BATCH_TX_INVALID_FORMAT, - message: `Invalid format of transaction[${i}].` - } + result: null, + code: JsonRpcApiResultCode.BATCH_TX_INVALID_FORMAT, + message: `Invalid format of transaction[${i}].` })); return; } @@ -190,10 +183,9 @@ module.exports = function getTransactionApis(node, p2pServer) { NodeConfigs.ENABLE_EARLY_TX_SIG_VERIF && !Transaction.verifyTransaction(createdTx, chainId)) { done(null, JsonRpcUtil.addProtocolVersion({ - result: { - code: JsonRpcApiResultCode.BATCH_TX_INVALID_SIGNATURE, - message: `Invalid signature of transaction[${i}].` - } + result: null, + code: JsonRpcApiResultCode.BATCH_TX_INVALID_SIGNATURE, + message: `Invalid signature of transaction[${i}].` })); return; } diff --git a/json_rpc/version.js b/json_rpc/version.js index e0e8521cf..e4f7cea3a 100644 --- a/json_rpc/version.js +++ b/json_rpc/version.js @@ -6,10 +6,11 @@ const { } = require('../common/constants'); const { JsonRpcApiResultCode } = require('../common/result-code'); const JsonRpcUtil = require('./json-rpc-util'); +const { JSON_RPC_METHODS } = require('./constants'); module.exports = function getVersionApis(minProtocolVersion, maxProtocolVersion) { return { - ain_getProtocolVersion: function(args, done) { + [JSON_RPC_METHODS.AIN_GET_PROTOCOL_VERSION]: function(args, done) { const beginTime = Date.now(); const result = BlockchainConsts.CURRENT_PROTOCOL_VERSION; const latency = Date.now() - beginTime; @@ -17,7 +18,7 @@ module.exports = function getVersionApis(minProtocolVersion, maxProtocolVersion) done(null, JsonRpcUtil.addProtocolVersion({ result })); }, - ain_checkProtocolVersion: function(args, done) { + [JSON_RPC_METHODS.AIN_CHECK_PROTOCOL_VERSION]: function(args, done) { const beginTime = Date.now(); const version = args.protoVer; const coercedVer = semver.coerce(version); @@ -25,6 +26,7 @@ module.exports = function getVersionApis(minProtocolVersion, maxProtocolVersion) const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_GET, latency); done(null, JsonRpcUtil.addProtocolVersion({ + result: false, code: JsonRpcApiResultCode.PROTO_VERSION_NOT_SPECIFIED, message: 'Protocol version not specified.' })); @@ -32,6 +34,7 @@ module.exports = function getVersionApis(minProtocolVersion, maxProtocolVersion) const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_GET, latency); done(null, JsonRpcUtil.addProtocolVersion({ + result: false, code: JsonRpcApiResultCode.PROTO_VERSION_INVALID, message: 'Invalid protocol version.' })); @@ -40,6 +43,7 @@ module.exports = function getVersionApis(minProtocolVersion, maxProtocolVersion) const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_GET, latency); done(null, JsonRpcUtil.addProtocolVersion({ + result: false, code: JsonRpcApiResultCode.PROTO_VERSION_INCOMPATIBLE, message: 'Incompatible protocol version.' })); @@ -47,8 +51,8 @@ module.exports = function getVersionApis(minProtocolVersion, maxProtocolVersion) const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_GET, latency); done(null, JsonRpcUtil.addProtocolVersion({ + result: true, code: JsonRpcApiResultCode.SUCCESS, - result: 'Success' })); } }, diff --git a/node/index.js b/node/index.js index 4444ba50f..bf9c29aac 100644 --- a/node/index.js +++ b/node/index.js @@ -612,6 +612,7 @@ class BlockchainNode { tree_height: !CommonUtil.isEmpty(rawUsage) ? rawUsage[StateLabelProperties.TREE_HEIGHT] : 0, tree_size: !CommonUtil.isEmpty(rawUsage) ? rawUsage[StateLabelProperties.TREE_SIZE] : 0, tree_bytes: !CommonUtil.isEmpty(rawUsage) ? rawUsage[StateLabelProperties.TREE_BYTES] : 0, + tree_max_siblings: !CommonUtil.isEmpty(rawUsage) ? rawUsage[StateLabelProperties.TREE_MAX_SIBLINGS] : 0, }; const availableTreeBytes = appStake > 0 ? Math.max(0, appsStateBudget * appStakeRatio - usage.tree_bytes) : @@ -935,12 +936,12 @@ class BlockchainNode { resList.push(res); } } - if (isExecutionOnly) { - return; - } // Once successfully executed txs (when submitted to tx pool) can become invalid // after some blocks are created. Remove those transactions from tx pool. this.tp.removeInvalidTxsFromPool(invalidTransactions); + if (isExecutionOnly) { + return; + } const gasPriceUnit = this.getBlockchainParam('resource/gas_price_unit', blockNumber, baseDb.stateVersion); const { gasAmountTotal, gasCostTotal } = @@ -1065,7 +1066,7 @@ class BlockchainNode { if (NodeConfigs.UPDATE_NEW_FINAL_FRONT_DB_WITH_TX_POOL) { // Apply the txs from the tx pool to the new final front db. this.executeAndGetValidTransactions( - null, lastFinalizedBlock.number, lastFinalizedBlock.timestamp, this.db, true, ValueChangedEventSources.USER); + null, lastFinalizedBlock.number, lastFinalizedBlock.timestamp, this.db, true); } // Clean up block pool this.bp.cleanUpAfterFinalization(this.bc.lastBlock(), recordedInvalidBlocks); diff --git a/p2p/index.js b/p2p/index.js index 4ac6ae422..d03e85e19 100644 --- a/p2p/index.js +++ b/p2p/index.js @@ -10,7 +10,7 @@ const { DevFlags, BlockchainConsts, NodeConfigs, - MessageTypes, + P2pMessageTypes, BlockchainNodeStates, P2pNetworkStates, TrafficEventTypes, @@ -22,10 +22,9 @@ const { } = require('../common/constants'); const FileUtil = require('../common/file-util'); const P2pUtil = require('./p2p-util'); -const { - sendGetRequest -} = require('../common/network-util'); +const { sendGetRequest } = require('../common/network-util'); const { Block } = require('../blockchain/block'); +const { JSON_RPC_METHODS } = require('../json_rpc/constants'); class P2pClient { constructor(node, minProtocolVersion, maxProtocolVersion) { @@ -62,7 +61,7 @@ class P2pClient { } // 4. Start peer discovery process - await this.discoverPeerWithGuardingFlag(); + await this.discoverNewPeers(); this.setIntervalForPeerCandidatesConnection(); // 5. Set up blockchain node @@ -183,32 +182,13 @@ class P2pClient { return Object.fromEntries(outboundEntries); } - /** - * Returns P2p endpoint urls. - */ - getPeerP2pUrlList() { - const outboundEntries = Object.entries(this.outbound) - .filter(([, peer]) => { - const incomingPeers = - _.get(peer, 'peerInfo.networkStatus.connectionStatus.incomingPeers', []); - const maxInbound = _.get(peer, 'peerInfo.networkStatus.connectionStatus.maxInbound', 0); - return incomingPeers.length < maxInbound; - }) - .map(([address, peer]) => { - const p2pUrl = _.get(peer, 'peerInfo.networkStatus.urls.p2p.url'); - return [address, p2pUrl]; - }); - return Object.fromEntries(outboundEntries); - } - getPeerCandidateInfo() { return { address: this.server.getNodeAddress(), isAvailableForConnection: NodeConfigs.MAX_NUM_INBOUND_CONNECTION > Object.keys(this.server.inbound).length, networkStatus: this.server.getNetworkStatus(), - peerCandidateJsonRpcUrlList: this.getPeerCandidateJsonRpcUrlList(), - newPeerP2pUrlList: this.getPeerP2pUrlList() + peerCandidateJsonRpcUrlList: this.getPeerCandidateJsonRpcUrlList() } } @@ -331,41 +311,100 @@ class P2pClient { /** * Returns randomly picked connectable peers. Refer to details below: - * 1) Pick one if it is never queried. - * 2) Choose one in all peerCandidates if there no exists never queried peerCandidates. + * 1) Pick some if they are never queried. + * 2) Choose more candidates in all peerCandidates if there are not enough number of never queried + * peerCandidates. * 3) Use PEER_CANDIDATE_JSON_RPC_URL if there are no peerCandidates at all. */ - assignRandomPeerCandidate() { + assignRandomPeerCandidates() { if (this.peerCandidates.size === 0) { - return NodeConfigs.PEER_CANDIDATE_JSON_RPC_URL; + return [NodeConfigs.PEER_CANDIDATE_JSON_RPC_URL]; } else { - const notQueriedCandidateEntries = [...this.peerCandidates.entries()].filter(([, value]) => { + const notQueriedCandidates = [...this.peerCandidates.entries()].filter(([, value]) => { // NOTE(minsulee2): this gets stuck if the never queried node gets offline. To avoid this, // the node which queried more than 5 minutes ago can also be considered as notQueried. return value.queriedAt === null ? true : Date.now() - value.queriedAt > NodeConfigs.PEER_CANDIDATE_RETRY_THRESHOLD_MS; + }).map(([key,]) => { + return key; }); - if (notQueriedCandidateEntries.length > 0) { - return _.shuffle(notQueriedCandidateEntries)[0][0]; + if (notQueriedCandidates.length > 0) { + return _.shuffle(notQueriedCandidates); } else { - return _.shuffle(this.peerCandidates.keys())[0]; + return _.shuffle(Array.from(this.peerCandidates.keys())); + } + } + } + + getMyJsonRpcUrl() { + return _.get(this.server.urls, 'jsonRpc.url', ''); + } + + async sendP2pGetPeerCandidateInfoRequest(peerCandidateJsonRpcUrl) { + if (!P2pUtil.isValidJsonRpcUrl(peerCandidateJsonRpcUrl)) { + throw new Error(`Wrong peerCandidateJsonRpcUrl(${peerCandidateJsonRpcUrl})`); + } + const resp = await sendGetRequest( + peerCandidateJsonRpcUrl, JSON_RPC_METHODS.P2P_GET_PEER_CANDIDATE_INFO, { }); + const peerCandidateInfo = _.get(resp, 'data.result.result'); + if (!CommonUtil.isDict(peerCandidateInfo)) { + throw new Error(`Invalid peerCandidateInfo from peer candidate url (${peerCandidateInfo}).`); + } + return peerCandidateInfo; + } + + populatePeerCandidatesFromPeerCandidateInfo(peerCandidateInfo) { + const LOG_HEADER = 'populatePeerCandidatesFromPeerCandidateInfo'; + const jsonRpcUrl = _.get(peerCandidateInfo, 'networkStatus.urls.jsonRpc.url'); + const address = _.get(peerCandidateInfo, 'address'); + if (!P2pUtil.isValidJsonRpcUrl(jsonRpcUrl) || !CommonUtil.isCksumAddr(address)) { + const errorMsg = `[${LOG_HEADER}] peerCandidateInfo is invalid.` + + `(${JSON.stringify(peerCandidateInfo)})`; + logger.error(errorMsg); + throw new Error(errorMsg); + } + this.updatePeerCandidateInfo(jsonRpcUrl, address, Date.now()); + const myJsonRpcUrl = this.getMyJsonRpcUrl(); + const peerCandidateJsonRpcUrlList = _.get(peerCandidateInfo, 'peerCandidateJsonRpcUrlList', []); + Object.entries(peerCandidateJsonRpcUrlList).forEach(([address, url]) => { + if (!P2pUtil.areIdenticalUrls(url, myJsonRpcUrl) && !this.peerCandidates.has(url) && + P2pUtil.isValidJsonRpcUrl(url) && P2pUtil.checkPeerWhitelist(address)) { + this.updatePeerCandidateInfo(url, address, null); } + }); + } + + async tryToQueryAndConnectNewPeer(jsonRpcUrl) { + const LOG_HEADER = 'tryToQueryAndConnectNewPeer'; + try { + // 1. Ask peer candidate info + const peerCandidateInfo = await this.sendP2pGetPeerCandidateInfoRequest(jsonRpcUrl); + // 2. Update peerCandidate + this.populatePeerCandidatesFromPeerCandidateInfo(peerCandidateInfo); + // 3. Try to connect to peer candidates + await this.connectWithPeerCandidateInfo(peerCandidateInfo); + } catch (e) { + this.peerCandidates.delete(jsonRpcUrl); + logger.error(`[${LOG_HEADER}] ${e}`); } } - async discoverPeerWithGuardingFlag() { - const LOG_HEADER = 'discoverPeerWithGuardingFlag'; - if (!this.isConnectingToPeerCandidates) { + async discoverNewPeers() { + try { this.peerConnectionStartedAt = Date.now(); - try { + if (!this.isConnectingToPeerCandidates) { this.isConnectingToPeerCandidates = true; - const nextPeerCandidate = this.assignRandomPeerCandidate(); - await this.connectWithPeerCandidateUrl(nextPeerCandidate); - } catch (e) { - logger.error(`[${LOG_HEADER}] ${e}`); - } finally { - this.isConnectingToPeerCandidates = false; + const nextPeerCandidateJsonRpcUrlList = this.assignRandomPeerCandidates(); + for (const jsonRpcUrl of nextPeerCandidateJsonRpcUrlList) { + const maxNumberOfNewPeers = this.getMaxNumberOfNewPeers(); + if (maxNumberOfNewPeers === 0) { + break; + } + await this.tryToQueryAndConnectNewPeer(jsonRpcUrl); + } } + } finally { + this.isConnectingToPeerCandidates = false; } } @@ -434,7 +473,7 @@ class P2pClient { this.steadyIntervalCount = 0; this.disconnectRandomPeer(); this.updateP2pState(); - await this.discoverPeerWithGuardingFlag(); + await this.discoverNewPeers(); } } @@ -442,7 +481,7 @@ class P2pClient { this.intervalPeerCandidatesConnection = setInterval(async () => { this.updateP2pState(); if (this.p2pState === P2pNetworkStates.EXPANDING) { - await this.discoverPeerWithGuardingFlag(); + await this.discoverNewPeers(); } else if (this.p2pState === P2pNetworkStates.STEADY) { await this.tryReorgPeerConnections(); } @@ -513,7 +552,7 @@ class P2pClient { broadcastConsensusMessage(consensusMessage, tags = []) { tags.push(this.server.node.account.address); - const payload = P2pUtil.encapsulateMessage(MessageTypes.CONSENSUS, { message: consensusMessage, tags }); + const payload = P2pUtil.encapsulateMessage(P2pMessageTypes.CONSENSUS, { message: consensusMessage, tags }); if (!payload) { logger.error('The consensus msg cannot be broadcasted because of msg encapsulation failure.'); return; @@ -555,7 +594,7 @@ class P2pClient { logger.error(`[${LOG_HEADER}] Failed to get a peer for SNAPSHOT_CHUNK_REQUEST`); return; } - const payload = P2pUtil.encapsulateMessage(MessageTypes.SNAPSHOT_CHUNK_REQUEST, {}); + const payload = P2pUtil.encapsulateMessage(P2pMessageTypes.SNAPSHOT_CHUNK_REQUEST, {}); if (!payload) { logger.error(`[${LOG_HEADER}] The request for snapshot chunks couldn't be sent because ` + `of msg encapsulation failure.`); @@ -587,7 +626,7 @@ class P2pClient { logger.info(`[${LOG_HEADER}] Already sent a request with the same/higher lastBlockNumber`); return; } - const payload = P2pUtil.encapsulateMessage(MessageTypes.CHAIN_SEGMENT_REQUEST, { lastBlockNumber }); + const payload = P2pUtil.encapsulateMessage(P2pMessageTypes.CHAIN_SEGMENT_REQUEST, { lastBlockNumber }); if (!payload) { logger.error(`[${LOG_HEADER}] The request for chain segment couldn't be sent because ` + `of msg encapsulation failure.`); @@ -623,7 +662,7 @@ class P2pClient { return; } const payload = P2pUtil.encapsulateMessage( - MessageTypes.OLD_CHAIN_SEGMENT_REQUEST, { oldestBlockNumber }); + P2pMessageTypes.OLD_CHAIN_SEGMENT_REQUEST, { oldestBlockNumber }); if (!payload) { logger.error(`[${LOG_HEADER}] The request for old chain segment couldn't be sent because ` + `of msg encapsulation failure.`); @@ -634,7 +673,7 @@ class P2pClient { broadcastTransaction(transaction, tags = []) { tags.push(this.server.node.account.address); - const payload = P2pUtil.encapsulateMessage(MessageTypes.TRANSACTION, { transaction, tags }); + const payload = P2pUtil.encapsulateMessage(P2pMessageTypes.TRANSACTION, { transaction, tags }); if (!payload) { logger.error('The transaction cannot be broadcasted because of msg encapsulation failure.'); return; @@ -668,7 +707,7 @@ class P2pClient { logger.error('The signaure is not correctly generated. Discard the message!'); return false; } - const payload = P2pUtil.encapsulateMessage(MessageTypes.ADDRESS_REQUEST, + const payload = P2pUtil.encapsulateMessage(P2pMessageTypes.ADDRESS_REQUEST, { body: body, signature: signature }); if (!payload) { logger.error('The peerInfo message cannot be sent because of msg encapsulation failure.'); @@ -720,11 +759,11 @@ class P2pClient { switch (parsedMessage.type) { // NOTE(minsulee2): Now, a distribution of peer nodes are fused in the tracker and node. // To integrate the role, TrackerMessageTypes PEER_INFO_REQUEST and PEER_INFO_REPONSE will - // be moved from tracker into peer node and be combined into MessageTypes ADDRESS_RESPONSE - // and ADDRESS_REQUEST. - case MessageTypes.ADDRESS_RESPONSE: + // be moved from tracker into peer node and be combined into + // P2pMessageTypes ADDRESS_RESPONSE and ADDRESS_REQUEST. + case P2pMessageTypes.ADDRESS_RESPONSE: const dataVersionCheckForAddress = - this.server.checkDataProtoVer(dataProtoVer, MessageTypes.ADDRESS_RESPONSE); + this.server.checkDataProtoVer(dataProtoVer, P2pMessageTypes.ADDRESS_RESPONSE); if (dataVersionCheckForAddress < 0) { // TODO(minsulee2): need to convert message when updating ADDRESS_RESPONSE necessary. // this.convertAddressMessage(); @@ -772,7 +811,7 @@ class P2pClient { this.updateNodeInfoToTracker(); } break; - case MessageTypes.SNAPSHOT_CHUNK_RESPONSE: + case P2pMessageTypes.SNAPSHOT_CHUNK_RESPONSE: if (this.server.node.state !== BlockchainNodeStates.STATE_SYNCING && this.server.node.state !== BlockchainNodeStates.SERVING) { logger.error(`[${LOG_HEADER}] Not ready to process snapshot chunk response.\n` + @@ -782,7 +821,7 @@ class P2pClient { return; } const dataVersionCheckForSnapshotChunk = - this.server.checkDataProtoVer(dataProtoVer, MessageTypes.SNAPSHOT_CHUNK_RESPONSE); + this.server.checkDataProtoVer(dataProtoVer, P2pMessageTypes.SNAPSHOT_CHUNK_RESPONSE); if (dataVersionCheckForSnapshotChunk > 0) { logger.error(`[${LOG_HEADER}] CANNOT deal with higher data protocol ` + `version(${dataProtoVer}). Discard the SNAPSHOT_CHUNK_RESPONSE message.`); @@ -802,7 +841,7 @@ class P2pClient { `of chunkIndex ${chunkIndex} and numChunks ${numChunks}.`); await this.handleSnapshotChunk(chunk, chunkIndex, numChunks, blockNumber, socket); break; - case MessageTypes.CHAIN_SEGMENT_RESPONSE: + case P2pMessageTypes.CHAIN_SEGMENT_RESPONSE: if (this.server.node.state !== BlockchainNodeStates.CHAIN_SYNCING && this.server.node.state !== BlockchainNodeStates.SERVING) { logger.error(`[${LOG_HEADER}] Not ready to process chain segment response.\n` + @@ -812,7 +851,7 @@ class P2pClient { return; } const dataVersionCheckForChainSegment = - this.server.checkDataProtoVer(dataProtoVer, MessageTypes.CHAIN_SEGMENT_RESPONSE); + this.server.checkDataProtoVer(dataProtoVer, P2pMessageTypes.CHAIN_SEGMENT_RESPONSE); if (dataVersionCheckForChainSegment > 0) { logger.error(`[${LOG_HEADER}] CANNOT deal with higher data protocol ` + `version(${dataProtoVer}). Discard the CHAIN_SEGMENT_RESPONSE message.`); @@ -830,7 +869,7 @@ class P2pClient { `${JSON.stringify(chainSegment, null, 2)}`); await this.handleChainSegment(number, chainSegment, catchUpInfo, socket); break; - case MessageTypes.OLD_CHAIN_SEGMENT_RESPONSE: + case P2pMessageTypes.OLD_CHAIN_SEGMENT_RESPONSE: const oldChainSegment = _.get(parsedMessage, 'data.oldChainSegment'); const segmentSize = CommonUtil.isArray(oldChainSegment) ? oldChainSegment.length : 0; const fromBlockNumber = segmentSize > 0 ? oldChainSegment[0].number : -1; @@ -1137,26 +1176,7 @@ class P2pClient { }, NodeConfigs.P2P_WAIT_FOR_ADDRESS_TIMEOUT_MS); } - /** - * Checks validity of JSON-RPC endpoint url based on HOSTING_ENV. - * @param {string} url is an IPv4 ip address. - */ - isValidJsonRpcUrl(url) { - if (!CommonUtil.isString(url)) { - return false; - } - const JSON_RPC_PATH = '/json-rpc'; - const urlWithoutJsonRpc = - url.endsWith(JSON_RPC_PATH) ? url.slice(0, -JSON_RPC_PATH.length) : false; - if (!urlWithoutJsonRpc) { - return urlWithoutJsonRpc; - } else { - return NodeConfigs.HOSTING_ENV === 'local' ? CommonUtil.isValidPrivateUrl(urlWithoutJsonRpc) : - CommonUtil.isValidUrl(urlWithoutJsonRpc); - } - } - - setPeerCandidate(jsonRpcUrl, address, queriedAt) { + updatePeerCandidateInfo(jsonRpcUrl, address, queriedAt) { if (CommonUtil.isWildcard(NodeConfigs.PEER_WHITELIST) || (CommonUtil.isArray(NodeConfigs.PEER_WHITELIST) && NodeConfigs.PEER_WHITELIST.includes(address))) { @@ -1169,55 +1189,25 @@ class P2pClient { * @param {string} peerCandidateJsonRpcUrl should be something like * http(s)://xxx.xxx.xxx.xxx/json-rpc */ - async connectWithPeerCandidateUrl(peerCandidateJsonRpcUrl) { - const LOG_HEADER = 'connectWithPeerCandidateUrl'; + async connectWithPeerCandidateInfo(peerCandidateInfo) { + const LOG_HEADER = 'connectWithPeerCandidateInfo'; const myP2pUrl = _.get(this.server.urls, 'p2p.url', ''); - const myJsonRpcUrl = _.get(this.server.urls, 'jsonRpc.url', ''); - if (!peerCandidateJsonRpcUrl || peerCandidateJsonRpcUrl === '' || - P2pUtil.areIdenticalUrls(peerCandidateJsonRpcUrl, myJsonRpcUrl)) { - this.peerCandidates.delete(peerCandidateJsonRpcUrl); - return; - } - const resp = await sendGetRequest(peerCandidateJsonRpcUrl, 'p2p_getPeerCandidateInfo', { }); - const peerCandidateInfo = _.get(resp, 'data.result.result'); - if (!peerCandidateInfo) { - logger.error(`Invalid peer candidate info from peer candidate url ` + - `(${peerCandidateJsonRpcUrl}).`); - return; - } - // NOTE(platfowner): As peerCandidateUrl can be a domain name url with multiple nodes, - // use the json rpc url in response instead. - const jsonRpcUrlFromResp = _.get(peerCandidateInfo, 'networkStatus.urls.jsonRpc.url'); const address = _.get(peerCandidateInfo, 'address'); - if (!jsonRpcUrlFromResp) { - logger.error(`Invalid peer candidate json rpc url from peer candidate url ` + - `(${peerCandidateJsonRpcUrl}).`); - return; - } - if (jsonRpcUrlFromResp !== myJsonRpcUrl) { - this.setPeerCandidate(jsonRpcUrlFromResp, address, Date.now()); - } - const peerCandidateJsonRpcUrlList = _.get(peerCandidateInfo, 'peerCandidateJsonRpcUrlList', []); - Object.entries(peerCandidateJsonRpcUrlList).forEach(([address, url]) => { - if (url !== myJsonRpcUrl && !this.peerCandidates.has(url) && this.isValidJsonRpcUrl(url) && - P2pUtil.checkPeerWhitelist(address)) { - this.setPeerCandidate(url, address, null); - } - }); - const newPeerP2pUrlList = _.get(peerCandidateInfo, 'newPeerP2pUrlList', []); - const newPeerP2pUrlListWithoutMyUrl = Object.entries(newPeerP2pUrlList) - .filter(([address, p2pUrl]) => { - return P2pUtil.checkPeerWhitelist(address) && p2pUrl !== myP2pUrl; - }) - .map(([, p2pUrl]) => p2pUrl); const isAvailableForConnection = _.get(peerCandidateInfo, 'isAvailableForConnection'); + // NOTE(platfowner): As peerCandidateUrl can be a domain name url with multiple nodes, + // use the json rpc url in response instead. const peerCandidateP2pUrl = _.get(peerCandidateInfo, 'networkStatus.urls.p2p.url'); if (peerCandidateP2pUrl !== myP2pUrl && isAvailableForConnection && !this.outbound[address]) { - // NOTE(minsulee2): Add a peer candidate up on the list if it is not connected. - newPeerP2pUrlListWithoutMyUrl.push(peerCandidateP2pUrl); + logger.info(`[${LOG_HEADER}] Try to connect(${peerCandidateP2pUrl})`); + const addressFromOutbound = this.getAddrFromOutboundMapping(peerCandidateP2pUrl); + if (addressFromOutbound) { + logger.debug(`[${LOG_HEADER}] Node ${addressFromOutbound}(${peerCandidateP2pUrl}) is` + + `already a managed peer.`); + } else { + logger.info(`[${LOG_HEADER}] Connecting to peer(${peerCandidateP2pUrl})`); + this.connectToPeer(peerCandidateP2pUrl); + } } - logger.info(`[${LOG_HEADER}] Try to connect(${JSON.stringify(newPeerP2pUrlListWithoutMyUrl)})`); - this.connectWithPeerUrlList(_.shuffle(newPeerP2pUrlListWithoutMyUrl)); } setIsFirstNode(isFirstNode) { @@ -1319,19 +1309,6 @@ class P2pClient { return Math.max(0, NodeConfigs.TARGET_NUM_OUTBOUND_CONNECTION - totalConnections); } - connectWithPeerUrlList(newPeerP2pUrlList) { - const maxNumberOfNewPeers = this.getMaxNumberOfNewPeers(); - newPeerP2pUrlList.slice(0, maxNumberOfNewPeers).forEach((url) => { - const address = this.getAddrFromOutboundMapping(url); - if (address) { - logger.debug(`Node ${address}(${url}) is already a managed peer.`); - } else { - logger.info(`Connecting to peer(${url})`); - this.connectToPeer(url); - } - }); - } - disconnectFromPeers() { Object.values(this.outbound).forEach((node) => { node.socket.close(); @@ -1365,7 +1342,7 @@ class P2pClient { } updateStatusToPeer(socket, address) { - const payload = P2pUtil.encapsulateMessage(MessageTypes.PEER_INFO_UPDATE, this.getStatus()); + const payload = P2pUtil.encapsulateMessage(P2pMessageTypes.PEER_INFO_UPDATE, this.getStatus()); if (!payload) { logger.error('The message cannot be sent because of msg encapsulation failure.'); return; diff --git a/p2p/p2p-util.js b/p2p/p2p-util.js index d5218414a..d7b5e23ac 100644 --- a/p2p/p2p-util.js +++ b/p2p/p2p-util.js @@ -8,6 +8,7 @@ const logger = new (require('../logger'))('SERVER_UTIL'); const _ = require('lodash'); +const ip = require('ip'); const ainUtil = require('@ainblockchain/ain-util'); const { BlockchainConsts, @@ -156,6 +157,54 @@ class P2pUtil { return url1 === url2; } } + + static toHostname(url) { + try { + const fromUrl = new URL(url); + return fromUrl.hostname; + } catch (e) { + return null; + } + } + + static isValidIpAddress(ipAddress) { + return CommonUtil.isValidIpV4(ipAddress) || CommonUtil.isValidIpV6(ipAddress); + } + + /** + * Returns true if the socket ip address is the same as the given p2p url ip address, + * false otherwise. + * @param {string} ipAddressFromSocket can be either ipv4 or ipv6 socket._socket.remoteAddress. + * @param {string} ipAddressFromPeerInfo is peerInfo.networkStatus.urls.p2p.url. + */ + static checkIpAddressFromPeerInfo(ipAddressFromSocket, ipAddressFromPeerInfo) { + if (!P2pUtil.isValidIpAddress(ipAddressFromSocket) || + !P2pUtil.isValidIpAddress(ipAddressFromPeerInfo)) { + return false; + } else { + return ip.isEqual(ipAddressFromSocket, ipAddressFromPeerInfo); + } + } + + /** + * Checks validity of JSON-RPC endpoint url based on HOSTING_ENV. + * @param {string} url is json rpc endpoint url. + */ + static isValidJsonRpcUrl(url) { + try { + const newUrl = new URL(url); + const urlWithProtocolAndHost = newUrl.protocol + '//' + newUrl.host; + if (!(CommonUtil.isValidUrl(urlWithProtocolAndHost) || CommonUtil.isValidPrivateUrl(urlWithProtocolAndHost))) { + return false; + } + if (newUrl.pathname !== '/json-rpc') { + return false; + } + return true; + } catch (error) { + return false; + } + } } module.exports = P2pUtil; diff --git a/p2p/server.js b/p2p/server.js index 47054bee2..e65c1987a 100644 --- a/p2p/server.js +++ b/p2p/server.js @@ -16,7 +16,7 @@ const { DevFlags, BlockchainConsts, NodeConfigs, - MessageTypes, + P2pMessageTypes, BlockchainNodeStates, PredefinedDbPaths, WriteDbOperations, @@ -36,11 +36,11 @@ const { sendGetRequest, signAndSendTx, sendTxAndWaitForFinalization, - getIpAddress, - convertIpv6ToIpv4 + getIpAddress } = require('../common/network-util'); const P2pUtil = require('./p2p-util'); const PathUtil = require('../common/path-util'); +const { JSON_RPC_METHODS } = require('../json_rpc/constants'); const DISK_USAGE_PATH = os.platform() === 'win32' ? 'c:' : '/'; @@ -387,16 +387,6 @@ class P2pServer { return 0; } - /** - * Returns true if the socket ip address is the same as the given p2p url ip address, - * false otherwise. - * @param {string} ipv4Address is ipv4 socket._socket.remoteAddress - * @param {string} url is peerInfo.networkStatus.urls.p2p.url - */ - checkIpAddressFromPeerInfo(ipv4Address, url) { - return url.includes(ipv4Address); - } - setServerSidePeerEventHandlers(socket, url) { const LOG_HEADER = 'setServerSidePeerEventHandlers'; socket.on('message', async (message) => { @@ -433,9 +423,9 @@ class P2pServer { } switch (_.get(parsedMessage, 'type')) { - case MessageTypes.ADDRESS_REQUEST: + case P2pMessageTypes.ADDRESS_REQUEST: const dataVersionCheckForAddress = - this.checkDataProtoVer(dataProtoVer, MessageTypes.ADDRESS_REQUEST); + this.checkDataProtoVer(dataProtoVer, P2pMessageTypes.ADDRESS_REQUEST); if (dataVersionCheckForAddress < 0) { // TODO(minsulee2): need to convert message when updating ADDRESS_REQUEST necessary. // this.convertAddressMessage(); @@ -496,7 +486,7 @@ class P2pServer { P2pUtil.removeFromPeerConnectionsInProgress(this.peerConnectionsInProgress, url); const jsonRpcUrl = _.get(peerInfo, 'networkStatus.urls.jsonRpc.url'); if (!this.client.peerCandidates.has(jsonRpcUrl)) { - this.client.setPeerCandidate(jsonRpcUrl, address, null); + this.client.updatePeerCandidateInfo(jsonRpcUrl, address, null); } const body = { address: this.getNodeAddress(), @@ -511,7 +501,7 @@ class P2pServer { return; } const payload = P2pUtil.encapsulateMessage( - MessageTypes.ADDRESS_RESPONSE, { body: body, signature: signature }); + P2pMessageTypes.ADDRESS_RESPONSE, { body: body, signature: signature }); if (!payload) { logger.error('The address cannot be sent because of msg encapsulation failure.'); const latency = Date.now() - beginTime; @@ -521,8 +511,9 @@ class P2pServer { socket.send(JSON.stringify(payload)); if (!this.client.outbound[address]) { const p2pUrl = _.get(peerInfo, 'networkStatus.urls.p2p.url'); - const ipv4Address = convertIpv6ToIpv4(socket._socket.remoteAddress); - if (this.checkIpAddressFromPeerInfo(ipv4Address, p2pUrl)) { + const ipAddressFromSocket = _.get(socket, '_socket.remoteAddress'); + const ipAddressFromPeerInfo = P2pUtil.toHostname(p2pUrl); + if (P2pUtil.checkIpAddressFromPeerInfo(ipAddressFromSocket, ipAddressFromPeerInfo)) { this.client.connectToPeer(p2pUrl); } else { P2pUtil.removeFromPeerConnectionsInProgress(this.peerConnectionsInProgress, url); @@ -531,9 +522,9 @@ class P2pServer { } } break; - case MessageTypes.CONSENSUS: + case P2pMessageTypes.CONSENSUS: const dataVersionCheckForConsensus = - this.checkDataProtoVer(dataProtoVer, MessageTypes.CONSENSUS); + this.checkDataProtoVer(dataProtoVer, P2pMessageTypes.CONSENSUS); if (dataVersionCheckForConsensus !== 0) { logger.error(`[${LOG_HEADER}] The message DATA_PROTOCOL_VERSION(${dataProtoVer}) ` + 'is not compatible. CANNOT proceed the CONSENSUS message.'); @@ -559,9 +550,9 @@ class P2pServer { this.client.requestChainSegment(); } break; - case MessageTypes.TRANSACTION: + case P2pMessageTypes.TRANSACTION: const dataVersionCheckForTransaction = - this.checkDataProtoVer(dataProtoVer, MessageTypes.TRANSACTION); + this.checkDataProtoVer(dataProtoVer, P2pMessageTypes.TRANSACTION); if (dataVersionCheckForTransaction > 0) { logger.error(`[${LOG_HEADER}] CANNOT deal with higher data protocol ` + `version(${dataProtoVer}). Discard the TRANSACTION message.`); @@ -629,7 +620,7 @@ class P2pServer { } } break; - case MessageTypes.SNAPSHOT_CHUNK_REQUEST: + case P2pMessageTypes.SNAPSHOT_CHUNK_REQUEST: logger.info(`[${LOG_HEADER}] Receiving a snapshot chunk request`); if (this.node.state !== BlockchainNodeStates.SERVING) { logger.info(`[${LOG_HEADER}] Not ready to accept snapshot chunk requests.\n` + @@ -641,7 +632,7 @@ class P2pServer { // Send the chunks of the latest snapshot one by one to the requester. await this.loadAndStreamLatestSnapshot(socket); break; - case MessageTypes.CHAIN_SEGMENT_REQUEST: + case P2pMessageTypes.CHAIN_SEGMENT_REQUEST: const lastBlockNumber = _.get(parsedMessage, 'data.lastBlockNumber'); logger.debug(`[${LOG_HEADER}] Receiving a chain segment request: ${lastBlockNumber}`); if (this.node.bc.chain.length === 0) { @@ -683,7 +674,7 @@ class P2pServer { ); } break; - case MessageTypes.OLD_CHAIN_SEGMENT_REQUEST: + case P2pMessageTypes.OLD_CHAIN_SEGMENT_REQUEST: const oldestBlockNumber = _.get(parsedMessage, 'data.oldestBlockNumber'); logger.info(`[${LOG_HEADER}] Receiving an old chain segment request: ${oldestBlockNumber}`); if (!CommonUtil.isNumber(oldestBlockNumber) || oldestBlockNumber <= 0) { @@ -703,7 +694,7 @@ class P2pServer { const oldChainSegment = this.node.bc.getOldBlockList(oldestBlockNumber - 1); this.sendOldChainSegment(socket, oldChainSegment); break; - case MessageTypes.PEER_INFO_UPDATE: + case P2pMessageTypes.PEER_INFO_UPDATE: const updatePeerInfo = parsedMessage.data; const addressFromSocket = P2pUtil.getAddressFromSocket(this.inbound, socket); // Keep updating both inbound and outbound. @@ -758,7 +749,7 @@ class P2pServer { logger.info( `[${LOG_HEADER}] Sending a snapshot chunk ${chunkIndex} / ${numChunks} of blockNumber ${blockNumber}.`); const payload = P2pUtil.encapsulateMessage( - MessageTypes.SNAPSHOT_CHUNK_RESPONSE, { blockNumber, numChunks, chunkIndex, chunk }); + P2pMessageTypes.SNAPSHOT_CHUNK_RESPONSE, { blockNumber, numChunks, chunkIndex, chunk }); if (!payload) { logger.error( `[${LOG_HEADER}] The snapshot chunk couldn't be sent because of msg encapsulation failure.`); @@ -770,7 +761,7 @@ class P2pServer { sendChainSegment(socket, chainSegment, number, catchUpInfo) { const LOG_HEADER = 'sendChainSegment'; const payload = P2pUtil.encapsulateMessage( - MessageTypes.CHAIN_SEGMENT_RESPONSE, { chainSegment, number, catchUpInfo }); + P2pMessageTypes.CHAIN_SEGMENT_RESPONSE, { chainSegment, number, catchUpInfo }); if (!payload) { logger.error( `[${LOG_HEADER}] The chain segment couldn't be sent because of msg encapsulation failure.`); @@ -788,7 +779,7 @@ class P2pServer { `[${LOG_HEADER}] Sending an old chain segment of size ${segmentSize} ` + `(${fromBlockNumber} ~ ${toBlockNumber})`); const payload = P2pUtil.encapsulateMessage( - MessageTypes.OLD_CHAIN_SEGMENT_RESPONSE, { oldChainSegment }); + P2pMessageTypes.OLD_CHAIN_SEGMENT_RESPONSE, { oldChainSegment }); if (!payload) { logger.error( `[${LOG_HEADER}] The old chain segment couldn't be sent because of msg encapsulation failure.`); @@ -808,7 +799,10 @@ class P2pServer { if (this.node.state !== BlockchainNodeStates.SERVING) { logger.debug(`[${LOG_HEADER}] Not ready to process transactions (${this.node.state})`); this.client.requestChainSegment(); - return; + return { + tx_hash: null, + result: false + }; } if (Transaction.isBatchTransaction(tx)) { const resultList = []; @@ -960,7 +954,7 @@ class P2pServer { static async getLastReportedBlockNumber(parentChainEndpoint, shardingPath) { const resp = await sendGetRequest( parentChainEndpoint, - 'ain_get', + JSON_RPC_METHODS.AIN_GET, { type: ReadDbOperations.GET_VALUE, ref: `${shardingPath}/${PredefinedDbPaths.DOT_SHARD}/${ShardingProperties.LATEST_BLOCK_NUMBER}` @@ -970,7 +964,7 @@ class P2pServer { } static async getShardingAppConfig(parentChainEndpoint, appName) { - const resp = await sendGetRequest(parentChainEndpoint, 'ain_get', { + const resp = await sendGetRequest(parentChainEndpoint, JSON_RPC_METHODS.AIN_GET, { type: ReadDbOperations.GET_VALUE, ref: PathUtil.getManageAppConfigPath(appName) }); diff --git a/package.json b/package.json index 440809af4..da0233e39 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ain-blockchain", "description": "AI Network Blockchain", - "version": "1.0.7", + "version": "1.0.8", "private": true, "license": "MIT", "author": "dev@ainetwork.ai", @@ -40,7 +40,6 @@ "test_unit_db": "BLOCKCHAIN_CONFIGS_DIR=blockchain-configs/1-node ENABLE_REST_FUNCTION_CALL=true UNSAFE_PRIVATE_KEY=b22c95ffc4a5c096f7d7d0487ba963ce6ac945bdc91c79b64ce209de289bec96 ./node_modules/mocha/bin/mocha --timeout 160000 test/unit/db.test.js", "test_unit_event_handler": "BLOCKCHAIN_CONFIGS_DIR=blockchain-configs/1-node ENABLE_REST_FUNCTION_CALL=true UNSAFE_PRIVATE_KEY=b22c95ffc4a5c096f7d7d0487ba963ce6ac945bdc91c79b64ce209de289bec96 ./node_modules/mocha/bin/mocha --timeout 160000 test/unit/event-handler.test.js", "test_unit_functions": "BLOCKCHAIN_CONFIGS_DIR=blockchain-configs/1-node ENABLE_REST_FUNCTION_CALL=true UNSAFE_PRIVATE_KEY=b22c95ffc4a5c096f7d7d0487ba963ce6ac945bdc91c79b64ce209de289bec96 ./node_modules/mocha/bin/mocha --timeout 160000 test/unit/functions.test.js", - "test_unit_network_util": "BLOCKCHAIN_CONFIGS_DIR=blockchain-configs/1-node ENABLE_REST_FUNCTION_CALL=true UNSAFE_PRIVATE_KEY=b22c95ffc4a5c096f7d7d0487ba963ce6ac945bdc91c79b64ce209de289bec96 ./node_modules/mocha/bin/mocha --timeout 160000 test/unit/network-util.test.js", "test_unit_object_util": "BLOCKCHAIN_CONFIGS_DIR=blockchain-configs/1-node ENABLE_REST_FUNCTION_CALL=true UNSAFE_PRIVATE_KEY=b22c95ffc4a5c096f7d7d0487ba963ce6ac945bdc91c79b64ce209de289bec96 ./node_modules/mocha/bin/mocha --timeout 160000 test/unit/object-util.test.js", "test_unit_p2p": "BLOCKCHAIN_CONFIGS_DIR=blockchain-configs/1-node ENABLE_REST_FUNCTION_CALL=true UNSAFE_PRIVATE_KEY=b22c95ffc4a5c096f7d7d0487ba963ce6ac945bdc91c79b64ce209de289bec96 ./node_modules/mocha/bin/mocha --timeout 320000 test/unit/p2p.test.js", "test_unit_p2p_util": "BLOCKCHAIN_CONFIGS_DIR=blockchain-configs/1-node ENABLE_REST_FUNCTION_CALL=true UNSAFE_PRIVATE_KEY=b22c95ffc4a5c096f7d7d0487ba963ce6ac945bdc91c79b64ce209de289bec96 ./node_modules/mocha/bin/mocha --timeout 160000 test/unit/p2p-util.test.js", diff --git a/test/integration/blockchain.test.js b/test/integration/blockchain.test.js index bbeb2fdbe..33dfc6524 100644 --- a/test/integration/blockchain.test.js +++ b/test/integration/blockchain.test.js @@ -16,11 +16,11 @@ const { waitUntilTxFinalized, waitForNewBlocks, waitUntilNetworkIsReady, - waitUntilNodeSyncs, parseOrLog, setUpApp } = require('../test-util'); const { Block } = require('../../blockchain/block'); +const { JSON_RPC_METHODS } = require('../../json_rpc/constants'); const PROJECT_ROOT = require('path').dirname(__filename) + '/../../'; const TRACKER_SERVER = PROJECT_ROOT + 'tracker-server/index.js'; @@ -46,26 +46,17 @@ const ENV_VARIABLES = [ ]; // Server configurations -const trackerServer = 'http://localhost:5000'; const server1 = 'http://localhost:8081'; const server2 = 'http://localhost:8082'; const server3 = 'http://localhost:8083'; const serverList = [server1, server2, server3 ]; const JSON_RPC_ENDPOINT = '/json-rpc'; -const JSON_RPC_GET_LAST_BLOCK = 'ain_getLastBlock'; -const JSON_RPC_GET_BLOCKS = 'ain_getBlockList'; -const JSON_RPC_GET_BLOCK_HEADERS = 'ain_getBlockHeadersList'; -const JSON_RPC_GET_BLOCK_BY_HASH = 'ain_getBlockByHash'; -const JSON_RPC_GET_BLOCK_BY_NUMBER = 'ain_getBlockByNumber'; -const JSON_RPC_GET_NONCE = 'ain_getNonce'; -const JSON_RPC_NET_SYNCING = 'net_syncing'; const SET_VALUE_ENDPOINT = '/set_value'; -const GET_VALUE_ENDPOINT = '/get_value' -const BLOCKS_ENDPOINT = '/blocks' +const GET_VALUE_ENDPOINT = '/get_value'; +const BLOCKS_ENDPOINT = '/blocks'; // NOTE(minsulee2): keep it for commented out part const GET_ADDR_ENDPOINT = '/get_address'; -const LAST_BLOCK_NUMBER_ENDPOINT = '/last_block_number' // Data options RANDOM_OPERATION = [ @@ -184,7 +175,7 @@ async function sendTransactions(sentOperations) { 'POST', serverList[serverIndex] + '/json-rpc', { json: { jsonrpc: '2.0', - method: 'ain_getNonce', + method: JSON_RPC_METHODS.AIN_GET_NONCE, id: 0, params: { address, @@ -238,7 +229,7 @@ describe('Blockchain Cluster', () => { await waitUntilNetworkIsReady(serverList); jsonRpcClient = jayson.client.http(server2 + JSON_RPC_ENDPOINT); promises.push(new Promise((resolve) => { - jsonRpcClient.request(JSON_RPC_GET_LAST_BLOCK, + jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET_LAST_BLOCK, {protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION}, function(err, response) { if (err) { resolve(); @@ -282,32 +273,33 @@ describe('Blockchain Cluster', () => { await sendTransactions(sentOperations); return new Promise((resolve) => { jayson.client.http(server1 + JSON_RPC_ENDPOINT) - .request(JSON_RPC_GET_BLOCKS, {protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION}, - function(err, response) { + .request(JSON_RPC_METHODS.AIN_GET_BLOCK_LIST, + { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }, + function(err, response) { if (err) throw err; baseChain = response.result.result; resolve(); }); }).then(() => { return new Promise((resolve) => { - jayson.client.http(serverList[i] + JSON_RPC_ENDPOINT).request(JSON_RPC_GET_BLOCKS, - {protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION}, - function(err, response) { - if (err) throw err; - const newChain = response.result.result; - const diff = Math.abs(baseChain.length - newChain.length); - assert.isBelow(diff, MAX_CHAIN_LENGTH_DIFF); - while (baseChain.length !== newChain.length) { - if (baseChain.length > newChain.length) { - baseChain.pop(); - } else { - newChain.pop(); - } + jayson.client.http(serverList[i] + JSON_RPC_ENDPOINT).request(JSON_RPC_METHODS.AIN_GET_BLOCK_LIST, + { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }, + function (err, response) { + if (err) throw err; + const newChain = response.result.result; + const diff = Math.abs(baseChain.length - newChain.length); + assert.isBelow(diff, MAX_CHAIN_LENGTH_DIFF); + while (baseChain.length !== newChain.length) { + if (baseChain.length > newChain.length) { + baseChain.pop(); + } else { + newChain.pop(); } - assert.deepEqual(newChain.length, baseChain.length); - assert.deepEqual(newChain, baseChain); - resolve(); - }); + } + assert.deepEqual(newChain.length, baseChain.length); + assert.deepEqual(newChain, baseChain); + resolve(); + }); }); }); } @@ -372,7 +364,7 @@ describe('Blockchain Cluster', () => { 'POST', serverList[i] + '/json-rpc', { json: { jsonrpc: '2.0', - method: JSON_RPC_GET_BLOCKS, + method: JSON_RPC_METHODS.AIN_GET_BLOCK_LIST, id: 0, params: { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION @@ -419,9 +411,13 @@ describe('Blockchain Cluster', () => { for (let i = 0; i < serverList.length; i++) { await sendTransactions(sentOperations); const blocks = parseOrLog(syncRequest('POST', serverList[i] + '/json-rpc', - {json: {jsonrpc: '2.0', method: JSON_RPC_GET_BLOCKS, id: 0, - params: {protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION}}}) - .body.toString('utf-8')).result.result; + { + json: { + jsonrpc: '2.0', method: JSON_RPC_METHODS.AIN_GET_BLOCK_LIST, id: 0, + params: { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION } + } + }) + .body.toString('utf-8')).result.result; const len = blocks.length; for (let j = 0; j < len; j++) { const block = blocks[j]; @@ -512,7 +508,7 @@ describe('Blockchain Cluster', () => { it('ain_getBlockHeadersList', async () => { await sendTransactions(sentOperations); return new Promise((resolve) => { - jsonRpcClient.request(JSON_RPC_GET_BLOCK_HEADERS, + jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET_BLOCK_HEADERS_LIST, {from: 2, to: 4, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION}, function(err, response) { if (err) throw err; @@ -528,14 +524,14 @@ describe('Blockchain Cluster', () => { it('ain_getBlockByHash and ain_getBlockByNumber', async () => { await sendTransactions(sentOperations); return new Promise((resolve) => { - jsonRpcClient.request(JSON_RPC_GET_BLOCK_BY_NUMBER, + jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET_BLOCK_BY_NUMBER, {number: 2, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION}, function(err, response) { if (err) throw err; resolve(response.result.result); }); }).then((resultByNumber) => { return new Promise((resolve) => { - jsonRpcClient.request(JSON_RPC_GET_BLOCK_BY_HASH, + jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET_BLOCK_BY_HASH, {hash: resultByNumber.hash, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION}, function(err, response) { if (err) throw err; @@ -559,9 +555,9 @@ describe('Blockchain Cluster', () => { it('pendingNonceTracker', () => { return new Promise((resolve, reject) => { let promises = []; - promises.push(jsonRpcClient.request(JSON_RPC_GET_NONCE, + promises.push(jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET_NONCE, { address, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION })); - promises.push(jsonRpcClient.request(JSON_RPC_GET_NONCE, + promises.push(jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET_NONCE, { address, from: 'pending', protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION })); Promise.all(promises).then(res => { promises = []; @@ -574,9 +570,9 @@ describe('Blockchain Cluster', () => { value: 'testing...' } }).body.toString('utf-8')).result.tx_hash; - promises.push(jsonRpcClient.request(JSON_RPC_GET_NONCE, + promises.push(jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET_NONCE, { address, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION })); - promises.push(jsonRpcClient.request(JSON_RPC_GET_NONCE, + promises.push(jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET_NONCE, { address, from: 'pending', protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION })); Promise.all(promises).then(async resAfterBroadcast => { promises = []; @@ -599,9 +595,9 @@ describe('Blockchain Cluster', () => { return new Promise(async (resolve, reject) => { await waitForNewBlocks(server2); let promises = []; - promises.push(jsonRpcClient.request(JSON_RPC_GET_NONCE, + promises.push(jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET_NONCE, { address, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION })); - promises.push(jsonRpcClient.request(JSON_RPC_GET_NONCE, + promises.push(jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET_NONCE, { address, from: 'pending', protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION })); Promise.all(promises).then(resAfterCommit => { const committedNonceAfterCommit = resAfterCommit[0].result.result; @@ -621,25 +617,25 @@ describe('Blockchain Cluster', () => { describe('Gas fee', () => { it('collected gas cost matches the gas_cost_total in the block', () => { return new Promise((resolve) => { - jayson.client.http(serverList[1] + JSON_RPC_ENDPOINT).request - (JSON_RPC_GET_BLOCKS, {protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION}, function(err, response) { - if (err) throw err; - const chain = response.result.result; - for (const block of chain) { - if (block.number > 0) { - // Amount specified in block - const gasCostTotal = block.gas_cost_total; - // Amount actually collected & distributed. Write rule prevents writing a gas_cost_total - // that is different from the value at /service_accounts/gas_fee/gas_fee/${block.number}/balance. - const collectedGas = parseOrLog(syncRequest( - 'GET', server1 + GET_VALUE_ENDPOINT + `?ref=/consensus/number/${block.number}/propose/gas_cost_total`) - .body.toString('utf-8')).result; - assert.deepEqual(gasCostTotal, collectedGas); - } + jayson.client.http(serverList[1] + JSON_RPC_ENDPOINT).request(JSON_RPC_METHODS.AIN_GET_BLOCK_LIST, + { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }, + function (err, response) { + if (err) throw err; + const chain = response.result.result; + for (const block of chain) { + if (block.number > 0) { + // Amount specified in block + const gasCostTotal = block.gas_cost_total; + // Amount actually collected & distributed. Write rule prevents writing a gas_cost_total + // that is different from the value at /service_accounts/gas_fee/gas_fee/${block.number}/balance. + const collectedGas = parseOrLog(syncRequest( + 'GET', server1 + GET_VALUE_ENDPOINT + `?ref=/consensus/number/${block.number}/propose/gas_cost_total`) + .body.toString('utf-8')).result; + assert.deepEqual(gasCostTotal, collectedGas); } - resolve(); } - ); + resolve(); + }); }); }); }); @@ -692,7 +688,7 @@ describe('Blockchain Cluster', () => { describe('Protocol versions', () => { it('accepts API calls with correct protoVer', () => { - return jsonRpcClient.request(JSON_RPC_GET_BLOCK_BY_NUMBER, + return jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET_BLOCK_BY_NUMBER, { number: 0, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }) .then(res => { expect(res.result.result.number).to.equal(0); @@ -702,50 +698,50 @@ describe('Blockchain Cluster', () => { it('rejects API calls with invalid protoVer - case 1', () => { return jsonRpcClient.request( - JSON_RPC_GET_BLOCK_BY_NUMBER, + JSON_RPC_METHODS.AIN_GET_BLOCK_BY_NUMBER, { number: 0, protoVer: 'a.b.c' }) .then(res => { - expect(res.code).to.equal(40102); + expect(res.code).to.equal(30102); expect(res.message).to.equal("Invalid protocol version."); }); }); it('rejects API calls with invalid protoVer - case 2', () => { return jsonRpcClient.request( - JSON_RPC_GET_BLOCK_BY_NUMBER, + JSON_RPC_METHODS.AIN_GET_BLOCK_BY_NUMBER, { number: 0, protoVer: '0.01.0' }) .then(res => { - expect(res.code).to.equal(40102); + expect(res.code).to.equal(30102); expect(res.message).to.equal("Invalid protocol version."); }); }); it('rejects API calls with incompatible protoVer - case 1', () => { return jsonRpcClient.request( - JSON_RPC_GET_BLOCK_BY_NUMBER, + JSON_RPC_METHODS.AIN_GET_BLOCK_BY_NUMBER, { number: 0, protoVer: 'v0.1' }) .then(res => { - expect(res.code).to.equal(40103); + expect(res.code).to.equal(30103); expect(res.message).to.equal("Incompatible protocol version."); }); }); it('rejects API calls with incompatible protoVer - case 2', () => { return jsonRpcClient.request( - JSON_RPC_GET_BLOCK_BY_NUMBER, + JSON_RPC_METHODS.AIN_GET_BLOCK_BY_NUMBER, { number: 0, protoVer: '0.1.0' }) .then(res => { - expect(res.code).to.equal(40103); + expect(res.code).to.equal(30103); expect(res.message).to.equal("Incompatible protocol version."); }); }); it('rejects API calls with no protoVer', () => { return jsonRpcClient.request( - JSON_RPC_GET_BLOCK_BY_NUMBER, + JSON_RPC_METHODS.AIN_GET_BLOCK_BY_NUMBER, { number: 0 }) .then(res => { - expect(res.code).to.equal(40101); + expect(res.code).to.equal(30101); expect(res.message).to.equal("Protocol version not specified."); }); }); diff --git a/test/integration/consensus.test.js b/test/integration/consensus.test.js index 64064b4e9..71a0c0825 100644 --- a/test/integration/consensus.test.js +++ b/test/integration/consensus.test.js @@ -29,6 +29,7 @@ const { const { Block } = require('../../blockchain/block'); const Functions = require('../../db/functions'); const ConsensusUtil = require('../../consensus/consensus-util'); +const { JSON_RPC_METHODS } = require('../../json_rpc/constants'); const PROJECT_ROOT = require('path').dirname(__filename) + '/../../'; const TRACKER_SERVER = PROJECT_ROOT + 'tracker-server/index.js'; @@ -72,7 +73,6 @@ const server5 = 'http://localhost:8085'; const serverList = [server1, server2, server3, server4, server5]; const JSON_RPC_ENDPOINT = '/json-rpc'; -const JSON_RPC_GET_LAST_BLOCK = 'ain_getLastBlock'; class Process { constructor(application, envVariables) { @@ -142,7 +142,7 @@ describe('Consensus', () => { await waitUntilNetworkIsReady(serverList); jsonRpcClient = jayson.client.http(server2 + JSON_RPC_ENDPOINT); promises.push(new Promise((resolve) => { - jsonRpcClient.request(JSON_RPC_GET_LAST_BLOCK, + jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET_LAST_BLOCK, {protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION}, function(err, response) { if (err) { resolve(); @@ -192,7 +192,7 @@ describe('Consensus', () => { }; const signature = ainUtil.ecSignTransaction(txBody, Buffer.from('a2b5848760d81afe205884284716f90356ad82be5ab77b8130980bdb0b7ba2ba', 'hex')); - const res = await client.request('ain_sendSignedTransaction', { + const res = await client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { tx_body: txBody, signature, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION diff --git a/test/integration/event_handler.test.js b/test/integration/event_handler.test.js index 7acb92545..6443dc271 100644 --- a/test/integration/event_handler.test.js +++ b/test/integration/event_handler.test.js @@ -18,6 +18,7 @@ const { setUpApp, waitUntilNetworkIsReady, } = require('../test-util'); +const { JSON_RPC_METHODS } = require('../../json_rpc/constants'); const SET_VALUE_ENDPOINT = '/set_value'; const PROJECT_ROOT = require('path').dirname(__filename) + '/../../'; const TRACKER_SERVER = PROJECT_ROOT + 'tracker-server/index.js'; @@ -69,7 +70,7 @@ function startServer(application, serverName, envVars, stdioInherit = false) { function getEventHandlerNetworkInfo() { return _.get(parseOrLog(syncRequest('POST', serverList[EVENT_HANDLER_NODE_INDEX] + '/json-rpc', { json: { - jsonrpc: '2.0', method: 'net_getEventHandlerNetworkInfo', id: 0, + jsonrpc: '2.0', method: JSON_RPC_METHODS.NET_GET_EVENT_HANDLER_NETWORK_INFO, id: 0, params: { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }, }, }).body.toString('utf-8')), 'result.result'); @@ -78,7 +79,7 @@ function getEventHandlerNetworkInfo() { function getEventHandlerChannelInfo() { return _.get(parseOrLog(syncRequest('POST', serverList[EVENT_HANDLER_NODE_INDEX] + '/json-rpc', { json: { - jsonrpc: '2.0', method: 'ain_getEventHandlerChannelInfo', id: 0, + jsonrpc: '2.0', method: JSON_RPC_METHODS.AIN_GET_EVENT_HANDLER_CHANNEL_INFO, id: 0, params: { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }, }, }).body.toString('utf-8')), 'result.result'); @@ -87,7 +88,7 @@ function getEventHandlerChannelInfo() { function getEventHandlerFilterInfo() { return _.get(parseOrLog(syncRequest('POST', serverList[EVENT_HANDLER_NODE_INDEX] + '/json-rpc', { json: { - jsonrpc: '2.0', method: 'ain_getEventHandlerFilterInfo', id: 0, + jsonrpc: '2.0', method: JSON_RPC_METHODS.AIN_GET_EVENT_HANDLER_FILTER_INFO, id: 0, params: { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }, }, }).body.toString('utf-8')), 'result.result'); diff --git a/test/integration/function.test.js b/test/integration/function.test.js index 4e4fc05a7..4c1a9b939 100644 --- a/test/integration/function.test.js +++ b/test/integration/function.test.js @@ -23,6 +23,7 @@ const { getBlockByNumber, eraseStateGas, } = require('../test-util'); +const { JSON_RPC_METHODS } = require('../../json_rpc/constants'); const PROJECT_ROOT = require('path').dirname(__filename) + "/../../" const TRACKER_SERVER = PROJECT_ROOT + "tracker-server/index.js" @@ -932,7 +933,7 @@ describe('Native Function', () => { }; const signature = ainUtil.ecSignTransaction(txBody, Buffer.from('a2b5848760d81afe205884284716f90356ad82be5ab77b8130980bdb0b7ba2ba', 'hex')); - const res = await client.request('ain_sendSignedTransaction', { + const res = await client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { tx_body: txBody, signature, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION @@ -4818,7 +4819,7 @@ describe('Native Function', () => { }; const signature = ainUtil.ecSignTransaction(txBody, Buffer.from('d42f73de4ee706a4891dad643e0a65c0677020dbc2425f585442d0de2c742a44', 'hex')); - const res = await client.request('ain_sendSignedTransaction', { + const res = await client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { tx_body: txBody, signature, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION @@ -4861,7 +4862,7 @@ describe('Native Function', () => { }; const signature = ainUtil.ecSignTransaction(txBody, Buffer.from('d42f73de4ee706a4891dad643e0a65c0677020dbc2425f585442d0de2c742a44', 'hex')); - const res = await client.request('ain_sendSignedTransaction', { + const res = await client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { tx_body: txBody, signature, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION @@ -5010,7 +5011,7 @@ describe('Native Function', () => { }; const signature = ainUtil.ecSignTransaction(txBody, Buffer.from('d42f73de4ee706a4891dad643e0a65c0677020dbc2425f585442d0de2c742a44', 'hex')); - const res = await client.request('ain_sendSignedTransaction', { + const res = await client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { tx_body: txBody, signature, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION @@ -5614,7 +5615,7 @@ describe('Native Function', () => { }; const signature = ainUtil.ecSignTransaction(txBody, Buffer.from('d42f73de4ee706a4891dad643e0a65c0677020dbc2425f585442d0de2c742a44', 'hex')); - const res = await client.request('ain_sendSignedTransaction', { + const res = await client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { tx_body: txBody, signature, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION @@ -5660,7 +5661,7 @@ describe('Native Function', () => { }; const signature = ainUtil.ecSignTransaction(txBody, Buffer.from('d42f73de4ee706a4891dad643e0a65c0677020dbc2425f585442d0de2c742a44', 'hex')); - const res = await client.request('ain_sendSignedTransaction', { + const res = await client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { tx_body: txBody, signature, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION @@ -5839,7 +5840,7 @@ describe('Native Function', () => { }; const signature = ainUtil.ecSignTransaction( closeCheckinTxBody, Buffer.from('d42f73de4ee706a4891dad643e0a65c0677020dbc2425f585442d0de2c742a44', 'hex')); - const closeCheckinRes = await client.request('ain_sendSignedTransaction', { + const closeCheckinRes = await client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { tx_body: closeCheckinTxBody, signature, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION @@ -5957,7 +5958,7 @@ describe('Native Function', () => { }; const signature = ainUtil.ecSignTransaction(txBody, Buffer.from('d42f73de4ee706a4891dad643e0a65c0677020dbc2425f585442d0de2c742a44', 'hex')); - const res = await client.request('ain_sendSignedTransaction', { + const res = await client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { tx_body: txBody, signature, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION diff --git a/test/integration/he_sharding.test.js b/test/integration/he_sharding.test.js index b5c760994..3137c2c39 100644 --- a/test/integration/he_sharding.test.js +++ b/test/integration/he_sharding.test.js @@ -344,6 +344,10 @@ describe('HE Sharding', () => { }); describe('Shard reporter node restart', () => { + before(() => { + ENV_VARIABLES[2].PEER_CANDIDATE_JSON_RPC_URL = "http://localhost:9092/json-rpc"; + }); + it('can resume reporting after missing some reports', async () => { const latestBefore = parseOrLog(syncRequest( 'GET', parentServer + `/get_value?ref=${sharding.sharding_path}/.shard/latest_block_number`) @@ -369,6 +373,10 @@ describe('HE Sharding', () => { } expect(latestAfter).to.be.greaterThan(latestBefore); }); + + after(() => { + delete ENV_VARIABLES[2].PEER_CANDIDATE_JSON_RPC_URL; + }); }); }); }) diff --git a/test/integration/node.test.js b/test/integration/node.test.js index d38291842..fa7cfb2bb 100644 --- a/test/integration/node.test.js +++ b/test/integration/node.test.js @@ -9,20 +9,17 @@ const jayson = require('jayson/promise'); const ainUtil = require('@ainblockchain/ain-util'); const { BlockchainConsts, BlockchainParams, NodeConfigs } = require('../../common/constants'); const CommonUtil = require('../../common/common-util'); -const PathUtil = require('../../common/path-util'); -const { JsonRpcApiResultCode } = require('../../common/result-code'); const { verifyStateProof, } = require('../../db/state-util'); -const DB = require('../../db'); const { parseOrLog, setUpApp, waitUntilNetworkIsReady, waitUntilTxFinalized, - getBlockByNumber, - eraseEvalResMatched, + eraseEvalResMatched } = require('../test-util'); +const { JSON_RPC_METHODS } = require('../../json_rpc/constants'); const PROJECT_ROOT = require('path').dirname(__filename) + "/../../" const TRACKER_SERVER = PROJECT_ROOT + "tracker-server/index.js" @@ -39,7 +36,7 @@ const ENV_VARIABLES = [ UNSAFE_PRIVATE_KEY: '921cc48e48c876fc6ed1eb02a76ad520e8d16a91487f9c7e03441da8e35a0947', BLOCKCHAIN_CONFIGS_DIR: 'blockchain-configs/3-nodes', PORT: 8082, P2P_PORT: 5002, ENABLE_GAS_FEE_WORKAROUND: true, ENABLE_EXPRESS_RATE_LIMIT: false, - GET_RESP_BYTES_LIMIT: 77819, GET_RESP_MAX_SIBLINGS: 999, // For get_value limit tests + GET_RESP_BYTES_LIMIT: 77819, GET_RESP_MAX_SIBLINGS: 1000, // For get_value limit tests }, { UNSAFE_PRIVATE_KEY: '41e6e5718188ce9afd25e4b386482ac2c5272c49a622d8d217887bce21dce560', @@ -570,7 +567,7 @@ describe('Blockchain Node', () => { }) }) - describe('/get api', () => { + describe('get api', () => { it('get', () => { const request = { op_list: [ @@ -727,6 +724,33 @@ describe('Blockchain Node', () => { ] }); }) + + it('get with empty op_list', () => { + const request = { + op_list: [ // empty op_list + ] + }; + const body = parseOrLog(syncRequest('POST', server1 + '/get', {json: request}) + .body.toString('utf-8')); + assert.deepEqual(body, { + "result": null, + "code": 30006, + "message": "Invalid op_list given" + }); + }) + + it('get with null op_list', () => { + const request = { + op_list: null // null op_list + }; + const body = parseOrLog(syncRequest('POST', server1 + '/get', {json: request}) + .body.toString('utf-8')); + assert.deepEqual(body, { + "result": null, + "code": 30006, + "message": "Invalid op_list given" + }); + }) }) describe('get_state_proof api', () => { @@ -773,6 +797,7 @@ describe('Blockchain Node', () => { "#state_ph": "erased", "#tree_bytes": 0, "#tree_height": 2, + "#tree_max_siblings": 2, "#tree_size": 5, "#version": "erased", }}); @@ -798,6 +823,7 @@ describe('Blockchain Node', () => { "usage": { "tree_bytes": 12414, "tree_height": 24, + "tree_max_siblings": 12, "tree_size": 66 } }); @@ -821,17 +847,18 @@ describe('Blockchain Node', () => { "usage": { "tree_bytes": 0, "tree_height": 0, + "tree_max_siblings": 0, "tree_size": 0 } }); }); }); - describe('ain_get api', () => { + describe('json-rpc api: ain_get', () => { it('returns the correct value', () => { const expected = 100; const jsonRpcClient = jayson.client.http(server2 + '/json-rpc'); - return jsonRpcClient.request('ain_get', { + return jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET, { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, type: 'GET_VALUE', ref: "/apps/test/test_value/some/path" @@ -841,24 +868,151 @@ describe('Blockchain Node', () => { }); }); - it('returns error when invalid op_list is given', () => { + it('returns the correct value with is_shallow = true', () => { + const expected = 100; + const jsonRpcClient = jayson.client.http(server2 + '/json-rpc'); + return jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET, { + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, + type: 'GET_VALUE', + ref: "/apps/test/test_value/some/path", + is_shallow: true // w/ is_shallow = true + }) + .then(res => { + expect(res.result.result).to.equal(expected); + }); + }); + + it('returns the correct value with is_partial = true', () => { + const expected = 100; + const jsonRpcClient = jayson.client.http(server2 + '/json-rpc'); + return jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET, { + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, + type: 'GET_VALUE', + ref: "/apps/test/test_value/some/path", + is_partial: true // w/ is_partial = true + }) + .then(res => { + expect(res.result.result).to.equal(expected); + }); + }); + + it('returns the correct object value', () => { + const jsonRpcClient = jayson.client.http(server2 + '/json-rpc'); + return jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET, { + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, + type: 'GET_VALUE', + ref: "/apps/test/test_value" + }) + .then(res => { + assert.deepEqual(res.result.result, { + "some": { + "path": 100 + } + }); + }); + }); + + it('returns the correct object value with is_shallow = true', () => { + const jsonRpcClient = jayson.client.http(server2 + '/json-rpc'); + return jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET, { + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, + type: 'GET_VALUE', + ref: "/apps/test/test_value", + is_shallow: true // w/ is_shallow = true + }) + .then(res => { + assert.deepEqual(res.result.result, { + "some": { + "#state_ph": "0x1f8ea4b70d822143cd8545d3c248ac33f14c60053c86d2b44ee6bb9381c21d62" + } + }); + }); + }); + + it('returns the correct object value with is_partial = true', () => { + const jsonRpcClient = jayson.client.http(server2 + '/json-rpc'); + return jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET, { + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, + type: 'GET_VALUE', + ref: "/apps/test/test_value", + is_partial: true // w/ is_partial = true + }) + .then(res => { + assert.deepEqual(res.result.result, { + "#end_label": "736f6d65", + "some": { + "#serial": 2, + "#state_ph": "0x1f8ea4b70d822143cd8545d3c248ac33f14c60053c86d2b44ee6bb9381c21d62", + } + }); + }); + }); + + it('returns error when empty op_list is given', () => { + const jsonRpcClient = jayson.client.http(server2 + '/json-rpc'); + return jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET, { + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, + type: 'GET', + op_list: [ // empty op_list + ] + }) + .then(res => { + expect(res.result.result).to.equal(null); + expect(res.result.code).to.equal(30006); + expect(res.result.message).to.equal('Invalid op_list given'); + }); + }); + + it('returns error when null op_list is given', () => { const jsonRpcClient = jayson.client.http(server2 + '/json-rpc'); - return jsonRpcClient.request('ain_get', { + return jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET, { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, type: 'GET', op_list: null }) .then(res => { - expect(res.result.result.code).to.equal(JsonRpcApiResultCode.GET_INVALID_OP_LIST); - expect(res.result.result.message).to.equal('Invalid op_list given'); + expect(res.result.result).to.equal(null); + expect(res.result.code).to.equal(30006); + expect(res.result.message).to.equal('Invalid op_list given'); + }); + }); + + it('returns error when requested data exceeds the get response bytes limit', async () => { + // Note: GET_RESP_BYTES_LIMIT = 77819 + const bigTree = {}; + for (let i = 0; i < 100; i++) { + bigTree[i] = {}; + for (let j = 0; j < 100; j++) { + bigTree[i][j] = 'a'; + } + } + const body = parseOrLog(syncRequest('POST', server1 + '/set_value', {json: { + ref: '/apps/test/test_value/some/path', + value: bigTree, // 77820 bytes (using object-sizeof) + }}).body.toString('utf-8')); + if (!(await waitUntilTxFinalized(serverList, _.get(body, 'result.tx_hash')))) { + console.error(`Failed to check finalization of tx.`); + } + const jsonRpcClient = jayson.client.http(server2 + '/json-rpc'); + return jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET, { + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, + type: 'GET_VALUE', + ref: "/apps/test/test_value/some/path", + }) + .then(res => { + expect(res.result.result).to.equal(null); + expect(res.result.code).to.equal(30002); + expect(res.result.message.includes( + 'The data exceeds the max byte limit of the requested node'), true); }); }); - it('returns error when requested data exceeds the get response limits (bytes)', async () => { + // the same as the previous test case but is_shallow = true + it('returns a correct value with is_shallow = true', async () => { const bigTree = {}; - for (let i = 0; i < 10; i++) { + for (let i = 0; i < 100; i++) { bigTree[i] = {}; - for (let j = 0; j < 1000; j++) { + for (let j = 0; j < 100; j++) { bigTree[i][j] = 'a'; } } @@ -870,51 +1024,104 @@ describe('Blockchain Node', () => { console.error(`Failed to check finalization of tx.`); } const jsonRpcClient = jayson.client.http(server2 + '/json-rpc'); - return jsonRpcClient.request('ain_get', { + return jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET, { + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, + type: 'GET_VALUE', + ref: "/apps/test/test_value/some/path", + is_shallow: true // w/ is_shallow = true + }) + .then(res => { + expect(res.result.code).to.equal(undefined); + }); + }); + + it('returns error when requested data exceeds the get response max siblings limit - node num children', async () => { + // Note: GET_RESP_MAX_SIBLINGS = 1000 + const wideTree = {}; + for (let i = 0; i < 1001; i++) { + wideTree[i] = 'a'; + } + const body = parseOrLog(syncRequest('POST', server1 + '/set_value', {json: { + ref: '/apps/test/test_value/some/path', + value: wideTree, // 1001 siblings (num children) + }}).body.toString('utf-8')); + if (!(await waitUntilTxFinalized(serverList, _.get(body, 'result.tx_hash')))) { + console.error(`Failed to check finalization of tx.`); + } + const jsonRpcClient = jayson.client.http(server2 + '/json-rpc'); + return jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET, { + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, + type: 'GET_VALUE', + ref: "/apps/test/test_value/some/path", + }) + .then(res => { + expect(res.result.result).to.equal(null); + expect(res.result.code).to.equal(30003); + expect(res.result.message.includes( + 'The data exceeds the max sibling limit of the requested node'), true); + }); + }); + + it('returns error when requested data exceeds the get response max siblings limit - subtree num children', async () => { + // Note: GET_RESP_MAX_SIBLINGS = 1000 + const wideTree = {}; + wideTree[0] = {}; + for (let i = 0; i < 1001; i++) { + wideTree[0][i] = 'a'; + } + const body = parseOrLog(syncRequest('POST', server1 + '/set_value', {json: { + ref: '/apps/test/test_value/some/path', + value: wideTree, // 1001 max siblings (subtree num children) + }}).body.toString('utf-8')); + if (!(await waitUntilTxFinalized(serverList, _.get(body, 'result.tx_hash')))) { + console.error(`Failed to check finalization of tx.`); + } + const jsonRpcClient = jayson.client.http(server2 + '/json-rpc'); + return jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET, { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, type: 'GET_VALUE', ref: "/apps/test/test_value/some/path", }) .then(res => { - expect(res.result.result.code).to.equal(JsonRpcApiResultCode.GET_EXCEEDS_MAX_BYTES); - expect( - res.result.result.message - .includes('The data exceeds the max byte limit of the requested node'), true); + expect(res.result.result).to.equal(null); + expect(res.result.code).to.equal(30003); + expect(res.result.message.includes( + 'The data exceeds the max sibling limit of the requested node'), true); }); }); - it('returns error when requested data exceeds the get response limits (siblings)', async () => { + // the same as the previous test case but is_partial = true + it('returns a correct value with is_partial = true', async () => { + // Note: GET_RESP_MAX_SIBLINGS = 1000 const wideTree = {}; - for (let i = 0; i < 1000; i++) { + for (let i = 0; i < 1001; i++) { wideTree[i] = 'a'; } const body = parseOrLog(syncRequest('POST', server1 + '/set_value', {json: { ref: '/apps/test/test_value/some/path', - value: wideTree, // 1000 siblings + value: wideTree, // 1001 siblings (num children) }}).body.toString('utf-8')); if (!(await waitUntilTxFinalized(serverList, _.get(body, 'result.tx_hash')))) { console.error(`Failed to check finalization of tx.`); } const jsonRpcClient = jayson.client.http(server2 + '/json-rpc'); - return jsonRpcClient.request('ain_get', { + return jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET, { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, type: 'GET_VALUE', ref: "/apps/test/test_value/some/path", + is_partial: true // w/ is_partial = true }) .then(res => { - expect(res.result.result.code).to.equal(JsonRpcApiResultCode.GET_EXCEEDS_MAX_SIBLINGS); - expect( - res.result.result.message - .includes('The data exceeds the max sibling limit of the requested node'), true); + expect(res.result.result['#end_label']).to.equal('393938'); }); }); }); - describe('ain_matchFunction api', () => { + describe('json-rpc api: ain_matchFunction', () => { it('returns correct value', () => { const ref = "/apps/test/test_function/some/path"; const request = { ref, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }; - return jayson.client.http(server1 + '/json-rpc').request('ain_matchFunction', request) + return jayson.client.http(server1 + '/json-rpc').request(JSON_RPC_METHODS.AIN_MATCH_FUNCTION, request) .then(res => { assert.deepEqual(res.result.result, { "matched_path": { @@ -938,11 +1145,11 @@ describe('Blockchain Node', () => { }) }) - describe('ain_matchRule api', () => { + describe('json-rpc api: ain_matchRule', () => { it('returns correct value (write)', () => { const ref = "/apps/test/test_rule/some/path"; const request = { ref, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }; - return jayson.client.http(server1 + '/json-rpc').request('ain_matchRule', request) + return jayson.client.http(server1 + '/json-rpc').request(JSON_RPC_METHODS.AIN_MATCH_RULE, request) .then(res => { assert.deepEqual(res.result.result, { "write": { @@ -977,7 +1184,7 @@ describe('Blockchain Node', () => { it('returns correct value (state)', () => { const ref = "/apps/test/test_rule/state"; const request = { ref, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }; - return jayson.client.http(server1 + '/json-rpc').request('ain_matchRule', request) + return jayson.client.http(server1 + '/json-rpc').request(JSON_RPC_METHODS.AIN_MATCH_RULE, request) .then(res => { assert.deepEqual(res.result.result, { "write": { @@ -1025,7 +1232,7 @@ describe('Blockchain Node', () => { it('returns correct value (state & write)', () => { const ref = "/apps/test/test_rule/state/and/write"; const request = { ref, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }; - return jayson.client.http(server1 + '/json-rpc').request('ain_matchRule', request) + return jayson.client.http(server1 + '/json-rpc').request(JSON_RPC_METHODS.AIN_MATCH_RULE, request) .then(res => { assert.deepEqual(res.result.result, { "write": { @@ -1064,11 +1271,11 @@ describe('Blockchain Node', () => { }) }) - describe('ain_matchOwner api', () => { + describe('json-rpc api: ain_matchOwner', () => { it('returns correct value', () => { const ref = "/apps/test/test_owner/some/path"; const request = { ref, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }; - return jayson.client.http(server1 + '/json-rpc').request('ain_matchOwner', request) + return jayson.client.http(server1 + '/json-rpc').request(JSON_RPC_METHODS.AIN_MATCH_OWNER, request) .then(res => { assert.deepEqual(res.result.result, { "matched_path": { @@ -1093,13 +1300,13 @@ describe('Blockchain Node', () => { }) }) - describe('ain_evalRule api', () => { + describe('json-rpc api: ain_evalRule', () => { it('returns true', () => { const ref = "/apps/test/test_rule/some/path"; const value = "value"; const address = "abcd"; const request = { ref, value, address, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }; - return jayson.client.http(server1 + '/json-rpc').request('ain_evalRule', request) + return jayson.client.http(server1 + '/json-rpc').request(JSON_RPC_METHODS.AIN_EVAL_RULE, request) .then(res => { assert.deepEqual(eraseEvalResMatched(res.result.result), { "code": 0, @@ -1113,7 +1320,7 @@ describe('Blockchain Node', () => { const value = "value"; const address = "efgh"; const request = { ref, value, address, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }; - return jayson.client.http(server1 + '/json-rpc').request('ain_evalRule', request) + return jayson.client.http(server1 + '/json-rpc').request(JSON_RPC_METHODS.AIN_EVAL_RULE, request) .then(res => { res.result.result.message = 'erased'; assert.deepEqual(eraseEvalResMatched(res.result.result), { @@ -1125,13 +1332,13 @@ describe('Blockchain Node', () => { }) }) - describe('ain_evalOwner api', () => { + describe('json-rpc api: ain_evalOwner', () => { it('returns correct value', () => { const ref = "/apps/test/test_owner/some/path"; const address = "abcd"; const permission = "write_owner"; const request = { ref, permission, address, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }; - return jayson.client.http(server1 + '/json-rpc').request('ain_evalOwner', request) + return jayson.client.http(server1 + '/json-rpc').request(JSON_RPC_METHODS.AIN_EVAL_OWNER, request) .then(res => { assert.deepEqual(res.result.result, { "code": 0, @@ -1169,11 +1376,11 @@ describe('Blockchain Node', () => { }) }) - describe('ain_getStateProof api', () => { + describe('json-rpc api: ain_getStateProof', () => { it('returns correct value', () => { const ref = '/values/blockchain_params/token/symbol'; const request = { ref, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }; - return jayson.client.http(server1 + '/json-rpc').request('ain_getStateProof', request) + return jayson.client.http(server1 + '/json-rpc').request(JSON_RPC_METHODS.AIN_GET_STATE_PROOF, request) .then(res => { expect(res.result.result['#state_ph']).to.not.equal(null); const verifResult = verifyStateProof(res.result.result); @@ -1189,22 +1396,22 @@ describe('Blockchain Node', () => { }) }) - describe('ain_getProofHash api', () => { + describe('json-rpc api: ain_getProofHash', () => { it('returns correct value', () => { const ref = '/values/blockchain_params/token/symbol'; const request = { ref, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }; - return jayson.client.http(server1 + '/json-rpc').request('ain_getProofHash', request) + return jayson.client.http(server1 + '/json-rpc').request(JSON_RPC_METHODS.AIN_GET_PROOF_HASH, request) .then(res => { expect(res.result.result).to.not.equal(null); }) }) }) - describe('ain_getStateInfo api', () => { + describe('json-rpc api: ain_getStateInfo', () => { it('returns correct value', () => { const ref = '/values/apps/test/test_state_info/some/path'; const request = { ref, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }; - return jayson.client.http(server1 + '/json-rpc').request('ain_getStateInfo', request) + return jayson.client.http(server1 + '/json-rpc').request(JSON_RPC_METHODS.AIN_GET_STATE_INFO, request) .then(res => { const stateInfo = res.result.result; // Erase some properties for stable comparison. @@ -1215,6 +1422,7 @@ describe('Blockchain Node', () => { "#num_children": 2, "#state_ph": "erased", "#tree_height": 2, + "#tree_max_siblings": 2, "#tree_size": 5, "#tree_bytes": 0, "#version": "erased" @@ -1223,17 +1431,17 @@ describe('Blockchain Node', () => { }) }) - describe('ain_getStateUsage api', () => { + describe('json-rpc api: ain_getStateUsage', () => { it('with existing app name', () => { const request = { app_name: 'test', protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }; - return jayson.client.http(server1 + '/json-rpc').request('ain_getStateUsage', request) + return jayson.client.http(server1 + '/json-rpc').request(JSON_RPC_METHODS.AIN_GET_STATE_USAGE, request) .then(res => { const stateUsage = res.result.result; assert.deepEqual(stateUsage, { "available": { - "tree_bytes": 2474819814, + "tree_bytes": 2474819644, "tree_height": 30, - "tree_size": 15467623.8375 + "tree_size": 15467622.775 }, "staking": { "app": 1, @@ -1241,9 +1449,10 @@ describe('Blockchain Node', () => { "unstakeable": 1 }, "usage": { - "tree_bytes": 180186, + "tree_bytes": 180356, "tree_height": 24, - "tree_size": 1066 + "tree_max_siblings": 1011, + "tree_size": 1067 } }); }) @@ -1251,7 +1460,7 @@ describe('Blockchain Node', () => { it('with non-existing app name', () => { const request = { app_name: 'app_non_existing', protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }; - return jayson.client.http(server1 + '/json-rpc').request('ain_getStateUsage', request) + return jayson.client.http(server1 + '/json-rpc').request(JSON_RPC_METHODS.AIN_GET_STATE_USAGE, request) .then(res => { const stateUsage = res.result.result; assert.deepEqual(stateUsage, { @@ -1268,6 +1477,7 @@ describe('Blockchain Node', () => { "usage": { "tree_bytes": 0, "tree_height": 0, + "tree_max_siblings": 0, "tree_size": 0 } }); @@ -1275,30 +1485,31 @@ describe('Blockchain Node', () => { }) }) - describe('ain_getProtocolVersion api', () => { + describe('json-rpc api: ain_getProtocolVersion', () => { it('returns the correct version', () => { const client = jayson.client.http(server1 + '/json-rpc'); - return client.request('ain_getProtocolVersion', {}) + return client.request(JSON_RPC_METHODS.AIN_GET_PROTOCOL_VERSION, {}) .then(res => { expect(res.result.protoVer).to.equal(BlockchainConsts.CURRENT_PROTOCOL_VERSION); }) }); }); - describe('ain_checkProtocolVersion api', () => { + describe('json-rpc api: ain_checkProtocolVersion', () => { it('returns success code', () => { const client = jayson.client.http(server1 + '/json-rpc'); - return client.request('ain_checkProtocolVersion', { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }) + return client.request(JSON_RPC_METHODS.AIN_CHECK_PROTOCOL_VERSION, { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }) .then(res => { + expect(res.result.result).to.equal(true); expect(res.result.code).to.equal(0); - expect(res.result.result).to.equal("Success"); }); }); it('returns version not specified code', () => { const client = jayson.client.http(server1 + '/json-rpc'); - return client.request('ain_checkProtocolVersion', {}) + return client.request(JSON_RPC_METHODS.AIN_CHECK_PROTOCOL_VERSION, {}) .then(res => { + expect(res.result.result).to.equal(false); expect(res.result.code).to.equal(30101); expect(res.result.message).to.equal("Protocol version not specified."); }); @@ -1306,8 +1517,9 @@ describe('Blockchain Node', () => { it('returns invalid version code', () => { const client = jayson.client.http(server1 + '/json-rpc'); - return client.request('ain_checkProtocolVersion', { protoVer: 'a.b.c' }) + return client.request(JSON_RPC_METHODS.AIN_CHECK_PROTOCOL_VERSION, { protoVer: 'a.b.c' }) .then(res => { + expect(res.result.result).to.equal(false); expect(res.result.code).to.equal(30102); expect(res.result.message).to.equal("Invalid protocol version."); }); @@ -1315,8 +1527,9 @@ describe('Blockchain Node', () => { it('returns incompatible version code for ill-formatted version', () => { const client = jayson.client.http(server1 + '/json-rpc'); - return client.request('ain_checkProtocolVersion', { protoVer: 0 }) + return client.request(JSON_RPC_METHODS.AIN_CHECK_PROTOCOL_VERSION, { protoVer: 0 }) .then(res => { + expect(res.result.result).to.equal(false); expect(res.result.code).to.equal(30103); expect(res.result.message).to.equal("Incompatible protocol version."); }); @@ -1324,20 +1537,45 @@ describe('Blockchain Node', () => { it('returns incompatible version code for low version', () => { const client = jayson.client.http(server1 + '/json-rpc'); - return client.request('ain_checkProtocolVersion', { protoVer: '0.0.1' }) + return client.request(JSON_RPC_METHODS.AIN_CHECK_PROTOCOL_VERSION, { protoVer: '0.0.1' }) .then(res => { + expect(res.result.result).to.equal(false); expect(res.result.code).to.equal(30103); expect(res.result.message).to.equal("Incompatible protocol version."); }); }); }) - describe('ain_getAddress api', () => { + describe('json-rpc api: ain_validateAppName', () => { + it('returns true', () => { + const client = jayson.client.http(server1 + '/json-rpc'); + const request = { app_name: 'app_name_valid0', protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }; + return client.request(JSON_RPC_METHODS.AIN_VALIDATE_APP_NAME, request) + .then(res => { + expect(res.result.is_valid).to.equal(true); + expect(res.result.code).to.equal(0); + }) + }); + + it('returns false', () => { + const client = jayson.client.http(server1 + '/json-rpc'); + const request = { app_name: 'app/path', protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }; + return client.request(JSON_RPC_METHODS.AIN_VALIDATE_APP_NAME, request) + .then(res => { + expect(res.result.is_valid).to.equal(false); + expect(res.result.code).to.equal(30601); + expect(res.result.message).to.equal('Invalid app name for state label: app/path'); + }) + }); + }); + + describe('json-rpc api: ain_getAddress', () => { it('returns the correct node address', () => { const expAddr = '0x01A0980d2D4e418c7F27e1ef539d01A5b5E93204'; const jsonRpcClient = jayson.client.http(server2 + '/json-rpc'); - return jsonRpcClient.request('ain_getAddress', { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }) - .then(res => { + return jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET_ADDRESS, { + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION + }).then(res => { expect(res.result.result).to.equal(expAddr); }); }); @@ -2187,7 +2425,7 @@ describe('Blockchain Node', () => { } const body = parseOrLog(syncRequest('POST', server1 + '/set', {json: request}) .body.toString('utf-8')); - expect(body.result.result.code).to.equal(JsonRpcApiResultCode.SET_EXCEEDS_OP_LIST_SIZE_LIMIT); + expect(body.result.result.code).to.equal(30005); expect( body.result.result.message .includes('The transaction exceeds the max op_list size limit')).to.equal(true); @@ -2978,7 +3216,7 @@ describe('Blockchain Node', () => { }) }) - describe('ain_sendSignedTransaction api', () => { + describe('json-rpc api: ain_sendSignedTransaction', () => { const account = { address: "0x9534bC7529961E5737a3Dd317BdEeD41AC08a52D", private_key: "e96292ef0676287908fc3461f747f106b7b9336f183b1766f83672fbe893664d", @@ -3092,7 +3330,7 @@ describe('Blockchain Node', () => { }; const signature = ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex')); - return client.request('ain_sendSignedTransaction', { + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { tx_body: txBody, signature, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION @@ -3130,7 +3368,7 @@ describe('Blockchain Node', () => { it('accepts a transaction with numbered nonce', () => { const client = jayson.client.http(server1 + '/json-rpc'); - return client.request('ain_getNonce', { + return client.request(JSON_RPC_METHODS.AIN_GET_NONCE, { address: account.address, from: 'pending', protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION @@ -3149,7 +3387,7 @@ describe('Blockchain Node', () => { }; const signature = ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex')); - return client.request('ain_sendSignedTransaction', { + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { tx_body: txBody, signature, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION @@ -3190,7 +3428,7 @@ describe('Blockchain Node', () => { it('accepts a transaction with account registration gas amount from nonce', () => { // NOTE: account2 does not have balance nor nonce/timestamp. const client = jayson.client.http(server1 + '/json-rpc'); - return client.request('ain_getNonce', { + return client.request(JSON_RPC_METHODS.AIN_GET_NONCE, { address: account2.address, from: 'pending', protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION @@ -3209,7 +3447,7 @@ describe('Blockchain Node', () => { }; const signature = ainUtil.ecSignTransaction(txBody, Buffer.from(account2.private_key, 'hex')); - return client.request('ain_sendSignedTransaction', { + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { tx_body: txBody, signature, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION @@ -3250,7 +3488,7 @@ describe('Blockchain Node', () => { it('accepts a transaction without account registration gas amount from nonce', () => { // NOTE: account2 already has nonce/timestamp. const client = jayson.client.http(server1 + '/json-rpc'); - return client.request('ain_getNonce', { + return client.request(JSON_RPC_METHODS.AIN_GET_NONCE, { address: account2.address, from: 'pending', protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION @@ -3269,7 +3507,7 @@ describe('Blockchain Node', () => { }; const signature = ainUtil.ecSignTransaction(txBody, Buffer.from(account2.private_key, 'hex')); - return client.request('ain_sendSignedTransaction', { + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { tx_body: txBody, signature, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION @@ -3322,7 +3560,7 @@ describe('Blockchain Node', () => { }; const signature = ainUtil.ecSignTransaction(txBody, Buffer.from(account09.private_key, 'hex')); - return client.request('ain_sendSignedTransaction', { + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { tx_body: txBody, signature, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION @@ -3390,7 +3628,7 @@ describe('Blockchain Node', () => { }; const signature = ainUtil.ecSignTransaction(txBody, Buffer.from(account09.private_key, 'hex')); - return client.request('ain_sendSignedTransaction', { + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { tx_body: txBody, signature, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION @@ -3458,7 +3696,7 @@ describe('Blockchain Node', () => { }; const signature = ainUtil.ecSignTransaction(txBody, Buffer.from(account09.private_key, 'hex')); - return client.request('ain_sendSignedTransaction', { + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { tx_body: txBody, signature, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION @@ -3514,7 +3752,7 @@ describe('Blockchain Node', () => { it('accepts a multi-set transaction with account registration gas amount from nonce', () => { // NOTE: account4 does not have balance nor nonce/timestamp. const client = jayson.client.http(server1 + '/json-rpc'); - return client.request('ain_getNonce', { + return client.request(JSON_RPC_METHODS.AIN_GET_NONCE, { address: account4.address, from: 'pending', protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION @@ -3543,7 +3781,7 @@ describe('Blockchain Node', () => { }; const signature = ainUtil.ecSignTransaction(txBody, Buffer.from(account4.private_key, 'hex')); - return client.request('ain_sendSignedTransaction', { + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { tx_body: txBody, signature, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION @@ -3592,7 +3830,7 @@ describe('Blockchain Node', () => { it('accepts a multi-set transaction without account registration gas amount from nonce', () => { // NOTE: account4 already has nonce/timestamp. const client = jayson.client.http(server1 + '/json-rpc'); - return client.request('ain_getNonce', { + return client.request(JSON_RPC_METHODS.AIN_GET_NONCE, { address: account4.address, from: 'pending', protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION @@ -3621,7 +3859,7 @@ describe('Blockchain Node', () => { }; const signature = ainUtil.ecSignTransaction(txBody, Buffer.from(account4.private_key, 'hex')); - return client.request('ain_sendSignedTransaction', { + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { tx_body: txBody, signature, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION @@ -3684,7 +3922,7 @@ describe('Blockchain Node', () => { }; const signature = ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex')); - return client.request('ain_sendSignedTransaction', { + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { tx_body: txBody, signature, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION @@ -3765,7 +4003,7 @@ describe('Blockchain Node', () => { }; const signature = ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex')); - return client.request('ain_sendSignedTransaction', { + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { tx_body: txBody, signature, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION @@ -3797,6 +4035,58 @@ describe('Blockchain Node', () => { }); }) + it('rejects a transaction that exceeds the op_list size limit.', () => { + const client = jayson.client.http(server1 + '/json-rpc'); + const setOpListSizeLimit = BlockchainParams.resource.set_op_list_size_limit; + const opList = []; + for (let i = 0; i < setOpListSizeLimit + 1; i++) { // 1 more than the limit + opList.push({ + type: 'SET_VALUE', + ref: `/apps/test/test_value/some/path`, + value: 'some other value' + }); + } + const txBody = { + operation: { + type: 'SET', + op_list: opList, + }, + gas_price: 0, + timestamp: Date.now(), + nonce: -1 + }; + const signature = + ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex')); + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { + tx_body: txBody, + signature, + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION + }).then((res) => { + res.result.result.tx_hash = 'erased'; + assert.deepEqual(res.result, { + "protoVer": BlockchainConsts.CURRENT_PROTOCOL_VERSION, + "result": { + "result": { + "code": 30005, + "gas_amount_charged": 0, + "gas_amount_total": { + "bandwidth": { + "service": 0 + }, + "state": { + "service": 0 + } + }, + "gas_cost_total": 0, + "message": "The transaction exceeds the max op_list size limit: 51 > 50", + "result_list": null, + }, + "tx_hash": "erased" + } + }); + }) + }) + it('rejects a transaction that exceeds its size limit.', () => { const client = jayson.client.http(server1 + '/json-rpc'); const longText = 'a'.repeat(BlockchainParams.resource.tx_bytes_limit / 2); @@ -3812,16 +4102,15 @@ describe('Blockchain Node', () => { }; const signature = ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex')); - return client.request('ain_sendSignedTransaction', { + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { tx_body: txBody, signature, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }).then((res) => { assert.deepEqual(res.result, { - result: { - code: 30301, - message: `Transaction size exceeds its limit: ${BlockchainParams.resource.tx_bytes_limit} bytes.`, - }, + result: null, + code: 30301, + message: `Transaction size exceeds its limit: ${BlockchainParams.resource.tx_bytes_limit} bytes.`, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }); }) @@ -3841,16 +4130,15 @@ describe('Blockchain Node', () => { }; const signature = ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex')); - return client.request('ain_sendSignedTransaction', { + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { transaction: txBody, // wrong field name signature, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }).then((res) => { assert.deepEqual(res.result, { - result: { - code: 30302, - message: `Missing properties.`, - }, + result: null, + code: 30302, + message: 'Missing properties.', protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }); }) @@ -3870,16 +4158,15 @@ describe('Blockchain Node', () => { }; const signature = ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex')); - return client.request('ain_sendSignedTransaction', { + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { tx_body: txBody, signature, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }).then((res) => { assert.deepEqual(res.result, { - result: { - code: 30303, - message: `Invalid transaction format.`, - }, + result: null, + code: 30303, + message: 'Invalid transaction format.', protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }); }) @@ -3899,23 +4186,22 @@ describe('Blockchain Node', () => { }; const signature = ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex')); - return client.request('ain_sendSignedTransaction', { + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { tx_body: txBody, signature: signature + '0', // invalid signature protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }).then((res) => { assert.deepEqual(res.result, { - result: { - code: 30304, - message: `Invalid transaction signature.`, - }, + result: null, + code: 30304, + message: 'Invalid transaction signature.', protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }); }) }) }) - describe('ain_sendSignedTransactionBatch api', () => { + describe('json-rpc api: ain_sendSignedTransactionBatch', () => { const account = { address: "0x85a620A5A46d01cc1fCF49E73ab00710d4da943E", private_key: "b542fc2ca4a68081b3ba238888d3a8783354c3aa81711340fd69f6ff32798525", @@ -4114,7 +4400,7 @@ describe('Blockchain Node', () => { signature }); } - return client.request('ain_sendSignedTransactionBatch', { + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION_BATCH, { tx_list: txList, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }).then((res) => { @@ -4140,7 +4426,7 @@ describe('Blockchain Node', () => { }; const signature = ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex')); - return client.request('ain_sendSignedTransactionBatch', { + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION_BATCH, { tx_list: { // should be an array tx_body: txBody, signature, @@ -4148,16 +4434,15 @@ describe('Blockchain Node', () => { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }).then((res) => { assert.deepEqual(res.result, { - result: { - code: 30401, - message: `Invalid batch transaction format.` - }, + result: null, + code: 30401, + message: 'Invalid batch transaction format.', protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, }); }) }) - it('accepts a batch transaction of under transaction list size limit.', () => { + it('accepts a batch transaction of under the tx_list size limit.', () => { const client = jayson.client.http(server1 + '/json-rpc'); const timestamp = Date.now(); const txBodyTemplate = { @@ -4180,7 +4465,7 @@ describe('Blockchain Node', () => { signature, }); } - return client.request('ain_sendSignedTransactionBatch', { + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION_BATCH, { tx_list: txList, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }).then((res) => { @@ -4193,7 +4478,7 @@ describe('Blockchain Node', () => { }) }) - it('rejects a batch transaction of over transaction list size limit.', () => { + it('rejects a batch transaction of over the tx_list size limit.', () => { const client = jayson.client.http(server1 + '/json-rpc'); const timestamp = Date.now(); const txBodyTemplate = { @@ -4216,15 +4501,14 @@ describe('Blockchain Node', () => { signature, }); } - return client.request('ain_sendSignedTransactionBatch', { + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION_BATCH, { tx_list: txList, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }).then((res) => { assert.deepEqual(res.result, { - result: { - code: 30402, - message: `Batch transaction list size exceeds its limit: ${BlockchainParams.resource.batch_tx_list_size_limit}.` - }, + result: null, + code: 30402, + message: `Batch transaction list size exceeds its limit: ${BlockchainParams.resource.batch_tx_list_size_limit}.`, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, }); }) @@ -4263,7 +4547,7 @@ describe('Blockchain Node', () => { signature, }); } - const res1 = await client.request('ain_sendSignedTransactionBatch', { + const res1 = await client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION_BATCH, { tx_list: txList1, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }); @@ -4287,7 +4571,7 @@ describe('Blockchain Node', () => { tx_body: txBody, signature, }); - const res2 = await client.request('ain_sendSignedTransactionBatch', { + const res2 = await client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION_BATCH, { tx_list: txList2, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }); @@ -4323,7 +4607,7 @@ describe('Blockchain Node', () => { }; const signature = ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex')); - return client.request('ain_sendSignedTransactionBatch', { + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION_BATCH, { tx_list: [ { tx_body: txBodyBefore, @@ -4343,10 +4627,9 @@ describe('Blockchain Node', () => { const resultList = _.get(res, 'result.result'); expect(CommonUtil.isArray(resultList)).to.equal(false); assert.deepEqual(res.result, { - result: { - code: 30403, - message: `Transaction[1]'s size exceededs its limit: ${BlockchainParams.resource.tx_bytes_limit} bytes.`, - }, + result: null, + code: 30403, + message: `Transaction[1]'s size exceededs its limit: ${BlockchainParams.resource.tx_bytes_limit} bytes.`, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, }); }) @@ -4363,7 +4646,7 @@ describe('Blockchain Node', () => { timestamp: Date.now(), nonce: -1 }; - return client.request('ain_sendSignedTransactionBatch', { + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION_BATCH, { tx_list: [ { tx_body: txBodyBefore, @@ -4381,10 +4664,9 @@ describe('Blockchain Node', () => { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }).then((res) => { assert.deepEqual(res.result, { - result: { - code: 30404, - message: `Missing properties of transaction[1].`, - }, + result: null, + code: 30404, + message: 'Missing properties of transaction[1].', protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, }); }) @@ -4404,7 +4686,7 @@ describe('Blockchain Node', () => { }; const signature = ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex')); - return client.request('ain_sendSignedTransactionBatch', { + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION_BATCH, { tx_list: [ { tx_body: txBodyBefore, @@ -4422,10 +4704,9 @@ describe('Blockchain Node', () => { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }).then((res) => { assert.deepEqual(res.result, { - result: { - code: 30405, - message: `Invalid format of transaction[1].` - }, + result: null, + code: 30405, + message: 'Invalid format of transaction[1].', protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }); }) @@ -4445,7 +4726,7 @@ describe('Blockchain Node', () => { }; const signature = ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex')); - return client.request('ain_sendSignedTransactionBatch', { + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION_BATCH, { tx_list: [ { tx_body: txBodyBefore, @@ -4463,10 +4744,9 @@ describe('Blockchain Node', () => { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }).then((res) => { assert.deepEqual(res.result, { - result: { - code: 30406, - message: `Invalid signature of transaction[1].` - }, + result: null, + code: 30406, + message: 'Invalid signature of transaction[1].', protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }); }) diff --git a/test/integration/sharding.test.js b/test/integration/sharding.test.js index 26843e9cb..882ffb681 100644 --- a/test/integration/sharding.test.js +++ b/test/integration/sharding.test.js @@ -34,6 +34,7 @@ const { setUpApp, eraseEvalResMatched, } = require('../test-util'); +const { JSON_RPC_METHODS } = require('../../json_rpc/constants'); const PROJECT_ROOT = require('path').dirname(__filename) + "/../../" const TRACKER_SERVER = PROJECT_ROOT + "tracker-server/index.js" @@ -448,6 +449,10 @@ describe('Sharding', () => { }); describe('Shard reporter node restart', () => { + before(() => { + ENV_VARIABLES[2].PEER_CANDIDATE_JSON_RPC_URL = "http://localhost:9002/json-rpc"; + }); + it('can resume reporting after missing some reports', async () => { const latestBefore = parseOrLog(syncRequest( 'GET', parentServer + `/get_value?ref=${sharding.sharding_path}/.shard/latest_block_number`) @@ -474,6 +479,10 @@ describe('Sharding', () => { expect(latestAfter).to.be.greaterThan(latestBefore); }); }); + + after(() => { + delete ENV_VARIABLES[2].PEER_CANDIDATE_JSON_RPC_URL; + }); }); describe('API tests', () => { @@ -1251,7 +1260,7 @@ describe('Sharding', () => { it('ain_get with is_global = false', () => { const expected = 100; const jsonRpcClient = jayson.client.http(server2 + '/json-rpc'); - return jsonRpcClient.request('ain_get', { + return jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET, { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, type: 'GET_VALUE', ref: "/apps/test/test_value/some/path" @@ -1264,7 +1273,7 @@ describe('Sharding', () => { it('ain_get with is_global = false (explicit)', () => { const expected = 100; const jsonRpcClient = jayson.client.http(server2 + '/json-rpc'); - return jsonRpcClient.request('ain_get', { + return jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET, { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, type: 'GET_VALUE', ref: "/apps/test/test_value/some/path", @@ -1278,7 +1287,7 @@ describe('Sharding', () => { it('ain_get with is_global = true', () => { const expected = 100; const jsonRpcClient = jayson.client.http(server2 + '/json-rpc'); - return jsonRpcClient.request('ain_get', { + return jsonRpcClient.request(JSON_RPC_METHODS.AIN_GET, { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, type: 'GET_VALUE', ref: "/apps/afan/apps/test/test_value/some/path", @@ -1294,7 +1303,7 @@ describe('Sharding', () => { it('ain_matchFunction with is_global = false', () => { const ref = "/apps/test/test_function/some/path"; const request = { ref, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }; - return jayson.client.http(server1 + '/json-rpc').request('ain_matchFunction', request) + return jayson.client.http(server1 + '/json-rpc').request(JSON_RPC_METHODS.AIN_MATCH_FUNCTION, request) .then(res => { assert.deepEqual(res.result.result, { "matched_path": { @@ -1320,7 +1329,7 @@ describe('Sharding', () => { it('ain_matchFunction with is_global = true', () => { const ref = "/apps/afan/apps/test/test_function/some/path"; const request = { ref, is_global: true, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }; - return jayson.client.http(server1 + '/json-rpc').request('ain_matchFunction', request) + return jayson.client.http(server1 + '/json-rpc').request(JSON_RPC_METHODS.AIN_MATCH_FUNCTION, request) .then(res => { assert.deepEqual(res.result.result, { "matched_path": { @@ -1348,7 +1357,7 @@ describe('Sharding', () => { it('ain_matchRule with is_global = false', () => { const ref = "/apps/test/test_rule/some/path"; const request = { ref, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }; - return jayson.client.http(server1 + '/json-rpc').request('ain_matchRule', request) + return jayson.client.http(server1 + '/json-rpc').request(JSON_RPC_METHODS.AIN_MATCH_RULE, request) .then(res => { assert.deepEqual(res.result.result, { "write": { @@ -1383,7 +1392,7 @@ describe('Sharding', () => { it('ain_matchRule with is_global = true', () => { const ref = "/apps/afan/apps/test/test_rule/some/path"; const request = { ref, is_global: true, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }; - return jayson.client.http(server1 + '/json-rpc').request('ain_matchRule', request) + return jayson.client.http(server1 + '/json-rpc').request(JSON_RPC_METHODS.AIN_MATCH_RULE, request) .then(res => { assert.deepEqual(res.result.result, { "write": { @@ -1420,7 +1429,7 @@ describe('Sharding', () => { it('ain_matchOwner with is_global = false', () => { const ref = "/apps/test/test_owner/some/path"; const request = { ref, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }; - return jayson.client.http(server1 + '/json-rpc').request('ain_matchOwner', request) + return jayson.client.http(server1 + '/json-rpc').request(JSON_RPC_METHODS.AIN_MATCH_OWNER, request) .then(res => { assert.deepEqual(res.result.result, { "matched_path": { @@ -1447,7 +1456,7 @@ describe('Sharding', () => { it('ain_matchOwner with is_global = true', () => { const ref = "/apps/afan/apps/test/test_owner/some/path"; const request = { ref, is_global: true, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }; - return jayson.client.http(server1 + '/json-rpc').request('ain_matchOwner', request) + return jayson.client.http(server1 + '/json-rpc').request(JSON_RPC_METHODS.AIN_MATCH_OWNER, request) .then(res => { assert.deepEqual(res.result.result, { "matched_path": { @@ -1478,7 +1487,7 @@ describe('Sharding', () => { const value = "value"; const address = "abcd"; const request = { ref, value, address, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }; - return jayson.client.http(server1 + '/json-rpc').request('ain_evalRule', request) + return jayson.client.http(server1 + '/json-rpc').request(JSON_RPC_METHODS.AIN_EVAL_RULE, request) .then(res => { assert.deepEqual(eraseEvalResMatched(res.result.result), { "code": 0, @@ -1493,7 +1502,7 @@ describe('Sharding', () => { const address = "abcd"; const request = { ref, value, address, is_global: true, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }; - return jayson.client.http(server1 + '/json-rpc').request('ain_evalRule', request) + return jayson.client.http(server1 + '/json-rpc').request(JSON_RPC_METHODS.AIN_EVAL_RULE, request) .then(res => { assert.deepEqual(eraseEvalResMatched(res.result.result), { "code": 0, @@ -1509,7 +1518,7 @@ describe('Sharding', () => { const address = "abcd"; const permission = "write_owner"; const request = { ref, permission, address, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }; - return jayson.client.http(server1 + '/json-rpc').request('ain_evalOwner', request) + return jayson.client.http(server1 + '/json-rpc').request(JSON_RPC_METHODS.AIN_EVAL_OWNER, request) .then(res => { assert.deepEqual(res.result.result, { "code": 0, @@ -1552,7 +1561,7 @@ describe('Sharding', () => { const permission = "write_owner"; const request = { ref, permission, address, is_global: true, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }; - return jayson.client.http(server1 + '/json-rpc').request('ain_evalOwner', request) + return jayson.client.http(server1 + '/json-rpc').request(JSON_RPC_METHODS.AIN_EVAL_OWNER, request) .then(res => { assert.deepEqual(res.result.result, { "code": 0, @@ -2037,7 +2046,7 @@ describe('Sharding', () => { } const signature = ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex')); - return client.request('ain_sendSignedTransaction', + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { tx_body: txBody, signature, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }) .then((res) => { const result = _.get(res, 'result.result', null); @@ -2086,8 +2095,8 @@ describe('Sharding', () => { } const signature = ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex')); - return client.request('ain_sendSignedTransaction', { tx_body: txBody, signature, - protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }) + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, + { tx_body: txBody, signature, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }) .then((res) => { const result = _.get(res, 'result.result', null); expect(result).to.not.equal(null); @@ -2135,8 +2144,8 @@ describe('Sharding', () => { } const signature = ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex')); - return client.request('ain_sendSignedTransaction', { tx_body: txBody, signature, - protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }) + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, + { tx_body: txBody, signature, protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION }) .then((res) => { const result = _.get(res, 'result.result', null); expect(result).to.not.equal(null); @@ -2185,7 +2194,7 @@ describe('Sharding', () => { } const signature = ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex')); - return client.request('ain_sendSignedTransactionBatch', { + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION_BATCH, { tx_list: [ { tx_body: txBody, @@ -2243,7 +2252,7 @@ describe('Sharding', () => { } const signature = ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex')); - return client.request('ain_sendSignedTransactionBatch', { + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION_BATCH, { tx_list: [ { tx_body: txBody, @@ -2304,7 +2313,7 @@ describe('Sharding', () => { } const signature = ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex')); - return client.request('ain_sendSignedTransactionBatch', { + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION_BATCH, { tx_list: [ { tx_body: txBody, diff --git a/test/test-util.js b/test/test-util.js index 1de0afca9..e56ebd720 100644 --- a/test/test-util.js +++ b/test/test-util.js @@ -7,10 +7,10 @@ const DB = require('../db'); const { BlockchainConsts, StateVersions, - BlockchainParams, ValueChangedEventSources, } = require('../common/constants'); const CommonUtil = require('../common/common-util'); +const { JSON_RPC_METHODS } = require('../json_rpc/constants'); const GET_OPTIONS_INCLUDE_ALL = { includeTreeInfo: true, @@ -176,8 +176,12 @@ async function waitUntilNodeSyncs(server) { while (isSyncing) { try { isSyncing = parseOrLog(syncRequest('POST', server + '/json-rpc', - {json: {jsonrpc: '2.0', method: 'net_syncing', id: 0, - params: {protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION}}}) + { + json: { + jsonrpc: '2.0', method: JSON_RPC_METHODS.NET_SYNCING, id: 0, + params: { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION } + } + }) .body.toString('utf-8')).result.result; } catch (e) { // server may not be ready yet diff --git a/test/unit/db.test.js b/test/unit/db.test.js index cf1e19b56..3c99aee0f 100644 --- a/test/unit/db.test.js +++ b/test/unit/db.test.js @@ -319,7 +319,7 @@ describe("DB operations", () => { node.db.stateManager.finalizeVersion(backupFinalVersion); }) - it('getValue to retrieve value near top of database with is_shallow', () => { + it('getValue to retrieve value near top of database with isShallow = true', () => { assert.deepEqual(node.db.getValue('/apps/test', { isShallow: true }), { 'ai': { "#state_ph": "0x4c6895fec04b40d425d1542b7cfb2f78b0e8cd2dc4d35d0106100f1ecc168cec" @@ -339,12 +339,92 @@ describe("DB operations", () => { }) }); + it('getValue to retrieve value near top of database with isPartial = true and lastEndLabel', () => { + assert.deepEqual(node.db.getValue('/apps/test', { isPartial: true }), { + 'ai': { + "#state_ph": "0x4c6895fec04b40d425d1542b7cfb2f78b0e8cd2dc4d35d0106100f1ecc168cec", + "#serial": 2, + }, + 'increment': { + "#state_ph": "0x11d1aa4946a3e44e3d467d4da85617d56aecd2559fdd6d9e5dd8fb6b5ded71b8", + "#serial": 5, + }, + 'decrement': { + "#state_ph": "0x11d1aa4946a3e44e3d467d4da85617d56aecd2559fdd6d9e5dd8fb6b5ded71b8", + "#serial": 7, + }, + 'nested': { + "#state_ph": "0x8763e301c728729e38c1f5500a2af7163783bdf0948a7baf7bc87b35f33b347f", + "#serial": 9, + }, + 'shards': { + "#state_ph": "0xbe0fbf9fec28b21de391ebb202517a420f47ee199aece85153e8fb4d9453f223", + "#serial": 11, + }, + "#end_label": "736861726473" + }) + // lastEndLabel = "736861726472" ("736861726473" - 1) + assert.deepEqual(node.db.getValue('/apps/test', { isPartial: true, lastEndLabel: "736861726472" }), { + 'shards': { + "#state_ph": "0xbe0fbf9fec28b21de391ebb202517a420f47ee199aece85153e8fb4d9453f223", + "#serial": 11, + }, + "#end_label": "736861726473" + }) + assert.deepEqual(node.db.getValue('/apps/test', { isPartial: true, lastEndLabel: "736861726473" }), { + "#end_label": null + }) + }); + + it('getValue to retrieve value near top of database with isPartial = true and lastEndLabel - chaining', () => { + // Change GET_RESP_MAX_SIBLINGS value for testing. + const originalGetRespMaxSiblings = NodeConfigs.GET_RESP_MAX_SIBLINGS; + NodeConfigs.GET_RESP_MAX_SIBLINGS = 2; + + assert.deepEqual(node.db.getValue('/apps/test', { isPartial: true }), { + "ai": { + "#state_ph": "0x4c6895fec04b40d425d1542b7cfb2f78b0e8cd2dc4d35d0106100f1ecc168cec", + "#serial": 2, + }, + "decrement": { + "#state_ph": "0x11d1aa4946a3e44e3d467d4da85617d56aecd2559fdd6d9e5dd8fb6b5ded71b8", + "#serial": 7, + }, + "#end_label": "64656372656d656e74" + }) + assert.deepEqual(node.db.getValue('/apps/test', { isPartial: true, lastEndLabel: "64656372656d656e74" }), { + "increment": { + "#state_ph": "0x11d1aa4946a3e44e3d467d4da85617d56aecd2559fdd6d9e5dd8fb6b5ded71b8", + "#serial": 5, + }, + "nested": { + "#state_ph": "0x8763e301c728729e38c1f5500a2af7163783bdf0948a7baf7bc87b35f33b347f", + "#serial": 9, + }, + "#end_label": "6e6573746564" + }) + assert.deepEqual(node.db.getValue('/apps/test', { isPartial: true, lastEndLabel: "6e6573746564" }), { + "shards": { + "#state_ph": "0xbe0fbf9fec28b21de391ebb202517a420f47ee199aece85153e8fb4d9453f223", + "#serial": 11, + }, + "#end_label": "736861726473" + }) + assert.deepEqual(node.db.getValue('/apps/test', { isPartial: true, lastEndLabel: "736861726473" }), { + "#end_label": null + }) + + // Restore GET_RESP_MAX_SIBLINGS value. + NodeConfigs.GET_RESP_MAX_SIBLINGS = originalGetRespMaxSiblings; + }); + it('getValue to retrieve value with include_tree_info', () => { assert.deepEqual(node.db.getValue('/apps/test', { includeTreeInfo: true }), { "#num_children": 5, "#num_parents": 1, "#tree_bytes": 3708, "#tree_height": 4, + "#tree_max_siblings": 5, "#tree_size": 21, "ai": { "#num_children": 3, @@ -363,6 +443,10 @@ describe("DB operations", () => { "#tree_height:baz": 0, "#tree_height:comcom": 0, "#tree_height:foo": 0, + "#tree_max_siblings": 3, + "#tree_max_siblings:baz": 1, + "#tree_max_siblings:comcom": 1, + "#tree_max_siblings:foo": 1, "#tree_size": 4, "#tree_size:baz": 1, "#tree_size:comcom": 1, @@ -380,6 +464,8 @@ describe("DB operations", () => { "#tree_bytes:value": 168, "#tree_height": 1, "#tree_height:value": 0, + "#tree_max_siblings": 1, + "#tree_max_siblings:value": 1, "#tree_size": 2, "#tree_size:value": 1, "value": 20, @@ -393,6 +479,8 @@ describe("DB operations", () => { "#tree_bytes:value": 168, "#tree_height": 1, "#tree_height:value": 0, + "#tree_max_siblings": 1, + "#tree_max_siblings:value": 1, "#tree_size": 2, "#tree_size:value": 1, "value": 20, @@ -402,6 +490,7 @@ describe("DB operations", () => { "#num_parents": 1, "#tree_bytes": 502, "#tree_height": 2, + "#tree_max_siblings": 1, "#tree_size": 3, "far": { "#num_children": 1, @@ -412,6 +501,8 @@ describe("DB operations", () => { "#tree_bytes:down": 168, "#tree_height": 1, "#tree_height:down": 0, + "#tree_max_siblings": 1, + "#tree_max_siblings:down": 1, "#tree_size": 2, "#tree_size:down": 1, "down": 456, @@ -422,6 +513,7 @@ describe("DB operations", () => { "#num_parents": 1, "#tree_bytes": 1622, "#tree_height": 3, + "#tree_max_siblings": 2, "#tree_size": 9, "disabled_shard": { "#num_children": 2, @@ -432,6 +524,8 @@ describe("DB operations", () => { "#tree_bytes:path": 168, "#tree_height": 2, "#tree_height:path": 0, + "#tree_max_siblings": 2, + "#tree_max_siblings:path": 1, "#tree_size": 4, "#tree_size:path": 1, ".shard": { @@ -443,6 +537,8 @@ describe("DB operations", () => { "#tree_bytes:sharding_enabled": 164, "#tree_height": 1, "#tree_height:sharding_enabled": 0, + "#tree_max_siblings": 1, + "#tree_max_siblings:sharding_enabled": 1, "#tree_size": 2, "#tree_size:sharding_enabled": 1, "sharding_enabled": false, @@ -458,6 +554,8 @@ describe("DB operations", () => { "#tree_bytes:path": 168, "#tree_height": 2, "#tree_height:path": 0, + "#tree_max_siblings": 2, + "#tree_max_siblings:path": 1, "#tree_size": 4, "#tree_size:path": 1, ".shard": { @@ -469,6 +567,8 @@ describe("DB operations", () => { "#tree_bytes:sharding_enabled": 164, "#tree_height": 1, "#tree_height:sharding_enabled": 0, + "#tree_max_siblings": 1, + "#tree_max_siblings:sharding_enabled": 1, "#tree_size": 2, "#tree_size:sharding_enabled": 1, "sharding_enabled": true, @@ -603,7 +703,7 @@ describe("DB operations", () => { expect(node.db.getValue("/apps/test/nested/far/down/to/nowhere")).to.equal(null) }) - it("getValue to fail with value not present with is_shallow", () => { + it("getValue to fail with value not present with isShallow = true", () => { expect(node.db.getValue("/apps/test/nested/far/down/to/nowhere", true, false)).to.equal(null) }) }) @@ -957,13 +1057,34 @@ describe("DB operations", () => { }); }) - it("getFunction to retrieve existing function config with is_shallow", () => { + it("getFunction to retrieve existing function config with isShallow = true", () => { assert.deepEqual(node.db.getFunction('/apps/test/test_function', { isShallow: true }), { - some: { + "some": { "#state_ph": "0x637e4fb9edc3f569e3a4bced647d706bf33742bca14b1aae3ca01fd5b44120d5" }, }); }) + + it("getFunction to retrieve existing function config with isPartial = true and lastEndLabel", () => { + assert.deepEqual(node.db.getFunction('/apps/test/test_function', { isPartial: true }), { + "some": { + "#state_ph": "0x637e4fb9edc3f569e3a4bced647d706bf33742bca14b1aae3ca01fd5b44120d5", + "#serial": 2, + }, + "#end_label": "736f6d65" + }); + // lastEndLabel = "736f6d64" ("736f6d65" - 1) + assert.deepEqual(node.db.getFunction('/apps/test/test_function', { isPartial: true, lastEndLabel: "736f6d64" }), { + "some": { + "#state_ph": "0x637e4fb9edc3f569e3a4bced647d706bf33742bca14b1aae3ca01fd5b44120d5", + "#serial": 2, + }, + "#end_label": "736f6d65" + }); + assert.deepEqual(node.db.getFunction('/apps/test/test_function', { isPartial: true, lastEndLabel: "736f6d65" }), { + "#end_label": null + }); + }) }) describe("matchFunction:", () => { @@ -1224,14 +1345,28 @@ describe("DB operations", () => { }); }) - it('getRule to retrieve existing rule config with is_shallow', () => { - assert.deepEqual(node.db.getRule('/apps/test/test_rule', { isShallow: true }), { + it('getRule to retrieve existing rule config with isPartial = true and lastEndLabel', () => { + assert.deepEqual(node.db.getRule('/apps/test/test_rule', { isPartial: true }), { "some": { - "#state_ph": "0x2be40be7d05dfe5a88319f6aa0f1a7eb61691f8f5fae8c7c993f10892cd29038" + "#state_ph": "0x2be40be7d05dfe5a88319f6aa0f1a7eb61691f8f5fae8c7c993f10892cd29038", + "#serial": 2, }, "syntax": { - "#state_ph": "0x9bf58fc0d77ba1ec1271522dbaab398ebe0e8ea002bb43f6bd860b665e53b732" - } + "#state_ph": "0x9bf58fc0d77ba1ec1271522dbaab398ebe0e8ea002bb43f6bd860b665e53b732", + "#serial": 5, + }, + "#end_label": "73796e746178" + }); + // lastEndLabel = "73796e746177" ("73796e746178" - 1) + assert.deepEqual(node.db.getRule('/apps/test/test_rule', { isPartial: true, lastEndLabel: "73796e746177" }), { + "syntax": { + "#state_ph": "0x9bf58fc0d77ba1ec1271522dbaab398ebe0e8ea002bb43f6bd860b665e53b732", + "#serial": 5, + }, + "#end_label": "73796e746178" + }); + assert.deepEqual(node.db.getRule('/apps/test/test_rule', { isPartial: true, lastEndLabel: "73796e746178" }), { + "#end_label": null }); }); }) @@ -1764,13 +1899,34 @@ describe("DB operations", () => { }); }) - it("getOwner to retrieve existing owner config with is_shallow", () => { + it("getOwner to retrieve existing owner config with isShallow = true", () => { assert.deepEqual(node.db.getOwner("/apps/test/test_owner", { isShallow: true }), { - some: { + "some": { "#state_ph": "0x6127bafe410040319f8d36b1ec0491e16db32d2d0be00f8fc28015c564582b80" }, }) }) + + it("getOwner to retrieve existing owner config with isPartial = true and lastEndLabel", () => { + assert.deepEqual(node.db.getOwner("/apps/test/test_owner", { isPartial: true }), { + "some": { + "#state_ph": "0x6127bafe410040319f8d36b1ec0491e16db32d2d0be00f8fc28015c564582b80", + "#serial": 2, + }, + "#end_label": "736f6d65" + }) + // lastEndLabel = "736f6d64" ("736f6d65" - 1) + assert.deepEqual(node.db.getOwner("/apps/test/test_owner", { isPartial: true, lastEndLabel: "736f6d64" }), { + "some": { + "#state_ph": "0x6127bafe410040319f8d36b1ec0491e16db32d2d0be00f8fc28015c564582b80", + "#serial": 2, + }, + "#end_label": "736f6d65" + }) + assert.deepEqual(node.db.getOwner("/apps/test/test_owner", { isPartial: true, lastEndLabel: "736f6d65" }), { + "#end_label": null + }) + }) }) describe("matchOwner:", () => { @@ -5920,6 +6076,7 @@ describe("State info - getStateInfo", () => { "#state_ph": "0xe4fd1f81f45b74ccd16540efa905abde37b6660d3fe9fb18eb3bf6b3e7cd215a", "#tree_bytes": 922, "#tree_height": 2, + "#tree_max_siblings": 2, "#tree_size": 5, "#version": "NODE:0" }); @@ -5928,6 +6085,7 @@ describe("State info - getStateInfo", () => { "#state_ph": "0xa8681012b27ff56a45aa80f6f4d95c66c3349046cdd18cdc77028b6a634c9b0b", "#tree_bytes": 174, "#tree_height": 0, + "#tree_max_siblings": 1, "#tree_size": 1, "#version": "NODE:0", }); @@ -5936,6 +6094,7 @@ describe("State info - getStateInfo", () => { "#state_ph": "0x19037329315c0182c0f965a786e6d0659bb374e907a3937f885f0da3984cfa6e", "#tree_bytes": 560, "#tree_height": 1, + "#tree_max_siblings": 2, "#tree_size": 3, "#version": "NODE:0", }); @@ -5944,6 +6103,7 @@ describe("State info - getStateInfo", () => { "#state_ph": "0xfbe04067ec980e5d7364e8b6cf45f4bee9d53be89419211d0233aada9151ad50", "#tree_bytes": 184, "#tree_height": 0, + "#tree_max_siblings": 1, "#tree_size": 1, "#version": "NODE:0", }); @@ -5952,6 +6112,7 @@ describe("State info - getStateInfo", () => { "#state_ph": "0x8f17965ac862bad15172d21facff45ff3efb8a55ae50ca085131a3012e001c1f", "#tree_bytes": 184, "#tree_height": 0, + "#tree_max_siblings": 1, "#tree_size": 1, "#version": "NODE:0", }); @@ -5960,6 +6121,7 @@ describe("State info - getStateInfo", () => { "#state_ph": "0x0088bff9a36081510c230f5fd6b6581b81966b185414e625df7553693d6517e3", "#tree_bytes": 536, "#tree_height": 1, + "#tree_max_siblings": 2, "#tree_size": 3, "#version": "NODE:0", }); @@ -5968,6 +6130,7 @@ describe("State info - getStateInfo", () => { "#state_ph": "0xa8681012b27ff56a45aa80f6f4d95c66c3349046cdd18cdc77028b6a634c9b0b", "#tree_bytes": 174, "#tree_height": 0, + "#tree_max_siblings": 1, "#tree_size": 1, "#version": "NODE:0", }); @@ -5976,6 +6139,7 @@ describe("State info - getStateInfo", () => { "#state_ph": "0xc0da1458b190e12347891ab14253518f5e43d95473cd2546dbf8852dfb3dc281", "#tree_bytes": 174, "#tree_height": 0, + "#tree_max_siblings": 1, "#tree_size": 1, "#version": "NODE:0", }); @@ -5995,6 +6159,7 @@ describe("State info - getStateInfo", () => { "#state_ph": "0xe037f0083e30127f0e5088be69c2629a7e14e18518ee736fc31d86ec39b3c459", "#tree_bytes": 348, "#tree_height": 1, + "#tree_max_siblings": 1, "#tree_size": 2, "#version": "NODE:0" }); @@ -6003,6 +6168,7 @@ describe("State info - getStateInfo", () => { "#state_ph": "0xa8681012b27ff56a45aa80f6f4d95c66c3349046cdd18cdc77028b6a634c9b0b", "#tree_bytes": 174, "#tree_height": 0, + "#tree_max_siblings": 1, "#tree_size": 1, "#version": "NODE:0", }); @@ -6012,6 +6178,7 @@ describe("State info - getStateInfo", () => { "#state_ph": "0x0088bff9a36081510c230f5fd6b6581b81966b185414e625df7553693d6517e3", "#tree_bytes": 536, "#tree_height": 1, + "#tree_max_siblings": 2, "#tree_size": 3, "#version": "NODE:0", }); @@ -6031,6 +6198,7 @@ describe("State info - getStateInfo", () => { "#state_ph": "0xc751739c3275e0b4c143835fcc0342b80af43a74cf338a8571c17e727643bbe7", "#tree_bytes": 906, "#tree_height": 2, + "#tree_max_siblings": 2, "#tree_size": 5, "#version": "NODE:0" }); @@ -6039,6 +6207,7 @@ describe("State info - getStateInfo", () => { "#state_ph": "0xdd1c06ba6d6ff93fea2f2a1a3a026692858cd3528424b2f86197e1761539b0e4", "#tree_bytes": 906, "#tree_height": 2, + "#tree_max_siblings": 2, "#tree_size": 5, "#version": "NODE:0", }); @@ -6047,6 +6216,7 @@ describe("State info - getStateInfo", () => { "#state_ph": "0xdfe61d4a6c026b34261bc83f4c9d5d24deaed1671177fee24a889930588edd89", "#tree_bytes": 544, "#tree_height": 1, + "#tree_max_siblings": 2, "#tree_size": 3, "#version": "NODE:0", }); @@ -6055,6 +6225,7 @@ describe("State info - getStateInfo", () => { "#state_ph": "0xc7b107bdd716d26c8fe34fbcec5b91d738c3f53ee09fdf047678e85181e5f90c", "#tree_bytes": 176, "#tree_height": 0, + "#tree_max_siblings": 1, "#tree_size": 1, "#version": "NODE:0", }); @@ -6063,6 +6234,7 @@ describe("State info - getStateInfo", () => { "#state_ph": "0x736c5dded3f67ab5717c8c7c1b15580cb0bbf23562edd4a6898f2c1a6ca63200", "#tree_bytes": 176, "#tree_height": 0, + "#tree_max_siblings": 1, "#tree_size": 1, "#version": "NODE:0", }); @@ -6071,6 +6243,7 @@ describe("State info - getStateInfo", () => { "#state_ph": "0xc0da1458b190e12347891ab14253518f5e43d95473cd2546dbf8852dfb3dc281", "#tree_bytes": 174, "#tree_height": 0, + "#tree_max_siblings": 1, "#tree_size": 1, "#version": "NODE:0", }); @@ -6295,6 +6468,7 @@ describe("Util methods", () => { const appName = 'app_name_valid0'; assert.deepEqual(node.db.validateAppName(appName, 2, stateLabelLengthLimit), { "is_valid": true, + "result": true, "code": 0, }); }); @@ -6303,7 +6477,8 @@ describe("Util methods", () => { const appName = 'app/path'; assert.deepEqual(node.db.validateAppName(appName, 2, stateLabelLengthLimit), { "is_valid": false, - "code": 30701, + "result": false, + "code": 30601, "message": `Invalid app name for state label: ${appName}`, }); }); @@ -6315,7 +6490,8 @@ describe("Util methods", () => { } assert.deepEqual(node.db.validateAppName(appName, 2, stateLabelLengthLimit), { "is_valid": false, - "code": 30701, + "result": false, + "code": 30601, "message": `Invalid app name for state label: ${appName}`, }); }); @@ -6324,7 +6500,8 @@ describe("Util methods", () => { const appName = 'balance_total_sum'; assert.deepEqual(node.db.validateAppName(appName, 2, stateLabelLengthLimit), { "is_valid": false, - "code": 30702, + "result": false, + "code": 30602, "message": `Invalid app name for service name: ${appName}`, }); }); @@ -6333,21 +6510,24 @@ describe("Util methods", () => { const appName = 'appName'; assert.deepEqual(node.db.validateAppName(appName, 2, stateLabelLengthLimit), { "is_valid": false, - "code": 30702, + "result": false, + "code": 30602, "message": `Invalid app name for service name: ${appName}`, }); const appName2 = 'app-name'; assert.deepEqual(node.db.validateAppName(appName2, 2, stateLabelLengthLimit), { "is_valid": false, - "code": 30702, + "result": false, + "code": 30602, "message": `Invalid app name for service name: ${appName2}`, }); const appName3 = '0app'; assert.deepEqual(node.db.validateAppName(appName3, 2, stateLabelLengthLimit), { "is_valid": false, - "code": 30702, + "result": false, + "code": 30602, "message": `Invalid app name for service name: ${appName3}`, }); }); @@ -6355,7 +6535,8 @@ describe("Util methods", () => { it("app name in use", () => { assert.deepEqual(node.db.validateAppName(appNameInUse, 2, stateLabelLengthLimit), { "is_valid": false, - "code": 30703, + "result": false, + "code": 30603, "message": `App name already in use: ${appNameInUse}`, }); }); diff --git a/test/unit/network-util.test.js b/test/unit/network-util.test.js deleted file mode 100644 index df96cf4dd..000000000 --- a/test/unit/network-util.test.js +++ /dev/null @@ -1,30 +0,0 @@ -const { convertIpv6ToIpv4 } = require('../../common/network-util'); -const _ = require('lodash'); -const chai = require('chai'); -const expect = chai.expect; - -describe("network-util", () => { - describe("convertIpv6ToIpv4", () => { - it("when non-string input", () => { - expect(convertIpv6ToIpv4(null)).to.equal(''); - expect(convertIpv6ToIpv4(undefined)).to.equal(''); - expect(convertIpv6ToIpv4(true)).to.equal(''); - expect(convertIpv6ToIpv4(false)).to.equal(''); - expect(convertIpv6ToIpv4(0)).to.equal(''); - expect(convertIpv6ToIpv4([])).to.equal(''); - expect(convertIpv6ToIpv4({})).to.equal(''); - }) - - it("when string input not replaced", () => { - expect(convertIpv6ToIpv4('')).to.equal(''); - expect(convertIpv6ToIpv4('abc')).to.equal('abc'); - expect(convertIpv6ToIpv4('0')).to.equal('0'); - expect(convertIpv6ToIpv4('\u2000\u2E00')).to.equal('\u2000\u2E00'); - expect(convertIpv6ToIpv4('192.0.2.146')).to.equal('192.0.2.146'); - }) - - it("when string input replaced", () => { - expect(convertIpv6ToIpv4('::ffff:192.0.2.146')).to.equal('192.0.2.146'); - }) - }) -}) \ No newline at end of file diff --git a/test/unit/p2p-util.test.js b/test/unit/p2p-util.test.js index db1bb6782..4b4a2efe7 100644 --- a/test/unit/p2p-util.test.js +++ b/test/unit/p2p-util.test.js @@ -302,4 +302,133 @@ describe("P2P Util", () => { expect(util.verifySignedMessage(mockMessage, address)).to.equal(true); }); }); + + describe("toHostname", () => { + it("returns null if invalid url format is given", () => { + const stringValue = 'stringValue'; + const numberValue = 123456789; + const booleanValue = true; + const nullValue = null; + const undefinedValue = undefined; + const arrayValue = []; + const objectValue = {}; + const onlyIpAddress = '172.20.10.2'; + + expect(util.toHostname(stringValue)).to.be.null; + expect(util.toHostname(numberValue)).to.be.null; + expect(util.toHostname(booleanValue)).to.be.null; + expect(util.toHostname(nullValue)).to.be.null; + expect(util.toHostname(undefinedValue)).to.be.null; + expect(util.toHostname(arrayValue)).to.be.null; + expect(util.toHostname(objectValue)).to.be.null; + expect(util.toHostname(onlyIpAddress)).to.be.null; + }); + + it("returns hostname if valid url is specified", () => { + const validHttpValue = 'http://172.20.10.2:8080'; + const validWsValue = 'ws://172.20.10.2:8080'; + + expect(util.toHostname(validHttpValue)).to.equal('172.20.10.2'); + expect(util.toHostname(validWsValue)).to.equal('172.20.10.2'); + }); + }); + + describe("isValidIpAddress", () => { + it("returns false if invalid ipAddress is given", () => { + const stringValue = 'stringValue'; + const numberValue = 123456789; + const booleanValue = true; + const nullValue = null; + const undefinedValue = undefined; + const arrayValue = []; + const objectValue = {}; + const url1 = 'ainetwork.ai'; + const url2 = 'https://*.ainetwork.ai'; + const url3 = 'http://172.16.0.36:8080'; + const url4 = 'http://172.16.0.36'; + const url5 = 'http://172.16.0.36:8080/json-rpc'; + + expect(util.isValidIpAddress(stringValue)).to.be.false; + expect(util.isValidIpAddress(numberValue)).to.be.false; + expect(util.isValidIpAddress(booleanValue)).to.be.false; + expect(util.isValidIpAddress(nullValue)).to.be.false; + expect(util.isValidIpAddress(undefinedValue)).to.be.false; + expect(util.isValidIpAddress(arrayValue)).to.be.false; + expect(util.isValidIpAddress(objectValue)).to.be.false; + expect(util.isValidIpAddress(url1)).to.be.false; + expect(util.isValidIpAddress(url2)).to.be.false; + expect(util.isValidIpAddress(url3)).to.be.false; + expect(util.isValidIpAddress(url4)).to.be.false; + expect(util.isValidIpAddress(url5)).to.be.false; + }); + + it("returns true if valid ip address is set", () => { + const ipV4 = '172.20.10.2'; + const ipV6 = '::ffff:172.20.10.2'; + + expect(util.isValidIpAddress(ipV4)).to.be.true; + expect(util.isValidIpAddress(ipV6)).to.be.true; + }); + }); + + describe("checkIpAddressFromPeerInfo", () => { + it("returns false if the give ip addresses are not the same", () => { + const ip1 = '172.20.10.1'; + const ip2 = '172.20.10.2'; + + expect(util.checkIpAddressFromPeerInfo(ip1, ip2)).to.be.false; + }); + + it("returns true if the given ips are the same", () => { + const ip1 = '172.20.10.1'; + const ip2 = '172.20.10.1'; + + expect(util.checkIpAddressFromPeerInfo(ip1, ip2)).to.be.true; + }); + + it("works also with mix matches", () => { + const ipV4 = '172.20.10.2'; + const ipV6 = '::ffff:172.20.10.2'; + + expect(util.checkIpAddressFromPeerInfo(ipV4, ipV6)).to.be.true; + }); + }); + + describe("isValidJsonRpcUrl", () => { + it("returns false if invalid url is given", () => { + const stringValue = 'stringValue'; + const numberValue = 123456789; + const booleanValue = true; + const nullValue = null; + const undefinedValue = undefined; + const arrayValue = []; + const objectValue = {}; + const url1 = 'ainetwork.ai'; + const url2 = 'https://*.ainetwork.ai'; + const url3 = 'http://172.16.0.36:8080'; + const url4 = 'http://172.16.0.36'; + + expect(util.isValidJsonRpcUrl(stringValue)).to.be.false; + expect(util.isValidJsonRpcUrl(numberValue)).to.be.false; + expect(util.isValidJsonRpcUrl(booleanValue)).to.be.false; + expect(util.isValidJsonRpcUrl(nullValue)).to.be.false; + expect(util.isValidJsonRpcUrl(undefinedValue)).to.be.false; + expect(util.isValidJsonRpcUrl(arrayValue)).to.be.false; + expect(util.isValidJsonRpcUrl(objectValue)).to.be.false; + expect(util.isValidJsonRpcUrl(url1)).to.be.false; + expect(util.isValidJsonRpcUrl(url2)).to.be.false; + expect(util.isValidJsonRpcUrl(url3)).to.be.false; + expect(util.isValidJsonRpcUrl(url4)).to.be.false; + }); + + it("returns true if valid", () => { + const url1 = 'https://api.ainetwork.ai/json-rpc'; + const url2 = 'http://172.16.0.36/json-rpc'; + const url3 = 'https://172.16.0.36:8080/json-rpc'; + + expect(util.isValidJsonRpcUrl(url1)).to.be.true; + expect(util.isValidJsonRpcUrl(url2)).to.be.true; + expect(util.isValidJsonRpcUrl(url3)).to.be.true; + }); + }); }); diff --git a/test/unit/p2p.test.js b/test/unit/p2p.test.js index 34b46179b..2bd37aad5 100644 --- a/test/unit/p2p.test.js +++ b/test/unit/p2p.test.js @@ -208,6 +208,7 @@ describe("P2p", () => { "#state_ph": 'erased', "#tree_bytes": 'erased', "#tree_height": 11, + "#tree_max_siblings": 26, "#tree_size": 'erased', "#version": "NODE:0", }, diff --git a/test/unit/radix-node.test.js b/test/unit/radix-node.test.js index 945858c86..7cd8ccc98 100644 --- a/test/unit/radix-node.test.js +++ b/test/unit/radix-node.test.js @@ -26,6 +26,7 @@ describe("radix-node", () => { expect(node.treeHeight).to.equal(0); expect(node.treeSize).to.equal(0); expect(node.treeBytes).to.equal(0); + expect(node.treeMaxSiblings).to.equal(0); }); it("construct with version", () => { @@ -64,6 +65,7 @@ describe("radix-node", () => { const treeHeight = 1; const treeSize = 10; const treeBytes = 100; + const treeMaxSiblings = 5; node.setVersion(version); node.setParentStateNode(parentStateNode); @@ -76,6 +78,7 @@ describe("radix-node", () => { node.setTreeHeight(treeHeight); node.setTreeSize(treeSize); node.setTreeBytes(treeBytes); + node.setTreeMaxSiblings(treeMaxSiblings); node.reset(); expect(node.version).to.equal(null); @@ -89,6 +92,7 @@ describe("radix-node", () => { expect(node.treeHeight).to.equal(0); expect(node.treeSize).to.equal(0); expect(node.treeBytes).to.equal(0); + expect(node.treeMaxSiblings).to.equal(0); }); }); @@ -105,6 +109,7 @@ describe("radix-node", () => { const treeHeight = 1; const treeSize = 10; const treeBytes = 100; + const treeMaxSiblings = 5; let parentStateNode; let childStateNode; @@ -132,6 +137,7 @@ describe("radix-node", () => { node.setTreeHeight(treeHeight); node.setTreeSize(treeSize); node.setTreeBytes(treeBytes); + node.setTreeMaxSiblings(treeMaxSiblings); }); it("clone without version", () => { @@ -150,6 +156,7 @@ describe("radix-node", () => { expect(cloned.getTreeHeight()).to.equal(treeHeight); expect(cloned.getTreeSize()).to.equal(treeSize); expect(cloned.getTreeBytes()).to.equal(treeBytes); + expect(cloned.getTreeMaxSiblings()).to.equal(treeMaxSiblings); }); it("clone with version", () => { @@ -633,6 +640,16 @@ describe("radix-node", () => { expect(node.getTreeBytes()).to.equal(0); }); + it("get / set tree max siblings", () => { + const treeMaxSiblings = 10; + + expect(node.getTreeMaxSiblings()).to.equal(0); + node.setTreeMaxSiblings(treeMaxSiblings); + expect(node.getTreeMaxSiblings()).to.equal(treeMaxSiblings); + node.resetTreeMaxSiblings(); + expect(node.getTreeMaxSiblings()).to.equal(0); + }); + it("buildRadixInfo", () => { const hashDelimiter = StateLabelProperties.HASH_DELIMITER; @@ -651,6 +668,10 @@ describe("radix-node", () => { const childTB1 = 1000; const childTB2 = 2100; + const stateNodeTMS = 52; + const childTMS1 = 50; + const childTMS2 = 51; + child1.setProofHash(childPH1); child2.setProofHash(childPH2); @@ -666,6 +687,10 @@ describe("radix-node", () => { child1.setTreeBytes(childTB1); child2.setTreeBytes(childTB2); + stateNode.setTreeMaxSiblings(stateNodeTMS); + child1.setTreeMaxSiblings(childTMS1); + child2.setTreeMaxSiblings(childTMS2); + stateNode.setLabel('stateLabel'); stateNode1.setLabel('stateLabel1'); @@ -677,6 +702,7 @@ describe("radix-node", () => { "#radix_ph": "childPH1", "#tree_bytes": 1000, "#tree_height": 10, + "#tree_max_siblings": 50, "#tree_size": 100, "#state:stateLabel1": { "#state_ph": "ph1_state", @@ -686,11 +712,13 @@ describe("radix-node", () => { "#radix_ph": "childPH2", "#tree_bytes": 2100, "#tree_height": 11, + "#tree_max_siblings": 51, "#tree_size": 110, }, "#radix_ph": null, "#tree_bytes": 0, "#tree_height": 0, + "#tree_max_siblings": 0, "#tree_size": 0, "#state:stateLabel": { "#state_ph": "ph_state", @@ -710,6 +738,7 @@ describe("radix-node", () => { expect(treeInfo.treeSize).to.equal(stateNodeTS + childTS1 + childTS2); // 10('stateLabel') * 2 = 20 expect(treeInfo.treeBytes).to.equal(stateNodeTB + childTB1 + childTB2 + 20); + expect(treeInfo.treeMaxSiblings).to.equal(Math.max(stateNodeTMS, childTMS1, childTMS2)); // Without stateNode node.resetChildStateNode(); @@ -719,6 +748,11 @@ describe("radix-node", () => { `${hashDelimiter}${labelRadix2}${labelSuffix2}${hashDelimiter}${childPH2}`; const proofHash2 = CommonUtil.hashString(preimage2); expect(treeInfo2.proofHash).to.equal(proofHash2) + + expect(treeInfo2.treeHeight).to.equal(Math.max(childTH1, childTH2)); + expect(treeInfo2.treeSize).to.equal(childTS1 + childTS2); + expect(treeInfo2.treeBytes).to.equal(childTB1 + childTB2); + expect(treeInfo2.treeMaxSiblings).to.equal(Math.max(childTMS1, childTMS2)); }); it("updateRadixInfo / verifyRadixInfo", () => { @@ -737,6 +771,10 @@ describe("radix-node", () => { const childTB1 = 1000; const childTB2 = 2100; + const stateNodeTMS = 52; + const childTMS1 = 50; + const childTMS2 = 51; + child1.setProofHash(childPH1); child2.setProofHash(childPH2); @@ -752,6 +790,10 @@ describe("radix-node", () => { child1.setTreeBytes(childTB1); child2.setTreeBytes(childTB2); + stateNode.setTreeMaxSiblings(stateNodeTMS); + child1.setTreeMaxSiblings(childTMS1); + child2.setTreeMaxSiblings(childTMS2); + stateNode.setLabel('stateLabel'); stateNode1.setLabel('stateLabel1'); @@ -1183,6 +1225,7 @@ describe("radix-node", () => { stateNode.setTreeHeight(1); stateNode.setTreeSize(10); stateNode.setTreeBytes(100); + stateNode.setTreeMaxSiblings(5); childStateNode1 = new StateNode(); childStateNode1.setVersion('ver1_state'); @@ -1191,6 +1234,7 @@ describe("radix-node", () => { childStateNode1.setTreeHeight(2); childStateNode1.setTreeSize(20); childStateNode1.setTreeBytes(200); + childStateNode1.setTreeMaxSiblings(10); childStateNode2 = new StateNode(); childStateNode2.setVersion('ver2_state'); @@ -1199,6 +1243,7 @@ describe("radix-node", () => { childStateNode2.setTreeHeight(3); childStateNode2.setTreeSize(30); childStateNode2.setTreeBytes(300); + childStateNode2.setTreeMaxSiblings(15); childStateNode21 = new StateNode(); childStateNode21.setVersion('ver21_state'); @@ -1207,6 +1252,7 @@ describe("radix-node", () => { childStateNode21.setTreeHeight(4); childStateNode21.setTreeSize(40); childStateNode21.setTreeBytes(400); + childStateNode21.setTreeMaxSiblings(20); childStateNode22 = new StateNode(); childStateNode22.setVersion('ver22_state'); @@ -1215,6 +1261,7 @@ describe("radix-node", () => { childStateNode22.setTreeHeight(5); childStateNode22.setTreeSize(50); childStateNode22.setTreeBytes(500); + childStateNode22.setTreeMaxSiblings(25); parent1 = new RadixNode(version1, null, parentStateNode1 ); parent2 = new RadixNode(version21); @@ -1276,11 +1323,383 @@ describe("radix-node", () => { }); }); - it("getChildStateNodeList", () => { - const stateNodes = node.getChildStateNodeList(); - expect(stateNodes.length).to.equal(5) + it("compareRadixLabelWithPrefix", () => { + expect(RadixNode.compareRadixLabelWithPrefix('', '')).to.equal(0); + expect(RadixNode.compareRadixLabelWithPrefix('', 'aabbcc')).to.equal(0); + expect(RadixNode.compareRadixLabelWithPrefix('a', 'aabbcc')).to.equal(0); + expect(RadixNode.compareRadixLabelWithPrefix('aa', 'aabbcc')).to.equal(0); + expect(RadixNode.compareRadixLabelWithPrefix('aab', 'aabbcc')).to.equal(0); + expect(RadixNode.compareRadixLabelWithPrefix('aab9', 'aabbcc')).to.equal(-1); + expect(RadixNode.compareRadixLabelWithPrefix('aab9c', 'aabbcc')).to.equal(-1); + expect(RadixNode.compareRadixLabelWithPrefix('aabc', 'aabbcc')).to.equal(1); + expect(RadixNode.compareRadixLabelWithPrefix('aabcc', 'aabbcc')).to.equal(1); + expect(RadixNode.compareRadixLabelWithPrefix('ab', 'aabbcc')).to.equal(1); + expect(RadixNode.compareRadixLabelWithPrefix('aabbcc', 'aabbcc')).to.equal(0); + }); + + it("getChildStateNodeListWithEndLabel", () => { + const nodeListWithEndLabel = node.getChildStateNodeListWithEndLabel(); + expect(nodeListWithEndLabel.endLabel).to.equal('000020022022') + expect(nodeListWithEndLabel.list.length).to.equal(5) + assert.deepEqual( + nodeListWithEndLabel.list, [ + { + serial: 0, + stateNode: stateNode, + }, + { + serial: 3, + stateNode: childStateNode1, + }, + { + serial: 4, + stateNode: childStateNode2, + }, + { + serial: 2, + stateNode: childStateNode21, + }, + { + serial: 1, + stateNode: childStateNode22, + }, + ]); + }); + + it("getChildStateNodeListWithLabel with non-null maxListSize", () => { + // maxListSize = -1 + const nodeListWithEndLabel1 = node.getChildStateNodeListWithEndLabel(-1); + expect(nodeListWithEndLabel1.endLabel).to.equal(null) + expect(nodeListWithEndLabel1.list.length).to.equal(0) + assert.deepEqual(nodeListWithEndLabel1.list, []); + + // maxListSize = 0 + const nodeListWithEndLabel2 = node.getChildStateNodeListWithEndLabel(0); + expect(nodeListWithEndLabel2.endLabel).to.equal(null) + expect(nodeListWithEndLabel2.list.length).to.equal(0) + assert.deepEqual(nodeListWithEndLabel2.list, []); + + // maxListSize = 1 + const nodeListWithEndLabel3 = node.getChildStateNodeListWithEndLabel(1); + expect(nodeListWithEndLabel3.endLabel).to.equal('0000') + expect(nodeListWithEndLabel3.list.length).to.equal(1) + assert.deepEqual( + nodeListWithEndLabel3.list, [ + { + serial: 0, + stateNode: stateNode, + }, + // skip the other nodes + ]); + + // maxListSize = 2 + const nodeListWithEndLabel4 = node.getChildStateNodeListWithEndLabel(2); + expect(nodeListWithEndLabel4.endLabel).to.equal('00001001') + expect(nodeListWithEndLabel4.list.length).to.equal(2) + assert.deepEqual( + nodeListWithEndLabel4.list, [ + { + serial: 0, + stateNode: stateNode, + }, + { + serial: 3, + stateNode: childStateNode1, + }, + // skip the other nodes + ]); + + // maxListSize = 3 + const nodeListWithEndLabel5 = node.getChildStateNodeListWithEndLabel(3); + expect(nodeListWithEndLabel5.endLabel).to.equal('00002002') + expect(nodeListWithEndLabel5.list.length).to.equal(3) + assert.deepEqual( + nodeListWithEndLabel5.list, [ + { + serial: 0, + stateNode: stateNode, + }, + { + serial: 3, + stateNode: childStateNode1, + }, + { + serial: 4, + stateNode: childStateNode2, + }, + // skip the other nodes + ]); + + // maxListSize = 4 + const nodeListWithEndLabel6 = node.getChildStateNodeListWithEndLabel(4); + expect(nodeListWithEndLabel6.endLabel).to.equal('000020021021') + expect(nodeListWithEndLabel6.list.length).to.equal(4) + assert.deepEqual( + nodeListWithEndLabel6.list, [ + { + serial: 0, + stateNode: stateNode, + }, + { + serial: 3, + stateNode: childStateNode1, + }, + { + serial: 4, + stateNode: childStateNode2, + }, + { + serial: 2, + stateNode: childStateNode21, + }, + // skip the other nodes + ]); + + // maxListSize = 5 + const nodeListWithEndLabel7 = node.getChildStateNodeListWithEndLabel(5); + expect(nodeListWithEndLabel7.endLabel).to.equal('000020022022') + expect(nodeListWithEndLabel7.list.length).to.equal(5) + assert.deepEqual( + nodeListWithEndLabel7.list, [ + { + serial: 0, + stateNode: stateNode, + }, + { + serial: 3, + stateNode: childStateNode1, + }, + { + serial: 4, + stateNode: childStateNode2, + }, + { + serial: 2, + stateNode: childStateNode21, + }, + { + serial: 1, + stateNode: childStateNode22, + }, + ]); + + // maxListSize = 6 + const nodeListWithEndLabel8 = node.getChildStateNodeListWithEndLabel(6); + expect(nodeListWithEndLabel8.endLabel).to.equal('000020022022') + expect(nodeListWithEndLabel8.list.length).to.equal(5) + assert.deepEqual( + nodeListWithEndLabel8.list, [ + { + serial: 0, + stateNode: stateNode, + }, + { + serial: 3, + stateNode: childStateNode1, + }, + { + serial: 4, + stateNode: childStateNode2, + }, + { + serial: 2, + stateNode: childStateNode21, + }, + { + serial: 1, + stateNode: childStateNode22, + }, + ]); + }); + + it("getChildStateNodeListWithLabel with non-null maxListSize and lastEndLabel", () => { + // maxListSize = -1 + // lastEndLabel = '00001001' (childStateNode1) + const nodeListWithEndLabel1 = node.getChildStateNodeListWithEndLabel(-1, '00001001'); + expect(nodeListWithEndLabel1.endLabel).to.equal(null) + expect(nodeListWithEndLabel1.list.length).to.equal(0) + assert.deepEqual(nodeListWithEndLabel1.list, []); + + // maxListSize = 0 + // lastEndLabel = '00001001' (childStateNode1) + const nodeListWithEndLabel2 = node.getChildStateNodeListWithEndLabel(0, '00001001'); + expect(nodeListWithEndLabel2.endLabel).to.equal(null) + expect(nodeListWithEndLabel2.list.length).to.equal(0) + assert.deepEqual(nodeListWithEndLabel2.list, []); + + // maxListSize = 1 + // lastEndLabel = '00001001' (childStateNode1) + const nodeListWithEndLabel3 = node.getChildStateNodeListWithEndLabel(1, '00001001'); + expect(nodeListWithEndLabel3.endLabel).to.equal('00002002') + expect(nodeListWithEndLabel3.list.length).to.equal(1) + assert.deepEqual( + nodeListWithEndLabel3.list, [ + // skip previous nodes + { + serial: 4, + stateNode: childStateNode2, + }, + // skip the other nodes + ]); + + // maxListSize = 2 + // lastEndLabel = '00001001' (childStateNode1) + const nodeListWithEndLabel4 = node.getChildStateNodeListWithEndLabel(2, '00001001'); + expect(nodeListWithEndLabel4.endLabel).to.equal('000020021021') + expect(nodeListWithEndLabel4.list.length).to.equal(2) + assert.deepEqual( + nodeListWithEndLabel4.list, [ + // skip previous nodes + { + serial: 4, + stateNode: childStateNode2, + }, + { + serial: 2, + stateNode: childStateNode21, + }, + // skip the other nodes + ]); + + // maxListSize = 5 + // lastEndLabel = '00001001' (childStateNode1) + const nodeListWithEndLabel5 = node.getChildStateNodeListWithEndLabel(5, '00001001'); + expect(nodeListWithEndLabel5.endLabel).to.equal('000020022022') + expect(nodeListWithEndLabel5.list.length).to.equal(3) + assert.deepEqual( + nodeListWithEndLabel5.list, [ + // skip previous nodes + { + serial: 4, + stateNode: childStateNode2, + }, + { + serial: 2, + stateNode: childStateNode21, + }, + { + serial: 1, + stateNode: childStateNode22, + }, + ]); + + // maxListSize = 6 + // lastEndLabel = '00001001' (childStateNode1) + const nodeListWithEndLabel6 = node.getChildStateNodeListWithEndLabel(6, '00001001'); + expect(nodeListWithEndLabel6.endLabel).to.equal('000020022022') + expect(nodeListWithEndLabel6.list.length).to.equal(3) + assert.deepEqual( + nodeListWithEndLabel6.list, [ + // skip previous nodes + { + serial: 4, + stateNode: childStateNode2, + }, + { + serial: 2, + stateNode: childStateNode21, + }, + { + serial: 1, + stateNode: childStateNode22, + }, + ]); + }); + + it("getChildStateNodeListWithLabel with non-existing lastEndLabel", () => { + // maxListSize = 2 + // lastEndLabel = '000020020' (= '00002002' (childStateNode2) + '0') + const nodeListWithEndLabel1 = node.getChildStateNodeListWithEndLabel(2, '000020020'); + expect(nodeListWithEndLabel1.endLabel).to.equal('000020022022') + expect(nodeListWithEndLabel1.list.length).to.equal(2) + assert.deepEqual( + nodeListWithEndLabel1.list, [ + // skip previous nodes + { + serial: 2, + stateNode: childStateNode21, + }, + { + serial: 1, + stateNode: childStateNode22, + }, + // skip the other nodes + ]); + + // maxListSize = 2 + // lastEndLabel = '0000200200' (= '00002002' (childStateNode2) + '00') + const nodeListWithEndLabel2 = node.getChildStateNodeListWithEndLabel(2, '0000200200'); + expect(nodeListWithEndLabel2.endLabel).to.equal('000020022022') + expect(nodeListWithEndLabel2.list.length).to.equal(2) assert.deepEqual( - stateNodes, [ + nodeListWithEndLabel2.list, [ + // skip previous nodes + { + serial: 2, + stateNode: childStateNode21, + }, + { + serial: 1, + stateNode: childStateNode22, + }, + // skip the other nodes + ]); + + // maxListSize = 2 + // lastEndLabel = '000020' (= '00002002' (childStateNode2) - '02') + const nodeListWithEndLabel3 = node.getChildStateNodeListWithEndLabel(2, '000020'); + expect(nodeListWithEndLabel3.endLabel).to.equal('000020021021') + expect(nodeListWithEndLabel3.list.length).to.equal(2) + assert.deepEqual( + nodeListWithEndLabel3.list, [ + // skip previous nodes + { + serial: 4, + stateNode: childStateNode2, + }, + { + serial: 2, + stateNode: childStateNode21, + }, + // skip the other nodes + ]); + + // maxListSize = 2 + // lastEndLabel = '00002' (= '00002002' (childStateNode2) - '002') + const nodeListWithEndLabel4 = node.getChildStateNodeListWithEndLabel(2, '00002'); + expect(nodeListWithEndLabel4.endLabel).to.equal('000020021021') + expect(nodeListWithEndLabel4.list.length).to.equal(2) + assert.deepEqual( + nodeListWithEndLabel4.list, [ + // skip previous nodes + { + serial: 4, + stateNode: childStateNode2, + }, + { + serial: 2, + stateNode: childStateNode21, + }, + // skip the other nodes + ]); + + // maxListSize = 2 + // lastEndLabel = '000021' (= '00002002' (childStateNode2) - '002' + '1') + const nodeListWithEndLabel5 = node.getChildStateNodeListWithEndLabel(2, '000021'); + expect(nodeListWithEndLabel5.endLabel).to.equal(null) + expect(nodeListWithEndLabel5.list.length).to.equal(0) + assert.deepEqual( + nodeListWithEndLabel5.list, []); + }); + + it("getChildStateNodeListWithLabel chaining with endLabel and lastEndLabel", () => { + // maxListSize = 2 + // lastEndLabel = null + const nodeListWithEndLabel1 = node.getChildStateNodeListWithEndLabel(2, null); + expect(nodeListWithEndLabel1.endLabel).to.equal('00001001') + expect(nodeListWithEndLabel1.list.length).to.equal(2) + assert.deepEqual( + nodeListWithEndLabel1.list, [ { serial: 0, stateNode: stateNode, @@ -1289,6 +1708,15 @@ describe("radix-node", () => { serial: 3, stateNode: childStateNode1, }, + ]); + + // maxListSize = 2 + // lastEndLabel = '00001001' + const nodeListWithEndLabel2 = node.getChildStateNodeListWithEndLabel(2, '00001001'); + expect(nodeListWithEndLabel2.endLabel).to.equal('000020021021') + expect(nodeListWithEndLabel2.list.length).to.equal(2) + assert.deepEqual( + nodeListWithEndLabel2.list, [ { serial: 4, stateNode: childStateNode2, @@ -1297,11 +1725,27 @@ describe("radix-node", () => { serial: 2, stateNode: childStateNode21, }, + ]); + + // maxListSize = 2 + // lastEndLabel = '000020021021' + const nodeListWithEndLabel3 = node.getChildStateNodeListWithEndLabel(2, '000020021021'); + expect(nodeListWithEndLabel3.endLabel).to.equal('000020022022') + expect(nodeListWithEndLabel3.list.length).to.equal(1) + assert.deepEqual( + nodeListWithEndLabel3.list, [ { serial: 1, stateNode: childStateNode22, }, ]); + + // maxListSize = 2 + // lastEndLabel = '000020022022' + const nodeListWithEndLabel4 = node.getChildStateNodeListWithEndLabel(2, '000020022022'); + expect(nodeListWithEndLabel4.endLabel).to.equal(null) + expect(nodeListWithEndLabel4.list.length).to.equal(0) + assert.deepEqual(nodeListWithEndLabel4.list, []); }); it("deleteRadixTreeVersion", () => { @@ -1636,6 +2080,7 @@ describe("radix-node", () => { }, "#tree_bytes": 208, "#tree_height": 2, + "#tree_max_siblings": 10, "#tree_size": 20, "#version": "ver1", }, @@ -1650,6 +2095,7 @@ describe("radix-node", () => { }, "#tree_bytes": 408, "#tree_height": 4, + "#tree_max_siblings": 20, "#tree_size": 40, "#version": "ver21", }, @@ -1663,6 +2109,7 @@ describe("radix-node", () => { }, "#tree_bytes": 508, "#tree_height": 5, + "#tree_max_siblings": 25, "#tree_size": 50, "#version": "ver22", }, @@ -1675,6 +2122,7 @@ describe("radix-node", () => { }, "#tree_bytes": 1224, "#tree_height": 5, + "#tree_max_siblings": 25, "#tree_size": 120, "#version": "ver2", }, @@ -1687,6 +2135,7 @@ describe("radix-node", () => { }, "#tree_bytes": 1540, "#tree_height": 5, + "#tree_max_siblings": 25, "#tree_size": 150, "#version": "ver", }); diff --git a/test/unit/radix-tree.test.js b/test/unit/radix-tree.test.js index 5292a962c..d088e850e 100644 --- a/test/unit/radix-tree.test.js +++ b/test/unit/radix-tree.test.js @@ -4,6 +4,7 @@ const expect = chai.expect; const assert = chai.assert; const RadixNode = require('../../db/radix-node'); const StateNode = require('../../db/state-node'); +const { NodeConfigs } = require('../../common/constants'); describe("radix-tree", () => { @@ -2837,18 +2838,26 @@ describe("radix-tree", () => { cloned = tree.clone(version2); }); - it("getChildStateLabels / getChildStateNodes", () => { + it("getChildStateLabelsWithEndLabel / getChildStateNodesWithEndLabel", () => { // Insertion order is kept - assert.deepEqual(tree.getChildStateLabels(), [ label22, label21, label1, label2 ]); + const labelsWithEndLabel = tree.getChildStateLabelsWithEndLabel(); + assert.deepEqual(labelsWithEndLabel.list, [ label22, label21, label1, label2 ]); + expect(labelsWithEndLabel.endLabel).to.equal('000bbb222'); + const nodesWithEndLabel = tree.getChildStateNodesWithEndLabel(); assert.deepEqual( - tree.getChildStateNodes(), [ stateNode22, stateNode21, stateNode1, stateNode2 ]); + nodesWithEndLabel.list, [ stateNode22, stateNode21, stateNode1, stateNode2 ]); + expect(nodesWithEndLabel.endLabel).to.equal('000bbb222'); }); - it("getChildStateLabels / getChildStateNodes with cloned tree", () => { + it("getChildStateLabelsWithEndLabel / getChildStateNodesWithEndLabel with cloned tree", () => { // Insertion order is kept. - assert.deepEqual(cloned.getChildStateLabels(), [ label22, label21, label1, label2 ]); + const labelsWithEndLabelBefore = cloned.getChildStateLabelsWithEndLabel(); + assert.deepEqual(labelsWithEndLabelBefore.list, [ label22, label21, label1, label2 ]); + expect(labelsWithEndLabelBefore.endLabel).to.equal('000bbb222'); + const nodesWithEndLabelBefore = cloned.getChildStateNodesWithEndLabel(); assert.deepEqual( - cloned.getChildStateNodes(), [ stateNode22, stateNode21, stateNode1, stateNode2 ]); + nodesWithEndLabelBefore.list, [ stateNode22, stateNode21, stateNode1, stateNode2 ]); + expect(nodesWithEndLabelBefore.endLabel).to.equal('000bbb222'); const newStateNode21 = new StateNode(version2); newStateNode21.setLabel(label21); @@ -2859,11 +2868,66 @@ describe("radix-tree", () => { cloned.set(label23, newStateNode23); // The order does NOT change. + const labelsWithEndLabelAfter = cloned.getChildStateLabelsWithEndLabel(); assert.deepEqual( - cloned.getChildStateLabels(), [ label22, label21, label1, label2, label23 ]); + labelsWithEndLabelAfter.list, [ label22, label21, label1, label2, label23 ]); + expect(labelsWithEndLabelAfter.endLabel).to.equal('000bbb333'); + const nodesWithEndLabelAfter = cloned.getChildStateNodesWithEndLabel(); assert.deepEqual( - cloned.getChildStateNodes(), + nodesWithEndLabelAfter.list, [ stateNode22, newStateNode21, stateNode1, stateNode2, newStateNode23 ]); + expect(nodesWithEndLabelAfter.endLabel).to.equal('000bbb333'); + }); + + it("getChildStateLabelsWithEndLabel / getChildStateNodesWithEndLabel with isPartial = true", () => { + // Change GET_RESP_MAX_SIBLINGS value for testing. + const originalGetRespMaxSiblings = NodeConfigs.GET_RESP_MAX_SIBLINGS; + NodeConfigs.GET_RESP_MAX_SIBLINGS = 3; + + // Insertion order is kept + const labelsWithEndLabel = tree.getChildStateLabelsWithEndLabel(true); + assert.deepEqual(labelsWithEndLabel.list, [ + // skip label22 + label21, + label1, + label2 + ]); + expect(labelsWithEndLabel.endLabel).to.equal('000bbb111'); + const nodesWithEndLabel = tree.getChildStateNodesWithEndLabel(true); + assert.deepEqual(nodesWithEndLabel.list, [ + // skip stateNode22 + stateNode21, + stateNode1, + stateNode2 + ]); + expect(nodesWithEndLabel.endLabel).to.equal('000bbb111'); + + // Restore GET_RESP_MAX_SIBLINGS value. + NodeConfigs.GET_RESP_MAX_SIBLINGS = originalGetRespMaxSiblings; + }); + + it("getChildStateLabelsWithEndLabel / getChildStateNodesWithEndLabel with isPartial = true and lastEndLabel", () => { + // Change GET_RESP_MAX_SIBLINGS value for testing. + const originalGetRespMaxSiblings = NodeConfigs.GET_RESP_MAX_SIBLINGS; + NodeConfigs.GET_RESP_MAX_SIBLINGS = 2; + + // lastEndLabel = '000bbb' (stateNode2) + + const labelsWithEndLabel = tree.getChildStateLabelsWithEndLabel(true, '000bbb'); + assert.deepEqual(labelsWithEndLabel.list, [ + label21, + label22, + ]); + expect(labelsWithEndLabel.endLabel).to.equal('000bbb222'); + const nodesWithEndLabel = tree.getChildStateNodesWithEndLabel(true, '000bbb'); + assert.deepEqual(nodesWithEndLabel.list, [ + stateNode21, + stateNode22, + ]); + expect(nodesWithEndLabel.endLabel).to.equal('000bbb222'); + + // Restore GET_RESP_MAX_SIBLINGS value. + NodeConfigs.GET_RESP_MAX_SIBLINGS = originalGetRespMaxSiblings; }); }); @@ -3396,7 +3460,7 @@ describe("radix-tree", () => { "#version": "ver", }); assert.deepEqual(treeRebuilt.toRadixSnapshot(), snapshot); - assert.deepEqual(treeRebuilt.getChildStateLabels(), [ + assert.deepEqual(treeRebuilt.getChildStateLabelsWithEndLabel().list, [ "0x000aaa", "0x000bbb", "0x000bbb111", diff --git a/test/unit/rule-util.test.js b/test/unit/rule-util.test.js index 2fcb6ce53..33bfbe18b 100644 --- a/test/unit/rule-util.test.js +++ b/test/unit/rule-util.test.js @@ -344,6 +344,80 @@ describe("RuleUtil", () => { }) }) + describe("isValidIpV4", () => { + it("when invalid input", () => { + util.isValid + expect(util.isValidIpV4(true)).to.equal(false); + expect(util.isValidIpV4(false)).to.equal(false); + expect(util.isValidIpV4(0)).to.equal(false); + expect(util.isValidIpV4(10)).to.equal(false); + expect(util.isValidIpV4(null)).to.equal(false); + expect(util.isValidIpV4(undefined)).to.equal(false); + expect(util.isValidIpV4(Infinity)).to.equal(false); + expect(util.isValidIpV4(NaN)).to.equal(false); + expect(util.isValidIpV4('')).to.equal(false); + expect(util.isValidIpV4('abc')).to.equal(false); + expect(util.isValidIpV4('0')).to.equal(false); + expect(util.isValidIpV4([10])).to.equal(false); + expect(util.isValidIpV4({a: 'A'})).to.equal(false); + expect(util.isValidIpV4('0x')).to.equal(false); + expect(util.isValidIpV4('0x6af1ec8d4f0a55bac328cb20336ed0eff46fa6334ebd112147892f1b15aafc8')).to.equal(false); + expect(util.isValidIpV4('ainetwork.ai')).to.equal(false); + expect(util.isValidIpV4('https://*.ainetwork.ai')).to.equal(false); + expect(util.isValidIpV4('http://172.16.0.36:8080/json-rpc')).to.equal(false); + expect(util.isValidIpV4('http://172.16.0.36')).to.equal(false); + expect(util.isValidIpV4('https://172.16.0.36')).to.equal(false); + expect(util.isValidIpV4('http://172.16.0.36:8080')).to.equal(false); + expect(util.isValidIpV4('https://172.16.0.36:9000')).to.equal(false); + expect(util.isValidIpV4('::ffff:172.20.10.2')).to.equal(false); + expect(util.isValidIpV4('172.')).to.equal(false); + expect(util.isValidIpV4('172.16.')).to.equal(false); + expect(util.isValidIpV4('172.16.0.')).to.equal(false); + }) + + it("when valid input", () => { + expect(util.isValidIpV4('0.0.0.0')).to.equal(true); + expect(util.isValidIpV4('172.16.0.36')).to.equal(true); + expect(util.isValidIpV4('255.255.255.255')).to.equal(true); + }) + }) + + describe("isValidIpV6", () => { + it("when invalid input", () => { + expect(util.isValidIpV6(true)).to.equal(false); + expect(util.isValidIpV6(false)).to.equal(false); + expect(util.isValidIpV6(0)).to.equal(false); + expect(util.isValidIpV6(10)).to.equal(false); + expect(util.isValidIpV6(null)).to.equal(false); + expect(util.isValidIpV6(undefined)).to.equal(false); + expect(util.isValidIpV6(Infinity)).to.equal(false); + expect(util.isValidIpV6(NaN)).to.equal(false); + expect(util.isValidIpV6('')).to.equal(false); + expect(util.isValidIpV6('abc')).to.equal(false); + expect(util.isValidIpV6('0')).to.equal(false); + expect(util.isValidIpV6([10])).to.equal(false); + expect(util.isValidIpV6({a: 'A'})).to.equal(false); + expect(util.isValidIpV6('0x')).to.equal(false); + expect(util.isValidIpV6('0x6af1ec8d4f0a55bac328cb20336ed0eff46fa6334ebd112147892f1b15aafc8')).to.equal(false); + expect(util.isValidIpV6('ainetwork.ai')).to.equal(false); + expect(util.isValidIpV6('https://*.ainetwork.ai')).to.equal(false); + expect(util.isValidIpV6('http://172.16.0.36:8080/json-rpc')).to.equal(false); + expect(util.isValidIpV6('172.16.0.36')).to.equal(false); + expect(util.isValidIpV6('http://172.16.0.36')).to.equal(false); + expect(util.isValidIpV6('https://172.16.0.36')).to.equal(false); + expect(util.isValidIpV6('http://172.16.0.36:8080')).to.equal(false); + expect(util.isValidIpV6('https://172.16.0.36:9000')).to.equal(false); + }) + + it("when valid input", () => { + expect(util.isValidIpV6('::ffff:172.20.10.2')).to.equal(true); + expect(util.isValidIpV6('1:2:3:4:5:6:7:8')).to.equal(true); + expect(util.isValidIpV6('1::4:5:6:7:8')).to.equal(true); + expect(util.isValidIpV6('::2:3:4:5:6:7:8')).to.equal(true); + expect(util.isValidIpV6('::255.255.255.255')).to.equal(true); + }) + }) + describe("isValidUrlWhitelistItem", () => { it("when invalid input", () => { expect(util.isValidUrlWhitelistItem(true)).to.equal(false); diff --git a/test/unit/state-node.test.js b/test/unit/state-node.test.js index 64bfad1ec..552a0e493 100644 --- a/test/unit/state-node.test.js +++ b/test/unit/state-node.test.js @@ -3,6 +3,7 @@ const chai = require('chai'); const expect = chai.expect; const assert = chai.assert; +const { NodeConfigs } = require('../../common/constants'); const CommonUtil = require('../../common/common-util'); const RadixNode = require('../../db/radix-node'); const RadixTree = require('../../db/radix-tree'); @@ -58,6 +59,7 @@ describe("state-node", () => { expect(node.treeHeight).to.equal(0); expect(node.treeSize).to.equal(0); expect(node.treeBytes).to.equal(0); + expect(node.treeMaxSiblings).to.equal(0); }); it("reset", () => { @@ -71,6 +73,7 @@ describe("state-node", () => { const treeHeight = 1; const treeSize = 10; const treeBytes = 100; + const treeMaxSiblings = 5; node.setVersion(version); node.setLabel(label); @@ -81,6 +84,7 @@ describe("state-node", () => { node.setTreeHeight(treeHeight); node.setTreeSize(treeSize); node.setTreeBytes(treeBytes); + node.setTreeMaxSiblings(treeMaxSiblings); node.reset(); expect(node.version).to.equal(null); @@ -94,6 +98,7 @@ describe("state-node", () => { expect(node.treeHeight).to.equal(0); expect(node.treeSize).to.equal(0); expect(node.treeBytes).to.equal(0); + expect(node.treeMaxSiblings).to.equal(0); }); }); @@ -111,6 +116,7 @@ describe("state-node", () => { expect(node2.treeHeight).to.equal(0); expect(node2.treeSize).to.equal(0); expect(node2.treeBytes).to.equal(0); + expect(node2.treeMaxSiblings).to.equal(0); }); }); @@ -136,6 +142,7 @@ describe("state-node", () => { expect(clone.getTreeHeight()).to.equal(node.getTreeHeight()); expect(clone.getTreeSize()).to.equal(node.getTreeSize()); expect(clone.getTreeBytes()).to.equal(node.getTreeBytes()); + expect(clone.getTreeMaxSiblings()).to.equal(node.getTreeMaxSiblings()); assert.deepEqual( clone.toStateSnapshot(GET_OPTIONS_INCLUDE_ALL), node.toStateSnapshot(GET_OPTIONS_INCLUDE_ALL)); @@ -173,6 +180,7 @@ describe("state-node", () => { expect(clone.getTreeHeight()).to.equal(stateTree.getTreeHeight()); expect(clone.getTreeSize()).to.equal(stateTree.getTreeSize()); expect(clone.getTreeBytes()).to.equal(stateTree.getTreeBytes()); + expect(clone.getTreeMaxSiblings()).to.equal(stateTree.getTreeMaxSiblings()); assert.deepEqual( clone.toStateSnapshot(GET_OPTIONS_INCLUDE_ALL), stateTree.toStateSnapshot(GET_OPTIONS_INCLUDE_ALL)); @@ -196,6 +204,7 @@ describe("state-node", () => { expect(clone.getTreeHeight()).to.equal(node.getTreeHeight()); expect(clone.getTreeSize()).to.equal(node.getTreeSize()); expect(clone.getTreeBytes()).to.equal(node.getTreeBytes()); + expect(clone.getTreeMaxSiblings()).to.equal(node.getTreeMaxSiblings()); }); it("internal node", () => { @@ -223,6 +232,7 @@ describe("state-node", () => { expect(clone.getTreeHeight()).to.equal(stateTree.getTreeHeight()); expect(clone.getTreeSize()).to.equal(stateTree.getTreeSize()); expect(clone.getTreeBytes()).to.equal(stateTree.getTreeBytes()); + expect(clone.getTreeMaxSiblings()).to.equal(stateTree.getTreeMaxSiblings()); }); }); @@ -611,6 +621,9 @@ describe("state-node", () => { "#tree_height": 2, "#tree_height:a": 0, "#tree_height:b": 0, + "#tree_max_siblings": 4, + "#tree_max_siblings:a": 1, + "#tree_max_siblings:b": 1, "#tree_size": 9, "#tree_size:a": 1, "#tree_size:b": 1, @@ -629,6 +642,9 @@ describe("state-node", () => { "#tree_height": 1, "#tree_height:ca": 0, "#tree_height:cb": 0, + "#tree_max_siblings": 2, + "#tree_max_siblings:ca": 1, + "#tree_max_siblings:cb": 1, "#tree_size": 3, "#tree_size:ca": 1, "#tree_size:cb": 1, @@ -648,6 +664,9 @@ describe("state-node", () => { "#tree_height": 1, "#tree_height:da": 0, "#tree_height:db": 0, + "#tree_max_siblings": 2, + "#tree_max_siblings:da": 1, + "#tree_max_siblings:db": 1, "#tree_size": 3, "#tree_size:da": 1, "#tree_size:db": 1, @@ -976,11 +995,13 @@ describe("state-node", () => { describe("child", () => { const label1 = 'label1'; const label2 = 'label2'; + const label3 = 'label3'; let parent1; let parent2; let child1; let child2; + let child3; beforeEach(() => { parent1 = new StateNode(); @@ -991,6 +1012,9 @@ describe("state-node", () => { child2 = new StateNode(); child2.setValue('value2'); + + child3 = new StateNode(); + child3.setValue('value3'); }); it("get / set / has / delete with single parent", () => { @@ -1234,7 +1258,24 @@ describe("state-node", () => { expect(child2.getIsLeaf()).to.equal(true); expect(parent1.getIsLeaf()).to.equal(false); + parent1.setChild(label3, child3); + assert.deepEqual(parent1.getChildLabels(), ['label1', 'label2', 'label3']); + assert.deepEqual(parent1.getChildNodes(), [child1, child2, child3]); + expect(parent1.numChildren()).to.equal(3); + expect(child1.getIsLeaf()).to.equal(true); + expect(child2.getIsLeaf()).to.equal(true); + expect(child3.getIsLeaf()).to.equal(true); + expect(parent1.getIsLeaf()).to.equal(false); + parent1.deleteChild(label2); + assert.deepEqual(parent1.getChildLabels(), ['label1', 'label3']); + assert.deepEqual(parent1.getChildNodes(), [child1, child3]); + expect(parent1.numChildren()).to.equal(2); + expect(child1.getIsLeaf()).to.equal(true); + expect(child2.getIsLeaf()).to.equal(true); + expect(parent1.getIsLeaf()).to.equal(false); + + parent1.deleteChild(label3); assert.deepEqual(parent1.getChildLabels(), ['label1']); assert.deepEqual(parent1.getChildNodes(), [child1]); expect(parent1.numChildren()).to.equal(1); @@ -1250,6 +1291,174 @@ describe("state-node", () => { expect(child2.getIsLeaf()).to.equal(true); expect(parent1.getIsLeaf()).to.equal(true); }); + + it("getChildLabelsWithEndLabel / getChildNodesWithEndLabel with isPartial = true", () => { + // Change GET_RESP_MAX_SIBLINGS value for testing. + const originalGetRespMaxSiblings = NodeConfigs.GET_RESP_MAX_SIBLINGS; + NodeConfigs.GET_RESP_MAX_SIBLINGS = 2; + + const labelsWithEndLabel1 = parent1.getChildLabelsWithEndLabel(true); + assert.deepEqual(labelsWithEndLabel1.list, []); + assert.deepEqual(labelsWithEndLabel1.serialList, []); + expect(labelsWithEndLabel1.endLabel).to.equal(null); + const nodesWithEndLabel1 = parent1.getChildNodesWithEndLabel(true); + assert.deepEqual(nodesWithEndLabel1.list, []); + assert.deepEqual(nodesWithEndLabel1.serialList, []); + expect(nodesWithEndLabel1.endLabel).to.equal(null); + + parent1.setChild(label1, child1); + parent1.setChild(label2, child2); + const labelsWithEndLabel2 = parent1.getChildLabelsWithEndLabel(true); + assert.deepEqual(labelsWithEndLabel2.list, ['label1', 'label2']); + assert.deepEqual(labelsWithEndLabel2.serialList, [2, 5]); + expect(labelsWithEndLabel2.endLabel).to.equal('6c6162656c32'); + const nodesWithEndLabel2 = parent1.getChildNodesWithEndLabel(true); + assert.deepEqual(nodesWithEndLabel2.list, [child1, child2]); + assert.deepEqual(nodesWithEndLabel2.serialList, [2, 5]); + expect(nodesWithEndLabel2.endLabel).to.equal('6c6162656c32'); + + parent1.setChild(label3, child3); + const labelsWithEndLabel3 = parent1.getChildLabelsWithEndLabel(true); + // skip label3 + assert.deepEqual(labelsWithEndLabel3.list, ['label1', 'label2']); + assert.deepEqual(labelsWithEndLabel3.serialList, [2, 5]); + expect(labelsWithEndLabel3.endLabel).to.equal('6c6162656c32'); + const nodesWithEndLabel3 = parent1.getChildNodesWithEndLabel(true); + // skip child3 + assert.deepEqual(nodesWithEndLabel3.list, [child1, child2]); + assert.deepEqual(nodesWithEndLabel3.serialList, [2, 5]); + expect(nodesWithEndLabel3.endLabel).to.equal('6c6162656c32'); + + parent1.deleteChild(label2); + const labelsWithEndLabel4 = parent1.getChildLabelsWithEndLabel(true); + assert.deepEqual(labelsWithEndLabel4.list, ['label1', 'label3']); + assert.deepEqual(labelsWithEndLabel4.serialList, [2, 7]); + expect(labelsWithEndLabel4.endLabel).to.equal('6c6162656c33'); + const nodesWithEndLabel4 = parent1.getChildNodesWithEndLabel(true); + assert.deepEqual(nodesWithEndLabel4.list, [child1, child3]); + assert.deepEqual(nodesWithEndLabel4.serialList, [2, 7]); + expect(nodesWithEndLabel4.endLabel).to.equal('6c6162656c33'); + + parent1.deleteChild(label3); + parent1.deleteChild(label1); + const labelsWithEndLabel5 = parent1.getChildLabelsWithEndLabel(true); + assert.deepEqual(labelsWithEndLabel5.list, []); + assert.deepEqual(labelsWithEndLabel5.serialList, []); + expect(labelsWithEndLabel5.endLabel).to.equal(null); + const nodesWithEndLabel5 = parent1.getChildNodesWithEndLabel(true); + assert.deepEqual(nodesWithEndLabel5.list, []); + assert.deepEqual(nodesWithEndLabel5.serialList, []); + expect(nodesWithEndLabel5.endLabel).to.equal(null); + + // Restore GET_RESP_MAX_SIBLINGS value. + NodeConfigs.GET_RESP_MAX_SIBLINGS = originalGetRespMaxSiblings; + }); + + it("getChildLabelsWithEndLabel / getChildNodesWithEndLabel with isPartial = true and lastEndLabel", () => { + // Change GET_RESP_MAX_SIBLINGS value for testing. + const originalGetRespMaxSiblings = NodeConfigs.GET_RESP_MAX_SIBLINGS; + NodeConfigs.GET_RESP_MAX_SIBLINGS = 2; + + // lastEndLabel = ''6c6162656c31' (child1) + + const labelsWithEndLabel1 = parent1.getChildLabelsWithEndLabel(true, '6c6162656c31'); + assert.deepEqual(labelsWithEndLabel1.list, []); + assert.deepEqual(labelsWithEndLabel1.serialList, []); + expect(labelsWithEndLabel1.endLabel).to.equal(null); + const nodesWithEndLabel1 = parent1.getChildNodesWithEndLabel(true, '6c6162656c31'); + assert.deepEqual(nodesWithEndLabel1.list, []); + assert.deepEqual(nodesWithEndLabel1.serialList, []); + expect(nodesWithEndLabel1.endLabel).to.equal(null); + + parent1.setChild(label1, child1); + parent1.setChild(label2, child2); + const labelsWithEndLabel2 = parent1.getChildLabelsWithEndLabel(true, '6c6162656c31'); + assert.deepEqual(labelsWithEndLabel2.list, ['label2']); + assert.deepEqual(labelsWithEndLabel2.serialList, [5]); + expect(labelsWithEndLabel2.endLabel).to.equal('6c6162656c32'); + const nodesWithEndLabel2 = parent1.getChildNodesWithEndLabel(true, '6c6162656c31'); + assert.deepEqual(nodesWithEndLabel2.list, [child2]); + assert.deepEqual(nodesWithEndLabel2.serialList, [5]); + expect(nodesWithEndLabel2.endLabel).to.equal('6c6162656c32'); + + parent1.setChild(label3, child3); + const labelsWithEndLabel3 = parent1.getChildLabelsWithEndLabel(true, '6c6162656c31'); + // skip label1 + assert.deepEqual(labelsWithEndLabel3.list, ['label2', 'label3']); + assert.deepEqual(labelsWithEndLabel3.serialList, [5, 7]); + expect(labelsWithEndLabel3.endLabel).to.equal('6c6162656c33'); + const nodesWithEndLabel3 = parent1.getChildNodesWithEndLabel(true, '6c6162656c31'); + // skip child1 + assert.deepEqual(nodesWithEndLabel3.list, [child2, child3]); + assert.deepEqual(nodesWithEndLabel3.serialList, [5, 7]); + expect(nodesWithEndLabel3.endLabel).to.equal('6c6162656c33'); + + parent1.deleteChild(label2); + const labelsWithEndLabel4 = parent1.getChildLabelsWithEndLabel(true, '6c6162656c31'); + // skip label1 + assert.deepEqual(labelsWithEndLabel4.list, ['label3']); + assert.deepEqual(labelsWithEndLabel4.serialList, [7]); + expect(labelsWithEndLabel4.endLabel).to.equal('6c6162656c33'); + const nodesWithEndLabel4 = parent1.getChildNodesWithEndLabel(true, '6c6162656c31'); + // skip child1 + assert.deepEqual(nodesWithEndLabel4.list, [child3]); + assert.deepEqual(nodesWithEndLabel4.serialList, [7]); + expect(nodesWithEndLabel4.endLabel).to.equal('6c6162656c33'); + + parent1.deleteChild(label3); + parent1.deleteChild(label1); + const labelsWithEndLabel5 = parent1.getChildLabelsWithEndLabel(true, '6c6162656c31'); + assert.deepEqual(labelsWithEndLabel5.list, []); + assert.deepEqual(labelsWithEndLabel5.serialList, []); + expect(labelsWithEndLabel5.endLabel).to.equal(null); + const nodesWithEndLabel5 = parent1.getChildNodesWithEndLabel(true, '6c6162656c31'); + assert.deepEqual(nodesWithEndLabel5.list, []); + assert.deepEqual(nodesWithEndLabel5.serialList, []); + expect(nodesWithEndLabel5.endLabel).to.equal(null); + + // Restore GET_RESP_MAX_SIBLINGS value. + NodeConfigs.GET_RESP_MAX_SIBLINGS = originalGetRespMaxSiblings; + }); + + it("getChildLabelsWithEndLabel / getChildNodesWithEndLabel with isPartial = true and lastEndLabel - chaining", () => { + // Change GET_RESP_MAX_SIBLINGS value for testing. + const originalGetRespMaxSiblings = NodeConfigs.GET_RESP_MAX_SIBLINGS; + NodeConfigs.GET_RESP_MAX_SIBLINGS = 2; + + parent1.setChild(label1, child1); + parent1.setChild(label2, child2); + parent1.setChild(label3, child3); + + const labelsWithEndLabel1 = parent1.getChildLabelsWithEndLabel(true); + assert.deepEqual(labelsWithEndLabel1.list, ['label1', 'label2']); + assert.deepEqual(labelsWithEndLabel1.serialList, [2, 5]); + expect(labelsWithEndLabel1.endLabel).to.equal('6c6162656c32'); + const nodesWithEndLabel1 = parent1.getChildNodesWithEndLabel(true); + assert.deepEqual(nodesWithEndLabel1.list, [child1, child2]); + assert.deepEqual(nodesWithEndLabel1.serialList, [2, 5]); + expect(nodesWithEndLabel1.endLabel).to.equal('6c6162656c32'); + + const labelsWithEndLabel2 = parent1.getChildLabelsWithEndLabel(true, '6c6162656c32'); + assert.deepEqual(labelsWithEndLabel2.list, ['label3']); + assert.deepEqual(labelsWithEndLabel2.serialList, [7]); + expect(labelsWithEndLabel2.endLabel).to.equal('6c6162656c33'); + const nodesWithEndLabel2 = parent1.getChildNodesWithEndLabel(true, '6c6162656c32'); + assert.deepEqual(nodesWithEndLabel2.list, [child3]); + assert.deepEqual(nodesWithEndLabel2.serialList, [7]); + expect(nodesWithEndLabel2.endLabel).to.equal('6c6162656c33'); + + const labelsWithEndLabel3 = parent1.getChildLabelsWithEndLabel(true, '6c6162656c33'); + assert.deepEqual(labelsWithEndLabel3.list, []); + assert.deepEqual(labelsWithEndLabel3.serialList, []); + expect(labelsWithEndLabel3.endLabel).to.equal(null); + const nodesWithEndLabel3 = parent1.getChildNodesWithEndLabel(true, '6c6162656c33'); + assert.deepEqual(nodesWithEndLabel3.list, []); + assert.deepEqual(nodesWithEndLabel3.serialList, []); + expect(nodesWithEndLabel3.endLabel).to.equal(null); + + // Restore GET_RESP_MAX_SIBLINGS value. + NodeConfigs.GET_RESP_MAX_SIBLINGS = originalGetRespMaxSiblings; + }); }); describe("radix tree", () => { @@ -1316,6 +1525,16 @@ describe("state-node", () => { }); }); + describe("tree max siblings", () => { + it("getTreeMaxSiblings / setTreeMaxSiblings", () => { + expect(node.getTreeMaxSiblings()).to.equal(0); + node.setTreeMaxSiblings(5); + expect(node.getTreeMaxSiblings()).to.equal(5); + node.setTreeMaxSiblings(3); + expect(node.getTreeMaxSiblings()).to.equal(3); + }); + }); + describe("buildStateInfo", () => { describe("proof hash", () => { it("leaf node", () => { @@ -1484,6 +1703,41 @@ describe("state-node", () => { expect(stateTree.buildStateInfo().treeBytes).to.equal(324); }); }); + + describe("tree max siblings", () => { + it("leaf node", () => { + node.setValue(true); + expect(node.buildStateInfo().treeMaxSiblings).to.equal(1); + node.setValue(10); + expect(node.buildStateInfo().treeMaxSiblings).to.equal(1); + node.setValue(-200); + expect(node.buildStateInfo().treeMaxSiblings).to.equal(1); + node.setValue(''); + expect(node.buildStateInfo().treeMaxSiblings).to.equal(1); + node.setValue('unittest'); + expect(node.buildStateInfo().treeMaxSiblings).to.equal(1); + node.setValue(null); + expect(node.buildStateInfo().treeMaxSiblings).to.equal(1); + node.setValue(undefined); + expect(node.buildStateInfo().treeMaxSiblings).to.equal(1); + }); + + it("internal node", () => { + child1.setTreeMaxSiblings(5); + child2.setTreeMaxSiblings(15); + child3.setTreeMaxSiblings(25); + child4.setTreeMaxSiblings(35); + stateTree.radixTree.root.setTreeMaxSiblings(1000); + + // With updatedChildLabel = null, shouldRebuildRadixInfo = false + expect(stateTree.buildStateInfo(null, false).treeMaxSiblings).to.equal(1000); + // With updatedChildLabel = label1, shouldRebuildRadixInfo = true + expect(stateTree.buildStateInfo(label1).treeMaxSiblings).to.equal(5); + // With updatedChildLabel = null, shouldRebuildRadixInfo = true + expect(stateTree.buildStateInfo().treeMaxSiblings).to.equal(35); + }); + }); + }); describe("updateStateInfo / verifyStateInfo", () => { @@ -1539,6 +1793,10 @@ describe("state-node", () => { child2.setTreeSize(20); child3.setTreeSize(30); child4.setTreeSize(40); + child1.setTreeMaxSiblings(5); + child2.setTreeMaxSiblings(15); + child3.setTreeMaxSiblings(25); + child4.setTreeMaxSiblings(35); expect(stateTree.verifyStateInfo()).to.equal(false); // update without updatedChildLabel diff --git a/test/unit/state-util.test.js b/test/unit/state-util.test.js index e13f82ffd..92bfff333 100644 --- a/test/unit/state-util.test.js +++ b/test/unit/state-util.test.js @@ -690,6 +690,9 @@ describe("state-util", () => { expect(isValidWriteRule({}, "newData = 'some code'")).to.equal(false); // assignment & invoke expect(isValidWriteRule({}, "newData = 'some code'; newData();")).to.equal(false); + // function + expect(isValidWriteRule({}, "[function(){while(true){}}][0]()")).to.equal(false); + expect(isValidWriteRule({}, "[()=>{while(true){}}][0]()")).to.equal(false); }) it('when valid input', () => { @@ -2414,6 +2417,7 @@ describe("state-util", () => { "#state_ph": null, "#tree_bytes": 0, "#tree_height": 0, + "#tree_max_siblings": 0, "#tree_size": 0, "#version": "ver3", "label1": { @@ -2432,6 +2436,9 @@ describe("state-util", () => { "#tree_height": 0, "#tree_height:label11": 0, "#tree_height:label12": 0, + "#tree_max_siblings": 0, + "#tree_max_siblings:label11": 0, + "#tree_max_siblings:label12": 0, "#tree_size": 0, "#tree_size:label11": 0, "#tree_size:label12": 0, @@ -2457,6 +2464,9 @@ describe("state-util", () => { "#tree_height": 0, "#tree_height:label21": 0, "#tree_height:label22": 0, + "#tree_max_siblings": 0, + "#tree_max_siblings:label21": 0, + "#tree_max_siblings:label22": 0, "#tree_size": 0, "#tree_size:label21": 0, "#tree_size:label22": 0, @@ -2476,6 +2486,7 @@ describe("state-util", () => { "#state_ph": null, "#tree_bytes": 0, "#tree_height": 0, + "#tree_max_siblings": 0, "#tree_size": 0, "#version": "ver3", "label1": { @@ -2494,6 +2505,9 @@ describe("state-util", () => { "#tree_height": 0, "#tree_height:label11": 0, "#tree_height:label12": 0, + "#tree_max_siblings": 0, + "#tree_max_siblings:label11": 0, + "#tree_max_siblings:label12": 0, "#tree_size": 0, "#tree_size:label11": 0, "#tree_size:label12": 0, @@ -2519,6 +2533,9 @@ describe("state-util", () => { "#tree_height": 0, "#tree_height:label21": 0, "#tree_height:label22": 0, + "#tree_max_siblings": 0, + "#tree_max_siblings:label21": 0, + "#tree_max_siblings:label22": 0, "#tree_size": 0, "#tree_size:label21": 0, "#tree_size:label22": 0, @@ -2628,6 +2645,9 @@ describe("state-util", () => { "#tree_height": 1, "#tree_height:label1": 0, "#tree_height:label2": 0, + "#tree_max_siblings": 2, + "#tree_max_siblings:label1": 1, + "#tree_max_siblings:label2": 1, "#tree_size": 3, "#tree_size:label1": 1, "#tree_size:label2": 1, diff --git a/tools/api-access/addToDevClientApiIpWhitelist.js b/tools/api-access/addToDevClientApiIpWhitelist.js index b87c9717a..281e1a9cd 100644 --- a/tools/api-access/addToDevClientApiIpWhitelist.js +++ b/tools/api-access/addToDevClientApiIpWhitelist.js @@ -4,18 +4,20 @@ const ainUtil = require('@ainblockchain/ain-util'); const stringify = require('fast-json-stable-stringify'); const { BlockchainConsts } = require('../../common/constants'); const { getAccountPrivateKey } = require('./util'); +const { JSON_RPC_METHODS } = require('../../json_rpc/constants'); async function sendAddToDevClientApiIpWhitelistRequest(endpointUrl, privateKey, chainId, ip) { const message = { timestamp: Date.now(), - method: 'ain_addToDevClientApiIpWhitelist', - ip, + method: JSON_RPC_METHODS.AIN_ADD_TO_WHITELIST_NODE_PARAM, + param: 'DEV_CLIENT_API_IP_WHITELIST', + value: ip, }; const signature = ainUtil.ecSignMessage(stringify(message), Buffer.from(privateKey, 'hex'), chainId); return await axios.post( `${endpointUrl}/json-rpc`, { - method: 'ain_addToDevClientApiIpWhitelist', + method: JSON_RPC_METHODS.AIN_ADD_TO_WHITELIST_NODE_PARAM, params: { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, message, @@ -60,9 +62,9 @@ async function processArguments() { function usage() { console.log('\nUsage:\n node addToDevClientApiIpWhitelist.js [] \n'); console.log('\nExamples:'); - console.log('node tools/api-access/getDevClientApiIpWhitelist.js http://localhost:8081 0 private_key 127.0.0.1'); - console.log('node tools/api-access/getDevClientApiIpWhitelist.js http://localhost:8081 0 mnemonic 127.0.0.1'); - console.log('node tools/api-access/getDevClientApiIpWhitelist.js http://localhost:8081 0 keystore /path/to/keystore/file 127.0.0.1'); + console.log('node tools/api-access/addToDevClientApiIpWhitelist.js http://localhost:8081 0 private_key 127.0.0.1'); + console.log('node tools/api-access/addToDevClientApiIpWhitelist.js http://localhost:8081 0 mnemonic 127.0.0.1'); + console.log('node tools/api-access/addToDevClientApiIpWhitelist.js http://localhost:8081 0 keystore /path/to/keystore/file "*"'); process.exit(0); } diff --git a/tools/api-access/addToWhiteListNodeParam.js b/tools/api-access/addToWhiteListNodeParam.js new file mode 100644 index 000000000..0a824d64c --- /dev/null +++ b/tools/api-access/addToWhiteListNodeParam.js @@ -0,0 +1,74 @@ +const axios = require('axios'); +const _ = require('lodash'); +const ainUtil = require('@ainblockchain/ain-util'); +const stringify = require('fast-json-stable-stringify'); +const { BlockchainConsts } = require('../../common/constants'); +const { getAccountPrivateKey } = require('./util'); +const { JSON_RPC_METHODS } = require('../../json_rpc/constants'); + +async function sendAddToWhiteListNodeParamRequest(endpointUrl, privateKey, chainId, param, value) { + const message = { + timestamp: Date.now(), + method: JSON_RPC_METHODS.AIN_ADD_TO_WHITELIST_NODE_PARAM, + param, + value, + }; + const signature = ainUtil.ecSignMessage(stringify(message), Buffer.from(privateKey, 'hex'), chainId); + return await axios.post( + `${endpointUrl}/json-rpc`, + { + method: JSON_RPC_METHODS.AIN_ADD_TO_WHITELIST_NODE_PARAM, + params: { + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, + message, + signature, + }, + jsonrpc: '2.0', + id: 0 + } + ).then(function(resp) { + return _.get(resp, 'data.result.result'); + }); +} + +async function addToWhiteListNodeParam(endpointUrl, chainId, type, keystoreFilePath, param, value) { + const privateKey = await getAccountPrivateKey(type, keystoreFilePath); + const res = await sendAddToWhiteListNodeParamRequest(endpointUrl, privateKey, chainId, param, value); + console.log('Result:', res); +} + +async function processArguments() { + if (process.argv.length !== 7 && process.argv.length !== 8) { + usage(); + } + const endpointUrl = process.argv[2]; + const chainId = Number(process.argv[3]); + const accountType = process.argv[4]; + let keystoreFilePath = null; + let param = null; + let value = null; + if (accountType === 'keystore') { + keystoreFilePath = process.argv[5]; + param = process.argv[6]; + value = process.argv[7]; + } else { + param = process.argv[5]; + value = process.argv[6]; + } + if (!value) { + console.error('Please specify a value'); + usage(); + } + await addToWhiteListNodeParam(endpointUrl, chainId, accountType, keystoreFilePath, param, value); +} + +function usage() { + console.log('\nUsage:\n node addToWhiteListNodeParam.js [] \n'); + console.log('\nExamples:'); + console.log('node tools/api-access/addToWhiteListNodeParam.js http://localhost:8081 0 private_key DEV_CLIENT_API_IP_WHITELIST 127.0.0.1'); + console.log('node tools/api-access/addToWhiteListNodeParam.js http://localhost:8081 0 mnemonic DEV_CLIENT_API_IP_WHITELIST "*"'); + console.log('node tools/api-access/addToWhiteListNodeParam.js http://localhost:8081 0 keystore /path/to/kezystore/file CORS_WHITELIST "https://ainetwork\\.ai"'); + process.exit(0); +} + +processArguments(); diff --git a/tools/api-access/getDevClientApiIpWhitelist.js b/tools/api-access/getDevClientApiIpWhitelist.js index f1277bdd7..7fb66a2f5 100644 --- a/tools/api-access/getDevClientApiIpWhitelist.js +++ b/tools/api-access/getDevClientApiIpWhitelist.js @@ -4,17 +4,19 @@ const ainUtil = require('@ainblockchain/ain-util'); const stringify = require('fast-json-stable-stringify'); const { BlockchainConsts } = require('../../common/constants'); const { getAccountPrivateKey } = require('./util'); +const { JSON_RPC_METHODS } = require('../../json_rpc/constants'); async function sendGetDevClientApiIpWhitelistRequest(endpointUrl, privateKey, chainId) { const message = { timestamp: Date.now(), - method: 'ain_getDevClientApiIpWhitelist', + method: JSON_RPC_METHODS.AIN_GET_NODE_PARAM, + param: 'DEV_CLIENT_API_IP_WHITELIST', }; const signature = ainUtil.ecSignMessage(stringify(message), Buffer.from(privateKey, 'hex'), chainId); return await axios.post( `${endpointUrl}/json-rpc`, { - method: 'ain_getDevClientApiIpWhitelist', + method: JSON_RPC_METHODS.AIN_GET_NODE_PARAM, params: { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, message, diff --git a/tools/api-access/getNodeParam.js b/tools/api-access/getNodeParam.js new file mode 100644 index 000000000..44e026ac2 --- /dev/null +++ b/tools/api-access/getNodeParam.js @@ -0,0 +1,66 @@ +const axios = require('axios'); +const _ = require('lodash'); +const ainUtil = require('@ainblockchain/ain-util'); +const stringify = require('fast-json-stable-stringify'); +const { BlockchainConsts } = require('../../common/constants'); +const { getAccountPrivateKey } = require('./util'); +const { JSON_RPC_METHODS } = require('../../json_rpc/constants'); + +async function sendGetNodeParamRequest(endpointUrl, privateKey, chainId, param) { + const message = { + timestamp: Date.now(), + method: JSON_RPC_METHODS.AIN_GET_NODE_PARAM, + param, + }; + const signature = ainUtil.ecSignMessage(stringify(message), Buffer.from(privateKey, 'hex'), chainId); + return await axios.post( + `${endpointUrl}/json-rpc`, + { + method: JSON_RPC_METHODS.AIN_GET_NODE_PARAM, + params: { + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, + message, + signature, + }, + jsonrpc: '2.0', + id: 0 + } + ).then(function(resp) { + return _.get(resp, 'data.result.result'); + }); +} + +async function getNodeParam(endpointUrl, chainId, type, keystoreFilePath, param) { + const privateKey = await getAccountPrivateKey(type, keystoreFilePath); + const res = await sendGetNodeParamRequest(endpointUrl, privateKey, chainId, param); + console.log('Result:', res); +} + +async function processArguments() { + if (process.argv.length !== 6 && process.argv.length !== 7) { + usage(); + } + const endpointUrl = process.argv[2]; + const chainId = Number(process.argv[3]); + const accountType = process.argv[4]; + let keystoreFilePath = null; + let param = null; + if (accountType === 'keystore') { + keystoreFilePath = process.argv[5]; + param = process.argv[6]; + } else { + param = process.argv[5]; + } + await getNodeParam(endpointUrl, chainId, accountType, keystoreFilePath, param); +} + +function usage() { + console.log('\nUsage:\n node getNodeParam.js [] \n'); + console.log('\nExamples:'); + console.log('node tools/api-access/getNodeParam.js http://localhost:8081 0 private_key DEV_CLIENT_API_IP_WHITELIST'); + console.log('node tools/api-access/getNodeParam.js http://localhost:8081 0 mnemonic P2P_MESSAGE_TIMEOUT_MS'); + console.log('node tools/api-access/getNodeParam.js http://localhost:8081 0 keystore /path/to/keystore/file SYNC_MODE'); + process.exit(0); +} + +processArguments(); diff --git a/tools/api-access/removeFromDevClientApiIpWhitelist.js b/tools/api-access/removeFromDevClientApiIpWhitelist.js index 3edd2da33..275c34b74 100644 --- a/tools/api-access/removeFromDevClientApiIpWhitelist.js +++ b/tools/api-access/removeFromDevClientApiIpWhitelist.js @@ -4,18 +4,20 @@ const ainUtil = require('@ainblockchain/ain-util'); const stringify = require('fast-json-stable-stringify'); const { BlockchainConsts } = require('../../common/constants'); const { getAccountPrivateKey } = require('./util'); +const { JSON_RPC_METHODS } = require('../../json_rpc/constants'); async function sendRemoveFromToDevClientApiIpWhitelistRequest(endpointUrl, privateKey, chainId, ip) { const message = { timestamp: Date.now(), - method: 'ain_removeFromDevClientApiIpWhitelist', - ip, + method: JSON_RPC_METHODS.AIN_REMOVE_FROM_WHITELIST_NODE_PARAM, + param: 'DEV_CLIENT_API_IP_WHITELIST', + value: ip, }; const signature = ainUtil.ecSignMessage(stringify(message), Buffer.from(privateKey, 'hex'), chainId); return await axios.post( `${endpointUrl}/json-rpc`, { - method: 'ain_removeFromDevClientApiIpWhitelist', + method: JSON_RPC_METHODS.AIN_REMOVE_FROM_WHITELIST_NODE_PARAM, params: { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, message, @@ -60,9 +62,9 @@ async function processArguments() { function usage() { console.log('\nUsage:\n node removeFromDevClientApiIpWhitelist.js [] \n'); console.log('\nExamples:'); - console.log('node tools/api-access/getDevClientApiIpWhitelist.js http://localhost:8081 0 private_key 127.0.0.1'); - console.log('node tools/api-access/getDevClientApiIpWhitelist.js http://localhost:8081 0 mnemonic 127.0.0.1'); - console.log('node tools/api-access/getDevClientApiIpWhitelist.js http://localhost:8081 0 keystore /path/to/keystore/file 127.0.0.1'); + console.log('node tools/api-access/removeFromDevClientApiIpWhitelist.js http://localhost:8081 0 private_key 127.0.0.1'); + console.log('node tools/api-access/removeFromDevClientApiIpWhitelist.js http://localhost:8081 0 mnemonic 127.0.0.1'); + console.log('node tools/api-access/removeFromDevClientApiIpWhitelist.js http://localhost:8081 0 keystore /path/to/keystore/file "*"'); process.exit(0); } diff --git a/tools/api-access/removeFromWhitelistNodeParam.js b/tools/api-access/removeFromWhitelistNodeParam.js new file mode 100644 index 000000000..042a5f9dc --- /dev/null +++ b/tools/api-access/removeFromWhitelistNodeParam.js @@ -0,0 +1,74 @@ +const axios = require('axios'); +const _ = require('lodash'); +const ainUtil = require('@ainblockchain/ain-util'); +const stringify = require('fast-json-stable-stringify'); +const { BlockchainConsts } = require('../../common/constants'); +const { getAccountPrivateKey } = require('./util'); +const { JSON_RPC_METHODS } = require('../../json_rpc/constants'); + +async function sendRemoveFromWhiteListNodeParamRequest(endpointUrl, privateKey, chainId, param, value) { + const message = { + timestamp: Date.now(), + method: JSON_RPC_METHODS.AIN_REMOVE_FROM_WHITELIST_NODE_PARAM, + param, + value, + }; + const signature = ainUtil.ecSignMessage(stringify(message), Buffer.from(privateKey, 'hex'), chainId); + return await axios.post( + `${endpointUrl}/json-rpc`, + { + method: JSON_RPC_METHODS.AIN_REMOVE_FROM_WHITELIST_NODE_PARAM, + params: { + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, + message, + signature, + }, + jsonrpc: '2.0', + id: 0 + } + ).then(function(resp) { + return _.get(resp, 'data.result.result'); + }); +} + +async function removeFromWhiteListNodeParam(endpointUrl, chainId, type, keystoreFilePath, param, value) { + const privateKey = await getAccountPrivateKey(type, keystoreFilePath); + const res = await sendRemoveFromWhiteListNodeParamRequest(endpointUrl, privateKey, chainId, param, value); + console.log('Result:', res); +} + +async function processArguments() { + if (process.argv.length !== 7 && process.argv.length !== 8) { + usage(); + } + const endpointUrl = process.argv[2]; + const chainId = Number(process.argv[3]); + const accountType = process.argv[4]; + let keystoreFilePath = null; + let param = null; + let value = null; + if (accountType === 'keystore') { + keystoreFilePath = process.argv[5]; + param = process.argv[6]; + value = process.argv[7]; + } else { + param = process.argv[5]; + value = process.argv[6]; + } + if (!value) { + console.error('Please specify a value'); + usage(); + } + await removeFromWhiteListNodeParam(endpointUrl, chainId, accountType, keystoreFilePath, param, value); +} + +function usage() { + console.log('\nUsage:\n node removeFromWhiteListNodeParam.js [] \n'); + console.log('\nExamples:'); + console.log('node tools/api-access/removeFromWhiteListNodeParam.js http://localhost:8081 0 private_key DEV_CLIENT_API_IP_WHITELIST 127.0.0.1'); + console.log('node tools/api-access/removeFromWhiteListNodeParam.js http://localhost:8081 0 mnemonic DEV_CLIENT_API_IP_WHITELIST "*"'); + console.log('node tools/api-access/removeFromWhiteListNodeParam.js http://localhost:8081 0 keystore /path/to/kezystore/file CORS_WHITELIST "https://ainetwork\\.ai"'); + process.exit(0); +} + +processArguments(); diff --git a/tools/api-access/setNodeParam.js b/tools/api-access/setNodeParam.js new file mode 100644 index 000000000..5424a5b47 --- /dev/null +++ b/tools/api-access/setNodeParam.js @@ -0,0 +1,74 @@ +const axios = require('axios'); +const _ = require('lodash'); +const ainUtil = require('@ainblockchain/ain-util'); +const stringify = require('fast-json-stable-stringify'); +const { BlockchainConsts } = require('../../common/constants'); +const { getAccountPrivateKey } = require('./util'); +const { JSON_RPC_METHODS } = require('../../json_rpc/constants'); + +async function sendSetNodeParamRequest(endpointUrl, privateKey, chainId, param, value) { + const message = { + timestamp: Date.now(), + method: JSON_RPC_METHODS.AIN_REMOVE_FROM_WHITELIST_NODE_PARAM, + param, + value, + }; + const signature = ainUtil.ecSignMessage(stringify(message), Buffer.from(privateKey, 'hex'), chainId); + return await axios.post( + `${endpointUrl}/json-rpc`, + { + method: JSON_RPC_METHODS.AIN_REMOVE_FROM_WHITELIST_NODE_PARAM, + params: { + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, + message, + signature, + }, + jsonrpc: '2.0', + id: 0 + } + ).then(function(resp) { + return _.get(resp, 'data.result.result'); + }); +} + +async function setNodeParam(endpointUrl, chainId, type, keystoreFilePath, param, value) { + const privateKey = await getAccountPrivateKey(type, keystoreFilePath); + const res = await sendSetNodeParamRequest(endpointUrl, privateKey, chainId, param, value); + console.log('Result:', res); +} + +async function processArguments() { + if (process.argv.length !== 7 && process.argv.length !== 8) { + usage(); + } + const endpointUrl = process.argv[2]; + const chainId = Number(process.argv[3]); + const accountType = process.argv[4]; + let keystoreFilePath = null; + let param = null; + let value = null; + if (accountType === 'keystore') { + keystoreFilePath = process.argv[5]; + param = process.argv[6]; + value = process.argv[7]; + } else { + param = process.argv[5]; + value = process.argv[6]; + } + if (!value) { + console.error('Please specify a value'); + usage(); + } + await setNodeParam(endpointUrl, chainId, accountType, keystoreFilePath, param, value); +} + +function usage() { + console.log('\nUsage:\n node setNodeParam.js [] \n'); + console.log('\nExamples:'); + console.log('node tools/api-access/setNodeParam.js http://localhost:8081 0 private_key DEV_CLIENT_API_IP_WHITELIST "*"'); + console.log('node tools/api-access/setNodeParam.js http://localhost:8081 0 mnemonic P2P_MESSAGE_TIMEOUT_MS 200000'); + console.log('node tools/api-access/setNodeParam.js http://localhost:8081 0 keystore /path/to/kezystore/file SYNC_MODE peer'); + process.exit(0); +} + +processArguments(); diff --git a/tools/checkin/sendCloseCheckinTx.js b/tools/checkin/sendCloseCheckinTx.js index 54e995adb..1f1d13259 100644 --- a/tools/checkin/sendCloseCheckinTx.js +++ b/tools/checkin/sendCloseCheckinTx.js @@ -2,10 +2,11 @@ const path = require('path'); const _ = require('lodash'); const { signAndSendTx, confirmTransaction } = require('../util'); const { sendGetRequest } = require('../../common/network-util'); +const { JSON_RPC_METHODS } = require('../../json_rpc/constants'); let config = {}; async function buildCloseCheckinTxBody(fromAddr, checkinId, failed = false) { - const request = (await sendGetRequest(`${config.endpointUrl}/json-rpc`, 'ain_get', { + const request = (await sendGetRequest(`${config.endpointUrl}/json-rpc`, JSON_RPC_METHODS.AIN_GET, { type: 'GET_VALUE', ref: `/checkin/requests/ETH/3/0xB16c0C80a81f73204d454426fC413CAe455525A7/${fromAddr}/${checkinId}`, })).data.result.result; diff --git a/tools/json-rpc-get/ainGetLastBlock.js b/tools/json-rpc-get/ainGetLastBlock.js new file mode 100644 index 000000000..9800a3e7c --- /dev/null +++ b/tools/json-rpc-get/ainGetLastBlock.js @@ -0,0 +1,37 @@ +const _ = require('lodash'); +const axios = require('axios'); +const { JSON_RPC_METHODS } = require('../../json_rpc/constants'); +const { endpoint } = require('./config_local'); + +const queryOnNode = (method, params) => { + return axios.post( + endpoint, + { + method, + params: Object.assign(params, { protoVer: '1.0.6' }), + jsonrpc: '2.0', + id: 1 + } + ).then((resp) => { + return _.get(resp, 'data.result.result'); + }).catch((err) => { + console.error(`Failed to send get request: ${err}`); + return null; + }); +} + +const usage = () => { + console.log('\nExample commandlines:\n node index.js\n'); +} + +const main = async () => { + if (process.argv.length !== 2) { + usage(); + process.exit(0); + } + const result = await queryOnNode(JSON_RPC_METHODS.AIN_GET_LAST_BLOCK, { }); + // const result = await queryOnNode(JSON_RPC_METHODS.AIN_GET_LAST_BLOCK_NUMBER, { }); + console.log(result); +} + +main(); diff --git a/tools/json-rpc-get/config_local.js b/tools/json-rpc-get/config_local.js new file mode 100644 index 000000000..b925dbf8d --- /dev/null +++ b/tools/json-rpc-get/config_local.js @@ -0,0 +1,3 @@ +module.exports = { + endpoint: 'http://localhost:8081/json-rpc' +}; diff --git a/tools/proposer-stats/getProposerStats.js b/tools/proposer-stats/getProposerStats.js index 36e84594a..eddddb6cb 100644 --- a/tools/proposer-stats/getProposerStats.js +++ b/tools/proposer-stats/getProposerStats.js @@ -6,13 +6,14 @@ const axios = require('axios'); const _ = require('lodash'); const CommonUtil = require('../../common/common-util'); const { BlockchainConsts, NodeConfigs } = require('../../common/constants'); +const { JSON_RPC_METHODS } = require('../../json_rpc/constants'); async function getBlockList(from, to, endpointUrl) { console.log(`getting block list from ${from} to ${to}`); return await axios.post( `${endpointUrl}/json-rpc`, { - method: 'ain_getBlockList', + method: JSON_RPC_METHODS.AIN_GET_BLOCK_LIST, params: { from, to, diff --git a/tools/simple-load-test/config.js b/tools/simple-load-test/config.js new file mode 100644 index 000000000..641f71928 --- /dev/null +++ b/tools/simple-load-test/config.js @@ -0,0 +1,6 @@ +module.exports = { + appName: 'loadtest', + testPath: `/apps/loadtest`, + ainPrivateKey: 'b22c95ffc4a5c096f7d7d0487ba963ce6ac945bdc91c79b64ce209de289bec96', + ainAddress: '0x00ADEc28B6a845a085e03591bE7550dd68673C1C' +}; diff --git a/tools/simple-load-test/index.js b/tools/simple-load-test/index.js index 34e298705..ea8e1df55 100644 --- a/tools/simple-load-test/index.js +++ b/tools/simple-load-test/index.js @@ -3,17 +3,18 @@ * This tool increases '/apps/loadtest/visit_count' * Usage: 'node index.js --help' */ -const _ = require('lodash'); -const axios = require('axios'); const commandLineArgs = require('command-line-args'); const getUsage = require('command-line-usage'); -const CommonUtil = require('../../common/common-util'); -const {signTx} = require('../util'); +const { signAndSendTx } = require('../util'); +const { + appName, + testPath, + ainPrivateKey, + ainAddress +} = require('./config'); + const delay = (time) => new Promise((resolve) => setTimeout(resolve, time)); -const testPath = '/apps/loadtest'; -const ainPrivateKey = '4207f5dcacb1b601d3a1f8cb10afaca158f6ebe383c0b30d02b39f8d2060cce3'; -const ainAddress = '0xF2be7f1356347a8960630c112AcB6Da61eE94632'; -const TIMEOUT_MS = 10 * 1000; + const optionDefinitions = [ { name: 'help', @@ -37,13 +38,14 @@ const optionDefinitions = [ group: 'options', }, { - name: 'number_txs', + name: 'num_txs', alias: 'n', type: Number, - description: 'Number of transactions (Default: 300)', + description: 'transactions per second (Default: 2)', group: 'options', }, ]; + const sections = [ { header: 'AIN Simple load test', @@ -55,124 +57,76 @@ const sections = [ } ]; -function sendTx(endpointUrl, signedTx) { - return axios.post(`${endpointUrl}/json-rpc`, { - method: 'ain_sendSignedTransaction', - params: signedTx, - jsonrpc: '2.0', - id: 0, - }, { - timeout: TIMEOUT_MS, - }).then((result) => { - return _.get(result, 'data.result.result.result', false); - }).catch((err) => { - console.error(err); - return false; - }); -} - -async function initPermission(targetUrl) { - const setOwnerTx = { - operation: { - type: 'SET_OWNER', - ref: testPath, - value: { - '.owner': { - owners: { - '*': { - write_owner: true, - write_rule: true, - write_function: true, - branch_owner: true, - }, - }, - }, - }, - }, - timestamp: Date.now(), - nonce: -1, - }; - const setRuleTx = { +async function initLoadTestApp(targetUrl) { + const setRuleTxBody = { operation: { type: 'SET_RULE', - ref: testPath, + ref: `/apps/${appName}`, value: { '.rule': { 'write': true, - } + }, }, }, timestamp: Date.now(), + gas_price: 500, nonce: -1, }; - const setValueTx = { + + const createAppTxBody = { operation: { type: 'SET_VALUE', - ref: testPath, - value: 0, + ref: `/manage_app/${appName}/create/${Date.now()}`, + value: { + admin: { + [ainAddress]: true, + }, + }, }, timestamp: Date.now(), nonce: -1, + gas_price: 500, }; - const {signedTx: signedSetOwnerTx} = CommonUtil.signTransaction(setOwnerTx, ainPrivateKey); - const {signedTx: signedSetRuleTx} = CommonUtil.signTransaction(setRuleTx, ainPrivateKey); - const {signedTx: signedSetValueTx} = CommonUtil.signTransaction(setValueTx, ainPrivateKey); - const promiseList = []; - promiseList.push(sendTx(targetUrl, signedSetOwnerTx)); - promiseList.push(sendTx(targetUrl, signedSetRuleTx)); - promiseList.push(sendTx(targetUrl, signedSetValueTx)); - const resultList = await Promise.all(promiseList); - if (resultList.includes(false)) { - throw Error(`Error while init permission`); - } - await delay(10 * 1000); + + const createAppTxResult = await signAndSendTx(targetUrl, createAppTxBody, ainPrivateKey, 0); + const setRuleTxResult = await signAndSendTx(targetUrl, setRuleTxBody, ainPrivateKey, 0); + console.log(createAppTxResult); + console.log(setRuleTxResult); } -function makeBaseTransaction() { +function buildIncTxBody() { return { operation: { type: 'INC_VALUE', ref: `${testPath}/visit_count`, value: 1, }, + timestamp: Date.now(), nonce: -1, + gas_price: 500 }; } -async function sendTxs(targetUrl, duration, numberOfTransactions) { - const delayTime = duration / numberOfTransactions * 1000; - const sendTxPromiseList = []; - const timestamp = Date.now(); - const baseTx = makeBaseTransaction(); - let sendCnt = 0; - +async function sendTxInSecond(targetUrl, numberOfTransactions) { + const delayMs = 1000 / numberOfTransactions; for (let i = 0; i < numberOfTransactions; i++) { - await delay(delayTime); - if (i % 1000 === 0) { - console.log(`[${i}/${numberOfTransactions}]`); - } + const result = signAndSendTx(targetUrl, buildIncTxBody(), ainPrivateKey, 0); + await delay(delayMs); + } + return numberOfTransactions; +} - sendTxPromiseList.push( - new Promise((resolve, reject) => { - setTimeout((txTimestamp) => { - baseTx.timestamp = txTimestamp; - const {signedTx} = CommonUtil.signTransaction(baseTx, ainPrivateKey); - sendTx(targetUrl, signedTx).then((result) => { - if (result === true) { - sendCnt++; - } - resolve(result); - }).catch((err) => { - console.error(err); - resolve(false); - }); - }, 0, timestamp + i); - }), - ); +async function runLoadtest(targetUrl, numberOfTransactionsInSecond, duration) { + if (numberOfTransactionsInSecond < 0 || duration < 0) { + return; } - await Promise.all(sendTxPromiseList); - return sendCnt; + let count = 0; + for (let i = 0; i < duration; i++) { + const tmpCount = await sendTxInSecond(targetUrl, numberOfTransactionsInSecond); + count += tmpCount; + } + return count; } async function main() { @@ -183,13 +137,16 @@ async function main() { process.exit(0); } const targetUrl = args.target_url || 'http://localhost:8081'; - const duration = args.duration || 60; // 60: 1min - const numberOfTransactions = args.number_txs || 300; - console.log(`Initialize permission (${testPath})`); - await initPermission(targetUrl); - console.log(`Start to send transactions (${numberOfTransactions})`); - const sendCnt = await sendTxs(targetUrl, duration, numberOfTransactions); - console.log(`Finish load test! (${sendCnt}/${numberOfTransactions})`); + const duration = args.duration || 60; // 1 minute by default + const numberOfTransactionsInSecond = args.num_txs || 2; + console.log(`Initialize loadTestApp (${testPath})`); + await initLoadTestApp(targetUrl); + const total = await runLoadtest(targetUrl, numberOfTransactionsInSecond, duration); + + console.log('===========================REPORT==========================='); + console.log(`Target TPS: ${numberOfTransactionsInSecond}`); + console.log(`TXS(sent/target): ${total}/${duration * numberOfTransactionsInSecond} in ${duration} seconds`); + console.log(`TPS: ${total / duration}`); } main(); diff --git a/tools/util.js b/tools/util.js index 51628539f..33a977fe1 100644 --- a/tools/util.js +++ b/tools/util.js @@ -2,6 +2,7 @@ const _ = require('lodash'); const axios = require('axios'); const { BlockchainConsts } = require('../common/constants'); const CommonUtil = require('../common/common-util'); +const { JSON_RPC_METHODS } = require('../json_rpc/constants'); // FIXME(minsulee2): this is duplicated function see: ./common/network-util.js function signAndSendTx(endpointUrl, txBody, privateKey, chainId) { @@ -14,7 +15,7 @@ function signAndSendTx(endpointUrl, txBody, privateKey, chainId) { return axios.post( `${endpointUrl}/json-rpc`, { - method: 'ain_sendSignedTransaction', + method: JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, params: signedTx, jsonrpc: '2.0', id: 0 @@ -35,7 +36,7 @@ async function sendGetTxByHashRequest(endpointUrl, txHash) { return await axios.post( `${endpointUrl}/json-rpc`, { - method: 'ain_getTransactionByHash', + method: JSON_RPC_METHODS.AIN_GET_TRANSACTION_BY_HASH, params: { protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, hash: txHash, diff --git a/tracker-server/example.json b/tracker-server/example.json new file mode 100644 index 000000000..fd1b10aac --- /dev/null +++ b/tracker-server/example.json @@ -0,0 +1,49 @@ +{ + "nodes": [ + { + "address": "0x000" + }, + { + "address": "0x001" + }, + { + "address": "0x002" + }, + { + "address": "0x003" + }, + { + "address": "foo" + }, + { + "address": "bar" + } + ], + "links": [ + { + "source": 0, + "target": 1, + "weight": 1 + }, + { + "source": 1, + "target": 2, + "weight": 1 + }, + { + "source": 2, + "target": 3, + "weight": 1 + }, + { + "source": 4, + "target": 2, + "weight": 1 + }, + { + "source": 5, + "target": 1, + "weight": 1 + } + ] +} \ No newline at end of file diff --git a/tracker-server/index.html b/tracker-server/index.html index 8feb4343d..d0ab0de38 100644 --- a/tracker-server/index.html +++ b/tracker-server/index.html @@ -5,73 +5,96 @@ - \ No newline at end of file diff --git a/tracker-server/index.js b/tracker-server/index.js index 06d551990..06adac533 100755 --- a/tracker-server/index.js +++ b/tracker-server/index.js @@ -57,10 +57,9 @@ app.get('/network_status', (req, res, next) => { }); app.get('/network_topology', (req, res) => { - res.render(__dirname + '/index.html', {}, (err, html) => { - const networkStatus = tracker.getNetworkStatus(); - const graphData = getGraphData(networkStatus); - html = html.replace(/{ \/\* replace this \*\/ };/g, JSON.stringify(graphData)); + const networkStatus = tracker.getNetworkStatus(); + const graphData = getGraphData(networkStatus); + res.render(__dirname + '/index.html', { data: JSON.stringify(graphData) }, (err, html) => { res.send(html); }); });