packages/oo7-substrate/src/bonds.js
- const { camel } = require('change-case');
- const { Bond, TransformBond, TimeBond } = require('oo7')
- const { nodeService } = require('./nodeService')
- const { SubscriptionBond } = require('./subscriptionBond')
- const { BlockNumber, Hash } = require('./types');
- const { decode, encode } = require('./codec');
- const { stringToBytes, hexToBytes, bytesToHex, toLE } = require('./utils')
- const { StorageBond } = require('./storageBond')
- const { setMetadata } = require('./metadata')
-
- let chain = (() => {
- let head = new SubscriptionBond('chain_newHead').subscriptable()
- let finalisedHead = new SubscriptionBond('chain_finalisedHead').subscriptable()
- let height = head.map(h => new BlockNumber(h.number))
- let finalisedHeight = finalisedHead.map(h => new BlockNumber(h.number))
- let lag = Bond.all([height, finalisedHeight]).map(([h, f]) => new BlockNumber(h - f))
- let header = hashBond => new TransformBond(hash => nodeService().request('chain_getHeader', [hash]), [hashBond]).subscriptable()
- let block = hashBond => new TransformBond(hash => nodeService().request('chain_getBlock', [hash]), [hashBond]).subscriptable()
- let hash = numberBond => new TransformBond(number => nodeService().request('chain_getBlockHash', [number]), [numberBond])
- return { head, finalisedHead, height, finalisedHeight, header, hash, block, lag }
- })()
-
- let system = (() => {
- let time = new TimeBond
- let name = new TransformBond(() => nodeService().request('system_name')).subscriptable()
- let version = new TransformBond(() => nodeService().request('system_version')).subscriptable()
- let chain = new TransformBond(() => nodeService().request('system_chain')).subscriptable()
- let properties = new TransformBond(() => nodeService().request('system_properties')).subscriptable()
- let health = new TransformBond(() => nodeService().request('system_health'), [], [time]).subscriptable()
- let peers = new TransformBond(() => nodeService().request('system_peers'), [], [time]).subscriptable()
- let pendingTransactions = new TransformBond(() => nodeService().request('author_pendingExtrinsics')).subscriptable()
- return { name, version, chain, properties, pendingTransactions, health, peers }
- })()
-
- let version = (new SubscriptionBond('state_runtimeVersion', [], r => {
- let apis = {}
- r.apis.forEach(([id, version]) => {
- if (typeof id !== 'string') {
- id = String.fromCharCode.apply(null, id)
- }
- apis[id] = version
- })
- return {
- authoringVersion: r.authoringVersion,
- implName: r.implName,
- implVersion: r.implVersion,
- specName: r.specName,
- specVersion: r.specVersion,
- apis
- }
- })).subscriptable()
-
- let runtime = {
- version,
- metadata: new Bond,
- core: (() => {
- let authorityCount = new SubscriptionBond('state_storage', [['0x' + bytesToHex(stringToBytes(':auth:len'))]], r => decode(hexToBytes(r.changes[0][1]), 'u32'))
- let authorities = authorityCount.map(
- n => [...Array(n)].map((_, i) =>
- new SubscriptionBond('state_storage',
- [[ '0x' + bytesToHex(stringToBytes(":auth:")) + bytesToHex(toLE(i, 4)) ]],
- r => decode(hexToBytes(r.changes[0][1]), 'AccountId')
- )
- ), 2)
- let code = new SubscriptionBond('state_storage', [['0x' + bytesToHex(stringToBytes(':code'))]], r => hexToBytes(r.changes[0][1]))
- let codeHash = new TransformBond(() => nodeService().request('state_getStorageHash', ['0x' + bytesToHex(stringToBytes(":code"))]).then(hexToBytes), [], [version])
- let codeSize = new TransformBond(() => nodeService().request('state_getStorageSize', ['0x' + bytesToHex(stringToBytes(":code"))]), [], [version])
- let heapPages = new SubscriptionBond('state_storage', [['0x' + bytesToHex(stringToBytes(':heappages'))]], r => decode(hexToBytes(r.changes[0][1]), 'u64'))
- return { authorityCount, authorities, code, codeHash, codeSize, version, heapPages }
- })()
- }
-
- let calls = {}
-
- class RuntimeUp extends Bond {
- initialise() {
- let that = this
- initRuntime(() => that.trigger(true))
- }
- }
- let runtimeUp = new RuntimeUp
-
- let onRuntimeInit = []
-
- function initialiseFromMetadata (md) {
- console.log("initialiseFromMetadata", md)
- setMetadata(md)
- let callIndex = 0;
- md.modules.forEach((m) => {
- let o = {}
- let c = {}
- if (m.storage) {
- let storePrefix = m.prefix
- m.storage.forEach(item => {
- switch (item.type.option) {
- case 'Plain': {
- o[camel(item.name)] = new StorageBond(`${storePrefix} ${item.name}`, item.type.value, [], item.default)
- break
- }
- case 'Map': {
- let keyType = item.type.value.key
- let valueType = item.type.value.value
- o[camel(item.name)] = keyBond => new TransformBond(
- key => new StorageBond(`${storePrefix} ${item.name}`, valueType, encode(key, keyType), item.default),
- [keyBond]
- ).subscriptable()
- break
- }
- }
- })
- }
- if (m.calls) {
- let thisCallIndex = callIndex
- callIndex++
- m.calls.forEach((item, id) => {
- if (item.arguments.length > 0 && item.arguments[0].name == 'origin' && item.arguments[0].type == 'Origin') {
- item.arguments = item.arguments.slice(1)
- }
- c[camel(item.name)] = function (...bondArgs) {
- if (bondArgs.length != item.arguments.length) {
- throw `Invalid number of argments (${bondArgs.length} given, ${item.arguments.length} expected)`
- }
- return new TransformBond(args => {
- let encoded_args = encode(args, item.arguments.map(x => x.type))
- let res = new Uint8Array([thisCallIndex, id, ...encoded_args]);
- console.log(`Encoding call ${m.name}.${item.name} (${thisCallIndex}.${id}): ${bytesToHex(res)}`)
- return res
- }, [bondArgs], [], 3, 3, undefined, true)
- }
- c[camel(item.name)].help = item.arguments.map(a => a.name)
- })
- }
- runtime[camel(m.name)] = o
- calls[camel(m.name)] = c
- })
- md.modules.forEach(m => {
- if (m.storage) {
- try {
- require(`./srml/${m.name}`).augment(runtime, chain)
- }
- catch (e) {
- if (!e.toString().startsWith('Error: Cannot find module')) {
- throw e
- }
- }
- }
- })
- if (onRuntimeInit !== null) {
- onRuntimeInit.forEach(f => { if (f) f() })
- onRuntimeInit = null
- }
-
- runtime.metadata.trigger(md)
- }
-
- function decodeMetadata(bytes) {
- let input = { data: bytes }
- let head = decode(input, 'MetadataHead')
- if (head.magic === 0x6174656d) {
- if (head.version == 1) {
- return decode(input, 'MetadataBody')
- } else {
- throw `Metadata version ${head.version} not supported`
- }
- } else {
- let md = decode(bytes, 'Legacy_RuntimeMetadata')
- md.modules = md.modules.map(m => {
- m.name = m.prefix
- m.prefix = m.storage ? m.storage.prefix : null
- m.storage = m.storage ? m.storage.items : null
- m.calls = m.module && m.module.call ? m.module.call.functions : null
- return m
- })
- return md
- }
- }
-
- function initRuntime (callback = null) {
- if (onRuntimeInit instanceof Array) {
- onRuntimeInit.push(callback)
- version.tie(() => {
- // console.info("Initialising runtime")
- nodeService().request('state_getMetadata')
- .then(blob => decodeMetadata(hexToBytes(blob)))
- .then(initialiseFromMetadata)
- })
- } else {
- // already inited runtime
- if (callback) {
- callback()
- }
- }
- }
-
- function runtimePromise() {
- return new Promise((resolve, reject) => initRuntime(() => resolve(runtime)))
- }
-
- function callsPromise() {
- return new Promise((resolve, reject) => initRuntime(() => resolve(calls)))
- }
-
- module.exports = { initRuntime, runtimeUp, runtimePromise, callsPromise, runtime, calls, chain, system }