Home Reference Source

packages/oo7-substrate/src/bonds.js

  1. const { camel } = require('change-case');
  2. const { Bond, TransformBond, TimeBond } = require('oo7')
  3. const { nodeService } = require('./nodeService')
  4. const { SubscriptionBond } = require('./subscriptionBond')
  5. const { BlockNumber, Hash } = require('./types');
  6. const { decode, encode } = require('./codec');
  7. const { stringToBytes, hexToBytes, bytesToHex, toLE } = require('./utils')
  8. const { StorageBond } = require('./storageBond')
  9. const { setMetadata } = require('./metadata')
  10.  
  11. let chain = (() => {
  12. let head = new SubscriptionBond('chain_newHead').subscriptable()
  13. let finalisedHead = new SubscriptionBond('chain_finalisedHead').subscriptable()
  14. let height = head.map(h => new BlockNumber(h.number))
  15. let finalisedHeight = finalisedHead.map(h => new BlockNumber(h.number))
  16. let lag = Bond.all([height, finalisedHeight]).map(([h, f]) => new BlockNumber(h - f))
  17. let header = hashBond => new TransformBond(hash => nodeService().request('chain_getHeader', [hash]), [hashBond]).subscriptable()
  18. let block = hashBond => new TransformBond(hash => nodeService().request('chain_getBlock', [hash]), [hashBond]).subscriptable()
  19. let hash = numberBond => new TransformBond(number => nodeService().request('chain_getBlockHash', [number]), [numberBond])
  20. return { head, finalisedHead, height, finalisedHeight, header, hash, block, lag }
  21. })()
  22.  
  23. let system = (() => {
  24. let time = new TimeBond
  25. let name = new TransformBond(() => nodeService().request('system_name')).subscriptable()
  26. let version = new TransformBond(() => nodeService().request('system_version')).subscriptable()
  27. let chain = new TransformBond(() => nodeService().request('system_chain')).subscriptable()
  28. let properties = new TransformBond(() => nodeService().request('system_properties')).subscriptable()
  29. let health = new TransformBond(() => nodeService().request('system_health'), [], [time]).subscriptable()
  30. let peers = new TransformBond(() => nodeService().request('system_peers'), [], [time]).subscriptable()
  31. let pendingTransactions = new TransformBond(() => nodeService().request('author_pendingExtrinsics')).subscriptable()
  32. return { name, version, chain, properties, pendingTransactions, health, peers }
  33. })()
  34.  
  35. let version = (new SubscriptionBond('state_runtimeVersion', [], r => {
  36. let apis = {}
  37. r.apis.forEach(([id, version]) => {
  38. if (typeof id !== 'string') {
  39. id = String.fromCharCode.apply(null, id)
  40. }
  41. apis[id] = version
  42. })
  43. return {
  44. authoringVersion: r.authoringVersion,
  45. implName: r.implName,
  46. implVersion: r.implVersion,
  47. specName: r.specName,
  48. specVersion: r.specVersion,
  49. apis
  50. }
  51. })).subscriptable()
  52.  
  53. let runtime = {
  54. version,
  55. metadata: new Bond,
  56. core: (() => {
  57. let authorityCount = new SubscriptionBond('state_storage', [['0x' + bytesToHex(stringToBytes(':auth:len'))]], r => decode(hexToBytes(r.changes[0][1]), 'u32'))
  58. let authorities = authorityCount.map(
  59. n => [...Array(n)].map((_, i) =>
  60. new SubscriptionBond('state_storage',
  61. [[ '0x' + bytesToHex(stringToBytes(":auth:")) + bytesToHex(toLE(i, 4)) ]],
  62. r => decode(hexToBytes(r.changes[0][1]), 'AccountId')
  63. )
  64. ), 2)
  65. let code = new SubscriptionBond('state_storage', [['0x' + bytesToHex(stringToBytes(':code'))]], r => hexToBytes(r.changes[0][1]))
  66. let codeHash = new TransformBond(() => nodeService().request('state_getStorageHash', ['0x' + bytesToHex(stringToBytes(":code"))]).then(hexToBytes), [], [version])
  67. let codeSize = new TransformBond(() => nodeService().request('state_getStorageSize', ['0x' + bytesToHex(stringToBytes(":code"))]), [], [version])
  68. let heapPages = new SubscriptionBond('state_storage', [['0x' + bytesToHex(stringToBytes(':heappages'))]], r => decode(hexToBytes(r.changes[0][1]), 'u64'))
  69. return { authorityCount, authorities, code, codeHash, codeSize, version, heapPages }
  70. })()
  71. }
  72.  
  73. let calls = {}
  74.  
  75. class RuntimeUp extends Bond {
  76. initialise() {
  77. let that = this
  78. initRuntime(() => that.trigger(true))
  79. }
  80. }
  81. let runtimeUp = new RuntimeUp
  82.  
  83. let onRuntimeInit = []
  84.  
  85. function initialiseFromMetadata (md) {
  86. console.log("initialiseFromMetadata", md)
  87. setMetadata(md)
  88. let callIndex = 0;
  89. md.modules.forEach((m) => {
  90. let o = {}
  91. let c = {}
  92. if (m.storage) {
  93. let storePrefix = m.prefix
  94. m.storage.forEach(item => {
  95. switch (item.type.option) {
  96. case 'Plain': {
  97. o[camel(item.name)] = new StorageBond(`${storePrefix} ${item.name}`, item.type.value, [], item.default)
  98. break
  99. }
  100. case 'Map': {
  101. let keyType = item.type.value.key
  102. let valueType = item.type.value.value
  103. o[camel(item.name)] = keyBond => new TransformBond(
  104. key => new StorageBond(`${storePrefix} ${item.name}`, valueType, encode(key, keyType), item.default),
  105. [keyBond]
  106. ).subscriptable()
  107. break
  108. }
  109. }
  110. })
  111. }
  112. if (m.calls) {
  113. let thisCallIndex = callIndex
  114. callIndex++
  115. m.calls.forEach((item, id) => {
  116. if (item.arguments.length > 0 && item.arguments[0].name == 'origin' && item.arguments[0].type == 'Origin') {
  117. item.arguments = item.arguments.slice(1)
  118. }
  119. c[camel(item.name)] = function (...bondArgs) {
  120. if (bondArgs.length != item.arguments.length) {
  121. throw `Invalid number of argments (${bondArgs.length} given, ${item.arguments.length} expected)`
  122. }
  123. return new TransformBond(args => {
  124. let encoded_args = encode(args, item.arguments.map(x => x.type))
  125. let res = new Uint8Array([thisCallIndex, id, ...encoded_args]);
  126. console.log(`Encoding call ${m.name}.${item.name} (${thisCallIndex}.${id}): ${bytesToHex(res)}`)
  127. return res
  128. }, [bondArgs], [], 3, 3, undefined, true)
  129. }
  130. c[camel(item.name)].help = item.arguments.map(a => a.name)
  131. })
  132. }
  133. runtime[camel(m.name)] = o
  134. calls[camel(m.name)] = c
  135. })
  136. md.modules.forEach(m => {
  137. if (m.storage) {
  138. try {
  139. require(`./srml/${m.name}`).augment(runtime, chain)
  140. }
  141. catch (e) {
  142. if (!e.toString().startsWith('Error: Cannot find module')) {
  143. throw e
  144. }
  145. }
  146. }
  147. })
  148. if (onRuntimeInit !== null) {
  149. onRuntimeInit.forEach(f => { if (f) f() })
  150. onRuntimeInit = null
  151. }
  152.  
  153. runtime.metadata.trigger(md)
  154. }
  155.  
  156. function decodeMetadata(bytes) {
  157. let input = { data: bytes }
  158. let head = decode(input, 'MetadataHead')
  159. if (head.magic === 0x6174656d) {
  160. if (head.version == 1) {
  161. return decode(input, 'MetadataBody')
  162. } else {
  163. throw `Metadata version ${head.version} not supported`
  164. }
  165. } else {
  166. let md = decode(bytes, 'Legacy_RuntimeMetadata')
  167. md.modules = md.modules.map(m => {
  168. m.name = m.prefix
  169. m.prefix = m.storage ? m.storage.prefix : null
  170. m.storage = m.storage ? m.storage.items : null
  171. m.calls = m.module && m.module.call ? m.module.call.functions : null
  172. return m
  173. })
  174. return md
  175. }
  176. }
  177.  
  178. function initRuntime (callback = null) {
  179. if (onRuntimeInit instanceof Array) {
  180. onRuntimeInit.push(callback)
  181. version.tie(() => {
  182. // console.info("Initialising runtime")
  183. nodeService().request('state_getMetadata')
  184. .then(blob => decodeMetadata(hexToBytes(blob)))
  185. .then(initialiseFromMetadata)
  186. })
  187. } else {
  188. // already inited runtime
  189. if (callback) {
  190. callback()
  191. }
  192. }
  193. }
  194.  
  195. function runtimePromise() {
  196. return new Promise((resolve, reject) => initRuntime(() => resolve(runtime)))
  197. }
  198.  
  199. function callsPromise() {
  200. return new Promise((resolve, reject) => initRuntime(() => resolve(calls)))
  201. }
  202.  
  203. module.exports = { initRuntime, runtimeUp, runtimePromise, callsPromise, runtime, calls, chain, system }