diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index 1585dfede..42637e93e 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -28,6 +28,8 @@ jobs: with: node-version: ${{ matrix.node-version }} registry-url: 'https://registry.npmjs.org' + - name: node-gyp install + run: yarn global add node-gyp - name: yarn install run: yarn install - name: run unittest diff --git a/blockchain-configs/base/timer_flags.json b/blockchain-configs/base/timer_flags.json index 702db6a3d..308ee2500 100644 --- a/blockchain-configs/base/timer_flags.json +++ b/blockchain-configs/base/timer_flags.json @@ -55,5 +55,9 @@ "tweak_transfer_gc_rule": { "enabled_block": 2, "has_bandage": true + }, + "allow_up_to_6_decimal_transfer_value_only": { + "enabled_block": 2, + "has_bandage": true } } diff --git a/blockchain-configs/mainnet-prod/timer_flags.json b/blockchain-configs/mainnet-prod/timer_flags.json index 521d2da6a..31958e064 100644 --- a/blockchain-configs/mainnet-prod/timer_flags.json +++ b/blockchain-configs/mainnet-prod/timer_flags.json @@ -54,5 +54,9 @@ "tweak_transfer_gc_rule": { "enabled_block": 2430100, "has_bandage": true + }, + "allow_up_to_6_decimal_transfer_value_only": { + "enabled_block": 3064900, + "has_bandage": true } } diff --git a/blockchain-configs/testnet-prod/timer_flags.json b/blockchain-configs/testnet-prod/timer_flags.json index dcbf38c4a..af7881d70 100644 --- a/blockchain-configs/testnet-prod/timer_flags.json +++ b/blockchain-configs/testnet-prod/timer_flags.json @@ -58,5 +58,9 @@ "tweak_transfer_gc_rule": { "enabled_block": 2429500, "has_bandage": true + }, + "allow_up_to_6_decimal_transfer_value_only": { + "enabled_block": 3062600, + "has_bandage": true } } diff --git a/blockchain/index.js b/blockchain/index.js index 2fbe2699c..70cfce1b3 100644 --- a/blockchain/index.js +++ b/blockchain/index.js @@ -89,7 +89,7 @@ class Blockchain { * the blockchain. * * @param {string} hash - hash or hash substring of block. - * @return {Block} Block instance corresponding to the queried block hash. + * @returns {Block} Block instance corresponding to the queried block hash. */ getBlockByHash(hash) { if (!hash) return null; @@ -108,7 +108,7 @@ class Blockchain { * Given a block number, returns the block that corresponds to the block number. * * @param {integer} number - block number - * @return {Block} Block instance corresponding to the queried block number. + * @returns {Block} Block instance corresponding to the queried block number. */ getBlockByNumber(number) { if (number === undefined || number === null) return null; @@ -324,7 +324,7 @@ class Blockchain { * * @param {Number} from - The lowest block number to get (included) * @param {Number} to - The highest block number to geet (excluded) - * @return {list} A list of blocks, up to CHAIN_SEGMENT_LENGTH blocks + * @returns {list} A list of blocks, up to CHAIN_SEGMENT_LENGTH blocks */ getBlockList(from, to) { const blockList = []; @@ -358,7 +358,7 @@ class Blockchain { * starting from the `from` block number (included). * * @param {Number} from - The highest block number to get (included) - * @return {list} A list of blocks, up to OLD_CHAIN_SEGMENT_LENGTH blocks + * @returns {list} A list of blocks, up to OLD_CHAIN_SEGMENT_LENGTH blocks */ getOldBlockList(from) { const lastBlock = this.lastBlock(); diff --git a/client/protocol_versions.json b/client/protocol_versions.json index 47fe133cb..8fbd9754e 100644 --- a/client/protocol_versions.json +++ b/client/protocol_versions.json @@ -131,5 +131,8 @@ }, "1.1.2": { "min": "1.0.0" + }, + "1.1.3": { + "min": "1.0.0" } } \ No newline at end of file diff --git a/common/common-util.js b/common/common-util.js index df816c836..736fd1aea 100644 --- a/common/common-util.js +++ b/common/common-util.js @@ -751,22 +751,27 @@ class CommonUtil { * @param {Object} gasAmount gas amount * @returns */ - static getTotalGasCost(gasPrice, gasAmount, gasPriceUnit) { + static getTotalGasCost(gasPrice, gasAmount, gasPriceUnit, enableGasCostFlooring = true) { if (!CommonUtil.isNumber(gasPrice)) { gasPrice = 0; // Default gas price = 0 microain } if (!CommonUtil.isNumber(gasAmount)) { gasAmount = 0; // Default gas amount = 0 } - return gasPrice * gasPriceUnit * gasAmount; + let cost = gasPrice * gasPriceUnit * gasAmount; + // NOTE(platfowner): Apply gas cost flooring up-to 6 decimals. + if (enableGasCostFlooring) { + cost = Math.floor(cost * 1000000) / 1000000; // gas cost flooring + } + return cost; } - static getServiceGasCostTotalFromTxList(txList, resList, gasPriceUnit) { + static getServiceGasCostTotalFromTxList(txList, resList, gasPriceUnit, enableGasCostFlooring = true) { return resList.reduce((acc, cur, index) => { const tx = txList[index]; return CommonUtil.mergeNumericJsObjects(acc, { gasAmountTotal: cur.gas_amount_charged, - gasCostTotal: CommonUtil.getTotalGasCost(tx.tx_body.gas_price, cur.gas_amount_charged, gasPriceUnit) + gasCostTotal: CommonUtil.getTotalGasCost(tx.tx_body.gas_price, cur.gas_amount_charged, gasPriceUnit, enableGasCostFlooring) }); }, { gasAmountTotal: 0, gasCostTotal: 0 }); } diff --git a/consensus/index.js b/consensus/index.js index 94b89a0ab..0834de7c8 100644 --- a/consensus/index.js +++ b/consensus/index.js @@ -18,6 +18,7 @@ const { StateVersions, ValueChangedEventSources, TransactionStates, + isEnabledTimerFlag, } = require('../common/constants'); const { ConsensusErrorCode } = require('../common/result-code'); const { @@ -713,8 +714,9 @@ class Consensus { }); } const gasPriceUnit = node.getBlockchainParam('resource/gas_price_unit', number, db.stateVersion); + const enableGasCostFlooring = isEnabledTimerFlag('allow_up_to_6_decimal_transfer_value_only', number); const { gasAmountTotal, gasCostTotal } = - CommonUtil.getServiceGasCostTotalFromTxList(transactions, txsRes, gasPriceUnit); + CommonUtil.getServiceGasCostTotalFromTxList(transactions, txsRes, gasPriceUnit, enableGasCostFlooring); if (gasAmountTotal !== expectedGasAmountTotal) { throw new ConsensusError({ code: ConsensusErrorCode.INVALID_GAS_AMOUNT_TOTAL, diff --git a/db/bandage-files/allow_up_to_6_decimal_transfer_value_only.js b/db/bandage-files/allow_up_to_6_decimal_transfer_value_only.js new file mode 100644 index 000000000..c74c788f6 --- /dev/null +++ b/db/bandage-files/allow_up_to_6_decimal_transfer_value_only.js @@ -0,0 +1,18 @@ +module.exports = { + data: [ + { + path: ['rules', 'transfer', '$from', '$to', '$key', 'value' ], + value: { + '.rule': { + "write": "(auth.addr === $from || auth.fid === '_stake' || auth.fid === '_unstake' || auth.fid === '_pay' || auth.fid === '_claim' || auth.fid === '_hold' || auth.fid === '_release' || auth.fid === '_collectFee' || auth.fid === '_claimReward' || auth.fid === '_openCheckout' || auth.fid === '_closeCheckout' || auth.fid === '_closeCheckin') && !getValue('transfer/' + $from + '/' + $to + '/' + $key) && (util.isServAcntName($from, blockNumber) || util.isCksumAddr($from)) && (util.isServAcntName($to, blockNumber) || util.isCksumAddr($to)) && $from !== $to && util.isNumber(newData) && newData > 0 && util.countDecimals(newData) <= 6 && util.getBalance($from, getValue) >= newData" + } + }, + // From allow_non_negative_transfer_value_only.js bandage file. + prevValue: { + ".rule": { + "write": "(auth.addr === $from || auth.fid === '_stake' || auth.fid === '_unstake' || auth.fid === '_pay' || auth.fid === '_claim' || auth.fid === '_hold' || auth.fid === '_release' || auth.fid === '_collectFee' || auth.fid === '_claimReward' || auth.fid === '_openCheckout' || auth.fid === '_closeCheckout' || auth.fid === '_closeCheckin') && !getValue('transfer/' + $from + '/' + $to + '/' + $key) && (util.isServAcntName($from, blockNumber) || util.isCksumAddr($from)) && (util.isServAcntName($to, blockNumber) || util.isCksumAddr($to)) && $from !== $to && util.isNumber(newData) && newData > 0 && util.getBalance($from, getValue) >= newData" + } + } + } + ] +}; diff --git a/db/index.js b/db/index.js index 95755c0c4..deee3b9cc 100644 --- a/db/index.js +++ b/db/index.js @@ -1702,6 +1702,7 @@ class DB { auth, tx, timestamp, blockNumber, blockTime, executionResult, eventSource, isDryrun) { const gasPriceUnit = DB.getBlockchainParam('resource/gas_price_unit', blockNumber, this.stateRoot); + const enableGasCostFlooring = isEnabledTimerFlag('allow_up_to_6_decimal_transfer_value_only', blockNumber); const gasPrice = tx.tx_body.gas_price; // Use only the service gas amount total const serviceBandwidthGasAmount = _.get(tx, 'extra.gas.bandwidth.service', 0); @@ -1711,7 +1712,7 @@ class DB { if (gasAmountChargedByTransfer <= 0 || gasPrice === 0) { // No fees to collect executionResult.gas_amount_charged = gasAmountChargedByTransfer; executionResult.gas_cost_total = - CommonUtil.getTotalGasCost(gasPrice, gasAmountChargedByTransfer, gasPriceUnit); + CommonUtil.getTotalGasCost(gasPrice, gasAmountChargedByTransfer, gasPriceUnit, enableGasCostFlooring); return; } const billing = tx.tx_body.billing; @@ -1724,7 +1725,7 @@ class DB { } } let balance = this.getBalance(billedTo); - const gasCost = CommonUtil.getTotalGasCost(gasPrice, gasAmountChargedByTransfer, gasPriceUnit); + const gasCost = CommonUtil.getTotalGasCost(gasPrice, gasAmountChargedByTransfer, gasPriceUnit, enableGasCostFlooring); if (!isDryrun && balance < gasCost) { Object.assign(executionResult, { code: TxResultCode.FEE_BALANCE_TOO_LOW, @@ -1736,7 +1737,7 @@ class DB { } executionResult.gas_amount_charged = gasAmountChargedByTransfer; executionResult.gas_cost_total = - CommonUtil.getTotalGasCost(gasPrice, executionResult.gas_amount_charged, gasPriceUnit); + CommonUtil.getTotalGasCost(gasPrice, executionResult.gas_amount_charged, gasPriceUnit, enableGasCostFlooring); if (isDryrun || executionResult.gas_cost_total <= 0) { return; } diff --git a/db/rule-util.js b/db/rule-util.js index 9872b79e0..83aa8bddd 100644 --- a/db/rule-util.js +++ b/db/rule-util.js @@ -122,6 +122,20 @@ class RuleUtil { return this.isString(str) ? str : ''; } + countDecimals(value) { + const decimalExponentRegex = /^-{0,1}(\d*\.{0,1}\d*)e-(\d+)$/gm; + + if (Math.floor(value) === value) { + return 0; + } + const valueString = value.toString(); + const matches = decimalExponentRegex.exec(valueString); + if (matches) { + return Number(matches[2]) + this.countDecimals(Number(matches[1])); + } + return valueString.split('.')[1].length || 0; + } + toBool(value) { return this.isBool(value) ? value : value === 'true'; } diff --git a/deploy_blockchain_genesis_gcp.sh b/deploy_blockchain_genesis_gcp.sh index 3edd7d0ae..72d4d6eab 100644 --- a/deploy_blockchain_genesis_gcp.sh +++ b/deploy_blockchain_genesis_gcp.sh @@ -119,7 +119,7 @@ JSON_RPC_NODE_INDEX_LE=4 REST_FUNC_NODE_INDEX_GE=0 REST_FUNC_NODE_INDEX_LE=2 # Event-Handler-enabled blockchain nodes -EVENT_HANDLER_NODE_INDEX_GE=3 +EVENT_HANDLER_NODE_INDEX_GE=0 EVENT_HANDLER_NODE_INDEX_LE=4 printf "\n" diff --git a/deploy_blockchain_incremental_gcp.sh b/deploy_blockchain_incremental_gcp.sh index 3a3c32efd..a76939487 100644 --- a/deploy_blockchain_incremental_gcp.sh +++ b/deploy_blockchain_incremental_gcp.sh @@ -105,7 +105,7 @@ JSON_RPC_NODE_INDEX_LE=4 REST_FUNC_NODE_INDEX_GE=0 REST_FUNC_NODE_INDEX_LE=2 # Event-Handler-enabled blockchain nodes -EVENT_HANDLER_NODE_INDEX_GE=3 +EVENT_HANDLER_NODE_INDEX_GE=0 EVENT_HANDLER_NODE_INDEX_LE=4 printf "\n" diff --git a/json_rpc/index.js b/json_rpc/index.js index cbd6b2c99..8a8a87026 100644 --- a/json_rpc/index.js +++ b/json_rpc/index.js @@ -22,8 +22,8 @@ const { JSON_RPC_METHODS } = require('./constants'); * @param {EventHandler} eventHandler Instance of the EventHandler class. * @param {string} minProtocolVersion Minimum compatible protocol version. * @param {string} maxProtocolVersion Maximum compatible protocol version. - * @return {dict} A closure of functions compatible with the jayson library for - * servicing JSON-RPC requests. + * @returns {dict} A closure of functions compatible with the jayson library for + * servicing JSON-RPC requests. */ module.exports = function getApis(node, p2pServer, minProtocolVersion, maxProtocolVersion) { // Minimally required APIs diff --git a/node/index.js b/node/index.js index e601dd88a..902eef579 100644 --- a/node/index.js +++ b/node/index.js @@ -23,6 +23,7 @@ const { TrafficEventTypes, trafficStatsManager, ValueChangedEventSources, + isEnabledTimerFlag, } = require('../common/constants'); const { TxResultCode } = require('../common/result-code'); const { ValidatorOffenseTypes } = require('../consensus/constants'); @@ -683,7 +684,7 @@ class BlockchainNode { * * @param {dict} operation - Database write operation to be converted to transaction * not - * @return {Transaction} Instance of the transaction class + * @returns {Transaction} Instance of the transaction class */ createTransaction(txBody) { const LOG_HEADER = 'createTransaction'; @@ -949,8 +950,9 @@ class BlockchainNode { } const gasPriceUnit = this.getBlockchainParam('resource/gas_price_unit', blockNumber, baseDb.stateVersion); + const enableGasCostFlooring = isEnabledTimerFlag('allow_up_to_6_decimal_transfer_value_only', blockNumber); const { gasAmountTotal, gasCostTotal } = - CommonUtil.getServiceGasCostTotalFromTxList(transactions, resList, gasPriceUnit); + CommonUtil.getServiceGasCostTotalFromTxList(transactions, resList, gasPriceUnit, enableGasCostFlooring); const receipts = CommonUtil.txResultsToReceipts(resList); return { transactions, receipts, gasAmountTotal, gasCostTotal }; } diff --git a/package.json b/package.json index 33c682d76..e681f467b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ain-blockchain", "description": "AI Network Blockchain", - "version": "1.1.2", + "version": "1.1.3", "private": true, "license": "MIT", "author": "dev@ainetwork.ai", diff --git a/start_node_genesis_gcp.sh b/start_node_genesis_gcp.sh index bc8908ec0..12a2294a1 100644 --- a/start_node_genesis_gcp.sh +++ b/start_node_genesis_gcp.sh @@ -1,8 +1,8 @@ #!/bin/bash # NOTE(minsulee2): Since exit really exits terminals, those are replaced to return 1. -if [[ $# -lt 4 ]] || [[ $# -gt 12 ]]; then - printf "Usage: bash start_node_genesis_gcp.sh [dev|staging|sandbox|exp|spring|summer|mainnet] [--keystore|--mnemonic|--private-key] [--keep-code|--no-keep-code] [--keep-data|--no-keep-data] [--full-sync|--fast-sync] [--chown-data|--no-chown-data] [--json-rpc] [--update-front-db] [--rest-func]\n" +if [[ $# -lt 4 ]] || [[ $# -gt 13 ]]; then + printf "Usage: bash start_node_genesis_gcp.sh [dev|staging|sandbox|exp|spring|summer|mainnet] [--keystore|--mnemonic|--private-key] [--keep-code|--no-keep-code] [--keep-data|--no-keep-data] [--full-sync|--fast-sync] [--chown-data|--no-chown-data] [--json-rpc] [--update-front-db] [--rest-func] [--event-handler]\n" printf "Example: bash start_node_genesis_gcp.sh spring gcp_user 0 0 --keystore --no-keep-code --full-sync --no-chown-data\n" printf "\n" return 1 diff --git a/start_node_incremental_gcp.sh b/start_node_incremental_gcp.sh index db8bf3a9e..9cb9767c8 100644 --- a/start_node_incremental_gcp.sh +++ b/start_node_incremental_gcp.sh @@ -1,7 +1,7 @@ #!/bin/bash -if [[ $# -lt 4 ]] || [[ $# -gt 12 ]]; then - printf "Usage: bash start_node_incremental_gcp.sh [dev|staging|sandbox|exp|spring|summer|mainnet] [--keystore|--mnemonic|--private-key] [--keep-code|--no-keep-code] [--keep-data|--no-keep-data] [--full-sync|--fast-sync] [--chown-data|--no-chown-data] [--json-rpc] [--update-front-db] [--rest-func]\n" +if [[ $# -lt 4 ]] || [[ $# -gt 13 ]]; then + printf "Usage: bash start_node_incremental_gcp.sh [dev|staging|sandbox|exp|spring|summer|mainnet] [--keystore|--mnemonic|--private-key] [--keep-code|--no-keep-code] [--keep-data|--no-keep-data] [--full-sync|--fast-sync] [--chown-data|--no-chown-data] [--json-rpc] [--update-front-db] [--rest-func] [--event-handler]\n" printf "Example: bash start_node_incremental_gcp.sh spring gcp_user 0 0 --keystore --no-keep-code --full-sync --no-chown-data\n" printf "\n" exit diff --git a/test/integration/consensus.test.js b/test/integration/consensus.test.js index 71a0c0825..1818ef785 100644 --- a/test/integration/consensus.test.js +++ b/test/integration/consensus.test.js @@ -405,13 +405,29 @@ describe('Consensus', () => { }); }); + it('cannot claim with an amount of more than 6 decimals', async () => { + const claimTx = parseOrLog(syncRequest('POST', server1 + '/set_value', {json: { + ref: `/gas_fee/claim/${server1Addr}/0`, + value: { + amount: 0.0000001 // an amount of 7 decimals + } + }}).body.toString('utf-8')).result; + if (!(await waitUntilTxFinalized(serverList, claimTx.tx_hash))) { + console.error(`Failed to check finalization of tx.`); + } + expect(claimTx.result.code).to.equal(10104); + expect(claimTx.result.func_results._claimReward.code).to.equal(20001); + expect(claimTx.result.func_results._claimReward.op_results['0'].result.code).to.equal(12103); + }); + it('can claim unclaimed rewards', async () => { const unclaimed = parseOrLog(syncRequest('GET', server1 + `/get_value?ref=/consensus/rewards/${server1Addr}/unclaimed&is_final=true`).body.toString('utf-8')).result; + const unclaimedFloored = Math.floor(unclaimed * 1000000) / 1000000; const claimTx = parseOrLog(syncRequest('POST', server1 + '/set_value', {json: { ref: `/gas_fee/claim/${server1Addr}/1`, value: { - amount: unclaimed + amount: unclaimedFloored }, timestamp: 1629377509815 }}).body.toString('utf-8')).result; diff --git a/test/integration/function.test.js b/test/integration/function.test.js index 41bfb7862..c1dc56d37 100644 --- a/test/integration/function.test.js +++ b/test/integration/function.test.js @@ -2683,13 +2683,35 @@ describe('Native Function', () => { expect(toAfterBalance).to.equal(toBeforeBalance); }); - it('transfer: transfer more than account balance', async () => { + it('transfer: transfer with a value of 7 decimals', async () => { let fromBeforeBalance = parseOrLog(syncRequest('GET', server2 + `/get_value?ref=${transferFromBalancePath}`).body.toString('utf-8')).result; let toBeforeBalance = parseOrLog(syncRequest('GET', server2 + `/get_value?ref=${transferToBalancePath}`).body.toString('utf-8')).result; const body = parseOrLog(syncRequest('POST', server1 + '/set_value', {json: { ref: transferPath + '/5/value', + value: 0.0000001, // a value of 7 decimals + }}).body.toString('utf-8')); + assert.deepEqual(_.get(body, 'result.result.code'), 12103); + assert.deepEqual(body.code, 40001); + if (!(await waitUntilTxFinalized([server2], _.get(body, 'result.tx_hash')))) { + console.error(`Failed to check finalization of tx.`); + } + const fromAfterBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=${transferFromBalancePath}`).body.toString('utf-8')).result; + const toAfterBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=${transferToBalancePath}`).body.toString('utf-8')).result; + expect(fromAfterBalance).to.equal(fromBeforeBalance); + expect(toAfterBalance).to.equal(toBeforeBalance); + }); + + it('transfer: transfer more than account balance', async () => { + let fromBeforeBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=${transferFromBalancePath}`).body.toString('utf-8')).result; + let toBeforeBalance = parseOrLog(syncRequest('GET', + server2 + `/get_value?ref=${transferToBalancePath}`).body.toString('utf-8')).result; + const body = parseOrLog(syncRequest('POST', server1 + '/set_value', {json: { + ref: transferPath + '/6/value', value: fromBeforeBalance + 1 }}).body.toString('utf-8')); expect(body.code).to.equals(40001); @@ -2710,7 +2732,7 @@ describe('Native Function', () => { let toBeforeBalance = parseOrLog(syncRequest('GET', server2 + `/get_value?ref=${transferToBalancePath}`).body.toString('utf-8')).result; const body = parseOrLog(syncRequest('POST', server3 + '/set_value', {json: { - ref: transferPath + '/6/value', + ref: transferPath + '/7/value', value: transferAmount }}).body.toString('utf-8')); expect(body.code).to.equals(40001); @@ -2739,7 +2761,7 @@ describe('Native Function', () => { it('transfer: transfer with same addresses', async () => { const transferPathSameAddrs = `/transfer/${transferFrom}/${transferFrom}`; const body = parseOrLog(syncRequest('POST', server1 + '/set_value', {json: { - ref: transferPathSameAddrs + '/7/value', + ref: transferPathSameAddrs + '/8/value', value: transferAmount }}).body.toString('utf-8')); expect(body.code).to.equals(40001); @@ -2936,7 +2958,7 @@ describe('Native Function', () => { } }, "gas_cost_total": 0, - "message": "Write rule evaluated false: [(auth.addr === $from || auth.fid === '_stake' || auth.fid === '_unstake' || auth.fid === '_pay' || auth.fid === '_claim' || auth.fid === '_hold' || auth.fid === '_release' || auth.fid === '_collectFee' || auth.fid === '_claimReward' || auth.fid === '_openCheckout' || auth.fid === '_closeCheckout' || auth.fid === '_closeCheckin') && !getValue('transfer/' + $from + '/' + $to + '/' + $key) && (util.isServAcntName($from, blockNumber) || util.isCksumAddr($from)) && (util.isServAcntName($to, blockNumber) || util.isCksumAddr($to)) && $from !== $to && util.isNumber(newData) && newData > 0 && util.getBalance($from, getValue) >= newData] at '/transfer/$from/$to/$key/value' for value path '/transfer/0x00ADEc28B6a845a085e03591bE7550dd68673C1C/staking|Test_Service|0x01A0980d2D4e418c7F27e1ef539d01A5b5E93204|0/203/value' with path vars '{\"$key\":\"203\",\"$to\":\"staking|Test_Service|0x01A0980d2D4e418c7F27e1ef539d01A5b5E93204|0\",\"$from\":\"0x00ADEc28B6a845a085e03591bE7550dd68673C1C\"}', data 'null', newData '33', auth '{\"addr\":\"0x00ADEc28B6a845a085e03591bE7550dd68673C1C\"}', timestamp '1234567890001'", + "message": "Write rule evaluated false: [(auth.addr === $from || auth.fid === '_stake' || auth.fid === '_unstake' || auth.fid === '_pay' || auth.fid === '_claim' || auth.fid === '_hold' || auth.fid === '_release' || auth.fid === '_collectFee' || auth.fid === '_claimReward' || auth.fid === '_openCheckout' || auth.fid === '_closeCheckout' || auth.fid === '_closeCheckin') && !getValue('transfer/' + $from + '/' + $to + '/' + $key) && (util.isServAcntName($from, blockNumber) || util.isCksumAddr($from)) && (util.isServAcntName($to, blockNumber) || util.isCksumAddr($to)) && $from !== $to && util.isNumber(newData) && newData > 0 && util.countDecimals(newData) <= 6 && util.getBalance($from, getValue) >= newData] at '/transfer/$from/$to/$key/value' for value path '/transfer/0x00ADEc28B6a845a085e03591bE7550dd68673C1C/staking|Test_Service|0x01A0980d2D4e418c7F27e1ef539d01A5b5E93204|0/203/value' with path vars '{\"$key\":\"203\",\"$to\":\"staking|Test_Service|0x01A0980d2D4e418c7F27e1ef539d01A5b5E93204|0\",\"$from\":\"0x00ADEc28B6a845a085e03591bE7550dd68673C1C\"}', data 'null', newData '33', auth '{\"addr\":\"0x00ADEc28B6a845a085e03591bE7550dd68673C1C\"}', timestamp '1234567890001'", }); if (!(await waitUntilTxFinalized([server2], _.get(body, 'result.tx_hash')))) { console.error(`Failed to check finalization of tx.`); diff --git a/test/unit/consensus.test.js b/test/unit/consensus.test.js index 67a2aa4ea..53756a540 100644 --- a/test/unit/consensus.test.js +++ b/test/unit/consensus.test.js @@ -85,7 +85,8 @@ describe("Consensus", () => { timestamp } ); - expect(node1.db.executeTransaction(voteTx).code).to.equal(0); + // NOTE(platfowner): Set blockNumber = 2 to enable allow_up_to_6_decimal_transfer_value_only timer flag. + expect(node1.db.executeTransaction(voteTx, false, false, 2).code).to.equal(0); }); it('Staked nodes without producing rights cannot propose blocks', () => { diff --git a/test/unit/rule-util.test.js b/test/unit/rule-util.test.js index 33bfbe18b..2d08e7077 100644 --- a/test/unit/rule-util.test.js +++ b/test/unit/rule-util.test.js @@ -650,6 +650,74 @@ describe("RuleUtil", () => { }) }) + describe('countDecimals', () => { + it('returns zero', () => { + expect(util.countDecimals(0)).to.equal(0); // '0' + expect(util.countDecimals(1)).to.equal(0); // '1' + expect(util.countDecimals(10)).to.equal(0); // '10' + expect(util.countDecimals(100)).to.equal(0); // '100' + expect(util.countDecimals(1000)).to.equal(0); // '1000' + expect(util.countDecimals(10000)).to.equal(0); // '10000' + expect(util.countDecimals(100000)).to.equal(0); // '100000' + expect(util.countDecimals(1000000)).to.equal(0); // '1000000' + expect(util.countDecimals(10000000)).to.equal(0); // '10000000' + expect(util.countDecimals(100000000)).to.equal(0); // '100000000' + expect(util.countDecimals(1000000000)).to.equal(0); // '1000000000' + expect(util.countDecimals(1234567890)).to.equal(0); // '1234567890' + expect(util.countDecimals(-1)).to.equal(0); // '-1' + expect(util.countDecimals(-1000000000)).to.equal(0); // '-1000000000' + expect(util.countDecimals(11)).to.equal(0); // '11' + expect(util.countDecimals(101)).to.equal(0); // '101' + expect(util.countDecimals(1001)).to.equal(0); // '1001' + expect(util.countDecimals(10001)).to.equal(0); // '10001' + expect(util.countDecimals(100001)).to.equal(0); // '100001' + expect(util.countDecimals(1000001)).to.equal(0); // '1000001' + expect(util.countDecimals(10000001)).to.equal(0); // '10000001' + expect(util.countDecimals(100000001)).to.equal(0); // '100000001' + expect(util.countDecimals(1000000001)).to.equal(0); // '1000000001' + expect(util.countDecimals(-11)).to.equal(0); // '-11' + expect(util.countDecimals(-1000000001)).to.equal(0); // '-1000000001' + }); + + it('returns positive', () => { + expect(util.countDecimals(0.1)).to.equal(1); // '0.1' + expect(util.countDecimals(0.01)).to.equal(2); // '0.01' + expect(util.countDecimals(0.001)).to.equal(3); // '0.001' + expect(util.countDecimals(0.0001)).to.equal(4); // '0.0001' + expect(util.countDecimals(0.00001)).to.equal(5); // '0.00001' + expect(util.countDecimals(0.000001)).to.equal(6); // '0.000001' + expect(util.countDecimals(0.0000001)).to.equal(7); // '1e-7' + expect(util.countDecimals(0.00000001)).to.equal(8); // '1e-8' + expect(util.countDecimals(0.000000001)).to.equal(9); // '1e-9' + expect(util.countDecimals(0.0000000001)).to.equal(10); // '1e-10' + expect(util.countDecimals(-0.1)).to.equal(1); // '-0.1' + expect(util.countDecimals(-0.0000000001)).to.equal(10); // '-1e-10' + expect(util.countDecimals(1.2)).to.equal(1); // '1.2' + expect(util.countDecimals(0.12)).to.equal(2); // '0.12' + expect(util.countDecimals(0.012)).to.equal(3); // '0.012' + expect(util.countDecimals(0.0012)).to.equal(4); // '0.0012' + expect(util.countDecimals(0.00012)).to.equal(5); // '0.00012' + expect(util.countDecimals(0.000012)).to.equal(6); // '0.000012' + expect(util.countDecimals(0.0000012)).to.equal(7); // '0.0000012' + expect(util.countDecimals(0.00000012)).to.equal(8); // '1.2e-7' + expect(util.countDecimals(0.000000012)).to.equal(9); // '1.2e-8' + expect(util.countDecimals(0.0000000012)).to.equal(10); // '1.2e-9' + expect(util.countDecimals(-1.2)).to.equal(1); // '-1.2' + expect(util.countDecimals(-0.0000000012)).to.equal(10); // '-1.2e-9' + expect(util.countDecimals(1.03)).to.equal(2); // '1.03' + expect(util.countDecimals(1.003)).to.equal(3); // '1.003' + expect(util.countDecimals(1.0003)).to.equal(4); // '1.0003' + expect(util.countDecimals(1.00003)).to.equal(5); // '1.00003' + expect(util.countDecimals(1.000003)).to.equal(6); // '1.000003' + expect(util.countDecimals(1.0000003)).to.equal(7); // '1.0000003' + expect(util.countDecimals(1.00000003)).to.equal(8); // '1.00000003' + expect(util.countDecimals(1.000000003)).to.equal(9); // '1.000000003' + expect(util.countDecimals(1.0000000003)).to.equal(10); // '1.0000000003' + expect(util.countDecimals(-1.03)).to.equal(2); // '-1.03' + expect(util.countDecimals(-1.0000000003)).to.equal(10); // '-1.0000000003' + }); + }); + describe("toBool", () => { it("returns false", () => { expect(util.toBool(0)).to.equal(false); diff --git a/tools/genesis-file/createGenesisBlock.js b/tools/genesis-file/createGenesisBlock.js index 54cae1fe4..151cc0fad 100644 --- a/tools/genesis-file/createGenesisBlock.js +++ b/tools/genesis-file/createGenesisBlock.js @@ -14,6 +14,7 @@ const { getBlockchainConfig, buildOwnerPermissions, BlockchainParams, + isEnabledTimerFlag, } = require('../../common/constants'); const CommonUtil = require('../../common/common-util'); const FileUtil = require('../../common/file-util'); @@ -400,8 +401,9 @@ function executeGenesisTxsAndGetData(genesisTxs) { } resList.push(res); } + const enableGasCostFlooring = isEnabledTimerFlag('allow_up_to_6_decimal_transfer_value_only', 0); const { gasAmountTotal, gasCostTotal } = CommonUtil.getServiceGasCostTotalFromTxList( - genesisTxs, resList, BlockchainParams.resource.gas_price_unit); + genesisTxs, resList, BlockchainParams.resource.gas_price_unit, enableGasCostFlooring); return { stateProofHash: tempGenesisDb.getProofHash('/'), gasAmountTotal, diff --git a/tracker-server/json-rpc.js b/tracker-server/json-rpc.js index 99a24bd10..53cca1c4a 100644 --- a/tracker-server/json-rpc.js +++ b/tracker-server/json-rpc.js @@ -6,8 +6,8 @@ const { getGraphData } = require('./network-topology'); * JSON-RPC calls * * @param {list} nodes - List of all nodes from. - * @return {dict} A closure of functions compatible with the jayson library for - * servicing JSON-RPC requests. + * @returns {dict} A closure of functions compatible with the jayson library for + * servicing JSON-RPC requests. */ module.exports = function getMethods(tracker, logger) { const blockchainNodes = tracker.blockchainNodes;