Skip to content

Commit

Permalink
Merge pull request #242 from aeternity/release/2.1.0
Browse files Browse the repository at this point in the history
Release 2.1.0
  • Loading branch information
nduchak authored Feb 14, 2019
2 parents cac8f19 + e3d736a commit fc1128e
Show file tree
Hide file tree
Showing 13 changed files with 1,998 additions and 959 deletions.
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1 +1 @@
TAG=v1.4.0
TAG=minerva
21 changes: 20 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,24 @@ All notable changes to this project will be documented in this file. This change
log follows the conventions of [keepachangelog.com](http://keepachangelog.com/).


## [2.1.0]
### Added
- `Minerva` comparability
- Add `Mnemonic` wallet implementation `es/utils/hd-wallet`

### Changed
- Change supported node version range to `1.4.0 <= version < 3.0.0`

### Removed
- none

### Breaking Changes
- none

### Notes and known Issues
- none


## [2.0.0]
### Added
- Add `unpackedTx`, `txType` and `signature` to `validate` transaction function
Expand Down Expand Up @@ -428,4 +446,5 @@ log follows the conventions of [keepachangelog.com](http://keepachangelog.com/).
[1.3.0]: https://github.com/aeternity/aepp-sdk-js/compare/1.2.1...1.3.0
[1.3.1]: https://github.com/aeternity/aepp-sdk-js/compare/1.3.0...1.3.1
[1.3.2]: https://github.com/aeternity/aepp-sdk-js/compare/1.3.1...1.3.2
[1.3.2]: https://github.com/aeternity/aepp-sdk-js/compare/1.3.2...2.0.0
[2.0.0]: https://github.com/aeternity/aepp-sdk-js/compare/1.3.2...2.0.0
[2.1.0]: https://github.com/aeternity/aepp-sdk-js/compare/2.0.0...2.1.0
9 changes: 6 additions & 3 deletions es/ae/contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ async function callStatic (address, abi = 'sophia-address', name, { top, args =
}

// Dry-run
const [{ result: status, callObj, reason }] = (await this.txDryRun([tx], [{ amount: opt.amount, pubKey: await this.address() }], top)).results
const [{ result: status, callObj, reason }] = (await this.txDryRun([tx], [{
amount: opt.amount,
pubKey: await this.address()
}], top)).results

// check response
if (status !== 'ok') throw new Error('Dry run error, ' + reason)
Expand Down Expand Up @@ -130,7 +133,7 @@ async function call (code, abi, address, name, { args = '()', options = {}, call
callData: await this.contractEncodeCall(code, abi, name, args, call)
}))

const {hash, rawTx} = await this.send(tx, opt)
const { hash, rawTx } = await this.send(tx, opt)
const result = await this.getTxInfo(hash)

if (result.returnType === 'ok') {
Expand Down Expand Up @@ -169,7 +172,7 @@ async function deploy (code, abi, { initState = '()', options = {} } = {}) {
ownerId
}))

const {hash, rawTx} = await this.send(tx, opt)
const { hash, rawTx } = await this.send(tx, opt)

return Object.freeze({
owner: ownerId,
Expand Down
28 changes: 15 additions & 13 deletions es/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import * as Crypto from './utils/crypto'
import * as TxBuilder from './tx/builder'
import * as TxBuilderHelper from './tx/builder/helpers'
import HdWallet from './utils/hd-wallet'

import Ae from './ae'
import Chain from './chain'
Expand All @@ -39,25 +40,26 @@ import Channel from './channel'
import Universal from './ae/universal'

export {
Account,
Ae,
Aens,
Aepp,
Contract,
ContractNodeAPI,
ChainNode,
Channel,
Crypto,
Chain,
ChainNode,
ContractNodeAPI,
OracleNodeAPI,
Tx,
Transaction,
Account,
HdWallet,
MemoryAccount,
Aens,
Contract,
Wallet,
Selector,
Universal,
Oracle,
Channel,
OracleNodeAPI,
Selector,
Transaction,
TransactionValidator,
Tx,
TxBuilder,
TxBuilderHelper
TxBuilderHelper,
Universal,
Wallet
}
4 changes: 2 additions & 2 deletions es/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,14 @@ const Node = stampit({
}, Swagger, {
async init ({ forceCompatibility = false }) {
const { nodeRevision: revision, genesisKeyBlockHash: genesisHash, networkId } = await this.api.getStatus()
if (!semver.satisfies(this.version, COMPATIBILITY_RANGE) && !forceCompatibility) throw new Error(`Unsupported node version ${this.version}. Supported: ${COMPATIBILITY_RANGE}`)
if (!semver.satisfies(this.version.split('-')[0], COMPATIBILITY_RANGE) && !forceCompatibility) throw new Error(`Unsupported node version ${this.version}. Supported: ${COMPATIBILITY_RANGE}`)

this.nodeNetworkId = networkId
return Object.assign(this, { revision, genesisHash })
}
})

// String of compatibility range (see https://www.npmjs.com/package/semver#ranges)
export const COMPATIBILITY_RANGE = '>= 1.0.0 < 2.0.0'
export const COMPATIBILITY_RANGE = '>= 1.4.0 < 3.0.0'

export default Node
18 changes: 16 additions & 2 deletions es/tx/builder/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@ const OBJECT_TAG_CONTRACT_CALL_TRANSACTION = 43
const TX_FIELD = (name, type, prefix) => [name, type, prefix]
const TX_SCHEMA_FIELD = (schema, objectId) => [schema, objectId]

// # 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
}

/**
* @constant
* @description Object with transaction types
Expand Down Expand Up @@ -392,7 +406,7 @@ export const BASE_VERIFICATION_SCHEMA = [
ERRORS.insufficientBalanceForAmountFee
),
VERIFICATION_FIELD(
({balance}) => `The account balance ${balance} is not enough to execute the transaction`,
({ balance }) => `The account balance ${balance} is not enough to execute the transaction`,
VALIDATORS.insufficientBalanceForAmount,
ERRORS.insufficientBalanceForAmount
),
Expand All @@ -402,7 +416,7 @@ export const BASE_VERIFICATION_SCHEMA = [
ERRORS.nonceUsed
),
VERIFICATION_FIELD(
({accountNonce}) => `The nonce is technically valid but will not be processed immediately by the node (next valid nonce is ${accountNonce + 1})`,
({ accountNonce }) => `The nonce is technically valid but will not be processed immediately by the node (next valid nonce is ${accountNonce + 1})`,
VALIDATORS.nonceHigh,
ERRORS.nonceHigh
)
Expand Down
34 changes: 25 additions & 9 deletions es/tx/tx.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
*/

import * as R from 'ramda'
import semver from 'semver'

import Tx from './'
import Node from '../node'
Expand All @@ -33,6 +34,10 @@ 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
Expand Down Expand Up @@ -121,39 +126,50 @@ async function nameRevokeTx ({ accountId, nameId }) {
return tx
}

async function contractCreateTx ({ ownerId, code, vmVersion = CONTRACT_VM_VERSION, deposit, amount, gas, gasPrice, callData }) {
// TODO move this to tx-builder
// Get VM_ABI version for minerva
function getContractVmVersion () {
return semver.satisfies(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, callData }) {
// TODO move this to tx-builder
// Get VM_ABI version for minerva
const { splitedVmAbi, contractVmVersion } = getContractVmVersion.bind(this)()
// Calculate fee, get absolute ttl (ttl + height), get account nonce
const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.contractCreate, { vmVersion: CONTRACT_VM_VERSION, senderId: ownerId, ...R.head(arguments) })
const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.contractCreate, { vmVersion: splitedVmAbi, senderId: ownerId, ...R.head(arguments) })

// Build transaction using sdk (if nativeMode) or build on `AETERNITY NODE` side
return this.nativeMode
? {
...buildTx(R.merge(R.head(arguments), { nonce, ttl, fee }), TX_TYPE.contractCreate),
...buildTx(R.merge(R.head(arguments), { nonce, ttl, fee, vmVersion: splitedVmAbi }), TX_TYPE.contractCreate),
contractId: buildContractId(ownerId, nonce)
}
: this.api.postContractCreate(R.merge(R.head(arguments), { nonce, ttl, fee: parseInt(fee), gas: parseInt(gas) }))
: this.api.postContractCreate(R.merge(R.head(arguments), { nonce, ttl, fee: parseInt(fee), gas: parseInt(gas), vmVersion: contractVmVersion, abiVersion: CONTRACT_MINERVA_ABI }))
}

async function contractCallTx ({ callerId, contractId, vmVersion, amount, gas, gasPrice, callData }) {
async function contractCallTx ({ callerId, contractId, vmVersion = CONTRACT_VM_VERSION, amount, gas, gasPrice, callData }) {
// 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) })
const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.contractCall, { vmVersion, senderId: callerId, ...R.head(arguments) })

// 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 }), TX_TYPE.contractCall)
? buildTx(R.merge(R.head(arguments), { nonce, ttl, fee, vmVersion }), TX_TYPE.contractCall)
: await this.api.postContractCall(R.merge(R.head(arguments), {
nonce,
ttl,
fee: parseInt(fee),
gas: parseInt(gas)
gas: parseInt(gas),
vmVersion
}))

return tx
}

async function oracleRegisterTx ({ accountId, queryFormat, responseFormat, queryFee, oracleTtl, vmVersion = ORACLE_VM_VERSION }) {
// Calculate fee, get absolute ttl (ttl + height), get account nonce
const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.oracleRegister, { vmVersion: ORACLE_VM_VERSION, senderId: accountId, ...R.head(arguments) })
const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.oracleRegister, { vmVersion, senderId: accountId, ...R.head(arguments) })
// Build transaction using sdk (if nativeMode) or build on `AETERNITY NODE` side
const { tx } = this.nativeMode
? buildTx({
Expand Down
103 changes: 103 additions & 0 deletions es/utils/hd-wallet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import nacl from 'tweetnacl'
import { full as hmac } from 'tweetnacl-auth'
import { fromString } from 'bip32-path'
import { validateMnemonic, mnemonicToSeed, generateMnemonic as genMnemonic } from '@aeternity/bip39'
import { decryptKey, encodeBase58Check, encryptKey } from './crypto'

const ED25519_CURVE = Buffer.from('ed25519 seed')
const HARDENED_OFFSET = 0x80000000

const toHex = (buffer) => Buffer.from(buffer).toString('hex')

export function derivePathFromKey (path, key) {
const segments = path === '' ? [] : fromString(path).toPathArray()
segments.forEach((segment, i) => {
if (segment < HARDENED_OFFSET) {
throw new Error(`Segment #${i + 1} is not hardened`)
}
})

return segments.reduce((parentKey, segment) => deriveChild(parentKey, segment), key)
}

export function derivePathFromSeed (path, seed) {
if (!['m', 'm/'].includes(path.slice(0, 2))) {
throw new Error('Invalid path')
}
const masterKey = getMasterKeyFromSeed(seed)
return derivePathFromKey(path.slice(2), masterKey)
}

function formatAccount (keys) {
const { secretKey, publicKey } = keys
return {
secretKey: toHex(secretKey),
publicKey: `ak_${encodeBase58Check(publicKey)}`
}
}

export function getKeyPair (secretKey) {
return nacl.sign.keyPair.fromSeed(secretKey)
}

export function generateMnemonic () {
return genMnemonic()
}

export function getMasterKeyFromSeed (seed) {
const I = hmac(seed, ED25519_CURVE)
const IL = I.slice(0, 32)
const IR = I.slice(32)
return {
secretKey: IL,
chainCode: IR
}
}

export function deriveChild ({ secretKey, chainCode }, index) {
if (index < HARDENED_OFFSET) {
throw new Error(`Child index #${index} is not supported`)
}
const indexBuffer = Buffer.allocUnsafe(4)
indexBuffer.writeUInt32BE(index, 0)

const data = Buffer.concat([Buffer.alloc(1, 0), secretKey, indexBuffer])

const I = hmac(data, chainCode)
const IL = I.slice(0, 32)
const IR = I.slice(32)
return {
secretKey: IL,
chainCode: IR
}
}

export function generateSaveHDWallet (mnemonic, password) {
if (!validateMnemonic(mnemonic)) {
throw new Error('Invalid mnemonic')
}
const seed = mnemonicToSeed(mnemonic)
const walletKey = derivePathFromSeed('m/44h/457h', seed)
return {
secretKey: toHex(encryptKey(password, walletKey.secretKey)),
chainCode: toHex(encryptKey(password, walletKey.chainCode))
}
}

export function getSaveHDWalletAccounts (saveHDWallet, password, accountCount) {
const walletKey = {
secretKey: decryptKey(password, Buffer.from(saveHDWallet.secretKey, 'hex')),
chainCode: decryptKey(password, Buffer.from(saveHDWallet.chainCode, 'hex'))
}
return (new Array(accountCount)).fill()
.map((_, idx) =>
formatAccount(getKeyPair(derivePathFromKey(`${idx}h/0h/0h`, walletKey).secretKey)))
}

export default {
getSaveHDWalletAccounts,
generateSaveHDWallet,
generateMnemonic,
deriveChild,
getMasterKeyFromSeed
}
Loading

0 comments on commit fc1128e

Please sign in to comment.