diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index 40969ecd8..b291ab59c 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -14,13 +14,11 @@ on: types: [opened, synchronize, reopened, closed] jobs: build_and_test: - if: ${{ github.event_name == 'pull_request' && github.event.action == 'opened' }} + if: ${{ github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'edited') }} runs-on: macos-latest steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v2 - with: - ref: ${{ github.event.pull_request.head.sha }} # Setup node environment for testing - uses: actions/setup-node@v2 with: @@ -30,9 +28,22 @@ jobs: run: yarn install - name: run unittest run: yarn run test_unit - - name: run integration test - if: github.event.pull_request.base.ref == 'master' # integration test only run when master merging - run: yarn run test_integration + # 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 diff --git a/README.md b/README.md index fd2e4b1ea..4662fc673 100644 --- a/README.md +++ b/README.md @@ -34,13 +34,13 @@ You can override default port numbering system by setting `PORT` and `P2P_PORT` ``` gcloud init # For one-off deploy -sh deploy_blockchain_gcp.sh {dev|spring|summer} [--setup] +bash deploy_blockchain_gcp.sh {dev|spring|summer} [--setup] # For incremental deploy -sh deploy_blockchain_incremental_gcp.sh {dev|staging|spring|summer} [--setup] +bash deploy_blockchain_incremental_gcp.sh {dev|staging|spring|summer} [--setup] ``` - Set up Ubuntu machine (if it's on a new VM) ``` -sh setup_blockchain_ubuntu.sh +bash setup_blockchain_ubuntu.sh ``` - Copy files to a sharable folder & install yarn packages ``` @@ -49,7 +49,7 @@ source setup_tracker_gcp.sh - Start tracker server job ``` cd ain-blockchain/ -sh start_tracker_genesis_gcp.sh +bash start_tracker_genesis_gcp.sh ``` Shutting down server[0]...`); + server1_proc.kill(); + await waitForNewBlocks(server2, sharding.reporting_period); + console.log(` --> Restarting server[0]...`); + server1_proc = startServer(APP_SERVER, 'server1', ENV_VARIABLES[2]); + await waitForNewBlocks(server2, sharding.reporting_period * 2); + await waitUntilNodeSyncs(server1); + await waitForNewBlocks(server1, sharding.reporting_period); + const reportsAfter = parseOrLog(syncRequest( + 'GET', parentServer + `/get_value?ref=${sharding.sharding_path}/.shard/proof_hash_map`) + .body.toString('utf-8')); + let blockNumber = 0; + const sortedReports = _.without( + Object.keys(reportsAfter.result), 'latest').sort((a, b) => Number(a) - Number(b)); + for (const key of sortedReports) { + expect(blockNumber).to.equal(Number(key)); + blockNumber++; + } + expect(reportsAfter.result.latest).to.be.greaterThan(reportsBefore.result.latest); + }); + }); + }); +}) diff --git a/integration/node.test.js b/integration/node.test.js index 1c1895624..d3f7aeb26 100644 --- a/integration/node.test.js +++ b/integration/node.test.js @@ -13,50 +13,52 @@ const APP_SERVER = PROJECT_ROOT + "client/index.js" const { CURRENT_PROTOCOL_VERSION, CHAINS_DIR, - HASH_DELIMITER, FunctionResultCode, GenesisAccounts, - ProofProperties, TX_BYTES_LIMIT, BATCH_TX_LIST_SIZE_LIMIT, TX_POOL_SIZE_LIMIT_PER_ACCOUNT, MICRO_AIN, } = require('../common/constants'); const CommonUtil = require('../common/common-util'); +const PathUtil = require('../common/path-util'); const { waitUntilTxFinalized, parseOrLog, setUpApp, getLastBlockNumber, + waitUntilNetworkIsReady, waitForNewBlocks, getBlockByNumber, + eraseStateGas, } = require('../unittest/test-util'); +const DB = require('../db'); const ENV_VARIABLES = [ { - MIN_NUM_VALIDATORS: 4, ACCOUNT_INDEX: 0, EPOCH_MS: 1000, DEBUG: false, - CONSOLE_LOG: false, ENABLE_DEV_SET_CLIENT_API: true, ENABLE_GAS_FEE_WORKAROUND: true, + MIN_NUM_VALIDATORS: 4, ACCOUNT_INDEX: 0, DEBUG: false, CONSOLE_LOG: false, + ENABLE_DEV_SET_CLIENT_API: true, ENABLE_GAS_FEE_WORKAROUND: true, MAX_BLOCK_NUMBERS_FOR_RECEIPTS: 100, ADDITIONAL_OWNERS: 'test:unittest/data/owners_for_testing.json', ADDITIONAL_RULES: 'test:unittest/data/rules_for_testing.json' }, { - MIN_NUM_VALIDATORS: 4, ACCOUNT_INDEX: 1, EPOCH_MS: 1000, DEBUG: false, - CONSOLE_LOG: false, ENABLE_DEV_SET_CLIENT_API: true, ENABLE_GAS_FEE_WORKAROUND: true, + MIN_NUM_VALIDATORS: 4, ACCOUNT_INDEX: 1, DEBUG: false, CONSOLE_LOG: false, + ENABLE_DEV_SET_CLIENT_API: true, ENABLE_GAS_FEE_WORKAROUND: true, MAX_BLOCK_NUMBERS_FOR_RECEIPTS: 100, ADDITIONAL_OWNERS: 'test:unittest/data/owners_for_testing.json', ADDITIONAL_RULES: 'test:unittest/data/rules_for_testing.json' }, { - MIN_NUM_VALIDATORS: 4, ACCOUNT_INDEX: 2, EPOCH_MS: 1000, DEBUG: false, - CONSOLE_LOG: false, ENABLE_DEV_SET_CLIENT_API: true, ENABLE_GAS_FEE_WORKAROUND: true, + MIN_NUM_VALIDATORS: 4, ACCOUNT_INDEX: 2, DEBUG: false, CONSOLE_LOG: false, + ENABLE_DEV_SET_CLIENT_API: true, ENABLE_GAS_FEE_WORKAROUND: true, MAX_BLOCK_NUMBERS_FOR_RECEIPTS: 100, ADDITIONAL_OWNERS: 'test:unittest/data/owners_for_testing.json', ADDITIONAL_RULES: 'test:unittest/data/rules_for_testing.json' }, { - MIN_NUM_VALIDATORS: 4, ACCOUNT_INDEX: 3, EPOCH_MS: 1000, DEBUG: false, - CONSOLE_LOG: false, ENABLE_DEV_SET_CLIENT_API: true, ENABLE_GAS_FEE_WORKAROUND: true, + MIN_NUM_VALIDATORS: 4, ACCOUNT_INDEX: 3, DEBUG: false, CONSOLE_LOG: false, + ENABLE_DEV_SET_CLIENT_API: true, ENABLE_GAS_FEE_WORKAROUND: true, MAX_BLOCK_NUMBERS_FOR_RECEIPTS: 100, ADDITIONAL_OWNERS: 'test:unittest/data/owners_for_testing.json', ADDITIONAL_RULES: 'test:unittest/data/rules_for_testing.json' @@ -160,22 +162,27 @@ async function cleanUp() { op_list: [ { type: 'SET_VALUE', - ref: '/apps/test/test_value/some/path', + ref: '/apps/test/test_value', + value: null + }, + { + type: 'SET_VALUE', + ref: '/apps/test/test_state_info', value: null }, { type: 'SET_RULE', - ref: '/apps/test/test_rule/some/path', + ref: '/apps/test/test_rule', value: null }, { type: 'SET_FUNCTION', - ref: '/apps/test/test_function/some/path', + ref: '/apps/test/test_function', value: null }, { type: 'SET_OWNER', - ref: '/apps/test/test_owner/some/path', + ref: '/apps/test/test_owner', value: null }, ], @@ -195,16 +202,15 @@ describe('Blockchain Node', () => { rimraf.sync(CHAINS_DIR) tracker_proc = startServer(TRACKER_SERVER, 'tracker server', { CONSOLE_LOG: false }, true); - await CommonUtil.sleep(2000); + await CommonUtil.sleep(3000); server1_proc = startServer(APP_SERVER, 'server1', ENV_VARIABLES[0], true); - await CommonUtil.sleep(2000); + await CommonUtil.sleep(10000); server2_proc = startServer(APP_SERVER, 'server2', ENV_VARIABLES[1], true); - await CommonUtil.sleep(2000); + await CommonUtil.sleep(3000); server3_proc = startServer(APP_SERVER, 'server3', ENV_VARIABLES[2], true); - await CommonUtil.sleep(2000); + await CommonUtil.sleep(3000); server4_proc = startServer(APP_SERVER, 'server4', ENV_VARIABLES[3], true); - await CommonUtil.sleep(2000); - + await waitUntilNetworkIsReady(serverList); const server1Addr = parseOrLog(syncRequest( 'GET', server1 + '/get_address').body.toString('utf-8')).result; @@ -502,25 +508,13 @@ describe('Blockchain Node', () => { it('get_state_proof', () => { const body = parseOrLog(syncRequest('GET', server1 + '/get_state_proof?ref=/') .body.toString('utf-8')); - const ownersBody = parseOrLog(syncRequest('GET', server1 + `/get_state_proof?ref=/owners`) - .body.toString('utf-8')); - const rulesBody = parseOrLog(syncRequest('GET', server1 + `/get_state_proof?ref=/rules`) - .body.toString('utf-8')); - const valuesBody = parseOrLog(syncRequest('GET', server1 + `/get_state_proof?ref=/values`) - .body.toString('utf-8')); - const functionsBody = parseOrLog(syncRequest( - 'GET', server1 + `/get_state_proof?ref=/functions`) - .body.toString('utf-8')); - const ownersProof = ownersBody.result.owners[ProofProperties.PROOF_HASH]; - const rulesProof = rulesBody.result.rules[ProofProperties.PROOF_HASH]; - const valuesProof = valuesBody.result.values[ProofProperties.PROOF_HASH]; - const functionProof = functionsBody.result.functions[ProofProperties.PROOF_HASH]; - const preimage = `owners${HASH_DELIMITER}${ownersProof}${HASH_DELIMITER}` + - `rules${HASH_DELIMITER}${rulesProof}${HASH_DELIMITER}` + - `values${HASH_DELIMITER}${valuesProof}${HASH_DELIMITER}` + - `functions${HASH_DELIMITER}${functionProof}`; - const proofHash = CommonUtil.hashString(CommonUtil.toString(preimage)); - assert.deepEqual(body, { code: 0, result: { '.proof_hash': proofHash } }); + body.result['.proof_hash'] = 'erased'; + assert.deepEqual(body, { + "code": 0, + "result": { + ".proof_hash": "erased" + } + }); }); }); @@ -531,12 +525,13 @@ describe('Blockchain Node', () => { .body.toString('utf-8')); // Erase some properties for stable comparison. infoBody.result.tree_bytes = 0; + infoBody.result.proof_hash = 'erased'; infoBody.result.version = 'erased'; assert.deepEqual( infoBody, { code: 0, result: { - "proof_hash": "0x972cc2f16c7b20173eb9426d2698459a9351d38b5bca7d2af70124cd617bbeac", + "proof_hash": "erased", "tree_bytes": 0, "tree_height": 2, "tree_size": 5, @@ -551,9 +546,9 @@ describe('Blockchain Node', () => { 'GET', server1 + `/get_state_usage?app_name=test`) .body.toString('utf-8')); assert.deepEqual(body.result, { - "tree_height": 17, - "tree_size": 52, - "tree_bytes": 10076, + "tree_height": 23, + "tree_size": 62, + "tree_bytes": 11966, }); }); }); @@ -693,30 +688,14 @@ describe('Blockchain Node', () => { describe('ain_getStateProof', () => { it('returns correct value', () => { - const ownersBody = parseOrLog(syncRequest('GET', server1 + `/get_state_proof?ref=/owners`) - .body.toString('utf-8')); - const rulesBody = parseOrLog(syncRequest('GET', server1 + `/get_state_proof?ref=/rules`) - .body.toString('utf-8')); - const valuesBody = parseOrLog(syncRequest('GET', server1 + `/get_state_proof?ref=/values`) - .body.toString('utf-8')); - const functionsBody = parseOrLog(syncRequest( - 'GET', server1 + `/get_state_proof?ref=/functions`) - .body.toString('utf-8')); - const ownersProof = ownersBody.result.owners[ProofProperties.PROOF_HASH]; - const rulesProof = rulesBody.result.rules[ProofProperties.PROOF_HASH]; - const valuesProof = valuesBody.result.values[ProofProperties.PROOF_HASH]; - const functionProof = functionsBody.result.functions[ProofProperties.PROOF_HASH]; - const preimage = `owners${HASH_DELIMITER}${ownersProof}${HASH_DELIMITER}` + - `rules${HASH_DELIMITER}${rulesProof}${HASH_DELIMITER}` + - `values${HASH_DELIMITER}${valuesProof}${HASH_DELIMITER}` + - `functions${HASH_DELIMITER}${functionProof}`; - const proofHash = CommonUtil.hashString(CommonUtil.toString(preimage)); - const ref = '/'; const request = { ref, protoVer: CURRENT_PROTOCOL_VERSION }; return jayson.client.http(server1 + '/json-rpc').request('ain_getStateProof', request) .then(res => { - assert.deepEqual(res.result.result, { '.proof_hash': proofHash }); + res.result.result['.proof_hash'] = 'erased'; + assert.deepEqual(res.result.result, { + ".proof_hash": "erased" + }); }) }) }) @@ -730,9 +709,10 @@ describe('Blockchain Node', () => { const stateInfo = res.result.result; // Erase some properties for stable comparison. stateInfo.tree_bytes = 0; + stateInfo.proof_hash = 'erased'; stateInfo.version = 'erased'; assert.deepEqual(stateInfo, { - "proof_hash": "0x972cc2f16c7b20173eb9426d2698459a9351d38b5bca7d2af70124cd617bbeac", + "proof_hash": "erased", "tree_height": 2, "tree_size": 5, "tree_bytes": 0, @@ -749,9 +729,9 @@ describe('Blockchain Node', () => { .then(res => { const stateUsage = res.result.result; assert.deepEqual(stateUsage, { - "tree_height": 17, - "tree_size": 52, - "tree_bytes": 10076, + "tree_height": 23, + "tree_size": 62, + "tree_bytes": 11966, }); }) }) @@ -956,7 +936,7 @@ describe('Blockchain Node', () => { it('inc_value', async () => { // Check the original value. const resultBefore = parseOrLog(syncRequest( - 'GET', server1 + '/get_value?ref=/apps/some/wrong/path2') + 'GET', server1 + '/get_value?ref=/apps/test/test_value/some/path2') .body.toString('utf-8')).result; assert.deepEqual(resultBefore, null); @@ -1493,7 +1473,7 @@ describe('Blockchain Node', () => { "bandwidth_gas_amount": 1 }, }, - "gas_amount_charged": 1552, + "gas_amount_charged": 0, "gas_amount_total": { "bandwidth": { "app": { @@ -1503,9 +1483,9 @@ describe('Blockchain Node', () => { }, "state": { "app": { - "test": 3070 + "test": 4622 }, - "service": 1552 + "service": 0 } }, "gas_cost_total": 0 @@ -1801,6 +1781,9 @@ describe('Blockchain Node', () => { expect(CommonUtil.isArray(body.result)).to.equal(true); for (let i = 0; i < body.result.length; i++) { const result = body.result[i]; + if (!(await waitUntilTxFinalized(serverList, result.tx_hash))) { + console.error(`Failed to check finalization of tx.`); + } result.tx_hash = 'erased'; } assert.deepEqual(body.result, [ @@ -1878,7 +1861,7 @@ describe('Blockchain Node', () => { "result": { "code": 0, "bandwidth_gas_amount": 1, - "gas_amount_charged": 1552, + "gas_amount_charged": 0, "gas_amount_total": { "bandwidth": { "service": 0, @@ -1887,7 +1870,10 @@ describe('Blockchain Node', () => { } }, "state": { - "service": 1552 + "app": { + "test": 1552 + }, + "service": 0 } }, "gas_cost_total": 0 @@ -1968,7 +1954,7 @@ describe('Blockchain Node', () => { "bandwidth_gas_amount": 1 } }, - "gas_amount_charged": 1552, + "gas_amount_charged": 0, "gas_amount_total": { "bandwidth": { "service": 0, @@ -1978,9 +1964,9 @@ describe('Blockchain Node', () => { }, "state": { "app": { - "test": 3070 + "test": 4622 }, - "service": 1552 + "service": 0 } }, "gas_cost_total": 0 @@ -1990,7 +1976,6 @@ describe('Blockchain Node', () => { expect(body.code).to.equal(0); // Confirm that the value is set properly. - await CommonUtil.sleep(6); const resultAfter = parseOrLog(syncRequest( 'GET', server1 + '/get_value?ref=/apps/test/test_value/some200/path') .body.toString('utf-8')).result; @@ -2025,7 +2010,7 @@ describe('Blockchain Node', () => { value: "some other202 value", }, timestamp: Date.now(), - nonce: nonce + nonce: -1 }, { operation: { @@ -2034,7 +2019,7 @@ describe('Blockchain Node', () => { value: 10 }, timestamp: Date.now(), - nonce: nonce + 1 + nonce: -1 }, { operation: { @@ -2043,7 +2028,7 @@ describe('Blockchain Node', () => { value: 10 }, timestamp: Date.now(), - nonce: nonce + 2 + nonce: -1 }, { operation: { @@ -2052,7 +2037,7 @@ describe('Blockchain Node', () => { value: "some other202 value", }, timestamp: Date.now(), - nonce: nonce + 3 + nonce: -1 }, { operation: { @@ -2070,7 +2055,7 @@ describe('Blockchain Node', () => { } }, timestamp: Date.now(), - nonce: nonce + 3 + nonce: -1 }, { operation: { @@ -2083,7 +2068,7 @@ describe('Blockchain Node', () => { } }, timestamp: Date.now(), - nonce: nonce + 4 + nonce: -1 }, { operation: { @@ -2103,7 +2088,7 @@ describe('Blockchain Node', () => { } }, timestamp: Date.now(), - nonce: nonce + 5 + nonce: -1 }, { operation: { @@ -2166,7 +2151,7 @@ describe('Blockchain Node', () => { ] }, timestamp: Date.now(), - nonce: nonce + 6 + nonce: -1 } ] }; @@ -2176,6 +2161,9 @@ describe('Blockchain Node', () => { expect(CommonUtil.isArray(body.result)).to.equal(true); for (let i = 0; i < body.result.length; i++) { const result = body.result[i]; + if (!(await waitUntilTxFinalized(serverList, result.tx_hash))) { + console.error(`Failed to check finalization of tx.`); + } result.tx_hash = 'erased'; } assert.deepEqual(body.result, [ @@ -2274,7 +2262,7 @@ describe('Blockchain Node', () => { "result": { "code": 0, "bandwidth_gas_amount": 1, - "gas_amount_charged": 1552, + "gas_amount_charged": 0, "gas_amount_total": { "bandwidth": { "service": 0, @@ -2283,7 +2271,10 @@ describe('Blockchain Node', () => { } }, "state": { - "service": 1552 + "app": { + "test": 1552 + }, + "service": 0 } }, "gas_cost_total": 0 @@ -2364,7 +2355,7 @@ describe('Blockchain Node', () => { "bandwidth_gas_amount": 1 } }, - "gas_amount_charged": 1552, + "gas_amount_charged": 0, "gas_amount_total": { "bandwidth": { "service": 0, @@ -2374,9 +2365,9 @@ describe('Blockchain Node', () => { }, "state": { "app": { - "test": 3070 + "test": 4622 }, - "service": 1552 + "service": 0 } }, "gas_cost_total": 0 @@ -2386,7 +2377,6 @@ describe('Blockchain Node', () => { expect(body.code).to.equal(0); // Confirm that the value is set properly. - await CommonUtil.sleep(6); const resultAfter = parseOrLog(syncRequest( 'GET', server1 + '/get_value?ref=/apps/test/test_value/some202/path') .body.toString('utf-8')).result; @@ -3209,6 +3199,7 @@ describe('Blockchain Node', () => { const res = parseOrLog(syncRequest('POST', server2 + '/set', { json: { op_list: [ + // allowed_path_with_fid { type: 'SET_FUNCTION', ref: '/apps/test/test_function_triggering/allowed_path_with_fid/value', @@ -3239,6 +3230,7 @@ describe('Blockchain Node', () => { } } }, + // not_allowed_path_with_fid { type: 'SET_FUNCTION', ref: '/apps/test/test_function_triggering/not_allowed_path_with_fid/value', @@ -3269,6 +3261,7 @@ describe('Blockchain Node', () => { } } }, + // allowed_path_with_fids { type: 'SET_FUNCTION', ref: '/apps/test/test_function_triggering/allowed_path_with_fids/value', @@ -3299,6 +3292,7 @@ describe('Blockchain Node', () => { } } }, + // not_allowed_path_with_fids { type: 'SET_FUNCTION', ref: '/apps/test/test_function_triggering/not_allowed_path_with_fids/value', @@ -3329,6 +3323,7 @@ describe('Blockchain Node', () => { } } }, + // set_owner_allowed_path_with_fid { type: 'SET_FUNCTION', ref: '/apps/test/test_function_triggering/set_owner_allowed_path_with_fid/value', @@ -3363,6 +3358,7 @@ describe('Blockchain Node', () => { } } }, + // set_owner_not_allowed_path_with_fid { type: 'SET_FUNCTION', ref: '/apps/test/test_function_triggering/set_owner_not_allowed_path_with_fid/value', @@ -3397,6 +3393,7 @@ describe('Blockchain Node', () => { } } }, + // rest_function_path { type: 'SET_FUNCTION', ref: '/apps/test/test_function_triggering/rest_function_path', @@ -3435,75 +3432,25 @@ describe('Blockchain Node', () => { json: { op_list: [ { - type: 'SET_FUNCTION', - ref: '/apps/test/test_function_triggering/allowed_path_with_fid/value', - value: null - }, - { - type: 'SET_RULE', - ref: '/apps/test/test_function_triggering/allowed_path_with_fid/value', - value: null - }, - { - type: 'SET_FUNCTION', - ref: '/apps/test/test_function_triggering/not_allowed_path_with_fid/value', - value: null - }, - { - type: 'SET_RULE', - ref: '/apps/test/test_function_triggering/not_allowed_path_with_fid/value', - value: null - }, - { - type: 'SET_FUNCTION', - ref: '/apps/test/test_function_triggering/allowed_path_with_fids/value', - value: null - }, - { - type: 'SET_RULE', - ref: '/apps/test/test_function_triggering/allowed_path_with_fids/value', - value: null - }, - { - type: 'SET_FUNCTION', - ref: '/apps/test/test_function_triggering/not_allowed_path_with_fids/value', + type: 'SET_VALUE', + ref: '/apps/test/test_function_triggering', value: null }, { type: 'SET_RULE', - ref: '/apps/test/test_function_triggering/not_allowed_path_with_fids/value', - value: null - }, - { - type: 'SET_FUNCTION', - ref: '/apps/test/test_function_triggering/set_owner_allowed_path_with_fid/value', - value: null - }, - { - type: 'SET_OWNER', - ref: '/apps/test/test_function_triggering/set_owner_allowed_path_with_fid', + ref: '/apps/test/test_function_triggering', value: null }, { type: 'SET_FUNCTION', - ref: '/apps/test/test_function_triggering/set_owner_not_allowed_path_with_fid/value', + ref: '/apps/test/test_function_triggering', value: null }, { type: 'SET_OWNER', - ref: '/apps/test/test_function_triggering/set_owner_not_allowed_path_with_fid', + ref: '/apps/test/test_function_triggering', value: null }, - { - type: 'SET_FUNCTION', - ref: '/apps/test/test_function_triggering/rest_function_path', - value: null, - }, - { - type: 'SET_RULE', - ref: '/apps/test/test_function_triggering/rest_function_path', - value: null, - }, ], nonce: -1, } @@ -3612,7 +3559,7 @@ describe('Blockchain Node', () => { "code": 0, "func_results": { "_saveLastTx": { - "code": "FAILURE", + "code": 1, "bandwidth_gas_amount": 0, "op_results": { "0": { @@ -3666,7 +3613,7 @@ describe('Blockchain Node', () => { "code": 0, "func_results": { "_saveLastTx": { - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, "op_results": { "0": { @@ -3680,7 +3627,7 @@ describe('Blockchain Node', () => { } }, "bandwidth_gas_amount": 1, - "gas_amount_charged": 0, + "gas_amount_charged": 8, "gas_amount_total": { "bandwidth": { "app": { @@ -3690,9 +3637,9 @@ describe('Blockchain Node', () => { }, "state": { "app": { - "test": 1252 + "test": 1412 }, - "service": 0 + "service": 8 } }, "gas_cost_total": 0, @@ -3718,7 +3665,7 @@ describe('Blockchain Node', () => { "code": 0, "func_results": { "_saveLastTx": { - "code": "FAILURE", + "code": 1, "bandwidth_gas_amount": 0, "op_results": { "0": { @@ -3769,7 +3716,7 @@ describe('Blockchain Node', () => { "code": 0, "func_results": { "_saveLastTx": { - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, "op_results": { "0": { @@ -3783,7 +3730,7 @@ describe('Blockchain Node', () => { } }, "bandwidth_gas_amount": 1, - "gas_amount_charged": 0, + "gas_amount_charged": 8, "gas_amount_total": { "bandwidth": { "app": { @@ -3793,9 +3740,9 @@ describe('Blockchain Node', () => { }, "state": { "app": { - "test": 1046 + "test": 1414 }, - "service": 0 + "service": 8 } }, "gas_cost_total": 0, @@ -3824,7 +3771,7 @@ describe('Blockchain Node', () => { "code": 0, "func_results": { "_setOwnerConfig": { - "code": "FAILURE", + "code": 1, "bandwidth_gas_amount": 0, "op_results": { "0": { @@ -3875,7 +3822,7 @@ describe('Blockchain Node', () => { "code": 0, "func_results": { "_setOwnerConfig": { - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, "op_results": { "0": { @@ -3889,7 +3836,7 @@ describe('Blockchain Node', () => { } }, "bandwidth_gas_amount": 1, - "gas_amount_charged": 0, + "gas_amount_charged": 8, "gas_amount_total": { "bandwidth": { "app": { @@ -3899,9 +3846,9 @@ describe('Blockchain Node', () => { }, "state": { "app": { - "test": 2832 + "test": 3200 }, - "service": 0 + "service": 8 } }, "gas_cost_total": 0, @@ -3920,6 +3867,18 @@ describe('Blockchain Node', () => { }); describe('Function execution', () => { + before(async () => { + const appStakingPath = + `/staking/test/${serviceAdmin}/0/stake/${Date.now()}/value`; + const appStakingRes = parseOrLog(syncRequest('POST', server1 + '/set_value', {json: { + ref: appStakingPath, + value: 1 + }}).body.toString('utf-8')).result; + if (!(await waitUntilTxFinalized(serverList, appStakingRes.tx_hash))) { + console.error(`Failed to check finalization of tx.`); + } + }); + describe('/set_value', () => { it("when successful with function triggering", async () => { const valuePath = '/apps/test/test_function_triggering/allowed_path1/value'; @@ -4281,61 +4240,61 @@ describe('Blockchain Node', () => { nonce: -1, timestamp: 1234567890000, }}).body.toString('utf-8')).result; - assert.deepEqual(createAppRes, { - "result": { - "code": 0, - "func_results": { - "_createApp": { - "code": "SUCCESS", - "bandwidth_gas_amount": 0, - "op_results": { - "0": { - "path": "/apps/test_service_create_app0", - "result": { - "code": 0, - "bandwidth_gas_amount": 1 - } - }, - "1": { - "path": "/apps/test_service_create_app0", - "result": { - "code": 0, - "bandwidth_gas_amount": 1 - } - }, - "2": { - "path": "/manage_app/test_service_create_app0/config", - "result": { - "code": 0, - "bandwidth_gas_amount": 1 - } - }, - "3": { - "path": "/manage_app/test_service_create_app0/create/1/result", - "result": { - "code": 0, - "bandwidth_gas_amount": 1 - } + assert.deepEqual(eraseStateGas(createAppRes.result, ['test_service_create_app0']), { + "func_results": { + "_createApp": { + "code": 0, + "bandwidth_gas_amount": 0, + "op_results": { + "0": { + "path": "/apps/test_service_create_app0", + "result": { + "code": 0, + "bandwidth_gas_amount": 1 + } + }, + "1": { + "path": "/apps/test_service_create_app0", + "result": { + "code": 0, + "bandwidth_gas_amount": 1 + } + }, + "2": { + "path": "/manage_app/test_service_create_app0/config", + "result": { + "code": 0, + "bandwidth_gas_amount": 1 + } + }, + "3": { + "path": "/manage_app/test_service_create_app0/create/1/result", + "result": { + "code": 0, + "bandwidth_gas_amount": 1 } } } - }, - "bandwidth_gas_amount": 1, - "gas_amount_charged": 2399, - "gas_amount_total": { - "bandwidth": { - "app": { - "test_service_create_app0": 2 - }, - "service": 3 + } + }, + "code": 0, + "bandwidth_gas_amount": 1, + "gas_amount_charged": 'erased', + "gas_amount_total": { + "bandwidth": { + "app": { + "test_service_create_app0": 2 }, - "state": { - "service": 2396 - } + "service": 3 }, - "gas_cost_total": 0 + "state": { + "app": { + "test_service_create_app0": 'erased' + }, + "service": 'erased' + } }, - "tx_hash": "0x4e2a4bc009347bbaa1a14f1ddecb0f2b06d02d46326d33def7c346c613093079" + "gas_cost_total": 0 }); if (!(await waitUntilTxFinalized(serverList, _.get(createAppRes, 'tx_hash')))) { console.error(`Failed to check finalization of tx.`); @@ -4357,7 +4316,7 @@ describe('Blockchain Node', () => { "code": 0, "func_results": { "_createApp": { - "code": "INVALID_SERVICE_NAME", + "code": 300, "bandwidth_gas_amount": 0, "op_results": { "0": { @@ -4409,63 +4368,63 @@ describe('Blockchain Node', () => { nonce: -1, timestamp: 1234567890000, }}).body.toString('utf-8')).result; - assert.deepEqual(createAppRes, { - "result": { - "code": 0, - "func_results": { - "_createApp": { - "code": "SUCCESS", - "bandwidth_gas_amount": 0, - "op_results": { - "0": { - "path": "/apps/test_service_create_app1", - "result": { - "code": 0, - "bandwidth_gas_amount": 1 - } - }, - "1": { - "path": "/apps/test_service_create_app1", - "result": { - "code": 0, - "bandwidth_gas_amount": 1 - } - }, - "2": { - "path": "/manage_app/test_service_create_app1/config", - "result": { - "code": 0, - "bandwidth_gas_amount": 1 - } - }, - "3": { - "path": "/manage_app/test_service_create_app1/create/0/result", - "result": { - "code": 0, - "bandwidth_gas_amount": 1 - } + assert.deepEqual(eraseStateGas(createAppRes.result, ['test_service_create_app1']), { + "func_results": { + "_createApp": { + "code": 0, + "bandwidth_gas_amount": 0, + "op_results": { + "0": { + "path": "/apps/test_service_create_app1", + "result": { + "code": 0, + "bandwidth_gas_amount": 1 } - } - } - }, - "bandwidth_gas_amount": 1, - "gas_amount_charged": 2763, - "gas_amount_total": { - "bandwidth": { - "app": { - "test_service_create_app1": 2 }, - "service": 3 - }, - "state": { - "service": 2760 - } - }, - "gas_cost_total": 0 - }, - "tx_hash": "0xaa4625dcf4dfa36d6e9a23b64236b88379cac1338d76b915e843fd7cfeda14bb" - }); - if (!(await waitUntilTxFinalized(serverList, createAppRes.tx_hash))) { + "1": { + "path": "/apps/test_service_create_app1", + "result": { + "code": 0, + "bandwidth_gas_amount": 1 + } + }, + "2": { + "path": "/manage_app/test_service_create_app1/config", + "result": { + "code": 0, + "bandwidth_gas_amount": 1 + } + }, + "3": { + "path": "/manage_app/test_service_create_app1/create/0/result", + "result": { + "code": 0, + "bandwidth_gas_amount": 1 + } + } + } + } + }, + "code": 0, + "bandwidth_gas_amount": 1, + "gas_amount_charged": 'erased', + "gas_amount_total": { + "bandwidth": { + "app": { + "test_service_create_app1": 2 + }, + "service": 3 + }, + "state": { + "app": { + "test_service_create_app1": 'erased' + }, + "service": 'erased' + } + }, + "gas_cost_total": 0 + }); + if (!(await waitUntilTxFinalized(serverList, createAppRes.tx_hash))) { console.error(`Failed to check finalization of tx.`); } const appConfig = parseOrLog(syncRequest('GET', @@ -4519,7 +4478,7 @@ describe('Blockchain Node', () => { timestamp: 1234567890000, nonce: -1, }}).body.toString('utf-8')); - assert.deepEqual(_.get(body, 'result.result'), { + assert.deepEqual(eraseStateGas(_.get(body, 'result.result')), { "func_results": { "_transfer": { "op_results": { @@ -4545,19 +4504,19 @@ describe('Blockchain Node', () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 1000 } }, "code": 0, "bandwidth_gas_amount": 1, - "gas_amount_charged": 2860, + "gas_amount_charged": 'erased', "gas_amount_total": { "bandwidth": { "service": 1004 }, "state": { - "service": 1856 + "service": 'erased' } }, "gas_cost_total": 0, @@ -4575,7 +4534,7 @@ describe('Blockchain Node', () => { timestamp: 1234567890000, nonce: -1, }}).body.toString('utf-8')); - assert.deepEqual(_.get(body, 'result.result'), { + assert.deepEqual(eraseStateGas(_.get(body, 'result.result')), { "func_results": { "_transfer": { "op_results": { @@ -4601,19 +4560,19 @@ describe('Blockchain Node', () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0 } }, "code": 0, "bandwidth_gas_amount": 1, - "gas_amount_charged": 1190, + "gas_amount_charged": 'erased', "gas_amount_total": { "bandwidth": { "service": 4 }, "state": { - "service": 1186 + "service": 'erased' } }, "gas_cost_total": 0, @@ -4631,7 +4590,7 @@ describe('Blockchain Node', () => { timestamp: 1234567890000, nonce: -1, }}).body.toString('utf-8')); - assert.deepEqual(_.get(body, 'result.result'), { + assert.deepEqual(eraseStateGas(_.get(body, 'result.result')), { "func_results": { "_stake": { "op_results": { @@ -4663,7 +4622,7 @@ describe('Blockchain Node', () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 1000 } }, @@ -4693,19 +4652,19 @@ describe('Blockchain Node', () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0 } }, "code": 0, "bandwidth_gas_amount": 1, - "gas_amount_charged": 4906, + "gas_amount_charged": 'erased', "gas_amount_total": { "bandwidth": { "service": 1008 }, "state": { - "service": 3898 + "service": 'erased' } }, "gas_cost_total": 0, @@ -4723,7 +4682,7 @@ describe('Blockchain Node', () => { timestamp: 1234567890001, nonce: -1, }}).body.toString('utf-8')); - assert.deepEqual(_.get(body, 'result.result'), { + assert.deepEqual(eraseStateGas(_.get(body, 'result.result')), { "func_results": { "_stake": { "op_results": { @@ -4755,7 +4714,7 @@ describe('Blockchain Node', () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0 } }, @@ -4764,20 +4723,13 @@ describe('Blockchain Node', () => { } }, "1": { - "path": "/staking/test_service_gas_fee/0x01A0980d2D4e418c7F27e1ef539d01A5b5E93204/0/expire_at", - "result": { - "code": 0, - "bandwidth_gas_amount": 1 - } - }, - "2": { "path": "/staking/test_service_gas_fee/balance_total", "result": { "code": 0, "bandwidth_gas_amount": 1 } }, - "3": { + "2": { "path": "/staking/test_service_gas_fee/0x01A0980d2D4e418c7F27e1ef539d01A5b5E93204/0/stake/101/result", "result": { "code": 0, @@ -4785,19 +4737,19 @@ describe('Blockchain Node', () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0 } }, "code": 0, "bandwidth_gas_amount": 1, - "gas_amount_charged": 2408, + "gas_amount_charged": 'erased', "gas_amount_total": { "bandwidth": { - "service": 8 + "service": 7 }, "state": { - "service": 2400 + "service": 'erased' } }, "gas_cost_total": 0, @@ -4816,7 +4768,7 @@ describe('Blockchain Node', () => { assert.deepEqual(_.get(body, 'result.result'), { "func_results": { "0x11111": { - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 10, } }, @@ -4832,7 +4784,7 @@ describe('Blockchain Node', () => { }, "state": { "app": { - "test": 216 + "test": 424 }, "service": 0 } @@ -5000,54 +4952,48 @@ describe('Blockchain Node', () => { nonce: -1, timestamp: 1234567890000, }}).body.toString('utf-8')); - assert.deepEqual(body, { - "code": 0, - "result": { - "result": { + assert.deepEqual(eraseStateGas(_.get(body, 'result.result')), { + "func_results": { + "_transfer": { "code": 0, - "func_results": { - "_transfer": { - "code": "SUCCESS", - "bandwidth_gas_amount": 1000, - "op_results": { - "0": { - "path": "/accounts/0x00ADEc28B6a845a085e03591bE7550dd68673C1C/balance", - "result": { - "code": 0, - "bandwidth_gas_amount": 1 - } - }, - "1": { - "path": "/service_accounts/staking/test_service/0x01A0980d2D4e418c7F27e1ef539d01A5b5E93204|0/balance", - "result": { - "code": 0, - "bandwidth_gas_amount": 1 - } - }, - "2": { - "path": "/transfer/0x00ADEc28B6a845a085e03591bE7550dd68673C1C/staking|test_service|0x01A0980d2D4e418c7F27e1ef539d01A5b5E93204|0/1/result", - "result": { - "code": 0, - "bandwidth_gas_amount": 1 - } - } + "bandwidth_gas_amount": 1000, + "op_results": { + "0": { + "path": "/accounts/0x00ADEc28B6a845a085e03591bE7550dd68673C1C/balance", + "result": { + "code": 0, + "bandwidth_gas_amount": 1 } - } - }, - "bandwidth_gas_amount": 1, - "gas_amount_charged": 3094, - "gas_amount_total": { - "bandwidth": { - "service": 1004 }, - "state": { - "service": 2090 + "1": { + "path": "/service_accounts/staking/test_service/0x01A0980d2D4e418c7F27e1ef539d01A5b5E93204|0/balance", + "result": { + "code": 0, + "bandwidth_gas_amount": 1 + } + }, + "2": { + "path": "/transfer/0x00ADEc28B6a845a085e03591bE7550dd68673C1C/staking|test_service|0x01A0980d2D4e418c7F27e1ef539d01A5b5E93204|0/1/result", + "result": { + "code": 0, + "bandwidth_gas_amount": 1 + } } - }, - "gas_cost_total": 0 + } + } + }, + "code": 0, + "bandwidth_gas_amount": 1, + "gas_amount_charged": 'erased', + "gas_amount_total": { + "bandwidth": { + "service": 1004 }, - "tx_hash": "0x62f01969d903d7a6f184279634249941a2c312e896f045c071afe78ac635fe96" - } + "state": { + "service": 'erased' + } + }, + "gas_cost_total": 0 }); if (!(await waitUntilTxFinalized([server2], _.get(body, 'result.tx_hash')))) { console.error(`Failed to check finalization of tx.`); @@ -5127,11 +5073,10 @@ describe('Blockchain Node', () => { nonce: -1, timestamp: 1234567890000, }}).body.toString('utf-8')); - assert.deepEqual(_.get(body, 'result.result'), { - "code": 0, + assert.deepEqual(eraseStateGas(_.get(body, 'result.result')), { "func_results": { "_stake": { - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, "op_results": { "0": { @@ -5140,7 +5085,7 @@ describe('Blockchain Node', () => { "code": 0, "func_results": { "_transfer": { - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 1000, "op_results": { "0": { @@ -5194,14 +5139,15 @@ describe('Blockchain Node', () => { } } }, + "code": 0, "bandwidth_gas_amount": 1, - "gas_amount_charged": 4902, + "gas_amount_charged": 'erased', "gas_amount_total": { "bandwidth": { "service": 1008 }, "state": { - "service": 3894 + "service": 'erased' } }, "gas_cost_total": 0, @@ -5269,71 +5215,6 @@ describe('Blockchain Node', () => { expect(afterStakingAccountBalance).to.equal(beforeStakingAccountBalance); }); - it('stake: stake with invalid timestamp', async () => { - const account = { - "address": "0x07A43138CC760C85A5B1F115aa60eADEaa0bf417", - "private_key": "0e9876c7e7966fb0237892eb2e890b4738d0e50adfcfe089ef31f5a1579d65cd", - "public_key": "1cc01c94edce1d5807685dc04de0a0e445b560090eb421fc087f95080eb7a12a41145cc17cf4476a1d2ec0c1f737f5d84e5d0fecbfb370869845714e4ecfdd53" - }; - const transferPath = `/transfer/${transferFrom}/${account.address}`; - const body = parseOrLog(syncRequest('POST', server1 + '/set_value', {json: { - ref: transferPath + '/100/value', - value: 1000 - }}).body.toString('utf-8')); - expect(body.code).to.equals(0); - if (!(await waitUntilTxFinalized(serverList, _.get(body, 'result.tx_hash')))) { - console.error(`Failed to check finalization of tx.`); - } - const txBody = { - operation: { - type: 'SET_VALUE', - value: stakeAmount, - ref: `/staking/test_service_staking/${account.address}/0/stake/1/value` - }, - timestamp: Date.now() + 100000, - nonce: 0 - } - const signature = - ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex')); - - const jsonRpcClient = jayson.client.http(server2 + '/json-rpc'); - return jsonRpcClient.request('ain_sendSignedTransaction', { - tx_body: txBody, - signature, - protoVer: CURRENT_PROTOCOL_VERSION - }).then(res => { - assert.deepEqual(_.get(res, 'result.result.result'), { - "code": 0, - "func_results": { - "_stake": { - "code": "FAILURE", - "bandwidth_gas_amount": 0, - "op_results": { - "0": { - "path": "/staking/test_service_staking/0x07A43138CC760C85A5B1F115aa60eADEaa0bf417/0/stake/1/result", - "result": { - "code": 0, - "bandwidth_gas_amount": 1, - } - } - } - } - }, - "bandwidth_gas_amount": 1, - "gas_amount_charged": 2, - "gas_amount_total": { - "bandwidth": { - "service": 2 - }, - "state": { - "service": 0 - } - }, - "gas_cost_total": 0, - }); - }); - }); - it('stake: stake with the same record_id', async () => { const body = parseOrLog(syncRequest('POST', server2 + '/set_value', {json: { ref: stakePath + '/1/value', @@ -5429,11 +5310,10 @@ describe('Blockchain Node', () => { nonce: -1, timestamp: 1234567890000, }}).body.toString('utf-8')); - assert.deepEqual(_.get(body, 'result.result'), { - "code": 0, + assert.deepEqual(eraseStateGas(_.get(body, 'result.result')), { "func_results": { "_unstake": { - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, "op_results": { "0": { @@ -5442,7 +5322,7 @@ describe('Blockchain Node', () => { "code": 0, "func_results": { "_transfer": { - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, "op_results": { "0": { @@ -5489,14 +5369,15 @@ describe('Blockchain Node', () => { } } }, + "code": 0, "bandwidth_gas_amount": 1, - "gas_amount_charged": 3127, + "gas_amount_charged": 'erased', "gas_amount_total": { "bandwidth": { "service": 7 }, "state": { - "service": 3120 + "service": 'erased' } }, "gas_cost_total": 0, @@ -5628,11 +5509,10 @@ describe('Blockchain Node', () => { nonce: -1, timestamp: 1234567890000, }}).body.toString('utf-8')); - assert.deepEqual(_.get(body, 'result.result'), { - "code": 0, + assert.deepEqual(eraseStateGas(_.get(body, 'result.result')), { "func_results": { "_pay": { - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, "op_results": { "0": { @@ -5641,7 +5521,7 @@ describe('Blockchain Node', () => { "code": 0, "func_results": { "_transfer": { - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 1000, "op_results": { "0": { @@ -5681,14 +5561,15 @@ describe('Blockchain Node', () => { } } }, + "code": 0, "bandwidth_gas_amount": 1, - "gas_amount_charged": 5472, + "gas_amount_charged": 'erased', "gas_amount_total": { "bandwidth": { "service": 1006 }, "state": { - "service": 4466 + "service": 'erased' } }, "gas_cost_total": 0, @@ -5775,11 +5656,10 @@ describe('Blockchain Node', () => { nonce: -1, timestamp: 1234567890000, }}).body.toString('utf-8')); - assert.deepEqual(_.get(body, 'result.result'), { - "code": 0, + assert.deepEqual(eraseStateGas(_.get(body, 'result.result')), { "func_results": { "_claim": { - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, "op_results": { "0": { @@ -5788,7 +5668,7 @@ describe('Blockchain Node', () => { "code": 0, "func_results": { "_transfer": { - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, "op_results": { "0": { @@ -5828,14 +5708,15 @@ describe('Blockchain Node', () => { } } }, + "code": 0, "bandwidth_gas_amount": 1, - "gas_amount_charged": 3388, + "gas_amount_charged": 'erased', "gas_amount_total": { "bandwidth": { "service": 6 }, "state": { - "service": 3382 + "service": 'erased' } }, "gas_cost_total": 0, @@ -5998,16 +5879,16 @@ describe('Blockchain Node', () => { nonce: -1, timestamp: 1234567890000, }}).body.toString('utf-8')); - assert.deepEqual(_.get(body, 'result.result'), { + assert.deepEqual(eraseStateGas(_.get(body, 'result.result')), { "code": 0, "bandwidth_gas_amount": 1, - "gas_amount_charged": 1241, + "gas_amount_charged": 'erased', "gas_amount_total": { "bandwidth": { "service": 1 }, "state": { - "service": 1240 + "service": 'erased' } }, "gas_cost_total": 0, @@ -6067,11 +5948,10 @@ describe('Blockchain Node', () => { nonce: -1, timestamp: 1234567890000, }}).body.toString('utf-8')); - assert.deepEqual(_.get(body, 'result.result'), { - "code": 0, + assert.deepEqual(eraseStateGas(_.get(body, 'result.result')), { "func_results": { "_hold": { - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, "op_results": { "0": { @@ -6080,7 +5960,7 @@ describe('Blockchain Node', () => { "code": 0, "func_results": { "_transfer": { - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 1000, "op_results": { "0": { @@ -6120,14 +6000,15 @@ describe('Blockchain Node', () => { } } }, + "code": 0, "bandwidth_gas_amount": 1, - "gas_amount_charged": 4474, + "gas_amount_charged": 'erased', "gas_amount_total": { "bandwidth": { "service": 1006 }, "state": { - "service": 3468 + "service": 'erased' } }, "gas_cost_total": 0, @@ -6206,11 +6087,10 @@ describe('Blockchain Node', () => { nonce: -1, timestamp: 1234567890000, }}).body.toString('utf-8')); - assert.deepEqual(_.get(body, 'result.result'), { - "code": 0, + assert.deepEqual(eraseStateGas(_.get(body, 'result.result')), { "func_results": { "_release": { - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, "op_results": { "0": { @@ -6219,7 +6099,7 @@ describe('Blockchain Node', () => { "code": 0, "func_results": { "_transfer": { - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, "op_results": { "0": { @@ -6259,14 +6139,15 @@ describe('Blockchain Node', () => { } } }, + "code": 0, "bandwidth_gas_amount": 1, - "gas_amount_charged": 3206, + "gas_amount_charged": 'erased', "gas_amount_total": { "bandwidth": { "service": 6 }, "state": { - "service": 3200 + "service": 'erased' } }, "gas_cost_total": 0, @@ -6327,16 +6208,16 @@ describe('Blockchain Node', () => { nonce: -1, timestamp: 1234567890000, }}).body.toString('utf-8')); - assert.deepEqual(_.get(body, 'result.result'), { + assert.deepEqual(eraseStateGas(_.get(body, 'result.result')), { "code": 0, "bandwidth_gas_amount": 1, - "gas_amount_charged": 1303, + "gas_amount_charged": 'erased', "gas_amount_total": { "bandwidth": { "service": 1 }, "state": { - "service": 1302 + "service": 'erased' } }, "gas_cost_total": 0, @@ -6386,11 +6267,10 @@ describe('Blockchain Node', () => { nonce: -1, timestamp: 1234567890000, }}).body.toString('utf-8')); - assert.deepEqual(_.get(body, 'result.result'), { - "code": 0, + assert.deepEqual(eraseStateGas(_.get(body, 'result.result')), { "func_results": { "_hold": { - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, "op_results": { "0": { @@ -6399,7 +6279,7 @@ describe('Blockchain Node', () => { "code": 0, "func_results": { "_transfer": { - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 1000, "op_results": { "0": { @@ -6439,14 +6319,15 @@ describe('Blockchain Node', () => { } } }, + "code": 0, "bandwidth_gas_amount": 1, - "gas_amount_charged": 4904, + "gas_amount_charged": 'erased', "gas_amount_total": { "bandwidth": { "service": 1006 }, "state": { - "service": 3898 + "service": 'erased' } }, "gas_cost_total": 0, @@ -6483,11 +6364,10 @@ describe('Blockchain Node', () => { nonce: -1, timestamp: 1234567890000, }}).body.toString('utf-8')); - assert.deepEqual(_.get(body, 'result.result'), { - "code": 0, + assert.deepEqual(eraseStateGas(_.get(body, 'result.result')), { "func_results": { "_release": { - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, "op_results": { "0": { @@ -6496,7 +6376,7 @@ describe('Blockchain Node', () => { "code": 0, "func_results": { "_transfer": { - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, "op_results": { "0": { @@ -6536,14 +6416,15 @@ describe('Blockchain Node', () => { } } }, + "code": 0, "bandwidth_gas_amount": 1, - "gas_amount_charged": 3330, + "gas_amount_charged": 'erased', "gas_amount_total": { "bandwidth": { "service": 6 }, "state": { - "service": 3324 + "service": 'erased' } }, "gas_cost_total": 0, @@ -6622,73 +6503,722 @@ describe('Blockchain Node', () => { }); }); }); - }); - describe('Billing', async () => { - let serviceAdmin; // = server1 - let billingUserA; // = server2 - let billingUserB; // = server3 - let userBalancePathA; - let userBalancePathB; - const billingAccountBalancePathA = '/get_value?ref=/service_accounts/billing/test_billing/A/balance'; - before(async () => { - serviceAdmin = - parseOrLog(syncRequest('GET', server1 + '/get_address').body.toString('utf-8')).result; - billingUserA = - parseOrLog(syncRequest('GET', server2 + '/get_address').body.toString('utf-8')).result; - billingUserB = - parseOrLog(syncRequest('GET', server3 + '/get_address').body.toString('utf-8')).result; - userBalancePathA = `/get_value?ref=/accounts/${billingUserA}/balance`; - userBalancePathB = `/get_value?ref=/accounts/${billingUserB}/balance`; - const adminConfig = { - [serviceAdmin]: true, - [billingUserA]: true, - [billingUserB]: true - }; - const billingConfig = { - A: { - users: { - [serviceAdmin]: true, - [billingUserA]: true - } - }, - B: { - users: { - [serviceAdmin]: true, - [billingUserB]: true + describe('Checkout: _openCheckout, _closeCheckout', () => { + const client = jayson.client.http(server1 + '/json-rpc'); + const tokenType = 'ETH'; + const tokenId = '0xB16c0C80a81f73204d454426fC413CAe455525A7'; + const tokenBridgeConfig = require('../genesis-configs/base/genesis_token.json')['bridge'][tokenType][tokenId]; + const { + token_pool: tokenPoolAddr, + min_checkout_per_request: minCheckoutPerRequest, + max_checkout_per_request: maxCheckoutPerRequest, + max_checkout_per_day: maxCheckoutPerDay, + } = tokenBridgeConfig; + const checkoutAmount = 100; + const ethAddress = '0x09A0d53FDf1c36A131938eb379b98910e55EEfe1'; + + it('cannot open checkout with invalid params: amount < min_checkout_per_request', async () => { + const beforeBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${serviceUser}/balance`) + .body.toString('utf-8')).result; + const beforeTokenPoolBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${tokenPoolAddr}/balance`).body.toString('utf-8')).result; + const body = parseOrLog(syncRequest('POST', server2 + '/set_value', {json: { + ref: `/checkout/requests/${serviceUser}/0`, + value: { + amount: minCheckoutPerRequest - 1, + type: tokenType, + token_id: tokenId, + recipient: ethAddress } + }}).body.toString('utf-8')); + expect(body.code).to.equal(1); + if (!(await waitUntilTxFinalized(serverList, _.get(body, 'result.tx_hash')))) { + console.error(`Failed to check finalization of tx.`); } - }; - await setUpApp('test_billing', serverList, { admin: adminConfig, billing: billingConfig }); + const checkoutRequest = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/checkout/requests/${serviceUser}/0`).body.toString('utf-8')).result; + const afterRequestUserBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${serviceUser}/balance`).body.toString('utf-8')).result; + const afterRequestTokenPoolBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${tokenPoolAddr}/balance`) + .body.toString('utf-8')).result; + expect(checkoutRequest).to.equal(null); + expect(afterRequestUserBalance).to.equal(beforeBalance); + expect(afterRequestTokenPoolBalance).to.equal(beforeTokenPoolBalance); + }); - const server4Addr = - parseOrLog(syncRequest('GET', server4 + '/get_address').body.toString('utf-8')).result; - const transferRes = parseOrLog(syncRequest('POST', server4 + '/set', {json: { - op_list: [ - { - ref: `/transfer/${server4Addr}/billing|test_billing|A/${Date.now()}/value`, - value: 100, - type: 'SET_VALUE' - }, - { - ref: `/transfer/${server4Addr}/billing|test_billing|B/${Date.now()}/value`, - value: 100, - type: 'SET_VALUE' + it('cannot open checkout with invalid params: amount > max_checkout_per_request', async () => { + const beforeBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${serviceUser}/balance`) + .body.toString('utf-8')).result; + const beforeTokenPoolBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${tokenPoolAddr}/balance`).body.toString('utf-8')).result; + const body = parseOrLog(syncRequest('POST', server2 + '/set_value', {json: { + ref: `/checkout/requests/${serviceUser}/0`, + value: { + amount: maxCheckoutPerRequest + 1, + type: tokenType, + token_id: tokenId, + recipient: ethAddress } - ], - nonce: -1, - timestamp: Date.now(), - }}).body.toString('utf-8')).result; - if (!(await waitUntilTxFinalized(serverList, transferRes.tx_hash))) { - console.error(`Failed to check finalization of transfer tx.`); - } - }); + }}).body.toString('utf-8')); + expect(body.code).to.equal(1); + if (!(await waitUntilTxFinalized(serverList, _.get(body, 'result.tx_hash')))) { + console.error(`Failed to check finalization of tx.`); + } + const checkoutRequest = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/checkout/requests/${serviceUser}/0`).body.toString('utf-8')).result; + const afterRequestUserBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${serviceUser}/balance`).body.toString('utf-8')).result; + const afterRequestTokenPoolBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${tokenPoolAddr}/balance`) + .body.toString('utf-8')).result; + expect(checkoutRequest).to.equal(null); + expect(afterRequestUserBalance).to.equal(beforeBalance); + expect(afterRequestTokenPoolBalance).to.equal(beforeTokenPoolBalance); + }); - it('app txs are not charged by transfer', async () => { - const balanceBefore = parseOrLog(syncRequest('GET', server2 + userBalancePathA).body.toString('utf-8')).result; - const txWithoutBillingRes = parseOrLog(syncRequest('POST', server2 + '/set_value', {json: { - ref: '/apps/test_billing/test', - value: 'testing app tx', + it('cannot open checkout with invalid params: type', async () => { + const beforeBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${serviceUser}/balance`) + .body.toString('utf-8')).result; + const beforeTokenPoolBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${tokenPoolAddr}/balance`).body.toString('utf-8')).result; + const body = parseOrLog(syncRequest('POST', server2 + '/set_value', {json: { + ref: `/checkout/requests/${serviceUser}/0`, + value: { + amount: checkoutAmount, + type: 'AIN', + token_id: tokenId, + recipient: ethAddress + } + }}).body.toString('utf-8')); + expect(body.code).to.equal(1); + if (!(await waitUntilTxFinalized(serverList, _.get(body, 'result.tx_hash')))) { + console.error(`Failed to check finalization of tx.`); + } + const checkoutRequest = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/checkout/requests/${serviceUser}/0`).body.toString('utf-8')).result; + const afterRequestUserBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${serviceUser}/balance`).body.toString('utf-8')).result; + const afterRequestTokenPoolBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${tokenPoolAddr}/balance`) + .body.toString('utf-8')).result; + expect(checkoutRequest).to.equal(null); + expect(afterRequestUserBalance).to.equal(beforeBalance); + expect(afterRequestTokenPoolBalance).to.equal(beforeTokenPoolBalance); + }); + + it('cannot open checkout with invalid params: token_id', async () => { + const beforeBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${serviceUser}/balance`) + .body.toString('utf-8')).result; + const beforeTokenPoolBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${tokenPoolAddr}/balance`).body.toString('utf-8')).result; + const body = parseOrLog(syncRequest('POST', server2 + '/set_value', {json: { + ref: `/checkout/requests/${serviceUser}/0`, + value: { + amount: checkoutAmount, + type: tokenType, + token_id: '', + recipient: ethAddress + } + }}).body.toString('utf-8')); + expect(body.code).to.equal(1); + if (!(await waitUntilTxFinalized(serverList, _.get(body, 'result.tx_hash')))) { + console.error(`Failed to check finalization of tx.`); + } + const checkoutRequest = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/checkout/requests/${serviceUser}/0`).body.toString('utf-8')).result; + const afterRequestUserBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${serviceUser}/balance`).body.toString('utf-8')).result; + const afterRequestTokenPoolBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${tokenPoolAddr}/balance`) + .body.toString('utf-8')).result; + expect(checkoutRequest).to.equal(null); + expect(afterRequestUserBalance).to.equal(beforeBalance); + expect(afterRequestTokenPoolBalance).to.equal(beforeTokenPoolBalance); + }); + + it('cannot open checkout with invalid params: recipient', async () => { + const beforeBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${serviceUser}/balance`) + .body.toString('utf-8')).result; + const beforeTokenPoolBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${tokenPoolAddr}/balance`).body.toString('utf-8')).result; + const body = parseOrLog(syncRequest('POST', server2 + '/set_value', {json: { + ref: `/checkout/requests/${serviceUser}/0`, + value: { + amount: checkoutAmount, + type: tokenType, + token_id: tokenId, + recipient: ethAddress.toLowerCase() + } + }}).body.toString('utf-8')); + expect(body.code).to.equal(1); + if (!(await waitUntilTxFinalized(serverList, _.get(body, 'result.tx_hash')))) { + console.error(`Failed to check finalization of tx.`); + } + const checkoutRequest = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/checkout/requests/${serviceUser}/0`).body.toString('utf-8')).result; + const afterRequestUserBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${serviceUser}/balance`).body.toString('utf-8')).result; + const afterRequestTokenPoolBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${tokenPoolAddr}/balance`) + .body.toString('utf-8')).result; + expect(checkoutRequest).to.equal(null); + expect(afterRequestUserBalance).to.equal(beforeBalance); + expect(afterRequestTokenPoolBalance).to.equal(beforeTokenPoolBalance); + }); + + it('cannot open checkout with insufficient funds', async () => { + const beforeBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${serviceUser}/balance`) + .body.toString('utf-8')).result; + const beforeTokenPoolBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${tokenPoolAddr}/balance`).body.toString('utf-8')).result; + const body = parseOrLog(syncRequest('POST', server2 + '/set_value', {json: { + ref: `/checkout/requests/${serviceUser}/0`, + value: { + amount: beforeBalance + 1, + type: tokenType, + token_id: tokenId, + recipient: ethAddress + } + }}).body.toString('utf-8')); + expect(body.code).to.equal(1); + if (!(await waitUntilTxFinalized(serverList, _.get(body, 'result.tx_hash')))) { + console.error(`Failed to check finalization of tx.`); + } + const checkoutRequest = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/checkout/requests/${serviceUser}/0`).body.toString('utf-8')).result; + const afterRequestUserBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${serviceUser}/balance`).body.toString('utf-8')).result; + const afterRequestTokenPoolBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${tokenPoolAddr}/balance`) + .body.toString('utf-8')).result; + expect(checkoutRequest).to.equal(null); + expect(afterRequestUserBalance).to.equal(beforeBalance); + expect(afterRequestTokenPoolBalance).to.equal(beforeTokenPoolBalance); + }); + + it('can open checkout', async () => { + const beforeBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${serviceUser}/balance`) + .body.toString('utf-8')).result; + const beforeTokenPoolBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${tokenPoolAddr}/balance`).body.toString('utf-8')).result; + const body = parseOrLog(syncRequest('POST', server2 + '/set_value', {json: { + ref: `/checkout/requests/${serviceUser}/0`, + value: { + amount: checkoutAmount, + type: tokenType, + token_id: tokenId, + recipient: ethAddress + }, + timestamp: 1628255843548 + }}).body.toString('utf-8')); + expect(body.code).to.equal(0); + if (!(await waitUntilTxFinalized(serverList, _.get(body, 'result.tx_hash')))) { + console.error(`Failed to check finalization of tx.`); + } + assert.deepEqual(eraseStateGas(_.get(body, 'result.result')), { + "func_results": { + "_openCheckout": { + "op_results": { + "0": { + "path": "/checkout/stats/pending/0x01A0980d2D4e418c7F27e1ef539d01A5b5E93204", + "result": { + "code": 0, + "bandwidth_gas_amount": 1 + } + }, + "1": { + "path": "/checkout/stats/pending/total", + "result": { + "code": 0, + "bandwidth_gas_amount": 1 + } + }, + "2": { + "path": "/transfer/0x01A0980d2D4e418c7F27e1ef539d01A5b5E93204/0x20ADd3d38405ebA6338CB9e57a0510DEB8f8e000/1628255843548/value", + "result": { + "func_results": { + "_transfer": { + "op_results": { + "0": { + "path": "/accounts/0x01A0980d2D4e418c7F27e1ef539d01A5b5E93204/balance", + "result": { + "code": 0, + "bandwidth_gas_amount": 1 + } + }, + "1": { + "path": "/accounts/0x20ADd3d38405ebA6338CB9e57a0510DEB8f8e000/balance", + "result": { + "code": 0, + "bandwidth_gas_amount": 1 + } + }, + "2": { + "path": "/transfer/0x01A0980d2D4e418c7F27e1ef539d01A5b5E93204/0x20ADd3d38405ebA6338CB9e57a0510DEB8f8e000/1628255843548/result", + "result": { + "code": 0, + "bandwidth_gas_amount": 1 + } + } + }, + "code": 0, + "bandwidth_gas_amount": 1000 + } + }, + "code": 0, + "bandwidth_gas_amount": 1 + } + } + }, + "code": 0, + "bandwidth_gas_amount": 0 + } + }, + "code": 0, + "bandwidth_gas_amount": 1, + "gas_amount_charged": 'erased', + "gas_amount_total": { + "bandwidth": { + "service": 1007 + }, + "state": { + "service": 'erased' + } + }, + "gas_cost_total": 0 + }); + const afterRequestUserBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${serviceUser}/balance`).body.toString('utf-8')).result; + const afterRequestTokenPoolBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${tokenPoolAddr}/balance`) + .body.toString('utf-8')).result; + const userPendingAmount = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/checkout/stats/pending/${serviceUser}`) + .body.toString('utf-8')).result; + const totalPendingAmount = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/checkout/stats/pending/total`) + .body.toString('utf-8')).result; + expect(afterRequestUserBalance).to.equal(beforeBalance - checkoutAmount); + expect(afterRequestTokenPoolBalance).to.equal(beforeTokenPoolBalance + checkoutAmount); + expect(userPendingAmount).to.equal(checkoutAmount); + expect(totalPendingAmount).to.equal(checkoutAmount); + }); + + it('cannot close checkout with a non-authorized address', async () => { + const body = parseOrLog(syncRequest('POST', server2 + '/set_value', {json: { + ref: `/checkout/history/${serviceUser}/0`, + value: { + request: { + amount: checkoutAmount, + type: tokenType, + token_id: tokenId, + recipient: ethAddress + }, + response: { + status: 0 + } + } + }}).body.toString('utf-8')); + expect(body.code).to.equal(1); + if (!(await waitUntilTxFinalized(serverList, _.get(body, 'result.tx_hash')))) { + console.error(`Failed to check finalization of tx.`); + } + const checkoutHistory = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/checkout/history/${serviceUser}/0`).body.toString('utf-8')).result; + expect(checkoutHistory).to.equal(null); + }); + + it('can close a successful checkout with token pool key', async () => { + const txBody = { + operation: { + type: 'SET_VALUE', + ref: `/checkout/history/${serviceUser}/0`, + value: { + request: { + amount: checkoutAmount, + type: tokenType, + token_id: tokenId, + recipient: ethAddress + }, + response: { + status: 0, + tx_hash: '0x6af1ec8d4f0a55bac328cb20336ed0eff46fa6334ebd112147892f1b15aafc8c' + } + } + }, + timestamp: 1628255843548, + nonce: -1 + }; + const signature = + ainUtil.ecSignTransaction(txBody, Buffer.from('d42f73de4ee706a4891dad643e0a65c0677020dbc2425f585442d0de2c742a44', 'hex')); + const res = await client.request('ain_sendSignedTransaction', { + tx_body: txBody, + signature, + protoVer: CURRENT_PROTOCOL_VERSION + }); + assert.deepEqual(eraseStateGas(_.get(res, 'result.result.result', null)), { + "func_results": { + "_closeCheckout": { + "op_results": { + "0": { + "path": "/checkout/stats/complete/1628208000000", + "result": { + "code": 0, + "bandwidth_gas_amount": 1 + } + }, + "1": { + "path": "/checkout/stats/complete/total", + "result": { + "code": 0, + "bandwidth_gas_amount": 1 + } + }, + "2": { + "path": "/checkout/requests/0x01A0980d2D4e418c7F27e1ef539d01A5b5E93204/0", + "result": { + "bandwidth_gas_amount": 1, + "code": 0, + "func_results": { + "_openCheckout": { + "bandwidth_gas_amount": 0, + "code": 0 + } + } + } + }, + "3": { + "path": "/checkout/stats/pending/0x01A0980d2D4e418c7F27e1ef539d01A5b5E93204", + "result": { + "bandwidth_gas_amount": 1, + "code": 0 + } + }, + "4": { + "path": "/checkout/stats/pending/total", + "result": { + "bandwidth_gas_amount": 1, + "code": 0 + } + } + }, + "code": 0, + "bandwidth_gas_amount": 0 + } + }, + "code": 0, + "bandwidth_gas_amount": 1, + "gas_amount_charged": 'erased', + "gas_amount_total": { + "bandwidth": { + "service": 6 + }, + "state": { + "service": 'erased' + } + }, + "gas_cost_total": 0 + }); + const userPendingAmount = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/checkout/stats/pending/${serviceUser}`) + .body.toString('utf-8')).result; + const totalPendingAmount = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/checkout/stats/pending/total`) + .body.toString('utf-8')).result; + const todayCompleteAmount = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/checkout/stats/complete/${CommonUtil.getDayTimestamp(1628255843548)}`) + .body.toString('utf-8')).result; + const totalCompleteAmount = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/checkout/stats/complete/total`) + .body.toString('utf-8')).result; + expect(userPendingAmount).to.equal(0); + expect(totalPendingAmount).to.equal(0); + expect(todayCompleteAmount).to.equal(checkoutAmount); + expect(totalCompleteAmount).to.equal(checkoutAmount); + }); + + it('can close a failed checkout and refund with token pool key', async () => { + // open checkout + const beforeBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${serviceUser}/balance`) + .body.toString('utf-8')).result || 0; + const beforeTokenPoolBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${tokenPoolAddr}/balance`) + .body.toString('utf-8')).result || 0; + const body = parseOrLog(syncRequest('POST', server2 + '/set_value', {json: { + ref: `/checkout/requests/${serviceUser}/1`, + value: { + amount: checkoutAmount, + type: tokenType, + token_id: tokenId, + recipient: ethAddress + } + }}).body.toString('utf-8')); + expect(body.code).to.equal(0); + if (!(await waitUntilTxFinalized(serverList, _.get(body, 'result.tx_hash')))) { + console.error(`Failed to check finalization of tx.`); + } + expect(_.get(body, 'result.result.code')).to.equal(0); + const afterRequestUserBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${serviceUser}/balance`) + .body.toString('utf-8')).result || 0; + const afterRequestTokenPoolBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${tokenPoolAddr}/balance`) + .body.toString('utf-8')).result || 0; + expect(afterRequestUserBalance).to.equal(beforeBalance - checkoutAmount); + expect(afterRequestTokenPoolBalance).to.equal(beforeTokenPoolBalance + checkoutAmount); + // close failed checkout + const txBody = { + operation: { + type: 'SET_VALUE', + ref: `/checkout/history/${serviceUser}/1`, + value: { + request: { + amount: checkoutAmount, + type: tokenType, + token_id: tokenId, + recipient: ethAddress + }, + response: { + status: 1, + tx_hash: '0x6af1ec8d4f0a55bac328cb20336ed0eff46fa6334ebd112147892f1b15aafc8c', + error_message: 'Ethereum tx failed' + } + } + }, + timestamp: 1628255843548, + nonce: -1 + }; + const signature = + ainUtil.ecSignTransaction(txBody, Buffer.from('d42f73de4ee706a4891dad643e0a65c0677020dbc2425f585442d0de2c742a44', 'hex')); + const res = await client.request('ain_sendSignedTransaction', { + tx_body: txBody, + signature, + protoVer: CURRENT_PROTOCOL_VERSION + }); + if (!(await waitUntilTxFinalized(serverList, _.get(res, 'result.result.tx_hash')))) { + console.error(`Failed to check finalization of tx.`); + } + assert.deepEqual(eraseStateGas(_.get(res, 'result.result.result')), { + "func_results": { + "_closeCheckout": { + "op_results": { + "0": { + "path": "/transfer/0x20ADd3d38405ebA6338CB9e57a0510DEB8f8e000/0x01A0980d2D4e418c7F27e1ef539d01A5b5E93204/1628255843548/value", + "result": { + "func_results": { + "_transfer": { + "op_results": { + "0": { + "path": "/accounts/0x20ADd3d38405ebA6338CB9e57a0510DEB8f8e000/balance", + "result": { + "code": 0, + "bandwidth_gas_amount": 1 + } + }, + "1": { + "path": "/accounts/0x01A0980d2D4e418c7F27e1ef539d01A5b5E93204/balance", + "result": { + "code": 0, + "bandwidth_gas_amount": 1 + } + }, + "2": { + "path": "/transfer/0x20ADd3d38405ebA6338CB9e57a0510DEB8f8e000/0x01A0980d2D4e418c7F27e1ef539d01A5b5E93204/1628255843548/result", + "result": { + "code": 0, + "bandwidth_gas_amount": 1 + } + } + }, + "code": 0, + "bandwidth_gas_amount": 0 + } + }, + "code": 0, + "bandwidth_gas_amount": 1 + } + }, + "1": { + "path": "/checkout/history/0x01A0980d2D4e418c7F27e1ef539d01A5b5E93204/1/refund", + "result": { + "code": 0, + "bandwidth_gas_amount": 1 + } + }, + "2": { + "path": "/checkout/requests/0x01A0980d2D4e418c7F27e1ef539d01A5b5E93204/1", + "result": { + "func_results": { + "_openCheckout": { + "code": 0, + "bandwidth_gas_amount": 0 + } + }, + "code": 0, + "bandwidth_gas_amount": 1 + } + }, + "3": { + "path": "/checkout/stats/pending/0x01A0980d2D4e418c7F27e1ef539d01A5b5E93204", + "result": { + "code": 0, + "bandwidth_gas_amount": 1 + } + }, + "4": { + "path": "/checkout/stats/pending/total", + "result": { + "code": 0, + "bandwidth_gas_amount": 1 + } + } + }, + "code": 0, + "bandwidth_gas_amount": 0 + } + }, + "code": 0, + "bandwidth_gas_amount": 1, + "gas_amount_charged": 'erased', + "gas_amount_total": { + "bandwidth": { + "service": 9 + }, + "state": { + "service": 'erased' + } + }, + "gas_cost_total": 0 + }); + const refund = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/checkout/history/${serviceUser}/1/refund`).body.toString('utf-8')).result; + assert.deepEqual(refund, '/transfer/0x20ADd3d38405ebA6338CB9e57a0510DEB8f8e000/0x01A0980d2D4e418c7F27e1ef539d01A5b5E93204/1628255843548'); + const refundTransfer = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=${refund}`).body.toString('utf-8')).result; + assert.deepEqual(refundTransfer, { + "value": 100, + "result": { + "timestamp": 1628255843548, + "tx_hash": "0xbefafa6bb77dc60e4e3856124efff5c122c7d2a56ef305669cdf7f3e1d3aa642", + "code": 0 + } + }); + const afterCloseUserBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${serviceUser}/balance`) + .body.toString('utf-8')).result || 0; + const afterCloseTokenPoolBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/accounts/${tokenPoolAddr}/balance`) + .body.toString('utf-8')).result || 0; + const userPendingAmount = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/checkout/stats/pending/${serviceUser}`) + .body.toString('utf-8')).result; + const totalPendingAmount = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/checkout/stats/pending/total`) + .body.toString('utf-8')).result; + const todayCompleteAmount = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/checkout/stats/complete/${CommonUtil.getDayTimestamp(1628255843548)}`) + .body.toString('utf-8')).result; + const totalCompleteAmount = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=/checkout/stats/complete/total`) + .body.toString('utf-8')).result; + expect(afterCloseUserBalance).to.equal(beforeBalance); + expect(afterCloseTokenPoolBalance).to.equal(beforeTokenPoolBalance); + expect(userPendingAmount).to.equal(0); + expect(totalPendingAmount).to.equal(0); + expect(todayCompleteAmount).to.equal(checkoutAmount); + expect(totalCompleteAmount).to.equal(checkoutAmount); + }); + }); + }); + + describe('Billing', async () => { + let serviceAdmin; // = server1 + let billingUserA; // = server2 + let billingUserB; // = server3 + let userBalancePathA; + let userBalancePathB; + const billingAccountBalancePathA = '/get_value?ref=/service_accounts/billing/test_billing/A/balance'; + before(async () => { + serviceAdmin = + parseOrLog(syncRequest('GET', server1 + '/get_address').body.toString('utf-8')).result; + billingUserA = + parseOrLog(syncRequest('GET', server2 + '/get_address').body.toString('utf-8')).result; + billingUserB = + parseOrLog(syncRequest('GET', server3 + '/get_address').body.toString('utf-8')).result; + userBalancePathA = `/get_value?ref=/accounts/${billingUserA}/balance`; + userBalancePathB = `/get_value?ref=/accounts/${billingUserB}/balance`; + const adminConfig = { + [serviceAdmin]: true, + [billingUserA]: true, + [billingUserB]: true + }; + const billingConfig = { + A: { + users: { + [serviceAdmin]: true, + [billingUserA]: true + } + }, + B: { + users: { + [serviceAdmin]: true, + [billingUserB]: true + } + } + }; + await setUpApp('test_billing', serverList, { admin: adminConfig, billing: billingConfig }); + + const server4Addr = + parseOrLog(syncRequest('GET', server4 + '/get_address').body.toString('utf-8')).result; + const transferRes = parseOrLog(syncRequest('POST', server4 + '/set', {json: { + op_list: [ + { + ref: `/transfer/${server4Addr}/billing|test_billing|A/${Date.now()}/value`, + value: 100, + type: 'SET_VALUE' + }, + { + ref: `/transfer/${server4Addr}/billing|test_billing|B/${Date.now()}/value`, + value: 100, + type: 'SET_VALUE' + } + ], + nonce: -1, + timestamp: Date.now(), + }}).body.toString('utf-8')).result; + if (!(await waitUntilTxFinalized(serverList, transferRes.tx_hash))) { + console.error(`Failed to check finalization of transfer tx.`); + } + }); + + it('app txs are not charged by transfer', async () => { + // NOTE(platfowner): A pre-tx to guarantee that the service state delta of the next tx + // is zero. + const txPreRes = parseOrLog(syncRequest('POST', server2 + '/set_value', {json: { + ref: '/apps/test_billing/test_pre', + value: 'testing app tx', + gas_price: 1, + nonce: -1, + timestamp: Date.now(), + } + }).body.toString('utf-8')).result; + if (!(await waitUntilTxFinalized(serverList, txPreRes.tx_hash))) { + console.error(`Failed to check finalization of app tx.`); + } + const balanceBefore = parseOrLog(syncRequest('GET', server2 + userBalancePathA).body.toString('utf-8')).result; + const txWithoutBillingRes = parseOrLog(syncRequest('POST', server2 + '/set_value', {json: { + ref: '/apps/test_billing/test', + value: 'testing app tx', gas_price: 1, nonce: -1, timestamp: Date.now(), @@ -6737,7 +7267,7 @@ describe('Blockchain Node', () => { const tx = parseOrLog(syncRequest('GET', server2 + `/get_transaction?hash=${txRes.tx_hash}`).body.toString('utf-8')).result; const gasFeeCollected = parseOrLog(syncRequest( 'GET', - `${server2}/get_value?ref=/gas_fee/collect/${billingUserA}/${tx.number}/${txRes.tx_hash}/amount` + `${server2}/get_value?ref=/gas_fee/collect/${tx.number}/${billingUserA}/${txRes.tx_hash}/amount` ).body.toString('utf-8')).result; assert.deepEqual( gasFeeCollected, @@ -6815,7 +7345,7 @@ describe('Blockchain Node', () => { const tx = parseOrLog(syncRequest('GET', server2 + `/get_transaction?hash=${txRes.tx_hash}`).body.toString('utf-8')).result; const gasFeeCollected = parseOrLog(syncRequest( 'GET', - `${server2}/get_value?ref=/gas_fee/collect/${billingUserA}/${tx.number}/${txRes.tx_hash}/amount` + `${server2}/get_value?ref=/gas_fee/collect/${tx.number}/${billingUserA}/${txRes.tx_hash}/amount` ).body.toString('utf-8')).result; assert.deepEqual( gasFeeCollected, @@ -6875,7 +7405,7 @@ describe('Blockchain Node', () => { const tx = parseOrLog(syncRequest('GET', server2 + `/get_transaction?hash=${txRes.tx_hash}`).body.toString('utf-8')).result; const gasFeeCollected = parseOrLog(syncRequest( 'GET', - `${server2}/get_value?ref=/gas_fee/collect/${billingUserA}/${tx.number}/${txRes.tx_hash}/amount` + `${server2}/get_value?ref=/gas_fee/collect/${tx.number}/${billingUserA}/${txRes.tx_hash}/amount` ).body.toString('utf-8')).result; assert.deepEqual( gasFeeCollected, @@ -6980,7 +7510,7 @@ describe('Blockchain Node', () => { }); describe('Tx Receipts', () => { - it(`records a transaction's receipt`, async () => { + it(`records a transaction receipt`, async () => { const txSignerAddress = parseOrLog(syncRequest( 'GET', server1 + '/get_address').body.toString('utf-8')).result; const request = { @@ -6997,32 +7527,17 @@ describe('Blockchain Node', () => { } const receipt = parseOrLog(syncRequest( - 'GET', server1 + `/get_value?ref=/receipts/${body.result.tx_hash}`) + 'GET', server1 + `/get_value?ref=${PathUtil.getReceiptPath(body.result.tx_hash)}`) .body.toString('utf-8')).result; assert.deepEqual(receipt.address, txSignerAddress); assert.deepEqual(receipt.exec_result, { - bandwidth_gas_amount: 1, - code: 0, - gas_amount_charged: 0, - gas_amount_total: { - bandwidth: { - app: { - test: 1 - }, - service: 0 - }, - state: { - app: { - test: 188 - }, - service: 0 - }, - }, - gas_cost_total: 0 + "code": 0, + "gas_amount_charged": 0, + "gas_cost_total": 0 }); }); - it(`removes an old transaction's receipt`, async () => { + it(`removes an old transaction receipt`, async () => { const MAX_BLOCK_NUMBERS_FOR_RECEIPTS = 100; let lastBlockNumber = getLastBlockNumber(server1); if (lastBlockNumber <= MAX_BLOCK_NUMBERS_FOR_RECEIPTS) { @@ -7037,7 +7552,7 @@ describe('Blockchain Node', () => { } for (const tx of oldBlock.transactions) { const receipt = parseOrLog(syncRequest( - 'GET', server1 + `/get_value?ref=/receipts/${tx.hash}`) + 'GET', server1 + `/get_value?ref=${PathUtil.getReceiptPath(tx.hash)}`) .body.toString('utf-8')).result; assert.deepEqual(receipt, null); } @@ -7074,14 +7589,14 @@ describe('Blockchain Node', () => { // Failed tx's receipt is in state const txHash = body.result.tx_hash; const receipt = parseOrLog(syncRequest( - 'GET', server2 + `/get_value?ref=/receipts/${txHash}`).body.toString('utf-8')).result; + 'GET', server2 + `/get_value?ref=${PathUtil.getReceiptPath(txHash)}`).body.toString('utf-8')).result; expect(receipt).to.not.equal(null); - assert.deepEqual(receipt.exec_result, body.result.result); + assert.deepEqual(receipt.exec_result, DB.trimExecutionResult(body.result.result)); // Failed tx's gas fees have been collected const blockNumber = receipt.block_number; const gasFeeCollected = parseOrLog(syncRequest( - 'GET', server2 + `/get_value?ref=/gas_fee/collect/${server1Address}/${blockNumber}/${txHash}/amount` + 'GET', server2 + `/get_value?ref=/gas_fee/collect/${blockNumber}/${server1Address}/${txHash}/amount` ).body.toString('utf-8')).result; assert.deepEqual(gasFeeCollected, body.result.result.gas_cost_total); diff --git a/integration/sharding.test.js b/integration/sharding.test.js index 5a92a546a..ca0cf5e85 100644 --- a/integration/sharding.test.js +++ b/integration/sharding.test.js @@ -32,6 +32,7 @@ const { waitForNewBlocks, waitUntilNodeSyncs, waitUntilTxFinalized, + waitUntilNetworkIsReady, setUpApp, } = require('../unittest/test-util'); @@ -254,16 +255,16 @@ describe('Sharding', async () => { await setUpApp('afan', parentServerList, { admin: { [shardOwnerAddr]: true } }); tracker_proc = startServer(TRACKER_SERVER, 'tracker server', ENV_VARIABLES[1], true); - await CommonUtil.sleep(2000); + await CommonUtil.sleep(3000); server1_proc = startServer(APP_SERVER, 'server1', ENV_VARIABLES[2], true); - await CommonUtil.sleep(2000); + await CommonUtil.sleep(10000); await waitUntilShardReporterStarts(); server2_proc = startServer(APP_SERVER, 'server2', ENV_VARIABLES[3], true); - await CommonUtil.sleep(2000); + await CommonUtil.sleep(3000); server3_proc = startServer(APP_SERVER, 'server3', ENV_VARIABLES[4], true); - await CommonUtil.sleep(2000); + await CommonUtil.sleep(3000); server4_proc = startServer(APP_SERVER, 'server4', ENV_VARIABLES[5], true); - await CommonUtil.sleep(2000); + await CommonUtil.sleep(3000); // Before shard reporting begins }); after(() => { @@ -360,8 +361,9 @@ describe('Sharding', async () => { }); }); - describe('Child chain initialization', () => { + describe('Shard chain initialization', () => { before(async () => { + await waitUntilNetworkIsReady(shardServerList); await setUpApp('afan', shardServerList, { admin: { [shardOwnerAddr]: true } }); }); @@ -463,10 +465,10 @@ describe('Sharding', async () => { const reportsBefore = parseOrLog(syncRequest( 'GET', parentServer + `/get_value?ref=${sharding.sharding_path}/.shard/proof_hash_map`) .body.toString('utf-8')); - console.log(`Shutting down server[0]...`); + console.log(` --> Shutting down server[0]...`); server1_proc.kill(); await waitForNewBlocks(server2, sharding.reporting_period); - console.log(`Restarting server[0]...`); + console.log(` --> Restarting server[0]...`); server1_proc = startServer(APP_SERVER, 'server1', ENV_VARIABLES[2]); await waitForNewBlocks(server2, sharding.reporting_period * 2); await waitUntilNodeSyncs(server1); @@ -1543,7 +1545,7 @@ describe('Sharding', async () => { "bandwidth_gas_amount": 1 }, }, - "gas_amount_charged": 1548, + "gas_amount_charged": 0, "gas_amount_total": { "bandwidth": { "app": { @@ -1553,9 +1555,9 @@ describe('Sharding', async () => { }, "state": { "app": { - "test": 2874 + "test": 4422 }, - "service": 1548 + "service": 0 } }, "gas_cost_total": 0 @@ -2083,7 +2085,7 @@ describe('Sharding', async () => { "code": 0, "func_results": { "_updateLatestShardReport": { - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, "op_results": { "0": { @@ -2150,7 +2152,7 @@ describe('Sharding', async () => { "code": 0, "func_results": { "_updateLatestShardReport": { - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, "op_results": { "0": { @@ -2169,7 +2171,7 @@ describe('Sharding', async () => { "code": 0, "func_results": { "_updateLatestShardReport": { - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, } }, diff --git a/json_rpc/index.js b/json_rpc/index.js index 5ceb5f5a6..ef724d5f9 100644 --- a/json_rpc/index.js +++ b/json_rpc/index.js @@ -8,6 +8,7 @@ const { TX_BYTES_LIMIT, BATCH_TX_LIST_SIZE_LIMIT, NETWORK_ID, + CHAIN_ID, BlockchainNodeStates, ReadDbOperations, PredefinedDbPaths, @@ -374,35 +375,40 @@ module.exports = function getMethods(node, p2pServer, minProtocolVersion, maxPro }, // Network API - net_listening: function(args, done) { + net_listening: function (args, done) { const peerCount = Object.keys(p2pServer.inbound).length; - done(null, addProtocolVersion({result: !!peerCount})); + done(null, addProtocolVersion({ result: !!peerCount })); }, - net_peerCount: function(args, done) { + net_peerCount: function (args, done) { const peerCount = Object.keys(p2pServer.inbound).length; - done(null, addProtocolVersion({result: peerCount})); + done(null, addProtocolVersion({ result: peerCount })); }, - net_syncing: function(args, done) { + net_syncing: function (args, done) { // TODO(liayoo): Return { starting, latest } with block numbers // if the node is currently syncing. - done(null, addProtocolVersion( - {result: p2pServer.node.state === BlockchainNodeStates.SYNCING})); + done(null, addProtocolVersion({ + result: p2pServer.node.state === BlockchainNodeStates.SYNCING + })); + }, + + net_getNetworkId: function (args, done) { + done(null, addProtocolVersion({ result: NETWORK_ID })); }, - net_getNetworkId: function(args, done) { - done(null, addProtocolVersion({result: NETWORK_ID})); + net_getChainId: function (args, done) { + done(null, addProtocolVersion({ result: CHAIN_ID })); }, - net_consensusStatus: function(args, done) { + net_consensusStatus: function (args, done) { const result = p2pServer.consensus.getStatus(); - done(null, addProtocolVersion({result})); + done(null, addProtocolVersion({ result })); }, - net_rawConsensusStatus: function(args, done) { + net_rawConsensusStatus: function (args, done) { const result = p2pServer.consensus.getRawStatus(); - done(null, addProtocolVersion({result})); + done(null, addProtocolVersion({ result })); } }; }; diff --git a/loadtest/load_tester.sh b/loadtest/load_tester.sh index 7ae97c5db..6f2c97b7d 100644 --- a/loadtest/load_tester.sh +++ b/loadtest/load_tester.sh @@ -12,7 +12,7 @@ sleep 5 LOCAL=true node $BASEDIR/../client/index.js > $BASEDIR/log1.txt & PID2=$! sleep 10 -P2P_PORT=5020 PORT=8081 STAKE=250 node $BASEDIR/../client/index.js > $BASEDIR/log2.txt & +P2P_PORT=5020 PORT=8081 STAKE=100000 node $BASEDIR/../client/index.js > $BASEDIR/log2.txt & PID3=$! sleep 20 diff --git a/node/index.js b/node/index.js index 937a4b08f..ddd516eda 100644 --- a/node/index.js +++ b/node/index.js @@ -28,6 +28,7 @@ const { } = require('../common/constants'); const FileUtil = require('../common/file-util'); const CommonUtil = require('../common/common-util'); +const PathUtil = require('../common/path-util'); const Blockchain = require('../blockchain'); const TransactionPool = require('../tx-pool'); const StateManager = require('../db/state-manager'); @@ -416,12 +417,14 @@ class BlockchainNode { removeOldReceipts(blockNumber, db) { const LOG_HEADER = 'removeOldReceipts'; if (blockNumber > MAX_BLOCK_NUMBERS_FOR_RECEIPTS) { - const receiptsPrefixFullPath = DB.getFullPath( - [PredefinedDbPaths.RECEIPTS], PredefinedDbPaths.VALUES_ROOT); const oldBlock = this.bc.getBlockByNumber(blockNumber - MAX_BLOCK_NUMBERS_FOR_RECEIPTS); if (oldBlock) { oldBlock.transactions.forEach((tx) => { - db.writeDatabase([...receiptsPrefixFullPath, tx.hash], null); + db.writeDatabase( + [ + PredefinedDbPaths.VALUES_ROOT, + ...CommonUtil.parsePath(PathUtil.getReceiptPath(tx.hash)) + ], null); }); } else { logger.error( @@ -436,13 +439,13 @@ class BlockchainNode { for (const block of blockList) { this.removeOldReceipts(block.number, db); if (block.number > 0) { - if (!db.executeTransactionList(block.last_votes, true)) { + if (!db.executeTransactionList(block.last_votes, true, false, 0, block.timestamp)) { logger.error(`[${LOG_HEADER}] Failed to execute last_votes of block: ` + `${JSON.stringify(block, null, 2)}`); return false; } } - if (!db.executeTransactionList(block.transactions, block.number === 0, true, block.number)) { + if (!db.executeTransactionList(block.transactions, block.number === 0, false, block.number, block.timestamp)) { logger.error(`[${LOG_HEADER}] Failed to execute transactions of block: ` + `${JSON.stringify(block, null, 2)}`); return false; @@ -532,13 +535,13 @@ class BlockchainNode { this.removeOldReceipts(block.number, db); if (block.number > 0) { - if (!db.executeTransactionList(block.last_votes, true)) { + if (!db.executeTransactionList(block.last_votes, true, false, block.number, block.timestamp)) { logger.error(`[${LOG_HEADER}] Failed to execute last_votes (${block.number})`); // NOTE(liayoo): Quick fix for the problem. May be fixed by deleting the block files. process.exit(1); } } - if (!db.executeTransactionList(block.transactions, block.number === 0, true, block.number)) { + if (!db.executeTransactionList(block.transactions, block.number === 0, false, block.number, block.timestamp)) { logger.error(`[${LOG_HEADER}] Failed to execute transactions (${block.number})`) // NOTE(liayoo): Quick fix for the problem. May be fixed by deleting the block files. process.exit(1); @@ -574,7 +577,8 @@ class BlockchainNode { // NOTE(liayoo): Quick fix for the problem. May be fixed by deleting the block files. process.exit(1); } - // NOTE(minsulee2): Deal with the case the only genesis block was generated. + // NOTE(liayoo): we don't have the votes for the last block, so remove it and try to + // receive from peers. if (deleteLastBlock && number > 0 && number === numBlockFiles - 1) { lastBlockWithoutProposal = block; this.bc.deleteBlock(lastBlockWithoutProposal); diff --git a/p2p/index.js b/p2p/index.js index 0c3c3fce0..e9fed0dff 100644 --- a/p2p/index.js +++ b/p2p/index.js @@ -6,6 +6,7 @@ const logger = require('../logger')('P2P_CLIENT'); const { ConsensusStates } = require('../consensus/constants'); const VersionUtil = require('../common/version-util'); const { + HOSTING_ENV, PORT, P2P_PORT, TRACKER_WS_ADDR, @@ -13,8 +14,7 @@ const { BlockchainNodeStates, DEFAULT_MAX_OUTBOUND, DEFAULT_MAX_INBOUND, - MAX_OUTBOUND_LIMIT, - MAX_INBOUND_LIMIT + NETWORK_ID, } = require('../common/constants'); const { sleep } = require('../common/common-util'); const { @@ -25,7 +25,8 @@ const { verifySignedMessage, checkTimestamp, closeSocketSafe, - encapsulateMessage + encapsulateMessage, + isValidNetworkId } = require('./util'); const RECONNECT_INTERVAL_MS = 5 * 1000; // 5 seconds @@ -52,12 +53,10 @@ class P2pClient { // maxOutbound is for now limited equal or less than 2. // maxInbound is a rest of connection after maxOutbound is set. initConnections() { - const numOutbound = process.env.MAX_OUTBOUND ? + this.maxOutbound = process.env.MAX_OUTBOUND ? Number(process.env.MAX_OUTBOUND) : DEFAULT_MAX_OUTBOUND; - const numInbound = process.env.MAX_INBOUND ? + this.maxInbound = process.env.MAX_INBOUND ? Number(process.env.MAX_INBOUND) : DEFAULT_MAX_INBOUND; - this.maxOutbound = Math.min(numOutbound, MAX_OUTBOUND_LIMIT); - this.maxInbound = Math.min(numInbound, MAX_INBOUND_LIMIT); } getConnectionStatus() { @@ -94,14 +93,16 @@ class P2pClient { } getNetworkStatus() { + const intIp = this.server.getInternalIp(); const extIp = this.server.getExternalIp(); - const url = new URL(`ws://${extIp}:${P2P_PORT}`); - const p2pUrl = url.toString(); - url.protocol = 'http:'; - url.port = PORT; - const clientApiUrl = url.toString(); - url.pathname = 'json-rpc'; - const jsonRpcUrl = url.toString(); + const intUrl = new URL(`ws://${intIp}:${P2P_PORT}`); + const extUrl = new URL(`ws://${extIp}:${P2P_PORT}`); + const p2pUrl = HOSTING_ENV === 'comcom' ? intUrl.toString() : extUrl.toString(); + extUrl.protocol = 'http:'; + extUrl.port = PORT; + const clientApiUrl = extUrl.toString(); + extUrl.pathname = 'json-rpc'; + const jsonRpcUrl = extUrl.toString(); return { ip: extIp, p2p: { @@ -271,9 +272,16 @@ class P2pClient { const LOG_HEADER = 'setClientSidePeerEventHandlers'; socket.on('message', (message) => { const parsedMessage = JSON.parse(message); + const networkId = _.get(parsedMessage, 'networkId'); + const address = getAddressFromSocket(this.outbound, socket); + if (!isValidNetworkId(networkId)) { + logger.error(`The given network ID(${networkId}) of the node(${address}) is MISSING or ` + + `DIFFERENT from mine(${NETWORK_ID}). Disconnect the connection.`); + closeSocketSafe(this.outbound, socket); + return; + } const dataProtoVer = _.get(parsedMessage, 'dataProtoVer'); if (!VersionUtil.isValidProtocolVersion(dataProtoVer)) { - const address = getAddressFromSocket(this.outbound, socket); logger.error(`The data protocol version of the node(${address}) is MISSING or ` + `INAPPROPRIATE. Disconnect the connection.`); closeSocketSafe(this.outbound, socket); diff --git a/p2p/server.js b/p2p/server.js index 794791a3f..83d331775 100644 --- a/p2p/server.js +++ b/p2p/server.js @@ -18,8 +18,6 @@ const { DATA_PROTOCOL_VERSION, P2P_PORT, HOSTING_ENV, - COMCOM_HOST_EXTERNAL_IP, - COMCOM_HOST_INTERNAL_IP_MAP, MessageTypes, BlockchainNodeStates, PredefinedDbPaths, @@ -34,7 +32,8 @@ const { FunctionTypes, NativeFunctionIds, LIGHTWEIGHT, - FeatureFlags + FeatureFlags, + NETWORK_ID } = require('../common/constants'); const CommonUtil = require('../common/common-util'); const { @@ -49,7 +48,8 @@ const { verifySignedMessage, checkTimestamp, closeSocketSafe, - encapsulateMessage + encapsulateMessage, + isValidNetworkId } = require('./util'); const PathUtil = require('../common/path-util'); @@ -116,6 +116,10 @@ class P2pServer { return this.node.account.private_key; } + getInternalIp() { + return this.node.ipAddrInternal; + } + getExternalIp() { return this.node.ipAddrExternal; } @@ -294,19 +298,11 @@ class P2pServer { process.exit(0); }); } else if (HOSTING_ENV === 'comcom') { - let ipAddr = null; if (internal) { - const hostname = _.toLower(os.hostname()); - logger.info(`Hostname: ${hostname}`); - ipAddr = COMCOM_HOST_INTERNAL_IP_MAP[hostname]; + return ip.address(); } else { - ipAddr = COMCOM_HOST_EXTERNAL_IP; + return publicIp.v4(); } - if (ipAddr) { - return ipAddr; - } - logger.error(`Failed to get ${internal ? 'internal' : 'external'} ip address.`); - process.exit(0); } else if (HOSTING_ENV === 'local') { return ip.address(); } else { @@ -358,12 +354,19 @@ class P2pServer { socket.on('message', (message) => { try { const parsedMessage = JSON.parse(message); - const dataProtoVer = _.get(parsedMessage, 'dataProtoVer'); + const networkId = _.get(parsedMessage, 'networkId'); const address = getAddressFromSocket(this.inbound, socket); + if (!isValidNetworkId(networkId)) { + logger.error(`The given network ID(${networkId}) of the node(${address}) is MISSING or ` + + `DIFFERENT from mine(${NETWORK_ID}). Disconnect the connection.`); + closeSocketSafe(this.inbound, socket); + return; + } + const dataProtoVer = _.get(parsedMessage, 'dataProtoVer'); if (!VersionUtil.isValidProtocolVersion(dataProtoVer)) { logger.error(`The data protocol version of the node(${address}) is MISSING or ` + `INAPPROPRIATE. Disconnect the connection.`); - closeSocketSafe(this.outbound, socket); + closeSocketSafe(this.inbound, socket); return; } if (!checkTimestamp(_.get(parsedMessage, 'timestamp'))) { diff --git a/p2p/util.js b/p2p/util.js index fdec15ddb..66da756a1 100644 --- a/p2p/util.js +++ b/p2p/util.js @@ -11,6 +11,7 @@ const { CURRENT_PROTOCOL_VERSION, DATA_PROTOCOL_VERSION, P2P_MESSAGE_TIMEOUT_MS, + NETWORK_ID } = require('../common/constants'); const CommonUtil = require('../common/common-util'); @@ -95,6 +96,7 @@ function encapsulateMessage(type, dataObj) { data: dataObj, protoVer: CURRENT_PROTOCOL_VERSION, dataProtoVer: DATA_PROTOCOL_VERSION, + networkId: NETWORK_ID, timestamp: Date.now() }; return message; @@ -113,6 +115,14 @@ function checkTimestamp(timestamp) { } } +function isValidNetworkId(networkId) { + if (networkId !== NETWORK_ID) { + return false; + } else { + return true; + } +} + module.exports = { getAddressFromSocket, removeSocketConnectionIfExists, @@ -121,5 +131,6 @@ module.exports = { verifySignedMessage, closeSocketSafe, checkTimestamp, - encapsulateMessage + encapsulateMessage, + isValidNetworkId }; diff --git a/package.json b/package.json index 52a24ddb8..cba2cf4f6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ain-blockchain", "description": "AI Network Blockchain", - "version": "0.9.0", + "version": "0.9.1", "private": true, "license": "MIT", "author": "dev@ainetwork.ai", @@ -18,11 +18,13 @@ "lint": "eslint --ext js -c .eslintrc.json .", "loadtest": "sh loadtest/load_tester.sh", "test_integration": "./node_modules/mocha/bin/mocha --timeout 640000 \"integration/*.test.js\"", - "test_integration_blockchain": "./node_modules/mocha/bin/mocha --timeout 160000 integration/blockchain.test.js", - "test_integration_consensus": "./node_modules/mocha/bin/mocha --timeout 160000 integration/consensus.test.js", - "test_integration_dapp": "./node_modules/mocha/bin/mocha --timeout 160000 integration/afan_dapp.test.js", + "test_integration_blockchain": "./node_modules/mocha/bin/mocha --timeout 640000 integration/blockchain.test.js", + "test_integration_consensus": "./node_modules/mocha/bin/mocha --timeout 640000 integration/consensus.test.js", + "test_integration_dapp": "./node_modules/mocha/bin/mocha --timeout 640000 integration/afan_dapp.test.js", "test_integration_node": "./node_modules/mocha/bin/mocha --timeout 640000 integration/node.test.js", - "test_integration_sharding": "./node_modules/mocha/bin/mocha --timeout 320000 integration/sharding.test.js", + "test_integration_sharding": "./node_modules/mocha/bin/mocha --timeout 640000 integration/sharding.test.js", + "test_integration_he_protocol": "./node_modules/mocha/bin/mocha --timeout 640000 integration/he_protocol.test.js", + "test_integration_he_sharding": "./node_modules/mocha/bin/mocha --timeout 640000 integration/he_sharding.test.js", "test_unit": "MIN_NUM_VALIDATORS=1 STATE_TREE_BYTES_LIMIT=20000000 ./node_modules/mocha/bin/mocha --timeout 160000 \"unittest/*.test.js\"", "test_unit_block_pool": "MIN_NUM_VALIDATORS=1 ./node_modules/mocha/bin/mocha --timeout 160000 unittest/block-pool.test.js", "test_unit_blockchain": "MIN_NUM_VALIDATORS=1 ./node_modules/mocha/bin/mocha --timeout 160000 unittest/blockchain.test.js", @@ -36,6 +38,9 @@ "test_unit_state_manager": "./node_modules/mocha/bin/mocha --timeout 160000 unittest/state-manager.test.js", "test_unit_state_node": "./node_modules/mocha/bin/mocha --timeout 160000 unittest/state-node.test.js", "test_unit_state_util": "./node_modules/mocha/bin/mocha --timeout 160000 unittest/state-util.test.js", + "test_unit_radix_child_map": "./node_modules/mocha/bin/mocha --timeout 160000 unittest/radix-child-map.test.js", + "test_unit_radix_node": "./node_modules/mocha/bin/mocha --timeout 160000 unittest/radix-node.test.js", + "test_unit_radix_tree": "./node_modules/mocha/bin/mocha --timeout 160000 unittest/radix-tree.test.js", "test_unit_tx": "MIN_NUM_VALIDATORS=1 ./node_modules/mocha/bin/mocha --timeout 160000 unittest/transaction.test.js", "test_unit_tx_pool": "MIN_NUM_VALIDATORS=1 ./node_modules/mocha/bin/mocha --timeout 160000 unittest/tx-pool.test.js", "tracker": "npm run clean & node ./tracker-server/index.js" diff --git a/port_list_macos.sh b/port_list_macos.sh index 1f3007018..5d88e7492 100644 --- a/port_list_macos.sh +++ b/port_list_macos.sh @@ -1,2 +1,4 @@ +#!/bin/bash + # Gets a list of the system port numbers in use for MacOS sudo lsof -i -P -n | grep LISTEN diff --git a/reset_blockchain_gcp.sh b/reset_blockchain_gcp.sh index d4ab9363b..b3a7cc804 100644 --- a/reset_blockchain_gcp.sh +++ b/reset_blockchain_gcp.sh @@ -1,7 +1,7 @@ -#!/bin/sh +#!/bin/bash if [[ "$#" -lt 3 ]]; then - echo "Usage: sh reset_blockchain_gcp.sh dev lia 0" + echo "Usage: bash reset_blockchain_gcp.sh dev lia 0" exit fi diff --git a/restart_node_gcp.sh b/restart_node_gcp.sh new file mode 100644 index 000000000..3dd31d639 --- /dev/null +++ b/restart_node_gcp.sh @@ -0,0 +1,156 @@ +#!/bin/bash + +if [[ "$#" -lt 4 ]] || [[ "$#" -gt 4 ]]; then + printf "Usage: bash restart_node_gcp.sh [dev|staging|spring|summer] [fast|full]\n" + printf "Example: bash restart_node_gcp.sh spring 0 0 fast\n" + exit +fi + +# 1. Configure env vars (GENESIS_CONFIGS_DIR, TRACKER_WS_ADDR, ACCOUNT_INDEX, ...) +printf "\n#### [Step 1] Configure env vars ####\n\n" + +export GENESIS_CONFIGS_DIR=genesis-configs/testnet +if [[ "$1" = 'spring' ]]; then + export TRACKER_WS_ADDR=ws://35.221.137.80:5000 +elif [[ "$1" = 'summer' ]]; then + export TRACKER_WS_ADDR=ws://35.194.172.106:5000 +elif [[ "$1" = 'staging' ]]; then + export TRACKER_WS_ADDR=ws://35.221.150.73:5000 +elif [[ "$1" = 'dev' ]]; then + if [[ "$2" = 0 ]]; then + export TRACKER_WS_ADDR=ws://34.80.184.73:5000 # dev-tracker-ip + elif [[ "$2" = 1 ]]; then + export TRACKER_WS_ADDR=ws://35.187.153.22:5000 # dev-shard-1-tracker-ip + elif [[ "$2" = 2 ]]; then + export TRACKER_WS_ADDR=ws://34.80.203.104:5000 # dev-shard-2-tracker-ip + elif [[ "$2" = 3 ]]; then + export TRACKER_WS_ADDR=ws://35.189.174.17:5000 # dev-shard-3-tracker-ip + elif [[ "$2" = 4 ]]; then + export TRACKER_WS_ADDR=ws://35.221.164.158:5000 # dev-shard-4-tracker-ip + elif [[ "$2" = 5 ]]; then + export TRACKER_WS_ADDR=ws://35.234.46.65:5000 # dev-shard-5-tracker-ip + elif [[ "$2" = 6 ]]; then + export TRACKER_WS_ADDR=ws://35.221.210.171:5000 # dev-shard-6-tracker-ip + elif [[ "$2" = 7 ]]; then + export TRACKER_WS_ADDR=ws://34.80.222.121:5000 # dev-shard-7-tracker-ip + elif [[ "$2" = 8 ]]; then + export TRACKER_WS_ADDR=ws://35.221.200.95:5000 # dev-shard-8-tracker-ip + elif [[ "$2" = 9 ]]; then + export TRACKER_WS_ADDR=ws://34.80.216.199:5000 # dev-shard-9-tracker-ip + elif [[ "$2" = 10 ]]; then + export TRACKER_WS_ADDR=ws://34.80.161.85:5000 # dev-shard-10-tracker-ip + elif [[ "$2" = 11 ]]; then + export TRACKER_WS_ADDR=ws://35.194.239.169:5000 # dev-shard-11-tracker-ip + elif [[ "$2" = 12 ]]; then + export TRACKER_WS_ADDR=ws://35.185.156.22:5000 # dev-shard-12-tracker-ip + elif [[ "$2" = 13 ]]; then + export TRACKER_WS_ADDR=ws://35.229.247.143:5000 # dev-shard-13-tracker-ip + elif [[ "$2" = 14 ]]; then + export TRACKER_WS_ADDR=ws://35.229.226.47:5000 # dev-shard-14-tracker-ip + elif [[ "$2" = 15 ]]; then + export TRACKER_WS_ADDR=ws://35.234.61.23:5000 # dev-shard-15-tracker-ip + elif [[ "$2" = 16 ]]; then + export TRACKER_WS_ADDR=ws://34.80.66.41:5000 # dev-shard-16-tracker-ip + elif [[ "$2" = 17 ]]; then + export TRACKER_WS_ADDR=ws://35.229.143.18:5000 # dev-shard-17-tracker-ip + elif [[ "$2" = 18 ]]; then + export TRACKER_WS_ADDR=ws://35.234.58.137:5000 # dev-shard-18-tracker-ip + elif [[ "$2" = 19 ]]; then + export TRACKER_WS_ADDR=ws://34.80.249.104:5000 # dev-shard-19-tracker-ip + elif [[ "$2" = 20 ]]; then + export TRACKER_WS_ADDR=ws://35.201.248.92:5000 # dev-shard-20-tracker-ip + else + printf "Invalid argument: $2\n" + exit + fi + if [[ "$2" -gt 0 ]]; then + # Create a genesis_params.json + export GENESIS_CONFIGS_DIR="genesis-configs/shard_$2" + mkdir -p "./$GENESIS_CONFIGS_DIR" + node > "./$GENESIS_CONFIGS_DIR/genesis_params.json" < argument: $1\n" + exit +fi + +printf "TRACKER_WS_ADDR=$TRACKER_WS_ADDR\n" +printf "GENESIS_CONFIGS_DIR=$GENESIS_CONFIGS_DIR\n" + +if [[ "$3" -lt 0 ]] || [[ "$3" -gt 4 ]]; then + printf "Invalid argument: $3\n" + exit +fi + +export ACCOUNT_INDEX="$3" +printf "ACCOUNT_INDEX=$ACCOUNT_INDEX\n" + +if [[ "$4" != 'fast' ]] && [[ "$4" != 'full' ]]; then + printf "Invalid argument: $2\n" + exit +fi + +export SYNC_MODE="$4" +printf "SYNC_MODE=$SYNC_MODE\n" + +export DEBUG=false +export CONSOLE_LOG=false +export ENABLE_DEV_SET_CLIENT_API=false +export ENABLE_TX_SIG_VERIF_WORKAROUND=false +export ENABLE_GAS_FEE_WORKAROUND=true +export LIGHTWEIGHT=false +export STAKE=100000 +export BLOCKCHAIN_DATA_DIR="/home/ain_blockchain_data" + +# 2. Kill the existing node server +printf "\n#### [Step 2] Kill the existing node server ####\n\n" + +KILL_CMD="sudo killall node" +printf "KILL_CMD='$KILL_CMD'\n\n" +eval $KILL_CMD +sleep 10 + +# 3. Restart the node server +printf "\n#### [Step 3] Restart the node server ####\n\n" + +MAX_OLD_SPACE_SIZE_MB=6000 + +START_CMD="nohup node --async-stack-traces --max-old-space-size=$MAX_OLD_SPACE_SIZE_MB client/index.js >/dev/null 2>error_logs.txt &" +printf "START_CMD='$START_CMD'\n" +eval $START_CMD + +# 4. Wait until the node server catches up +printf "\n#### [Step 4] Wait until the node server catches up ####\n\n" + +SECONDS=0 +loopCount=0 + +generate_post_data() +{ + cat <\n" + printf "Example: bash restart_tracker_gcp.sh 5\n" + exit +fi + +# 1. Configure env vars +printf "\n#### [Step 1] Configure env vars ####\n\n" + +NUM_NODES="$1" + +# 2. Kill the existing tracker server +printf "\n#### [Step 2] Kill the existing tracker server ####\n\n" + +KILL_CMD="sudo killall node" +printf "KILL_CMD='$KILL_CMD'\n\n" +eval $KILL_CMD +sleep 10 + +# 3. Restart the tracker server +printf "\n#### [Step 3] Restart the tracker server ####\n\n" + +export CONSOLE_LOG=false + +START_CMD="nohup node --async-stack-traces tracker-server/index.js >/dev/null 2>error_logs.txt &" +printf "START_CMD='$START_CMD'\n" +eval $START_CMD + +# 4. Wait until the tracker server catches up +printf "\n#### [Step 4] Wait until the tracker server catches up ####\n\n" + +SECONDS=0 +loopCount=0 + +while : +do + numAliveNodes=$(curl -m 20 -X GET -H "Content-Type: application/json" "http://localhost:8080/network_status" | jq -r '.numAliveNodes') + printf "\nnumAliveNodes = ${numAliveNodes}\n" + if [[ "$numAliveNodes" = "$NUM_NODES" ]]; then + printf "\nBlockchain Tracker server is running!\n" + printf "\nTime it took to sync in seconds: $SECONDS\n" + break + fi + ((loopCount++)) + printf "\nLoop count: ${loopCount}\n" + sleep 20 +done + +printf "\n* << Tracker server successfully deployed! ************************************\n\n" diff --git a/setup_blockchain_ubuntu.sh b/setup_blockchain_ubuntu.sh index ef34718e8..d1fd39098 100644 --- a/setup_blockchain_ubuntu.sh +++ b/setup_blockchain_ubuntu.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash echo 'Installing NodeJS..' sudo apt update diff --git a/setup_monitoring_gcp.sh b/setup_monitoring_gcp.sh index 27cec41cf..9a3deacaa 100644 --- a/setup_monitoring_gcp.sh +++ b/setup_monitoring_gcp.sh @@ -1,8 +1,8 @@ -#!/bin/sh +#!/bin/bash if [[ "$#" -lt 1 ]]; then - echo "Usage: sh setup_monitoring_gcp.sh [dev|staging|spring|summer]" - echo "Example: sh setup_monitoring_gcp.sh dev" + echo "Usage: bash setup_monitoring_gcp.sh [dev|staging|spring|summer]" + echo "Example: bash setup_monitoring_gcp.sh dev" exit fi diff --git a/setup_monitoring_ubuntu.sh b/setup_monitoring_ubuntu.sh index edb4b7090..d40c2819e 100644 --- a/setup_monitoring_ubuntu.sh +++ b/setup_monitoring_ubuntu.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash echo 'Installing NodeJS..' sudo apt update diff --git a/start_monitoring_gcp.sh b/start_monitoring_gcp.sh index 74bdaecfb..c97a2d1cc 100644 --- a/start_monitoring_gcp.sh +++ b/start_monitoring_gcp.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash echo 'Starting up Prometheus..' cd prometheus diff --git a/start_node_genesis_gcp.sh b/start_node_genesis_gcp.sh index 590842239..51a762c70 100644 --- a/start_node_genesis_gcp.sh +++ b/start_node_genesis_gcp.sh @@ -1,8 +1,8 @@ -#!/bin/sh +#!/bin/bash if [[ "$#" -lt 2 ]]; then - echo "Usage: sh start_node_genesis_gcp.sh [dev|staging|spring|summer] " - echo "Example: sh start_node_genesis_gcp.sh spring 0 0" + echo "Usage: bash start_node_genesis_gcp.sh [dev|staging|spring|summer] " + echo "Example: bash start_node_genesis_gcp.sh spring 0 0" exit fi diff --git a/start_node_incremental_gcp.sh b/start_node_incremental_gcp.sh index 8f8c24657..850170711 100644 --- a/start_node_incremental_gcp.sh +++ b/start_node_incremental_gcp.sh @@ -1,8 +1,8 @@ -#!/bin/sh +#!/bin/bash if [[ "$#" -lt 4 ]] || [[ "$#" -gt 4 ]]; then - printf "Usage: sh start_node_incremental_gcp.sh [dev|staging|spring|summer] [fast|full]\n" - printf "Example: sh start_node_incremental_gcp.sh spring 0 0 fast\n" + printf "Usage: bash start_node_incremental_gcp.sh [dev|staging|spring|summer] [fast|full]\n" + printf "Example: bash start_node_incremental_gcp.sh spring 0 0 fast\n" exit fi @@ -142,11 +142,11 @@ printf "\n#### [Step 5] Kill old node server ####\n\n" KILL_CMD="sudo killall node" printf "KILL_CMD='$KILL_CMD'\n\n" eval $KILL_CMD +sleep 10 # 6. Start a new node server printf "\n#### [Step 6] Start new node server ####\n\n" -sleep 10 MAX_OLD_SPACE_SIZE_MB=6000 START_CMD="nohup node --async-stack-traces --max-old-space-size=$MAX_OLD_SPACE_SIZE_MB client/index.js >/dev/null 2>error_logs.txt &" diff --git a/start_servers_afan_local.sh b/start_servers_afan_local.sh index eb12b56c2..15230edd5 100755 --- a/start_servers_afan_local.sh +++ b/start_servers_afan_local.sh @@ -1,3 +1,5 @@ +#!/bin/bash + # PARENT CHAIN CONSOLE_LOG=true BLOCKCHAIN_DATA_DIR=~/ain_blockchain_data node ./tracker-server/index.js & sleep 5 diff --git a/start_servers_local.sh b/start_servers_local.sh index 3f0a76ca0..85032396d 100755 --- a/start_servers_local.sh +++ b/start_servers_local.sh @@ -1,3 +1,5 @@ +#!/bin/bash + # PARENT CHAIN BLOCKCHAIN_DATA_DIR=~/ain_blockchain_data node ./tracker-server/index.js & sleep 5 diff --git a/start_tracker_genesis_gcp.sh b/start_tracker_genesis_gcp.sh index a1c2a601a..c53092684 100644 --- a/start_tracker_genesis_gcp.sh +++ b/start_tracker_genesis_gcp.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash echo 'Killing jobs..' killall node diff --git a/start_tracker_incremental_gcp.sh b/start_tracker_incremental_gcp.sh index 71343f1f5..5d38620cd 100644 --- a/start_tracker_incremental_gcp.sh +++ b/start_tracker_incremental_gcp.sh @@ -1,8 +1,8 @@ -#!/bin/sh +#!/bin/bash if [[ "$#" -lt 1 ]] || [[ "$#" -gt 1 ]]; then - printf "Usage: sh start_tracker_incremental_gcp.sh \n" - printf "Example: sh start_tracker_incremental_gcp.sh 5\n" + printf "Usage: bash start_tracker_incremental_gcp.sh \n" + printf "Example: bash start_tracker_incremental_gcp.sh 5\n" exit fi @@ -44,11 +44,11 @@ printf "\n#### [Step 5] Kill old tracker server ####\n\n" KILL_CMD="sudo killall node" printf "KILL_CMD='$KILL_CMD'\n\n" eval $KILL_CMD +sleep 10 # 6. Start new tracker server printf "\n#### [Step 6] Start new tracker server ####\n\n" -sleep 10 export CONSOLE_LOG=false START_CMD="nohup node --async-stack-traces tracker-server/index.js >/dev/null 2>error_logs.txt &" diff --git a/stop_servers_local.sh b/stop_servers_local.sh index f9ccbd0c2..f014f6d6d 100755 --- a/stop_servers_local.sh +++ b/stop_servers_local.sh @@ -1,3 +1,5 @@ +#!/bin/bash + killall -9 node # SIGKILL rm -rf ~/ain_blockchain_data/ BASEDIR=$(dirname "$0") diff --git a/tools/checkout/config_gcp.js b/tools/checkout/config_gcp.js new file mode 100644 index 000000000..05a19c224 --- /dev/null +++ b/tools/checkout/config_gcp.js @@ -0,0 +1,10 @@ +module.exports = { + endpointUrl: "http://dev-node.ainetwork.ai:8080", + userAddr: "0x09A0d53FDf1c36A131938eb379b98910e55EEfe1", + userPrivateKey: "ee0b1315d446e5318eb6eb4e9d071cd12ef42d2956d546f9acbdc3b75c469640", + recipientAddr: "0x09A0d53FDf1c36A131938eb379b98910e55EEfe1", + tokenPoolAddr: "0x20ADd3d38405ebA6338CB9e57a0510DEB8f8e000", + tokenPoolPrivateKey: "REDACTED", + checkoutId: "0", + tokenAmount: 100 +}; \ No newline at end of file diff --git a/tools/checkout/config_local.js b/tools/checkout/config_local.js new file mode 100644 index 000000000..88f69e3b6 --- /dev/null +++ b/tools/checkout/config_local.js @@ -0,0 +1,10 @@ +module.exports = { + endpointUrl: "http://localhost:8081", + userAddr: "0x09A0d53FDf1c36A131938eb379b98910e55EEfe1", + userPrivateKey: "ee0b1315d446e5318eb6eb4e9d071cd12ef42d2956d546f9acbdc3b75c469640", + recipientAddr: "0x09A0d53FDf1c36A131938eb379b98910e55EEfe1", + tokenPoolAddr: "0x20ADd3d38405ebA6338CB9e57a0510DEB8f8e000", + tokenPoolPrivateKey: "REDACTED", + checkoutId: "0", + tokenAmount: 100 +}; \ No newline at end of file diff --git a/tools/checkout/sendCloseCheckoutTx.js b/tools/checkout/sendCloseCheckoutTx.js new file mode 100644 index 000000000..604d0e5e9 --- /dev/null +++ b/tools/checkout/sendCloseCheckoutTx.js @@ -0,0 +1,63 @@ +const path = require('path'); +const { signAndSendTx, confirmTransaction } = require('../util'); +let config = {}; + +function buildCloseCheckoutTxBody(fromAddr, tokenAmount, checkoutId, failed = false) { + const response = { + tx_hash: '0xETH_TX_HASH' + }; + if (failed) { + response.status = 'FAILURE'; + response.error_message = 'Ethereum tx failed..' + } else { + response.status = 0; + } + return { + operation: { + type: 'SET_VALUE', + ref: `/checkout/history/${fromAddr}/${checkoutId}`, + value: { + request: { + amount: tokenAmount, + type: 'ETH', + token_id: '0xB16c0C80a81f73204d454426fC413CAe455525A7', + recipient: config.recipientAddr + }, + response + }, + is_global: true, + }, + timestamp: Date.now(), + nonce: -1 + } +} + +async function sendTransaction(failed) { + console.log('\n*** sendTransaction():'); + const timestamp = Date.now(); + const txBody = buildCloseCheckoutTxBody(config.userAddr, config.tokenAmount, config.checkoutId, failed); + console.log(`txBody: ${JSON.stringify(txBody, null, 2)}`); + + const txInfo = await signAndSendTx(config.endpointUrl, txBody, config.tokenPoolPrivateKey); + console.log(`txInfo: ${JSON.stringify(txInfo, null, 2)}`); + if (!txInfo.success) { + console.log(`Close checkout transaction failed.`); + process.exit(0); + } + await confirmTransaction(config.endpointUrl, timestamp, txInfo.txHash); +} + +async function processArguments() { + if (process.argv.length !== 3 && process.argv.length !== 4) { + usage(); + } + config = require(path.resolve(__dirname, process.argv[2])); + await sendTransaction(process.argv[3] === '--failed'); +} + +function usage() { + console.log('\nExample commandlines:\n node sendCloseCheckoutTx.js config_local.js [--failed]\n') + process.exit(0) +} + +processArguments(); diff --git a/tools/checkout/sendOpenCheckoutTx.js b/tools/checkout/sendOpenCheckoutTx.js new file mode 100644 index 000000000..3da73ba09 --- /dev/null +++ b/tools/checkout/sendOpenCheckoutTx.js @@ -0,0 +1,51 @@ +const path = require('path'); +const { signAndSendTx, confirmTransaction } = require('../util'); +let config = {}; + +function buildOpenCheckoutTxBody(fromAddr, tokenAmount, checkoutId) { + return { + operation: { + type: 'SET_VALUE', + ref: `/checkout/requests/${fromAddr}/${checkoutId}`, + value: { + amount: tokenAmount, + type: 'ETH', + token_id: '0xB16c0C80a81f73204d454426fC413CAe455525A7', + recipient: config.recipientAddr + }, + is_global: true, + }, + timestamp: Date.now(), + nonce: -1 + } +} + +async function sendTransaction() { + console.log('\n*** sendTransaction():'); + const timestamp = Date.now(); + const txBody = buildOpenCheckoutTxBody(config.userAddr, config.tokenAmount, config.checkoutId); + console.log(`txBody: ${JSON.stringify(txBody, null, 2)}`); + + const txInfo = await signAndSendTx(config.endpointUrl, txBody, config.userPrivateKey); + console.log(`txInfo: ${JSON.stringify(txInfo, null, 2)}`); + if (!txInfo.success) { + console.log(`Open checkout transaction failed.`); + process.exit(0); + } + await confirmTransaction(config.endpointUrl, timestamp, txInfo.txHash); +} + +async function processArguments() { + if (process.argv.length !== 3) { + usage(); + } + config = require(path.resolve(__dirname, process.argv[2])); + await sendTransaction(); +} + +function usage() { + console.log('\nExample commandlines:\n node sendOpenCheckoutTx.js config_local.js\n') + process.exit(0) +} + +processArguments(); diff --git a/tools/cicd/deployment.js b/tools/cicd/deployment.js index 909ff2df8..f918b64de 100644 --- a/tools/cicd/deployment.js +++ b/tools/cicd/deployment.js @@ -101,7 +101,7 @@ const main = async () => { const insightVersion = getVersion(`${INSIGHT}/src/data/constants`, 'const.js', 'VERSION', 1); const faucetVersion = getVersionFromAinJs(ainJsVersion, FAUCET); const connectVersion = faucetVersion; - const pipelineVersion = getVersion(`${PIPELINE}/constants`, 'const.js', 'AIN_PROTOCOL_VERSION', 1); + const pipelineVersion = getVersion(`${PIPELINE}/constants`, 'const.js', 'AIN_PROTOCOL_VERSION', 3); const dataVersion = getVersionFromAinJs(ainJsVersion, DATA); const exporterVersion = GPT2Version; @@ -125,7 +125,8 @@ const main = async () => { // Compare versions and set color const currentRowNumber = (await sheet.getRows()).length + 1; await sheet.loadCells(`E${currentRowNumber}:L${currentRowNumber}`); - Object.keys(CELL_DICTIONARY).forEach(repo => { + for (const repo of Object.keys(CELL_DICTIONARY)) { + if (!semver.valid(row[repo])) continue; const isLower = semver.lt(row[repo], row.min); if (isLower) { const latestAinJsCell = sheet.getCellByA1(`${CELL_DICTIONARY[repo]}${currentRowNumber}`); @@ -135,7 +136,7 @@ const main = async () => { blue: 0 }; } - }); + } await sheet.saveUpdatedCells(); } diff --git a/tools/he-health-care/config_local.js b/tools/he-health-care/config_local.js new file mode 100644 index 000000000..3efd95c1f --- /dev/null +++ b/tools/he-health-care/config_local.js @@ -0,0 +1,9 @@ +module.exports = { + serviceOwnerAddr: '0xb2585543Cfcfb79CF73a1a14b2DfBC411913940F', + serviceOwnerPrivateKey: 'd910c1835eaa89f15452aa3f0bd95f61fb9a04464150e37d617a40ed0071558c', + userAddr: '0x282cc0b0c38Bcf800F4CD620F5476F9960837E3A', + userPrivateKey: 'd64c90a4c5c07418c3db8da0cb786f341ba59fa10550babfbd40c8f6f8c57b81', + endpointUrl: 'http://localhost:8081', + healthCareAppName: 'he_health_care', + healthCareServiceName: 'https://ainetwork.ai', +}; diff --git a/tools/he-health-care/sendCreateAppTx.js b/tools/he-health-care/sendCreateAppTx.js new file mode 100644 index 000000000..448675d37 --- /dev/null +++ b/tools/he-health-care/sendCreateAppTx.js @@ -0,0 +1,29 @@ +const { signAndSendTx } = require('../util'); +const { healthCareAppName, endpointUrl, serviceOwnerPrivateKey, serviceOwnerAddr } = require('./config_local'); + +function buildCreateAppTxBody(appName, timestamp) { + return { + operation: { + type: 'SET_VALUE', + ref: `/manage_app/${appName}/create/${timestamp}`, + value: { + admin: { + [serviceOwnerAddr]: true, + }, + }, + }, + timestamp, + nonce: -1, + }; +} + +async function main() { + // TODO(sanghee): Support 'node sendCreateAppTx.js ' and check args + const createHealthCareAppTxBody = buildCreateAppTxBody(healthCareAppName, Date.now()); + const createResult = await signAndSendTx(endpointUrl, createHealthCareAppTxBody, serviceOwnerPrivateKey); + if (!createResult.success) { + throw Error(`Can't create health care app (${JSON.stringify(createResult, null, 2)})`); + } +} + +main(); diff --git a/tools/he-health-care/sendSetFunctionTx.js b/tools/he-health-care/sendSetFunctionTx.js new file mode 100644 index 000000000..c8c5f0270 --- /dev/null +++ b/tools/he-health-care/sendSetFunctionTx.js @@ -0,0 +1,41 @@ +const { signAndSendTx } = require('../util'); +const { + healthCareAppName, + endpointUrl, + serviceOwnerPrivateKey, + healthCareServiceName, +} = require('./config_local'); + +const workerTriggerUrl = 'http://localhost:3000/trigger'; + +function buildSetFunctionTxBody(appName, timestamp) { + return { + operation: { + type: 'SET_FUNCTION', + ref: `/apps/${appName}/tasks/$key`, + value: { + '.function': { + 'he-trigger': { + function_type: 'REST', + event_listener: workerTriggerUrl, + service_name: healthCareServiceName, + function_id: 'he-trigger', + }, + }, + }, + }, + timestamp, + nonce: -1, + }; +} + +async function main() { + // TODO(sanghee): Support 'node sendSetFunctionTx.js ' and check args + const setFunctionTxBody = buildSetFunctionTxBody(healthCareAppName, Date.now()); + const setFunctionResult = await signAndSendTx(endpointUrl, setFunctionTxBody, serviceOwnerPrivateKey); + if (!setFunctionResult.success) { + throw Error(`Can't set function (${JSON.stringify(setFunctionResult, null, 2)})`); + } +} + +main(); diff --git a/tools/he-health-care/sendSetRuleTx.js b/tools/he-health-care/sendSetRuleTx.js new file mode 100644 index 000000000..5be7e2fc5 --- /dev/null +++ b/tools/he-health-care/sendSetRuleTx.js @@ -0,0 +1,29 @@ +const { signAndSendTx } = require('../util'); +const { healthCareAppName, endpointUrl, serviceOwnerPrivateKey } = require('./config_local'); + +function buildSetRuleTxBody(appName, timestamp) { + return { + operation: { + type: 'SET_RULE', + ref: `/apps/${appName}`, + value: { + '.rule': { + 'write': true, + }, + }, + }, + timestamp, + nonce: -1, + }; +} + +async function main() { + // TODO(sanghee): Support 'node sendSetRuleTx.js ' and check args + const setRuleTxBody = buildSetRuleTxBody(healthCareAppName, Date.now()); + const setRuleResult = await signAndSendTx(endpointUrl, setRuleTxBody, serviceOwnerPrivateKey); + if (!setRuleResult.success) { + throw Error(`Can't set rule (${JSON.stringify(setRuleResult, null, 2)})`); + } +} + +main(); diff --git a/tools/he-health-care/sendTaskRequestTx.js b/tools/he-health-care/sendTaskRequestTx.js new file mode 100644 index 000000000..2a89eb569 --- /dev/null +++ b/tools/he-health-care/sendTaskRequestTx.js @@ -0,0 +1,26 @@ +const { signAndSendTx } = require('../util'); +const { healthCareAppName, endpointUrl, userPrivateKey } = require('./config_local'); + +function buildSetValueTxBody(appName, timestamp) { + const dummy = 'a'.repeat(4 * 1024); // 8KB + return { + operation: { + type: 'SET_VALUE', + ref: `/apps/${appName}/tasks/${timestamp}`, + value: dummy, + }, + timestamp, + nonce: -1, + }; +} + +async function main() { + // TODO(sanghee): Support 'node sendTaskRequestTx.js ' and check args + const setValueTxBody = buildSetValueTxBody(healthCareAppName, Date.now()); + const setValueResult = await signAndSendTx(endpointUrl, setValueTxBody, userPrivateKey); + if (!setValueResult.success) { + throw Error(`Can't set value (${JSON.stringify(setValueResult, null, 2)})`); + } +} + +main(); diff --git a/tools/he-health-care/startMockWorker.js b/tools/he-health-care/startMockWorker.js new file mode 100644 index 000000000..a74eca18a --- /dev/null +++ b/tools/he-health-care/startMockWorker.js @@ -0,0 +1,22 @@ +const express = require('express'); +const app = express(); +const port = 3000; + +app.use(express.json()); +app.use((req, res, next) => { + const method = req.method; + const url = req.url; + const status = res.statusCode; + const log = `${method}:${url} ${status}`; + console.log(log); + next(); +}); + +app.post('/trigger', (req, res) => { + res.send('Triggered!'); + console.log(`Body: ${JSON.stringify(req.body, null, 2)}`); +}); + +app.listen(port, () => { + console.log(`App listening at http://localhost:${port}`); +}); diff --git a/tools/util.js b/tools/util.js index de18406f2..803fddb8a 100644 --- a/tools/util.js +++ b/tools/util.js @@ -1,12 +1,15 @@ const _ = require('lodash'); const axios = require('axios'); -const { CURRENT_PROTOCOL_VERSION } = require('../common/constants'); +const { + CURRENT_PROTOCOL_VERSION, + CHAIN_ID +} = require('../common/constants'); const CommonUtil = require('../common/common-util'); // FIXME(minsulee2): this is duplicated function see: ./common/network-util.js function signAndSendTx(endpointUrl, txBody, privateKey) { console.log('\n*** signAndSendTx():'); - const {txHash, signedTx} = CommonUtil.signTransaction(txBody, privateKey); + const { txHash, signedTx } = CommonUtil.signTransaction(txBody, privateKey, CHAIN_ID); console.log(`signedTx: ${JSON.stringify(signedTx, null, 2)}`); console.log(`txHash: ${txHash}`); console.log('Sending transaction...'); diff --git a/tracker-server/index.js b/tracker-server/index.js index f935b8303..0a890167a 100755 --- a/tracker-server/index.js +++ b/tracker-server/index.js @@ -8,7 +8,10 @@ const { v4: uuidv4 } = require('uuid'); const disk = require('diskusage'); const os = require('os'); const v8 = require('v8'); -const { CURRENT_PROTOCOL_VERSION } = require('../common/constants'); +const { + CURRENT_PROTOCOL_VERSION, + MAX_NUM_PEER_CANDIDATES_AT_ONCE +} = require('../common/constants'); const CommonUtil = require('../common/common-util'); const logger = require('../logger')('TRACKER_SERVER'); @@ -120,11 +123,7 @@ server.on('connection', (ws) => { logger.info(`\n<< Update from node [${abbrAddr(nodeInfo.address)}]`); logger.debug(`: ${JSON.stringify(nodeInfo, null, 2)}`); - let newManagedPeerInfoList = []; - if (nodeInfo.networkStatus.connectionStatus.outgoingPeers.length < - nodeInfo.networkStatus.connectionStatus.maxOutbound) { - newManagedPeerInfoList = assignRandomPeers(getPeerCandidates(nodeInfo.address)); - } + const newManagedPeerInfoList = assignRandomPeers(nodeInfo); const msgToNode = { newManagedPeerInfoList, numLivePeers: getNumAliveNodes() - 1 // except for me. @@ -164,34 +163,33 @@ function getNumNodes() { return Object.keys(peerNodes).length; } -function assignRandomPeers(candidates) { - if (_.isEmpty(candidates)) { - return []; - } - - const shuffled = _.shuffle(candidates); - if (shuffled.length > 1) { - return [shuffled.pop(), shuffled.pop()]; +function getMaxNumberOfNewPeers(nodeInfo) { + const numOfCandidates = nodeInfo.networkStatus.connectionStatus.maxOutbound - + nodeInfo.networkStatus.connectionStatus.outgoingPeers.length; + if (numOfCandidates >= MAX_NUM_PEER_CANDIDATES_AT_ONCE) { + return MAX_NUM_PEER_CANDIDATES_AT_ONCE; } else { - return shuffled; + return numOfCandidates; } } -function getPeerCandidates(myself) { - const candidates = []; - Object.values(peerNodes).forEach(nodeInfo => { - if (nodeInfo.address !== myself && - nodeInfo.isAlive === true && - !nodeInfo.networkStatus.connectionStatus.incomingPeers.includes(myself) && - nodeInfo.networkStatus.connectionStatus.incomingPeers.length < - nodeInfo.networkStatus.connectionStatus.maxInbound) { - candidates.push({ - address: nodeInfo.address, - url: nodeInfo.networkStatus.p2p.url - }); - } - }); - return candidates; +function assignRandomPeers(nodeInfo) { + const maxNumberOfNewPeers = getMaxNumberOfNewPeers(nodeInfo); + if (maxNumberOfNewPeers) { + const candidates = Object.values(peerNodes) + .filter(peer => + peer.address !== nodeInfo.address && + peer.isAlive === true && + !peer.networkStatus.connectionStatus.incomingPeers.includes(nodeInfo.address) && + peer.networkStatus.connectionStatus.incomingPeers.length < + peer.networkStatus.connectionStatus.maxInbound) + .map(peer => ({ address: peer.address, url: peer.networkStatus.p2p.url })); + + const shuffled = _.shuffle(candidates); + return shuffled.slice(0, maxNumberOfNewPeers); + } else { + return []; + } } function printNodesInfo() { diff --git a/tx-pool/transaction.js b/tx-pool/transaction.js index eecfd8287..f6a959d19 100644 --- a/tx-pool/transaction.js +++ b/tx-pool/transaction.js @@ -5,6 +5,7 @@ const { ENABLE_TX_SIG_VERIF_WORKAROUND, ENABLE_GAS_FEE_WORKAROUND, WriteDbOperations, + CHAIN_ID } = require('../common/constants'); const CommonUtil = require('../common/common-util'); @@ -51,7 +52,7 @@ class Transaction { // A devel method for bypassing the transaction verification. let signature = ''; if (!txBody.address) { - const signed = CommonUtil.signTransaction(txBody, privateKey); + const signed = CommonUtil.signTransaction(txBody, privateKey, CHAIN_ID); const sig = _.get(signed, 'signedTx.signature', null); if (!sig) { return null; diff --git a/unittest/block-pool.test.js b/unittest/block-pool.test.js index ef65a2956..22470c071 100644 --- a/unittest/block-pool.test.js +++ b/unittest/block-pool.test.js @@ -24,8 +24,8 @@ describe("BlockPool", () => { function createAndAddBlock(node, blockPool, lastBlock, number, epoch) { const block = Block.create( - lastBlock.hash, [], [], number, epoch, '', node.account.address, - {[node.account.address]: { [PredefinedDbPaths.STAKE]: 100000, [PredefinedDbPaths.PROPOSAL_RIGHT]: true } }, 0, 0); + lastBlock.hash, [], {}, [], number, epoch, '', node.account.address, + {[node.account.address]: { [PredefinedDbPaths.CONSENSUS_STAKE]: 100000, [PredefinedDbPaths.CONSENSUS_PROPOSAL_RIGHT]: true } }, 0, 0); const proposal = getTransaction(node, { operation: { type: 'SET_VALUE', @@ -33,7 +33,7 @@ describe("BlockPool", () => { value: { number: block.number, epoch: block.epoch, - validators: { [node.account.address]: { [PredefinedDbPaths.STAKE]: 100000, [PredefinedDbPaths.PROPOSAL_RIGHT]: true } }, + validators: { [node.account.address]: { [PredefinedDbPaths.CONSENSUS_STAKE]: 100000, [PredefinedDbPaths.CONSENSUS_PROPOSAL_RIGHT]: true } }, total_at_stake: 100000, proposer: node.account.address, block_hash: block.hash @@ -50,10 +50,11 @@ describe("BlockPool", () => { const voteTx = getTransaction(node, { operation: { type: 'SET_VALUE', - ref: `/consensus/number/${block.number}/vote`, + ref: `/consensus/number/${block.number}/${block.hash}/vote`, value: { block_hash: block.hash, - stake: 100000 + stake: 100000, + is_against: false } }, gas_price: 1 @@ -65,8 +66,8 @@ describe("BlockPool", () => { const lastBlock = node1.bc.lastBlock(); const addr = node1.account.address; const block = Block.create( - lastBlock.hash, [], [], lastBlock.number + 1, lastBlock.epoch + 1, '', addr, - {[addr]: { [PredefinedDbPaths.STAKE]: 100000, [PredefinedDbPaths.PROPOSAL_RIGHT]: true }}, 0, 0); + lastBlock.hash, [], {}, [], lastBlock.number + 1, lastBlock.epoch + 1, '', addr, + {[addr]: { [PredefinedDbPaths.CONSENSUS_STAKE]: 100000, [PredefinedDbPaths.CONSENSUS_PROPOSAL_RIGHT]: true }}, 0, 0); const proposalTx = getTransaction(node1, { operation: { type: 'SET_VALUE', @@ -74,7 +75,7 @@ describe("BlockPool", () => { value: { number: block.number, epoch: block.epoch, - validators: {[addr]: { [PredefinedDbPaths.STAKE]: 100000, [PredefinedDbPaths.PROPOSAL_RIGHT]: true } }, + validators: {[addr]: { [PredefinedDbPaths.CONSENSUS_STAKE]: 100000, [PredefinedDbPaths.CONSENSUS_PROPOSAL_RIGHT]: true } }, total_at_stake: 100000, proposer: addr, block_hash: block.hash @@ -94,8 +95,8 @@ describe("BlockPool", () => { const addr = node1.account.address; const lastBlock = node1.bc.lastBlock(); const block = Block.create( - lastBlock.hash, [], [], lastBlock.number + 1, lastBlock.epoch + 1, '', addr, - {[addr]: { [PredefinedDbPaths.STAKE]: 100000, [PredefinedDbPaths.PROPOSAL_RIGHT]: true }}, 0, 0); + lastBlock.hash, [], {}, [], lastBlock.number + 1, lastBlock.epoch + 1, '', addr, + {[addr]: { [PredefinedDbPaths.CONSENSUS_STAKE]: 100000, [PredefinedDbPaths.CONSENSUS_PROPOSAL_RIGHT]: true }}, 0, 0); const proposalTx = getTransaction(node1, { operation: { type: 'SET_VALUE', @@ -103,7 +104,7 @@ describe("BlockPool", () => { value: { number: block.number, epoch: block.epoch, - validators: {[addr]: { [PredefinedDbPaths.STAKE]: 100000, [PredefinedDbPaths.PROPOSAL_RIGHT]: true } }, + validators: {[addr]: { [PredefinedDbPaths.CONSENSUS_STAKE]: 100000, [PredefinedDbPaths.CONSENSUS_PROPOSAL_RIGHT]: true } }, total_at_stake: 100000, proposer: addr, block_hash: block.hash diff --git a/unittest/blockchain.test.js b/unittest/blockchain.test.js index 1decd0f19..b43c4def5 100644 --- a/unittest/blockchain.test.js +++ b/unittest/blockchain.test.js @@ -36,7 +36,7 @@ describe('Blockchain', () => { }); const lastBlock = node1.bc.lastBlock(); node1.addNewBlock(Block.create( - lastBlock.hash, [], [tx], lastBlock.number + 1, lastBlock.epoch + 1, '', + lastBlock.hash, [], {}, [tx], lastBlock.number + 1, lastBlock.epoch + 1, '', node1.account.address, {}, 0, 0)); assert.deepEqual( node1.bc.chain[node1.bc.chain.length -1].transactions[0], @@ -66,7 +66,7 @@ describe('Blockchain', () => { }); const lastBlock = node1.bc.lastBlock(); node1.addNewBlock(Block.create( - lastBlock.hash, [], [tx], lastBlock.number + 1, lastBlock.epoch + 1, '', + lastBlock.hash, [], {}, [tx], lastBlock.number + 1, lastBlock.epoch + 1, '', node1.account.address, {}, 0, 0)); node1.bc.chain[node1.bc.chain.length - 1].transactions = ':('; expect(Blockchain.validateChainSegment(node1.bc.chain)).to.equal(false); @@ -91,7 +91,7 @@ describe('Blockchain', () => { const lastBlock = node1.bc.lastBlock(); const finalRoot = node1.stateManager.getFinalRoot(); const block = Block.create( - lastBlock.hash, [], node1.tp.getValidTransactions(), lastBlock.number + 1, i, + lastBlock.hash, [], {}, node1.tp.getValidTransactions(), lastBlock.number + 1, i, finalRoot.getProofHash(), node1.account.address, [], 0, 0); if (block.number === 500) { blockHash = block.hash; diff --git a/unittest/common-util.test.js b/unittest/common-util.test.js index 7f8e22a7b..874514b0b 100644 --- a/unittest/common-util.test.js +++ b/unittest/common-util.test.js @@ -60,6 +60,31 @@ describe("CommonUtil", () => { }) }) + describe("toHexString", () => { + it("when non-string input", () => { + expect(CommonUtil.toHexString(0)).to.equal('0x'); + expect(CommonUtil.toHexString(10)).to.equal('0x'); + expect(CommonUtil.toHexString(-1)).to.equal('0x'); + expect(CommonUtil.toHexString(15.5)).to.equal('0x'); + expect(CommonUtil.toHexString(null)).to.equal('0x'); + expect(CommonUtil.toHexString(undefined)).to.equal('0x'); + expect(CommonUtil.toHexString(Infinity)).to.equal('0x'); + expect(CommonUtil.toHexString(NaN)).to.equal('0x'); + expect(CommonUtil.toHexString({})).to.equal('0x'); + expect(CommonUtil.toHexString({a: 'A'})).to.equal('0x'); + expect(CommonUtil.toHexString([])).to.equal('0x'); + expect(CommonUtil.toHexString([10])).to.equal('0x'); + expect(CommonUtil.toHexString(false)).to.equal('0x'); + }) + + it("when string input", () => { + expect(CommonUtil.toHexString('')).to.equal('0x'); + expect(CommonUtil.toHexString('0x0123456789abcdef')).to.equal('0x0123456789abcdef'); + expect(CommonUtil.toHexString('0x0123456789ABCDEF')).to.equal('0x0123456789abcdef'); + expect(CommonUtil.toHexString('aAzZ')).to.equal('0x61417a5a'); + }) + }) + describe("parseJsonOrNull", () => { it("when abnormal input", () => { assert.deepEqual(CommonUtil.parseJsonOrNull(''), null); @@ -421,7 +446,7 @@ describe("CommonUtil", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, } }, @@ -430,7 +455,7 @@ describe("CommonUtil", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, } }, @@ -456,7 +481,7 @@ describe("CommonUtil", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, } }, @@ -465,7 +490,7 @@ describe("CommonUtil", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, } }, @@ -493,7 +518,7 @@ describe("CommonUtil", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, } }, @@ -502,7 +527,7 @@ describe("CommonUtil", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, } }, @@ -528,7 +553,7 @@ describe("CommonUtil", () => { } } }, - "code": "FAILURE", // A function failed. + "code": 1, // A function failed. "bandwidth_gas_amount": 0, } }, @@ -537,7 +562,7 @@ describe("CommonUtil", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, } }, @@ -551,7 +576,7 @@ describe("CommonUtil", () => { "code": 0, "func_results": { "0x11111": { - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 10, } }, @@ -627,7 +652,7 @@ describe("CommonUtil", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, } }, @@ -636,7 +661,7 @@ describe("CommonUtil", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, } }, @@ -674,7 +699,7 @@ describe("CommonUtil", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, } }, @@ -683,7 +708,7 @@ describe("CommonUtil", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, } }, @@ -723,7 +748,7 @@ describe("CommonUtil", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, } }, @@ -732,7 +757,7 @@ describe("CommonUtil", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, } }, @@ -779,7 +804,7 @@ describe("CommonUtil", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, } }, @@ -811,7 +836,7 @@ describe("CommonUtil", () => { "code": 0, "func_results": { "0x11111": { - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 10, } }, @@ -873,7 +898,7 @@ describe("CommonUtil", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 10 } }, @@ -882,7 +907,7 @@ describe("CommonUtil", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 20, } }, @@ -913,7 +938,7 @@ describe("CommonUtil", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 10 } }, @@ -922,7 +947,7 @@ describe("CommonUtil", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 20, } }, @@ -956,7 +981,7 @@ describe("CommonUtil", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 10 } }, @@ -965,7 +990,7 @@ describe("CommonUtil", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 20, } }, @@ -1002,7 +1027,7 @@ describe("CommonUtil", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 10 } }, @@ -1011,7 +1036,7 @@ describe("CommonUtil", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 20 } }, @@ -1051,7 +1076,7 @@ describe("CommonUtil", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 10 } }, @@ -1060,7 +1085,7 @@ describe("CommonUtil", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 20 } }, @@ -1103,7 +1128,7 @@ describe("CommonUtil", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 10 } }, @@ -1112,7 +1137,7 @@ describe("CommonUtil", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 20 } }, diff --git a/unittest/consensus.test.js b/unittest/consensus.test.js index 91566b203..b21b48c2b 100644 --- a/unittest/consensus.test.js +++ b/unittest/consensus.test.js @@ -42,8 +42,8 @@ describe("Consensus", () => { const voteTx = getTransaction(node2, { operation: { type: 'SET_VALUE', - ref: `/consensus/number/1/vote/${node2.account.address}`, - value: { block_hash: lastBlock.hash, stake: 100000 } + ref: `/consensus/number/1/${lastBlock.hash}/vote/${node2.account.address}`, + value: { block_hash: lastBlock.hash, stake: 100000, is_against: false } }, nonce: -1, gas_price: 1 @@ -69,8 +69,8 @@ describe("Consensus", () => { const voteTx = getTransaction(node2, { operation: { type: 'SET_VALUE', - ref: `/consensus/number/${lastBlock.number}/vote/${addr}`, - value: { block_hash: lastBlock.hash, stake: 100000 } + ref: `/consensus/number/${lastBlock.number}/${lastBlock.hash}/vote/${addr}`, + value: { block_hash: lastBlock.hash, stake: 100000, is_against: false } }, nonce: -1, gas_price: 1 @@ -116,7 +116,7 @@ describe("Consensus", () => { // Bypass whitelist rule check (need owner's private key) const tempDb = node1.createTempDb(node1.db.stateVersion, 'CONSENSUS_UNIT_TEST', lastBlock.number); tempDb.writeDatabase( - [PredefinedDbPaths.VALUES_ROOT, PredefinedDbPaths.CONSENSUS, PredefinedDbPaths.WHITELIST, addr], + [PredefinedDbPaths.VALUES_ROOT, PredefinedDbPaths.CONSENSUS, PredefinedDbPaths.CONSENSUS_WHITELIST, addr], true); node1.cloneAndFinalizeVersion(tempDb.stateVersion, -1); // Bypass already existing final state version diff --git a/unittest/db.test.js b/unittest/db.test.js index fce55e399..a9c6a5b6e 100644 --- a/unittest/db.test.js +++ b/unittest/db.test.js @@ -12,7 +12,6 @@ const { GenesisFunctions, GenesisRules, GenesisOwners, - ProofProperties, PredefinedDbPaths, StateInfoProperties, SERVICE_STATE_BUDGET, @@ -414,55 +413,55 @@ describe("DB operations", () => { it('when retrieving value with include_proof', () => { assert.deepEqual(node.db.getValue('/apps/test', { includeProof: true }), { - ".proof_hash": "0x753458b0796af84a76e4256ec295c65a7563f5ef2c855f2ecc3e15f47887b8bf", + ".proof_hash": "0xf1f51cb458ef3881fefbecf91db5450e601d462099e43776863f2cbb78642286", "ai": { - ".proof_hash": "0x475a10041bd4a36132a63b16f5ba0a2b528642f01a8d00923969507289239c44", + ".proof_hash": "0xc49ca014a4d5328cfdb74164bcb8c00578719335194b07a8b91af1db0048f0bb", ".proof_hash:baz": "0x74e6d7e9818333ef5d6f4eb74dc0ee64537c9e142e4fe55e583476a62b539edf", ".proof_hash:comcom": "0x90840252cdaacaf90d95c14f9d366f633fd53abf7a2c359f7abfb7f651b532b5", ".proof_hash:foo": "0xea86f62ccb8ed9240afb6c9090be001ef7859bf40e0782f2b8d3579b3d8310a4", "baz": "qux", "comcom": 123, - "foo": "bar", + "foo": "bar" }, "decrement": { - ".proof_hash": "0x875a06d5687f3fe01ddee9a76b48c5439a233ad4753980d239e7b91793f4b2a3", + ".proof_hash": "0x11d1aa4946a3e44e3d467d4da85617d56aecd2559fdd6d9e5dd8fb6b5ded71b8", ".proof_hash:value": "0xc3c28ad8a683cb7f3d8cf05420651e08e14564e18a1805fe33720cd9d7d2deb2", - "value": 20, + "value": 20 }, "increment": { - ".proof_hash": "0x875a06d5687f3fe01ddee9a76b48c5439a233ad4753980d239e7b91793f4b2a3", + ".proof_hash": "0x11d1aa4946a3e44e3d467d4da85617d56aecd2559fdd6d9e5dd8fb6b5ded71b8", ".proof_hash:value": "0xc3c28ad8a683cb7f3d8cf05420651e08e14564e18a1805fe33720cd9d7d2deb2", - "value": 20, + "value": 20 }, "nested": { - ".proof_hash": "0xb2d436a46347f073c9fbbc6daf074c6020493d39bbcf6219f483d80ba4fcba12", + ".proof_hash": "0x8763e301c728729e38c1f5500a2af7163783bdf0948a7baf7bc87b35f33b347f", "far": { - ".proof_hash": "0xdb10e1d3b0aa83908d4414d79940160b4ee59fab7363c3223685829b31c9912d", + ".proof_hash": "0xc8b9114b37d8ece398eb8dde73b00bf5037f6b11d97eff11b5212b5f30f32417", ".proof_hash:down": "0x4611868537ffbffa17f70f8ddb7cf5aacc6b4d1b32817315f631a2c7d6b6481d", - "down": 456, - }, + "down": 456 + } }, "shards": { - ".proof_hash": "0xca7a8628678dd40de218e17a9df8acbbf31e596397ffcf94af36c2134be31e16", + ".proof_hash": "0x8ab46f23c6dddce173709cbc935a33d816825ffebabc4f23d02c3d2aea85fba3", "disabled_shard": { - ".proof_hash": "0xd05c3b7418eedc09e24d61376ddffabc28245006150e8ade7ee586109821c3f9", + ".proof_hash": "0xc0d5ac161046ecbf67ae597b3e1d96e53e78d71c0193234f78f2514dbf952161", ".proof_hash:path": "0xd024945cba75febe35837d24c977a187a6339888d99d505c1be63251fec52279", ".shard": { - ".proof_hash": "0xc1def4354bd8f269460896e38f288dba21d77d2c115a02285f6cce8f7e646fca", + ".proof_hash": "0x1908ddba2acbc0181cdc29b035c7ce371d3ed38f39b39cad3eb7e0704ccaa57b", ".proof_hash:sharding_enabled": "0x055600b34c3a8a69ea5dfc2cd2f92336933be237c8b265089f3114b38b4a540a", - "sharding_enabled": false, + "sharding_enabled": false }, - "path": 10, + "path": 10 }, "enabled_shard": { - ".proof_hash": "0xb9ab65702643eccef3d025b650a02af574b22fa52be1ac9272f20b382e21e84d", + ".proof_hash": "0x4b754c4a1a1f99d1ad9bc4a1edbeb7e2ceec6828313b52f8b880ee1cded3e4d3", ".proof_hash:path": "0xd024945cba75febe35837d24c977a187a6339888d99d505c1be63251fec52279", ".shard": { - ".proof_hash": "0xca256dadfca4c89edbc3de62b0732eac55d792b4661fdbb1c1455bfc1ef9048b", + ".proof_hash": "0xc308394fc297eb293cbef148c58665e9208a96e3664e86695db9d29c273dae96", ".proof_hash:sharding_enabled": "0x1eafc1e61d5b7b28f90a34330bf62265eeb466e012aa7318098003f37e4c61cc", - "sharding_enabled": true, + "sharding_enabled": true }, - "path": 10, + "path": 10 } } }) @@ -1968,7 +1967,7 @@ describe("DB operations", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0, } }, @@ -1977,7 +1976,7 @@ describe("DB operations", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0 } }, @@ -2073,7 +2072,7 @@ describe("DB operations", () => { } } }, - "code": "FAILURE", + "code": 1, "bandwidth_gas_amount": 0, } }, @@ -2082,7 +2081,7 @@ describe("DB operations", () => { } } }, - "code": "FAILURE", + "code": 1, "bandwidth_gas_amount": 0, } }, @@ -2383,7 +2382,7 @@ describe("DB operations", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0 } }, @@ -2392,7 +2391,7 @@ describe("DB operations", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0 } }, @@ -2507,7 +2506,7 @@ describe("DB operations", () => { } } }, - "code": "FAILURE", + "code": 1, "bandwidth_gas_amount": 0, } }, @@ -2516,7 +2515,7 @@ describe("DB operations", () => { } } }, - "code": "FAILURE", + "code": 1, "bandwidth_gas_amount": 0, } }, @@ -2604,7 +2603,7 @@ describe("DB operations", () => { const maxHeightTxBody = { operation: { type: 'SET_VALUE', - ref: '/apps/test/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20', + ref: '/apps/test/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30', value: 'some value', }, gas_price: 0, @@ -2625,7 +2624,7 @@ describe("DB operations", () => { }, state: { app: { - test: 2956 + test: 4596 }, service: 8 } @@ -2636,7 +2635,7 @@ describe("DB operations", () => { const overHeightTxBody = { operation: { type: 'SET_VALUE', - ref: '/apps/test/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21', + ref: '/apps/test/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31', value: 'some value', }, gas_price: 0, @@ -2646,7 +2645,7 @@ describe("DB operations", () => { const overHeightTx = Transaction.fromTxBody(overHeightTxBody, node.account.private_key); assert.deepEqual(node.db.executeTransaction(overHeightTx, false, true, node.bc.lastBlockNumber() + 1), { code: 23, - error_message: "Out of tree height limit (21 > 20)", + error_message: "Out of tree height limit (31 > 30)", gas_amount_charged: 0, bandwidth_gas_amount: 1, gas_cost_total: 0, @@ -2674,7 +2673,7 @@ describe("DB operations", () => { result: { timestamp: 1568798344000, tx_hash: "0xb23fbdfb7b38dc4859872c565b1b0e4140ca4b7896397c817a290b2507e79708", - code: "SUCCESS" + code: 0 } } } @@ -2694,7 +2693,7 @@ describe("DB operations", () => { app: {}, }, state: { - service: 3550560 + service: 3541560 } }; const overSizeTxBody = { @@ -2717,16 +2716,16 @@ describe("DB operations", () => { const overSizeTx = Transaction.fromTxBody(overSizeTxBody, node.account.private_key); const res = node.db.executeTransaction(overSizeTx, false, true, node.bc.lastBlockNumber() + 1); assert.deepEqual(res.code, 25); - assert.deepEqual(res.error_message, "Exceeded state budget limit for services (10791482 > 10000000)"); + assert.deepEqual(res.error_message, "Exceeded state budget limit for services (10757870 > 10000000)"); assert.deepEqual(res.gas_amount_total, expectedGasAmountTotal); - assert.deepEqual(res.gas_cost_total, 3.5550599999999997); + assert.deepEqual(res.gas_cost_total, 3.5460599999999998); }); it("cannot exceed apps state budget", () => { const overSizeTree = {}; for (let i = 0; i < 1000; i++) { overSizeTree[i] = {}; - for (let j = 0; j < 100; j++) { + for (let j = 0; j < 75; j++) { overSizeTree[i][j] = 'a'; } } @@ -2743,10 +2742,10 @@ describe("DB operations", () => { const overSizeTx = Transaction.fromTxBody(overSizeTxBody, node.account.private_key); const res = node.db.executeTransaction(overSizeTx, false, true, node.bc.lastBlockNumber() + 1); assert.deepEqual(res.code, 26); - assert.deepEqual(res.error_message, "Exceeded state budget limit for apps (16769332 > 9000000)"); + assert.deepEqual(res.error_message, "Exceeded state budget limit for apps (12621228 > 9000000)"); assert.deepEqual(res.gas_amount_total, { bandwidth: { service: 0, app: { test: 1 } }, - state: { service: 8, app: { test: 16746108 } } + state: { service: 8, app: { test: 12596108 } } }); assert.deepEqual(res.gas_cost_total, 0); }); @@ -2862,7 +2861,6 @@ describe("DB operations", () => { "terminal_1c": "", "node_1a": { "node_2": { - "terminal_3": null, "node_3": "a value" } }, @@ -2954,7 +2952,6 @@ describe("DB operations", () => { "terminal_1c": "", "node_1a": { "node_2": { - "terminal_3": null, "node_3": "another value" } }, @@ -2968,6 +2965,8 @@ describe("DB operations", () => { expect(node.db.setValue( "/apps/test/empty_values/node_0/node_1a/node_2/node_3", null).code).to.equal(0); assert.deepEqual(node.db.getValue("/apps/test/empty_values/node_0"), { + "terminal_1a": null, + "terminal_1b": null, "terminal_1c": "", "node_1b": { "terminal_2": null, @@ -3723,7 +3722,7 @@ describe("DB sharding config", () => { it("setValue with isGlobal = true", () => { expect(node.db.setValue( "/apps/afan/apps/test/test_sharding/some/path/to/value", newValue, { addr: '0x09A0d53FDf1c36A131938eb379b98910e55EEfe1' }, - null, { extra: { executed_at: 1234567890000 }}, { isGlobal: true }).code) + null, { extra: { executed_at: 1234567890000 }}, 1234567890000, { isGlobal: true }).code) .to.equal(0); expect(node.db.getValue("/apps/test/test_sharding/some/path/to/value")).to.equal(newValue); }) @@ -3731,7 +3730,7 @@ describe("DB sharding config", () => { it("setValue with isGlobal = true and non-existing path", () => { expect(node.db.setValue( "/apps/some/non-existing/path", newValue, { addr: '0x09A0d53FDf1c36A131938eb379b98910e55EEfe1' }, - null, { extra: { executed_at: 1234567890000 }}, { isGlobal: true }).code) + null, { extra: { executed_at: 1234567890000 }}, 1234567890000, { isGlobal: true }).code) .to.equal(0); }) @@ -3746,7 +3745,7 @@ describe("DB sharding config", () => { it("setValue with isGlobal = true and non-writable path with sharding", () => { expect(node.db.setValue( "/apps/afan/apps/test/test_sharding/shards/enabled_shard/path", 20, '0x09A0d53FDf1c36A131938eb379b98910e55EEfe1', null, null, - { isGlobal: true }).code) + 1234567890000, { isGlobal: true }).code) .to.equal(0); expect(node.db.getValue("/apps/afan/apps/test/test_sharding/shards/enabled_shard/path", { isShallow: false, isGlobal: true })) .to.equal(10); // value unchanged @@ -3761,7 +3760,7 @@ describe("DB sharding config", () => { it("setValue with isGlobal = true and writable path with sharding", () => { expect(node.db.setValue( "apps/afan/apps/test/test_sharding/shards/disabled_shard/path", 20, { addr: '0x09A0d53FDf1c36A131938eb379b98910e55EEfe1' }, - null, { extra: { executed_at: 1234567890000 }}, { isGlobal: true }).code) + null, { extra: { executed_at: 1234567890000 }}, 1234567890000, { isGlobal: true }).code) .to.equal(0); expect(node.db.getValue("apps/afan/apps/test/test_sharding/shards/disabled_shard/path", { isShallow: false, isGlobal: true })) .to.equal(20); // value changed @@ -3778,14 +3777,14 @@ describe("DB sharding config", () => { it("incValue with isGlobal = true", () => { expect(node.db.incValue( "/apps/afan/apps/test/test_sharding/some/path/to/number", incDelta, { addr: '0x09A0d53FDf1c36A131938eb379b98910e55EEfe1' }, - null, { extra: { executed_at: 1234567890000 }}, { isGlobal: true }).code) + null, { extra: { executed_at: 1234567890000 }}, 1234567890000, { isGlobal: true }).code) .to.equal(0); expect(node.db.getValue("/apps/test/test_sharding/some/path/to/number")).to.equal(10 + incDelta); }) it("incValue with isGlobal = true and non-existing path", () => { expect(node.db.incValue( - "/apps/some/non-existing/path", incDelta, { addr: '0x09A0d53FDf1c36A131938eb379b98910e55EEfe1' }, null, null, { isGlobal: true }).code) + "/apps/some/non-existing/path", incDelta, { addr: '0x09A0d53FDf1c36A131938eb379b98910e55EEfe1' }, null, null, 1234567890000, { isGlobal: true }).code) .to.equal(0); }) @@ -3800,7 +3799,7 @@ describe("DB sharding config", () => { it("setValue with isGlobal = true and non-writable path with sharding", () => { expect(node.db.incValue( "/apps/afan/apps/test/test_sharding/shards/enabled_shard/path", 5, { addr: '0x09A0d53FDf1c36A131938eb379b98910e55EEfe1' }, - null, null, { isGlobal: true }).code) + null, null, 1234567890000, { isGlobal: true }).code) .to.equal(0); expect(node.db.getValue("apps/afan/apps/test/test_sharding/shards/enabled_shard/path", { isShallow: false, isGlobal: true })) .to.equal(10); // value unchanged @@ -3815,7 +3814,7 @@ describe("DB sharding config", () => { it("setValue with isGlobal = true and writable path with sharding", () => { expect(node.db.incValue( "/apps/afan/apps/test/test_sharding/shards/disabled_shard/path", 5, { addr: '0x09A0d53FDf1c36A131938eb379b98910e55EEfe1' }, - null, { extra: { executed_at: 1234567890000 }}, { isGlobal: true }).code) + null, { extra: { executed_at: 1234567890000 }}, 1234567890000, { isGlobal: true }).code) .to.equal(0); expect(node.db.getValue("/apps/afan/apps/test/test_sharding/shards/disabled_shard/path", { isShallow: false, isGlobal: true })) .to.equal(15); // value changed @@ -3832,14 +3831,14 @@ describe("DB sharding config", () => { it("decValue with isGlobal = true", () => { expect(node.db.decValue( "/apps/afan/apps/test/test_sharding/some/path/to/number", decDelta, { addr: '0x09A0d53FDf1c36A131938eb379b98910e55EEfe1' }, - null, { extra: { executed_at: 1234567890000 }}, { isGlobal: true }).code) + null, { extra: { executed_at: 1234567890000 }}, 1234567890000, { isGlobal: true }).code) .to.equal(0); expect(node.db.getValue("/apps/test/test_sharding/some/path/to/number")).to.equal(10 - decDelta); }) it("decValue with isGlobal = true and non-existing path", () => { expect(node.db.decValue( - "/apps/some/non-existing/path", decDelta, { addr: '0x09A0d53FDf1c36A131938eb379b98910e55EEfe1' }, null, null, { isGlobal: true }).code) + "/apps/some/non-existing/path", decDelta, { addr: '0x09A0d53FDf1c36A131938eb379b98910e55EEfe1' }, null, null, 1234567890000, { isGlobal: true }).code) .to.equal(0); }) @@ -3854,7 +3853,7 @@ describe("DB sharding config", () => { it("setValue with isGlobal = true and non-writable path with sharding", () => { expect(node.db.decValue( "/apps/afan/apps/test/test_sharding/shards/enabled_shard/path", 5, { addr: '0x09A0d53FDf1c36A131938eb379b98910e55EEfe1' }, - null, null, { isGlobal: true }).code) + null, null, 1234567890000, { isGlobal: true }).code) .to.equal(0); expect(node.db.getValue( "/apps/afan/apps/test/test_sharding/shards/enabled_shard/path", { isShallow: false, isGlobal: true })) @@ -3871,7 +3870,7 @@ describe("DB sharding config", () => { it("setValue with isGlobal = true and writable path with sharding", () => { expect(node.db.decValue( "/apps/afan/apps/test/test_sharding/shards/disabled_shard/path", 5, { addr: '0x09A0d53FDf1c36A131938eb379b98910e55EEfe1' }, - null, { extra: { executed_at: 1234567890000 }}, { isGlobal: true }).code) + null, { extra: { executed_at: 1234567890000 }}, 1234567890000, { isGlobal: true }).code) .to.equal(0); expect(node.db.getValue("/apps/afan/apps/test/test_sharding/shards/disabled_shard/path", { isShallow: false, isGlobal: true })) .to.equal(5); // value changed @@ -4455,47 +4454,11 @@ describe("Proof hash", () => { describe("State proof (getStateProof)", () => { it("tests proof with a null case", () => { - const rootNode = node.db.stateRoot; assert.deepEqual(null, node.db.getStateProof('/apps/test/test')); }); it("tests proof with owners, rules, values and functions", () => { - const rootNode = node.db.stateRoot; - const ownersNode = node.db.getRefForReading(['owners']); - const rulesNode = node.db.getRefForReading(['rules']); - const valuesNode = node.db.getRefForReading(['values']); - const functionNode = node.db.getRefForReading(['functions']); - const rootProof = { [ProofProperties.PROOF_HASH]: rootNode.getProofHash() }; - const secondLevelProof = JSON.parse(JSON.stringify(rootProof)); - rootNode.getChildLabels().forEach(label => { - Object.assign(secondLevelProof, - { [label]: { [ProofProperties.PROOF_HASH]: rootNode.getChild(label).getProofHash() } }); - }); - const ownersProof = JSON.parse(JSON.stringify(secondLevelProof)); - ownersNode.getChildLabels().forEach(label => { - Object.assign(ownersProof.owners, - { [label]: { [ProofProperties.PROOF_HASH]: ownersNode.getChild(label).getProofHash() } }); - }); - const rulesProof = JSON.parse(JSON.stringify(secondLevelProof)); - rulesNode.getChildLabels().forEach(label => { - Object.assign(rulesProof.rules, - { [label]: { [ProofProperties.PROOF_HASH]: rulesNode.getChild(label).getProofHash() } }); - }); - const valuesProof = JSON.parse(JSON.stringify(secondLevelProof)); - valuesNode.getChildLabels().forEach(label => { - Object.assign(valuesProof.values, - { [label]: { [ProofProperties.PROOF_HASH]: valuesNode.getChild(label).getProofHash() } }); - }); - const functionsProof = JSON.parse(JSON.stringify(secondLevelProof)); - functionNode.getChildLabels().forEach(label => { - Object.assign(functionsProof.functions, - { [label]: { [ProofProperties.PROOF_HASH]: functionNode.getChild(label).getProofHash() } }); - }); - assert.deepEqual(rootProof, node.db.getStateProof('/')); - assert.deepEqual(ownersProof, node.db.getStateProof('/owners/apps')); - assert.deepEqual(rulesProof, node.db.getStateProof('/rules/apps')); - assert.deepEqual(valuesProof, node.db.getStateProof('/values/apps')); - assert.deepEqual(functionsProof, node.db.getStateProof('/functions/apps')); + expect(node.db.getStateProof('/')['.proof_hash']).to.not.equal(null); }); }); }); @@ -4542,11 +4505,11 @@ describe("State info (getStateInfo)", () => { // Existing paths. assert.deepEqual(node.db.getStateInfo('/values/apps/test/label1'), { - "proof_hash": "0x213304021f1ea1e8f7954c815d49207c0a42ab4bdf09929263369fa5f4d77c8b", + "proof_hash": "0xe4fd1f81f45b74ccd16540efa905abde37b6660d3fe9fb18eb3bf6b3e7cd215a", "tree_bytes": 922, "tree_height": 2, "tree_size": 5, - "version": "NODE:0", + "version": "NODE:0" }); assert.deepEqual(node.db.getStateInfo('/values/apps/test/label1/label11'), { "proof_hash": "0xa8681012b27ff56a45aa80f6f4d95c66c3349046cdd18cdc77028b6a634c9b0b", @@ -4556,7 +4519,7 @@ describe("State info (getStateInfo)", () => { "version": "NODE:0", }); assert.deepEqual(node.db.getStateInfo('/values/apps/test/label1/label12'), { - "proof_hash": "0xbc8b6e1e9e369b5af09e14fea3769c348d66e453b3a2fc6dbec0d00278e094e7", + "proof_hash": "0x19037329315c0182c0f965a786e6d0659bb374e907a3937f885f0da3984cfa6e", "tree_bytes": 560, "tree_height": 1, "tree_size": 3, @@ -4577,7 +4540,7 @@ describe("State info (getStateInfo)", () => { "version": "NODE:0", }); assert.deepEqual(node.db.getStateInfo('/values/apps/test/label2'), { - "proof_hash": "0x7b614d2449c2ce477ac040c52b78798e5ff36a20b83115b6af8688f5e88a813f", + "proof_hash": "0x0088bff9a36081510c230f5fd6b6581b81966b185414e625df7553693d6517e3", "tree_bytes": 536, "tree_height": 1, "tree_size": 3, @@ -4609,11 +4572,11 @@ describe("State info (getStateInfo)", () => { assert.deepEqual(result.code, 0); assert.deepEqual(node.db.getStateInfo('/values/apps/test/label1'), { - "proof_hash": "0x1b8f144f5692c88c242776485c0cafc184d4724942578752d083c615d84a1caa", + "proof_hash": "0xe037f0083e30127f0e5088be69c2629a7e14e18518ee736fc31d86ec39b3c459", "tree_bytes": 348, "tree_height": 1, "tree_size": 2, - "version": "NODE:0", + "version": "NODE:0" }); assert.deepEqual(node.db.getStateInfo('/values/apps/test/label1/label11'), { "proof_hash": "0xa8681012b27ff56a45aa80f6f4d95c66c3349046cdd18cdc77028b6a634c9b0b", @@ -4624,7 +4587,7 @@ describe("State info (getStateInfo)", () => { }); assert.deepEqual(node.db.getStateInfo('/values/apps/test/label1/label12'), null); assert.deepEqual(node.db.getStateInfo('/values/apps/test/label2'), { - "proof_hash": "0x7b614d2449c2ce477ac040c52b78798e5ff36a20b83115b6af8688f5e88a813f", + "proof_hash": "0x0088bff9a36081510c230f5fd6b6581b81966b185414e625df7553693d6517e3", "tree_bytes": 536, "tree_height": 1, "tree_size": 3, @@ -4642,21 +4605,21 @@ describe("State info (getStateInfo)", () => { assert.deepEqual(result.code, 0); assert.deepEqual(node.db.getStateInfo('/values/apps/test/label1'), { - "proof_hash": "0x052b9dbac10fca45626652f264b9896216da0ce6f1b55d10934b7e9cb9141871", + "proof_hash": "0xc751739c3275e0b4c143835fcc0342b80af43a74cf338a8571c17e727643bbe7", "tree_bytes": 906, "tree_height": 2, "tree_size": 5, - "version": "NODE:0", + "version": "NODE:0" }); assert.deepEqual(node.db.getStateInfo('/values/apps/test/label2'), { - "proof_hash": "0x7da207e739139a3fabbcb53c9a2b91f786441b903ffd1de445e69d921f9f30af", + "proof_hash": "0xdd1c06ba6d6ff93fea2f2a1a3a026692858cd3528424b2f86197e1761539b0e4", "tree_bytes": 906, "tree_height": 2, "tree_size": 5, "version": "NODE:0", }); assert.deepEqual(node.db.getStateInfo('/values/apps/test/label2/label21'), { - "proof_hash": "0x805586e32d13b938808c5e283c027d0fa7f8b496bdb6fdc8cd5a57d0b12c72af", + "proof_hash": "0xdfe61d4a6c026b34261bc83f4c9d5d24deaed1671177fee24a889930588edd89", "tree_bytes": 544, "tree_height": 1, "tree_size": 3, diff --git a/unittest/functions.test.js b/unittest/functions.test.js index 77d778fb3..07ab5c4b2 100644 --- a/unittest/functions.test.js +++ b/unittest/functions.test.js @@ -146,7 +146,7 @@ describe("Functions", () => { null, null, null, transaction); assert.deepEqual(func_results, { "0x11111": { - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 10, } }); @@ -422,7 +422,7 @@ describe("Functions", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 1000 } }); @@ -477,7 +477,7 @@ describe("Functions", () => { } } }, - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 0 } }); @@ -512,7 +512,7 @@ describe("Functions", () => { null, null, null, transaction); assert.deepEqual(func_results, { "0x11111": { - "code": "SUCCESS", + "code": 0, "bandwidth_gas_amount": 10, } }); diff --git a/unittest/p2p-util.test.js b/unittest/p2p-util.test.js index 6a99a621d..d88bed57a 100644 --- a/unittest/p2p-util.test.js +++ b/unittest/p2p-util.test.js @@ -5,7 +5,8 @@ const expect = chai.expect; const assert = chai.assert; const { CURRENT_PROTOCOL_VERSION, - DATA_PROTOCOL_VERSION + DATA_PROTOCOL_VERSION, + NETWORK_ID } = require('../common/constants'); describe("P2P Util", () => { @@ -107,6 +108,7 @@ describe("P2P Util", () => { data: mockDataObj, protoVer: CURRENT_PROTOCOL_VERSION, dataProtoVer: DATA_PROTOCOL_VERSION, + networkId: NETWORK_ID, timestamp: encapsulatedMessage.timestamp }); }); diff --git a/unittest/radix-child-map.test.js b/unittest/radix-child-map.test.js new file mode 100644 index 000000000..37b1f6d27 --- /dev/null +++ b/unittest/radix-child-map.test.js @@ -0,0 +1,191 @@ +const RadixChildMap = require('../db/radix-child-map'); +const chai = require('chai'); +const expect = chai.expect; +const assert = chai.assert; + +describe("radix-child-map", () => { + describe("Static utils", () => { + it("_labelRadixToIndex", () => { + // valid input + expect(RadixChildMap._labelRadixToIndex('0')).to.equal(0); + expect(RadixChildMap._labelRadixToIndex('1')).to.equal(1); + expect(RadixChildMap._labelRadixToIndex('2')).to.equal(2); + expect(RadixChildMap._labelRadixToIndex('3')).to.equal(3); + expect(RadixChildMap._labelRadixToIndex('4')).to.equal(4); + expect(RadixChildMap._labelRadixToIndex('5')).to.equal(5); + expect(RadixChildMap._labelRadixToIndex('6')).to.equal(6); + expect(RadixChildMap._labelRadixToIndex('7')).to.equal(7); + expect(RadixChildMap._labelRadixToIndex('8')).to.equal(8); + expect(RadixChildMap._labelRadixToIndex('9')).to.equal(9); + expect(RadixChildMap._labelRadixToIndex('a')).to.equal(10); + expect(RadixChildMap._labelRadixToIndex('b')).to.equal(11); + expect(RadixChildMap._labelRadixToIndex('c')).to.equal(12); + expect(RadixChildMap._labelRadixToIndex('d')).to.equal(13); + expect(RadixChildMap._labelRadixToIndex('e')).to.equal(14); + expect(RadixChildMap._labelRadixToIndex('f')).to.equal(15); + + // invalid input + expect(RadixChildMap._labelRadixToIndex('')).to.equal(-1); + expect(RadixChildMap._labelRadixToIndex(' ')).to.equal(-1); + expect(RadixChildMap._labelRadixToIndex('A')).to.equal(-1); + expect(RadixChildMap._labelRadixToIndex('B')).to.equal(-1); + expect(RadixChildMap._labelRadixToIndex('C')).to.equal(-1); + expect(RadixChildMap._labelRadixToIndex('D')).to.equal(-1); + expect(RadixChildMap._labelRadixToIndex('E')).to.equal(-1); + expect(RadixChildMap._labelRadixToIndex('F')).to.equal(-1); + }); + + it("_indexToLabelRadix", () => { + // valid input + expect(RadixChildMap._indexToLabelRadix(0)).to.equal('0'); + expect(RadixChildMap._indexToLabelRadix(1)).to.equal('1'); + expect(RadixChildMap._indexToLabelRadix(2)).to.equal('2'); + expect(RadixChildMap._indexToLabelRadix(3)).to.equal('3'); + expect(RadixChildMap._indexToLabelRadix(4)).to.equal('4'); + expect(RadixChildMap._indexToLabelRadix(5)).to.equal('5'); + expect(RadixChildMap._indexToLabelRadix(6)).to.equal('6'); + expect(RadixChildMap._indexToLabelRadix(7)).to.equal('7'); + expect(RadixChildMap._indexToLabelRadix(8)).to.equal('8'); + expect(RadixChildMap._indexToLabelRadix(9)).to.equal('9'); + expect(RadixChildMap._indexToLabelRadix(10)).to.equal('a'); + expect(RadixChildMap._indexToLabelRadix(11)).to.equal('b'); + expect(RadixChildMap._indexToLabelRadix(12)).to.equal('c'); + expect(RadixChildMap._indexToLabelRadix(13)).to.equal('d'); + expect(RadixChildMap._indexToLabelRadix(14)).to.equal('e'); + expect(RadixChildMap._indexToLabelRadix(15)).to.equal('f'); + + // invalid input + expect(RadixChildMap._indexToLabelRadix(-1)).to.equal(''); + expect(RadixChildMap._indexToLabelRadix(16)).to.equal(''); + expect(RadixChildMap._indexToLabelRadix(17)).to.equal(''); + expect(RadixChildMap._indexToLabelRadix(100)).to.equal(''); + expect(RadixChildMap._indexToLabelRadix(0.1)).to.equal(''); + expect(RadixChildMap._indexToLabelRadix(Infinity)).to.equal(''); + }); + }); + + describe("APIs", () => { + let map; + + beforeEach(() => { + map = new RadixChildMap(); + }) + + describe("Initialization", () => { + it("constructor", () => { + expect(map.size).to.equal(0); + expect(map.childArray.length).to.equal(16); + map.childArray.forEach((elem) => { + expect(elem).to.equal(null); + }) + }); + }); + + describe("get / set / has / delete / keys / values / size", () => { + it("simple operations with valid label radix", () => { + const labelRadix1 = '0'; + const labelRadix2 = '1'; + const child1 = { value: 'child1' }; + const child2 = { value: 'child2' }; + + expect(map.has(labelRadix1)).to.equal(false); + expect(map.get(labelRadix1)).to.equal(null); + expect(map.has(labelRadix2)).to.equal(false); + expect(map.get(labelRadix2)).to.equal(null); + expect(map.size).to.equal(0); + assert.deepEqual(map.keys(), []); + assert.deepEqual(map.values(), []); + + expect(map.set(labelRadix1, child1)).to.equal(true); + expect(map.has(labelRadix1)).to.equal(true); + expect(map.get(labelRadix1)).to.equal(child1); + expect(map.has(labelRadix2)).to.equal(false); + expect(map.get(labelRadix2)).to.equal(null); + expect(map.size).to.equal(1); + assert.deepEqual(map.keys(), [labelRadix1]); + assert.deepEqual(map.values(), [child1]); + + expect(map.set(labelRadix2, child2)).to.equal(true); + expect(map.has(labelRadix1)).to.equal(true); + expect(map.get(labelRadix1)).to.equal(child1); + expect(map.has(labelRadix2)).to.equal(true); + expect(map.get(labelRadix2)).to.equal(child2); + expect(map.size).to.equal(2); + assert.deepEqual(map.keys(), [labelRadix1, labelRadix2]); + assert.deepEqual(map.values(), [child1, child2]); + + expect(map.delete(labelRadix1)).to.equal(true); + expect(map.has(labelRadix1)).to.equal(false); + expect(map.get(labelRadix1)).to.equal(null); + expect(map.has(labelRadix2)).to.equal(true); + expect(map.get(labelRadix2)).to.equal(child2); + expect(map.size).to.equal(1); + assert.deepEqual(map.keys(), [labelRadix2]); + assert.deepEqual(map.values(), [child2]); + + expect(map.delete(labelRadix2)).to.equal(true); + expect(map.has(labelRadix1)).to.equal(false); + expect(map.get(labelRadix1)).to.equal(null); + expect(map.has(labelRadix2)).to.equal(false); + expect(map.get(labelRadix2)).to.equal(null); + expect(map.size).to.equal(0); + assert.deepEqual(map.keys(), []); + assert.deepEqual(map.values(), []); + }); + + it("simple operations with invalid label radix", () => { + const child1 = { value: 'child1' }; + + expect(map.has(undefined)).to.equal(false); + expect(map.has(null)).to.equal(false); + expect(map.has(false)).to.equal(false); + expect(map.has(0)).to.equal(false); + expect(map.has('A')).to.equal(false); + + expect(map.get(undefined)).to.equal(undefined); + expect(map.get(null)).to.equal(undefined); + expect(map.get(false)).to.equal(undefined); + expect(map.get(0)).to.equal(undefined); + expect(map.get('A')).to.equal(undefined); + + expect(map.set(undefined, child1)).to.equal(false); + expect(map.set(null, child1)).to.equal(false); + expect(map.set(false, child1)).to.equal(false); + expect(map.set(0, child1)).to.equal(false); + expect(map.set('A', child1)).to.equal(false); + + expect(map.delete(undefined)).to.equal(false); + expect(map.delete(null)).to.equal(false); + expect(map.delete(false)).to.equal(false); + expect(map.delete(0)).to.equal(false); + expect(map.delete('A')).to.equal(false); + }); + + it("overwrite", () => { + const labelRadix = '0'; + const child1 = { value: 'child1' }; + const child2 = { value: 'child2' }; + + expect(map.has(labelRadix)).to.equal(false); + expect(map.get(labelRadix)).to.equal(null); + expect(map.size).to.equal(0); + assert.deepEqual(map.keys(), []); + assert.deepEqual(map.values(), []); + + expect(map.set(labelRadix, child1)).to.equal(true); + expect(map.has(labelRadix)).to.equal(true); + expect(map.get(labelRadix)).to.equal(child1); + expect(map.size).to.equal(1); + assert.deepEqual(map.keys(), [labelRadix]); + assert.deepEqual(map.values(), [child1]); + + expect(map.set(labelRadix, child2)).to.equal(true); + expect(map.has(labelRadix)).to.equal(true); + expect(map.get(labelRadix)).to.equal(child2); + expect(map.size).to.equal(1); + assert.deepEqual(map.keys(), [labelRadix]); + assert.deepEqual(map.values(), [child2]); + }); + }); + }); +}); diff --git a/unittest/radix-node.test.js b/unittest/radix-node.test.js new file mode 100644 index 000000000..f508683a3 --- /dev/null +++ b/unittest/radix-node.test.js @@ -0,0 +1,850 @@ +const RadixNode = require('../db/radix-node'); +const CommonUtil = require('../common/common-util'); +const StateNode = require('../db/state-node'); +const { + HASH_DELIMITER, +} = require('../common/constants'); +const chai = require('chai'); +const expect = chai.expect; +const assert = chai.assert; + +describe("radix-node", () => { + let node; + + beforeEach(() => { + node = new RadixNode(); + }) + + describe("Initialization", () => { + it("constructor", () => { + expect(node.stateNode).to.equal(null); + expect(node.labelRadix).to.equal(''); + expect(node.labelSuffix).to.equal(''); + expect(node.parent).to.equal(null); + expect(node.radixChildMap.size).to.equal(0); + expect(node.proofHash).to.equal(null); + }); + }); + + describe("stateNode", () => { + it("get / set / has / reset", () => { + const stateNode = new StateNode(); + expect(node.getStateNode()).to.equal(null); + expect(node.hasStateNode()).to.equal(false); + expect(node.setStateNode(stateNode)).to.equal(true); + expect(node.getStateNode()).to.equal(stateNode); + expect(node.hasStateNode()).to.equal(true); + node.resetStateNode(); + expect(node.getStateNode()).to.equal(null); + expect(node.hasStateNode()).to.equal(false); + }); + + it("set with invalid state node", () => { + const invalidStateNode = new RadixNode(); + expect(node.setStateNode(invalidStateNode)).to.equal(false); + expect(node.setStateNode('')).to.equal(false); + expect(node.setStateNode(true)).to.equal(false); + expect(node.setStateNode(null)).to.equal(false); + expect(node.setStateNode(undefined)).to.equal(false); + }); + }); + + describe("labelRadix", () => { + it("get / set / has / reset", () => { + const labelRadix = '0'; + expect(node.getLabelRadix()).to.equal(''); + node.setLabelRadix(labelRadix); + expect(node.getLabelRadix()).to.equal(labelRadix); + node.resetLabelRadix(); + expect(node.getLabelRadix()).to.equal(''); + }); + }); + + describe("labelSuffix", () => { + it("get / set / has / reset", () => { + const labelSuffix = 'ffff'; + expect(node.getLabelSuffix()).to.equal(''); + expect(node.getLabelSuffix()).to.equal(''); + node.setLabelSuffix(labelSuffix); + expect(node.getLabelSuffix()).to.equal(labelSuffix); + node.resetLabelSuffix(); + expect(node.getLabelSuffix()).to.equal(''); + }); + }); + + describe("label", () => { + it("get", () => { + const labelRadix = '0'; + const labelSuffix = 'ffff'; + expect(node.getLabel()).to.equal(''); + node.setLabelRadix(labelRadix); + expect(node.getLabel()).to.equal(labelRadix); + node.setLabelSuffix(labelSuffix); + expect(node.getLabel()).to.equal(labelRadix + labelSuffix); + node.resetLabelSuffix(); + expect(node.getLabel()).to.equal(labelRadix); + node.resetLabelRadix(); + expect(node.getLabel()).to.equal(''); + }); + }); + + describe("parent", () => { + it("get / set / has / reset", () => { + const parent = new RadixNode(); + expect(node.getParent()).to.equal(null); + expect(node.hasParent()).to.equal(false); + node.setParent(parent); + expect(node.getParent()).to.equal(parent); + expect(node.hasParent()).to.equal(true); + node.resetParent(); + expect(node.getParent()).to.equal(null); + expect(node.hasParent()).to.equal(false); + }); + }); + + describe("child", () => { + it("set / delete with a child with invalid labels", () => { + const labelRadix = '0'; + const labelSuffix = 'ffff'; + const child = new RadixNode(); + + // setChild() with invalid label radix + expect(node.setChild(undefined, labelSuffix, child)).to.equal(false); + expect(node.setChild(null, labelSuffix, child)).to.equal(false); + expect(node.setChild(true, labelSuffix, child)).to.equal(false); + expect(node.setChild(1, labelSuffix, child)).to.equal(false); + expect(node.setChild('', labelSuffix, child)).to.equal(false); + expect(node.setChild('00', labelSuffix, child)).to.equal(false); + + // setChild() with invalid label suffix + expect(node.setChild(labelRadix, undefined, child)).to.equal(false); + expect(node.setChild(labelRadix, null, child)).to.equal(false); + expect(node.setChild(labelRadix, true, child)).to.equal(false); + expect(node.setChild(labelRadix, 1, child)).to.equal(false); + + // deleteChild() with invalid label radix + expect(node.deleteChild(undefined)).to.equal(false); + expect(node.deleteChild(null)).to.equal(false); + expect(node.deleteChild(true)).to.equal(false); + expect(node.deleteChild(1)).to.equal(false); + }); + + it("get / set / has / delete with a child with valid labels", () => { + const labelRadix = '0'; + const labelSuffix = 'ffff'; + const child = new RadixNode(); + + expect(node.hasStateNode()).to.equal(false); + expect(node.getLabelRadix()).to.equal(''); + expect(node.getLabelSuffix()).to.equal(''); + expect(node.hasParent()).to.equal(false); + expect(node.hasChild()).to.equal(false); + expect(node.getChild(labelRadix)).to.equal(null); + assert.deepEqual(node.getChildLabelRadices(), []); + assert.deepEqual(node.getChildNodes(), []); + expect(node.numChildren()).to.equal(0); + + expect(child.hasStateNode()).to.equal(false); + expect(child.getLabelRadix()).to.equal(''); + expect(child.getLabelSuffix()).to.equal(''); + expect(child.hasParent()).to.equal(false); + expect(child.hasChild()).to.equal(false); + assert.deepEqual(child.getChildLabelRadices(), []); + assert.deepEqual(child.getChildNodes(), []); + expect(child.numChildren()).to.equal(0); + + // setChild() + node.setChild(labelRadix, labelSuffix, child); + + expect(node.hasStateNode()).to.equal(false); + expect(node.getLabelRadix()).to.equal(''); + expect(node.getLabelSuffix()).to.equal(''); + expect(node.hasParent()).to.equal(false); + expect(node.hasChild()).to.equal(true); + expect(node.getChild(labelRadix)).to.equal(child); + assert.deepEqual(node.getChildLabelRadices(), [labelRadix]); + assert.deepEqual(node.getChildNodes(), [child]); + expect(node.numChildren()).to.equal(1); + + expect(child.hasStateNode()).to.equal(false); + expect(child.getLabelRadix()).to.equal(labelRadix); + expect(child.getLabelSuffix()).to.equal(labelSuffix); + expect(child.hasParent()).to.equal(true); + expect(child.getParent()).to.equal(node); + expect(child.hasChild()).to.equal(false); + assert.deepEqual(child.getChildLabelRadices(), []); + assert.deepEqual(child.getChildNodes(), []); + expect(child.numChildren()).to.equal(0); + + // deleteChild() + node.deleteChild(labelRadix); + + expect(node.hasStateNode()).to.equal(false); + expect(node.getLabelRadix()).to.equal(''); + expect(node.getLabelSuffix()).to.equal(''); + expect(node.hasParent()).to.equal(false); + expect(node.hasChild()).to.equal(false); + expect(node.getChild(labelRadix)).to.equal(null); + assert.deepEqual(node.getChildLabelRadices(), []); + assert.deepEqual(node.getChildNodes(), []); + expect(node.numChildren()).to.equal(0); + + expect(child.hasStateNode()).to.equal(false); + expect(child.getLabelRadix()).to.equal(''); + expect(child.getLabelSuffix()).to.equal(''); + expect(child.hasParent()).to.equal(false); + expect(child.hasChild()).to.equal(false); + assert.deepEqual(child.getChildLabelRadices(), []); + assert.deepEqual(child.getChildNodes(), []); + expect(child.numChildren()).to.equal(0); + }); + + it("get / set / has / delete with children with valid labels", () => { + const labelRadix1 = '0'; + const labelSuffix1 = '0000'; + const child1 = new RadixNode(); + + const labelRadix2 = '1'; + const labelSuffix2 = '1111'; + const child2 = new RadixNode(); + + // setChild() with child1 + node.setChild(labelRadix1, labelSuffix1, child1); + expect(node.hasStateNode()).to.equal(false); + expect(node.getLabelRadix()).to.equal(''); + expect(node.getLabelSuffix()).to.equal(''); + expect(node.hasParent()).to.equal(false); + expect(node.hasChild()).to.equal(true); + expect(node.getChild(labelRadix1)).to.equal(child1); + expect(node.getChild(labelRadix2)).to.equal(null); + assert.deepEqual(node.getChildLabelRadices(), [labelRadix1]); + assert.deepEqual(node.getChildNodes(), [child1]); + expect(node.numChildren()).to.equal(1); + + expect(child1.hasStateNode()).to.equal(false); + expect(child1.getLabelRadix()).to.equal(labelRadix1); + expect(child1.getLabelSuffix()).to.equal(labelSuffix1); + expect(child1.hasParent()).to.equal(true); + expect(child1.getParent()).to.equal(node); + expect(child1.hasChild()).to.equal(false); + assert.deepEqual(child1.getChildLabelRadices(), []); + assert.deepEqual(child1.getChildNodes(), []); + expect(child1.numChildren()).to.equal(0); + + // setChild() with child2 + node.setChild(labelRadix2, labelSuffix2, child2); + + expect(node.hasStateNode()).to.equal(false); + expect(node.getLabelRadix()).to.equal(''); + expect(node.getLabelSuffix()).to.equal(''); + expect(node.hasParent()).to.equal(false); + expect(node.hasChild()).to.equal(true); + expect(node.getChild(labelRadix1)).to.equal(child1); + expect(node.getChild(labelRadix2)).to.equal(child2); + assert.deepEqual(node.getChildLabelRadices(), [labelRadix1, labelRadix2]); + assert.deepEqual(node.getChildNodes(), [child1, child2]); + expect(node.numChildren()).to.equal(2); + + expect(child1.hasStateNode()).to.equal(false); + expect(child1.getLabelRadix()).to.equal(labelRadix1); + expect(child1.getLabelSuffix()).to.equal(labelSuffix1); + expect(child1.hasParent()).to.equal(true); + expect(child1.getParent()).to.equal(node); + expect(child1.hasChild()).to.equal(false); + assert.deepEqual(child1.getChildLabelRadices(), []); + assert.deepEqual(child1.getChildNodes(), []); + expect(child1.numChildren()).to.equal(0); + + expect(child2.hasStateNode()).to.equal(false); + expect(child1.getLabelRadix()).to.equal(labelRadix1); + expect(child1.getLabelSuffix()).to.equal(labelSuffix1); + expect(child2.hasParent()).to.equal(true); + expect(child2.getParent()).to.equal(node); + expect(child2.hasChild()).to.equal(false); + assert.deepEqual(child2.getChildLabelRadices(), []); + assert.deepEqual(child2.getChildNodes(), []); + expect(child2.numChildren()).to.equal(0); + + // deleteChild() with child1 + node.deleteChild(labelRadix1); + + expect(node.hasStateNode()).to.equal(false); + expect(node.getLabelRadix()).to.equal(''); + expect(node.getLabelSuffix()).to.equal(''); + expect(node.hasParent()).to.equal(false); + expect(node.getChild(labelRadix1)).to.equal(null); + expect(node.getChild(labelRadix2)).to.equal(child2); + expect(node.hasChild()).to.equal(true); + assert.deepEqual(node.getChildLabelRadices(), [labelRadix2]); + assert.deepEqual(node.getChildNodes(), [child2]); + expect(node.numChildren()).to.equal(1); + + expect(child1.hasStateNode()).to.equal(false); + expect(child1.getLabelRadix()).to.equal(''); + expect(child1.getLabelSuffix()).to.equal(''); + expect(child1.hasParent()).to.equal(false); + expect(child1.hasChild()).to.equal(false); + assert.deepEqual(child1.getChildLabelRadices(), []); + assert.deepEqual(child1.getChildNodes(), []); + expect(child1.numChildren()).to.equal(0); + + expect(child2.hasStateNode()).to.equal(false); + expect(child2.getLabelRadix()).to.equal(labelRadix2); + expect(child2.getLabelSuffix()).to.equal(labelSuffix2); + expect(child2.hasParent()).to.equal(true); + expect(child2.getParent()).to.equal(node); + expect(child2.hasChild()).to.equal(false); + assert.deepEqual(child2.getChildLabelRadices(), []); + assert.deepEqual(child2.getChildNodes(), []); + expect(child2.numChildren()).to.equal(0); + }); + }); + + describe("proofHash", () => { + const labelRadix1 = '1'; + const labelSuffix1 = '100'; + let child1; + + const labelRadix2 = '2'; + const labelSuffix2 = '200'; + let child2; + + const labelRadix11 = '1'; + const labelSuffix11 = '110'; + let child11; + + const labelRadix12 = '2'; + const labelSuffix12 = '120'; + let child12; + + const labelRadix21 = '1'; + const labelSuffix21 = '210'; + let child21; + + const labelRadix22 = '2'; + const labelSuffix22 = '220'; + let child22; + + let stateNode; + const stateNodePH = 'stateNodePH'; + + let stateNode1; + const stateNodePH1 = 'stateNodePH1'; + + let stateNode2; + const stateNodePH2 = 'stateNodePH2'; + + let stateNode11; + const stateNodePH11 = 'stateNodePH11'; + + let stateNode12; + const stateNodePH12 = 'stateNodePH12'; + + let stateNode21; + const stateNodePH21 = 'stateNodePH21'; + + let stateNode22; + const stateNodePH22 = 'stateNodePH22'; + + beforeEach(() => { + child1 = new RadixNode(); + child2 = new RadixNode(); + child11 = new RadixNode(); + child12 = new RadixNode(); + child21 = new RadixNode(); + child22 = new RadixNode(); + + stateNode = new StateNode(); + stateNode.setProofHash(stateNodePH); + node.setStateNode(stateNode); + + stateNode1 = new StateNode(); + stateNode1.setProofHash(stateNodePH1); + child1.setStateNode(stateNode1); + + stateNode11 = new StateNode(); + stateNode11.setProofHash(stateNodePH11); + child11.setStateNode(stateNode11); + + stateNode12 = new StateNode(); + stateNode12.setProofHash(stateNodePH12); + child12.setStateNode(stateNode12); + + stateNode21 = new StateNode(); + stateNode21.setProofHash(stateNodePH21); + child21.setStateNode(stateNode21); + + stateNode22 = new StateNode(); + stateNode22.setProofHash(stateNodePH22); + child22.setStateNode(stateNode22); + }) + + it("get / set / has / reset", () => { + const proofHash = 'proofHash'; + + expect(node.getProofHash()).to.equal(null); + node.setProofHash(proofHash); + expect(node.getProofHash()).to.equal(proofHash); + node.resetProofHash(); + expect(node.getProofHash()).to.equal(null); + }); + + it("build", () => { + const childPH1 = 'childPH1'; + const childPH2 = 'childPH2'; + + child1.setProofHash(childPH1); + child2.setProofHash(childPH2); + + node.setChild(labelRadix1, labelSuffix1, child1); + node.setChild(labelRadix2, labelSuffix2, child2); + + assert.deepEqual(node.toJsObject(true), { + "1100": { + ".label": null, + ".proof_hash": "stateNodePH1", + ".radix_ph": "childPH1" + }, + "2200": { + ".radix_ph": "childPH2" + }, + ".label": null, + ".proof_hash": "stateNodePH", + ".radix_ph": null + }); + + // With stateNode + node.setStateNode(stateNode); + const preimage2 = `${stateNodePH}${HASH_DELIMITER}${HASH_DELIMITER}` + + `${labelRadix1}${labelSuffix1}${HASH_DELIMITER}${childPH1}` + + `${HASH_DELIMITER}${labelRadix2}${labelSuffix2}${HASH_DELIMITER}${childPH2}`; + const proofHash2 = CommonUtil.hashString(preimage2); + expect(node._buildProofHash()).to.equal(proofHash2) + + // Without stateNode + node.resetStateNode(); + const preimage1 = `${HASH_DELIMITER}${HASH_DELIMITER}` + + `${labelRadix1}${labelSuffix1}${HASH_DELIMITER}${childPH1}` + + `${HASH_DELIMITER}${labelRadix2}${labelSuffix2}${HASH_DELIMITER}${childPH2}`; + const proofHash1 = CommonUtil.hashString(preimage1); + expect(node._buildProofHash()).to.equal(proofHash1) + }); + + it("update / verify", () => { + const childPH1 = 'childPH1'; + const childPH2 = 'childPH2'; + + child1.setProofHash(childPH1); + child2.setProofHash(childPH2); + + node.setChild(labelRadix1, labelSuffix1, child1); + node.setChild(labelRadix2, labelSuffix2, child2); + + assert.deepEqual(node.toJsObject(true), { + "1100": { + ".label": null, + ".proof_hash": "stateNodePH1", + ".radix_ph": "childPH1" + }, + "2200": { + ".radix_ph": "childPH2" + }, + ".label": null, + ".proof_hash": "stateNodePH", + ".radix_ph": null + }); + + node.resetProofHash(); + expect(node.verifyProofHash()).to.equal(false); + node.updateProofHash(); + expect(node.verifyProofHash()).to.equal(true); + expect(node.getProofHash()).to.equal(node._buildProofHash()); + }); + + it("updateProofHashForRadixSubtree", () => { + node.setStateNode(stateNode); + node.setChild(labelRadix1, labelSuffix1, child1); + node.setChild(labelRadix2, labelSuffix2, child2); + child1.setChild(labelRadix11, labelSuffix11, child11); + child1.setChild(labelRadix12, labelSuffix12, child12); + child2.setChild(labelRadix21, labelSuffix21, child21); + child2.setChild(labelRadix22, labelSuffix22, child22); + + assert.deepEqual(node.toJsObject(true), { + "1100": { + "1110": { + ".label": null, + ".proof_hash": "stateNodePH11", + ".radix_ph": null + }, + "2120": { + ".label": null, + ".proof_hash": "stateNodePH12", + ".radix_ph": null + }, + ".label": null, + ".proof_hash": "stateNodePH1", + ".radix_ph": null + }, + "2200": { + "1210": { + ".label": null, + ".proof_hash": "stateNodePH21", + ".radix_ph": null + }, + "2220": { + ".label": null, + ".proof_hash": "stateNodePH22", + ".radix_ph": null + }, + ".radix_ph": null + }, + ".label": null, + ".proof_hash": "stateNodePH", + ".radix_ph": null + }); + + // initial status + expect(node.verifyProofHash()).to.equal(false); + expect(child1.verifyProofHash()).to.equal(false); + expect(child2.verifyProofHash()).to.equal(false); + expect(child11.verifyProofHash()).to.equal(false); + expect(child12.verifyProofHash()).to.equal(false); + expect(child21.verifyProofHash()).to.equal(false); + expect(child22.verifyProofHash()).to.equal(false); + + // set + expect(node.updateProofHashForRadixSubtree()).to.equal(7); + expect(node.verifyProofHash()).to.equal(true); + expect(child1.verifyProofHash()).to.equal(true); + expect(child2.verifyProofHash()).to.equal(true); + expect(child11.verifyProofHash()).to.equal(true); + expect(child12.verifyProofHash()).to.equal(true); + expect(child21.verifyProofHash()).to.equal(true); + expect(child22.verifyProofHash()).to.equal(true); + + // change of a state node's proof hash + stateNode12.setProofHash('another PH'); + expect(node.verifyProofHash()).to.equal(true); + expect(child1.verifyProofHash()).to.equal(true); + expect(child2.verifyProofHash()).to.equal(true); + expect(child11.verifyProofHash()).to.equal(true); + expect(child12.verifyProofHash()).to.equal(false); + expect(child21.verifyProofHash()).to.equal(true); + expect(child22.verifyProofHash()).to.equal(true); + }); + + it("updateProofHashForRadixPath", () => { + node.setStateNode(stateNode); + node.setChild(labelRadix1, labelSuffix1, child1); + node.setChild(labelRadix2, labelSuffix2, child2); + child1.setChild(labelRadix11, labelSuffix11, child11); + child1.setChild(labelRadix12, labelSuffix12, child12); + child2.setChild(labelRadix21, labelSuffix21, child21); + child2.setChild(labelRadix22, labelSuffix22, child22); + + assert.deepEqual(node.toJsObject(true), { + "1100": { + "1110": { + ".label": null, + ".proof_hash": "stateNodePH11", + ".radix_ph": null + }, + "2120": { + ".label": null, + ".proof_hash": "stateNodePH12", + ".radix_ph": null + }, + ".label": null, + ".proof_hash": "stateNodePH1", + ".radix_ph": null + }, + "2200": { + "1210": { + ".label": null, + ".proof_hash": "stateNodePH21", + ".radix_ph": null + }, + "2220": { + ".label": null, + ".proof_hash": "stateNodePH22", + ".radix_ph": null + }, + ".radix_ph": null + }, + ".label": null, + ".proof_hash": "stateNodePH", + ".radix_ph": null + }); + + // initial status + expect(node.verifyProofHash()).to.equal(false); + expect(child1.verifyProofHash()).to.equal(false); + expect(child2.verifyProofHash()).to.equal(false); + expect(child11.verifyProofHash()).to.equal(false); + expect(child12.verifyProofHash()).to.equal(false); + expect(child21.verifyProofHash()).to.equal(false); + expect(child22.verifyProofHash()).to.equal(false); + + // update + expect(child21.updateProofHashForRadixPath()).to.equal(3); + expect(node.verifyProofHash()).to.equal(true); + expect(child1.verifyProofHash()).to.equal(false); + expect(child2.verifyProofHash()).to.equal(true); + expect(child11.verifyProofHash()).to.equal(false); + expect(child12.verifyProofHash()).to.equal(false); + expect(child21.verifyProofHash()).to.equal(true); + expect(child22.verifyProofHash()).to.equal(false); + }); + + it("verifyProofHashForRadixSubtree", () => { + node.setStateNode(stateNode); + node.setChild(labelRadix1, labelSuffix1, child1); + node.setChild(labelRadix2, labelSuffix2, child2); + child1.setChild(labelRadix11, labelSuffix11, child11); + child1.setChild(labelRadix12, labelSuffix12, child12); + child2.setChild(labelRadix21, labelSuffix21, child21); + child2.setChild(labelRadix22, labelSuffix22, child22); + + assert.deepEqual(node.toJsObject(true), { + "1100": { + "1110": { + ".label": null, + ".proof_hash": "stateNodePH11", + ".radix_ph": null + }, + "2120": { + ".label": null, + ".proof_hash": "stateNodePH12", + ".radix_ph": null + }, + ".label": null, + ".proof_hash": "stateNodePH1", + ".radix_ph": null + }, + "2200": { + "1210": { + ".label": null, + ".proof_hash": "stateNodePH21", + ".radix_ph": null + }, + "2220": { + ".label": null, + ".proof_hash": "stateNodePH22", + ".radix_ph": null + }, + ".radix_ph": null + }, + ".label": null, + ".proof_hash": "stateNodePH", + ".radix_ph": null + }); + + // initial status + expect(node.verifyProofHashForRadixSubtree()).to.equal(false); + + // set + expect(node.updateProofHashForRadixSubtree()).to.equal(7); + expect(node.verifyProofHashForRadixSubtree()).to.equal(true); + + // change of a state node's proof hash + stateNode21.setProofHash('another PH'); + expect(node.verifyProofHashForRadixSubtree()).to.equal(false); + + // update + expect(child21.updateProofHashForRadixPath()).to.equal(3); + expect(node.verifyProofHashForRadixSubtree()).to.equal(true); + }); + + it("getProofOfRadixNode", () => { + node.setStateNode(stateNode); + node.setChild(labelRadix1, labelSuffix1, child1); + node.setChild(labelRadix2, labelSuffix2, child2); + child1.setChild(labelRadix11, labelSuffix11, child11); + child1.setChild(labelRadix12, labelSuffix12, child12); + child2.setChild(labelRadix21, labelSuffix21, child21); + child2.setChild(labelRadix22, labelSuffix22, child22); + + expect(node.updateProofHashForRadixSubtree()).to.equal(7); + + assert.deepEqual(node.toJsObject(true), { + "1100": { + "1110": { + ".label": null, + ".proof_hash": "stateNodePH11", + ".radix_ph": "0xac8e0ca829cea8d80a79260078fb8e1b38a05b6d087c72a1c92f63849a47b96b" + }, + "2120": { + ".label": null, + ".proof_hash": "stateNodePH12", + ".radix_ph": "0x7fc53637a6ff6b7efa8cf7c9ba95552ed7479262ad8c07a61b4d2b1e8002d360" + }, + ".label": null, + ".proof_hash": "stateNodePH1", + ".radix_ph": "0x58b89a07baf039f5a5420aeafed213b7abe18c3f1537e9626628719f56ab5434" + }, + "2200": { + "1210": { + ".label": null, + ".proof_hash": "stateNodePH21", + ".radix_ph": "0xa8c806fde336879bd0cb320c809ad8a1f6e1e526711ed239eb216f83e4fb19d7" + }, + "2220": { + ".label": null, + ".proof_hash": "stateNodePH22", + ".radix_ph": "0x0dd8afcb4c2839ff30e6872c7268f9ed687fd53c52ce78f0330de82d5b33a0a2" + }, + ".radix_ph": "0xb822c6a20a4128f025019f9f03cb802f86998f48073118b132fd40fbd1620fed" + }, + ".label": null, + ".proof_hash": "stateNodePH", + ".radix_ph": "0xf29196bc2c6609216445dc878baf97143463a00c9e03c6af0ba6d38a2817b3b3" + }); + + const label11 = labelRadix11 + labelSuffix11; + const label21 = labelRadix21 + labelSuffix21; + + // on a node with state node value with child label and child proof + assert.deepEqual(child1.getProofOfRadixNode(label11, 'childProof1'), { + "1110": "childProof1", + "2120": { + ".radix_ph": "0x7fc53637a6ff6b7efa8cf7c9ba95552ed7479262ad8c07a61b4d2b1e8002d360" + }, + ".label": null, + ".proof_hash": "stateNodePH1", + ".radix_ph": "0x58b89a07baf039f5a5420aeafed213b7abe18c3f1537e9626628719f56ab5434" + }); + + // on a node without state node value with child label and child proof + assert.deepEqual(child2.getProofOfRadixNode(label21, 'childProof2'), { + "1210": "childProof2", + "2220": { + ".radix_ph": "0x0dd8afcb4c2839ff30e6872c7268f9ed687fd53c52ce78f0330de82d5b33a0a2" + }, + ".radix_ph": "0xb822c6a20a4128f025019f9f03cb802f86998f48073118b132fd40fbd1620fed" + }); + + // on a node with state node value with state label/proof + assert.deepEqual(child1.getProofOfRadixNode(null, null, 'stateLabel1', 'stateProof1'), { + ".label": "stateLabel1", + ".proof_hash": "stateProof1", + ".radix_ph": "0x58b89a07baf039f5a5420aeafed213b7abe18c3f1537e9626628719f56ab5434" + }); + + // on a node without state node value with state label/proof + assert.deepEqual(child2.getProofOfRadixNode(null, null, 'stateLabel1', 'stateProof2'), { + ".radix_ph": "0xb822c6a20a4128f025019f9f03cb802f86998f48073118b132fd40fbd1620fed" + }); + }); + }); + + describe("utils", () => { + let stateNode1; + let stateNode2; + let stateNode21; + let stateNode22; + let child1; + let child2; + let child21; + let child22; + + beforeEach(() => { + stateNode1 = new StateNode(); + stateNode1.setProofHash('stateNodePH1'); + stateNode2 = new StateNode(); + stateNode2.setProofHash('stateNodePH2'); + stateNode21 = new StateNode(); + stateNode21.setProofHash('stateNodePH21'); + stateNode22 = new StateNode(); + stateNode22.setProofHash('stateNodePH22'); + + child1 = new RadixNode(); + child1.setProofHash('childPH1'); + child1.setStateNode(stateNode1); + + child2 = new RadixNode(); + child2.setProofHash('childPH2'); + child2.setStateNode(stateNode2); + + child21 = new RadixNode(); + child21.setProofHash('childPH21'); + child21.setStateNode(stateNode21); + + child22 = new RadixNode(); + child22.setProofHash('childPH22'); + child22.setStateNode(stateNode22); + + node.setChild('0', '001', child1); + node.setChild('1', '002', child2); + child2.setChild('2', '021', child21); + child2.setChild('3', '022', child22); + }); + + it("copyFrom", () => { + const newNode = new RadixNode(); + newNode.copyFrom(node); + assert.deepEqual(newNode.toJsObject(), { + "1002": { + "2021": { + ".label": null, + ".proof_hash": "stateNodePH21" + }, + "3022": { + ".label": null, + ".proof_hash": "stateNodePH22" + }, + ".label": null, + ".proof_hash": "stateNodePH2" + }, + "0001": { + ".label": null, + ".proof_hash": "stateNodePH1" + } + }); + }); + + it("toJsObject", () => { + assert.deepEqual(node.toJsObject(), { + "1002": { + "2021": { + ".label": null, + ".proof_hash": "stateNodePH21" + }, + "3022": { + ".label": null, + ".proof_hash": "stateNodePH22" + }, + ".label": null, + ".proof_hash": "stateNodePH2" + }, + "0001": { + ".label": null, + ".proof_hash": "stateNodePH1" + } + }); + assert.deepEqual(node.toJsObject(true, true), { + "1002": { + "2021": { + ".label": null, + ".proof_hash": "stateNodePH21", + ".radix_ph": "childPH21" + }, + "3022": { + ".label": null, + ".proof_hash": "stateNodePH22", + ".radix_ph": "childPH22" + }, + ".label": null, + ".proof_hash": "stateNodePH2", + ".radix_ph": "childPH2" + }, + ".radix_ph": null, + "0001": { + ".label": null, + ".proof_hash": "stateNodePH1", + ".radix_ph": "childPH1" + } + }); + }); + }); +}); diff --git a/unittest/radix-tree.test.js b/unittest/radix-tree.test.js new file mode 100644 index 000000000..f0e70334b --- /dev/null +++ b/unittest/radix-tree.test.js @@ -0,0 +1,1814 @@ +const RadixTree = require('../db/radix-tree'); +const RadixNode = require('../db/radix-node'); +const StateNode = require('../db/state-node'); +const chai = require('chai'); +const expect = chai.expect; +const assert = chai.assert; + +describe("radix-tree", () => { + describe("Utils", () => { + it("_toHexLabel", () => { + const tree = new RadixTree(); + expect(tree._toHexLabel('0x1234567890abcdef')).to.equal('1234567890abcdef'); + expect(tree._toHexLabel('aAzZ')).to.equal('61417a5a'); + }); + + it("_matchLabelSuffix with empty label suffix", () => { + const hexLabel = '1234abcd'; + const radixNode = new RadixNode(); + radixNode.setLabelSuffix(''); + expect(RadixTree._matchLabelSuffix(radixNode, hexLabel, 0)).to.equal(true); + expect(RadixTree._matchLabelSuffix(radixNode, hexLabel, 1)).to.equal(true); + expect(RadixTree._matchLabelSuffix(radixNode, hexLabel, 2)).to.equal(true); + expect(RadixTree._matchLabelSuffix(radixNode, hexLabel, 3)).to.equal(true); + expect(RadixTree._matchLabelSuffix(radixNode, hexLabel, 4)).to.equal(true); + expect(RadixTree._matchLabelSuffix(radixNode, hexLabel, 5)).to.equal(true); + expect(RadixTree._matchLabelSuffix(radixNode, hexLabel, 6)).to.equal(true); + expect(RadixTree._matchLabelSuffix(radixNode, hexLabel, 7)).to.equal(true); + expect(RadixTree._matchLabelSuffix(radixNode, hexLabel, 8)).to.equal(true); + expect(RadixTree._matchLabelSuffix(radixNode, '', 0)).to.equal(true); + }); + + it("_matchLabelSuffix with non-empty label suffix", () => { + const hexLabel = '1234abcd'; + const radixNode = new RadixNode(); + // a shorter length + radixNode.setLabelSuffix('ab'); + expect(RadixTree._matchLabelSuffix(radixNode, hexLabel, 3)).to.equal(false); + expect(RadixTree._matchLabelSuffix(radixNode, hexLabel, 4)).to.equal(true); + expect(RadixTree._matchLabelSuffix(radixNode, hexLabel, 5)).to.equal(false); + + // the same length + radixNode.setLabelSuffix('abcd'); + expect(RadixTree._matchLabelSuffix(radixNode, hexLabel, 3)).to.equal(false); + expect(RadixTree._matchLabelSuffix(radixNode, hexLabel, 4)).to.equal(true); + expect(RadixTree._matchLabelSuffix(radixNode, hexLabel, 5)).to.equal(false); + + // a longer length + radixNode.setLabelSuffix('abcd123'); + expect(RadixTree._matchLabelSuffix(radixNode, hexLabel, 3)).to.equal(false); + expect(RadixTree._matchLabelSuffix(radixNode, hexLabel, 4)).to.equal(false); + expect(RadixTree._matchLabelSuffix(radixNode, hexLabel, 5)).to.equal(false); + }); + + it("_getCommonPrefix", () => { + expect(RadixTree._getCommonPrefix('1234567890abcdef', '1234567890abcdef')) + .to.equal('1234567890abcdef'); + expect(RadixTree._getCommonPrefix('1234567890000000', '1234567890abcdef')) + .to.equal('1234567890'); + expect(RadixTree._getCommonPrefix('1234567890abcdef', '1234567890000000')) + .to.equal('1234567890'); + expect(RadixTree._getCommonPrefix('1234567890', '1234567890abcdef')) + .to.equal('1234567890'); + expect(RadixTree._getCommonPrefix('1234567890abcdef', '1234567890')) + .to.equal('1234567890'); + expect(RadixTree._getCommonPrefix('1234567890abcdef', '01234567890abcdef')) + .to.equal(''); + }); + + it("_setChildWithLabel with empty label suffix", () => { + const node = new RadixNode(); + const child = new RadixNode(); + + expect(RadixTree._setChildWithLabel(node, '1', child)).to.equal(true); + assert.deepEqual(node.getChild('1'), child); + expect(child.getLabelRadix()).to.equal('1'); + expect(child.getLabelSuffix()).to.equal(''); + }); + + it("_setChildWithLabel with non-empty label suffix", () => { + const node = new RadixNode(); + const child = new RadixNode(); + + expect(RadixTree._setChildWithLabel(node, '1234567890abcdef', child)).to.equal(true); + expect(node.hasChild('1')).to.equal(true); + assert.deepEqual(node.getChild('1'), child); + expect(child.getLabelRadix()).to.equal('1'); + expect(child.getLabelSuffix()).to.equal('234567890abcdef'); + }); + }); + + describe("Map APIs", () => { + let tree; + + beforeEach(() => { + tree = new RadixTree(); + }) + + describe("_setInMap / _deleteFromMap / _hasInMap / _getFromMap / labels / stateNodes / size", () => { + const stateNode1 = new StateNode(); + const stateNode2 = new StateNode(); + + it("with non-empty label suffices", () => { + const label1 = '0x000111aaa'; + const label2 = '0x000111bbb'; + + expect(tree._getFromMap(label1)).to.equal(null); + expect(tree._getFromMap(label2)).to.equal(null); + expect(tree._hasInMap(label1)).to.equal(false); + expect(tree._hasInMap(label2)).to.equal(false); + assert.deepEqual(tree.labels(), []); + assert.deepEqual(tree.stateNodes(), []); + assert.deepEqual(tree.size(), 0); + + // set first node + tree._setInMap(label1, stateNode1); + + expect(tree._getFromMap(label1)).to.equal(stateNode1); + expect(tree._getFromMap(label2)).to.equal(null); + expect(tree._hasInMap(label1)).to.equal(true); + expect(tree._hasInMap(label2)).to.equal(false); + assert.deepEqual(tree.labels(), [label1]); + assert.deepEqual(tree.stateNodes(), [stateNode1]); + assert.deepEqual(tree.size(), 1); + + // set second node + tree._setInMap(label2, stateNode2); + + expect(tree._getFromMap(label1)).to.equal(stateNode1); + expect(tree._getFromMap(label2)).to.equal(stateNode2); + expect(tree._hasInMap(label1)).to.equal(true); + expect(tree._hasInMap(label2)).to.equal(true); + assert.deepEqual(tree.labels(), [label1, label2]); + assert.deepEqual(tree.stateNodes(), [stateNode1, stateNode2]); + assert.deepEqual(tree.size(), 2); + + // delete first node + tree._deleteFromMap(label1); + + expect(tree._getFromMap(label1)).to.equal(null); + expect(tree._getFromMap(label2)).to.equal(stateNode2); + expect(tree._hasInMap(label1)).to.equal(false); + expect(tree._hasInMap(label2)).to.equal(true); + assert.deepEqual(tree.labels(), [label2]); + assert.deepEqual(tree.stateNodes(), [stateNode2]); + assert.deepEqual(tree.size(), 1); + + // delete second node + tree._deleteFromMap(label2); + + expect(tree._getFromMap(label1)).to.equal(null); + expect(tree._getFromMap(label2)).to.equal(null); + expect(tree._hasInMap(label1)).to.equal(false); + expect(tree._hasInMap(label2)).to.equal(false); + assert.deepEqual(tree.labels(), []); + assert.deepEqual(tree.stateNodes(), []); + assert.deepEqual(tree.size(), 0); + }); + }); + + describe("_copyMapFrom", () => { + const stateNode1 = new StateNode(); + const stateNode2 = new StateNode(); + + it("copy with non-empty label suffices", () => { + const label1 = '0x000111aaa'; + const label2 = '0x000111bbb'; + + // set state nodes + tree._setInMap(label1, stateNode1); + tree._setInMap(label2, stateNode2); + + const newTree = new RadixTree(); + newTree._copyMapFrom(tree); + assert.deepEqual(newTree.labels(), [label1, label2]); + assert.deepEqual(newTree.stateNodes(), [stateNode1, stateNode2]); + assert.deepEqual(newTree.size(), 2); + }); + }); + }); + + describe("Tree APIs", () => { + let tree; + + beforeEach(() => { + tree = new RadixTree(); + }) + + describe("_setInTree / _deleteFromTree / _hasInTree / _getFromTree", () => { + let stateNode1; + let stateNode2; + let stateNode21; + let stateNode22; + let stateNodeInternal; + + beforeEach(() => { + stateNode1 = new StateNode(); + stateNode2 = new StateNode(); + stateNode21 = new StateNode(); + stateNode22 = new StateNode(); + stateNodeInternal = new StateNode(); + }) + + it("set with invalid state node", () => { + const invalidStateNode = new RadixNode(); + const label = '0x000aaa'; + + expect(tree._setInTree(label, invalidStateNode)).to.equal(false); + expect(tree._setInTree(label, '')).to.equal(false); + expect(tree._setInTree(label, true)).to.equal(false); + expect(tree._setInTree(label, null)).to.equal(false); + expect(tree._setInTree(label, undefined)).to.equal(false); + }); + + it("set / delete without common label prefix - without label suffices", () => { + const label1 = '0xa'; + const label2 = '0xb'; + + stateNode1._setLabel(label1); + stateNode2._setLabel(label2); + + expect(tree._hasInTree(label1)).to.equal(false); + expect(tree._hasInTree(label2)).to.equal(false); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null + }); + + // set first node + tree._setInTree(label1, stateNode1); + + expect(tree._hasInTree(label1)).to.equal(true); + expect(tree._getFromTree(label1)).to.equal(stateNode1); + expect(tree._hasInTree(label2)).to.equal(false); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "a": { + ".label": "0xa", + ".proof_hash": null, + ".radix_ph": null + } + }); + + // set second node + tree._setInTree(label2, stateNode2); + + expect(tree._hasInTree(label1)).to.equal(true); + expect(tree._getFromTree(label1)).to.equal(stateNode1); + expect(tree._hasInTree(label2)).to.equal(true); + expect(tree._getFromTree(label2)).to.equal(stateNode2); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "a": { + ".label": "0xa", + ".proof_hash": null, + ".radix_ph": null + }, + "b": { + ".label": "0xb", + ".proof_hash": null, + ".radix_ph": null + } + }); + + // delete first node + tree._deleteFromTree(label1); + + expect(tree._hasInTree(label1)).to.equal(false); + expect(tree._hasInTree(label2)).to.equal(true); + expect(tree._getFromTree(label2)).to.equal(stateNode2); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "b": { + ".label": "0xb", + ".proof_hash": null, + ".radix_ph": null + } + }); + + // delete second node + tree._deleteFromTree(label2); + + expect(tree._hasInTree(label1)).to.equal(false); + expect(tree._hasInTree(label2)).to.equal(false); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null + }); + }); + + it("set / delete without common label prefix - with label suffices", () => { + const label1 = '0xaaa'; + const label2 = '0xbbb'; + + stateNode1._setLabel(label1); + stateNode2._setLabel(label2); + + expect(tree._hasInTree(label1)).to.equal(false); + expect(tree._hasInTree(label2)).to.equal(false); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null + }); + + // set first node + tree._setInTree(label1, stateNode1); + + expect(tree._hasInTree(label1)).to.equal(true); + expect(tree._getFromTree(label1)).to.equal(stateNode1); + expect(tree._hasInTree(label2)).to.equal(false); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "aaa": { + ".label": "0xaaa", + ".proof_hash": null, + ".radix_ph": null + } + }); + + // set second node + tree._setInTree(label2, stateNode2); + + expect(tree._hasInTree(label1)).to.equal(true); + expect(tree._getFromTree(label1)).to.equal(stateNode1); + expect(tree._hasInTree(label2)).to.equal(true); + expect(tree._getFromTree(label2)).to.equal(stateNode2); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "aaa": { + ".label": "0xaaa", + ".proof_hash": null, + ".radix_ph": null + }, + "bbb": { + ".label": "0xbbb", + ".proof_hash": null, + ".radix_ph": null + } + }); + + // delete first node + tree._deleteFromTree(label1); + + expect(tree._hasInTree(label1)).to.equal(false); + expect(tree._hasInTree(label2)).to.equal(true); + expect(tree._getFromTree(label2)).to.equal(stateNode2); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "bbb": { + ".label": "0xbbb", + ".proof_hash": null, + ".radix_ph": null + } + }); + + // delete second node + tree._deleteFromTree(label2); + + expect(tree._hasInTree(label1)).to.equal(false); + expect(tree._hasInTree(label2)).to.equal(false); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null + }); + }); + + it("set / delete without common label prefix - set with substring label suffix", () => { + const label1 = '0xaabb'; + const label2 = '0xaa'; + + stateNode1._setLabel(label1); + stateNode2._setLabel(label2); + + tree._setInTree(label1, stateNode1); + tree._setInTree(label2, stateNode2); + + expect(tree._hasInTree(label1)).to.equal(true); + expect(tree._getFromTree(label1)).to.equal(stateNode1); + expect(tree._hasInTree(label2)).to.equal(true); + expect(tree._getFromTree(label2)).to.equal(stateNode2); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "aa": { + ".label": "0xaa", + ".proof_hash": null, + ".radix_ph": null, + "bb": { + ".label": "0xaabb", + ".proof_hash": null, + ".radix_ph": null + } + } + }); + }); + + it("set / delete without common label prefix - set with superstring label suffix", () => { + const label1 = '0xaa'; + const label2 = '0xaabb'; + + stateNode1._setLabel(label1); + stateNode2._setLabel(label2); + + tree._setInTree(label1, stateNode1); + tree._setInTree(label2, stateNode2); + + expect(tree._hasInTree(label1)).to.equal(true); + expect(tree._getFromTree(label1)).to.equal(stateNode1); + expect(tree._hasInTree(label2)).to.equal(true); + expect(tree._getFromTree(label2)).to.equal(stateNode2); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "aa": { + ".label": "0xaa", + ".proof_hash": null, + ".radix_ph": null, + "bb": { + ".label": "0xaabb", + ".proof_hash": null, + ".radix_ph": null + } + } + }); + }); + + it("set / delete without common label prefix - set with exact-matched label suffix", () => { + const label = '0xaa'; + + stateNode1._setLabel(label); + stateNode2._setLabel(label + '_'); // tweak in order to distinguish + + tree._setInTree(label, stateNode1); + tree._setInTree(label, stateNode2); + + expect(tree._hasInTree(label)).to.equal(true); + expect(tree._getFromTree(label)).to.equal(stateNode2); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "aa": { + ".label": "0xaa_", + ".proof_hash": null, + ".radix_ph": null, + } + }); + }); + + it("set / delete without common label prefix - set / delete with children", () => { + const label1 = '0xaaa'; + const label2 = '0xbbb'; + const label21 = '0xbbb111'; + const label22 = '0xbbb222'; + + stateNode1._setLabel(label1); + stateNode2._setLabel(label2); + stateNode21._setLabel(label21); + stateNode22._setLabel(label22); + + // set first node + tree._setInTree(label1, stateNode1); + // set second node + tree._setInTree(label2, stateNode2); + // set children + tree._setInTree(label21, stateNode21); + tree._setInTree(label22, stateNode22); + + expect(tree._hasInTree(label1)).to.equal(true); + expect(tree._getFromTree(label1)).to.equal(stateNode1); + expect(tree._hasInTree(label2)).to.equal(true); + expect(tree._getFromTree(label2)).to.equal(stateNode2); + expect(tree._hasInTree(label21)).to.equal(true); + expect(tree._getFromTree(label21)).to.equal(stateNode21); + expect(tree._hasInTree(label22)).to.equal(true); + expect(tree._getFromTree(label22)).to.equal(stateNode22); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "aaa": { + ".label": "0xaaa", + ".proof_hash": null, + ".radix_ph": null + }, + "bbb": { + "111": { + ".label": "0xbbb111", + ".proof_hash": null, + ".radix_ph": null + }, + "222": { + ".label": "0xbbb222", + ".proof_hash": null, + ".radix_ph": null + }, + ".label": "0xbbb", + ".proof_hash": null, + ".radix_ph": null + } + }); + + // delete first node + tree._deleteFromTree(label1); + + expect(tree._hasInTree(label1)).to.equal(false); + expect(tree._hasInTree(label2)).to.equal(true); + expect(tree._getFromTree(label2)).to.equal(stateNode2); + expect(tree._hasInTree(label21)).to.equal(true); + expect(tree._getFromTree(label21)).to.equal(stateNode21); + expect(tree._hasInTree(label22)).to.equal(true); + expect(tree._getFromTree(label22)).to.equal(stateNode22); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "bbb": { + "111": { + ".label": "0xbbb111", + ".proof_hash": null, + ".radix_ph": null + }, + "222": { + ".label": "0xbbb222", + ".proof_hash": null, + ".radix_ph": null + }, + ".label": "0xbbb", + ".proof_hash": null, + ".radix_ph": null + } + }); + }); + + it("set / delete without common label prefix - delete with only one child", () => { + const label2 = '0xbbb'; + const label21 = '0xbbb111'; + + stateNode2._setLabel(label2); + stateNode21._setLabel(label21); + + // set a node + tree._setInTree(label2, stateNode2); + // set a child + tree._setInTree(label21, stateNode21); + + expect(tree._hasInTree(label2)).to.equal(true); + expect(tree._getFromTree(label2)).to.equal(stateNode2); + expect(tree._hasInTree(label21)).to.equal(true); + expect(tree._getFromTree(label21)).to.equal(stateNode21); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "bbb": { + "111": { + ".label": "0xbbb111", + ".proof_hash": null, + ".radix_ph": null + }, + ".label": "0xbbb", + ".proof_hash": null, + ".radix_ph": null + } + }); + + // delete the node + tree._deleteFromTree(label2); + + expect(tree._hasInTree(label2)).to.equal(false); + expect(tree._hasInTree(label21)).to.equal(true); + expect(tree._getFromTree(label21)).to.equal(stateNode21); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "bbb111": { + ".label": "0xbbb111", + ".proof_hash": null, + ".radix_ph": null + } + }); + }); + + it("set / delete with common label prefix - without label suffices", () => { + const label1 = '0x000a'; + const label2 = '0x000b'; + + stateNode1._setLabel(label1); + stateNode2._setLabel(label2); + + expect(tree._hasInTree(label1)).to.equal(false); + expect(tree._hasInTree(label2)).to.equal(false); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null + }); + + // set first node + tree._setInTree(label1, stateNode1); + + expect(tree._hasInTree(label1)).to.equal(true); + expect(tree._getFromTree(label1)).to.equal(stateNode1); + expect(tree._hasInTree(label2)).to.equal(false); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000a": { + ".label": "0x000a", + ".proof_hash": null, + ".radix_ph": null + } + }); + + // set second node + tree._setInTree(label2, stateNode2); + + expect(tree._hasInTree(label1)).to.equal(true); + expect(tree._getFromTree(label1)).to.equal(stateNode1); + expect(tree._hasInTree(label2)).to.equal(true); + expect(tree._getFromTree(label2)).to.equal(stateNode2); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000": { + ".radix_ph": null, + "a": { + ".label": "0x000a", + ".proof_hash": null, + ".radix_ph": null + }, + "b": { + ".label": "0x000b", + ".proof_hash": null, + ".radix_ph": null + } + } + }); + + // delete first node + tree._deleteFromTree(label1); + + expect(tree._hasInTree(label1)).to.equal(false); + expect(tree._hasInTree(label2)).to.equal(true); + expect(tree._getFromTree(label2)).to.equal(stateNode2); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000b": { + ".label": "0x000b", + ".proof_hash": null, + ".radix_ph": null + } + }); + + // delete second node + tree._deleteFromTree(label2); + + expect(tree._hasInTree(label1)).to.equal(false); + expect(tree._hasInTree(label2)).to.equal(false); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null + }); + }); + + it("set / delete with common label prefix - with label suffices", () => { + const label1 = '0x000aaa'; + const label2 = '0x000bbb'; + + stateNode1._setLabel(label1); + stateNode2._setLabel(label2); + + expect(tree._hasInTree(label1)).to.equal(false); + expect(tree._hasInTree(label2)).to.equal(false); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null + }); + + // set first node + tree._setInTree(label1, stateNode1); + + expect(tree._hasInTree(label1)).to.equal(true); + expect(tree._getFromTree(label1)).to.equal(stateNode1); + expect(tree._hasInTree(label2)).to.equal(false); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000aaa": { + ".label": "0x000aaa", + ".proof_hash": null, + ".radix_ph": null + } + }); + + // set second node + tree._setInTree(label2, stateNode2); + + expect(tree._hasInTree(label1)).to.equal(true); + expect(tree._getFromTree(label1)).to.equal(stateNode1); + expect(tree._hasInTree(label2)).to.equal(true); + expect(tree._getFromTree(label2)).to.equal(stateNode2); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000": { + ".radix_ph": null, + "aaa": { + ".label": "0x000aaa", + ".proof_hash": null, + ".radix_ph": null + }, + "bbb": { + ".label": "0x000bbb", + ".proof_hash": null, + ".radix_ph": null + } + } + }); + + // delete first node + tree._deleteFromTree(label1); + + expect(tree._hasInTree(label1)).to.equal(false); + expect(tree._hasInTree(label2)).to.equal(true); + expect(tree._getFromTree(label2)).to.equal(stateNode2); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000bbb": { + ".label": "0x000bbb", + ".proof_hash": null, + ".radix_ph": null + } + }); + + // delete second node + tree._deleteFromTree(label2); + + expect(tree._hasInTree(label1)).to.equal(false); + expect(tree._hasInTree(label2)).to.equal(false); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null + }); + }); + + it("set / delete with common label prefix - set on an internal node", () => { + const label1 = '0x000aaa'; + const label2 = '0x000bbb'; + const labelInternal = '0x000'; + + stateNode1._setLabel(label1); + stateNode2._setLabel(label2); + stateNodeInternal._setLabel(labelInternal); + + // add terminal nodes + tree._setInTree(label1, stateNode1); + tree._setInTree(label2, stateNode2); + + expect(tree._hasInTree(label1)).to.equal(true); + expect(tree._getFromTree(label1)).to.equal(stateNode1); + expect(tree._hasInTree(label2)).to.equal(true); + expect(tree._getFromTree(label2)).to.equal(stateNode2); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000": { + ".radix_ph": null, + "aaa": { + ".label": "0x000aaa", + ".proof_hash": null, + ".radix_ph": null + }, + "bbb": { + ".label": "0x000bbb", + ".proof_hash": null, + ".radix_ph": null + } + } + }); + + // set on the internal node + tree._setInTree(labelInternal, stateNodeInternal); + + expect(tree._hasInTree(label1)).to.equal(true); + expect(tree._getFromTree(label1)).to.equal(stateNode1); + expect(tree._hasInTree(label2)).to.equal(true); + expect(tree._getFromTree(label2)).to.equal(stateNode2); + expect(tree._hasInTree(labelInternal)).to.equal(true); + expect(tree._getFromTree(labelInternal)).to.equal(stateNodeInternal); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000": { + ".label": "0x000", + ".proof_hash": null, + ".radix_ph": null, + "aaa": { + ".label": "0x000aaa", + ".proof_hash": null, + ".radix_ph": null + }, + "bbb": { + ".label": "0x000bbb", + ".proof_hash": null, + ".radix_ph": null + } + } + }); + }); + + it("set / delete with common label prefix - set with substring label suffix", () => { + const label1 = '0x000aabb'; + const label2 = '0x000aa'; + + stateNode1._setLabel(label1); + stateNode2._setLabel(label2); + + tree._setInTree(label1, stateNode1); + tree._setInTree(label2, stateNode2); + + expect(tree._hasInTree(label1)).to.equal(true); + expect(tree._getFromTree(label1)).to.equal(stateNode1); + expect(tree._hasInTree(label2)).to.equal(true); + expect(tree._getFromTree(label2)).to.equal(stateNode2); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000aa": { + ".label": "0x000aa", + ".proof_hash": null, + ".radix_ph": null, + "bb": { + ".label": "0x000aabb", + ".proof_hash": null, + ".radix_ph": null + } + } + }); + }); + + it("set / delete with common label prefix - set with superstring label suffix", () => { + const label1 = '0x000aa'; + const label2 = '0x000aabb'; + + stateNode1._setLabel(label1); + stateNode2._setLabel(label2); + + tree._setInTree(label1, stateNode1); + tree._setInTree(label2, stateNode2); + + expect(tree._hasInTree(label1)).to.equal(true); + expect(tree._getFromTree(label1)).to.equal(stateNode1); + expect(tree._hasInTree(label2)).to.equal(true); + expect(tree._getFromTree(label2)).to.equal(stateNode2); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000aa": { + ".label": "0x000aa", + ".proof_hash": null, + ".radix_ph": null, + "bb": { + ".label": "0x000aabb", + ".proof_hash": null, + ".radix_ph": null + } + } + }); + }); + + it("set / delete with common label prefix - set with exact-matched label suffix", () => { + const label = '0x000aa'; + + stateNode1._setLabel(label); + stateNode2._setLabel(label + '_'); // tweak in order to distinguish + + tree._setInTree(label, stateNode1); + tree._setInTree(label, stateNode2); + + expect(tree._hasInTree(label)).to.equal(true); + expect(tree._getFromTree(label)).to.equal(stateNode2); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000aa": { + ".label": "0x000aa_", + ".proof_hash": null, + ".radix_ph": null, + } + }); + }); + + it("set / delete with common label prefix - set / delete with children", () => { + const label1 = '0x000aaa'; + const label2 = '0x000bbb'; + const label21 = '0x000bbb111'; + const label22 = '0x000bbb222'; + + stateNode1._setLabel(label1); + stateNode2._setLabel(label2); + stateNode21._setLabel(label21); + stateNode22._setLabel(label22); + + // set first node + tree._setInTree(label1, stateNode1); + // set second node + tree._setInTree(label2, stateNode2); + // set children + tree._setInTree(label21, stateNode21); + tree._setInTree(label22, stateNode22); + + expect(tree._hasInTree(label1)).to.equal(true); + expect(tree._getFromTree(label1)).to.equal(stateNode1); + expect(tree._hasInTree(label2)).to.equal(true); + expect(tree._getFromTree(label2)).to.equal(stateNode2); + expect(tree._hasInTree(label21)).to.equal(true); + expect(tree._getFromTree(label21)).to.equal(stateNode21); + expect(tree._hasInTree(label22)).to.equal(true); + expect(tree._getFromTree(label22)).to.equal(stateNode22); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000": { + ".radix_ph": null, + "aaa": { + ".label": "0x000aaa", + ".proof_hash": null, + ".radix_ph": null + }, + "bbb": { + "111": { + ".label": "0x000bbb111", + ".proof_hash": null, + ".radix_ph": null + }, + "222": { + ".label": "0x000bbb222", + ".proof_hash": null, + ".radix_ph": null + }, + ".label": "0x000bbb", + ".proof_hash": null, + ".radix_ph": null + } + } + }); + + // delete first node + tree._deleteFromTree(label1); + + expect(tree._hasInTree(label1)).to.equal(false); + expect(tree._hasInTree(label2)).to.equal(true); + expect(tree._getFromTree(label2)).to.equal(stateNode2); + expect(tree._hasInTree(label21)).to.equal(true); + expect(tree._getFromTree(label21)).to.equal(stateNode21); + expect(tree._hasInTree(label22)).to.equal(true); + expect(tree._getFromTree(label22)).to.equal(stateNode22); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000bbb": { + "111": { + ".label": "0x000bbb111", + ".proof_hash": null, + ".radix_ph": null + }, + "222": { + ".label": "0x000bbb222", + ".proof_hash": null, + ".radix_ph": null + }, + ".label": "0x000bbb", + ".proof_hash": null, + ".radix_ph": null + } + }); + }); + + it("set / delete with common label prefix - delete with only one child", () => { + const label2 = '0x000bbb'; + const label21 = '0x000bbb111'; + + stateNode2._setLabel(label2); + stateNode21._setLabel(label21); + + // set a node + tree._setInTree(label2, stateNode2); + // set a child + tree._setInTree(label21, stateNode21); + + expect(tree._hasInTree(label2)).to.equal(true); + expect(tree._getFromTree(label2)).to.equal(stateNode2); + expect(tree._hasInTree(label21)).to.equal(true); + expect(tree._getFromTree(label21)).to.equal(stateNode21); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000bbb": { + "111": { + ".label": "0x000bbb111", + ".proof_hash": null, + ".radix_ph": null + }, + ".label": "0x000bbb", + ".proof_hash": null, + ".radix_ph": null + } + }); + + // delete the node + tree._deleteFromTree(label2); + + expect(tree._hasInTree(label2)).to.equal(false); + expect(tree._hasInTree(label21)).to.equal(true); + expect(tree._getFromTree(label21)).to.equal(stateNode21); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000bbb111": { + ".label": "0x000bbb111", + ".proof_hash": null, + ".radix_ph": null + } + }); + }); + }); + + describe("_copyTreeFrom", () => { + const stateNode1 = new StateNode(); + const stateNode2 = new StateNode(); + const stateNode21 = new StateNode(); + const stateNode22 = new StateNode(); + + it("copy with children", () => { + const label1 = '0x000aaa'; + const label2 = '0x000bbb'; + const label21 = '0x000bbb111'; + const label22 = '0x000bbb222'; + + stateNode1._setLabel(label1); + stateNode2._setLabel(label2); + stateNode21._setLabel(label21); + stateNode22._setLabel(label22); + + // set state nodes + tree._setInTree(label1, stateNode1); + tree._setInTree(label2, stateNode2); + tree._setInTree(label21, stateNode21); + tree._setInTree(label22, stateNode22); + + const newTree = new RadixTree(); + newTree._copyTreeFrom(tree); + expect(newTree._hasInTree(label1)).to.equal(true); + expect(newTree._getFromTree(label1)).to.equal(stateNode1); + expect(newTree._hasInTree(label2)).to.equal(true); + expect(newTree._getFromTree(label2)).to.equal(stateNode2); + expect(newTree._hasInTree(label21)).to.equal(true); + expect(newTree._getFromTree(label21)).to.equal(stateNode21); + expect(newTree._hasInTree(label22)).to.equal(true); + expect(newTree._getFromTree(label22)).to.equal(stateNode22); + assert.deepEqual(newTree.toJsObject(true), { + ".radix_ph": null, + "000": { + ".radix_ph": null, + "aaa": { + ".label": "0x000aaa", + ".proof_hash": null, + ".radix_ph": null + }, + "bbb": { + "111": { + ".label": "0x000bbb111", + ".proof_hash": null, + ".radix_ph": null + }, + "222": { + ".label": "0x000bbb222", + ".proof_hash": null, + ".radix_ph": null + }, + ".label": "0x000bbb", + ".proof_hash": null, + ".radix_ph": null + } + } + }); + }); + }); + }); + + describe("Common APIs", () => { + let tree; + + beforeEach(() => { + tree = new RadixTree(); + }) + + describe("set / deletep / has / get / labels / stateNodes / size", () => { + const stateNode1 = new StateNode(); + const stateNode2 = new StateNode(); + const stateNode21 = new StateNode(); + const stateNode22 = new StateNode(); + const stateNodeInternal = new StateNode(); + + it("set with invalid state node", () => { + const invalidStateNode = new RadixNode(); + const label = '0x000aaa'; + + expect(tree.set(label, invalidStateNode)).to.equal(false); + expect(tree.set(label, '')).to.equal(false); + expect(tree.set(label, true)).to.equal(false); + expect(tree.set(label, null)).to.equal(false); + expect(tree.set(label, undefined)).to.equal(false); + }); + + it("set / delete without common label prefix", () => { + const label1 = '0xa'; + const label2 = '0xb'; + + expect(tree.has(label1)).to.equal(false); + expect(tree.has(label2)).to.equal(false); + assert.deepEqual(tree.labels(), []); + assert.deepEqual(tree.stateNodes(), []); + assert.deepEqual(tree.size(), 0); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null + }); + + // set first node + tree.set(label1, stateNode1); + + expect(tree.has(label1)).to.equal(true); + expect(tree.get(label1)).to.equal(stateNode1); + expect(tree.has(label2)).to.equal(false); + assert.deepEqual(tree.labels(), [label1]); + assert.deepEqual(tree.stateNodes(), [stateNode1]); + assert.deepEqual(tree.size(), 1); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "a": { + ".label": null, + ".proof_hash": null, + ".radix_ph": null + } + }); + + // set second node + tree.set(label2, stateNode2); + + expect(tree.has(label1)).to.equal(true); + expect(tree.get(label1)).to.equal(stateNode1); + expect(tree.has(label2)).to.equal(true); + expect(tree.get(label2)).to.equal(stateNode2); + assert.deepEqual(tree.labels(), [label1, label2]); + assert.deepEqual(tree.stateNodes(), [stateNode1, stateNode2]); + assert.deepEqual(tree.size(), 2); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "a": { + ".label": null, + ".proof_hash": null, + ".radix_ph": null + }, + "b": { + ".label": null, + ".proof_hash": null, + ".radix_ph": null + } + }); + + // delete first node + tree.delete(label1); + + expect(tree.has(label1)).to.equal(false); + expect(tree.has(label2)).to.equal(true); + expect(tree.get(label2)).to.equal(stateNode2); + assert.deepEqual(tree.labels(), [label2]); + assert.deepEqual(tree.stateNodes(), [stateNode2]); + assert.deepEqual(tree.size(), 1); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "b": { + ".label": null, + ".proof_hash": null, + ".radix_ph": null + } + }); + + // delete second node + tree.delete(label2); + + expect(tree.has(label1)).to.equal(false); + expect(tree.has(label2)).to.equal(false); + assert.deepEqual(tree.labels(), []); + assert.deepEqual(tree.stateNodes(), []); + assert.deepEqual(tree.size(), 0); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null + }); + }); + + it("set / delete with common label prefix", () => { + const label1 = '0x000a'; + const label2 = '0x000b'; + + expect(tree.has(label1)).to.equal(false); + expect(tree.has(label2)).to.equal(false); + assert.deepEqual(tree.labels(), []); + assert.deepEqual(tree.stateNodes(), []); + assert.deepEqual(tree.size(), 0); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null + }); + + // set first node + tree.set(label1, stateNode1); + + expect(tree.has(label1)).to.equal(true); + expect(tree.get(label1)).to.equal(stateNode1); + expect(tree.has(label2)).to.equal(false); + assert.deepEqual(tree.labels(), [label1]); + assert.deepEqual(tree.stateNodes(), [stateNode1]); + assert.deepEqual(tree.size(), 1); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000a": { + ".label": null, + ".proof_hash": null, + ".radix_ph": null + } + }); + + // set second node + tree.set(label2, stateNode2); + + expect(tree.has(label1)).to.equal(true); + expect(tree.get(label1)).to.equal(stateNode1); + expect(tree.has(label2)).to.equal(true); + expect(tree.get(label2)).to.equal(stateNode2); + assert.deepEqual(tree.labels(), [label1, label2]); + assert.deepEqual(tree.stateNodes(), [stateNode1, stateNode2]); + assert.deepEqual(tree.size(), 2); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000": { + ".radix_ph": null, + "a": { + ".label": null, + ".proof_hash": null, + ".radix_ph": null + }, + "b": { + ".label": null, + ".proof_hash": null, + ".radix_ph": null + } + } + }); + + // delete first node + tree.delete(label1); + + expect(tree.has(label1)).to.equal(false); + expect(tree.has(label2)).to.equal(true); + expect(tree.get(label2)).to.equal(stateNode2); + assert.deepEqual(tree.labels(), [label2]); + assert.deepEqual(tree.stateNodes(), [stateNode2]); + assert.deepEqual(tree.size(), 1); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000b": { + ".label": null, + ".proof_hash": null, + ".radix_ph": null + } + }); + + // delete second node + tree.delete(label2); + + expect(tree.has(label1)).to.equal(false); + expect(tree.has(label2)).to.equal(false); + assert.deepEqual(tree.labels(), []); + assert.deepEqual(tree.stateNodes(), []); + assert.deepEqual(tree.size(), 0); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null + }); + }); + + it("set on an internal node", () => { + const label1 = '0x000a'; + const label2 = '0x000b'; + const labelInternal = '0x000'; + + // add terminal nodes + tree.set(label1, stateNode1); + tree.set(label2, stateNode2); + + expect(tree.has(label1)).to.equal(true); + expect(tree.get(label1)).to.equal(stateNode1); + expect(tree.has(label2)).to.equal(true); + expect(tree.get(label2)).to.equal(stateNode2); + assert.deepEqual(tree.labels(), [label1, label2]); + assert.deepEqual(tree.stateNodes(), [stateNode1, stateNode2]); + assert.deepEqual(tree.size(), 2); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000": { + ".radix_ph": null, + "a": { + ".label": null, + ".proof_hash": null, + ".radix_ph": null + }, + "b": { + ".label": null, + ".proof_hash": null, + ".radix_ph": null + } + } + }); + + // set on the internal node + tree.set(labelInternal, stateNodeInternal); + + expect(tree.has(label1)).to.equal(true); + expect(tree.get(label1)).to.equal(stateNode1); + expect(tree.has(label2)).to.equal(true); + expect(tree.get(label2)).to.equal(stateNode2); + expect(tree.has(labelInternal)).to.equal(true); + expect(tree.get(labelInternal)).to.equal(stateNodeInternal); + assert.deepEqual(tree.labels(), [label1, label2, labelInternal]); + assert.deepEqual(tree.stateNodes(), [stateNode1, stateNode2, stateNodeInternal]); + assert.deepEqual(tree.size(), 3); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000": { + ".label": null, + ".proof_hash": null, + ".radix_ph": null, + "a": { + ".label": null, + ".proof_hash": null, + ".radix_ph": null + }, + "b": { + ".label": null, + ".proof_hash": null, + ".radix_ph": null + } + } + }); + }); + + it("set / delete with non-empty label suffices", () => { + const label1 = '0x000aaa'; + const label2 = '0x000bbb'; + + expect(tree.has(label1)).to.equal(false); + expect(tree.has(label2)).to.equal(false); + assert.deepEqual(tree.labels(), []); + assert.deepEqual(tree.stateNodes(), []); + assert.deepEqual(tree.size(), 0); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null + }); + + // set first node + tree.set(label1, stateNode1); + + expect(tree.has(label1)).to.equal(true); + expect(tree.get(label1)).to.equal(stateNode1); + expect(tree.has(label2)).to.equal(false); + assert.deepEqual(tree.labels(), [label1]); + assert.deepEqual(tree.stateNodes(), [stateNode1]); + assert.deepEqual(tree.size(), 1); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000aaa": { + ".label": null, + ".proof_hash": null, + ".radix_ph": null + } + }); + + // set second node + tree.set(label2, stateNode2); + + expect(tree.has(label1)).to.equal(true); + expect(tree.get(label1)).to.equal(stateNode1); + expect(tree.has(label2)).to.equal(true); + expect(tree.get(label2)).to.equal(stateNode2); + assert.deepEqual(tree.labels(), [label1, label2]); + assert.deepEqual(tree.stateNodes(), [stateNode1, stateNode2]); + assert.deepEqual(tree.size(), 2); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000": { + ".radix_ph": null, + "aaa": { + ".label": null, + ".proof_hash": null, + ".radix_ph": null + }, + "bbb": { + ".label": null, + ".proof_hash": null, + ".radix_ph": null + } + } + }); + + // delete first node + tree.delete(label1); + + expect(tree.has(label1)).to.equal(false); + expect(tree.has(label2)).to.equal(true); + expect(tree.get(label2)).to.equal(stateNode2); + assert.deepEqual(tree.labels(), [label2]); + assert.deepEqual(tree.stateNodes(), [stateNode2]); + assert.deepEqual(tree.size(), 1); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000bbb": { + ".label": null, + ".proof_hash": null, + ".radix_ph": null + } + }); + + // delete second node + tree.delete(label2); + + expect(tree.has(label1)).to.equal(false); + expect(tree.has(label2)).to.equal(false); + assert.deepEqual(tree.labels(), []); + assert.deepEqual(tree.stateNodes(), []); + assert.deepEqual(tree.size(), 0); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null + }); + }); + + it("set / delete with children", () => { + const label1 = '0x000aaa'; + const label2 = '0x000bbb'; + const label21 = '0x000bbb111'; + const label22 = '0x000bbb222'; + + // set first node + tree.set(label1, stateNode1); + // set second node + tree.set(label2, stateNode2); + // set children + tree.set(label21, stateNode21); + tree.set(label22, stateNode22); + + expect(tree.has(label1)).to.equal(true); + expect(tree.get(label1)).to.equal(stateNode1); + expect(tree.has(label2)).to.equal(true); + expect(tree.get(label2)).to.equal(stateNode2); + expect(tree.has(label21)).to.equal(true); + expect(tree.get(label21)).to.equal(stateNode21); + expect(tree.has(label22)).to.equal(true); + expect(tree.get(label22)).to.equal(stateNode22); + assert.deepEqual(tree.labels(), [label1, label2, label21, label22]); + assert.deepEqual(tree.stateNodes(), [stateNode1, stateNode2, stateNode21, stateNode22]); + assert.deepEqual(tree.size(), 4); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000": { + ".radix_ph": null, + "aaa": { + ".label": null, + ".proof_hash": null, + ".radix_ph": null + }, + "bbb": { + "111": { + ".label": null, + ".proof_hash": null, + ".radix_ph": null + }, + "222": { + ".label": null, + ".proof_hash": null, + ".radix_ph": null + }, + ".label": null, + ".proof_hash": null, + ".radix_ph": null + } + } + }); + + // delete first node + tree.delete(label1); + + expect(tree.has(label1)).to.equal(false); + expect(tree.has(label2)).to.equal(true); + expect(tree.get(label2)).to.equal(stateNode2); + expect(tree.has(label21)).to.equal(true); + expect(tree.get(label21)).to.equal(stateNode21); + expect(tree.has(label22)).to.equal(true); + expect(tree.get(label22)).to.equal(stateNode22); + assert.deepEqual(tree.labels(), [label2, label21, label22]); + assert.deepEqual(tree.stateNodes(), [stateNode2, stateNode21, stateNode22]); + assert.deepEqual(tree.size(), 3); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000bbb": { + "111": { + ".label": null, + ".proof_hash": null, + ".radix_ph": null + }, + "222": { + ".label": null, + ".proof_hash": null, + ".radix_ph": null + }, + ".label": null, + ".proof_hash": null, + ".radix_ph": null + } + }); + }); + + it("delete with only one child", () => { + const label2 = '0x000bbb'; + const label21 = '0x000bbb111'; + + // set a node + tree.set(label2, stateNode2); + // set a child + tree.set(label21, stateNode21); + + expect(tree.has(label2)).to.equal(true); + expect(tree.get(label2)).to.equal(stateNode2); + expect(tree.has(label21)).to.equal(true); + expect(tree.get(label21)).to.equal(stateNode21); + assert.deepEqual(tree.labels(), [label2, label21]); + assert.deepEqual(tree.stateNodes(), [stateNode2, stateNode21]); + assert.deepEqual(tree.size(), 2); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000bbb": { + "111": { + ".label": null, + ".proof_hash": null, + ".radix_ph": null + }, + ".label": null, + ".proof_hash": null, + ".radix_ph": null + } + }); + + // delete the node + tree.delete(label2); + + expect(tree.has(label2)).to.equal(false); + expect(tree.has(label21)).to.equal(true); + expect(tree.get(label21)).to.equal(stateNode21); + assert.deepEqual(tree.labels(), [label21]); + assert.deepEqual(tree.stateNodes(), [stateNode21]); + assert.deepEqual(tree.size(), 1); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000bbb111": { + ".label": null, + ".proof_hash": null, + ".radix_ph": null + } + }); + }); + + it("get / has / set / update / verify proof hash", () => { + const label1 = '0x000aaa'; + const stateNode1 = new StateNode(); + const stateNodePH1 = 'childStateNodePH1'; + stateNode1.setProofHash(stateNodePH1); + + const label11 = '0x000aaa111'; + const stateNodePH11 = 'childStateNodePH11'; + const stateNode11 = new StateNode(); + stateNode11.setProofHash(stateNodePH11); + + const label12 = '0x000aaa212'; + const stateNodePH12 = 'childStateNodePH12'; + const stateNode12 = new StateNode(); + stateNode12.setProofHash(stateNodePH12); + + const label21 = '0x000bbb121'; + const stateNodePH21 = 'childStateNodePH21'; + const stateNode21 = new StateNode(); + stateNode21.setProofHash(stateNodePH21); + + const label22 = '0x000bbb222'; + const stateNodePH22 = 'childStateNodePH22'; + const stateNode22 = new StateNode(); + stateNode22.setProofHash(stateNodePH22); + + const label3 = '0x111ccc'; + const stateNode3 = new StateNode(); + const stateNodePH3 = 'childStateNodePH3'; + stateNode3.setProofHash(stateNodePH3); + + tree.set(label1, stateNode1); + tree.set(label11, stateNode11); + tree.set(label12, stateNode12); + tree.set(label21, stateNode21); + tree.set(label22, stateNode22); + tree.set(label3, stateNode3); + + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": null, + "000": { + ".radix_ph": null, + "aaa": { + "111": { + ".label": null, + ".proof_hash": "childStateNodePH11", + ".radix_ph": null + }, + "212": { + ".label": null, + ".proof_hash": "childStateNodePH12", + ".radix_ph": null + }, + ".label": null, + ".proof_hash": "childStateNodePH1", + ".radix_ph": null + }, + "bbb": { + "121": { + ".label": null, + ".proof_hash": "childStateNodePH21", + ".radix_ph": null + }, + "222": { + ".label": null, + ".proof_hash": "childStateNodePH22", + ".radix_ph": null + }, + ".radix_ph": null + } + }, + "111ccc": { + ".label": null, + ".proof_hash": "childStateNodePH3", + ".radix_ph": null + } + }); + + // initial status + expect(tree.getRootProofHash()).to.equal(null); + expect(tree.verifyProofHashForRadixTree()).to.equal(false); + + // set + expect(tree.updateProofHashForRadixTree()).to.equal(9); + expect(tree.getRootProofHash()).to.equal( + '0xbc310c6c1b9d339951756d3c0f90bb25e70be0c0a261e04564917ce6c57016d5'); + expect(tree.verifyProofHashForRadixTree()).to.equal(true); + + // change of a state node's proof hash + stateNode21.setProofHash('another PH'); + expect(tree.verifyProofHashForRadixTree()).to.equal(false); + + // update + expect(tree.updateProofHashForRadixPath(label21)).to.equal(4); + expect(tree.getRootProofHash()).to.equal( + '0x20520d0c668099565300affd3c4b288fb59dc37b9fbf31702e99a37b564d12d5'); + expect(tree.verifyProofHashForRadixTree()).to.equal(true); + }); + + it("getProofOfState", () => { + const label1 = '0x000aaa'; + const stateNode1 = new StateNode(); + const stateNodePH1 = 'childStateNodePH1'; + stateNode1.setProofHash(stateNodePH1); + + const label11 = '0x000aaa111'; + const stateNodePH11 = 'childStateNodePH11'; + const stateNode11 = new StateNode(); + stateNode11.setProofHash(stateNodePH11); + + const label12 = '0x000aaa212'; + const stateNodePH12 = 'childStateNodePH12'; + const stateNode12 = new StateNode(); + stateNode12.setProofHash(stateNodePH12); + + const label21 = '0x000bbb121'; + const stateNodePH21 = 'childStateNodePH21'; + const stateNode21 = new StateNode(); + stateNode21.setProofHash(stateNodePH21); + + const label22 = '0x000bbb222'; + const stateNodePH22 = 'childStateNodePH22'; + const stateNode22 = new StateNode(); + stateNode22.setProofHash(stateNodePH22); + + tree.set(label1, stateNode1); + tree.set(label11, stateNode11); + tree.set(label12, stateNode12); + tree.set(label21, stateNode21); + tree.set(label22, stateNode22); + + expect(tree.updateProofHashForRadixTree()).to.equal(8); + assert.deepEqual(tree.toJsObject(true), { + ".radix_ph": "0x890e6c975c63529362955c359c0b7552bf1f88c4945f12a18458f8acafb17b25", + "000": { + ".radix_ph": "0x7de885f5d5ecdd9e059584874ae468ba9d1ecdebb3c320fa02e6a2ac58413386", + "aaa": { + "111": { + ".label": null, + ".proof_hash": "childStateNodePH11", + ".radix_ph": "0x9fb9b2e6c1dc7fed76964029cb13fb1cdc115dc0a9cef54fe052533ab992a58c" + }, + "212": { + ".label": null, + ".proof_hash": "childStateNodePH12", + ".radix_ph": "0xbde6ad92fee46f223375703e8376c3c8d75989d3b9867520442e63e0836ff596" + }, + ".label": null, + ".proof_hash": "childStateNodePH1", + ".radix_ph": "0x47f652dd768456603c8bb25c5ab7157d43e3edafc51837038f42a6026bf6bb44" + }, + "bbb": { + "121": { + ".label": null, + ".proof_hash": "childStateNodePH21", + ".radix_ph": "0x68971271b6018c8827230bb696d7d2661ebb286f95851e72da889e1af6b22721" + }, + "222": { + ".label": null, + ".proof_hash": "childStateNodePH22", + ".radix_ph": "0xba9d1dcddd02911d1d260f8acd4e3857174d98a57e6b3c7e0577c8a07056b057" + }, + ".radix_ph": "0x35286cd28c53fbe623eed76d4c09246645451b92cdded41ce9952436dc4656c3" + } + } + }); + + // on an internal radix node + assert.deepEqual(tree.getProofOfState(label1, 'state_proof1'), { + ".radix_ph": "0x890e6c975c63529362955c359c0b7552bf1f88c4945f12a18458f8acafb17b25", + "000": { + ".radix_ph": "0x7de885f5d5ecdd9e059584874ae468ba9d1ecdebb3c320fa02e6a2ac58413386", + "aaa": { + ".label": "0x000aaa", + ".proof_hash": "state_proof1", + ".radix_ph": "0x47f652dd768456603c8bb25c5ab7157d43e3edafc51837038f42a6026bf6bb44" + }, + "bbb": { + ".radix_ph": "0x35286cd28c53fbe623eed76d4c09246645451b92cdded41ce9952436dc4656c3" + } + } + }); + + // on a terminal radix node + assert.deepEqual(tree.getProofOfState(label22, 'state_proof22'), { + ".radix_ph": "0x890e6c975c63529362955c359c0b7552bf1f88c4945f12a18458f8acafb17b25", + "000": { + ".radix_ph": "0x7de885f5d5ecdd9e059584874ae468ba9d1ecdebb3c320fa02e6a2ac58413386", + "aaa": { + ".radix_ph": "0x47f652dd768456603c8bb25c5ab7157d43e3edafc51837038f42a6026bf6bb44" + }, + "bbb": { + ".radix_ph": "0x35286cd28c53fbe623eed76d4c09246645451b92cdded41ce9952436dc4656c3", + "121": { + ".radix_ph": "0x68971271b6018c8827230bb696d7d2661ebb286f95851e72da889e1af6b22721" + }, + "222": { + ".label": "0x000bbb222", + ".proof_hash": "state_proof22", + ".radix_ph": "0xba9d1dcddd02911d1d260f8acd4e3857174d98a57e6b3c7e0577c8a07056b057" + } + } + } + }); + }); + }); + + describe("copyFrom", () => { + const stateNode1 = new StateNode(); + const stateNode2 = new StateNode(); + const stateNode21 = new StateNode(); + const stateNode22 = new StateNode(); + + it("copy with children", () => { + const label1 = '0x000aaa'; + const label2 = '0x000bbb'; + const label21 = '0x000bbb111'; + const label22 = '0x000bbb222'; + + // set state nodes + tree.set(label1, stateNode1); + tree.set(label2, stateNode2); + tree.set(label21, stateNode21); + tree.set(label22, stateNode22); + + const newTree = new RadixTree(); + newTree.copyFrom(tree); + + expect(newTree.has(label1)).to.equal(true); + expect(newTree.get(label1)).to.equal(stateNode1); + expect(newTree.has(label2)).to.equal(true); + expect(newTree.get(label2)).to.equal(stateNode2); + expect(newTree.has(label21)).to.equal(true); + expect(newTree.get(label21)).to.equal(stateNode21); + expect(newTree.has(label22)).to.equal(true); + expect(newTree.get(label22)).to.equal(stateNode22); + assert.deepEqual(newTree.labels(), [label1, label2, label21, label22]); + assert.deepEqual(newTree.stateNodes(), [stateNode1, stateNode2, stateNode21, stateNode22]); + assert.deepEqual(newTree.size(), 4); + assert.deepEqual(newTree.toJsObject(true), { + ".radix_ph": null, + "000": { + ".radix_ph": null, + "aaa": { + ".label": null, + ".proof_hash": null, + ".radix_ph": null + }, + "bbb": { + "111": { + ".label": null, + ".proof_hash": null, + ".radix_ph": null + }, + "222": { + ".label": null, + ".proof_hash": null, + ".radix_ph": null + }, + ".label": null, + ".proof_hash": null, + ".radix_ph": null + } + } + }); + }); + }); + }); +}); diff --git a/unittest/rule-util.test.js b/unittest/rule-util.test.js index 0c737e99d..6d66c5de6 100644 --- a/unittest/rule-util.test.js +++ b/unittest/rule-util.test.js @@ -171,6 +171,62 @@ describe("RuleUtil", () => { }) }) + describe("isHexString", () => { + it("when invalid input", () => { + expect(util.isHexString(true)).to.equal(false); + expect(util.isHexString(false)).to.equal(false); + expect(util.isHexString(0)).to.equal(false); + expect(util.isHexString(10)).to.equal(false); + expect(util.isHexString(null)).to.equal(false); + expect(util.isHexString(undefined)).to.equal(false); + expect(util.isHexString(Infinity)).to.equal(false); + expect(util.isHexString(NaN)).to.equal(false); + expect(util.isHexString('')).to.equal(false); + expect(util.isHexString('abc')).to.equal(false); + expect(util.isHexString('0')).to.equal(false); + expect(util.isHexString([10])).to.equal(false); + expect(util.isHexString({a: 'A'})).to.equal(false); + expect(util.isHexString('0x6af1ec8d4fx')).to.equal(false); + expect(util.isHexString('0x6AF1EC8D4FX')).to.equal(false); + expect(util.isHexString('0xx6af1ec8d4f')).to.equal(false); + expect(util.isHexString('0xx6AF1EC8D4F')).to.equal(false); + expect(util.isHexString('00x6af1ec8d4f')).to.equal(false); + expect(util.isHexString('00x6AF1EC8D4F')).to.equal(false); + }) + + it("when valid input", () => { + expect(util.isHexString('0x')).to.equal(true); + expect(util.isHexString('0x6af1ec8d4f')).to.equal(true); + expect(util.isHexString('0x6AF1EC8D4F')).to.equal(true); + }) + }) + + describe("isValidHash", () => { + it("when invalid input", () => { + expect(util.isValidHash(true)).to.equal(false); + expect(util.isValidHash(false)).to.equal(false); + expect(util.isValidHash(0)).to.equal(false); + expect(util.isValidHash(10)).to.equal(false); + expect(util.isValidHash(null)).to.equal(false); + expect(util.isValidHash(undefined)).to.equal(false); + expect(util.isValidHash(Infinity)).to.equal(false); + expect(util.isValidHash(NaN)).to.equal(false); + expect(util.isValidHash('')).to.equal(false); + expect(util.isValidHash('abc')).to.equal(false); + expect(util.isValidHash('0')).to.equal(false); + expect(util.isValidHash([10])).to.equal(false); + expect(util.isValidHash({a: 'A'})).to.equal(false); + expect(util.isValidHash('0x')).to.equal(false); + expect(util.isValidHash('0x6af1ec8d4f0a55bac328cb20336ed0eff46fa6334ebd112147892f1b15aafc8')).to.equal(false); // 63 chars + expect(util.isValidHash('0x6af1ec8d4f0a55bac328cb20336ed0eff46fa6334ebd112147892f1b15aafc8cc')).to.equal(false); // 65 chars + }) + + it("when valid input", () => { + expect(util.isValidHash('0x6af1ec8d4f0a55bac328cb20336ed0eff46fa6334ebd112147892f1b15aafc8c')).to.equal(true); + expect(util.isValidHash('0x89a1b02058f0d0b7c93957d0ff290cf44cef419d1275afcb430f6e9536e4afb5')).to.equal(true); + }) + }) + describe("keys", () => { it("when invalid input", () => { assert.deepEqual(util.keys(true), []); @@ -274,32 +330,6 @@ describe("RuleUtil", () => { }) }) - describe("toBool", () => { - it("returns false", () => { - expect(util.toBool(0)).to.equal(false); - expect(util.toBool(10)).to.equal(false); - expect(util.toBool(-1)).to.equal(false); - expect(util.toBool(15.5)).to.equal(false); - expect(util.toBool(null)).to.equal(false); - expect(util.toBool(undefined)).to.equal(false); - expect(util.toBool(Infinity)).to.equal(false); - expect(util.toBool(NaN)).to.equal(false); - expect(util.toBool('')).to.equal(false); - expect(util.toBool('abc')).to.equal(false); - expect(util.toBool('false')).to.equal(false); - expect(util.toBool({})).to.equal(false); - expect(util.toBool({a: 'A'})).to.equal(false); - expect(util.toBool([])).to.equal(false); - expect(util.toBool([10])).to.equal(false); - expect(util.toBool(false)).to.equal(false); - }) - - it("returns true", () => { - expect(util.toBool(true)).to.equal(true); - expect(util.toBool('true')).to.equal(true); - }) - }) - describe("isValAddr", () => { it("when non-string input", () => { expect(util.isValAddr(true)).to.equal(false); @@ -386,4 +416,30 @@ describe("RuleUtil", () => { 'staking|consensus|0x09A0d53FDf1c36A131938eb379b98910e55EEfe1|0')).to.equal(true); }) }) + + describe("toBool", () => { + it("returns false", () => { + expect(util.toBool(0)).to.equal(false); + expect(util.toBool(10)).to.equal(false); + expect(util.toBool(-1)).to.equal(false); + expect(util.toBool(15.5)).to.equal(false); + expect(util.toBool(null)).to.equal(false); + expect(util.toBool(undefined)).to.equal(false); + expect(util.toBool(Infinity)).to.equal(false); + expect(util.toBool(NaN)).to.equal(false); + expect(util.toBool('')).to.equal(false); + expect(util.toBool('abc')).to.equal(false); + expect(util.toBool('false')).to.equal(false); + expect(util.toBool({})).to.equal(false); + expect(util.toBool({a: 'A'})).to.equal(false); + expect(util.toBool([])).to.equal(false); + expect(util.toBool([10])).to.equal(false); + expect(util.toBool(false)).to.equal(false); + }) + + it("returns true", () => { + expect(util.toBool(true)).to.equal(true); + expect(util.toBool('true')).to.equal(true); + }) + }) }) \ No newline at end of file diff --git a/unittest/state-node.test.js b/unittest/state-node.test.js index 8c8525031..01f69f345 100644 --- a/unittest/state-node.test.js +++ b/unittest/state-node.test.js @@ -4,8 +4,11 @@ const expect = chai.expect; const assert = chai.assert; const CommonUtil = require('../common/common-util'); -const { HASH_DELIMITER } = require('../common/constants'); const { GET_OPTIONS_INCLUDE_ALL } = require('./test-util'); +const { + updateStateInfoForStateTree, + verifyProofHashForStateTree, +} = require('../db/state-util'); describe("state-node", () => { let node; @@ -13,9 +16,11 @@ describe("state-node", () => { let child1; let child2; let child3; - const label1 = 'label1'; - const label2 = 'label2'; - const label3 = 'label3'; + let child4; + const label1 = '0x00aaaa'; + const label2 = '0x11bbbb'; + const label3 = '0x11bb00'; + const label4 = '0x11bb11'; let stateTree; beforeEach(() => { @@ -24,22 +29,27 @@ describe("state-node", () => { child1 = new StateNode(); child2 = new StateNode(); child3 = new StateNode(); + child4 = new StateNode(); child1.setValue('value1'); child2.setValue('value2'); child3.setValue('value3'); + child4.setValue('value4'); stateTree = new StateNode(); stateTree.setChild(label1, child1); stateTree.setChild(label2, child2); stateTree.setChild(label3, child3); + stateTree.setChild(label4, child4); }) describe("Initialization", () => { it("constructor", () => { + expect(node.label).to.equal(null); expect(node.isLeaf).to.equal(true); assert.deepEqual(node.getParentNodes(), []); expect(node.numParents()).to.equal(0); assert.deepEqual(node.getChildLabels(), []); assert.deepEqual(node.getChildNodes(), []); + expect(node.label).to.equal(null); expect(node.value).to.equal(null); expect(node.proofHash).to.equal(null); expect(node.version).to.equal(null); @@ -50,6 +60,7 @@ describe("state-node", () => { it("constructor", () => { const node2 = new StateNode('version1'); expect(node2.version).to.equal('version1'); + expect(node2.label).to.equal(null); expect(node2.isLeaf).to.equal(true); expect(node2.parentSet).to.not.be.null; expect(node2.childMap).to.not.be.null; @@ -67,6 +78,7 @@ describe("state-node", () => { describe("clone", () => { it("leaf node", () => { + node._setLabel('label'); node.setValue('value0'); node.setProofHash('hash'); node.setVersion('version1'); @@ -77,6 +89,8 @@ describe("state-node", () => { assert.deepEqual(clone.getParentNodes(), []); assert.deepEqual(clone.getChildLabels(), []); assert.deepEqual(clone.getChildNodes(), []); + assert.deepEqual(clone.numChildren(), 0); + expect(clone.getLabel()).to.equal('label'); expect(clone.getValue()).to.equal('value0'); expect(clone.getProofHash()).to.equal('hash'); expect(clone.getTreeHeight()).to.equal(node.getTreeHeight()); @@ -87,12 +101,16 @@ describe("state-node", () => { }); it("internal node", () => { - stateTree.setProofHash('hash'); + stateTree._setLabel('label_root'); stateTree.setVersion('version1'); assert.deepEqual(stateTree.getParentNodes(), []); assert.deepEqual(child1.getParentNodes(), [stateTree]); assert.deepEqual(child2.getParentNodes(), [stateTree]); assert.deepEqual(child3.getParentNodes(), [stateTree]); + assert.deepEqual(child4.getParentNodes(), [stateTree]); + expect(verifyProofHashForStateTree(stateTree)).to.equal(false); + updateStateInfoForStateTree(stateTree); + expect(verifyProofHashForStateTree(stateTree)).to.equal(true); const clone = stateTree.clone(); expect(clone.getVersion()).to.equal(stateTree.getVersion()); @@ -100,11 +118,15 @@ describe("state-node", () => { assert.deepEqual(child1.getParentNodes(), [stateTree, clone]); assert.deepEqual(child2.getParentNodes(), [stateTree, clone]); assert.deepEqual(child3.getParentNodes(), [stateTree, clone]); + assert.deepEqual(child4.getParentNodes(), [stateTree, clone]); assert.deepEqual(clone.getParentNodes(), []); assert.deepEqual(clone.getChildLabels(), stateTree.getChildLabels()); assert.deepEqual(clone.getChildNodes(), stateTree.getChildNodes()); + assert.deepEqual(clone.numChildren(), stateTree.numChildren()); + // Proof hash is verified without updateStateInfoForStateTree() call! + expect(verifyProofHashForStateTree(clone)).to.equal(true); + expect(clone.getLabel()).to.equal('label_root'); expect(clone.getValue()).to.equal(null); - expect(clone.getProofHash()).to.equal('hash'); expect(clone.getTreeHeight()).to.equal(stateTree.getTreeHeight()); expect(clone.getTreeSize()).to.equal(stateTree.getTreeSize()); expect(clone.getTreeBytes()).to.equal(stateTree.getTreeBytes()); @@ -187,18 +209,18 @@ describe("state-node", () => { node2.setValue('value'); expect(node1.equal(node2)).to.equal(true); - node1._addParent(parent); + node1.addParent(parent); expect(node1.equal(node2)).to.equal(false); - node2._addParent(parent); + node2.addParent(parent); expect(node1.equal(node2)).to.equal(true); }); it("internal", () => { expect(node1.equal(node2)).to.equal(true); - node1._addParent(parent); + node1.addParent(parent); expect(node1.equal(node2)).to.equal(false); - node2._addParent(parent); + node2.addParent(parent); expect(node1.equal(node2)).to.equal(true); node1.setChild(label1, child1); @@ -595,6 +617,17 @@ describe("state-node", () => { }) + describe("label", () => { + it("get / has / set / reset", () => { + expect(node.hasLabel()).to.equal(false); + node._setLabel('label'); + expect(node.hasLabel()).to.equal(true); + expect(node.getLabel()).to.equal('label'); + node._resetLabel(); + expect(node.hasLabel()).to.equal(false); + }); + }); + describe("isLeaf", () => { it("get / set", () => { expect(node.getIsLeaf()).to.equal(true); @@ -632,13 +665,13 @@ describe("state-node", () => { assert.deepEqual(child.getParentNodes(), []); expect(child.numParents()).to.equal(0); - child._addParent(parent1); + child.addParent(parent1); expect(child._hasParent(parent1)).to.equal(true); expect(child._hasParent(parent2)).to.equal(false); assert.deepEqual(child.getParentNodes(), [parent1]); expect(child.numParents()).to.equal(1); - child._addParent(parent2); + child.addParent(parent2); expect(child._hasParent(parent1)).to.equal(true); expect(child._hasParent(parent2)).to.equal(true); assert.deepEqual(child.getParentNodes(), [parent1, parent2]); @@ -673,8 +706,8 @@ describe("state-node", () => { expect(child1.numParents()).to.equal(0); expect(child2.numParents()).to.equal(0); - child1._addParent(parent1); - child2._addParent(parent2); + child1.addParent(parent1); + child2.addParent(parent2); expect(child1._hasParent(parent1)).to.equal(true); expect(child1._hasParent(parent2)).to.equal(false); expect(child2._hasParent(parent1)).to.equal(false); @@ -684,8 +717,8 @@ describe("state-node", () => { expect(child1.numParents()).to.equal(1); expect(child2.numParents()).to.equal(1); - child1._addParent(parent2); - child2._addParent(parent1); + child1.addParent(parent2); + child2.addParent(parent1); expect(child1._hasParent(parent1)).to.equal(true); expect(child1._hasParent(parent2)).to.equal(true); expect(child2._hasParent(parent1)).to.equal(true); @@ -726,12 +759,12 @@ describe("state-node", () => { assert.deepEqual(child.getParentNodes(), []); expect(child.numParents()).to.equal(0); - child._addParent(parent); + child.addParent(parent); expect(child._hasParent(parent)).to.equal(true); assert.deepEqual(child.getParentNodes(), [parent]); expect(child.numParents()).to.equal(1); - child._addParent(parent); + child.addParent(parent); expect(child._hasParent(parent)).to.equal(true); assert.deepEqual(child.getParentNodes(), [parent]); expect(child.numParents()).to.equal(1); @@ -747,7 +780,7 @@ describe("state-node", () => { assert.deepEqual(child.getParentNodes(), []); expect(child.numParents()).to.equal(0); - child._addParent(parent1); + child.addParent(parent1); expect(child._hasParent(parent1)).to.equal(true); expect(child._hasParent(parent2)).to.equal(false); assert.deepEqual(child.getParentNodes(), [parent1]); @@ -778,6 +811,8 @@ describe("state-node", () => { assert.deepEqual(child2.getParentNodes(), []); expect(child1.numParents()).to.equal(0); expect(child2.numParents()).to.equal(0); + expect(child1.hasLabel()).to.equal(false); + expect(child2.hasLabel()).to.equal(false); expect(parent.getIsLeaf()).to.equal(true); parent.setChild(label1, child1); @@ -789,6 +824,9 @@ describe("state-node", () => { assert.deepEqual(child2.getParentNodes(), []); expect(child1.numParents()).to.equal(1); expect(child2.numParents()).to.equal(0); + expect(child1.hasLabel()).to.equal(true); + expect(child1.getLabel()).to.equal(label1); + expect(child2.hasLabel()).to.equal(false); expect(parent.getIsLeaf()).to.equal(false); parent.setChild(label2, child2); @@ -800,6 +838,10 @@ describe("state-node", () => { assert.deepEqual(child2.getParentNodes(), [parent]); expect(child1.numParents()).to.equal(1); expect(child2.numParents()).to.equal(1); + expect(child1.hasLabel()).to.equal(true); + expect(child1.getLabel()).to.equal(label1); + expect(child2.hasLabel()).to.equal(true); + expect(child2.getLabel()).to.equal(label2); expect(parent.getIsLeaf()).to.equal(false); parent.deleteChild(label1); @@ -811,6 +853,9 @@ describe("state-node", () => { assert.deepEqual(child2.getParentNodes(), [parent]); expect(child1.numParents()).to.equal(0); expect(child2.numParents()).to.equal(1); + expect(child1.hasLabel()).to.equal(true); + expect(child2.hasLabel()).to.equal(true); + expect(child2.getLabel()).to.equal(label2); expect(parent.getIsLeaf()).to.equal(false); parent.deleteChild(label2); @@ -822,6 +867,8 @@ describe("state-node", () => { assert.deepEqual(child2.getParentNodes(), []); expect(child1.numParents()).to.equal(0); expect(child2.numParents()).to.equal(0); + expect(child1.hasLabel()).to.equal(true); + expect(child2.hasLabel()).to.equal(true); expect(parent.getIsLeaf()).to.equal(true); }); @@ -846,6 +893,8 @@ describe("state-node", () => { assert.deepEqual(child2.getParentNodes(), []); expect(child1.numParents()).to.equal(0); expect(child2.numParents()).to.equal(0); + expect(child1.hasLabel()).to.equal(false); + expect(child2.hasLabel()).to.equal(false); expect(parent1.getIsLeaf()).to.equal(true); expect(parent2.getIsLeaf()).to.equal(true); @@ -863,6 +912,10 @@ describe("state-node", () => { assert.deepEqual(child2.getParentNodes(), [parent2]); expect(child1.numParents()).to.equal(1); expect(child2.numParents()).to.equal(1); + expect(child1.hasLabel()).to.equal(true); + expect(child1.getLabel()).to.equal(label1); + expect(child2.hasLabel()).to.equal(true); + expect(child2.getLabel()).to.equal(label2); expect(parent1.getIsLeaf()).to.equal(false); expect(parent2.getIsLeaf()).to.equal(false); @@ -880,11 +933,15 @@ describe("state-node", () => { assert.deepEqual(child2.getParentNodes(), [parent2, parent1]); expect(child1.numParents()).to.equal(2); expect(child2.numParents()).to.equal(2); + expect(child1.hasLabel()).to.equal(true); + expect(child1.getLabel()).to.equal(label1); + expect(child2.hasLabel()).to.equal(true); + expect(child2.getLabel()).to.equal(label2); expect(parent1.getIsLeaf()).to.equal(false); expect(parent2.getIsLeaf()).to.equal(false); - parent1.deleteChild(label1, child1); - parent2.deleteChild(label2, child2); + parent1.deleteChild(label1); + parent2.deleteChild(label2); expect(parent1.hasChild(label1)).to.equal(false); expect(parent1.hasChild(label2)).to.equal(true); expect(parent2.hasChild(label1)).to.equal(true); @@ -897,11 +954,15 @@ describe("state-node", () => { assert.deepEqual(child2.getParentNodes(), [parent1]); expect(child1.numParents()).to.equal(1); expect(child2.numParents()).to.equal(1); + expect(child1.hasLabel()).to.equal(true); + expect(child1.getLabel()).to.equal(label1); + expect(child2.hasLabel()).to.equal(true); + expect(child2.getLabel()).to.equal(label2); expect(parent1.getIsLeaf()).to.equal(false); expect(parent2.getIsLeaf()).to.equal(false); - parent1.deleteChild(label2, child2); - parent2.deleteChild(label1, child1); + parent1.deleteChild(label2); + parent2.deleteChild(label1); expect(parent1.hasChild(label1)).to.equal(false); expect(parent1.hasChild(label2)).to.equal(false); expect(parent2.hasChild(label1)).to.equal(false); @@ -914,6 +975,8 @@ describe("state-node", () => { assert.deepEqual(child2.getParentNodes(), []); expect(child1.numParents()).to.equal(0); expect(child2.numParents()).to.equal(0); + expect(child1.hasLabel()).to.equal(true); + expect(child2.hasLabel()).to.equal(true); expect(parent1.getIsLeaf()).to.equal(true); expect(parent2.getIsLeaf()).to.equal(true); }); @@ -1120,12 +1183,23 @@ describe("state-node", () => { child1.setProofHash('proofHash1'); child2.setProofHash('proofHash2'); child3.setProofHash('proofHash3'); - const preimage = - `${label1}${HASH_DELIMITER}${child1.getProofHash()}${HASH_DELIMITER}` + - `${label2}${HASH_DELIMITER}${child2.getProofHash()}${HASH_DELIMITER}` + - `${label3}${HASH_DELIMITER}${child3.getProofHash()}`; - expect(stateTree.buildProofHash()).to.equal( - CommonUtil.hashString(CommonUtil.toString(preimage))); + child4.setProofHash('proofHash4'); + expect(stateTree.radixTree.verifyProofHashForRadixTree()).to.equal(false); + + // build proof hash without updatedChildLabel + const proofHash = stateTree.buildProofHash(); + expect(proofHash).to.equal(stateTree.radixTree.getRootProofHash()); + expect(stateTree.radixTree.verifyProofHashForRadixTree()).to.equal(true); + + // set another proof hash value for a child + child2.setProofHash('another PH'); + expect(stateTree.radixTree.verifyProofHashForRadixTree()).to.equal(false); + + // build proof hash with updatedChildLabel + const newProofHash = stateTree.buildProofHash(label2); + expect(newProofHash).not.equal(proofHash); // Updated + expect(newProofHash).to.equal(stateTree.radixTree.getRootProofHash()); + expect(stateTree.radixTree.verifyProofHashForRadixTree()).to.equal(true); }); }); @@ -1151,7 +1225,8 @@ describe("state-node", () => { child1.setTreeHeight(0); child2.setTreeHeight(1); child3.setTreeHeight(2); - expect(stateTree.computeTreeHeight()).to.equal(3); + child4.setTreeHeight(3); + expect(stateTree.computeTreeHeight()).to.equal(4); }); }); @@ -1174,10 +1249,11 @@ describe("state-node", () => { }); it("internal node", () => { - child1.setTreeSize(2); - child2.setTreeSize(3); - child3.setTreeSize(5); - expect(stateTree.computeTreeSize()).to.equal(11); + child1.setTreeSize(10); + child2.setTreeSize(20); + child3.setTreeSize(30); + child4.setTreeSize(40); + expect(stateTree.computeTreeSize()).to.equal(101); }); }); @@ -1186,14 +1262,14 @@ describe("state-node", () => { const parent = new StateNode(); parent.setChild('child', node); - // node.parentSet : ref set (1 * 8 = 8 bytes) node.setVersion('ver'); // string (3 * 2 = 6 bytes) // node.isLeaf : boolean (4 bytses) node.setProofHash('hash'); // string (4 * 2 = 8 bytses) // node.treeHeight : number (8 bytses) // node.treeSize : number (8 bytses) // node.treeBytes : number (8 bytes) - // TOTAL: 50 - 6 = 44 bytes (exclude version) + // TOTAL: 42 - 6 = 36 bytes (exclude version) + expect(node.computeNodeBytes()).to.equal(36); node.setValue(true); // boolean (4 bytes) expect(node.computeTreeBytes()).to.equal(40); @@ -1215,59 +1291,80 @@ describe("state-node", () => { const parent = new StateNode(); parent.setChild('child', stateTree); - // stateTree.parentSet : ref set (1 * 8 = 8 bytes) - // stateTree.childMap : ref map (3 * 8 = 24 bytes) stateTree.setVersion('ver'); // string (3 * 2 = 6 bytes) // stateTree.isLeaf : boolean (4 bytses) + // stateTree.value : null (0 bytses) stateTree.setProofHash('hash'); // string (4 * 2 = 8 bytses) // stateTree.treeHeight : number (8 bytses) // stateTree.treeSize : number (8 bytses) // stateTree.treeBytes : number (8 bytes) - // TOTAL: 74 - 6 = 68 bytes (exclude version) + // TOTAL: 42 - 6 = 36 bytes (exclude version) + expect(stateTree.computeNodeBytes()).to.equal(36); child1.setTreeBytes(10); child2.setTreeBytes(20); child3.setTreeBytes(30); - // 68 + 6('label1') * 2 + 10 + 6('label2') * 2 + 20 + 6('label3') * 2 + 30 = 164 - expect(stateTree.computeTreeBytes()).to.equal(132); + child4.setTreeBytes(40); + // 36 + 8(label1) * 2 + 10 + 8(label2) * 2 + 20 + 8(label3) * 2 + 30 + 8(label4) * 2 + 40 = 200 + expect(stateTree.computeTreeBytes()).to.equal(200); }); }); - describe("updateProofHashAndStateInfo", () => { + describe("updateStateInfo / verifyProofHash", () => { it("leaf node", () => { node.setValue(true); - node.updateProofHashAndStateInfo(); + expect(node.verifyProofHash()).to.equal(false); + node.updateStateInfo(); expect(node.getProofHash()).to.equal(node.buildProofHash()); + expect(node.verifyProofHash()).to.equal(true); expect(node.getTreeHeight()).to.equal(node.computeTreeHeight()); expect(node.getTreeSize()).to.equal(node.computeTreeSize()); + node.setValue(10); - node.updateProofHashAndStateInfo(); + expect(node.verifyProofHash()).to.equal(false); + node.updateStateInfo(); expect(node.getProofHash()).to.equal(node.buildProofHash()); + expect(node.verifyProofHash()).to.equal(true); expect(node.getTreeHeight()).to.equal(node.computeTreeHeight()); expect(node.getTreeSize()).to.equal(node.computeTreeSize()); + node.setValue(-200); - node.updateProofHashAndStateInfo(); + expect(node.verifyProofHash()).to.equal(false); + node.updateStateInfo(); expect(node.getProofHash()).to.equal(node.buildProofHash()); + expect(node.verifyProofHash()).to.equal(true); expect(node.getTreeHeight()).to.equal(node.computeTreeHeight()); expect(node.getTreeSize()).to.equal(node.computeTreeSize()); + node.setValue(''); - node.updateProofHashAndStateInfo(); + expect(node.verifyProofHash()).to.equal(false); + node.updateStateInfo(); expect(node.getProofHash()).to.equal(node.buildProofHash()); + expect(node.verifyProofHash()).to.equal(true); expect(node.getTreeHeight()).to.equal(node.computeTreeHeight()); expect(node.getTreeSize()).to.equal(node.computeTreeSize()); - node.setValue('unittest'); - node.updateProofHashAndStateInfo(); + + node.setValue('str'); + expect(node.verifyProofHash()).to.equal(false); + node.updateStateInfo(); expect(node.getProofHash()).to.equal(node.buildProofHash()); + expect(node.verifyProofHash()).to.equal(true); expect(node.getTreeHeight()).to.equal(node.computeTreeHeight()); expect(node.getTreeSize()).to.equal(node.computeTreeSize()); + node.setValue(null); - node.updateProofHashAndStateInfo(); + expect(node.verifyProofHash()).to.equal(false); + node.updateStateInfo(); expect(node.getProofHash()).to.equal(node.buildProofHash()); + expect(node.verifyProofHash()).to.equal(true); expect(node.getTreeHeight()).to.equal(node.computeTreeHeight()); expect(node.getTreeSize()).to.equal(node.computeTreeSize()); + node.setValue(undefined); - node.updateProofHashAndStateInfo(); + expect(node.verifyProofHash()).to.equal(false); + node.updateStateInfo(); expect(node.getProofHash()).to.equal(node.buildProofHash()); + expect(node.verifyProofHash()).to.equal(true); expect(node.getTreeHeight()).to.equal(node.computeTreeHeight()); expect(node.getTreeSize()).to.equal(node.computeTreeSize()); }); @@ -1276,16 +1373,118 @@ describe("state-node", () => { child1.setProofHash('proofHash1'); child2.setProofHash('proofHash2'); child3.setProofHash('proofHash3'); + child4.setProofHash('proofHash4'); child1.setTreeHeight(1); child2.setTreeHeight(2); child3.setTreeHeight(3); - child1.setTreeSize(2); - child2.setTreeSize(3); - child3.setTreeSize(5); - node.updateProofHashAndStateInfo(); - expect(node.getProofHash()).to.equal(node.buildProofHash()); - expect(node.getTreeHeight()).to.equal(node.computeTreeHeight()); - expect(node.getTreeSize()).to.equal(node.computeTreeSize()); + child4.setTreeHeight(4); + child1.setTreeSize(10); + child2.setTreeSize(20); + child3.setTreeSize(30); + child4.setTreeSize(40); + expect(stateTree.verifyProofHash()).to.equal(false); + + // update without updatedChildLabel + stateTree.updateStateInfo(); + const proofHash = stateTree.getProofHash(); + expect(proofHash).to.equal(stateTree.buildProofHash()); + expect(stateTree.verifyProofHash()).to.equal(true); + expect(stateTree.getTreeHeight()).to.equal(stateTree.computeTreeHeight()); + expect(stateTree.getTreeSize()).to.equal(stateTree.computeTreeSize()); + expect(stateTree.getTreeBytes()).to.equal(stateTree.computeTreeBytes()); + + // set another proof hash value for a child + child2.setProofHash('another PH'); + expect(stateTree.verifyProofHash()).to.equal(false); + + // update with updatedChildLabel + stateTree.updateStateInfo(label2); + const newProofHash = stateTree.getProofHash(); + expect(newProofHash).not.equal(proofHash); // Updated + expect(newProofHash).to.equal(stateTree.buildProofHash()); + expect(stateTree.verifyProofHash()).to.equal(true); + }); + + it("verifyProofHash with updatedChildLabel", () => { + child1.setProofHash('proofHash1'); + child2.setProofHash('proofHash2'); + child3.setProofHash('proofHash3'); + child4.setProofHash('proofHash4'); + expect(stateTree.verifyProofHash(label2)).to.equal(false); + + // update with updatedChildLabel + stateTree.updateStateInfo(label2); + // verify with updatedChildLabel + expect(stateTree.verifyProofHash(label2)).to.equal(true); + // verify without updatedChildLabel + expect(stateTree.verifyProofHash()).to.equal(false); + }); + }); + + describe("getProofOfState", () => { + it("leaf node", () => { + node.setValue(true); // leaf node + node.setProofHash('proofHash'); + assert.deepEqual(node.getProofOfState(), { + ".proof_hash": "proofHash" + }); + }); + + it("internal node", () => { + child1.setProofHash('proofHash1'); + child2.setProofHash('proofHash2'); + child3.setProofHash('proofHash3'); + child4.setProofHash('proofHash4'); + stateTree.setProofHash('proofHash'); + + stateTree.updateStateInfo(); + assert.deepEqual(stateTree.radixTree.toJsObject(true), { + ".radix_ph": "0xd9251f484361885000e88f2385777e1c4558a08125199a99c6b3296b459628c6", + "00aaaa": { + ".label": "0x00aaaa", + ".proof_hash": "proofHash1", + ".radix_ph": "0xd8895ab36f227519e479a4bf7cfcbf963deb8e69e8172f395af8db83172bf22c" + }, + "11bb": { + "11": { + ".label": "0x11bb11", + ".proof_hash": "proofHash4", + ".radix_ph": "0x741ba4788b06907f8c99c60a6f483f885cc1b4fb27f9e1bed71dfd1d8a213214" + }, + ".radix_ph": "0x099ad81295e3257147362606afc34b47757dd5c1508d441e248302be8577ed44", + "00": { + ".label": "0x11bb00", + ".proof_hash": "proofHash3", + ".radix_ph": "0x3dfb52c0d974feb0559c9efafa996fb286717785e98871336e68ffb52d04bdf4" + }, + "bb": { + ".label": "0x11bbbb", + ".proof_hash": "proofHash2", + ".radix_ph": "0xbbc5610ad726c88350abbe6513ab8f7441cbe8ff09ece86642a827feb53ce184" + } + } + }); + + assert.deepEqual(stateTree.getProofOfState(label2, 'childProof2'), { + ".radix_ph": "0xd9251f484361885000e88f2385777e1c4558a08125199a99c6b3296b459628c6", + "00aaaa": { + ".radix_ph": "0xd8895ab36f227519e479a4bf7cfcbf963deb8e69e8172f395af8db83172bf22c" + }, + "11bb": { + "11": { + ".radix_ph": "0x741ba4788b06907f8c99c60a6f483f885cc1b4fb27f9e1bed71dfd1d8a213214" + }, + ".radix_ph": "0x099ad81295e3257147362606afc34b47757dd5c1508d441e248302be8577ed44", + "00": { + ".radix_ph": "0x3dfb52c0d974feb0559c9efafa996fb286717785e98871336e68ffb52d04bdf4" + }, + "bb": { + ".label": "0x11bbbb", + ".proof_hash": "childProof2", + ".radix_ph": "0xbbc5610ad726c88350abbe6513ab8f7441cbe8ff09ece86642a827feb53ce184" + } + } + }); }); }); }); diff --git a/unittest/state-util.test.js b/unittest/state-util.test.js index 2b0dff28d..4a5d95783 100644 --- a/unittest/state-util.test.js +++ b/unittest/state-util.test.js @@ -21,9 +21,10 @@ const { deleteStateTreeVersion, makeCopyOfStateTree, equalStateTrees, - setProofHashForStateTree, - updateProofHashForAllRootPaths, - verifyProofHashForStateTree + updateStateInfoForAllRootPaths, + updateStateInfoForStateTree, + verifyProofHashForStateTree, + getProofOfStatePath, } = require('../db/state-util'); const { STATE_LABEL_LENGTH_LIMIT } = require('../common/constants'); const { GET_OPTIONS_INCLUDE_ALL } = require('./test-util'); @@ -108,7 +109,7 @@ describe("state-util", () => { })), {isValid: false, invalidPath: '/some'}); }) - it("when writable path w/o shard config", () => { + it("when writable path without shard config", () => { assert.deepEqual(isWritablePathWithSharding( ['some', 'path'], StateNode.fromJsObject({ @@ -2085,7 +2086,7 @@ describe("state-util", () => { stateTree = new StateNode(ver3); stateTree.setChild('label1', child1); stateTree.setChild('label2', child2); - setProofHashForStateTree(stateTree); + updateStateInfoForStateTree(stateTree); }) it("leaf node", () => { @@ -2093,7 +2094,7 @@ describe("state-util", () => { // Delete a leaf node without version. const stateNode1 = StateNode.fromJsObject(true); - setProofHashForStateTree(stateNode1); + updateStateInfoForStateTree(stateNode1); const numNodes1 = deleteStateTree(stateNode1); expect(numNodes1).to.equal(1); expect(stateNode1.numChildren()).to.equal(0); @@ -2103,7 +2104,7 @@ describe("state-util", () => { // Delete a leaf node with version. const stateNode2 = StateNode.fromJsObject(true, ver1); - setProofHashForStateTree(stateNode2); + updateStateInfoForStateTree(stateNode2); const numNodes2 = deleteStateTree(stateNode2); expect(numNodes2).to.equal(1); expect(stateNode2.numChildren()).to.equal(0); @@ -2147,13 +2148,13 @@ describe("state-util", () => { node = new StateNode(ver3); node.setChild('label1', child1); node.setChild('label2', child2); - setProofHashForStateTree(node); + updateStateInfoForStateTree(node); }) it("leaf node", () => { // Delete a leaf node without version. const stateNode1 = StateNode.fromJsObject(true); - setProofHashForStateTree(stateNode1); + updateStateInfoForStateTree(stateNode1); const numNodes1 = deleteStateTreeVersion(stateNode1); expect(numNodes1).to.equal(1); expect(stateNode1.getValue()).to.equal(null); @@ -2162,7 +2163,7 @@ describe("state-util", () => { // Delete a leaf node with a different version. const stateNode2 = StateNode.fromJsObject(true, 'ver2'); - setProofHashForStateTree(stateNode2); + updateStateInfoForStateTree(stateNode2); const numNodes2 = deleteStateTreeVersion(stateNode2); expect(numNodes2).to.equal(1); expect(stateNode2.getValue()).to.equal(null); @@ -2171,7 +2172,7 @@ describe("state-util", () => { // Delete a leaf node with the same version. const stateNode3 = StateNode.fromJsObject(true, ver1); - setProofHashForStateTree(stateNode3); + updateStateInfoForStateTree(stateNode3); const numNodes3 = deleteStateTreeVersion(stateNode3); expect(numNodes3).to.equal(1); expect(stateNode3.getValue()).to.equal(null); @@ -2181,7 +2182,7 @@ describe("state-util", () => { // Delete a leaf node with the same version but with non-zero numParents() value. const stateNode4 = StateNode.fromJsObject(true, ver1); parent.setChild(nodeLabel, stateNode4); - setProofHashForStateTree(stateNode4); + updateStateInfoForStateTree(stateNode4); const numNodes4 = deleteStateTreeVersion(stateNode4); expect(numNodes4).to.equal(0); expect(stateNode4.getValue()).to.equal(true); @@ -2238,7 +2239,7 @@ describe("state-util", () => { ".num_parents": 1, ".num_parents:label1": 1, ".num_parents:label2": 1, - ".proof_hash": "0xa540d9d1906f4579604302acdee0b4c9742f537eb5f8397fb9a43ed458dad439", + ".proof_hash": "0x4ef3be0ba4fd9c5bc7994d3ed87ec958e11f97f1c974fba94037711e058328d6", ".proof_hash:label1": "0xb41f4a6e100333ddd8e8dcc01ca1fed23662d9faaec359ed255d21a900cecd08", ".proof_hash:label2": "0x7597bdc763c23c44e90f26c63d7eac963cc0d0aa8a0a3268e7f5691c5361d942", ".tree_height": 1, @@ -2480,261 +2481,365 @@ describe("state-util", () => { }) }) - describe("setProofHashForStateTree", () => { - it("generates a proof hash along with the given stateTree", () => { - const jsObject = { - level0: { - level1: { - foo: 'bar', - baz: 'caz' + describe("empty nodes removal", () => { + const label1 = '0x0001'; + const label11 = '0x0011'; + const label111 = '0x0111'; + const label1111 = '0x1111'; + const label12 = '0x0012'; + const label121 = '0x0121'; + let stateTree; + let child1; + let child11; + let child111; + let child1111; + const jsObject = { + [label1]: { + [label11]: { + [label111]: { + [label1111]: null, } }, - another_route: { - test: 10 + [label12]: { + [label121]: 'V0121' } - }; - const stateTree = StateNode.fromJsObject(jsObject); - const level0Node = stateTree.getChild('level0'); - const level1Node = level0Node.getChild('level1'); - const fooNode = level1Node.getChild('foo'); - const bazNode = level1Node.getChild('baz'); - const anotherNode = stateTree.getChild('another_route'); - const testNode = anotherNode.getChild('test'); - - const numAffectedNodes = setProofHashForStateTree(level0Node); - expect(numAffectedNodes).to.equal(4); - // Checks proof hashes. - expect(level0Node.getProofHash()).to.equal(level0Node.buildProofHash()); - expect(level1Node.getProofHash()).to.equal(level1Node.buildProofHash()); - expect(fooNode.getProofHash()).to.equal(fooNode.buildProofHash()); - expect(bazNode.getProofHash()).to.equal(bazNode.buildProofHash()); - expect(stateTree.getChild('another_route').getChild('test').getProofHash()).to.equal(null); - expect(stateTree.getChild('another_route').getProofHash()).to.equal(null); - expect(stateTree.getProofHash()).to.equal(null); - // Checks tree heights. - expect(fooNode.getTreeHeight()).to.equal(0); - expect(bazNode.getTreeHeight()).to.equal(0); - expect(level1Node.getTreeHeight()).to.equal(1); - expect(level0Node.getTreeHeight()).to.equal(2); - expect(anotherNode.getTreeHeight()).to.equal(0); - expect(testNode.getTreeHeight()).to.equal(0); - // Checks tree sizes. - expect(fooNode.getTreeSize()).to.equal(1); - expect(bazNode.getTreeSize()).to.equal(1); - expect(level1Node.getTreeSize()).to.equal(3); - expect(level0Node.getTreeSize()).to.equal(4); - expect(anotherNode.getTreeSize()).to.equal(0); - expect(testNode.getTreeSize()).to.equal(0); - // Checks tree bytes. - expect(fooNode.getTreeBytes()).to.equal(166); - expect(bazNode.getTreeBytes()).to.equal(166); - expect(level1Node.getTreeBytes()).to.equal(504); - expect(level0Node.getTreeBytes()).to.equal(676); - expect(anotherNode.getTreeBytes()).to.equal(0); - expect(testNode.getTreeBytes()).to.equal(0); + } + }; + + beforeEach(() => { + stateTree = StateNode.fromJsObject(jsObject); + child1 = stateTree.getChild(label1); + child11 = child1.getChild(label11); + child111 = child11.getChild(label111); + child1111 = child111.getChild(label1111); }); - }); - describe("updateProofHashForAllRootPaths", () => { - it("updates proof hashes for a single root path", () => { - const jsObject = { - level0: { - level1: { - level2: { - foo: 'bar', - baz: 'caz' + it("updateStateInfoForAllRootPaths on empty node with a single root path", () => { + assert.deepEqual(stateTree.toJsObject(), { + "0x0001": { + "0x0011": { + "0x0111": { + "0x1111": null } }, - another_route: { - test: -1000 + "0x0012": { + "0x0121": "V0121" } } - }; - const rootNode = StateNode.fromJsObject(jsObject); - const level0Node = rootNode.getChild('level0'); - const level1Node = level0Node.getChild('level1'); - const level2Node = level1Node.getChild('level2'); - const anotherNode = level0Node.getChild('another_route'); - - level2Node.setTreeHeight(1); - level2Node.setTreeSize(3); - level2Node.setTreeBytes(150); // An arbitrary value - anotherNode.setTreeHeight(1); - anotherNode.setTreeSize(2); - anotherNode.setTreeBytes(100); // An arbitrary value - - const numAffectedNodes = updateProofHashForAllRootPaths(['level0', 'level1'], rootNode); - expect(numAffectedNodes).to.equal(3); - // Checks proof hashes. - expect(level2Node.getChild('foo').getProofHash()).to.equal(null); - expect(level2Node.getChild('baz').getProofHash()).to.equal(null); - expect(level2Node.getProofHash()).to.equal(null); - expect(anotherNode.getChild('test').getProofHash()).to.equal(null); - expect(anotherNode.getProofHash()).to.equal(null); - expect(level1Node.getProofHash()).to.equal(level1Node.buildProofHash()); - expect(level0Node.getProofHash()).to.equal(level0Node.buildProofHash()); - expect(rootNode.getProofHash()).to.equal(rootNode.buildProofHash()); - // Checks tree heights. - expect(level1Node.getTreeHeight()).to.equal(2); - expect(level0Node.getTreeHeight()).to.equal(3); - expect(rootNode.getTreeHeight()).to.equal(4); - // Checks tree sizes. - expect(level1Node.getTreeSize()).to.equal(4); - expect(level0Node.getTreeSize()).to.equal(7); - expect(rootNode.getTreeSize()).to.equal(8); - // Checks tree bytes. - expect(level1Node.getTreeBytes()).to.equal(322); - expect(level0Node.getTreeBytes()).to.equal(620); - expect(rootNode.getTreeBytes()).to.equal(792); + }); + const numAffectedNodes = + updateStateInfoForAllRootPaths([label1, label11, label111, label1111], stateTree); + expect(numAffectedNodes).to.equal(5); + assert.deepEqual(stateTree.toJsObject(), { + "0x0001": { + "0x0012": { + "0x0121": "V0121" + } + } + }); }); - it("updates proof hashes for multiple root paths", () => { - const jsObject = { - level0: { - level1: { - level2: { - foo: 'bar', - baz: 'caz' + it("updateStateInfoForAllRootPaths on empty node with multiple root paths", () => { + const child111Clone = child111.clone(); + const child11Clone = new StateNode(); + child11Clone.setChild(label111, child111Clone); + const child1Clone = new StateNode(); + child1Clone.setChild(label11, child11Clone); + const stateTreeClone = new StateNode(); + stateTreeClone.setChild(label1, child1Clone); + const child3 = new StateNode(); + child3.setValue('V0003'); + const label3 = '0x003'; + stateTreeClone.setChild(label3, child3); + + assert.deepEqual(stateTree.toJsObject(), { + "0x0001": { + "0x0011": { + "0x0111": { + "0x1111": null } }, - another_route: { - test: -1000 + "0x0012": { + "0x0121": "V0121" } } - }; - const rootNode = StateNode.fromJsObject(jsObject); - const level0Node = rootNode.getChild('level0'); - const level1Node = level0Node.getChild('level1'); - const level2Node = level1Node.getChild('level2'); - const anotherNode = level0Node.getChild('another_route'); - const rootClone = rootNode.clone(); - const level0Clone = level0Node.clone(); - const level1Clone = level1Node.clone(); - const level2Clone = level2Node.clone(); - const anotherClone = anotherNode.clone(); - - const numAffectedNodes = updateProofHashForAllRootPaths(['level0', 'level1'], rootNode); - expect(numAffectedNodes).to.equal(5); - - // Checks proof hashes. - expect(level2Node.getChild('foo').getProofHash()).to.equal(null); - expect(level2Node.getChild('baz').getProofHash()).to.equal(null); - expect(level2Node.getProofHash()).to.equal(null); - expect(level2Clone.getProofHash()).to.equal(null); - - expect(anotherNode.getChild('test').getProofHash()).to.equal(null); - expect(anotherNode.getProofHash()).to.equal(null); - expect(anotherClone.getProofHash()).to.equal(null); - - expect(level1Node.getProofHash()).to.equal(level1Node.buildProofHash()); - expect(level1Clone.getProofHash()).to.equal(null); - - expect(level0Node.getProofHash()).to.equal(level0Node.buildProofHash()); - expect(level0Clone.getProofHash()).to.equal(level0Clone.buildProofHash()); - expect(level0Clone.getProofHash()).to.equal(level0Node.getProofHash()); - - expect(rootNode.getProofHash()).to.equal(rootNode.buildProofHash()); - expect(rootClone.getProofHash()).to.equal(rootClone.buildProofHash()); - expect(rootClone.getProofHash()).to.equal(rootNode.getProofHash()); + }); + assert.deepEqual(stateTreeClone.toJsObject(), { + "0x0001": { + "0x0011": { + "0x0111": { + "0x1111": null + } + } + }, + "0x003": "V0003" + }); + assert.deepEqual(child1111.getParentNodes(), [child111, child111Clone]); + const numAffectedNodes = + updateStateInfoForAllRootPaths([label1, label11, label111, label1111], stateTree); + expect(numAffectedNodes).to.equal(10); + assert.deepEqual(stateTree.toJsObject(), { + "0x0001": { + "0x0012": { + "0x0121": "V0121" + } + } + }); + assert.deepEqual(stateTreeClone.toJsObject(), { + "0x003": "V0003" + }); }); - it("updates proof hashes for multiple root paths with deleted nodes", () => { - const jsObject = { - level0: { - level1: { - level2: { - foo: 'bar', - baz: 'caz' + it("updateStateInfoAllRootPaths on non-empty node", () => { + assert.deepEqual(stateTree.toJsObject(), { + "0x0001": { + "0x0011": { + "0x0111": { + "0x1111": null } }, - another_route: { - test: -1000 + "0x0012": { + "0x0121": "V0121" } } - }; - const rootNode = StateNode.fromJsObject(jsObject); - const level0Node = rootNode.getChild('level0'); - const level1Node = level0Node.getChild('level1'); - const level2Node = level1Node.getChild('level2'); - const anotherNode = level0Node.getChild('another_route'); - const rootClone = rootNode.clone(); - const level0Clone = level0Node.clone(); - const level1Clone = level1Node.clone(); - const level2Clone = level2Node.clone(); - const anotherClone = anotherNode.clone(); - - const numAffectedNodes = updateProofHashForAllRootPaths( - ['level0', 'level1', 'deleted1', 'deleted2'], rootNode); // with deleted nodes + }); + const numAffectedNodes = + updateStateInfoForAllRootPaths([label1, label11, label111], stateTree); + expect(numAffectedNodes).to.equal(3); + assert.deepEqual(stateTree.toJsObject(), { + "0x0001": { + "0x0011": { + "0x0111": { + "0x1111": null + } + }, + "0x0012": { + "0x0121": "V0121" + } + } + }); + }); + + it("updateStateInfoForAllRootPaths with deleted nodes", () => { + // with deleted nodes + const numAffectedNodes = updateStateInfoForAllRootPaths( + [label1, label11, label111, label1111, 'deleted1', 'deleted2'], stateTree); + expect(numAffectedNodes).to.equal(5); + assert.deepEqual(stateTree.toJsObject(), { + "0x0001": { + "0x0012": { + "0x0121": "V0121" + } + } + }); + }) + }); + + describe("state info updates", () => { + const label1 = '0x0001'; + const label11 = '0x0011'; + const label111 = '0x0111'; + const label1111 = '0x1111'; + const label1112 = '0x1112'; + const label2 = '0x0002'; + const label21 = '0x0021'; + let stateTree; + let child1; + let child11; + let child111; + let child1111; + let child1112; + let child2; + let child21; + const jsObject = { + [label1]: { + [label11]: { + [label111]: { + [label1111]: 'V1111', + [label1112]: 'V1112' + } + } + }, + [label2]: { + [label21]: 'V0021' + } + }; + + beforeEach(() => { + stateTree = StateNode.fromJsObject(jsObject); + child1 = stateTree.getChild(label1); + child11 = child1.getChild(label11); + child111 = child11.getChild(label111); + child1111 = child111.getChild(label1111); + child1112 = child111.getChild(label1112); + child2 = stateTree.getChild(label2); + child21 = child2.getChild(label21); + }); + + it("updateStateInfoForStateTree", () => { + const numAffectedNodes = updateStateInfoForStateTree(child1); expect(numAffectedNodes).to.equal(5); + // Checks proof hashes. + expect(child1111.verifyProofHash()).to.equal(true); + expect(child1112.verifyProofHash()).to.equal(true); + expect(child111.verifyProofHash()).to.equal(true); + expect(child11.verifyProofHash()).to.equal(true); + expect(child1.verifyProofHash()).to.equal(true); + expect(child21.verifyProofHash()).to.equal(false); + expect(child2.verifyProofHash()).to.equal(false); + expect(stateTree.verifyProofHash()).to.equal(false); + // Checks tree heights. + expect(child1111.getTreeHeight()).to.equal(0); + expect(child1112.getTreeHeight()).to.equal(0); + expect(child111.getTreeHeight()).to.equal(1); + expect(child11.getTreeHeight()).to.equal(2); + expect(child1.getTreeHeight()).to.equal(3); + expect(child21.getTreeHeight()).to.equal(0); + expect(child2.getTreeHeight()).to.equal(0); + expect(stateTree.getTreeHeight()).to.equal(0); + // Checks tree sizes. + expect(child1111.getTreeSize()).to.equal(1); + expect(child1112.getTreeSize()).to.equal(1); + expect(child111.getTreeSize()).to.equal(3); + expect(child11.getTreeSize()).to.equal(4); + expect(child1.getTreeSize()).to.equal(5); + expect(child21.getTreeSize()).to.equal(0); + expect(child2.getTreeSize()).to.equal(0); + expect(stateTree.getTreeSize()).to.equal(0); + // Checks tree bytes. + expect(child1111.getTreeBytes()).to.not.equal(0); // non-zero value + expect(child1112.getTreeBytes()).to.not.equal(0); // non-zero value + expect(child111.getTreeBytes()).to.not.equal(0); // non-zero value + expect(child11.getTreeBytes()).to.not.equal(0); // non-zero value + expect(child1.getTreeBytes()).to.not.equal(0); // non-zero value + expect(child21.getTreeBytes()).to.equal(0); + expect(child2.getTreeBytes()).to.equal(0); + expect(stateTree.getTreeBytes()).to.equal(0); + }); + it("updateStateInfoForAllRootPaths with a single root path", () => { + const numAffectedNodes = + updateStateInfoForAllRootPaths([label1, label11, label111, label1112], stateTree); + expect(numAffectedNodes).to.equal(4); // Checks proof hashes. - expect(level2Node.getChild('foo').getProofHash()).to.equal(null); - expect(level2Node.getChild('baz').getProofHash()).to.equal(null); - expect(level2Node.getProofHash()).to.equal(null); - expect(level2Clone.getProofHash()).to.equal(null); + expect(child1111.verifyProofHash()).to.equal(false); + expect(child1112.verifyProofHash()).to.equal(false); + expect(child111.verifyProofHash(label1112)).to.equal(true); // verified + expect(child11.verifyProofHash()).to.equal(true); // verified + expect(child21.verifyProofHash()).to.equal(false); + expect(child2.verifyProofHash()).to.equal(false); + expect(child1.verifyProofHash()).to.equal(true); // verified + expect(stateTree.verifyProofHash(label1)).to.equal(true); // verified + + // Checks tree heights. + expect(child1111.getTreeHeight()).to.equal(0); + expect(child1112.getTreeHeight()).to.equal(0); + expect(child111.getTreeHeight()).to.equal(child111.computeTreeHeight()); + expect(child11.getTreeHeight()).to.equal(child11.computeTreeHeight()); + expect(child1.getTreeHeight()).to.equal(child1.computeTreeHeight()); + expect(child21.getTreeHeight()).to.equal(0); + expect(child2.getTreeHeight()).to.equal(0); + expect(stateTree.getTreeHeight()).to.equal(stateTree.computeTreeHeight()); + + // Checks tree sizes. + expect(child1111.getTreeSize()).to.equal(0); + expect(child1112.getTreeSize()).to.equal(0); + expect(child111.getTreeSize()).to.equal(child111.computeTreeSize()); + expect(child11.getTreeSize()).to.equal(child11.computeTreeSize()); + expect(child1.getTreeSize()).to.equal(child1.computeTreeSize()); + expect(child21.getTreeSize()).to.equal(0); + expect(child2.getTreeSize()).to.equal(0); + expect(stateTree.getTreeSize()).to.equal(stateTree.computeTreeSize()); - expect(anotherNode.getChild('test').getProofHash()).to.equal(null); - expect(anotherNode.getProofHash()).to.equal(null); - expect(anotherClone.getProofHash()).to.equal(null); + // Checks tree bytes. + expect(child1111.getTreeBytes()).to.equal(0); + expect(child1112.getTreeBytes()).to.equal(0); + expect(child111.getTreeBytes()).to.equal(child111.computeTreeBytes()); + expect(child11.getTreeBytes()).to.equal(child11.computeTreeBytes()); + expect(child1.getTreeBytes()).to.equal(child1.computeTreeBytes()); + expect(child21.getTreeBytes()).to.equal(0); + expect(child2.getTreeBytes()).to.equal(0); + expect(stateTree.getTreeBytes()).to.equal(stateTree.computeTreeBytes()); + }); - expect(level1Node.getProofHash()).to.equal(level1Node.buildProofHash()); - expect(level1Clone.getProofHash()).to.equal(null); + it("updateStateInfoForAllRootPaths with multiple root paths", () => { + const stateTreeClone = stateTree.clone(); + const child1Clone = child1.clone(); + const child11Clone = child11.clone(); + const child111Clone = child111.clone(); + const child2Clone = child2.clone(); - expect(level0Node.getProofHash()).to.equal(level0Node.buildProofHash()); - expect(level0Clone.getProofHash()).to.equal(level0Clone.buildProofHash()); - expect(level0Clone.getProofHash()).to.equal(level0Node.getProofHash()); + const numAffectedNodes = + updateStateInfoForAllRootPaths([label1, label11, label111, label1112], stateTree); + expect(numAffectedNodes).to.equal(8); - expect(rootNode.getProofHash()).to.equal(rootNode.buildProofHash()); - expect(rootClone.getProofHash()).to.equal(rootClone.buildProofHash()); - expect(rootClone.getProofHash()).to.equal(rootNode.getProofHash()); + // Checks proof hashes. + expect(child1111.verifyProofHash()).to.equal(false); + expect(child1112.verifyProofHash()).to.equal(false); // not verified!! + expect(child111.verifyProofHash(label1112)).to.equal(true); // verified + expect(child111Clone.verifyProofHash(label1112)).to.equal(true); // verified + expect(child11.verifyProofHash()).to.equal(true); // verified + expect(child11Clone.verifyProofHash()).to.equal(true); // verified + expect(child11Clone.getProofHash()).to.equal(child11.getProofHash()); + expect(child1.verifyProofHash()).to.equal(true); // verified + expect(child1Clone.verifyProofHash()).to.equal(true); // verified + expect(child1Clone.getProofHash()).to.equal(child1.getProofHash()); + expect(child21.verifyProofHash()).to.equal(false); + expect(child2.verifyProofHash()).to.equal(false); + expect(child2Clone.verifyProofHash()).to.equal(false); + expect(stateTree.verifyProofHash(label1)).to.equal(true); // verified + expect(stateTreeClone.verifyProofHash(label1)).to.equal(true); // verified + expect(stateTreeClone.getProofHash()).to.equal(stateTree.getProofHash()); }); - }); - describe("verifyProofHashForStateTree", () => { - it("verify correct proof hashes as true", () => { - const jsObject = { - level0: { - level1: { - level2: { - foo: 'bar', - baz: 'caz' - } - }, - another_route: { - test: -1000 - } - } - }; - const rootNode = StateNode.fromJsObject(jsObject); - setProofHashForStateTree(rootNode); - expect(verifyProofHashForStateTree(rootNode)).to.equal(true); + it("updateStateInfoForAllRootPaths with deleted nodes", () => { + const numAffectedNodes = updateStateInfoForAllRootPaths( + [label1, label11, label111, 'deleted1', 'deleted2'], stateTree); // with deleted nodes + expect(numAffectedNodes).to.equal(4); + + // Checks proof hashes. + expect(child1111.verifyProofHash()).to.equal(false); + expect(child1112.verifyProofHash()).to.equal(false); + expect(child111.verifyProofHash()).to.equal(true); // verified!! + expect(child11.verifyProofHash()).to.equal(true); // verified + expect(child21.verifyProofHash()).to.equal(false); + expect(child2.verifyProofHash()).to.equal(false); + expect(child1.verifyProofHash()).to.equal(true); // verified + expect(stateTree.verifyProofHash(label1)).to.equal(true); // verified }); - it("verify wrong proof hashes as false", () => { - const jsObject = { - level0: { - level11: { - level2: { - foo: 'bar', - baz: 'caz' - } + it("verifyProofHashForStateTree ", () => { + updateStateInfoForStateTree(stateTree); + expect(verifyProofHashForStateTree(stateTree)).to.equal(true); + child111.setProofHash('new ph'); + expect(verifyProofHashForStateTree(stateTree)).to.equal(false); + }); + + it("getProofOfState", () => { + updateStateInfoForStateTree(stateTree); + assert.deepEqual(getProofOfStatePath(stateTree, [label1, label11]), { + ".radix_ph": "0xeef6cf891adc1b4755cb54085116c08d7ced1afe8eee3bdaac2259d935b2befe", + "000": { + "1": { + ".label": "0x0001", + ".proof_hash": { + ".radix_ph": "0x7ba5e5356546d605d7b44d9fce969e41520b81b5df436cb57a9209e1fefab25b", + "0011": { + ".label": "0x0011", + ".proof_hash": { + ".proof_hash": "0x567383e2ed5a49d908498eda42457ce1ed07c3b6672b75c3e0be5d0da8de4b9d" + }, + ".radix_ph": "0x9361b41ef0b1b88c2ea20a7aeb471f3c84af681b6a747a0c09a47794189e1c51" + } + }, + ".radix_ph": "0xdfa952d88be9321937e4ce6918c03312c40725472ee08d6d61a3b1f277e2f38b" }, - level12: { - level2: { - foo2: 'bar2' - } + "2": { + ".radix_ph": "0xa64fc83d2b5a4193e285cf17f9f2ad02898730a74441c995409d3d9be3b63dc6" }, - another_route: { - test: -1000 - } + ".radix_ph": "0x0f1fdb35bd8e9ec757d12c8a3dafdcd83437aa392b1fcd22d1b0c0ee273aed31" } - }; - const rootNode = StateNode.fromJsObject(jsObject); - const level0Node = rootNode.getChild('level0'); - const level12Node = level0Node.getChild('level12'); - setProofHashForStateTree(rootNode); - level12Node.setProofHash('0xdeadbeaf'); - expect(verifyProofHashForStateTree(rootNode)).to.equal(false); + }); }); }); }) \ No newline at end of file diff --git a/unittest/test-util.js b/unittest/test-util.js index 336362878..800f49908 100644 --- a/unittest/test-util.js +++ b/unittest/test-util.js @@ -1,5 +1,6 @@ const path = require('path'); const fs = require("fs"); +const _ = require("lodash"); const syncRequest = require('sync-request'); const { Block } = require('../blockchain/block'); const DB = require('../db'); @@ -71,12 +72,12 @@ function addBlock(node, txs, votes, validators) { finalDb.executeTransactionList(txs, false, true, lastBlock.number + 1); node.syncDbAndNonce(`${StateVersions.NODE}:${lastBlock.number + 1}`); node.addNewBlock(Block.create( - lastBlock.hash, votes, txs, lastBlock.number + 1, lastBlock.epoch + 1, '', + lastBlock.hash, votes, {}, txs, lastBlock.number + 1, lastBlock.epoch + 1, '', node.account.address, validators, 0, 0)); } async function waitUntilTxFinalized(servers, txHash) { - const MAX_ITERATION = 200; + const MAX_ITERATION = 40; let iterCount = 0; const unchecked = new Set(servers); while (true) { @@ -95,7 +96,7 @@ async function waitUntilTxFinalized(servers, txHash) { unchecked.delete(server); } } - await CommonUtil.sleep(500); + await CommonUtil.sleep(3000); iterCount++; } } @@ -109,13 +110,46 @@ async function waitForNewBlocks(server, waitFor = 1) { } } +async function waitUntilNetworkIsReady(serverList) { + const MAX_ITERATION = 40; + let iterCount = 0; + const unchecked = new Set(serverList); + while (true) { + if (!unchecked.size) { + return true; + } + if (iterCount >= MAX_ITERATION) { + console.log(`Iteration count exceeded its limit before the network is ready (${JSON.stringify([...unchecked])})`); + return false; + } + for (const server of unchecked) { + try { + const healthCheck = parseOrLog(syncRequest('GET', server + '/health_check') + .body + .toString('utf-8')); + if (healthCheck === true) { + unchecked.delete(server); + } + } catch (e) { + // server may not be ready yet + } + } + await CommonUtil.sleep(3000); + iterCount++; + } +} + async function waitUntilNodeSyncs(server) { let isSyncing = true; while (isSyncing) { - isSyncing = parseOrLog(syncRequest('POST', server + '/json-rpc', - {json: {jsonrpc: '2.0', method: 'net_syncing', id: 0, - params: {protoVer: CURRENT_PROTOCOL_VERSION}}}) - .body.toString('utf-8')).result.result; + try { + isSyncing = parseOrLog(syncRequest('POST', server + '/json-rpc', + {json: {jsonrpc: '2.0', method: 'net_syncing', id: 0, + params: {protoVer: CURRENT_PROTOCOL_VERSION}}}) + .body.toString('utf-8')).result.result; + } catch (e) { + // server may not be ready yet + } await CommonUtil.sleep(1000); } } @@ -165,6 +199,16 @@ function getBlockByNumber(server, number) { .body.toString('utf-8')).result; } +function eraseStateGas(result, appNameList = []) { + const erased = JSON.parse(JSON.stringify(result)); + _.set(erased, 'gas_amount_charged', 'erased'); + _.set(erased, 'gas_amount_total.state.service', 'erased'); + for (const appName of appNameList) { + _.set(erased, `gas_amount_total.state.app.${appName}`, 'erased'); + } + return erased; +} + module.exports = { GET_OPTIONS_INCLUDE_ALL, readConfigFile, @@ -173,10 +217,12 @@ module.exports = { addBlock, waitUntilTxFinalized, waitForNewBlocks, + waitUntilNetworkIsReady, waitUntilNodeSyncs, parseOrLog, setUpApp, getLastBlock, getLastBlockNumber, getBlockByNumber, + eraseStateGas, }; diff --git a/yarn.lock b/yarn.lock index 6547a36fd..8d0992fef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3531,9 +3531,9 @@ path-key@^2.0.1: integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-to-regexp@0.1.7: version "0.1.7"