From 17b78d5c4152c6e0caf857fa7e867a6ecde1e122 Mon Sep 17 00:00:00 2001 From: naz_dou <41945483+nduchak@users.noreply.github.com> Date: Thu, 16 May 2019 12:41:29 +0300 Subject: [PATCH] feat(Fortuna): Node 3.0.0 compatibility (#397) * feat(Fortuna): Make combatible * change node version to 3.0.0 * point node to master * feat(Node): Change supported node version range from 2.5.0 to 4.0.0 * feat(TX): Refactor contract create tx schema. Add vm/abi serialization. Clean up high lv API. Add Fo * fix(Fix semver implem,entation): * fix(TX Builder): Fix linter * fix(Contract): Fix contract tests * fix(Node): Revert probe methods default block value * refactor(Node): Change supported node version range from 2.3.0 to 4.0.0 --- es/ae/contract.js | 1 - es/ae/oracle.js | 3 +- es/chain/node.js | 2 +- es/node.js | 4 +- es/tx/builder/index.js | 8 ++++ es/tx/builder/schema.js | 53 ++++++++++++++++---------- es/tx/tx.js | 72 ++++++++++++++++++++---------------- es/utils/semver-satisfies.js | 6 ++- test/integration/contract.js | 2 +- 9 files changed, 91 insertions(+), 60 deletions(-) diff --git a/es/ae/contract.js b/es/ae/contract.js index 506c6a96d9..b0e0d43668 100644 --- a/es/ae/contract.js +++ b/es/ae/contract.js @@ -298,7 +298,6 @@ export const Contract = Ae.compose(ContractBase, ContractACI, { Ae: { defaults: { deposit: 0, - vmVersion: 1, gasPrice: 1000000000, // min gasPrice 1e9 amount: 0, gas: 1600000 - 21000, diff --git a/es/ae/oracle.js b/es/ae/oracle.js index e67008b466..7e4fcbbe0d 100644 --- a/es/ae/oracle.js +++ b/es/ae/oracle.js @@ -122,7 +122,7 @@ export async function pollForQueryResponse (oracleId, queryId, { attempts = 20, * @return {Promise} Oracle object */ async function registerOracle (queryFormat, responseFormat, options = {}) { - const opt = R.merge(R.merge(this.Ae.defaults, { vmVersion: this.Ae.defaults.oracleVmVersion }), options) // Preset VmVersion for oracle + const opt = R.merge(this.Ae.defaults, options) // Preset VmVersion for oracle const accountId = await this.address() const oracleRegisterTx = await this.oracleRegisterTx(R.merge(opt, { @@ -248,7 +248,6 @@ const Oracle = Ae.compose({ getQueryObject }, deepProps: { Ae: { defaults: { - oracleVmVersion: 0, queryFee: 30000, oracleTtl: { type: 'delta', value: 500 }, queryTtl: { type: 'delta', value: 10 }, diff --git a/es/chain/node.js b/es/chain/node.js index 58feafcd2a..bda51c43a5 100644 --- a/es/chain/node.js +++ b/es/chain/node.js @@ -133,7 +133,7 @@ async function poll (th, { blocks = 10, interval = 5000 } = {}) { } async function getTxInfo (hash) { - return this.api.getTransactionInfoByHash(hash) + return this.api.getTransactionInfoByHash(hash).then(res => res.callInfo ? res.callInfo : res) } async function mempool () { diff --git a/es/node.js b/es/node.js index 77a334c83c..ed24e27155 100644 --- a/es/node.js +++ b/es/node.js @@ -119,7 +119,7 @@ const Node = stampit({ } }) -const NODE_GE_VERSION = '1.4.0' -const NODE_LT_VERSION = '3.0.0' +const NODE_GE_VERSION = '2.3.0' +const NODE_LT_VERSION = '4.0.0' export default Node diff --git a/es/tx/builder/index.js b/es/tx/builder/index.js index d4bf9826cd..a6dcf2d4fd 100644 --- a/es/tx/builder/index.js +++ b/es/tx/builder/index.js @@ -34,6 +34,10 @@ const ORACLE_TTL_TYPES = { function deserializeField (value, type, prefix) { if (!value) return '' switch (type) { + case FIELD_TYPES.ctVersion: + // eslint-disable-next-line no-unused-vars + const [vm, _, abi] = value + return { vmVersion: readInt(Buffer.from([vm])), abiVersion: readInt(Buffer.from([abi])) } case FIELD_TYPES.int: return readInt(value) case FIELD_TYPES.id: @@ -97,6 +101,8 @@ function serializeField (value, type, prefix) { return buildPointers(value) case FIELD_TYPES.mptree: return value.map(mpt.serialize) + case FIELD_TYPES.ctVersion: + return Buffer.from([...toBytes(value.vmVersion), 0, ...toBytes(value.abiVersion)]) case FIELD_TYPES.callReturnType: switch (value) { case 'ok': return writeInt(0) @@ -125,6 +131,8 @@ function validateField (value, key, type, prefix) { return assert(value.split('_')[0] === prefix, { prefix, value }) case FIELD_TYPES.string: return assert(true) + case FIELD_TYPES.ctVersion: + return assert(typeof value === 'object' && value.hasOwnProperty('abiVersion') && value.hasOwnProperty('vmVersion')) case FIELD_TYPES.pointers: return assert(Array.isArray(value) && !value.find(e => e !== Object(e)), { value }) default: diff --git a/es/tx/builder/schema.js b/es/tx/builder/schema.js index a62797a76f..46af8e6129 100644 --- a/es/tx/builder/schema.js +++ b/es/tx/builder/schema.js @@ -71,20 +71,6 @@ const TX_SCHEMA_FIELD = (schema, objectId) => [schema, objectId] export const MIN_GAS_PRICE = 1000000000 // min gasPrice 1e9 -// # see https://github.com/aeternity/protocol/blob/minerva/contracts/contract_vms.md#virtual-machines-on-the-%C3%A6ternity-blockchain -const VM_VERSIONS = { - NO_VM: 0, - SOPHIA: 1, - SOLIDITY: 2, - SOPHIA_IMPROVEMENTS: 3 -} -// # see https://github.com/aeternity/protocol/blob/minerva/contracts/contract_vms.md#virtual-machines-on-the-%C3%A6ternity-blockchain -const ABI_VERSIONS = { - NO_ABI: 0, - SOPHIA: 1, - SOLIDITY: 2 -} - const revertObject = (obj) => Object.entries(obj).reduce((acc, [key, v]) => (acc[v] = key) && acc, {}) /** @@ -154,6 +140,33 @@ export const TX_TYPE = { accountsTree: 'accountsTree' } +// # see https://github.com/aeternity/protocol/blob/minerva/contracts/contract_vms.md#virtual-machines-on-the-%C3%A6ternity-blockchain +export const VM_VERSIONS = { + NO_VM: 0, + SOPHIA: 1, + SOLIDITY: 2, + SOPHIA_IMPROVEMENTS_MINERVA: 3, + SOPHIA_IMPROVEMENTS_FORTUNA: 4 +} +// # see https://github.com/aeternity/protocol/blob/minerva/contracts/contract_vms.md#virtual-machines-on-the-%C3%A6ternity-blockchain +export const ABI_VERSIONS = { + NO_ABI: 0, + SOPHIA: 1, + SOLIDITY: 2 +} + +export const VM_ABI_MAP_MINERVA = { + [TX_TYPE.contractCreate]: { vmVersion: [VM_VERSIONS.SOPHIA_IMPROVEMENTS_MINERVA], abiVersion: [ABI_VERSIONS.SOPHIA] }, + [TX_TYPE.contractCall]: { vmVersion: [VM_VERSIONS.SOPHIA, VM_VERSIONS.SOPHIA_IMPROVEMENTS_MINERVA], abiVersion: [ABI_VERSIONS.SOPHIA] }, + [TX_TYPE.oracleRegister]: { vmVersion: [VM_VERSIONS.SOPHIA_IMPROVEMENTS_MINERVA], abiVersion: [ABI_VERSIONS.NO_ABI, ABI_VERSIONS.SOPHIA] } +} + +export const VM_ABI_MAP_FORTUNA = { + [TX_TYPE.contractCreate]: { vmVersion: [VM_VERSIONS.SOPHIA_IMPROVEMENTS_MINERVA, VM_VERSIONS.SOPHIA_IMPROVEMENTS_FORTUNA], abiVersion: [ABI_VERSIONS.SOPHIA] }, // vmVersion 0x4 do not work with fortuna + [TX_TYPE.contractCall]: { vmVersion: [VM_VERSIONS.SOPHIA, VM_VERSIONS.SOPHIA_IMPROVEMENTS_FORTUNA, VM_VERSIONS.SOPHIA_IMPROVEMENTS_MINERVA], abiVersion: [ABI_VERSIONS.SOPHIA] }, + [TX_TYPE.oracleRegister]: { vmVersion: [], abiVersion: [ABI_VERSIONS.NO_ABI, ABI_VERSIONS.SOPHIA] } +} + export const OBJECT_ID_TX_TYPE = { [OBJECT_TAG_ACCOUNT]: TX_TYPE.account, [OBJECT_TAG_SIGNED_TRANSACTION]: TX_TYPE.signed, @@ -219,7 +232,8 @@ export const FIELD_TYPES = { callStack: 'callStack', proofOfInclusion: 'proofOfInclusion', mptree: 'mptree', - callReturnType: 'callReturnType' + callReturnType: 'callReturnType', + ctVersion: 'ctVersion' } // FEE CALCULATION @@ -281,7 +295,8 @@ export const VALIDATION_MESSAGE = { [FIELD_TYPES.id]: ({ value, prefix }) => VALIDATION_ERROR(`'${value}' prefix doesn't match expected prefix '${prefix}' or ID_TAG for prefix not found`), [FIELD_TYPES.binary]: ({ prefix, value }) => VALIDATION_ERROR(`'${value}' prefix doesn't match expected prefix '${prefix}'`), [FIELD_TYPES.string]: ({ value }) => VALIDATION_ERROR(`Not a string`), - [FIELD_TYPES.pointers]: ({ value }) => VALIDATION_ERROR(`Value must be of type Array and contains only object's like '{key: "account_pubkey", id: "ak_lkamsflkalsdalksdlasdlasdlamd"}'`) + [FIELD_TYPES.pointers]: ({ value }) => VALIDATION_ERROR(`Value must be of type Array and contains only object's like '{key: "account_pubkey", id: "ak_lkamsflkalsdalksdlasdlasdlamd"}'`), + [FIELD_TYPES.ctVersion]: ({ value }) => VALIDATION_ERROR(`Value must be an object with "vmVersion" and "abiVersion" fields`) } const BASE_TX = [ @@ -378,7 +393,7 @@ const CONTRACT_CREATE_TX = [ TX_FIELD('ownerId', FIELD_TYPES.id, 'ak'), TX_FIELD('nonce', FIELD_TYPES.int), TX_FIELD('code', FIELD_TYPES.binary, 'cb'), - TX_FIELD('vmVersion', FIELD_TYPES.int), + TX_FIELD('ctVersion', FIELD_TYPES.ctVersion), TX_FIELD('fee', FIELD_TYPES.int), TX_FIELD('ttl', FIELD_TYPES.int), TX_FIELD('deposit', FIELD_TYPES.int), @@ -393,7 +408,7 @@ const CONTRACT_CALL_TX = [ TX_FIELD('callerId', FIELD_TYPES.id, 'ak'), TX_FIELD('nonce', FIELD_TYPES.int), TX_FIELD('contractId', FIELD_TYPES.id, 'ct'), - TX_FIELD('vmVersion', FIELD_TYPES.int), + TX_FIELD('abiVersion', FIELD_TYPES.int), TX_FIELD('fee', FIELD_TYPES.int), TX_FIELD('ttl', FIELD_TYPES.int), TX_FIELD('amount', FIELD_TYPES.int), @@ -427,7 +442,7 @@ const ORACLE_REGISTER_TX = [ TX_FIELD('oracleTtlValue', FIELD_TYPES.int), TX_FIELD('fee', FIELD_TYPES.int), TX_FIELD('ttl', FIELD_TYPES.int), - TX_FIELD('vmVersion', FIELD_TYPES.int) + TX_FIELD('abiVersion', FIELD_TYPES.int) ] const ORACLE_EXTEND_TX = [ diff --git a/es/tx/tx.js b/es/tx/tx.js index a886ca424d..c10cfac14b 100644 --- a/es/tx/tx.js +++ b/es/tx/tx.js @@ -28,16 +28,9 @@ import Tx from './' import Node from '../node' import { buildTx, calculateFee } from './builder' -import { MIN_GAS_PRICE, TX_TYPE } from './builder/schema' +import { MIN_GAS_PRICE, TX_TYPE, VM_ABI_MAP_FORTUNA, VM_ABI_MAP_MINERVA } from './builder/schema' import { buildContractId, oracleQueryId } from './builder/helpers' -const ORACLE_VM_VERSION = 0 -const CONTRACT_VM_VERSION = 1 -// TODO This values using as default for minerva node -const CONTRACT_MINERVA_VM_ABI = 196609 -const CONTRACT_MINERVA_VM = 3 -const CONTRACT_MINERVA_ABI = 1 - async function spendTx ({ senderId, recipientId, amount, payload = '' }) { // Calculate fee, get absolute ttl (ttl + height), get account nonce const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.spend, { senderId, ...R.head(arguments), payload }) @@ -125,58 +118,50 @@ async function nameRevokeTx ({ accountId, nameId }) { return tx } -// TODO move this to tx-builder -// Get VM_ABI version for minerva -function getContractVmVersion () { - return semverSatisfies(this.version.split('-')[0], '2.0.0', '3.0.0') // Minerva - ? { splitedVmAbi: CONTRACT_MINERVA_VM_ABI, contractVmVersion: CONTRACT_MINERVA_VM } - : { splitedVmAbi: CONTRACT_VM_VERSION, contractVmVersion: CONTRACT_VM_VERSION } -} async function contractCreateTx ({ ownerId, code, vmVersion, abiVersion, deposit, amount, gas, gasPrice = MIN_GAS_PRICE, callData }) { - // TODO move this to tx-builder - // Get VM_ABI version for minerva - const { splitedVmAbi, contractVmVersion } = getContractVmVersion.bind(this)() + // Get VM_ABI version + const ctVersion = this.getVmVersion(TX_TYPE.contractCreate, R.head(arguments)) // Calculate fee, get absolute ttl (ttl + height), get account nonce - - const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.contractCreate, { senderId: ownerId, ...R.head(arguments), vmVersion: splitedVmAbi, gasPrice }) - + const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.contractCreate, { senderId: ownerId, ...R.head(arguments), ctVersion, gasPrice }) // Build transaction using sdk (if nativeMode) or build on `AETERNITY NODE` side return this.nativeMode ? { - ...buildTx(R.merge(R.head(arguments), { nonce, ttl, fee, vmVersion: splitedVmAbi, gasPrice }), TX_TYPE.contractCreate), + ...buildTx(R.merge(R.head(arguments), { nonce, ttl, fee, ctVersion, gasPrice }), TX_TYPE.contractCreate), contractId: buildContractId(ownerId, nonce) } - : this.api.postContractCreate(R.merge(R.head(arguments), { nonce, ttl, fee: parseInt(fee), gas: parseInt(gas), gasPrice, vmVersion: contractVmVersion, abiVersion: CONTRACT_MINERVA_ABI })) + : this.api.postContractCreate(R.merge(R.head(arguments), { nonce, ttl, fee: parseInt(fee), gas: parseInt(gas), gasPrice, vmVersion: ctVersion.vmVersion, abiVersion: ctVersion.abiVersion })) } -async function contractCallTx ({ callerId, contractId, vmVersion = CONTRACT_VM_VERSION, amount, gas, gasPrice = MIN_GAS_PRICE, callData }) { +async function contractCallTx ({ callerId, contractId, abiVersion, amount, gas, gasPrice = MIN_GAS_PRICE, callData }) { + const ctVersion = this.getVmVersion(TX_TYPE.contractCall, R.head(arguments)) // Calculate fee, get absolute ttl (ttl + height), get account nonce - const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.contractCall, { senderId: callerId, ...R.head(arguments), gasPrice, vmVersion }) + const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.contractCall, { senderId: callerId, ...R.head(arguments), gasPrice, abiVersion: ctVersion.abiVersion }) // Build transaction using sdk (if nativeMode) or build on `AETERNITY NODE` side const { tx } = this.nativeMode - ? buildTx(R.merge(R.head(arguments), { nonce, ttl, fee, vmVersion, gasPrice }), TX_TYPE.contractCall) + ? buildTx(R.merge(R.head(arguments), { nonce, ttl, fee, abiVersion: ctVersion.abiVersion, gasPrice }), TX_TYPE.contractCall) : await this.api.postContractCall(R.merge(R.head(arguments), { nonce, ttl, fee: parseInt(fee), gas: parseInt(gas), gasPrice, - vmVersion + abiVersion: ctVersion.vmVersion })) return tx } -async function oracleRegisterTx ({ accountId, queryFormat, responseFormat, queryFee, oracleTtl, vmVersion = ORACLE_VM_VERSION }) { +async function oracleRegisterTx ({ accountId, queryFormat, responseFormat, queryFee, oracleTtl, abiVersion }) { + const { abiVersion: abi } = this.getVmVersion(TX_TYPE.oracleRegister, R.head(arguments)) // Calculate fee, get absolute ttl (ttl + height), get account nonce - const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.oracleRegister, { senderId: accountId, ...R.head(arguments), vmVersion }) + const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.oracleRegister, { senderId: accountId, ...R.head(arguments), abiVersion: abi }) // Build transaction using sdk (if nativeMode) or build on `AETERNITY NODE` side const { tx } = this.nativeMode ? buildTx({ accountId, queryFee, - vmVersion, + abiVersion: abi, fee, oracleTtl, nonce, @@ -187,7 +172,7 @@ async function oracleRegisterTx ({ accountId, queryFormat, responseFormat, query : await this.api.postOracleRegister({ accountId, queryFee, - vmVersion, + abiVersion: abi, fee: parseInt(fee), oracleTtl, nonce, @@ -354,6 +339,28 @@ async function channelSnapshotSoloTx ({ channelId, fromId, payload }) { return tx } +/** + * Validated vm/abi version or get default based on transaction type and NODE version + * + * @param {string} txType Type of transaction + * @param {object} vmAbi Object with vm and abi version fields + * @return {object} Object with vm/abi version ({ vmVersion: number, abiVersion: number }) + */ +function getVmVersion (txType, { vmVersion, abiVersion } = {}) { + const isMinerva = semverSatisfies(this.version.split('-')[0], '2.5.0', '3.0.0') + const supported = isMinerva ? VM_ABI_MAP_MINERVA[txType] : VM_ABI_MAP_FORTUNA[txType] + if (!supported) throw new Error('Not supported tx type') + + const ctVersion = { + abiVersion: abiVersion !== undefined ? abiVersion : supported.abiVersion[0], + vmVersion: vmVersion !== undefined ? vmVersion : supported.vmVersion[0] + } + if (supported.vmVersion.length && !R.contains(ctVersion.vmVersion, supported.vmVersion)) throw new Error(`VM VERSION ${ctVersion.vmVersion} do not support by this node. Supported: [${supported.vmVersion}]`) + if (!R.contains(ctVersion.abiVersion, supported.abiVersion)) throw new Error(`ABI VERSION ${ctVersion.abiVersion} do not support by this node. Supported: [${supported.abiVersion}]`) + + return ctVersion +} + /** * Compute the absolute ttl by adding the ttl to the current height of the chain * @@ -447,7 +454,8 @@ const Transaction = Node.compose(Tx, { channelSlashTx, channelSettleTx, channelSnapshotSoloTx, - getAccountNonce + getAccountNonce, + getVmVersion } }) diff --git a/es/utils/semver-satisfies.js b/es/utils/semver-satisfies.js index d4bd0365b0..d9598a8a5a 100644 --- a/es/utils/semver-satisfies.js +++ b/es/utils/semver-satisfies.js @@ -6,6 +6,8 @@ export default function (version, geVersion, ltVersion) { const toNumber = components => components.reverse() .reduce((acc, n, idx) => acc + n * Math.pow(base, idx), 0) - return toNumber(versionComponents) >= toNumber(geComponents) && - toNumber(versionComponents) < toNumber(ltComponents) + const vNumber = toNumber(versionComponents) + const geNumber = toNumber(geComponents) + const ltNumber = toNumber(ltComponents) + return vNumber >= geNumber && vNumber < ltNumber } diff --git a/test/integration/contract.js b/test/integration/contract.js index 9bffe5be67..3388469963 100644 --- a/test/integration/contract.js +++ b/test/integration/contract.js @@ -69,7 +69,7 @@ contract StateContract = const encodedNumberSix = 'cb_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaKNdnK' -plan('10000000000000000') +plan('1000000000000000000000') describe('Contract', function () { configure(this)