diff --git a/blockchain-configs/base/timer_flags.json b/blockchain-configs/base/timer_flags.json index 61630ce74..702db6a3d 100644 --- a/blockchain-configs/base/timer_flags.json +++ b/blockchain-configs/base/timer_flags.json @@ -47,5 +47,13 @@ "update_min_gc_num_siblings_deleted": { "enabled_block": 2, "has_bandage": true + }, + "update_min_gc_num_siblings_deleted2": { + "enabled_block": 2, + "has_bandage": true + }, + "tweak_transfer_gc_rule": { + "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 636bf9642..521d2da6a 100644 --- a/blockchain-configs/mainnet-prod/timer_flags.json +++ b/blockchain-configs/mainnet-prod/timer_flags.json @@ -46,5 +46,13 @@ "update_min_gc_num_siblings_deleted": { "enabled_block": 1414800, "has_bandage": true + }, + "update_min_gc_num_siblings_deleted2": { + "enabled_block": 2430100, + "has_bandage": true + }, + "tweak_transfer_gc_rule": { + "enabled_block": 2430100, + "has_bandage": true } } diff --git a/blockchain-configs/testnet-dev/timer_flags.json b/blockchain-configs/testnet-dev/timer_flags.json deleted file mode 100644 index 685b6bebc..000000000 --- a/blockchain-configs/testnet-dev/timer_flags.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "allow_lower_case_app_names_only": { - "enabled_block": 2, - "has_bandage": true - }, - "create_app_config_sanitization": { - "enabled_block": 2, - "has_bandage": true - }, - "native_service_path_length_check": { - "enabled_block": 2, - "has_bandage": true - }, - "update_max_state_tree_size_per_byte": { - "enabled_block": 2, - "has_bandage": true - }, - "staking_balance_total_sum": { - "enabled_block": 2, - "has_bandage": true - }, - "update_max_state_tree_size_per_byte2": { - "enabled_block": 2, - "has_bandage": true - }, - "update_bandwidth_budget_params": { - "enabled_block": 2, - "has_bandage": true - }, - "disable_tx_receipt_recording": { - "enabled_block": 2, - "has_bandage": false - }, - "extend_account_registration_gas_amount": { - "enabled_block": 2, - "has_bandage": false - }, - "add_app_creation_gas_amount": { - "enabled_block": 2, - "has_bandage": true - }, - "allow_non_negative_transfer_value_only": { - "enabled_block": 2, - "has_bandage": true - }, - "update_min_gc_num_siblings_deleted": { - "enabled_block": 2, - "has_bandage": true - } -} diff --git a/blockchain-configs/testnet-prod/timer_flags.json b/blockchain-configs/testnet-prod/timer_flags.json index 1d4aa38dd..dcbf38c4a 100644 --- a/blockchain-configs/testnet-prod/timer_flags.json +++ b/blockchain-configs/testnet-prod/timer_flags.json @@ -50,5 +50,13 @@ "update_min_gc_num_siblings_deleted": { "enabled_block": 1411300, "has_bandage": true + }, + "update_min_gc_num_siblings_deleted2": { + "enabled_block": 2429500, + "has_bandage": true + }, + "tweak_transfer_gc_rule": { + "enabled_block": 2429500, + "has_bandage": true } } diff --git a/client/index.js b/client/index.js index 29bb701c4..26d6a5e55 100755 --- a/client/index.js +++ b/client/index.js @@ -673,7 +673,8 @@ app.get('/get_config', (req, res) => { /** * Dev Client SET APIs (available to whitelisted IPs, if ENABLE_DEV_CLIENT_SET_API == true) */ - +// TODO(platfowner): Deprecate Dev Client SET APIs once the related test cases are migrated to +// json rpc APIs. if (NodeConfigs.ENABLE_DEV_CLIENT_SET_API) { app.post('/set_value', (req, res, next) => { const beginTime = Date.now(); diff --git a/client/protocol_versions.json b/client/protocol_versions.json index b6f87becc..6560b37d6 100644 --- a/client/protocol_versions.json +++ b/client/protocol_versions.json @@ -122,5 +122,8 @@ }, "1.0.15": { "min": "1.0.0" + }, + "1.1.0": { + "min": "1.0.0" } } \ No newline at end of file diff --git a/common/common-util.js b/common/common-util.js index 27b874fd2..df816c836 100644 --- a/common/common-util.js +++ b/common/common-util.js @@ -551,7 +551,7 @@ class CommonUtil { // TODO(platfowner): Consider some code (e.g. IN_LOCKUP_PERIOD, INSUFFICIENT_BALANCE) no failure // so that their transactions are not reverted. static isFailedFuncResultCode(code) { - return code !== FunctionResultCode.SUCCESS; + return code !== FunctionResultCode.SUCCESS && code !== FunctionResultCode.SKIP; } static isAppPath(parsedPath) { @@ -1057,7 +1057,9 @@ class CommonUtil { static createTimerFlagEnabledBandageMap(timerFlags) { const LOG_HEADER = 'createTimerFlagEnabledBandageMap'; const map = new Map(); - console.log(`[${LOG_HEADER}] Registering bandage files:`); + if (process.env.LOG_BANDAGE_INFO) { + console.log(`[${LOG_HEADER}] Registering bandage files:`); + } const flagNameList = Object.keys(timerFlags); for (let i = 0; i < flagNameList.length; i++) { const flagName = flagNameList[i]; @@ -1065,7 +1067,9 @@ class CommonUtil { const enabledBlockNumber = CommonUtil.getEnabledBlockNumberFromTimerFlag(flag); if (CommonUtil.isNumber(enabledBlockNumber) && flag['has_bandage'] === true) { const bandageFilePath = path.resolve(__dirname, '../db/bandage-files', `${flagName}.js`); - console.log(`[${LOG_HEADER}] [${i}] Registering ${bandageFilePath}`); + if (process.env.LOG_BANDAGE_INFO) { + console.log(`[${LOG_HEADER}] [${i}] Registering ${bandageFilePath}`); + } if (!fs.existsSync(bandageFilePath)) { throw Error(`Missing bandage file: ${bandageFilePath}`); } @@ -1074,7 +1078,9 @@ class CommonUtil { } map.get(enabledBlockNumber).push(flagName); } else { - console.log(`[${LOG_HEADER}] [${i}] Skipping for timer flag: ${flagName}`); + if (process.env.LOG_BANDAGE_INFO) { + console.log(`[${LOG_HEADER}] [${i}] Skipping for timer flag: ${flagName}`); + } } } return map; diff --git a/common/constants.js b/common/constants.js index 163e47571..a4106c79a 100644 --- a/common/constants.js +++ b/common/constants.js @@ -116,17 +116,18 @@ function setNodeConfigs() { NodeConfigs[param] = valFromNodeParams; } } - if (!fs.existsSync(NodeConfigs.BLOCKCHAIN_DATA_DIR)) { + const blockchainDataDirPath = path.resolve(__dirname, '..', NodeConfigs.BLOCKCHAIN_DATA_DIR); + if (!fs.existsSync(blockchainDataDirPath)) { try { - fs.mkdirSync(NodeConfigs.BLOCKCHAIN_DATA_DIR, { recursive: true }); + fs.mkdirSync(blockchainDataDirPath, { recursive: true }); } catch (e) { console.log(e) } } - NodeConfigs.LOGS_DIR = path.resolve(NodeConfigs.BLOCKCHAIN_DATA_DIR, 'logs'); - NodeConfigs.CHAINS_DIR = path.resolve(NodeConfigs.BLOCKCHAIN_DATA_DIR, 'chains'); - NodeConfigs.SNAPSHOTS_ROOT_DIR = path.resolve(NodeConfigs.BLOCKCHAIN_DATA_DIR, 'snapshots'); - NodeConfigs.KEYS_ROOT_DIR = path.resolve(NodeConfigs.BLOCKCHAIN_DATA_DIR, 'keys'); + NodeConfigs.LOGS_DIR = path.resolve(blockchainDataDirPath, 'logs'); + NodeConfigs.CHAINS_DIR = path.resolve(blockchainDataDirPath, 'chains'); + NodeConfigs.SNAPSHOTS_ROOT_DIR = path.resolve(blockchainDataDirPath, 'snapshots'); + NodeConfigs.KEYS_ROOT_DIR = path.resolve(blockchainDataDirPath, 'keys'); } setNodeConfigs(); diff --git a/common/result-code.js b/common/result-code.js index 14ad24d4d..5c28ba27c 100644 --- a/common/result-code.js +++ b/common/result-code.js @@ -126,6 +126,7 @@ const FailedTxPrecheckCodeSet = new Set([ // If they are altered and deployed, the full sync of the blockchain nodes can fail. const FunctionResultCode = { SUCCESS: 0, + SKIP: 20000, // Normal skip FAILURE: 20001, // Normal failure INTERNAL_ERROR: 20002, // Something went wrong but don't know why // Transfer diff --git a/db/bandage-files/allow_non_negative_transfer_value_only.js b/db/bandage-files/allow_non_negative_transfer_value_only.js index 16944074d..916ed76ec 100644 --- a/db/bandage-files/allow_non_negative_transfer_value_only.js +++ b/db/bandage-files/allow_non_negative_transfer_value_only.js @@ -7,7 +7,7 @@ module.exports = { "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" } }, - // From allow_lower_case_app_names_only bandage file. + // From allow_lower_case_app_names_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) && util.getBalance($from, getValue) >= newData" diff --git a/db/bandage-files/tweak_transfer_gc_rule.js b/db/bandage-files/tweak_transfer_gc_rule.js new file mode 100644 index 000000000..82fb4c5a6 --- /dev/null +++ b/db/bandage-files/tweak_transfer_gc_rule.js @@ -0,0 +1,19 @@ +module.exports = { + data: [ + { + path: ['rules', 'transfer', '$from', '$to', '$key', '.rule' ], + value: { + "state": { + "gc_max_siblings": 10, + "gc_num_siblings_deleted": 10 + } + }, + prevValue: { + "state": { + "gc_max_siblings": 200, + "gc_num_siblings_deleted": 100 + } + } + } + ] +}; diff --git a/db/bandage-files/update_min_gc_num_siblings_deleted2.js b/db/bandage-files/update_min_gc_num_siblings_deleted2.js new file mode 100644 index 000000000..146cb94eb --- /dev/null +++ b/db/bandage-files/update_min_gc_num_siblings_deleted2.js @@ -0,0 +1,10 @@ +module.exports = { + data: [ + { + path: ['values', 'blockchain_params', 'resource', 'min_gc_num_siblings_deleted'], + value: 10, + // From update_min_gc_num_siblings_deleted.js bandage file. + prevValue: 20 + } + ] +}; diff --git a/db/functions.js b/db/functions.js index 7f2fde8cb..8306c738e 100644 --- a/db/functions.js +++ b/db/functions.js @@ -237,9 +237,7 @@ class Functions { } } else if (functionEntry.function_type === FunctionTypes.REST) { // NOTE: Skipped when the event source is null. - if (NodeConfigs.ENABLE_REST_FUNCTION_CALL && - eventSource !== null && - functionEntry.function_url) { + if (functionEntry.function_url) { const restFunctionUrlWhitelist = this.db.getRestFunctionsUrlWhitelist(); if (!CommonUtil.isWhitelistedUrl(functionEntry.function_url, restFunctionUrlWhitelist)) { // NOTE: Skipped when the function url is not in the whitelist. @@ -255,43 +253,51 @@ class Functions { `function_url '${functionEntry.function_url}' with:\n` + formattedParams); } - const newAuth = Object.assign( - {}, auth, { fid: functionEntry.function_id, fids: this.getFids() }); - promises.push(axios.post(functionEntry.function_url, { - fid: functionEntry.function_id, - function: functionEntry, - valuePath, - functionPath, - value, - prevValue, - params, - timestamp, - executedAt, - transaction, - blockNumber, - blockTime, - options, - eventSource, - auth: newAuth, - chainId: blockchainParams.chainId, - networkId: blockchainParams.networkId, - }, { - timeout: NodeConfigs.REST_FUNCTION_CALL_TIMEOUT_MS - }).catch((error) => { - if (DevFlags.enableRichFunctionLogging) { - logger.error( - `Failed to trigger REST function [[ ${functionEntry.function_id} ]] of ` + - `function_url '${functionEntry.function_url}' with error: \n` + - `${JSON.stringify(error)}` + - formattedParams); - } - failCount++; - return true; - })); - funcResults[functionEntry.function_id] = { - code: FunctionResultCode.SUCCESS, - bandwidth_gas_amount: blockchainParams.restFunctionCallGasAmount, - }; + if (NodeConfigs.ENABLE_REST_FUNCTION_CALL && eventSource !== null) { + const newAuth = Object.assign( + {}, auth, { fid: functionEntry.function_id, fids: this.getFids() }); + promises.push(axios.post(functionEntry.function_url, { + fid: functionEntry.function_id, + function: functionEntry, + valuePath, + functionPath, + value, + prevValue, + params, + timestamp, + executedAt, + transaction, + blockNumber, + blockTime, + options, + eventSource, + auth: newAuth, + chainId: blockchainParams.chainId, + networkId: blockchainParams.networkId, + }, { + timeout: NodeConfigs.REST_FUNCTION_CALL_TIMEOUT_MS + }).catch((error) => { + if (DevFlags.enableRichFunctionLogging) { + logger.error( + `Failed to trigger REST function [[ ${functionEntry.function_id} ]] of ` + + `function_url '${functionEntry.function_url}' with error: \n` + + `${JSON.stringify(error)}` + + formattedParams); + } + failCount++; + return true; + })); + funcResults[functionEntry.function_id] = { + code: FunctionResultCode.SUCCESS, + bandwidth_gas_amount: blockchainParams.restFunctionCallGasAmount, + }; + } else { + // Rest function trigger is skipped. + funcResults[functionEntry.function_id] = { + code: FunctionResultCode.SKIP, + bandwidth_gas_amount: blockchainParams.restFunctionCallGasAmount, + }; + } triggerCount++; } } diff --git a/db/index.js b/db/index.js index 16f0dbdf7..29983cb65 100644 --- a/db/index.js +++ b/db/index.js @@ -1698,8 +1698,10 @@ class DB { } } - collectFee(auth, tx, timestamp, blockNumber, blockTime, executionResult, eventSource) { - const gasPriceUnit = DB.getBlockchainParam('resource/gas_price_unit', blockNumber, this.stateRoot); + collectFee( + auth, tx, timestamp, blockNumber, blockTime, executionResult, eventSource, isDryrun) { + const gasPriceUnit = + DB.getBlockchainParam('resource/gas_price_unit', blockNumber, this.stateRoot); const gasPrice = tx.tx_body.gas_price; // Use only the service gas amount total const serviceBandwidthGasAmount = _.get(tx, 'extra.gas.bandwidth.service', 0); @@ -1735,7 +1737,9 @@ class DB { executionResult.gas_amount_charged = gasAmountChargedByTransfer; executionResult.gas_cost_total = CommonUtil.getTotalGasCost(gasPrice, executionResult.gas_amount_charged, gasPriceUnit); - if (executionResult.gas_cost_total <= 0) return; + if (isDryrun || executionResult.gas_cost_total <= 0) { + return; + } const gasFeeCollectPath = PathUtil.getGasFeeCollectPath(blockNumber, billedTo, tx.hash); const newOptions = { timestamp, @@ -1948,7 +1952,9 @@ class DB { return true; } - executeTransaction(tx, skipFees = false, restoreIfFails = false, blockNumber = 0, blockTime = null, eventSource = null) { + executeTransaction( + tx, skipFees = false, restoreIfFails = false, blockNumber = 0, blockTime = null, + eventSource = null, isDryrun = false) { const LOG_HEADER = 'executeTransaction'; const precheckResult = this.precheckTransaction(tx, skipFees, blockNumber); @@ -1956,7 +1962,7 @@ class DB { logger.debug(`[${LOG_HEADER}] Pre-check failed`); return precheckResult; } - if (restoreIfFails) { + if (restoreIfFails || isDryrun) { if (!this.backupDb()) { return CommonUtil.logAndReturnTxResult( logger, @@ -1971,24 +1977,29 @@ class DB { const auth = { addr: tx.address }; const nonce = txBody.nonce; const timestamp = txBody.timestamp; - const executionResult = - this.executeOperation(txBody.operation, auth, nonce, timestamp, tx, blockNumber, blockTime, eventSource); - if (CommonUtil.isFailedTx(executionResult)) { - if (restoreIfFails) { - this.restoreDb(); - } else { - this.deleteBackupStateVersion(); - return executionResult; - } + const executionResult = this.executeOperation( + txBody.operation, auth, nonce, timestamp, tx, blockNumber, blockTime, eventSource); + if (isDryrun && executionResult) { + executionResult.is_dryrun = true; // Set is_dryrun = true } if (!skipFees) { if (DevFlags.enableGasFeeCollection) { - this.collectFee(auth, tx, timestamp, blockNumber, blockTime, executionResult, eventSource); + this.collectFee( + auth, tx, timestamp, blockNumber, blockTime, executionResult, eventSource, isDryrun); } if (!isEnabledTimerFlag('disable_tx_receipt_recording', blockNumber)) { this.recordReceipt(auth, tx, blockNumber, executionResult); } } + if (isDryrun) { + this.restoreDb(); + } else if (restoreIfFails) { + if (CommonUtil.isFailedTx(executionResult)) { + this.restoreDb(); + } else { + this.deleteBackupStateVersion(); + } + } return executionResult; } diff --git a/deploy_blockchain_genesis_gcp.sh b/deploy_blockchain_genesis_gcp.sh index dc3930277..3edd7d0ae 100644 --- a/deploy_blockchain_genesis_gcp.sh +++ b/deploy_blockchain_genesis_gcp.sh @@ -33,7 +33,7 @@ GCP_USER="runner" printf "GCP_USER=$GCP_USER\n" number_re='^[0-9]+$' -if ! [[ $2 =~ $number_re ]] ; then +if [[ ! $2 =~ $number_re ]] ; then printf "Invalid <# of Shards> argument: $2\n" exit fi @@ -140,11 +140,19 @@ if [[ "$SEASON" = "mainnet" ]]; then printf "\n" printf "Do you want to proceed for $SEASON? Enter [mainnet]: " read CONFIRM - printf "\n\n" + printf "\n" if [[ ! $CONFIRM = "mainnet" ]] then [[ "$0" = "$BASH_SOURCE" ]] && exit 1 || return 1 # handle exits from shell or function but don't exit interactive shell fi +elif [[ "$SEASON" = "spring" ]] || [[ "$SEASON" = "summer" ]]; then + printf "\n" + printf "Do you want to proceed for $SEASON? Enter [testnet]: " + read CONFIRM + printf "\n" + if [[ ! $CONFIRM = "testnet" ]]; then + [[ "$0" = "$BASH_SOURCE" ]] && exit 1 || return 1 # handle exits from shell or function but don't exit interactive shell + fi else printf "\n" read -p "Do you want to proceed for $SEASON? [y/N]: " -n 1 -r @@ -154,23 +162,25 @@ else fi fi -# Read node ip addresses -IFS=$'\n' read -d '' -r -a IP_ADDR_LIST < ./ip_addresses/$SEASON.txt -if [[ "$ACCOUNT_INJECTION_OPTION" = "--keystore" ]]; then - # Get keystore password - printf "Enter password: " - read -s PASSWORD - printf "\n\n" - - if [[ "$SEASON" = "mainnet" ]]; then - KEYSTORE_DIR="mainnet_prod_keys" - elif [[ "$SEASON" = "spring" ]] || [[ "$SEASON" = "summer" ]]; then - KEYSTORE_DIR="testnet_prod_keys" - else - KEYSTORE_DIR="testnet_dev_staging_keys" +if [[ ! $KILL_OPTION = '--kill-only' ]]; then + # Read node ip addresses + IFS=$'\n' read -d '' -r -a IP_ADDR_LIST < ./ip_addresses/$SEASON.txt + if [[ "$ACCOUNT_INJECTION_OPTION" = "--keystore" ]]; then + # Get keystore password + printf "Enter password: " + read -s PASSWORD + printf "\n\n" + + if [[ "$SEASON" = "mainnet" ]]; then + KEYSTORE_DIR="mainnet_prod_keys" + elif [[ "$SEASON" = "spring" ]] || [[ "$SEASON" = "summer" ]]; then + KEYSTORE_DIR="testnet_prod_keys" + else + KEYSTORE_DIR="testnet_dev_staging_keys" + fi + elif [[ "$ACCOUNT_INJECTION_OPTION" = "--mnemonic" ]]; then + IFS=$'\n' read -d '' -r -a MNEMONIC_LIST < ./testnet_mnemonics/$SEASON.txt fi -elif [[ "$ACCOUNT_INJECTION_OPTION" = "--mnemonic" ]]; then - IFS=$'\n' read -d '' -r -a MNEMONIC_LIST < ./testnet_mnemonics/$SEASON.txt fi function inject_account() { @@ -314,7 +324,7 @@ if [[ $KILL_OPTION = "--skip-kill" ]]; then printf "\nSkipping process kill...\n" else # kill any processes still alive - printf "\nKilling all tracker and blockchain node jobs...\n" + printf "\nKilling tracker / blockchain node jobs...\n" # Tracker server is killed with PARENT_NODE_INDEX_BEGIN = -1 if [[ $PARENT_NODE_INDEX_BEGIN = -1 ]]; then diff --git a/deploy_blockchain_incremental_gcp.sh b/deploy_blockchain_incremental_gcp.sh index d29ad593c..3a3c32efd 100644 --- a/deploy_blockchain_incremental_gcp.sh +++ b/deploy_blockchain_incremental_gcp.sh @@ -33,7 +33,7 @@ GCP_USER="runner" printf "GCP_USER=$GCP_USER\n" number_re='^[0-9]+$' -if ! [[ $2 =~ $number_re ]] ; then +if [[ ! $2 =~ $number_re ]] ; then printf "Invalid <# of Shards> argument: $2\n" exit fi @@ -126,11 +126,19 @@ if [[ "$SEASON" = "mainnet" ]]; then printf "\n" printf "Do you want to proceed for $SEASON? Enter [mainnet]: " read CONFIRM - printf "\n\n" + printf "\n" if [[ ! $CONFIRM = "mainnet" ]] then [[ "$0" = "$BASH_SOURCE" ]] && exit 1 || return 1 # handle exits from shell or function but don't exit interactive shell fi +elif [[ "$SEASON" = "spring" ]] || [[ "$SEASON" = "summer" ]]; then + printf "\n" + printf "Do you want to proceed for $SEASON? Enter [testnet]: " + read CONFIRM + printf "\n" + if [[ ! $CONFIRM = "testnet" ]]; then + [[ "$0" = "$BASH_SOURCE" ]] && exit 1 || return 1 # handle exits from shell or function but don't exit interactive shell + fi else printf "\n" read -p "Do you want to proceed for $SEASON? [y/N]: " -n 1 -r diff --git a/json_rpc/constants.js b/json_rpc/constants.js index 73e8fd99b..cf193b800 100644 --- a/json_rpc/constants.js +++ b/json_rpc/constants.js @@ -42,6 +42,7 @@ const JSON_RPC_METHODS = { AIN_MATCH_OWNER: 'ain_matchOwner', AIN_MATCH_RULE: 'ain_matchRule', AIN_REMOVE_FROM_WHITELIST_NODE_PARAM: 'ain_removeFromWhitelistNodeParam', + AIN_SEND_SIGNED_TRANSACTION_DRYRUN: 'ain_sendSignedTransactionDryrun', AIN_SEND_SIGNED_TRANSACTION: 'ain_sendSignedTransaction', AIN_SEND_SIGNED_TRANSACTION_BATCH: 'ain_sendSignedTransactionBatch', AIN_SET_NODE_PARAM: 'ain_setNodeParam', diff --git a/json_rpc/transaction.js b/json_rpc/transaction.js index e13177f3b..ac8b61294 100644 --- a/json_rpc/transaction.js +++ b/json_rpc/transaction.js @@ -10,6 +10,55 @@ const CommonUtil = require('../common/common-util'); const Transaction = require('../tx-pool/transaction'); const { JSON_RPC_METHODS } = require('./constants'); +function sendTransactionOnNode(node, p2pServer, args, done, isDryrun) { + const beginTime = Date.now(); + const txBytesLimit = node.getBlockchainParam('resource/tx_bytes_limit'); + if (sizeof(args) > txBytesLimit) { + const latency = Date.now() - beginTime; + trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_SET, latency); + done(null, JsonRpcUtil.addProtocolVersion({ + result: null, + code: JsonRpcApiResultCode.TX_EXCEEDS_SIZE_LIMIT, + message: `Transaction size exceeds its limit: ${txBytesLimit} bytes.` + })); + } else if (!args.tx_body || !args.signature) { + const latency = Date.now() - beginTime; + trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_SET, latency); + done(null, JsonRpcUtil.addProtocolVersion({ + result: null, + code: JsonRpcApiResultCode.TX_MISSING_PROPERTIES, + message: 'Missing properties.' + })); + } else { + const chainId = node.getBlockchainParam('genesis/chain_id'); + const createdTx = Transaction.create(args.tx_body, args.signature, chainId); + if (!createdTx) { + const latency = Date.now() - beginTime; + trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_SET, latency); + done(null, JsonRpcUtil.addProtocolVersion({ + result: null, + code: JsonRpcApiResultCode.TX_INVALID_FORMAT, + message: 'Invalid transaction format.' + })); + } else { + if (!NodeConfigs.LIGHTWEIGHT && + NodeConfigs.ENABLE_EARLY_TX_SIG_VERIF && + !Transaction.verifyTransaction(createdTx, chainId)) { + done(null, JsonRpcUtil.addProtocolVersion({ + result: null, + code: JsonRpcApiResultCode.TX_INVALID_SIGNATURE, + message: 'Invalid transaction signature.' + })); + } else { + const result = p2pServer.executeAndBroadcastTransaction(createdTx, isDryrun); + const latency = Date.now() - beginTime; + trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_SET, latency); + done(null, JsonRpcUtil.addProtocolVersion({ result })); + } + } + } +} + module.exports = function getTransactionApis(node, p2pServer) { return { [JSON_RPC_METHODS.AIN_GET_PENDING_TRANSACTIONS]: function(args, done) { @@ -75,53 +124,12 @@ module.exports = function getTransactionApis(node, p2pServer) { done(null, JsonRpcUtil.addProtocolVersion({ result })); }, + [JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION_DRYRUN]: function(args, done) { + sendTransactionOnNode(node, p2pServer, args, done, true); + }, + [JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION]: function(args, done) { - const beginTime = Date.now(); - const txBytesLimit = node.getBlockchainParam('resource/tx_bytes_limit'); - if (sizeof(args) > txBytesLimit) { - const latency = Date.now() - beginTime; - trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_SET, latency); - done(null, JsonRpcUtil.addProtocolVersion({ - result: null, - code: JsonRpcApiResultCode.TX_EXCEEDS_SIZE_LIMIT, - message: `Transaction size exceeds its limit: ${txBytesLimit} bytes.` - })); - } else if (!args.tx_body || !args.signature) { - const latency = Date.now() - beginTime; - trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_SET, latency); - done(null, JsonRpcUtil.addProtocolVersion({ - result: null, - code: JsonRpcApiResultCode.TX_MISSING_PROPERTIES, - message: 'Missing properties.' - })); - } else { - const chainId = node.getBlockchainParam('genesis/chain_id'); - const createdTx = Transaction.create(args.tx_body, args.signature, chainId); - if (!createdTx) { - const latency = Date.now() - beginTime; - trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_SET, latency); - done(null, JsonRpcUtil.addProtocolVersion({ - result: null, - code: JsonRpcApiResultCode.TX_INVALID_FORMAT, - message: 'Invalid transaction format.' - })); - } else { - if (!NodeConfigs.LIGHTWEIGHT && - NodeConfigs.ENABLE_EARLY_TX_SIG_VERIF && - !Transaction.verifyTransaction(createdTx, chainId)) { - done(null, JsonRpcUtil.addProtocolVersion({ - result: null, - code: JsonRpcApiResultCode.TX_INVALID_SIGNATURE, - message: 'Invalid transaction signature.' - })); - } else { - const result = p2pServer.executeAndBroadcastTransaction(createdTx); - const latency = Date.now() - beginTime; - trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_SET, latency); - done(null, JsonRpcUtil.addProtocolVersion({ result })); - } - } - } + sendTransactionOnNode(node, p2pServer, args, done, false); }, [JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION_BATCH]: function(args, done) { @@ -191,7 +199,7 @@ module.exports = function getTransactionApis(node, p2pServer) { } txList.push(createdTx); } - const result = p2pServer.executeAndBroadcastTransaction({tx_list: txList}); + const result = p2pServer.executeAndBroadcastTransaction({ tx_list: txList }); const latency = Date.now() - beginTime; trafficStatsManager.addEvent(TrafficEventTypes.JSON_RPC_SET, latency); done(null, JsonRpcUtil.addProtocolVersion({ result })); diff --git a/logger/index.js b/logger/index.js index c2a1c31df..d77894edc 100644 --- a/logger/index.js +++ b/logger/index.js @@ -23,7 +23,6 @@ if (DevFlags.enableWinstonLogger) { }); } - global.isFinished = false; class Logger { @@ -36,7 +35,7 @@ class Logger { try { logger.error(`[${this.prefix}] ${text}`); } catch (e) { - console.error(e); + console.log(e); } } } @@ -56,7 +55,7 @@ class Logger { try { logger.debug(`[${this.prefix}] ${text}`); } catch (e) { - console.debug(e); + console.log(e); } } } diff --git a/node/index.js b/node/index.js index 5312d24c1..a46a4c738 100644 --- a/node/index.js +++ b/node/index.js @@ -729,7 +729,7 @@ class BlockchainNode { * Executes a transaction and add it to the transaction pool if the execution was successful. * @param {Object} tx transaction */ - executeTransactionAndAddToPool(tx) { + executeTransactionAndAddToPool(tx, isDryrun = false) { const LOG_HEADER = 'executeTransactionAndAddToPool'; if (DevFlags.enableRichTransactionLogging) { logger.info(`[${LOG_HEADER}] EXECUTING TRANSACTION: ${JSON.stringify(tx, null, 2)}`); @@ -792,8 +792,10 @@ class BlockchainNode { `[${LOG_HEADER}] Tx pool does NOT have enough free room ` + `(${perAccountFreePoolSize}) for account: ${executableTx.address}`); } + const eventSource = isDryrun ? null : ValueChangedEventSources.USER; const result = this.db.executeTransaction( - executableTx, false, true, this.bc.lastBlockNumber() + 1, this.bc.lastBlockTimestamp(), ValueChangedEventSources.USER); + executableTx, false, true, this.bc.lastBlockNumber() + 1, this.bc.lastBlockTimestamp(), + eventSource, isDryrun); if (CommonUtil.isFailedTx(result)) { if (DevFlags.enableRichTransactionLogging) { logger.error( @@ -803,11 +805,13 @@ class BlockchainNode { // NOTE(liayoo): Transactions that don't pass the pre-checks will be rejected instantly and // will not be included in the tx pool, as they can't be included in a block // anyway, and may be used for attacks on blockchain nodes. - if (!CommonUtil.txPrecheckFailed(result)) { + if (!isDryrun && !CommonUtil.txPrecheckFailed(result)) { this.tp.addTransaction(executableTx); } } else { - this.tp.addTransaction(executableTx, true); + if (!isDryrun) { + this.tp.addTransaction(executableTx, true); + } } return result; diff --git a/p2p/index.js b/p2p/index.js index c56126dc8..7702049b7 100644 --- a/p2p/index.js +++ b/p2p/index.js @@ -616,7 +616,8 @@ class P2pClient { } const socket = this.assignRandomPeerForChainSync(); if (!socket) { - logger.error(`[${LOG_HEADER}] Failed to get a peer for CHAIN_SEGMENT_REQUEST`); + // NOTE(platfowner): This often occurs in the early stages of start_local_blockchain.sh. + logger.info(`[${LOG_HEADER}] Failed to get a peer for CHAIN_SEGMENT_REQUEST`); return; } const lastBlockNumber = this.server.node.bc.lastBlockNumber(); diff --git a/p2p/server.js b/p2p/server.js index 8eaad1f0e..e58b97211 100644 --- a/p2p/server.js +++ b/p2p/server.js @@ -605,7 +605,7 @@ class P2pServer { newTxList.push(createdTx); } if (newTxList.length > 0) { - this.executeAndBroadcastTransaction({ tx_list: newTxList }, txTags); + this.executeAndBroadcastTransaction({ tx_list: newTxList }, false, txTags); } } else { const createdTx = Transaction.create(tx.tx_body, tx.signature, chainId); @@ -618,7 +618,7 @@ class P2pServer { logger.info(`[${LOG_HEADER}] Invalid signature of tx: ` + `${JSON.stringify(tx, null, 2)}`); } else { - this.executeAndBroadcastTransaction(createdTx, txTags); + this.executeAndBroadcastTransaction(createdTx, false, txTags); } } break; @@ -790,7 +790,7 @@ class P2pServer { socket.send(JSON.stringify(payload)); } - executeAndBroadcastTransaction(tx, tags = []) { + executeAndBroadcastTransaction(tx, isDryrun = false, tags = []) { const LOG_HEADER = 'executeAndBroadcastTransaction'; if (!tx) { return { @@ -818,7 +818,7 @@ class P2pServer { continue; } - const result = this.node.executeTransactionAndAddToPool(subTx); + const result = this.node.executeTransactionAndAddToPool(subTx, isDryrun); resultList.push({ tx_hash: subTx.hash, result @@ -828,15 +828,15 @@ class P2pServer { } } logger.debug(`\n BATCH TX RESULT: ` + JSON.stringify(resultList)); - if (txListSucceeded.length > 0) { + if (!isDryrun && txListSucceeded.length > 0) { this.client.broadcastTransaction({ tx_list: txListSucceeded }, tags); } return resultList; } else { - const result = this.node.executeTransactionAndAddToPool(tx); + const result = this.node.executeTransactionAndAddToPool(tx, isDryrun); logger.debug(`\n TX RESULT: ` + JSON.stringify(result)); - if (!CommonUtil.isFailedTx(result)) { + if (!isDryrun && !CommonUtil.isFailedTx(result)) { this.client.broadcastTransaction(tx, tags); } diff --git a/package.json b/package.json index 301216573..e53d17fd1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ain-blockchain", "description": "AI Network Blockchain", - "version": "1.0.15", + "version": "1.1.0", "private": true, "license": "MIT", "author": "dev@ainetwork.ai", diff --git a/start_local_blockchain.sh b/start_local_blockchain.sh index 586bd4c40..df5efc6e1 100755 --- a/start_local_blockchain.sh +++ b/start_local_blockchain.sh @@ -12,7 +12,7 @@ printf "\n[[[[[ start_local_blockchain.sh ]]]]]\n\n" printf "\nStarting tracker..\n" PORT=8080 \ P2P_PORT=5000 \ - CONSOLE_LOG=true \ + CONSOLE_LOG=false \ node ./tracker-server/index.js & printf "\nDone\n\n" sleep 5 @@ -23,6 +23,7 @@ UNSAFE_PRIVATE_KEY=b22c95ffc4a5c096f7d7d0487ba963ce6ac945bdc91c79b64ce209de289be P2P_PORT=5001 \ STAKE=100000 \ CONSOLE_LOG=true \ + LOG_BANDAGE_INFO=true \ ENABLE_REST_FUNCTION_CALL=true \ ENABLE_TX_SIG_VERIF_WORKAROUND=true \ ENABLE_GAS_FEE_WORKAROUND=true \ @@ -38,6 +39,7 @@ UNSAFE_PRIVATE_KEY=921cc48e48c876fc6ed1eb02a76ad520e8d16a91487f9c7e03441da8e35a0 P2P_PORT=5002 \ STAKE=100000 \ CONSOLE_LOG=true \ + LOG_BANDAGE_INFO=true \ ENABLE_REST_FUNCTION_CALL=true \ ENABLE_TX_SIG_VERIF_WORKAROUND=true \ ENABLE_GAS_FEE_WORKAROUND=true \ @@ -53,6 +55,7 @@ UNSAFE_PRIVATE_KEY=41e6e5718188ce9afd25e4b386482ac2c5272c49a622d8d217887bce21dce P2P_PORT=5003 \ STAKE=100000 \ CONSOLE_LOG=true \ + LOG_BANDAGE_INFO=true \ ENABLE_REST_FUNCTION_CALL=true \ ENABLE_TX_SIG_VERIF_WORKAROUND=true \ ENABLE_GAS_FEE_WORKAROUND=true \ diff --git a/start_local_blockchain_afan_shard.sh b/start_local_blockchain_afan_shard.sh index 18c635013..449d8bbe0 100755 --- a/start_local_blockchain_afan_shard.sh +++ b/start_local_blockchain_afan_shard.sh @@ -10,7 +10,7 @@ printf "\n[[[[[ start_local_blockchain_afan_shard.sh ]]]]]\n\n" printf "\nStarting parent tracker..\n" PORT=8080 \ P2P_PORT=5000 \ - CONSOLE_LOG=true \ + CONSOLE_LOG=false \ node ./tracker-server/index.js & printf "\nDone\n\n" sleep 5 @@ -20,6 +20,7 @@ UNSAFE_PRIVATE_KEY=b22c95ffc4a5c096f7d7d0487ba963ce6ac945bdc91c79b64ce209de289be PORT=8081 \ P2P_PORT=5001 \ CONSOLE_LOG=true \ + LOG_BANDAGE_INFO=true \ ENABLE_REST_FUNCTION_CALL=true \ ENABLE_TX_SIG_VERIF_WORKAROUND=true \ ENABLE_GAS_FEE_WORKAROUND=true \ @@ -33,6 +34,7 @@ UNSAFE_PRIVATE_KEY=921cc48e48c876fc6ed1eb02a76ad520e8d16a91487f9c7e03441da8e35a0 PORT=8082 \ P2P_PORT=5002 \ CONSOLE_LOG=true \ + LOG_BANDAGE_INFO=true \ ENABLE_REST_FUNCTION_CALL=true \ ENABLE_TX_SIG_VERIF_WORKAROUND=true \ ENABLE_GAS_FEE_WORKAROUND=true \ @@ -46,6 +48,7 @@ UNSAFE_PRIVATE_KEY=41e6e5718188ce9afd25e4b386482ac2c5272c49a622d8d217887bce21dce PORT=8083 \ P2P_PORT=5003 \ CONSOLE_LOG=true \ + LOG_BANDAGE_INFO=true \ ENABLE_REST_FUNCTION_CALL=true \ ENABLE_TX_SIG_VERIF_WORKAROUND=true \ ENABLE_GAS_FEE_WORKAROUND=true \ @@ -60,7 +63,7 @@ printf "\nStarting afan child chain tracker..\n" BLOCKCHAIN_CONFIGS_DIR=blockchain-configs/afan-shard \ PORT=9000 \ P2P_PORT=6000 \ - CONSOLE_LOG=true \ + CONSOLE_LOG=false \ node ./tracker-server/index.js & printf "\nDone\n\n" sleep 5 @@ -71,6 +74,7 @@ BLOCKCHAIN_CONFIGS_DIR=blockchain-configs/afan-shard \ P2P_PORT=6001 \ UNSAFE_PRIVATE_KEY=d8f77aa2afe2580a858a8cc97b6056e10f888c6fd07ebb58755d8422b03da816 \ CONSOLE_LOG=true \ + LOG_BANDAGE_INFO=true \ ENABLE_REST_FUNCTION_CALL=true \ ENABLE_TX_SIG_VERIF_WORKAROUND=true \ ENABLE_GAS_FEE_WORKAROUND=true \ @@ -97,6 +101,7 @@ BLOCKCHAIN_CONFIGS_DIR=blockchain-configs/afan-shard \ P2P_PORT=6002 \ UNSAFE_PRIVATE_KEY=a3409e22bc14a3d0e73697df25617b3f2eaae9b5eade77615a32abc0ad5ee0df \ CONSOLE_LOG=true \ + LOG_BANDAGE_INFO=true \ ENABLE_REST_FUNCTION_CALL=true \ ENABLE_TX_SIG_VERIF_WORKAROUND=true \ ENABLE_GAS_FEE_WORKAROUND=true \ @@ -111,6 +116,7 @@ BLOCKCHAIN_CONFIGS_DIR=blockchain-configs/afan-shard \ P2P_PORT=6003 \ UNSAFE_PRIVATE_KEY=c4611582dbb5319f08ba0907af6430a79e02b87b112aa4039d43e8765384f568 \ CONSOLE_LOG=true \ + LOG_BANDAGE_INFO=true \ ENABLE_REST_FUNCTION_CALL=true \ ENABLE_TX_SIG_VERIF_WORKAROUND=true \ ENABLE_GAS_FEE_WORKAROUND=true \ diff --git a/start_local_blockchain_multi_shards.sh b/start_local_blockchain_multi_shards.sh index 26811e043..22fd03fc7 100755 --- a/start_local_blockchain_multi_shards.sh +++ b/start_local_blockchain_multi_shards.sh @@ -11,7 +11,7 @@ printf "\n[[[[[ start_local_blockchain_multi_shards.sh ]]]]]\n\n" printf "\nStarting parent tracker..\n" PORT=8080 \ P2P_PORT=5000 \ - CONSOLE_LOG=true \ + CONSOLE_LOG=false \ node ./tracker-server/index.js & printf "\nDone\n\n" sleep 5 @@ -22,6 +22,7 @@ UNSAFE_PRIVATE_KEY=b22c95ffc4a5c096f7d7d0487ba963ce6ac945bdc91c79b64ce209de289be P2P_PORT=5001 \ STAKE=100000 \ CONSOLE_LOG=true \ + LOG_BANDAGE_INFO=true \ ENABLE_REST_FUNCTION_CALL=true \ ENABLE_TX_SIG_VERIF_WORKAROUND=true \ ENABLE_GAS_FEE_WORKAROUND=true \ @@ -36,6 +37,7 @@ UNSAFE_PRIVATE_KEY=921cc48e48c876fc6ed1eb02a76ad520e8d16a91487f9c7e03441da8e35a0 P2P_PORT=5002 \ STAKE=100000 \ CONSOLE_LOG=true \ + LOG_BANDAGE_INFO=true \ ENABLE_REST_FUNCTION_CALL=true \ ENABLE_TX_SIG_VERIF_WORKAROUND=true \ ENABLE_GAS_FEE_WORKAROUND=true \ @@ -50,6 +52,7 @@ UNSAFE_PRIVATE_KEY=41e6e5718188ce9afd25e4b386482ac2c5272c49a622d8d217887bce21dce P2P_PORT=5003 \ STAKE=100000 \ CONSOLE_LOG=true \ + LOG_BANDAGE_INFO=true \ ENABLE_REST_FUNCTION_CALL=true \ ENABLE_TX_SIG_VERIF_WORKAROUND=true \ ENABLE_GAS_FEE_WORKAROUND=true \ @@ -64,6 +67,7 @@ printf "\nStarting child chain 1 tracker..\n" BLOCKCHAIN_CONFIGS_DIR=blockchain-configs/afan-shard \ PORT=9000 \ P2P_PORT=6000 \ + CONSOLE_LOG=false \ node ./tracker-server/index.js & printf "\nDone\n\n" sleep 10 @@ -75,6 +79,7 @@ BLOCKCHAIN_CONFIGS_DIR=blockchain-configs/afan-shard \ UNSAFE_PRIVATE_KEY=d8f77aa2afe2580a858a8cc97b6056e10f888c6fd07ebb58755d8422b03da816 \ STAKE=100000 \ CONSOLE_LOG=true \ + LOG_BANDAGE_INFO=true \ ENABLE_REST_FUNCTION_CALL=true \ ENABLE_TX_SIG_VERIF_WORKAROUND=true \ ENABLE_GAS_FEE_WORKAROUND=true \ @@ -102,6 +107,7 @@ BLOCKCHAIN_CONFIGS_DIR=blockchain-configs/afan-shard \ UNSAFE_PRIVATE_KEY=a3409e22bc14a3d0e73697df25617b3f2eaae9b5eade77615a32abc0ad5ee0df \ STAKE=100000 \ CONSOLE_LOG=true \ + LOG_BANDAGE_INFO=true \ ENABLE_REST_FUNCTION_CALL=true \ ENABLE_TX_SIG_VERIF_WORKAROUND=true \ ENABLE_GAS_FEE_WORKAROUND=true \ @@ -117,6 +123,7 @@ BLOCKCHAIN_CONFIGS_DIR=blockchain-configs/afan-shard \ UNSAFE_PRIVATE_KEY=c4611582dbb5319f08ba0907af6430a79e02b87b112aa4039d43e8765384f568 \ STAKE=100000 \ CONSOLE_LOG=true \ + LOG_BANDAGE_INFO=true \ ENABLE_REST_FUNCTION_CALL=true \ ENABLE_TX_SIG_VERIF_WORKAROUND=true \ ENABLE_GAS_FEE_WORKAROUND=true \ @@ -131,6 +138,7 @@ printf "\nStarting child chain 2 tracker..\n" BLOCKCHAIN_CONFIGS_DIR=blockchain-configs/sim-shard \ PORT=9010 \ P2P_PORT=6010 \ + CONSOLE_LOG=false \ node ./tracker-server/index.js & printf "\nDone\n\n" sleep 10 @@ -142,6 +150,7 @@ BLOCKCHAIN_CONFIGS_DIR=blockchain-configs/sim-shard \ UNSAFE_PRIVATE_KEY=275dac1207d58d7015b6b55dd13432337aabbb044635e447e819172daba7a69d \ STAKE=100000 \ CONSOLE_LOG=true \ + LOG_BANDAGE_INFO=true \ ENABLE_REST_FUNCTION_CALL=true \ ENABLE_TX_SIG_VERIF_WORKAROUND=true \ ENABLE_GAS_FEE_WORKAROUND=true \ @@ -169,6 +178,7 @@ BLOCKCHAIN_CONFIGS_DIR=blockchain-configs/sim-shard \ UNSAFE_PRIVATE_KEY=0d8073dc0ee25ea154762feffa601d83191d3c8f4d7a208dd5b9ce376e414cc2 \ STAKE=100000 \ CONSOLE_LOG=true \ + LOG_BANDAGE_INFO=true \ ENABLE_REST_FUNCTION_CALL=true \ ENABLE_TX_SIG_VERIF_WORKAROUND=true \ ENABLE_GAS_FEE_WORKAROUND=true \ @@ -184,6 +194,7 @@ BLOCKCHAIN_CONFIGS_DIR=blockchain-configs/sim-shard \ UNSAFE_PRIVATE_KEY=0e9b552ee38b0bc370b509b4705bae50e289acb641d9af9960e5e7604907961b \ STAKE=100000 \ CONSOLE_LOG=true \ + LOG_BANDAGE_INFO=true \ ENABLE_REST_FUNCTION_CALL=true \ ENABLE_TX_SIG_VERIF_WORKAROUND=true \ ENABLE_GAS_FEE_WORKAROUND=true \ diff --git a/start_node_docker.sh b/start_node_docker.sh index 11e7073e2..e94b8b0b4 100644 --- a/start_node_docker.sh +++ b/start_node_docker.sh @@ -15,6 +15,8 @@ elif [[ $SEASON = 'exp' ]]; then elif [[ $SEASON = 'dev' ]]; then export BLOCKCHAIN_CONFIGS_DIR=blockchain-configs/testnet-dev fi +export LOG_BANDAGE_INFO=true +printf "LOG_BANDAGE_INFO=$LOG_BANDAGE_INFO\n" nohup node --max-old-space-size=55000 ./client/index.js 2>error_logs.txt & diff --git a/start_node_genesis_gcp.sh b/start_node_genesis_gcp.sh index 79af1791f..bc8908ec0 100644 --- a/start_node_genesis_gcp.sh +++ b/start_node_genesis_gcp.sh @@ -310,6 +310,8 @@ printf "PEER_WHITELIST=$PEER_WHITELIST\n" export STAKE=100000 printf "STAKE=$STAKE\n" +export LOG_BANDAGE_INFO=true +printf "LOG_BANDAGE_INFO=$LOG_BANDAGE_INFO\n" # uncomment and set value when necessary #export TIMER_FLAG_EARLY_APPLIED_BLOCK_NUMBER=124440 # summer #printf "TIMER_FLAG_EARLY_APPLIED_BLOCK_NUMBER=$TIMER_FLAG_EARLY_APPLIED_BLOCK_NUMBER\n" diff --git a/start_node_incremental_gcp.sh b/start_node_incremental_gcp.sh index 1c5b472a1..db8bf3a9e 100644 --- a/start_node_incremental_gcp.sh +++ b/start_node_incremental_gcp.sh @@ -369,6 +369,8 @@ printf "\n#### [Step 7] Start new node server ####\n\n" export STAKE=100000 printf "STAKE=$STAKE\n" +export LOG_BANDAGE_INFO=true +printf "LOG_BANDAGE_INFO=$LOG_BANDAGE_INFO\n" if [[ "$SEASON" = "sandbox" ]]; then MAX_OLD_SPACE_SIZE_MB=11000 diff --git a/start_tracker_genesis_gcp.sh b/start_tracker_genesis_gcp.sh index 1f1c24609..33ecb59bf 100644 --- a/start_tracker_genesis_gcp.sh +++ b/start_tracker_genesis_gcp.sh @@ -53,7 +53,6 @@ else eval $CODE_CMD fi - printf "\nStarting up Blockchain Tracker server..\n\n" START_CMD="nohup node --async-stack-traces tracker-server/index.js >/dev/null 2>error_logs.txt &" printf "\nSTART_CMD=$START_CMD\n" diff --git a/test/integration/node.test.js b/test/integration/node.test.js index fa7cfb2bb..8c324c975 100644 --- a/test/integration/node.test.js +++ b/test/integration/node.test.js @@ -3216,6 +3216,617 @@ describe('Blockchain Node', () => { }) }) + describe('json-rpc api: ain_sendSignedTransactionDryrun', () => { + const account = { + address: "0x9534bC7529961E5737a3Dd317BdEeD41AC08a52D", + private_key: "e96292ef0676287908fc3461f747f106b7b9336f183b1766f83672fbe893664d", + public_key: "1e8de35ac153fa52cb61a7e887463c205b0121be659803e9f69dddcae8dfb5a3d4c96570c5c3fafa5755b89a90eb58a2041f8da9d909b9c4b6813c3832d1254a" + }; + + // for account registration gas amount (single set) + const account2 = { + address: "0x85a620A5A46d01cc1fCF49E73ab00710d4da943E", + private_key: "b542fc2ca4a68081b3ba238888d3a8783354c3aa81711340fd69f6ff32798525", + public_key: "eb8c8577e8be18a83829c5c8a2ec2a754ef0a190e5a01139e9a24aae8f56842dfaf708da56d0f395bbfef08633237398dec96343f62ce217130d9738a76adfdf" + }; + // for account registration gas amount (multi set) + const account3 = { + address: "0x758fd59D3f8157Ae4458f8E29E2A8317be3d5974", + private_key: "63200d28b05377f983103b1ac45a379b3d424c415f8a705c7cdd6365f7e828ea", + public_key: "0760186e6d1a37107217d68e491b4a4bd89e3b6642acfcf4b320acef24d5d0de1d33bcabd2e868776879c4776937a6785e71ee963efb40c4cf09283b542006ca" + }; + // for account registration gas amount (transfer) + const account4 = { + address: "0x652a5e81Dc2B62be4b7225584A1079C29334dE27", + private_key: "98a0cc69436b5fc635184bbe16ffa97284e099e8e84c0b7ecee61b1f92db29e5", + public_key: "b6c5920098836b4ee3dd9458c706470f539e81d7370534228ffe155fff4b9af8ccdb7f6ad1eeba135c30fe4a6175ecf0d8be4bd6813a8358bc19901df47f558a" + }; + // for account registration gas amount (transfer) + const account09 = { // genesis account 09 + address: "0x09A0d53FDf1c36A131938eb379b98910e55EEfe1", + private_key: "ee0b1315d446e5318eb6eb4e9d071cd12ef42d2956d546f9acbdc3b75c469640", + public_key: "e0a9c4697a41d7ecbd7660f43c59b0df8a3e9fa31ec87687b5b4592e1ab1f66e3b2503a966ca702051f4c8e1c37c9d88cd46242750e7fc9f65dfb14980101806" + }; + + before(async () => { + const currentRule = parseOrLog(syncRequest('GET', server1 + '/get_rule?ref=/apps/test') + .body.toString('utf-8')).result[".rule"]["write"]; + const newOwners = parseOrLog(syncRequest('GET', server1 + '/get_owner?ref=/apps/test') + .body.toString('utf-8')).result[".owner"]; + const newRule = `${currentRule} || auth.addr === '${account.address}' || auth.addr === '${account2.address}' || auth.addr === '${account4.address}'`; + newOwners["owners"][account.address] = { + "branch_owner": true, + "write_owner": true, + "write_rule": true, + "write_function": true + }; + const res = parseOrLog(syncRequest('POST', server1 + '/set', {json: { + op_list: [ + // clean up old owner configs. + { + type: 'SET_OWNER', + ref: '/apps/test/test_owner/other100/path', + value: null, + }, + { + type: 'SET_OWNER', + ref: '/apps/test/test_owner/other200/path', + value: null, + }, + { + type: 'SET_OWNER', + ref: '/apps/test/test_owner/other201/path', + value: null, + }, + { + type: 'SET_OWNER', + ref: '/apps/test/test_owner/other202/path', + value: null, + }, + { + type: 'SET_OWNER', + ref: '/apps/test/test_owner/other203/path', + value: null, + }, + { + type: 'SET_RULE', + ref: '/apps/test', + value: { + ".rule": { + "write": newRule + } + } + }, + // set new owner config. + { + type: 'SET_OWNER', + ref: '/apps/test', + value: { + ".owner": newOwners + } + } + ], + timestamp: Date.now(), + nonce: -1 + }}) + .body.toString('utf-8')).result; + assert.deepEqual(CommonUtil.isFailedTx(_.get(res, 'result')), false); + if (!(await waitUntilTxFinalized(serverList, _.get(res, 'tx_hash')))) { + console.error(`Failed to check finalization of tx.`); + } + }); + + it('accepts a transaction with unordered nonce (-1)', () => { + const client = jayson.client.http(server1 + '/json-rpc'); + const txBody = { + operation: { + type: 'SET_VALUE', + ref: `/apps/test/test_value/some/path`, + value: 'some other value 1' + }, + gas_price: 0, + timestamp: Date.now(), + nonce: -1 + }; + const signature = + ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex')); + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION_DRYRUN, { + tx_body: txBody, + signature, + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION + }).then((res) => { + const result = _.get(res, 'result.result', null); + expect(result).to.not.equal(null); + assert.deepEqual(res.result, { + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, + result: { + result: { + code: 0, + bandwidth_gas_amount: 1, + gas_amount_charged: 0, + gas_amount_total: { + bandwidth: { + app: { + test: 1 + }, + service: 0 + }, + state: { + app: { + test: 28 + }, + service: 0 + } + }, + gas_cost_total: 0, + is_dryrun: true + }, + tx_hash: CommonUtil.hashSignature(signature), + } + }); + }); + }); + + it('accepts a transaction with unordered nonce (-1) and non-zero gas_price', () => { + const client = jayson.client.http(server1 + '/json-rpc'); + const txBody = { + operation: { + type: 'SET_VALUE', + ref: `/apps/test/test_value/some/path`, + value: 'some other value 1 longer' + }, + gas_price: 500, // non-zero gas price + timestamp: Date.now(), + nonce: -1 + }; + const signature = + ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex')); + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION_DRYRUN, { + tx_body: txBody, + signature, + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION + }).then((res) => { + const result = _.get(res, 'result.result', null); + expect(result).to.not.equal(null); + assert.deepEqual(res.result, { + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, + result: { + result: { + code: 0, + bandwidth_gas_amount: 1, + gas_amount_charged: 0, + gas_amount_total: { + bandwidth: { + app: { + test: 1 + }, + service: 0 + }, + state: { + app: { + test: 42 + }, + service: 0 + } + }, + gas_cost_total: 0, + is_dryrun: true + }, + tx_hash: CommonUtil.hashSignature(signature), + } + }); + }); + }); + + it('accepts a transaction with numbered nonce', () => { + const client = jayson.client.http(server1 + '/json-rpc'); + return client.request(JSON_RPC_METHODS.AIN_GET_NONCE, { + address: account.address, + from: 'pending', + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION + }) + .then((nonceRes) => { + const nonce = _.get(nonceRes, 'result.result'); + const txBody = { + operation: { + type: 'SET_VALUE', + ref: `/apps/test/test_value/some/path`, + value: 'some other value 2' + }, + gas_price: 0, + timestamp: Date.now(), + nonce, // numbered nonce + }; + const signature = + ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex')); + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION_DRYRUN, { + tx_body: txBody, + signature, + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION + }) + .then((res) => { + const result = _.get(res, 'result.result', null); + expect(result).to.not.equal(null); + assert.deepEqual(res.result, { + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, + result: { + result: { + code: 0, + bandwidth_gas_amount: 2001, + gas_amount_charged: 0, + gas_amount_total: { + bandwidth: { + app: { + test: 2001 + }, + service: 0 + }, + state: { + app: { + test: 28 + }, + service: 0 + } + }, + gas_cost_total: 0, + is_dryrun: true + }, + tx_hash: CommonUtil.hashSignature(signature), + } + }); + }); + }); + }) + + it('accepts a transaction with numbered nonce and non-zero gas price', () => { + const client = jayson.client.http(server1 + '/json-rpc'); + return client.request(JSON_RPC_METHODS.AIN_GET_NONCE, { + address: account.address, + from: 'pending', + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION + }) + .then((nonceRes) => { + const nonce = _.get(nonceRes, 'result.result'); + const txBody = { + operation: { + type: 'SET_VALUE', + ref: `/apps/test/test_value/some/path`, + value: 'some other value 2 longer' + }, + gas_price: 500, // non-zero gas price + timestamp: Date.now(), + nonce, // numbered nonce + }; + const signature = + ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex')); + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION_DRYRUN, { + tx_body: txBody, + signature, + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION + }) + .then((res) => { + const result = _.get(res, 'result.result', null); + expect(result).to.not.equal(null); + assert.deepEqual(res.result, { + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, + result: { + result: { + code: 0, + bandwidth_gas_amount: 2001, + gas_amount_charged: 0, + gas_amount_total: { + bandwidth: { + app: { + test: 2001 + }, + service: 0 + }, + state: { + app: { + test: 42 + }, + service: 0 + } + }, + gas_cost_total: 0, + is_dryrun: true + }, + tx_hash: CommonUtil.hashSignature(signature), + } + }); + }); + }); + }) + + it('accepts a transaction with account registration gas amount from nonce', () => { + // NOTE: account2 does not have balance nor nonce/timestamp. + const client = jayson.client.http(server1 + '/json-rpc'); + return client.request(JSON_RPC_METHODS.AIN_GET_NONCE, { + address: account2.address, + from: 'pending', + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION + }) + .then((nonceRes) => { + const nonce = _.get(nonceRes, 'result.result'); + const txBody = { + operation: { + type: 'SET_VALUE', + ref: `/apps/test/test_value/some/path`, + value: 'some other value 4' + }, + gas_price: 0, + timestamp: Date.now(), + nonce, // numbered nonce + }; + const signature = + ainUtil.ecSignTransaction(txBody, Buffer.from(account2.private_key, 'hex')); + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION_DRYRUN, { + tx_body: txBody, + signature, + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION + }) + .then((res) => { + const result = _.get(res, 'result.result', null); + expect(result).to.not.equal(null); + assert.deepEqual(res.result, { + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, + result: { + result: { + code: 0, + bandwidth_gas_amount: 2001, + gas_amount_charged: 0, + gas_amount_total: { + bandwidth: { + app: { + test: 2001 + }, + service: 0 + }, + state: { + app: { + test: 28 + }, + service: 0 + } + }, + gas_cost_total: 0, + is_dryrun: true + }, + tx_hash: CommonUtil.hashSignature(signature), + } + }); + }); + }); + }) + + it('accepts a transaction with account registration gas amount from nonce and non-zero gas price', () => { + // NOTE: account2 does not have balance nor nonce/timestamp. + const client = jayson.client.http(server1 + '/json-rpc'); + return client.request(JSON_RPC_METHODS.AIN_GET_NONCE, { + address: account2.address, + from: 'pending', + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION + }) + .then((nonceRes) => { + const nonce = _.get(nonceRes, 'result.result'); + const txBody = { + operation: { + type: 'SET_VALUE', + ref: `/apps/test/test_value/some/path`, + value: 'some other value 4 longer' + }, + gas_price: 500, // non-zero gas price + timestamp: Date.now(), + nonce, // numbered nonce + }; + const signature = + ainUtil.ecSignTransaction(txBody, Buffer.from(account2.private_key, 'hex')); + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION_DRYRUN, { + tx_body: txBody, + signature, + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION + }) + .then((res) => { + const result = _.get(res, 'result.result', null); + expect(result).to.not.equal(null); + assert.deepEqual(res.result, { + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, + result: { + result: { + code: 0, + bandwidth_gas_amount: 2001, + gas_amount_charged: 0, + gas_amount_total: { + bandwidth: { + app: { + test: 2001 + }, + service: 0 + }, + state: { + app: { + test: 42 + }, + service: 0 + } + }, + gas_cost_total: 0, + is_dryrun: true + }, + tx_hash: CommonUtil.hashSignature(signature), + } + }); + }); + }); + }) + + it('accepts a transaction with account registration gas amount from balance', () => { + // NOTE: account3 does not have balance nor nonce/timestamp. + const client = jayson.client.http(server1 + '/json-rpc'); + const txBody = { + operation: { + type: 'SET_VALUE', + ref: `/transfer/${account09.address}/${account3.address}/${Date.now()}/value`, + value: 10 + }, + gas_price: 0, + timestamp: Date.now(), + nonce: -1, // unordered nonce + }; + const signature = + ainUtil.ecSignTransaction(txBody, Buffer.from(account09.private_key, 'hex')); + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION_DRYRUN, { + tx_body: txBody, + signature, + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION + }) + .then((res) => { + const result = _.get(res, 'result.result', null); + expect(result).to.not.equal(null); + assert.deepEqual(res.result, { + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, + result: { + result: { + "bandwidth_gas_amount": 1, + "code": 0, + "func_results": { + "_transfer": { + "bandwidth_gas_amount": 2000, + "code": 0, + "op_results": { + "0": { + "path": "/accounts/0x09A0d53FDf1c36A131938eb379b98910e55EEfe1/balance", + "result": { + "bandwidth_gas_amount": 1, + "code": 0 + } + }, + "1": { + "path": "/accounts/0x758fd59D3f8157Ae4458f8E29E2A8317be3d5974/balance", + "result": { + "bandwidth_gas_amount": 1, + "code": 0 + } + } + } + } + }, + "gas_amount_charged": 3281, + "gas_amount_total": { + "bandwidth": { + "service": 2003 + }, + "state": { + "service": 1278 + } + }, + "gas_cost_total": 0, + "is_dryrun": true + }, + tx_hash: CommonUtil.hashSignature(signature), + } + }); + }); + }) + + it('accepts a transaction with account registration gas amount from balance and non-zero gas price', () => { + // NOTE: account3 does not have balance nor nonce/timestamp. + const client = jayson.client.http(server1 + '/json-rpc'); + const txBody = { + operation: { + type: 'SET_VALUE', + ref: `/transfer/${account09.address}/${account3.address}/${Date.now()}/value`, + value: 10 + }, + gas_price: 500, // non-zero gas price + timestamp: Date.now(), + nonce: -1, // unordered nonce + }; + const signature = + ainUtil.ecSignTransaction(txBody, Buffer.from(account09.private_key, 'hex')); + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION_DRYRUN, { + tx_body: txBody, + signature, + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION + }) + .then((res) => { + const result = _.get(res, 'result.result', null); + expect(result).to.not.equal(null); + assert.deepEqual(res.result, { + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, + result: { + result: { + "bandwidth_gas_amount": 1, + "code": 0, + "func_results": { + "_transfer": { + "bandwidth_gas_amount": 2000, + "code": 0, + "op_results": { + "0": { + "path": "/accounts/0x09A0d53FDf1c36A131938eb379b98910e55EEfe1/balance", + "result": { + "bandwidth_gas_amount": 1, + "code": 0 + } + }, + "1": { + "path": "/accounts/0x758fd59D3f8157Ae4458f8E29E2A8317be3d5974/balance", + "result": { + "bandwidth_gas_amount": 1, + "code": 0 + } + } + } + } + }, + "gas_amount_charged": 3281, + "gas_amount_total": { + "bandwidth": { + "service": 2003 + }, + "state": { + "service": 1278 + } + }, + "gas_cost_total": 1.6405, + "is_dryrun": true + }, + tx_hash: CommonUtil.hashSignature(signature), + } + }); + }); + }) + + it('rejects a transaction with an invalid signature.', () => { + const client = jayson.client.http(server1 + '/json-rpc'); + const txBody = { + operation: { + type: 'SET_VALUE', + ref: `/apps/test/test_value/some/path`, + value: 'some other value 3' + }, + gas_price: 0, + timestamp: Date.now(), + nonce: -1 + }; + const signature = + ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex')); + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION_DRYRUN, { + tx_body: txBody, + signature: signature + '0', // invalid signature + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION + }).then((res) => { + assert.deepEqual(res.result, { + result: null, + code: 30304, + message: 'Invalid transaction signature.', + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION + }); + }) + }); + }); + describe('json-rpc api: ain_sendSignedTransaction', () => { const account = { address: "0x9534bC7529961E5737a3Dd317BdEeD41AC08a52D", @@ -3322,7 +3933,7 @@ describe('Blockchain Node', () => { operation: { type: 'SET_VALUE', ref: `/apps/test/test_value/some/path`, - value: 'some other value' + value: 'some other value 1' }, gas_price: 0, timestamp: Date.now(), @@ -3353,7 +3964,57 @@ describe('Blockchain Node', () => { }, state: { app: { - test: 24 + test: 28 + }, + service: 0 + } + }, + gas_cost_total: 0 + }, + tx_hash: CommonUtil.hashSignature(signature), + } + }); + }) + }) + + it('accepts a transaction with unordered nonce (-1) and non-zero gas_price', () => { + const client = jayson.client.http(server1 + '/json-rpc'); + const txBody = { + operation: { + type: 'SET_VALUE', + ref: `/apps/test/test_value/some/path`, + value: 'some other value 1 longer' + }, + gas_price: 500, // non-zero gas price + timestamp: Date.now(), + nonce: -1 + }; + const signature = + ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex')); + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { + tx_body: txBody, + signature, + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION + }).then((res) => { + const result = _.get(res, 'result.result', null); + expect(result).to.not.equal(null); + assert.deepEqual(res.result, { + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, + result: { + result: { + code: 0, + bandwidth_gas_amount: 1, + gas_amount_charged: 0, + gas_amount_total: { + bandwidth: { + app: { + test: 1 + }, + service: 0 + }, + state: { + app: { + test: 42 }, service: 0 } @@ -3425,6 +4086,65 @@ describe('Blockchain Node', () => { }); }) + it('accepts a transaction with numbered nonce and non-zero gas price', () => { + const client = jayson.client.http(server1 + '/json-rpc'); + return client.request(JSON_RPC_METHODS.AIN_GET_NONCE, { + address: account.address, + from: 'pending', + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION + }) + .then((nonceRes) => { + const nonce = _.get(nonceRes, 'result.result'); + const txBody = { + operation: { + type: 'SET_VALUE', + ref: `/apps/test/test_value/some/path`, + value: 'some other value 2 longer' + }, + gas_price: 500, // non-zero gas price + timestamp: Date.now(), + nonce, // numbered nonce + }; + const signature = + ainUtil.ecSignTransaction(txBody, Buffer.from(account.private_key, 'hex')); + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { + tx_body: txBody, + signature, + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION + }) + .then((res) => { + const result = _.get(res, 'result.result', null); + expect(result).to.not.equal(null); + assert.deepEqual(res.result, { + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, + result: { + result: { + code: 0, + bandwidth_gas_amount: 1, + gas_amount_charged: 0, + gas_amount_total: { + bandwidth: { + app: { + test: 1 + }, + service: 0 + }, + state: { + app: { + test: 42 + }, + service: 0 + } + }, + gas_cost_total: 0, + }, + tx_hash: CommonUtil.hashSignature(signature), + } + }); + }); + }); + }) + it('accepts a transaction with account registration gas amount from nonce', () => { // NOTE: account2 does not have balance nor nonce/timestamp. const client = jayson.client.http(server1 + '/json-rpc'); @@ -3545,6 +4265,66 @@ describe('Blockchain Node', () => { }); }) + it('accepts a transaction without account registration gas amount from nonce and non-zero gas price', () => { + // NOTE: account2 already has nonce/timestamp. + const client = jayson.client.http(server1 + '/json-rpc'); + return client.request(JSON_RPC_METHODS.AIN_GET_NONCE, { + address: account2.address, + from: 'pending', + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION + }) + .then((nonceRes) => { + const nonce = _.get(nonceRes, 'result.result'); + const txBody = { + operation: { + type: 'SET_VALUE', + ref: `/apps/test/test_value/some/path`, + value: 'some other value 4 longer' + }, + gas_price: 500, // non-zero gas price + timestamp: Date.now(), + nonce, // numbered nonce + }; + const signature = + ainUtil.ecSignTransaction(txBody, Buffer.from(account2.private_key, 'hex')); + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { + tx_body: txBody, + signature, + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION + }) + .then((res) => { + const result = _.get(res, 'result.result', null); + expect(result).to.not.equal(null); + assert.deepEqual(res.result, { + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, + result: { + result: { + code: 0, + bandwidth_gas_amount: 1, + gas_amount_charged: 0, + gas_amount_total: { + bandwidth: { + app: { + test: 1 + }, + service: 0 + }, + state: { + app: { + test: 42 + }, + service: 0 + } + }, + gas_cost_total: 0, + }, + tx_hash: CommonUtil.hashSignature(signature), + } + }); + }); + }); + }) + it('accepts a transaction without account registration gas amount from balance', () => { // NOTE: account2 does not have balance but already has nonce/timestamp. const client = jayson.client.http(server1 + '/json-rpc'); @@ -3749,6 +4529,74 @@ describe('Blockchain Node', () => { }); }) + it('accepts a transaction without account registration gas amount from balance and non-zero gas price', () => { + // NOTE: account3 already has balance. + const client = jayson.client.http(server1 + '/json-rpc'); + const txBody = { + operation: { + type: 'SET_VALUE', + ref: `/transfer/${account09.address}/${account3.address}/${Date.now()}/value`, + value: 10 + }, + gas_price: 500, // non-zero gas price + timestamp: Date.now(), + nonce: -1, // unordered nonce + }; + const signature = + ainUtil.ecSignTransaction(txBody, Buffer.from(account09.private_key, 'hex')); + return client.request(JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, { + tx_body: txBody, + signature, + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION + }) + .then((res) => { + const result = _.get(res, 'result.result', null); + expect(result).to.not.equal(null); + assert.deepEqual(res.result, { + protoVer: BlockchainConsts.CURRENT_PROTOCOL_VERSION, + result: { + result: { + "bandwidth_gas_amount": 1, + "code": 0, + "func_results": { + "_transfer": { + "bandwidth_gas_amount": 0, + "code": 0, + "op_results": { + "0": { + "path": "/accounts/0x09A0d53FDf1c36A131938eb379b98910e55EEfe1/balance", + "result": { + "bandwidth_gas_amount": 1, + "code": 0 + } + }, + "1": { + "path": "/accounts/0x758fd59D3f8157Ae4458f8E29E2A8317be3d5974/balance", + "result": { + "bandwidth_gas_amount": 1, + "code": 0 + } + } + } + } + }, + "gas_amount_charged": 367, + "gas_amount_total": { + "bandwidth": { + "service": 3 + }, + "state": { + "service": 364 + } + }, + "gas_cost_total": 0.1835 + }, + tx_hash: CommonUtil.hashSignature(signature), + } + }); + }); + }) + it('accepts a multi-set transaction with account registration gas amount from nonce', () => { // NOTE: account4 does not have balance nor nonce/timestamp. const client = jayson.client.http(server1 + '/json-rpc'); @@ -4043,7 +4891,7 @@ describe('Blockchain Node', () => { opList.push({ type: 'SET_VALUE', ref: `/apps/test/test_value/some/path`, - value: 'some other value' + value: 'some other value 9' }); } const txBody = { @@ -4122,7 +4970,7 @@ describe('Blockchain Node', () => { operation: { type: 'SET_VALUE', ref: `/apps/test/test_value/some/path`, - value: 'some other value' + value: 'some other value 10' }, gas_price: 0, timestamp: Date.now(), @@ -4150,7 +4998,7 @@ describe('Blockchain Node', () => { operation: { type: 'SET_VALUE', ref: `/apps/test/test_value/some/path`, - value: 'some other value' + value: 'some other value 11' }, gas_price: 0, timestamp: Date.now(), @@ -4178,7 +5026,7 @@ describe('Blockchain Node', () => { operation: { type: 'SET_VALUE', ref: `/apps/test/test_value/some/path`, - value: 'some other value 3' + value: 'some other value 12' }, gas_price: 0, timestamp: Date.now(), @@ -4418,7 +5266,7 @@ describe('Blockchain Node', () => { operation: { type: 'SET_VALUE', ref: `/apps/test/test_value/some/path`, - value: 'some other value' + value: 'some other value 1' }, gas_price: 0, timestamp: Date.now(), @@ -4449,7 +5297,7 @@ describe('Blockchain Node', () => { operation: { type: 'SET_VALUE', ref: `/apps/test/test_value/some/path`, - value: 'some other value' + value: 'some other value 2' }, gas_price: 0, nonce: -1 @@ -4485,7 +5333,7 @@ describe('Blockchain Node', () => { operation: { type: 'SET_VALUE', ref: `/apps/test/test_value/some/path`, - value: 'some other value' + value: 'some other value 3' }, gas_price: 0, nonce: -1 @@ -4524,7 +5372,7 @@ describe('Blockchain Node', () => { operation: { type: 'SET_VALUE', ref: `/apps/test/test_value/some/path`, - value: 'some other value' + value: 'some other value 4' }, gas_price: 0, nonce: -1 @@ -4641,7 +5489,7 @@ describe('Blockchain Node', () => { operation: { type: 'SET_VALUE', ref: `/apps/test/test_value/some/path`, - value: 'some other value' + value: 'some other value 5' }, timestamp: Date.now(), nonce: -1 @@ -4678,7 +5526,7 @@ describe('Blockchain Node', () => { operation: { type: 'SET_VALUE', ref: `/apps/test/test_value/some/path`, - value: 'some other value' + value: 'some other value 6' }, gas_price: 0, timestamp: Date.now(), @@ -4718,7 +5566,7 @@ describe('Blockchain Node', () => { operation: { type: 'SET_VALUE', ref: `/apps/test/test_value/some/path`, - value: 'some other value 3' + value: 'some other value 7' }, gas_price: 0, timestamp: Date.now(), diff --git a/test/unit/functions.test.js b/test/unit/functions.test.js index 70518f9a2..c2702ca5f 100644 --- a/test/unit/functions.test.js +++ b/test/unit/functions.test.js @@ -2723,7 +2723,7 @@ describe("Functions", () => { return triggerRes.func_promises.then((resp) => { assert.deepEqual(resp, { func_count: 1, - trigger_count: 0, + trigger_count: 1, fail_count: 0, }); }); diff --git a/test/unit/p2p.test.js b/test/unit/p2p.test.js index de7c38c93..079f58cbc 100644 --- a/test/unit/p2p.test.js +++ b/test/unit/p2p.test.js @@ -158,7 +158,7 @@ describe("P2p", () => { describe("getStateVersionStatus", () => { it("gets initial state version status", () => { const stateVersionStatus = p2pServer.getStateVersionStatus(); - expect(stateVersionStatus.numVersions).to.equal(5); + expect(stateVersionStatus.numVersions).to.equal(4); expect(stateVersionStatus.finalVersion).to.equal('FINAL:0'); }); }); @@ -219,7 +219,7 @@ describe("P2p", () => { numApps: 1, }, stateVersionStatus: { - numVersions: 5, + numVersions: 4, versionList: 'erased', finalVersion: 'FINAL:0' } diff --git a/tools/transfer/sendTransferTx.js b/tools/transfer/sendTransferTx.js index 483d5d2ad..87ae51c9d 100644 --- a/tools/transfer/sendTransferTx.js +++ b/tools/transfer/sendTransferTx.js @@ -1,7 +1,12 @@ // A tool to transfer native AIN tokens between accounts. const ainUtil = require('@ainblockchain/ain-util'); const CommonUtil = require('../../common/common-util'); -const { getAccountPrivateKey, signAndSendTx, confirmTransaction } = require('../util'); +const { + getAccountPrivateKey, + signAndSendTxDryrun, + signAndSendTx, + confirmTransaction +} = require('../util'); function buildTransferTxBody(fromAddr, toAddr, key, amount, timestamp) { return { @@ -16,7 +21,7 @@ function buildTransferTxBody(fromAddr, toAddr, key, amount, timestamp) { } } -async function sendTransaction(endpointUrl, chainId, toAddr, ainAmount, account) { +async function sendTransaction(endpointUrl, chainId, toAddr, ainAmount, account, isDryrun) { console.log('\n*** sendTransaction():'); const timestamp = Date.now(); @@ -24,24 +29,32 @@ async function sendTransaction(endpointUrl, chainId, toAddr, ainAmount, account) buildTransferTxBody(account.address, toAddr, timestamp, ainAmount, timestamp); console.log(`txBody: ${JSON.stringify(txBody, null, 2)}`); - const txInfo = await signAndSendTx(endpointUrl, txBody, account.private_key, chainId); + let txInfo = null; + if (isDryrun) { + txInfo = await signAndSendTxDryrun(endpointUrl, txBody, account.private_key, chainId); + } else { + txInfo = await signAndSendTx(endpointUrl, txBody, account.private_key, chainId); + } console.log(`txInfo: ${JSON.stringify(txInfo, null, 2)}`); if (!txInfo.success) { console.log(`Transfer transaction failed.`); process.exit(0); } - await confirmTransaction(endpointUrl, timestamp, txInfo.txHash); + if (!isDryrun) { + await confirmTransaction(endpointUrl, timestamp, txInfo.txHash); + } } -async function sendTransferTx(endpointUrl, chainId, toAddr, ainAmount, accountType, keystoreFilepath) { +async function sendTransferTx( + endpointUrl, chainId, toAddr, ainAmount, accountType, keystoreFilepath, isDryrun) { const privateKey = await getAccountPrivateKey(accountType, keystoreFilepath); const account = ainUtil.privateToAccount(Buffer.from(privateKey, 'hex')); console.log(`\nFrom-address: ${account.address}\n`); - await sendTransaction(endpointUrl, chainId, toAddr, ainAmount, account); + await sendTransaction(endpointUrl, chainId, toAddr, ainAmount, account, isDryrun); } async function processArguments() { - if (process.argv.length !== 7 && process.argv.length !== 8) { + if (process.argv.length < 7 || process.argv.length > 9) { usage(); } const endpointUrl = process.argv[2]; @@ -62,17 +75,30 @@ async function processArguments() { console.error('Please specify keystore filepath.'); usage(); } - await sendTransferTx(endpointUrl, chainId, toAddr, ainAmount, accountType, keystoreFilepath); + let dryrunOption = null; + if (accountType === 'keystore') { + if (process.argv.length === 9) { + dryrunOption = process.argv[8]; + } + } else { + if (process.argv.length === 8) { + dryrunOption = process.argv[7]; + } + } + const isDryrun = dryrunOption === '--dryrun'; + await sendTransferTx(endpointUrl, chainId, toAddr, ainAmount, accountType, keystoreFilepath, isDryrun); } function usage() { - console.log('\nUsage: node sendTransferTx.js []\n'); + console.log('\nUsage: node sendTransferTx.js [] [--dryrun]\n'); console.log('Example: node sendTransferTx.js http://localhost:8081 0 0x08Aed7AF9354435c38d52143EE50ac839D20696b 10 private_key'); console.log('Example: node sendTransferTx.js http://localhost:8081 0 0x08Aed7AF9354435c38d52143EE50ac839D20696b 10 mnemonic'); console.log('Example: node sendTransferTx.js http://localhost:8081 0 0x08Aed7AF9354435c38d52143EE50ac839D20696b 10 keystore keystore_from_account.json'); + console.log('Example: node sendTransferTx.js http://localhost:8081 0 0x08Aed7AF9354435c38d52143EE50ac839D20696b 10 keystore keystore_from_account.json --dryrun'); console.log('Example: node sendTransferTx.js https://staging-api.ainetwork.ai 0 0x08Aed7AF9354435c38d52143EE50ac839D20696b 10 keystore keystore_from_account.json'); console.log('Example: node sendTransferTx.js https://testnet-api.ainetwork.ai 0 0x08Aed7AF9354435c38d52143EE50ac839D20696b 10 keystore keystore_from_account.json'); console.log('Example: node sendTransferTx.js https://mainnet-api.ainetwork.ai 1 0x08Aed7AF9354435c38d52143EE50ac839D20696b 10 keystore keystore_from_account.json\n'); + console.log('Example: node sendTransferTx.js https://mainnet-api.ainetwork.ai 1 0x08Aed7AF9354435c38d52143EE50ac839D20696b 10 keystore keystore_from_account.json --dryrun\n'); process.exit(0) } diff --git a/tools/util.js b/tools/util.js index 975c11f27..972c80c46 100644 --- a/tools/util.js +++ b/tools/util.js @@ -87,9 +87,7 @@ async function keystoreToAccount(filePath) { return ainUtil.privateToAccount(ainUtil.v3KeystoreToPrivate(keystore, input.password)); } -// FIXME(minsulee2): this is duplicated function see: ./common/network-util.js -function signAndSendTx(endpointUrl, txBody, privateKey, chainId) { - console.log('\n*** signAndSendTx():'); +function signAndSendTxWithJsonRpcMethod(endpointUrl, txBody, privateKey, chainId, jsonRpcMethod) { const { txHash, signedTx } = CommonUtil.signTransaction(txBody, privateKey, chainId); console.log(`signedTx: ${JSON.stringify(signedTx, null, 2)}`); console.log(`txHash: ${txHash}`); @@ -98,7 +96,7 @@ function signAndSendTx(endpointUrl, txBody, privateKey, chainId) { return axios.post( `${endpointUrl}/json-rpc`, { - method: JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION, + method: jsonRpcMethod, params: signedTx, jsonrpc: '2.0', id: 0 @@ -115,6 +113,18 @@ function signAndSendTx(endpointUrl, txBody, privateKey, chainId) { }); } +function signAndSendTxDryrun(endpointUrl, txBody, privateKey, chainId) { + console.log('\n*** signAndSendTxDryrun():'); + return signAndSendTxWithJsonRpcMethod( + endpointUrl, txBody, privateKey, chainId, JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION_DRYRUN); +} + +function signAndSendTx(endpointUrl, txBody, privateKey, chainId) { + console.log('\n*** signAndSendTx():'); + return signAndSendTxWithJsonRpcMethod( + endpointUrl, txBody, privateKey, chainId, JSON_RPC_METHODS.AIN_SEND_SIGNED_TRANSACTION); +} + async function sendGetTxByHashRequest(endpointUrl, txHash) { return await axios.post( `${endpointUrl}/json-rpc`, @@ -152,6 +162,7 @@ async function confirmTransaction(endpointUrl, timestamp, txHash) { module.exports = { getAccountPrivateKey, keystoreToAccount, + signAndSendTxDryrun, signAndSendTx, sendGetTxByHashRequest, confirmTransaction,