Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Fortuna): Node 3.0.0 compatibility #397

Merged
merged 11 commits into from
May 16, 2019
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
TAG=v2.3.0
TAG=master
COMPILER_TAG=v2.1.0

1 change: 0 additions & 1 deletion es/ae/contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 1 addition & 2 deletions es/ae/oracle.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export async function pollForQueryResponse (oracleId, queryId, { attempts = 20,
* @return {Promise<Object>} 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, {
Expand Down Expand Up @@ -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 },
Expand Down
2 changes: 1 addition & 1 deletion es/chain/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down
4 changes: 2 additions & 2 deletions es/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.5.0'
nduchak marked this conversation as resolved.
Show resolved Hide resolved
const NODE_LT_VERSION = '4.0.0'

export default Node
8 changes: 8 additions & 0 deletions es/tx/builder/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down
53 changes: 34 additions & 19 deletions es/tx/builder/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, {})

/**
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -219,7 +232,8 @@ export const FIELD_TYPES = {
callStack: 'callStack',
proofOfInclusion: 'proofOfInclusion',
mptree: 'mptree',
callReturnType: 'callReturnType'
callReturnType: 'callReturnType',
ctVersion: 'ctVersion'
}

// FEE CALCULATION
Expand Down Expand Up @@ -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 = [
Expand Down Expand Up @@ -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),
Expand All @@ -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),
Expand Down Expand Up @@ -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 = [
Expand Down
72 changes: 40 additions & 32 deletions es/tx/tx.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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
*
Expand Down Expand Up @@ -447,7 +454,8 @@ const Transaction = Node.compose(Tx, {
channelSlashTx,
channelSettleTx,
channelSnapshotSoloTx,
getAccountNonce
getAccountNonce,
getVmVersion
}
})

Expand Down
6 changes: 4 additions & 2 deletions es/utils/semver-satisfies.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
2 changes: 1 addition & 1 deletion test/integration/contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ contract StateContract =

const encodedNumberSix = 'cb_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaKNdnK'

plan('10000000000000000')
plan('1000000000000000000000')

describe('Contract', function () {
configure(this)
Expand Down